mandrel 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.agents/agents/.markdownlint.json +4 -0
  2. package/.agents/agents/acceptance-critic.md +30 -5
  3. package/.agents/agents/auditor.md +36 -19
  4. package/.agents/agents/plan-critic.md +31 -5
  5. package/.agents/agents/story-worker.md +91 -100
  6. package/.agents/docs/configuration.md +39 -25
  7. package/.agents/docs/execution-reference.md +13 -0
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +131 -265
  10. package/.agents/rules/git-conventions.md +47 -83
  11. package/.agents/rules/orchestration-error-handling.md +28 -0
  12. package/.agents/schemas/agentrc.schema.json +36 -9
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +10 -6
  15. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  16. package/.agents/scripts/check-test-temp-hygiene.js +438 -0
  17. package/.agents/scripts/deliver-recover.js +23 -6
  18. package/.agents/scripts/lib/audit-suite/index.js +5 -0
  19. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
  20. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  21. package/.agents/scripts/lib/baselines/env-overrides.js +33 -0
  22. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  23. package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
  24. package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
  25. package/.agents/scripts/lib/config/quality.js +13 -0
  26. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  27. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  28. package/.agents/scripts/lib/config-settings-schema.js +12 -16
  29. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  30. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  31. package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
  32. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
  33. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
  34. package/.agents/scripts/lib/orchestration/complexity-gate.js +533 -93
  35. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  36. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  37. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  38. package/.agents/scripts/lib/orchestration/plan-context.js +119 -3
  39. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  40. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  41. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  42. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +153 -2
  43. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +221 -14
  44. package/.agents/scripts/lib/orchestration/resolve-stories.js +12 -1
  45. package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
  46. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  47. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  48. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  49. package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
  50. package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
  51. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  52. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  53. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
  55. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  56. package/.agents/scripts/lib/test-env.js +65 -0
  57. package/.agents/scripts/plan-context.js +84 -9
  58. package/.agents/scripts/plan-critics.js +115 -3
  59. package/.agents/scripts/plan-persist.js +11 -1
  60. package/.agents/scripts/plan-run-epilogue.js +1 -1
  61. package/.agents/scripts/resolve-stories.js +2 -0
  62. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  63. package/.agents/scripts/stories-wave-tick.js +1 -1
  64. package/.agents/workflows/deliver.md +88 -230
  65. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
  66. package/.agents/workflows/helpers/deliver-reference.md +171 -0
  67. package/.agents/workflows/helpers/deliver-story-reference.md +223 -0
  68. package/.agents/workflows/helpers/deliver-story.md +115 -432
  69. package/.agents/workflows/helpers/plan-reference.md +246 -0
  70. package/.agents/workflows/plan.md +108 -304
  71. package/docs/CHANGELOG.md +46 -0
  72. package/lib/cli/registry.js +31 -14
  73. package/lib/migrations/index.js +2 -0
  74. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
  75. package/package.json +1 -1
@@ -7,491 +7,174 @@ description:
7
7
 
8
8
  # /deliver-story #[Story ID]
9
9
 
10
- > **Runtime core.** Always-ingested per-Story delivery path. Lease / sweep /
11
- > CI-recovery detail lives in
12
- > [`deliver-story-reference.md`](deliver-story-reference.md); consult on demand.
13
- > Invoked by [`/deliver`](../deliver.md) for every Story (N=1 and N>1).
10
+ > **Lean spine.** Happy path + gate list; edge-case, recovery, and
11
+ > reference detail lives in
12
+ > [`deliver-story-reference.md`](deliver-story-reference.md) ("reference"
13
+ > below); consult on demand. Invoked by [`/deliver`](../deliver.md).
14
14
 
15
15
  ## Overview
16
16
 
17
- `/deliver-story` is the **one** delivery engine in v2. Every Story trivial or
18
- large uses the same machinery:
17
+ The **one** delivery engine in v2 every Story (a lite-**shaped** Story
18
+ runs inline with inline critics, #4722; engine, gates, envelope
19
+ byte-identical):
19
20
 
20
21
  ```text
21
- /deliver <storyId> [<storyId> ...] (each Story runs through this engine)
22
- → single-story-init.js (branch from main, worktree, agent::executing)
23
- agent implements + commits (optional ## Slicing intra-session checkpoints)
24
- → derived-level ceremony (acceptance critics · review depth)
25
- → single-story-close.js (gates, push, gh pr create → main, agent::closing)
26
- → CI watch + fix loop (until required checks pass + PR merged)
27
- → single-story-confirm-merge.js (PR merged → agent::done + follow-ups)
22
+ single-story-init.js implement + commits derived-level ceremony
23
+ → single-story-close.js (gates, push, PR → main, agent::closing)
24
+ CI watch + merge single-story-confirm-merge.js (agent::done)
28
25
  ```
29
26
 
30
- | Trait | v2 `/deliver-story` |
31
- | --- | --- |
32
- | Ticket type | `type::story` only |
33
- | Branch | `story-<id>` seeded from `project.baseBranch` (`main`) |
34
- | Merge target | `main` via PR (squash + required checks) |
35
- | Epic integration branch | **None** — no `epic/<id>`, no `--no-ff` wave merge |
36
- | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
37
- | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
38
-
39
- If the Story still carries an `Epic: #N` reference, **stop** — that is a v1
40
- Epic-attached ticket; re-plan as a v2 Story or finish it on a pre-v2 checkout.
41
-
42
- > **Ceremony-lite Stories still land through this engine unchanged (Story
43
- > #4683).** A Story that `/plan` routed onto the ceremony-lite path (its
44
- > `complexityRoute.route === "lite"`) collapses only the *advisory* plan/deliver
45
- > ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
46
- > not earn. It does **not** get a cheaper landing: the close-validation gates
47
- > (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
48
- > and the `rules/security-baseline.md` MUSTs all run here exactly as for a
49
- > full-ceremony Story. The lite route's `preserves` field is the machine-readable
50
- > record of those non-negotiables; there is no lite-specific gate bypass.
51
-
52
- ## Prerequisites
53
-
54
- 1. A GitHub Issue with the `type::story` label and **no** `Epic: #N`
55
- reference in its body.
56
- 2. `GITHUB_TOKEN` or `gh auth status` clean — `gh pr create` runs at close.
57
- 3. The base branch (`project.baseBranch`, default `main`) exists on
58
- both local and `origin`.
59
-
60
- ---
27
+ An `Epic: #N` reference marks a v1 ticket — **stop** and re-plan.
28
+ There is no `epic/<id>` branch, no `--no-ff` wave merge (trait table:
29
+ reference § Engine invariants). Prerequisites: a `type::story` issue,
30
+ clean `gh auth status`, and `project.baseBranch` on local and `origin`.
61
31
 
62
32
  ## Step 0 — Initialize (`single-story-init.js`)
63
33
 
64
- Run from the **main checkout** (the worktree does not exist yet):
34
+ From the **main checkout**, **synchronously** with the maximum Bash
35
+ timeout — the per-tree install can take minutes; never `run_in_background`:
65
36
 
66
37
  ```bash
67
38
  node .agents/scripts/single-story-init.js --story <storyId>
68
39
  ```
69
40
 
70
- Flags: `--dry-run` (no git/ticket mutation), `--steal` (forcibly transfer a
71
- foreign Story lease to this operator see the lease note below).
72
-
73
- > **Execution mode.** `single-story-init.js` can take 3–6 minutes when the
74
- > worktree's per-tree install runs. Invoke synchronously with
75
- > `Bash(timeout: 600000)`. Do **not** use `run_in_background` + `Monitor`
76
- > a sub-agent that exits mid-install leaves the worktree half-bootstrapped.
77
-
78
- The script validates `type::story`, **acquires the Story lease**, fetches
79
- `origin`, seeds `story-<id>` from `baseBranch`, materializes a worktree
80
- (when `delivery.worktreeIsolation.enabled` is true), upserts a
81
- `story-init` structured comment carrying `standalone: true`, and flips
82
- the Story to `agent::executing`. It also reuses an existing `story-<id>`
83
- branch (idempotent re-init) and runs a **merged-`story-*` sweep** between
84
- fetch and branch-seed.
85
-
86
- > **Lease preflight, branch reuse, and merged-sweep.** The standalone lease
87
- > **fails closed** on a foreign assignee (there is no Epic-scoped
88
- > heartbeat ledger to judge staleness) — coordinate or pass `--steal`. The
89
- > sweep is guarded (per-candidate protection + cross-session lock) and
90
- > never blocks init. See
91
- > [`deliver-story-reference.md` § Step 0 — Lease preflight and merged-sweep](deliver-story-reference.md#step-0--lease-preflight-and-merged-sweep)
92
- > for the fail-closed outcomes, the `--steal` contract, and the sweep
93
- > hardening layers.
94
-
95
- Capture `workCwd` from the result envelope. Add `--dry-run` to inspect
96
- the planned actions without git or ticket mutations (dry-run also skips
97
- the lease and the sweep).
98
-
99
- **Remote evidence — land or block (issue #4483).** The envelope also
100
- carries `remoteVerified` + `remoteProbe` (`git remote get-url origin` +
101
- bounded `git ls-remote origin HEAD`). When `remoteVerified` is `false`,
102
- transition the Story to `agent::blocked` quoting `remoteProbe.detail` and
103
- stop. Implementing the Story inline outside the worktree/branch/PR path
104
- and/or committing it to local `main` is expressly forbidden — the close
105
- pipeline's push is the only sanctioned landing.
106
-
107
- ### Step 0.5 — `cd` into the workCwd
108
-
109
- ```bash
110
- cd "<workCwd from Step 0 result>"
111
- ```
112
-
113
- All subsequent commands run from this directory.
41
+ Flags: `--dry-run` (no mutations; skips lease + sweep), `--steal` (transfer
42
+ a foreign lease). It validates `type::story`, **acquires the Story lease**
43
+ (fails closed on a foreign assignee), fetches `origin`, seeds `story-<id>`
44
+ from `baseBranch` (idempotent reuse), materializes a worktree, runs a
45
+ guarded merged-`story-*` sweep, and flips `agent::executing` (reference
46
+ § Step 0). Capture `workCwd` from the result envelope.
114
47
 
115
- > **Worktree scope is not just the Bash cwd.** `cd <workCwd>` steers the
116
- > Bash tool's cwd but does **not** scope the path-based Edit/Write/Read
117
- > tools you MUST prefix every such path with the absolute `workCwd` root or
118
- > risk silently editing the main checkout. Close's wrong-tree guard (Story
119
- > #3364) is a backstop, not a substitute. See
120
- > [`deliver-story-reference.md` § Worktree scope is not just the Bash cwd](deliver-story-reference.md#worktree-scope-is-not-just-the-bash-cwd).
48
+ **Land or block (issue #4483).** `remoteVerified: false` flip
49
+ `agent::blocked` quoting `remoteProbe.detail` and stop. Implementing outside
50
+ the worktree/branch/PR path or committing to local `main` is forbidden —
51
+ close's push is the only sanctioned landing.
121
52
 
122
- ---
53
+ **Step 0.5 — `cd "<workCwd>"`**, and prefix every path-based
54
+ Edit/Write/Read with that absolute root — the `cd` alone does not scope
55
+ those tools (reference § Worktree scope is not just the Bash cwd).
123
56
 
124
57
  ## Step 1 — Implementation
125
58
 
126
- A Story is **atomic** — one `story-<id>` branch, one PR to `main`. Work
127
- happens in one or more commits against the inline `acceptance[]` /
128
- `verify[]` arrays (and the folded `## Spec` when present).
129
-
130
- Operator/agent responsibilities while in the worktree:
131
-
132
- 1. Read the Story body. Treat its acceptance criteria as the contract.
133
-
134
- **Docs context digest-first.** Read a full doc only when the Story's
135
- own context points you at one do not ingest the whole
136
- `project.docsContextFiles` set up front. If the caller provides a
137
- `docsDigestPath`, prefer that compact outline and pull individual files
138
- on demand. See [`.agents/instructions.md` § 3](../../instructions.md).
139
-
140
- **Write-time audit checklists.** When the caller provides a
141
- `checklistPath` (footprint-matched **local**-lens authoring checklists),
142
- read it before you write and self-check as you author. When absent,
143
- lens-aware coverage still runs maker-blind at Story-scope review inside
144
- the close subprocess. The dispatch step produces `checklistPath` from the
145
- Story's predicted footprint before it spawns this worker (Story #4627) see
146
- [`/deliver`](../deliver.md).
147
- 2. Implement the changes. When the body has a `## Slicing` / Delivery
148
- Slicing table, walk rows as **intra-session checkpoints** (commit +
149
- flip each row when done) — never as sibling tickets.
150
- 3. Commit on the Story branch. Conventional-commit format is encouraged
151
- but not enforced — the PR title carries the canonical summary.
152
- 4. Iterate (read tests, run targeted gates, edit, commit) until the
153
- acceptance criteria are met.
154
- 5. Run the **bounded acceptance self-eval loop** (Step 1a below) before
155
- ceremony / close.
156
-
157
- Recommended quick gates while iterating (each is fast enough to run on
158
- save):
59
+ One branch, one PR to `main`, commits against the inline `acceptance[]` /
60
+ `verify[]` (and `## Spec`):
61
+
62
+ 1. Read the Story body; the acceptance criteria are the contract. Docs are
63
+ digest-first; read a caller-provided `checklistPath` before writing
64
+ (reference § Step 1).
65
+ 2. Implement. Walk any `## Slicing` rows as **intra-session checkpoints**
66
+ (commit + flip each row) — never sibling tickets.
67
+ 3. Commit on the Story branch; iterate with quick advisory gates
68
+ (`typecheck`, `lint`, scoped tests) the full close chain runs in
69
+ Step 3.
70
+ 4. Run the **full test command** once in the worktree (`npm test`) **before
71
+ Step 1a** repo-invariant guards outside the Story's scoped greps are
72
+ the failure class that bounces deliveries. Fix and commit first.
73
+ 5. Run the self-eval loop (Step 1a).
74
+
75
+ ### Step 1a Bounded acceptance self-eval loop (**required**)
76
+
77
+ Follow the single-homed include
78
+ [`acceptance-self-eval.md`](acceptance-self-eval.md) (single verdict-owner,
79
+ `verify[]`-as-evidence, proceed / redraft / block). Gate invocation (omit
80
+ `--epic`):
159
81
 
160
82
  ```bash
161
- npm run typecheck
162
- npm run lint
163
- npm test -- --grep "<scope>"
83
+ node <main-repo>/.agents/scripts/acceptance-eval.js \
84
+ --story <storyId> --verdict <verdict-path>
164
85
  ```
165
86
 
166
- The full close-validation chain runs in Step 3; the gates above are
167
- advisory pre-flight.
168
-
169
- > Conflict with `main` mid-implementation → resolve as you would any
170
- > branch rebase. There is no `epic/<id>` intermediate, so the rebase
171
- > base is `main` directly.
172
-
173
- ### Step 1a — Bounded acceptance self-eval loop (**required, not optional**)
174
-
175
- After the implementation commits land and **before** you proceed to close, run
176
- the bounded acceptance self-eval loop. The per-round critic mechanic (fresh-
177
- context critic, `verify[]`-as-evidence, the verdict schema, and the
178
- proceed / redraft / block decision) is the single-homed include
179
- [`acceptance-self-eval.md`](acceptance-self-eval.md) — read it and follow it.
180
-
181
- Story-path specifics:
182
-
183
- - **Critic evidence-share** (Story #4250). When the critic runs a `verify[]`
184
- command that is byte-identical to a close gate (`lint` / `typecheck`), it
185
- records the pass into the Story evidence keyspace via `--standalone` so
186
- Step 3's close short-circuits the gate at unchanged HEAD. Run it in the
187
- **Story worktree** (`workCwd` from Step 0):
188
-
189
- ```bash
190
- node <main-repo>/.agents/scripts/evidence-gate.js \
191
- --standalone --scope-id <storyId> --gate lint \
192
- --worktree <workCwd> -- npm run lint
193
- ```
194
-
195
- - **Gate invocation** (omit `--epic`):
196
-
197
- ```bash
198
- node <main-repo>/.agents/scripts/acceptance-eval.js \
199
- --story <storyId> --verdict <verdict-path>
200
- ```
201
-
202
- - **On `decision: "proceed"`** → proceed to Step 2 (ceremony) then Step 3.
203
- - **On `decision: "block"`** → **do not proceed to close.** Post a `friction`
204
- comment naming the unmet criteria, then transition the Story to
205
- `agent::blocked`:
206
-
207
- ```bash
208
- node .agents/scripts/diagnose-friction.js --story <storyId> \
209
- --cmd node .agents/scripts/acceptance-eval.js --story <storyId> --verdict <verdict-path>
210
- node .agents/scripts/update-ticket-state.js --ticket <storyId> --state agent::blocked
211
- ```
212
-
213
- ---
87
+ **`proceed`** Step 2 then Step 3. **`block`** **do not close**: post a
88
+ `friction` comment and flip `agent::blocked` — commands and the
89
+ `evidence-gate.js --standalone` evidence-share mechanic: reference
90
+ § Step 1a.
214
91
 
215
92
  ## Step 2 — Ceremony (profile + derived level)
216
93
 
217
- Per-Story ceremony is selected by `delivery.routing.ceremonyProfile`
218
- (`minimal` | `standard` | `strict`, default `standard`) and the Story's
219
- **derived change level** — not a planner-authored verdict (Story #4542 retired
220
- that).
221
-
94
+ Ceremony is `delivery.routing.ceremonyProfile` × the **derived change
95
+ level** never a planner-authored verdict (Story #4542).
222
96
  **Compute the change set once** (Story #4593) with the shared enumerator
223
- [`computeChangeSet`](../../scripts/lib/orchestration/change-set.js) — the same
224
- module close uses — and reuse that one list for everything downstream:
225
-
226
- ```bash
227
- node --input-type=module -e '
228
- import { computeChangeSet } from "<main-repo>/.agents/scripts/lib/orchestration/change-set.js";
229
- const { files } = computeChangeSet({ baseRef: "main", headRef: "story-<storyId>" });
230
- console.log(JSON.stringify(files));
231
- '
232
- ```
233
-
234
- Then derive the level with
235
- [`deriveChangeLevel`](../../scripts/lib/orchestration/review-depth.js) over that
236
- list: a diff touching a sensitive path registered in
237
- `.agents/schemas/audit-rules.json` derives `high`, one touching none derives
238
- `low`, and an unenumerable diff (`files === null`) derives `null`. Hand the
239
- **same** list to every acceptance critic you spawn (Step 1a) — a critic that
240
- re-ran its own `git diff` could score against a different set than the one that
241
- routed it.
242
-
243
- Resolve fresh-vs-inline acceptance critics per AC-cluster with
244
- [`resolveCeremonyForRisk`](../../scripts/lib/orchestration/ceremony-routing.js)
245
- (`minimal` → always inline; `strict` → always fresh; `standard` →
246
- `high`/`null` → `fresh`, `low` → `inline` unless the `freshCriticSampleRate`
247
- floor forces `fresh`). Review depth reads the same derived level via
248
- `review-depth.js` inside close, so the two decisions cannot disagree.
249
-
250
- Hard gates (lint / test / format / coverage / CRAP / maintainability) always
251
- run in Step 3 — the derived level never disables them. Do **not** pre-run the
252
- full close-validation chain here unless interactively iterating on a fix.
253
-
254
- ---
97
+ [`computeChangeSet`](../../scripts/lib/orchestration/change-set.js) — the
98
+ same module close uses — and hand that one list to every critic. Derive the
99
+ level with `deriveChangeLevel`, resolve fresh-vs-inline critics with
100
+ `resolveCeremonyForRisk` (`ceremony-routing.js`); a lite Story runs inline
101
+ regardless (exact incantation and routing rules: reference § Step 2).
102
+ Hard gates (lint / test / format / coverage / CRAP / maintainability)
103
+ always run in Step 3 the derived level never disables them; do **not**
104
+ pre-run the full close chain here.
255
105
 
256
106
  ## Step 3 — Close and land (`single-story-close.js`)
257
107
 
258
- Invoke from the main checkout (or pass `--cwd <main-repo>` from inside
259
- the worktree):
260
-
261
108
  ```bash
262
109
  node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo>
263
110
  ```
264
111
 
265
- **This step is the whole delivery tail.** Close owns the gates, the PR, the
266
- merge wait, the `agent::done` flip, and the post-land tail (follow-up
267
- capture, status-column resync, local ref cleanup, base fast-forward) in one
268
- process. Your job is to run it and **branch on the terminal envelope's
269
- `status`** — nothing more (Story #4543).
112
+ **The whole delivery tail** gates, PR, merge wait, `agent::done` flip,
113
+ post-land tail in one process. Run it and **branch on the terminal
114
+ envelope's `status`** (Story #4543):
270
115
 
271
- ### Branch on the terminal envelope
272
-
273
- Every invocation emits exactly one schema-validated envelope
274
- ([`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json))
275
- between `--- STORY DELIVER TERMINAL ---` markers, and the exit code mirrors
276
- its `status`:
277
-
278
- | `status` | Exit | What it means | What you do |
116
+ | `status` | Exit | Meaning | You do |
279
117
  | --- | --- | --- | --- |
280
- | `landed` | 0 | PR merged, Story `agent::done`, tail ran. `tail.*` booleans expose any partial degradation — a `false` there does **not** demote the land. | Go to Step 7 and relay the envelope. Nothing else. |
281
- | `pending` | 3 | **Resumable, not a failure.** The per-invocation merge wait expired with the PR healthy and in flight, or the operator owns the merge. No label was mutated; no `merge.unlanded` was emitted. | Run the envelope's `nextCommand`. Repeat until it resolves. Relay `pending` only once you have exhausted your own budget. |
282
- | `blocked` | 1 | A classified hard block. Story carries `agent::blocked`; `blocked.blockClass` names the class and `blocked.frictionCommentId` points at the remediation. | `checks-failed` → fix the red check and push (Step 4). Otherwise go to Step 7 and relay the envelope. |
118
+ | `landed` | 0 | PR merged, `agent::done`, tail ran (`tail.*: false` degrades the report, not the land). | Relay the envelope (Step 7). |
119
+ | `pending` | 3 | **Resumable, not a failure** wait expired healthy, or a human owns the merge. | Run `nextCommand` until resolved. |
120
+ | `blocked` | 1 | Hard block; `blocked.blockClass` names it. | `checks-failed` → Step 4; else relay. |
283
121
  | `failed` | 1 | A phase crashed; `phase` names which. | Diagnose, fix, re-run close. |
284
122
 
285
- Do **not** re-sequence the post-close steps by hand. Steps 4–6 below are
286
- **recovery-only** reached from a `blocked`/`pending` envelope, never as
287
- routine choreography.
288
-
289
- ### What close does internally
290
-
291
- The script runs the close-validation gates against `baseBranch`, syncs the
292
- Story branch from `origin/<baseBranch>` (Story #2580 — the parallel-race
293
- defence), pushes `story-<id>`, opens (or reuses) a PR against `baseBranch`
294
- with a `Closes #<storyId>` footer, enables GitHub native auto-merge
295
- (`--auto --squash --delete-branch`) **when `delivery.ci.autoMerge` is
296
- `"trust-ci"` (the default)**, flips the Story to `agent::closing`, reaps the
297
- worktree, releases the lease, then **waits for the merge** and — on a
298
- confirmed merge — flips `agent::done` and runs the post-land tail.
299
-
300
- ### The merge wait is bounded and resumable
301
-
302
- Two budgets, deliberately separate (`delivery.mergeWatch.*`):
303
-
304
- - **`maxWaitSeconds`** (default 300) bounds **one invocation**, sized to fit
305
- inside a single host tool invocation (~10 min ceiling) alongside the gates
306
- that precede it. Expiry → `pending`. Pass `--max-wait-seconds <n>` to raise
307
- it when your host has no such ceiling and you want to land in one block.
308
- - **`maxBudgetSeconds`** (default 3600) bounds the **cumulative** wait across
309
- resumes, anchored at the PR's `createdAt` so resuming does not restart the
310
- clock. Exhausting *this* is the genuine give-up → `blocked`.
311
-
312
- The wait probes the checks every poll: a red required check fails fast as
313
- `checks-failed` instead of burning the budget, and a PR that falls behind its
314
- base is brought up to date within `updateAttempts` tries.
315
-
316
- > **`delivery.ci.autoMerge` policy.** Under the default `"trust-ci"`, GitHub
317
- > native auto-merge is armed and the PR squash-merges once its **required**
318
- > checks pass. Under `"strict"`, the close **does not arm auto-merge** — the
319
- > PR opens and waits for an **operator merge**, exactly as `--no-auto-merge`
320
- > does per-run.
321
-
322
- Flags:
323
-
324
- - `--skip-validation` — bypass the gates (Step 1). Use only when re-running
325
- close after a fixed gate failure that's already known to pass.
326
- - `--skip-sync` — bypass the base-sync (Story #2580). Use only after a
327
- hand-resolved sync, or in tests.
328
- - `--no-auto-merge` — disable auto-merge. Use when the PR materially changes
329
- behaviour and warrants a pre-merge eyeball; the operator then merges via
330
- the GitHub UI.
331
- - `--wait-merge` — **close-and-land** (Story #4428). Forces close to poll
332
- the armed PR to merge confirmation and flip `agent::done` itself. When
333
- neither land flag is passed, close defaults from
334
- `delivery.routing.closeAndLand` (**true**): attended and headless delivers
335
- share the land-in-one-close happy path.
336
- - `--no-wait-merge` — explicit opt-out that always wins. Use when the
337
- operator wants the PR left at `agent::closing` for a human land (or a
338
- wrapper that will invoke `single-story-confirm-merge.js` itself). Reports
339
- `pending` — the work is not done, nothing is broken, and one named command
340
- finishes it.
341
- - `--max-wait-seconds <n>` — raise the merge wait's per-invocation bound for
342
- this run (Story #4543). Use from a headless caller with no host
343
- tool-invocation ceiling to keep single-block semantics without editing the
344
- consumer's config.
345
-
346
- > **Full close pipeline (base-sync outcomes, `agent::closing` rationale,
347
- > lease release).** For the numbered close pipeline, the base-sync outcome
348
- > table (no-op / conflict → `agent::blocked` / fetch-failed), and why the
349
- > issue stays OPEN at `agent::closing`, see
350
- > [`deliver-story-reference.md` § Step 3 — Close pipeline detail](deliver-story-reference.md#step-3--close-pipeline-detail).
351
-
352
- ---
123
+ Internals (gate order, base-sync, auto-merge arming), the merge-wait
124
+ budgets, the slow-CI **async** confirm mode (Story #4698 launch the
125
+ `pending` envelope's `nextCommand` as **background** Bash, never a
126
+ foreground poll), the `autoMerge` policy, and every close flag: reference
127
+ § Step 3 Merge wait, async mode, and flags.
353
128
 
354
129
  ## Steps 4–6 — Recovery router (**recovery-only**)
355
130
 
356
- > **Steps 4, 5, 5.5, and 6 are recovery paths, not routine choreography
357
- > (Story #4543).** On the default path Step 3 already polled the PR to a
358
- > confirmed merge, flipped `agent::done`, and ran the whole post-land tail —
359
- > follow-up capture, status resync, ref cleanup, base fast-forward — in one
360
- > process. A `landed` envelope means all of it ran; go straight to Step 7.
361
-
362
- Enter a recovery path **only** when Step 3's terminal envelope tells you to.
363
- The full procedures — commands, exit-code branches, and the
364
- internally-blocking-watch contract — live in
365
- [`deliver-story-reference.md`](deliver-story-reference.md); route by the
366
- envelope:
367
-
368
- - **`blocked` / `blockClass: "checks-failed"`** (a required check went red) →
369
- fix and push a new commit on `story-<storyId>` (auto-merge stays armed), then
370
- resume with the envelope's `nextCommand`. The watch is an internally-blocking
371
- step — never end your turn with prose and an unconfirmed merge (Story #1553).
372
- Procedure:
373
- [reference § Step 4 — CI watch + fix recovery](deliver-story-reference.md#step-4--ci-watch--fix-recovery)
374
- (triage per [`rules/ci-remediation.md`](../../rules/ci-remediation.md)).
375
- - **`pending`** (bounded merge wait expired, PR healthy; or a `--no-wait-merge`
376
- run to shepherd) → run the envelope's `nextCommand`
377
- (`single-story-confirm-merge.js`) until it resolves —
378
- [reference § Step 5 — Merge confirmation detail](deliver-story-reference.md#step-5--merge-confirmation-detail).
379
- - **`tail.statusResync: false`** → re-assert the Status column by hand —
380
- [reference § Step 5.5](deliver-story-reference.md#step-55--re-assert-status-column-detail).
381
- - **`tail.refCleanup: false` / `tail.baseFastForward: false`** → prune the local
382
- ref and fast-forward `main` by hand —
383
- [reference § Step 6](deliver-story-reference.md#step-6--local-branch-cleanup-detail).
131
+ A `landed` envelope means everything ran go straight to Step 7. Enter a
132
+ recovery path **only** when the envelope routes you there:
384
133
 
385
- ---
134
+ - **`blocked` / `checks-failed`** → fix, push a new commit (auto-merge stays
135
+ armed), resume via `nextCommand`; triage per
136
+ [`rules/ci-remediation.md`](../../rules/ci-remediation.md). The watch is
137
+ internally blocking — never end a turn with prose and an unconfirmed
138
+ merge (Story #1553). Reference § Step 4.
139
+ - **`pending`** → run `nextCommand` (`single-story-confirm-merge.js`) until
140
+ resolved. Reference § Step 5.
141
+ - **`tail.statusResync: false`** → reference § Step 5.5;
142
+ **`tail.refCleanup` / `tail.baseFastForward: false`** → reference § Step 6.
386
143
 
387
- ## Step 7 — Return contract (**required when dispatched as a sub-agent**) {#return-contract}
144
+ ## Step 7 — Return contract (**required as a sub-agent**) {#return-contract}
388
145
 
389
- The return contract is the shipped schema
146
+ The contract is the shipped schema
390
147
  [`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json)
391
- **the single source of truth for every field, and the only place they are
392
- defined** (Story #4543). Do not restate its fields here or anywhere else:
393
- this section and
394
- [`agents/story-worker.md`](../../agents/story-worker.md) each used to define
395
- their own divergent shape, neither validated by anything, which is exactly
396
- how they drifted apart.
397
-
398
- When this workflow runs as a per-Story sub-agent (dispatched by
399
- [`/deliver`](../deliver.md)), the **only** acceptable way to end your turn is
400
- to return a single terminal JSON object conforming to that schema — never
401
- free-form prose. `single-story-close.js` already emits a validated one
402
- between its `--- STORY DELIVER TERMINAL ---` markers; **relay that envelope**
403
- rather than composing a new object by hand.
404
-
405
- Its `status` is one of exactly four values, and the no-park rule follows
406
- directly from them:
407
-
408
- - `landed` — the PR merged, the Story is `agent::done`, and the tail was
409
- attempted. Terminal; you are done.
410
- - `pending` — **resumable**, and the only sanctioned way to end a turn
411
- without a merge. It carries the `nextCommand` that resumes it. Return this
412
- only when you have exhausted your own budget, not as a way to avoid
413
- waiting: the wait is internally blocking (Step 4).
414
- - `blocked` — the Story carries `agent::blocked` and `blocked.blockClass`
415
- names the class.
416
- - `failed` — a phase crashed; `phase` names it.
417
-
418
- Ending the turn with prose and an unconfirmed merge is a contract violation
419
- (the Story #1553 / PR #1554 failure mode).
420
-
421
- > **No-park rule + handoff discipline.** For why a prose hand-off with an
422
- > unconfirmed merge is the very bug this workflow prevents, and the
423
- > report-state-not-process handoff discipline, see
424
- > [`deliver-story-reference.md` § Step 7 — Return-contract detail](deliver-story-reference.md#step-7--return-contract-detail).
425
-
426
- ---
148
+ — the SSOT for every field (Story #4543). End your turn by relaying the
149
+ validated envelope close emits between its `--- STORY DELIVER TERMINAL ---`
150
+ markers never free-form prose, never a hand-composed object. `pending` is
151
+ the only sanctioned no-merge ending, returned only when your own budget is
152
+ exhausted (Story #1553). Reference § Step 7.
427
153
 
428
154
  ## Recovering a stranded Story {#recover}
429
155
 
430
- When a Story is in an unclear state — a killed run, a `pending` envelope you
431
- no longer have, a Story a `/deliver` re-run refuses — do not guess and do not
432
- re-run the pipeline hoping it converges. Probe it:
156
+ Unclear state (killed run, lost envelope, a re-run refusal incl.
157
+ merged-but-label-stale)? Do not guess probe **read-only** with
158
+ `node .agents/scripts/deliver-recover.js --story <storyId>`; it prints the
159
+ **one** next command with its evidence, never a menu.
433
160
 
434
- ```bash
435
- node .agents/scripts/deliver-recover.js --story <storyId>
436
- ```
161
+ ## Idempotence & constraints
437
162
 
438
- It is **read-only**: it probes the labels, lease, branch, worktree, and PR
439
- (state + checks), then prints the **one** next command with the evidence it
440
- was derived from — never a menu.
163
+ Every script no-ops safely on re-run (init re-prints `workCwd`; close and
164
+ confirm-merge short-circuit on a closed / `agent::done` Story; the PR probe
165
+ reuses an open PR).
441
166
 
442
- It is the only automated way out of the **merged-but-label-stale** strand: a
443
- `/deliver` re-run refuses that Story outright, because `single-story-init.js`
444
- hard-errors on an already-closed one.
445
-
446
- ---
447
-
448
- ## Idempotence
449
-
450
- - `single-story-init.js` re-prints the same `workCwd` without recreating
451
- the worktree when one already exists for `story-<id>`.
452
- - `single-story-close.js` short-circuits when the Story is already
453
- closed (returns `{ action: 'noop', reason: 'already-closed' }`).
454
- - `single-story-confirm-merge.js` short-circuits when the Story already
455
- carries `agent::done` or the issue is already closed (returns
456
- `{ action: 'noop', reason: 'already-done' }`), and is safe to re-run
457
- while the PR is still open (returns `{ action: 'pending', ... }` without
458
- mutating the Story).
459
- - The PR probe (`gh pr list --head <branch> --state open`) reuses an
460
- existing open PR rather than opening a duplicate.
461
-
462
- Re-running `/deliver-story` against an already-closed Story is
463
- safe.
464
-
465
- ---
466
-
467
- ## Constraints
468
-
469
- - **Never** push the Story branch directly to `main`. The PR is the only
167
+ - **Never** push the Story branch directly to `main` the PR is the only
470
168
  merge surface.
471
- - **Always** `cd` into the `workCwd` returned by Step 0 before editing,
472
- **and** prefix every path-based Edit/Write/Read with that absolute
473
- `workCwd` root the `cd` alone does not scope the path-based tools (see
474
- Step 0.5). Editing a bare main-checkout path lands the change in the wrong
475
- tree; close's wrong-tree guard (Story #3364) aborts when it detects this.
476
- - **Handoff discipline report state, not process.** When you hand back to
477
- your caller (the `/deliver` aggregator or the interactive operator),
478
- report essential terminal state only: the Story branch, the closing commit
479
- SHA, what changed, and what was verified. Mirror the fields the close
480
- pipeline already emits (the `single-story-close.js` terminal envelope)
481
- rather than inventing a new contract. Do not narrate the steps you took, and do not prescribe how the
482
- next stage should do its work. Prose process commentary only bloats the
483
- hydrated prompt.
484
- - **Label transitions**: drive every `agent::*` state change through
485
- `node .agents/scripts/update-ticket-state.js --ticket <id> --state <state>`.
486
- This CLI is the authoritative mechanism — there is no separate
487
- state-mutation MCP server to degrade from (see
488
- [`.agents/instructions.md` § 1.D](../../instructions.md)).
489
-
490
- ---
169
+ - **Always** prefix path-based tools with the absolute `workCwd` root
170
+ (Step 0.5); close's wrong-tree guard (Story #3364) is a backstop.
171
+ - **Report state, not process** mirror the close envelope's fields; no
172
+ step narration.
173
+ - Drive every `agent::*` transition through
174
+ `update-ticket-state.js --ticket <id> --state <state>`.
491
175
 
492
176
  ## See also
493
177
 
494
- - [`/deliver`](../deliver.md) — unified entry point (`<storyId...>`;
495
- sequences via `depends_on`, resolved from live state).
496
- - [`deliver-story-reference.md`](deliver-story-reference.md) —
497
- lease, sweep, CI-recovery, and Status-column reference detail.
178
+ - [`/deliver`](../deliver.md) — unified entry point.
179
+ - [`deliver-story-reference.md`](deliver-story-reference.md) all on-demand
180
+ detail.