mandrel 2.9.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.
- package/.agents/agents/.markdownlint.json +4 -0
- package/.agents/agents/acceptance-critic.md +30 -5
- package/.agents/agents/auditor.md +36 -19
- package/.agents/agents/plan-critic.md +31 -5
- package/.agents/agents/story-worker.md +91 -100
- package/.agents/docs/configuration.md +16 -4
- package/.agents/docs/execution-reference.md +13 -0
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +131 -265
- package/.agents/rules/git-conventions.md +47 -83
- package/.agents/rules/orchestration-error-handling.md +28 -0
- package/.agents/schemas/agentrc.schema.json +24 -2
- package/.agents/schemas/validation-evidence.schema.json +3 -1
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +1 -1
- package/.agents/scripts/check-test-temp-hygiene.js +438 -0
- package/.agents/scripts/deliver-recover.js +23 -6
- package/.agents/scripts/lib/audit-suite/index.js +5 -0
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +121 -1
- package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
- package/.agents/scripts/lib/config-settings-schema.js +1 -1
- package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
- package/.agents/scripts/lib/orchestration/complexity-gate.js +113 -2
- package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
- package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
- package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +57 -0
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
- package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +102 -2
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +215 -14
- package/.agents/scripts/lib/orchestration/resolve-stories.js +7 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
- package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
- package/.agents/scripts/lib/test-env.js +65 -0
- package/.agents/scripts/plan-context.js +66 -9
- package/.agents/scripts/plan-critics.js +115 -3
- package/.agents/scripts/plan-persist.js +11 -1
- package/.agents/scripts/plan-run-epilogue.js +1 -1
- package/.agents/scripts/single-story-confirm-merge.js +65 -5
- package/.agents/scripts/stories-wave-tick.js +1 -1
- package/.agents/workflows/deliver.md +86 -230
- package/.agents/workflows/helpers/deliver-reference.md +167 -0
- package/.agents/workflows/helpers/deliver-story-reference.md +203 -0
- package/.agents/workflows/helpers/deliver-story.md +114 -432
- package/.agents/workflows/helpers/plan-reference.md +211 -0
- package/.agents/workflows/plan.md +107 -304
- package/docs/CHANGELOG.md +27 -0
- package/package.json +1 -1
|
@@ -7,491 +7,173 @@ description:
|
|
|
7
7
|
|
|
8
8
|
# /deliver-story #[Story ID]
|
|
9
9
|
|
|
10
|
-
> **
|
|
11
|
-
>
|
|
12
|
-
> [`deliver-story-reference.md`](deliver-story-reference.md)
|
|
13
|
-
> Invoked by [`/deliver`](../deliver.md)
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
→ single-story-
|
|
23
|
-
→
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
---
|
|
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`.
|
|
61
30
|
|
|
62
31
|
## Step 0 — Initialize (`single-story-init.js`)
|
|
63
32
|
|
|
64
|
-
|
|
33
|
+
From the **main checkout**, **synchronously** with the maximum Bash
|
|
34
|
+
timeout — the per-tree install can take minutes; never `run_in_background`:
|
|
65
35
|
|
|
66
36
|
```bash
|
|
67
37
|
node .agents/scripts/single-story-init.js --story <storyId>
|
|
68
38
|
```
|
|
69
39
|
|
|
70
|
-
Flags: `--dry-run` (no
|
|
71
|
-
foreign
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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.
|
|
114
46
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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).
|
|
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.
|
|
121
51
|
|
|
122
|
-
|
|
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).
|
|
123
55
|
|
|
124
56
|
## Step 1 — Implementation
|
|
125
57
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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):
|
|
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`):
|
|
159
80
|
|
|
160
81
|
```bash
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
npm test -- --grep "<scope>"
|
|
82
|
+
node <main-repo>/.agents/scripts/acceptance-eval.js \
|
|
83
|
+
--story <storyId> --verdict <verdict-path>
|
|
164
84
|
```
|
|
165
85
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
---
|
|
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.
|
|
214
90
|
|
|
215
91
|
## Step 2 — Ceremony (profile + derived level)
|
|
216
92
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
**derived change level** — not a planner-authored verdict (Story #4542 retired
|
|
220
|
-
that).
|
|
221
|
-
|
|
93
|
+
Ceremony is `delivery.routing.ceremonyProfile` × the **derived change
|
|
94
|
+
level** — never a planner-authored verdict (Story #4542).
|
|
222
95
|
**Compute the change set once** (Story #4593) with the shared enumerator
|
|
223
|
-
[`computeChangeSet`](../../scripts/lib/orchestration/change-set.js) — the
|
|
224
|
-
module close uses — and
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
---
|
|
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.
|
|
255
104
|
|
|
256
105
|
## Step 3 — Close and land (`single-story-close.js`)
|
|
257
106
|
|
|
258
|
-
Invoke from the main checkout (or pass `--cwd <main-repo>` from inside
|
|
259
|
-
the worktree):
|
|
260
|
-
|
|
261
107
|
```bash
|
|
262
108
|
node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo>
|
|
263
109
|
```
|
|
264
110
|
|
|
265
|
-
**
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
process. Your job is to run it and **branch on the terminal envelope's
|
|
269
|
-
`status`** — nothing more (Story #4543).
|
|
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):
|
|
270
114
|
|
|
271
|
-
|
|
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 |
|
|
115
|
+
| `status` | Exit | Meaning | You do |
|
|
279
116
|
| --- | --- | --- | --- |
|
|
280
|
-
| `landed` | 0 | PR merged,
|
|
281
|
-
| `pending` | 3 | **Resumable, not a failure
|
|
282
|
-
| `blocked` | 1 |
|
|
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. |
|
|
283
120
|
| `failed` | 1 | A phase crashed; `phase` names which. | Diagnose, fix, re-run close. |
|
|
284
121
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
---
|
|
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.
|
|
353
127
|
|
|
354
128
|
## Steps 4–6 — Recovery router (**recovery-only**)
|
|
355
129
|
|
|
356
|
-
|
|
357
|
-
|
|
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).
|
|
130
|
+
A `landed` envelope means everything ran — go straight to Step 7. Enter a
|
|
131
|
+
recovery path **only** when the envelope routes you there:
|
|
384
132
|
|
|
385
|
-
|
|
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.
|
|
386
142
|
|
|
387
|
-
## Step 7 — Return contract (**required
|
|
143
|
+
## Step 7 — Return contract (**required as a sub-agent**) {#return-contract}
|
|
388
144
|
|
|
389
|
-
The
|
|
145
|
+
The contract is the shipped schema
|
|
390
146
|
[`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json)
|
|
391
|
-
—
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
---
|
|
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.
|
|
427
152
|
|
|
428
153
|
## Recovering a stranded Story {#recover}
|
|
429
154
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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.
|
|
433
159
|
|
|
434
|
-
|
|
435
|
-
node .agents/scripts/deliver-recover.js --story <storyId>
|
|
436
|
-
```
|
|
160
|
+
## Idempotence & constraints
|
|
437
161
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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).
|
|
441
165
|
|
|
442
|
-
|
|
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
|
|
166
|
+
- **Never** push the Story branch directly to `main` — the PR is the only
|
|
470
167
|
merge surface.
|
|
471
|
-
- **Always**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
-
|
|
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
|
-
---
|
|
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>`.
|
|
491
174
|
|
|
492
175
|
## See also
|
|
493
176
|
|
|
494
|
-
- [`/deliver`](../deliver.md) — unified entry point
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
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.
|