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