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.
- 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 +39 -1
- 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 +44 -1
- 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/audit-to-stories.js +51 -0
- 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/audit-to-stories/dedupe-against-github.js +120 -55
- 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 +32 -0
- package/.agents/scripts/lib/findings/semantic-issue-search.js +43 -5
- package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
- package/.agents/scripts/lib/observability/terse-result.js +114 -0
- package/.agents/scripts/lib/orchestration/complexity-gate.js +318 -0
- 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 +60 -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/auto-merge.js +221 -8
- 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/single-story-close/runner.js +55 -14
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +9 -3
- 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/orchestration/story-deliver-terminal.js +4 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +13 -40
- package/.agents/scripts/lib/story-body/body-format-lints.js +215 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +29 -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/providers/github/issues.js +54 -7
- package/.agents/scripts/providers/github/search-budget.js +124 -0
- package/.agents/scripts/providers/github/search-query.js +71 -0
- package/.agents/scripts/single-story-confirm-merge.js +79 -10
- package/.agents/scripts/single-story-init.js +19 -3
- package/.agents/scripts/stories-wave-tick.js +1 -1
- package/.agents/scripts/sync-branch-from-base.js +9 -3
- 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 -422
- package/.agents/workflows/helpers/plan-reference.md +211 -0
- package/.agents/workflows/plan.md +107 -279
- package/docs/CHANGELOG.md +47 -0
- package/package.json +1 -1
|
@@ -7,481 +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
|
-
## 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
|
-
|
|
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
|
|
61
|
-
foreign
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
|
214
|
-
module close uses — and
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
**
|
|
256
|
-
|
|
257
|
-
|
|
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 |
|
|
115
|
+
| `status` | Exit | Meaning | You do |
|
|
269
116
|
| --- | --- | --- | --- |
|
|
270
|
-
| `landed` | 0 | PR merged,
|
|
271
|
-
| `pending` | 3 | **Resumable, not a failure
|
|
272
|
-
| `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. |
|
|
273
120
|
| `failed` | 1 | A phase crashed; `phase` names which. | Diagnose, fix, re-run close. |
|
|
274
121
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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
|
|
143
|
+
## Step 7 — Return contract (**required as a sub-agent**) {#return-contract}
|
|
378
144
|
|
|
379
|
-
The
|
|
145
|
+
The contract is the shipped schema
|
|
380
146
|
[`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json)
|
|
381
|
-
—
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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**
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
-
|
|
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
|
|
485
|
-
|
|
486
|
-
|
|
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.
|