Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .abcd/development/brief/04-surfaces/04-launch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
The shipped verb surface is the `--dry-run` flag on `abcd launch` — a read-only preview of the bundle and gates — plus the `abcd launch ship` subcommand, which cuts the release (the RELEASE CUT slice only; see the ship bullet below). Bare `abcd launch` never mutates state: it refuses (exit 1) with a hint to pass `--dry-run`; a bare-as-status render is a design target, unshipped. The sub-verb design:

- **`/abcd:launch ship`** — **partly shipped: the RELEASE CUT only** (itd-73 derived versioning + itd-67's changelog slice). `abcd launch ship` derives the version and the record set from what shipped since the newest tag, runs the surface guardrail, and — with `--changelog-json` — validates the host-composed prose against the record set (the completeness bijection) and writes the dated `CHANGELOG.md` heading `.github/workflows/auto-release.yml` turns into a tag; `commands/abcd/launch.md` carries the emit → compose → ingest orchestration and the `release-changelog-composer` agent it dispatches. The `Ship` engine is wired: `abcd launch ship` is a live subcommand, and `--payload-dir <dir>` stages the versioned release payload — the derived version stamped into the payload's `plugin.json`/`marketplace.json` and lockstep-proved before return. Commit, tag, and publish stay a design target (itd-65 gate suite + itd-72 publishing): the verb neither commits, tags, nor publishes. The full-cut design: cut a curated release artefact from the one repo: run pre-flight gates, filter the artefact (default-deny, `.abcd/**` excluded by packaging), stamp the version, and on a `v*` tag publish a GitHub Release ([adr-28](../../decisions/adrs/0028-single-repo-curated-release.md)). The flow described in §§ 1–6 below is this sub-verb's behaviour. Flag-shaped modifiers `--allow-dirty` and `--allow-doc-warnings` belong to this sub-verb's design; the shipped `ship` accepts `--changelog-json` and `--payload-dir` (plus the global `--json`), and bare `abcd launch` accepts only `--dry-run` and the global `--json`. There is no version flag — the version is derived, never authored ([adr-31](../../decisions/adrs/0031-derived-versioning-from-intents.md), see [§ 3](#3-versioning--marketplace)).
- **`/abcd:launch scaffold`** — **shipped** (itd-93, spc-14). `abcd launch scaffold` writes the changelog-driven release machinery — `.github/workflows/release.yml`, `.github/workflows/auto-release.yml`, and the adr-37 release runbook (`.abcd/development/release-gate/README.md`) — into a managed repo that lacks it, wired to the repo's own default branch and Go version, `GITHUB_TOKEN`-only and injection-safe. The workflows ship from a single embedded template that abcd-cli's own release workflows are regenerated from (self-scaffold parity, a byte-exact test); the scaffolded `release.yml` carries a `workflow_dispatch` **rehearsal** that arms the full gate against a simulated changelog roll and reviewed-content commit and publishes nothing, so a green rehearsal is the runbook precondition for the first real release. A bare repo with no semantic detector degrades cleanly to the deterministic gates and a generic build. It is idempotent and fail-safe: a re-run on current machinery is a no-op (exit 0), a hand-edited file is refused (exit 1) rather than clobbered unless `--confirm` is passed, and a structural fault exits 2. `commands/abcd/launch.md` carries the flow. Accepts `--confirm` plus the global `--json`.
- **`/abcd:launch dry-run`** — shipped as the `--dry-run` flag (the plugin command `commands/abcd/launch.md` maps the `dry-run` of its `[dry-run | ship]` argument hint onto `abcd launch --dry-run`; the binary has no `dry-run` subcommand). **Report-only preview, always exit-0** (a preview never blocks). It runs the parts of the pre-flight suite that exist today: as of spc-64 (predecessor store) the **secret + PII scan gate** (the native scanners, see [§ 1](#1-pre-flight-gates)) runs for real in report-only mode and prints what it *would* refuse on (a finding, or a fail-closed reason such as "scanner unavailable"); the **installability smoke** runs for real at its light tier (see [§ 1](#1-pre-flight-gates)); the **manifest lockstep check** runs for real at its `dev` polarity over the working tree — the polarity adr-19 requires the committed manifests to satisfy (no version key), see [§ 3](#3-versioning--marketplace) — reports its result and folds any drift or an unreadable version-location contract into what it *would* refuse on; the remaining gates (marker-block, documentation-auditor) are the gate-suite intent's (itd-65): `--dry-run --json` reports each as `{"status": "not_implemented", "detail": "Phase-5 deferred"}`, while the plain-text `--dry-run` render omits the gate list entirely (it prints only version, files bundled, scan hardfails, and would-publish, plus a would-refuse-on line when there is a finding). It also produces the would-be artefact manifest, without writing the release artefact. dry-run is **not** "ship minus publish": running the *full* gate suite and **hard-failing** on a finding (exit non-zero) is the full `ship` verb's behaviour (itd-65 + itd-72), not dry-run's.

## 1. Pre-flight gates
Expand Down
13 changes: 13 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,19 @@
}
]
},
{
"path": "abcd launch scaffold",
"hidden": false,
"flags": [
{
"name": "confirm",
"shorthand": "",
"type": "bool",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd launch ship",
"hidden": false,
Expand Down
1 change: 1 addition & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -860,3 +860,4 @@ parallel-agent merge contention bites.
- 2026-07-27 (salvage): the four 2026-07-13/14 ideation-session entries above are appended out of chronological order — recovered from an unmerged ideation branch during branch cleanup, together with their research/plan docs; the /abcd:ideate seed from that session is re-recorded as itd-104 (its branch-local iss-93 capture id had collided with main's iss-93 and is retired unused).
- 2026-07-27 — DEFERRED the row-has-footnote structural docs-lint rule (spc-15 out-of-scope): no existing rule shape covers table-row-to-footnote structure, so it would need a bespoke check; the grill left it optional and the citation-gate intent (itd-101) is now its natural home — implement it there or not at all. Recorded per spc-15's deferral-is-recorded clause; surfaced by the itd-100 fidelity audit's gap check.
- 2026-07-27 — ADOPTED the canonical tagline: "A host-agnostic configuration layer for intent-driven development." Canonical identity home is the brief product chapter (01-product README, Identity section: title / tagline / pitch); README strapline, plugin manifest description, and AGENTS.md opening render from it. README's former strapline ("An opinionated, intent-driven development framework for product thinkers") is retired as a surface line; the product-thinker framing lives on in the README body. Rejected wordings: "for agent harnesses" (object shift, host/harness redundancy, plural over-promise), "over any agent harness" (drops the domain). Resolves iss-143; itd-102 generalises the drift check for managed repos.
- 2026-07-27 — itd-93 parity tension resolved: Branch A. The scaffold template is the single source for release.yml/auto-release.yml; abcd-cli's live workflows are regenerated from it; the only sanctioned live diff is the additive workflow_dispatch rehearsal (trigger + non-publishing dry-run job). Rejected: substitution-gating the rehearsal off for abcd (weakens the parity guarantee on the one novel component).
105 changes: 105 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ on:
required: false
type: string
default: ''
# Manual rehearsal. A workflow_dispatch runs ONLY the `rehearsal` job below and
# nothing that publishes: the publish job (`release`) is gated off this event,
# and the rehearsal arms the full gate against a simulated changelog roll and
# reviewed-content commit, proves the gate admits it, and creates no tag,
# Release, or attestation. Run it green once before the first real release (see
# the release-gate runbook) — it closes the private->public activation gap that
# otherwise surfaces only when the gate first meets a real tag.
workflow_dispatch:

# The released tag name. workflow_call: inputs.tag (the tag auto-release just
# made). push-tag: inputs is empty, so this falls through to github.ref_name
Expand Down Expand Up @@ -128,6 +136,10 @@ jobs:
# artefacts match the code that passed — even if the tag were re-pointed since.
release:
needs: verify
# The publish path never runs on a manual rehearsal (workflow_dispatch): that
# event runs the `rehearsal` job, which publishes nothing. The tag-push and
# workflow_call (auto-release) entry points are unaffected.
if: github.event_name != 'workflow_dispatch'
timeout-minutes: 20
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -343,3 +355,96 @@ jobs:
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# The manual rehearsal (workflow_dispatch). It arms the full gate against a
# SIMULATED changelog roll and reviewed-content commit — reproducing the
# two-commit release-branch shape and the auto-release merge — proves the gate
# resolves and admits, and PUBLISHES NOTHING: it holds contents: read only (so
# it cannot create a tag, Release, or attestation even by mistake), pushes
# nothing, and every mutation stays in the runner's throwaway checkout. A green
# rehearsal is the runbook precondition for the first real release.
rehearsal:
if: github.event_name == 'workflow_dispatch'
timeout-minutes: 15
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out (full history)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Full history so the content-commit resolution below can walk parents,
# exactly as release.yml's semantic gate does.
fetch-depth: 0
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: '1.25'
cache: false

- name: Simulate a changelog roll and the two-commit release branch
# No ${{ }} interpolation (injection-safe): the script writes only the
# runner's throwaway checkout and $GITHUB_ENV. Nothing here is pushed.
run: |
set -euo pipefail
git config user.name 'rehearsal[bot]'
git config user.email 'rehearsal@localhost'
base="$(git rev-parse --verify HEAD)"
date="$(date -u +%Y-%m-%d)"
# Content commit on a scratch branch: simulate rolling [Unreleased] into a
# dated heading — the reviewed content commit a real release names.
git switch -q -c rehearsal-sim
if grep -q '^## \[Unreleased\]' CHANGELOG.md 2>/dev/null; then
awk -v d="$date" '1; /^## \[Unreleased\]/ && !done {print ""; print "## [0.0.0-rehearsal] - " d; done=1}' CHANGELOG.md > CHANGELOG.rehearsal
mv CHANGELOG.rehearsal CHANGELOG.md
else
printf '\n## [0.0.0-rehearsal] - %s\n' "$date" >> CHANGELOG.md
fi
git add -A
git commit -q -m 'rehearsal: simulated changelog roll (content commit)'
content="$(git rev-parse --verify HEAD)"
# Receipts commit: names the content commit and sits one commit later —
# the two-commit shape that keeps the receipt-vs-tag self-reference from
# ever blocking the release.
mkdir -p ".abcd/work/reviews/$content"
printf '{"rehearsal":true,"content":"%s"}\n' "$content" > ".abcd/work/reviews/$content/rehearsal.json"
git add -A
git commit -q -m 'rehearsal: simulated receipts commit'
# Merge into the base, reproducing the auto-release merge whose second
# parent is the receipts commit (so HEAD^2^ resolves the content commit).
git switch -q -c rehearsal-merge "$base"
git merge -q --no-ff -m 'rehearsal: merge simulated release' rehearsal-sim
echo "REHEARSAL_CONTENT_SHA=$content" >> "$GITHUB_ENV"

- name: Resolve the reviewed content commit (release.yml's derivation)
# Mirrors release.yml's resolve step exactly: HEAD^2^ on the merge path,
# HEAD^ on a direct tag. Asserts the resolution lands on the simulated
# content commit and is an ancestor of the released commit.
run: |
set -euo pipefail
released="$(git rev-parse --verify HEAD)"
if git rev-parse -q --verify "HEAD^2" >/dev/null 2>&1; then
content="$(git rev-parse --verify "HEAD^2^")"
else
content="$(git rev-parse --verify "HEAD^")"
fi
test -n "$content"
git merge-base --is-ancestor "$content" "$released"
test "$content" = "${REHEARSAL_CONTENT_SHA}"
echo "rehearsal: the gate would arm against content commit $content (present in the released tree)"

- name: Arm the deterministic record gate against the simulated tree
# The deterministic half of the gate must pass on the simulated roll. The
# semantic half (record-lint --release-gate) activates with the real gate on
# the public flip; here the resolution above proves it would arm correctly.
run: go run ./cmd/record-lint

- name: Prove the gate admits and nothing was published
run: |
set -euo pipefail
# Build the simulated tree — the deterministic leg the gate requires.
go build ./...
echo "rehearsal: full gate armed against the simulated roll; the gate admits."
echo "rehearsal: nothing published — no tag, Release, or attestation was created (contents: read)."
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ called out in a **Breaking** section.

### Added

- **`abcd launch scaffold` — the changelog-driven release-gate scaffolder**
(itd-93, spc-14). Writes the fixed release machinery into a managed repo that
lacks it: `.github/workflows/release.yml` (verify → build → publish, the verify
gate armed against the reviewed **content** commit so the first public release
cannot hit the receipt-vs-tag self-reference), `.github/workflows/auto-release.yml`
(newest dated CHANGELOG heading → tag that commit → call `release.yml`), and the
adr-37 runbook — wired to the repo's own default branch and Go version,
`GITHUB_TOKEN`-only and injection-safe. The workflows ship from a **single
embedded template** that abcd-cli's own release workflows are regenerated from
(self-scaffold parity, proven by a byte-exact test), so every abcd release
exercises the exact machinery a managed repo receives. The scaffolded
`release.yml` carries a `workflow_dispatch` **rehearsal** that arms the full gate
against a simulated changelog roll and reviewed-content commit and publishes
nothing — a green rehearsal is the runbook precondition for the first real
release. A bare repo with no semantic detector degrades cleanly to the
deterministic gates and a generic build. The verb is idempotent and fail-safe: a
re-run on current machinery is a no-op, a hand-edited file is refused rather than
clobbered (unless `--confirm`), and it refuses rather than half-writing.
- **A public terminology crosswalk at `docs/reference/terminology.md`** (itd-100).
One reference page maps 26 established agentic-AI terms — protocols, the core
loop, context, safety, governance, operations — to abcd's position on each:
Expand Down
43 changes: 41 additions & 2 deletions commands/abcd/launch.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: launch
description: Preview the public launch — the file bundle, the secret/PII scan, and the release gates — in dry-run mode, and cut a release by deriving its version and composing its changelog. The preview performs zero writes; `ship` writes the dated CHANGELOG heading and never publishes.
argument-hint: "[dry-run | ship]"
description: Preview the public launch — the file bundle, the secret/PII scan, and the release gates — in dry-run mode, cut a release by deriving its version and composing its changelog, and scaffold the changelog-driven release gate into a managed repo. The preview performs zero writes; `ship` writes the dated CHANGELOG heading and never publishes; `scaffold` writes the release workflows and never publishes.
argument-hint: "[dry-run | ship | scaffold]"
---

# `/abcd:launch` release preview and release cut
Expand Down Expand Up @@ -137,6 +137,45 @@ Exit codes, same shape as step 1:
Then show the user the written heading and the diff, so a human reviews the release
record before it is committed. This command never commits, tags, or publishes.

## Scaffold — the release-gate scaffolder

`scaffold` writes the changelog-driven release machinery into a **managed repo that
lacks it** — a different job from `ship` (which cuts a release in a repo that
already has the machinery). It **never publishes**.

```bash
abcd launch scaffold --json
```

It writes three files, wired to the repo's own default branch and Go version:

- `.github/workflows/release.yml` — verify → build → publish, the verify gate
armed against the reviewed **content** commit (`HEAD^2^` on the auto-release
merge path, `HEAD^` on a direct tag), so the first public release cannot hit the
receipt-vs-tag self-reference.
- `.github/workflows/auto-release.yml` — newest dated CHANGELOG heading → tag that
commit → call `release.yml`. `GITHUB_TOKEN`-only, no personal access token.
- `.abcd/development/release-gate/README.md` — the adr-37 runbook.

The workflows come from one embedded template that abcd-cli's own release
workflows are regenerated from (self-scaffold parity), so every abcd release
exercises the exact machinery a managed repo receives. The scaffolded `release.yml`
carries a `workflow_dispatch` **rehearsal**: run it green once before the first
real release — it arms the full gate against a simulated changelog roll and
reviewed-content commit, proves the gate admits, and publishes nothing (no tag,
Release, or attestation).

It is idempotent and fail-safe. Exit codes gate the flow:

- **0** — every file written, or already current (a no-op re-run). Report the
per-file disposition.
- **1** — a file exists and **differs** (hand-edited or stale); the report names
it and **nothing was written**. Relay it; re-run with `--confirm` to overwrite.
- **2** — a structural fault (the repository or a template could not be read).

A refusal is a result to relay, not a crash. Never hand-edit the workflows to work
around it: re-run with `--confirm` when the operator intends to replace the drift.

If the `abcd` binary is not on `PATH`, fall back to
`go run ./cmd/abcd launch ...` from the repo root, or tell the user to build it
with `make build`.
Expand Down
12 changes: 12 additions & 0 deletions docs/reference/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,18 @@ Preview the public launch bundle and release gates (read-only)
--dry-run preview the launch bundle and gates without publishing
```

#### `abcd launch scaffold`

Scaffold the changelog-driven release gate (release.yml, auto-release.yml, runbook) into this repo

**Usage:** `abcd launch scaffold [--confirm] [flags]`

**Flags:**

```
--confirm overwrite a hand-edited scaffolded file with the current machinery
```

#### `abcd launch ship`

Cut a release: derive the version and the record set from what shipped (exit 1 when the cut refuses)
Expand Down
Loading