mandrel 1.82.0 → 1.83.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/README.md +41 -0
- package/.agents/docs/SDLC.md +4 -2
- package/.agents/docs/agentrc-reference.json +10 -2
- package/.agents/docs/execution-reference.md +52 -0
- package/.agents/instructions.md +76 -38
- package/.agents/schemas/agentrc.schema.json +31 -3
- package/.agents/schemas/qa-ledger.schema.json +2 -2
- package/.agents/scripts/epic-deliver-prepare.js +41 -1
- package/.agents/scripts/lib/config/explain.js +4 -1
- package/.agents/scripts/lib/config-settings-schema.js +25 -1
- package/.agents/scripts/lib/epic-body-sections.js +88 -0
- package/.agents/scripts/lib/findings/promote-finding.js +3 -3
- package/.agents/scripts/lib/findings/severity.js +5 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +96 -11
- package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
- package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
- package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +7 -5
- package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +4 -22
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +9 -8
- package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +21 -81
- package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
- package/.agents/skills/skills.index.json +3 -3
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
- package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
- package/.agents/workflows/helpers/deliver-epic.md +164 -469
- package/.agents/workflows/helpers/epic-deliver-story.md +35 -11
- package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
- package/.agents/workflows/helpers/plan-epic.md +56 -186
- package/.agents/workflows/helpers/plan-story.md +31 -61
- package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
- package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
- package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
- package/.agents/workflows/helpers/single-story-deliver.md +128 -392
- package/.agents/workflows/qa-explore.md +63 -32
- package/.agents/workflows/qa-run.md +293 -130
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/.agents/schemas/qa-finding.schema.json +0 -133
|
@@ -7,6 +7,15 @@ description:
|
|
|
7
7
|
|
|
8
8
|
# /single-story-deliver #[Story ID]
|
|
9
9
|
|
|
10
|
+
> **Runtime core.** This file is the always-ingested standalone-Story path:
|
|
11
|
+
> step flow, commands, gate contracts, and the return contract. The lease /
|
|
12
|
+
> sweep mechanics, the worktree-scope safety warning, the CI-recovery
|
|
13
|
+
> procedures, and the Status-column reconciliation live in the sibling
|
|
14
|
+
> [`single-story-deliver-reference.md`](single-story-deliver-reference.md);
|
|
15
|
+
> each moved procedure keeps a one-line pointer at its trigger point below.
|
|
16
|
+
> The reference is not projected to `.claude/commands/` — it is consulted on
|
|
17
|
+
> demand.
|
|
18
|
+
|
|
10
19
|
## Overview
|
|
11
20
|
|
|
12
21
|
`/single-story-deliver` is the standalone counterpart to
|
|
@@ -59,7 +68,7 @@ node .agents/scripts/single-story-init.js --story <storyId>
|
|
|
59
68
|
```
|
|
60
69
|
|
|
61
70
|
Flags: `--dry-run` (no git/ticket mutation), `--steal` (forcibly transfer a
|
|
62
|
-
foreign Story lease to this operator — see the
|
|
71
|
+
foreign Story lease to this operator — see the lease note below).
|
|
63
72
|
|
|
64
73
|
> **Execution mode.** Like `story-init.js`, this command can take 3–6
|
|
65
74
|
> minutes when the worktree's per-tree install runs. Invoke synchronously
|
|
@@ -71,81 +80,22 @@ The script validates `type::story`, **acquires the Story lease**, fetches
|
|
|
71
80
|
`origin`, seeds `story-<id>` from `baseBranch`, materializes a worktree
|
|
72
81
|
(when `delivery.worktreeIsolation.enabled` is true), upserts a
|
|
73
82
|
`story-init` structured comment carrying `standalone: true`, and flips
|
|
74
|
-
the Story to `agent::executing`.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
>
|
|
79
|
-
>
|
|
80
|
-
>
|
|
81
|
-
>
|
|
82
|
-
>
|
|
83
|
-
>
|
|
84
|
-
>
|
|
85
|
-
>
|
|
86
|
-
> has **no Epic-scoped lifecycle ledger** to read a per-owner
|
|
87
|
-
> `story.heartbeat` from, so there is no live-heartbeat source to decide
|
|
88
|
-
> whether a foreign claim is stale. Rather than silently reclaim every
|
|
89
|
-
> foreign assignee (which would leave the guard inert), the standalone lease
|
|
90
|
-
> **fails closed**: a foreign assignee is treated as a *live* claim. Outcomes:
|
|
91
|
-
>
|
|
92
|
-
> - **Unclaimed / self-held** → init proceeds (a self-held claim is
|
|
93
|
-
> re-affirmed without re-writing assignees).
|
|
94
|
-
> - **Any foreign assignee** → init **exits non-zero** with a message naming
|
|
95
|
-
> the current owner. Coordinate with that operator, or pass **`--steal`** to
|
|
96
|
-
> forcibly transfer the claim once you have confirmed the other run is dead.
|
|
97
|
-
>
|
|
98
|
-
> `--dry-run` skips the lease (no assignee mutation). The matching release
|
|
99
|
-
> runs in `single-story-close.js` (Step 3).
|
|
100
|
-
|
|
101
|
-
Init is also idempotent on the Story branch itself:
|
|
102
|
-
|
|
103
|
-
> **Branch reuse (Story #3483).** When a `story-<id>` branch already exists
|
|
104
|
-
> locally, init **reuses** it rather than re-creating it (re-running
|
|
105
|
-
> `git branch` on an existing ref throws `branch already exists`). The
|
|
106
|
-
> seed decision (`reuse` / `fetch` / `create`) keys off local + remote ref
|
|
107
|
-
> presence, so re-running init on a partially-initialized Story is
|
|
108
|
-
> idempotent.
|
|
109
|
-
|
|
110
|
-
Between the fetch and the branch-seed step, the script also runs a
|
|
111
|
-
**merged-`story-*` sweep**: it invokes the same primitive as
|
|
112
|
-
`<agentRoot>/scripts/git-cleanup.js` (`<agentRoot>` resolves
|
|
113
|
-
from `project.paths.agentRoot`, default `.agents`) scoped to `story-*`
|
|
114
|
-
only, in `--execute --remote` mode, with the current run's
|
|
115
|
-
`story-<id>` branch excluded from the candidate list. Local refs, the
|
|
116
|
-
matching `origin/` ref, and stale tracking refs for any merged sibling
|
|
117
|
-
stories are reaped in one pass. The sweep never blocks init — failures
|
|
118
|
-
are logged and the new story is initialized regardless.
|
|
119
|
-
|
|
120
|
-
The sweep applies two hardening layers (Story #2011):
|
|
121
|
-
|
|
122
|
-
- **Per-candidate protection.** Each merged-PR candidate is filtered
|
|
123
|
-
through three guards before reaching `executeCleanup`:
|
|
124
|
-
- `unpushed-work` — branch HEAD SHA differs from the PR's
|
|
125
|
-
`headRefOid`, meaning the operator has commits the merge didn't
|
|
126
|
-
capture.
|
|
127
|
-
- `dirty-tree` — the attached worktree (if any) has uncommitted
|
|
128
|
-
changes.
|
|
129
|
-
- `ticket-not-done` — the parent Story ticket isn't closed and
|
|
130
|
-
doesn't carry `agent::done`.
|
|
131
|
-
Protected candidates are skipped, listed in the sweep result envelope
|
|
132
|
-
under `protected[]`, and named in the `CLEANUP` log line so the
|
|
133
|
-
operator can see what was preserved.
|
|
134
|
-
- **Cross-session lock.** The sweep acquires a process-scoped lockfile
|
|
135
|
-
at `<tempRoot>/single-story-sweep.lock` before planning. On
|
|
136
|
-
contention (another `/single-story-deliver` already in the sweep
|
|
137
|
-
step), this run's sweep is **skipped** with a warn log; init
|
|
138
|
-
continues normally. Stale lockfiles (mtime older than the timeout)
|
|
139
|
-
are treated as expired. The timeout defaults to 60 seconds and is
|
|
140
|
-
overridable via `delivery.worktreeIsolation.sweepLockMs` in
|
|
141
|
-
`.agentrc.json`.
|
|
142
|
-
|
|
143
|
-
Both layers are non-fatal — sweep failure / skip never blocks init, and
|
|
144
|
-
the new story is always created.
|
|
83
|
+
the Story to `agent::executing`. It also reuses an existing `story-<id>`
|
|
84
|
+
branch (idempotent re-init) and runs a **merged-`story-*` sweep** between
|
|
85
|
+
fetch and branch-seed.
|
|
86
|
+
|
|
87
|
+
> **Lease preflight, branch reuse, and merged-sweep.** The standalone lease
|
|
88
|
+
> **fails closed** on a foreign assignee (there is no Epic-scoped
|
|
89
|
+
> heartbeat ledger to judge staleness) — coordinate or pass `--steal`. The
|
|
90
|
+
> sweep is guarded (per-candidate protection + cross-session lock) and
|
|
91
|
+
> never blocks init. See
|
|
92
|
+
> [`single-story-deliver-reference.md` § Step 0 — Lease preflight and merged-sweep](single-story-deliver-reference.md#step-0--lease-preflight-and-merged-sweep)
|
|
93
|
+
> for the fail-closed outcomes, the `--steal` contract, and the sweep
|
|
94
|
+
> hardening layers.
|
|
145
95
|
|
|
146
96
|
Capture `workCwd` from the result envelope. Add `--dry-run` to inspect
|
|
147
97
|
the planned actions without git or ticket mutations (dry-run also skips
|
|
148
|
-
the sweep).
|
|
98
|
+
the lease and the sweep).
|
|
149
99
|
|
|
150
100
|
### Step 0.5 — `cd` into the workCwd
|
|
151
101
|
|
|
@@ -156,18 +106,11 @@ cd "<workCwd from Step 0 result>"
|
|
|
156
106
|
All subsequent commands run from this directory.
|
|
157
107
|
|
|
158
108
|
> **Worktree scope is not just the Bash cwd.** `cd <workCwd>` steers the
|
|
159
|
-
>
|
|
160
|
-
>
|
|
161
|
-
>
|
|
162
|
-
>
|
|
163
|
-
>
|
|
164
|
-
> prefix **every Edit/Write/Read path with the absolute worktree root**
|
|
165
|
-
> (the `workCwd` value from Step 0), not merely `cd` into it. Never edit
|
|
166
|
-
> files under the bare main-checkout root. `single-story-close.js` runs a
|
|
167
|
-
> **wrong-tree guard** (Story #3364) that aborts close and posts a
|
|
168
|
-
> `friction` comment if it finds uncommitted tracked-path edits in the main
|
|
169
|
-
> checkout while the worktree is the active work tree — but that is a
|
|
170
|
-
> backstop, not a substitute for prefixing paths correctly.
|
|
109
|
+
> Bash tool's cwd but does **not** scope the path-based Edit/Write/Read
|
|
110
|
+
> tools — you MUST prefix every such path with the absolute `workCwd` root or
|
|
111
|
+
> risk silently editing the main checkout. Close's wrong-tree guard (Story
|
|
112
|
+
> #3364) is a backstop, not a substitute. See
|
|
113
|
+
> [`single-story-deliver-reference.md` § Worktree scope is not just the Bash cwd](single-story-deliver-reference.md#worktree-scope-is-not-just-the-bash-cwd).
|
|
171
114
|
|
|
172
115
|
---
|
|
173
116
|
|
|
@@ -181,6 +124,17 @@ Story body.
|
|
|
181
124
|
Operator/agent responsibilities while in the worktree:
|
|
182
125
|
|
|
183
126
|
1. Read the Story body. Treat its acceptance criteria as the contract.
|
|
127
|
+
|
|
128
|
+
**Docs context — digest-first.** A standalone Story has no parent Epic,
|
|
129
|
+
so there is usually no per-Epic docs digest to read. If the caller
|
|
130
|
+
provides a `docsDigestPath` (the per-Epic docs digest at
|
|
131
|
+
`temp/epic-<epicId>/docs-digest.md`), read that compact outline instead
|
|
132
|
+
of re-reading the full `project.docsContextFiles` set, and pull
|
|
133
|
+
individual docs files on demand (jump to the section at the line number
|
|
134
|
+
the digest names). When no digest path is provided, read a full doc
|
|
135
|
+
only when the Story's own context points you at one — do not ingest the
|
|
136
|
+
whole docs set up front. See
|
|
137
|
+
[`.agents/instructions.md` § 3](../../instructions.md).
|
|
184
138
|
2. Implement the changes.
|
|
185
139
|
3. Commit on the Story branch. Conventional-commit format is encouraged
|
|
186
140
|
but not enforced — the PR title carries the canonical summary.
|
|
@@ -265,76 +219,29 @@ the worktree):
|
|
|
265
219
|
node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo>
|
|
266
220
|
```
|
|
267
221
|
|
|
268
|
-
The script
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Note: the merge queue (when enabled) re-tests each PR against the
|
|
292
|
-
queue tip before merging, so this sync + merge queue is the complete
|
|
293
|
-
defence against the parallel race. Without merge queue, the sync
|
|
294
|
-
closes the PR-open-time race but a residual race remains between PR
|
|
295
|
-
open and auto-merge fire.
|
|
296
|
-
2. Pushes `story-<id>` to `origin`.
|
|
297
|
-
3. Probes for an existing open PR with `head = story-<id>`. If none
|
|
298
|
-
exists, opens one via `gh pr create --base <baseBranch>`. The PR
|
|
299
|
-
body carries `Closes #<storyId>` so the GitHub merge auto-closes the
|
|
300
|
-
issue.
|
|
301
|
-
3a. **Enables GitHub native auto-merge by default** via
|
|
302
|
-
`gh pr merge <prNumber> --auto --squash --delete-branch`. Once CI's
|
|
303
|
-
required checks turn green, GitHub squash-merges the PR and deletes
|
|
304
|
-
the source branch — the operator does not need to babysit the merge
|
|
305
|
-
button. Mirrors the `/deliver` finalize path. Failure is
|
|
306
|
-
non-fatal: the operator retains the manual merge surface in the
|
|
307
|
-
GitHub UI. Pass `--no-auto-merge` to opt out when the PR needs a
|
|
308
|
-
pre-merge eyeball.
|
|
309
|
-
4. Flips the Story to **`agent::closing`** (NOT `agent::done`) and leaves
|
|
310
|
-
the GitHub issue **OPEN** (Story #3385). Auto-merge completes
|
|
311
|
-
asynchronously *after* this script exits, so closing the issue here
|
|
312
|
-
would strand a CLOSED issue with no merged work if the PR later failed
|
|
313
|
-
CI, went `BEHIND` base, or was closed without merging. The Story rests
|
|
314
|
-
at `agent::closing` while the PR is open with auto-merge armed; the
|
|
315
|
-
`agent::done` flip (which closes the issue) is deferred to Step 5.5's
|
|
316
|
-
`single-story-confirm-merge.js`. This brings the standalone path to
|
|
317
|
-
parity with the epic path (#2155), where a Story only reaches
|
|
318
|
-
`agent::done` once its merge into `epic/<id>` is confirmed.
|
|
319
|
-
5. Reaps the worktree when `delivery.worktreeIsolation.reapOnSuccess`
|
|
320
|
-
is enabled.
|
|
321
|
-
6. **Releases the Story lease** (Story #3483). Clears the Story assignment
|
|
322
|
-
that init claimed so the next `/single-story-deliver` run sees an
|
|
323
|
-
unclaimed ticket. The release is a no-op when the operator no longer
|
|
324
|
-
holds the claim (a later run took over via reclaim/steal), so a late
|
|
325
|
-
close never yanks a live claim away from its current owner. Best-effort:
|
|
326
|
-
a release failure is logged but does not fail an otherwise-clean close —
|
|
327
|
-
the lease goes stale via TTL regardless. The close result carries
|
|
328
|
-
`leaseReleased: <boolean>`.
|
|
329
|
-
|
|
330
|
-
`--skip-validation` bypasses Step 1 (gates). Use only when re-running
|
|
331
|
-
close after a fixed gate failure that's already known to pass.
|
|
332
|
-
|
|
333
|
-
`--skip-sync` bypasses Step 1a (base-sync). Use only when re-running
|
|
334
|
-
close after a hand-resolved sync, or in tests.
|
|
335
|
-
|
|
336
|
-
`--no-auto-merge` disables Step 3a. Use when the PR materially changes
|
|
337
|
-
behaviour and warrants pre-merge review.
|
|
222
|
+
The script runs the close-validation gates against `baseBranch`, syncs the
|
|
223
|
+
Story branch from `origin/<baseBranch>` (Story #2580 — the parallel-race
|
|
224
|
+
defence), pushes `story-<id>`, opens (or reuses) a PR against `baseBranch`
|
|
225
|
+
with a `Closes #<storyId>` footer, enables GitHub native auto-merge
|
|
226
|
+
(`--auto --squash --delete-branch`), flips the Story to **`agent::closing`**
|
|
227
|
+
(NOT `agent::done` — the issue stays OPEN until Step 5 confirms the merge,
|
|
228
|
+
Story #3385), reaps the worktree, and releases the Story lease.
|
|
229
|
+
|
|
230
|
+
Flags:
|
|
231
|
+
|
|
232
|
+
- `--skip-validation` — bypass the gates (Step 1). Use only when re-running
|
|
233
|
+
close after a fixed gate failure that's already known to pass.
|
|
234
|
+
- `--skip-sync` — bypass the base-sync (Story #2580). Use only after a
|
|
235
|
+
hand-resolved sync, or in tests.
|
|
236
|
+
- `--no-auto-merge` — disable auto-merge. Use when the PR materially changes
|
|
237
|
+
behaviour and warrants a pre-merge eyeball; the operator then merges via
|
|
238
|
+
the GitHub UI.
|
|
239
|
+
|
|
240
|
+
> **Full close pipeline (base-sync outcomes, `agent::closing` rationale,
|
|
241
|
+
> lease release).** For the numbered close pipeline, the base-sync outcome
|
|
242
|
+
> table (no-op / conflict → `agent::blocked` / fetch-failed), and why the
|
|
243
|
+
> issue stays OPEN at `agent::closing`, see
|
|
244
|
+
> [`single-story-deliver-reference.md` § Step 3 — Close pipeline detail](single-story-deliver-reference.md#step-3--close-pipeline-detail).
|
|
338
245
|
|
|
339
246
|
---
|
|
340
247
|
|
|
@@ -342,31 +249,19 @@ behaviour and warrants pre-merge review.
|
|
|
342
249
|
|
|
343
250
|
The Story is **not done** when `single-story-close.js` returns. Auto-merge
|
|
344
251
|
only fires when every required CI check turns green. Local close-validation
|
|
345
|
-
gates pass on the dev host's environment
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
outcome, not just the push.
|
|
252
|
+
gates pass on the dev host's environment; CI runs on a different OS and
|
|
253
|
+
concurrency, and coverage rounding, platform-conditional branches, and
|
|
254
|
+
timing-sensitive tests routinely drift between the two. The agent owns the
|
|
255
|
+
green-CI outcome, not just the push.
|
|
350
256
|
|
|
351
257
|
> **The auto-merge wait is an internally-blocking step, not a reason to end
|
|
352
|
-
> your turn.**
|
|
353
|
-
>
|
|
354
|
-
>
|
|
355
|
-
>
|
|
356
|
-
>
|
|
357
|
-
>
|
|
358
|
-
>
|
|
359
|
-
> CI resolves — that is the mechanism by which you wait. You MUST keep your
|
|
360
|
-
> turn alive across the wait: watch → (fix + push + re-watch on red) → confirm
|
|
361
|
-
> the merge (Step 5) → flip `agent::done` → run the post-merge steps → and
|
|
362
|
-
> only then return the terminal JSON status contract (Step 4 of
|
|
363
|
-
> [`deliver-stories.md` § 2c](deliver-stories.md), mirrored in
|
|
364
|
-
> [§ Return contract](#return-contract) for the standalone caller). The CI
|
|
365
|
-
> wait NEVER terminates your turn; **only** a confirmed-`MERGED` PR (→
|
|
366
|
-
> `status: "done"`), an `agent::blocked` transition (→ `status: "blocked"`),
|
|
367
|
-
> or an unrecoverable failure (→ `status: "failed"`) does. Ending your turn
|
|
368
|
-
> with prose and an unconfirmed merge is a contract violation — it is the very
|
|
369
|
-
> bug this workflow exists to prevent.
|
|
258
|
+
> your turn.** `gh pr checks <prNumber> --watch` blocks the current turn
|
|
259
|
+
> until CI resolves — that IS how you wait. Keep the turn alive: watch →
|
|
260
|
+
> (fix + push + re-watch on red) → confirm the merge (Step 5) → flip
|
|
261
|
+
> `agent::done` → post-merge steps → return the terminal JSON contract.
|
|
262
|
+
> Ending the turn with prose and an unconfirmed merge is a contract
|
|
263
|
+
> violation (the Story #1553 / PR #1554 failure mode). See
|
|
264
|
+
> [`single-story-deliver-reference.md` § The auto-merge wait is an internally-blocking step](single-story-deliver-reference.md#the-auto-merge-wait-is-an-internally-blocking-step).
|
|
370
265
|
|
|
371
266
|
After `single-story-close.js` succeeds, enter the watch + fix loop:
|
|
372
267
|
|
|
@@ -377,80 +272,19 @@ gh pr checks <prNumber> --watch
|
|
|
377
272
|
When the watch exits:
|
|
378
273
|
|
|
379
274
|
- **All checks ✓** — auto-merge will fire (or has already). The Story is
|
|
380
|
-
still at `agent::closing` with its issue OPEN
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
flips the Story to `agent::done`. **Proceed to Step 5 within the same
|
|
384
|
-
turn** — do not end your turn here. Green CI is the *start* of the
|
|
385
|
-
merge-confirm sequence, not a terminal state (see Step 7's no-park rule).
|
|
275
|
+
still at `agent::closing` with its issue OPEN. **Proceed to Step 5 within
|
|
276
|
+
the same turn** — green CI is the *start* of the merge-confirm sequence,
|
|
277
|
+
not a terminal state.
|
|
386
278
|
- **Any check ✗** — diagnose, fix, and push a new commit on
|
|
387
279
|
`story-<storyId>`, then re-watch. Auto-merge stays enabled across
|
|
388
280
|
retries; no need to re-arm it. The Story stays at `agent::closing`
|
|
389
281
|
throughout, so a failed/abandoned PR never strands a CLOSED issue.
|
|
390
282
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
```bash
|
|
398
|
-
cd <main-repo>
|
|
399
|
-
git fetch origin story-<storyId>
|
|
400
|
-
git worktree add .worktrees/story-<storyId> story-<storyId>
|
|
401
|
-
cd .worktrees/story-<storyId>
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
Do **not** re-run `single-story-init.js` — it would reset the branch
|
|
405
|
-
state and lose the close commit's structured comment.
|
|
406
|
-
|
|
407
|
-
### Diagnosing the failure
|
|
408
|
-
|
|
409
|
-
Pull the failing job log via:
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
gh run view <runId> --repo <owner>/<repo> --log-failed
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
The `<runId>` is the run number that `gh pr checks` shows in the
|
|
416
|
-
failing row's URL. Read the bottom of the log — the gate that exited
|
|
417
|
-
non-zero is named there (e.g. `[Coverage] ❌ REGRESSION in …`).
|
|
418
|
-
|
|
419
|
-
### Fixing without re-running close-validation
|
|
420
|
-
|
|
421
|
-
For coverage / maintainability / CRAP regressions detected only on CI:
|
|
422
|
-
|
|
423
|
-
1. Update the relevant baseline file (`baselines/coverage.json`,
|
|
424
|
-
`baselines/maintainability.json`, `baselines/crap.json`) to absorb
|
|
425
|
-
CI's actual numbers. Edit by hand when CI's numbers are within the
|
|
426
|
-
tolerance you'd otherwise accept — don't re-run `npm run … :update`
|
|
427
|
-
locally, because Windows numbers will overwrite CI's Linux numbers
|
|
428
|
-
and the cycle repeats.
|
|
429
|
-
2. Commit the baseline delta with a `chore(baselines):` message that
|
|
430
|
-
names the CI run that produced the values.
|
|
431
|
-
3. `git push` to `origin/story-<storyId>` and re-watch.
|
|
432
|
-
|
|
433
|
-
For genuine test failures (a flaky test, a platform-conditional bug):
|
|
434
|
-
fix the code or test, commit, push, re-watch. Keep iterating until
|
|
435
|
-
the watch exits clean.
|
|
436
|
-
|
|
437
|
-
### When to stop iterating
|
|
438
|
-
|
|
439
|
-
- **Three consecutive failures with the same fix shape** — stop and
|
|
440
|
-
Re-Plan per Anti-Thrashing Protocol. The diagnosis is likely wrong.
|
|
441
|
-
- **Operator-blocking failure** (security scanner, branch-protection
|
|
442
|
-
rule the agent can't change) — transition the Story to
|
|
443
|
-
`agent::blocked`, summarize the blocker on the PR, and yield to the
|
|
444
|
-
operator.
|
|
445
|
-
|
|
446
|
-
### Idempotence of the loop
|
|
447
|
-
|
|
448
|
-
- The PR stays open across retries; `gh pr create` is a one-shot at
|
|
449
|
-
close, the loop only pushes new commits.
|
|
450
|
-
- Auto-merge stays armed across retries — pushing a new commit does
|
|
451
|
-
not disarm `gh pr merge --auto`.
|
|
452
|
-
- If the operator manually merges or disables auto-merge mid-loop,
|
|
453
|
-
exit the loop and report.
|
|
283
|
+
> **CI recovery procedures.** For resurrecting the worktree after
|
|
284
|
+
> `reapOnSuccess`, pulling the failing job log, fixing coverage/CRAP
|
|
285
|
+
> baselines without re-running close-validation, and the when-to-stop
|
|
286
|
+
> Anti-Thrashing rules, see
|
|
287
|
+
> [`single-story-deliver-reference.md` § Step 4 — CI watch + fix recovery](single-story-deliver-reference.md#step-4--ci-watch--fix-recovery).
|
|
454
288
|
|
|
455
289
|
---
|
|
456
290
|
|
|
@@ -467,117 +301,55 @@ gh pr view <prNumber> --json state,mergedAt,mergeCommit
|
|
|
467
301
|
```
|
|
468
302
|
|
|
469
303
|
Expect `state: "MERGED"`. With `--no-auto-merge`, the PR is the merge
|
|
470
|
-
gate
|
|
304
|
+
gate — the operator reviews and merges via the GitHub UI; the same
|
|
471
305
|
`Closes #<id>` auto-close fires when the merge lands on `main`.
|
|
472
306
|
|
|
473
307
|
**Then flip the Story to `agent::done`.** Step 3 deferred this flip
|
|
474
|
-
(Story #3385)
|
|
475
|
-
while the PR was open. Now that the merge is confirmed, drive the
|
|
308
|
+
(Story #3385); now that the merge is confirmed, drive the
|
|
476
309
|
`agent::closing → agent::done` transition (which closes the issue) via:
|
|
477
310
|
|
|
478
311
|
```bash
|
|
479
312
|
node .agents/scripts/single-story-confirm-merge.js --story <storyId> --cwd <main-repo>
|
|
480
313
|
```
|
|
481
314
|
|
|
482
|
-
|
|
483
|
-
state
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
issue, and fires the `story-merged` notify. Prints
|
|
488
|
-
`{ action: 'done', merged: true, ... }`.
|
|
489
|
-
- **PR still open / closed-without-merge** → leaves the Story at
|
|
490
|
-
`agent::closing` (issue stays OPEN) and prints
|
|
491
|
-
`{ action: 'pending', reason: 'pr-open' | 'pr-not-merged' | 'no-pr' }`.
|
|
492
|
-
Re-run after the merge lands.
|
|
493
|
-
- **Story already `agent::done` / issue already closed** → idempotent
|
|
494
|
-
`{ action: 'noop', reason: 'already-done' }`.
|
|
495
|
-
|
|
496
|
-
This is the standalone counterpart to the epic path's post-merge
|
|
497
|
-
`agent::done` flip in `post-merge-close.js` (#2155): the issue closes
|
|
498
|
-
exactly when the work has merged, never at PR-open.
|
|
315
|
+
> **Confirmation outcomes.** `single-story-confirm-merge.js` re-reads the
|
|
316
|
+
> live PR state and flips to `agent::done` only on a confirmed `MERGED` PR;
|
|
317
|
+
> it is idempotent and safe to re-run while the PR is still open (returns
|
|
318
|
+
> `pending`). See
|
|
319
|
+
> [`single-story-deliver-reference.md` § Step 5 — Merge confirmation detail](single-story-deliver-reference.md#step-5--merge-confirmation-detail).
|
|
499
320
|
|
|
500
321
|
---
|
|
501
322
|
|
|
502
323
|
## Step 5.5 — Re-assert Status column (**required, not optional**)
|
|
503
324
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
field as a side-effect, clobbering the `Done` value
|
|
508
|
-
`single-story-confirm-merge.js` set at the `agent::done` flip in Step 5
|
|
509
|
-
and leaving closed Stories stuck at `In Progress` on the board
|
|
510
|
-
(reproduced on Story #2813). The confirmation step has already exited by
|
|
511
|
-
then, so the bot gets the last write.
|
|
512
|
-
|
|
325
|
+
GitHub Projects v2 built-in workflows fire minutes *after* auto-merge lands
|
|
326
|
+
and clobber the `Done` Status the confirm step set, stranding closed
|
|
327
|
+
Stories at `In Progress` on the board (reproduced on Story #2813).
|
|
513
328
|
Re-assert authority once the merge confirms:
|
|
514
329
|
|
|
515
330
|
```bash
|
|
516
331
|
node .agents/scripts/resync-status-column.js --story <storyId>
|
|
517
332
|
```
|
|
518
333
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
-
|
|
522
|
-
|
|
523
|
-
at close, overwriting the bot's late write.
|
|
524
|
-
- **Polls the live Status for ~15 s after the initial write** and
|
|
525
|
-
re-fires on drift (Story #2876). Without this loop, a one-shot
|
|
526
|
-
mutation routinely lost the race against the bot's asynchronous
|
|
527
|
-
fire (reproduced on Story #2871 / PR #2872).
|
|
528
|
-
- Prints a single-line JSON envelope:
|
|
529
|
-
`{ ticketId, status, column?, reason?, attempts? }`. `attempts > 1`
|
|
530
|
-
means the helper had to fight a bot overwrite; `status: 'drifted'`
|
|
531
|
-
means the bot won every attempt in the poll budget (rare; usually
|
|
532
|
-
signals operator should reap the conflicting workflows).
|
|
533
|
-
|
|
534
|
-
Tuning flags (rarely needed):
|
|
535
|
-
|
|
536
|
-
- `--poll-attempts <n>` — total mutation attempts including the
|
|
537
|
-
initial sync. Default `4`. Pass `1` to disable the poll loop
|
|
538
|
-
(fastest, matches pre-#2876 behaviour).
|
|
539
|
-
- `--poll-delay-ms <ms>` — delay between drift checks. Default `5000`.
|
|
540
|
-
|
|
541
|
-
Idempotent: re-running on a ticket whose Status already matches the
|
|
542
|
-
target returns the same envelope. No-op skips (`no-project`,
|
|
543
|
-
`no-meta`, `not-on-project`) exit 0 with the reason in the envelope
|
|
544
|
-
so the workflow can continue.
|
|
545
|
-
|
|
546
|
-
**Canonical operator fix:** run
|
|
547
|
-
`node .agents/scripts/agents-bootstrap-github.js --reap-conflicting-workflows`
|
|
548
|
-
once per project to delete the conflicting bot workflows entirely.
|
|
549
|
-
This eliminates the race source; the poll loop becomes pure
|
|
550
|
-
defense-in-depth against re-enabled or future workflows.
|
|
551
|
-
|
|
552
|
-
Skip Step 5.5 only when the operator opted out of auto-merge AND has
|
|
553
|
-
not yet merged the PR (no `agent::done` to re-assert yet) — run it
|
|
334
|
+
The helper re-fires the `ColumnSync` mutation and **polls for ~15 s** to win
|
|
335
|
+
the race against the bot's late write (Story #2876). It is idempotent and
|
|
336
|
+
no-op-safe (`no-project` / `not-on-project` exit 0). Skip Step 5.5 only when
|
|
337
|
+
the operator opted out of auto-merge AND has not yet merged the PR — run it
|
|
554
338
|
after the manual merge instead.
|
|
555
339
|
|
|
340
|
+
> **Status-column detail + tuning flags + operator fix.** For the poll-loop
|
|
341
|
+
> flags (`--poll-attempts`, `--poll-delay-ms`), the `attempts` / `drifted`
|
|
342
|
+
> envelope semantics, and the canonical
|
|
343
|
+
> `--reap-conflicting-workflows` operator fix, see
|
|
344
|
+
> [`single-story-deliver-reference.md` § Step 5.5 — Re-assert Status column detail](single-story-deliver-reference.md#step-55--re-assert-status-column-detail).
|
|
345
|
+
|
|
556
346
|
---
|
|
557
347
|
|
|
558
348
|
## Step 6 — Local branch cleanup (**required, not optional**)
|
|
559
349
|
|
|
560
|
-
GitHub deletes the **remote** branch on auto-merge
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
checkout until something prunes it — `single-story-init.js` runs a
|
|
564
|
-
merged-sweep at the start of every *subsequent* `/single-story-deliver`
|
|
565
|
-
invocation, but that's next-run cleanup, not end-of-run cleanup. Stale
|
|
566
|
-
local refs accumulate between sessions, clutter `git branch`, and shadow
|
|
567
|
-
the lessons the sweep is meant to surface.
|
|
568
|
-
|
|
569
|
-
**Why local `main` goes stale:** `single-story-init.js` seeds new
|
|
570
|
-
`story-<id>` branches from the **local** `baseBranch` ref (default
|
|
571
|
-
`main`). Auto-merge updates **`origin/main`** on GitHub; nothing in
|
|
572
|
-
close or the old Step 6 command updated **local `main`**. The next init
|
|
573
|
-
then forked from a tip six merges behind until you manually pulled.
|
|
574
|
-
`single-story-init` also attempts the same fast-forward after `git fetch`
|
|
575
|
-
when the main checkout is clean (defense in depth if Step 6 was skipped).
|
|
576
|
-
Step 6 must still run `--fast-forward-main` so local `main` is current
|
|
577
|
-
before the next session — init may skip when the tree is dirty or the
|
|
578
|
-
operator is mid-checkout on another branch.
|
|
579
|
-
|
|
580
|
-
After Step 5 confirms `state: "MERGED"`, prune the story ref **and**
|
|
350
|
+
GitHub deletes the **remote** branch on auto-merge, but the **local**
|
|
351
|
+
`story-<storyId>` ref lingers in the main checkout until something prunes
|
|
352
|
+
it. After Step 5 confirms `state: "MERGED"`, prune the story ref **and**
|
|
581
353
|
fast-forward local `main` (or `project.baseBranch`):
|
|
582
354
|
|
|
583
355
|
```bash
|
|
@@ -590,29 +362,17 @@ node .agents/scripts/git-cleanup.js \
|
|
|
590
362
|
--include "story-<storyId>"
|
|
591
363
|
```
|
|
592
364
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
- **`--execute --remote --yes`** actually deletes the local ref, prunes
|
|
605
|
-
the matching `origin/` tracking ref, and runs non-interactively.
|
|
606
|
-
|
|
607
|
-
The sweep is idempotent. It is safe to run before `state: "MERGED"`
|
|
608
|
-
confirms (it will skip a not-yet-merged branch), and safe to re-run
|
|
609
|
-
after a successful cleanup (it reports "no merged branches to clean
|
|
610
|
-
up").
|
|
611
|
-
|
|
612
|
-
Skip Step 6 only when the operator has explicitly opted out via
|
|
613
|
-
`--no-auto-merge` AND has not yet merged the PR — in that case, leave
|
|
614
|
-
the branch in place until the manual merge lands, then run the
|
|
615
|
-
cleanup.
|
|
365
|
+
`--fast-forward-main` brings local `main` current (the next init seeds from
|
|
366
|
+
it), `--branches` + `--include` reap only this Story's ref, and
|
|
367
|
+
`--execute --remote --yes` run the deletes non-interactively. The sweep is
|
|
368
|
+
idempotent and safe to run before `MERGED` confirms. Skip Step 6 only when
|
|
369
|
+
the operator opted out via `--no-auto-merge` AND has not yet merged the PR —
|
|
370
|
+
run the cleanup after the manual merge lands.
|
|
371
|
+
|
|
372
|
+
> **Why local `main` goes stale + per-flag behaviour.** For the stale-`main`
|
|
373
|
+
> mechanism and the full `--fast-forward-main` / `--branches` / `--include`
|
|
374
|
+
> flag semantics, see
|
|
375
|
+
> [`single-story-deliver-reference.md` § Step 6 — Local branch cleanup detail](single-story-deliver-reference.md#step-6--local-branch-cleanup-detail).
|
|
616
376
|
|
|
617
377
|
---
|
|
618
378
|
|
|
@@ -640,40 +400,17 @@ mandates; this section is its single-homed restatement for the standalone
|
|
|
640
400
|
worker so the contract is self-contained when this workflow is the entry
|
|
641
401
|
point.
|
|
642
402
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
-
|
|
653
|
-
|
|
654
|
-
base-sync conflict, or an operator-blocking CI failure / Anti-Thrashing
|
|
655
|
-
stop in Step 4). `phase: "blocked"`, `blockerCommentId` set.
|
|
656
|
-
- **`status: "failed"`** — an unrecoverable failure outside the blocked
|
|
657
|
-
protocol. `phase` reflects where it died.
|
|
658
|
-
|
|
659
|
-
A turn that ends with prose ("I'll wait for the watch task…", "the next event
|
|
660
|
-
will be its completion notification…") and an **unconfirmed merge** is a
|
|
661
|
-
**contract violation** (the Story #1553 / PR #1554 failure mode): the parent
|
|
662
|
-
wave loop cannot distinguish "still working" from "done but silent", and the
|
|
663
|
-
Story strands at `agent::closing`. If you genuinely cannot confirm the merge,
|
|
664
|
-
that is a `blocked` or `failed` outcome with the JSON contract above — not a
|
|
665
|
-
prose hand-off.
|
|
666
|
-
|
|
667
|
-
> **Handoff discipline — report state, not process.** Populate the envelope
|
|
668
|
-
> with essential terminal state only (mirroring the fields
|
|
669
|
-
> `single-story-close.js` / `story-phase.js` already emit). Do not narrate the
|
|
670
|
-
> steps you took, and do not prescribe how the next stage should work. Prose
|
|
671
|
-
> process commentary only bloats the hydrated prompt
|
|
672
|
-
> (`delivery.maxTokenBudget` elision). When run **interactively** (no parent
|
|
673
|
-
> aggregator), this JSON envelope is optional — relay terminal state to the
|
|
674
|
-
> operator in prose instead — but the **no-park rule still holds**: never end
|
|
675
|
-
> an interactive turn with an unconfirmed merge either; block on the watch,
|
|
676
|
-
> confirm, and report the merged outcome.
|
|
403
|
+
There is **no fourth "pending" status** — the CI/auto-merge wait is handled
|
|
404
|
+
internally by blocking on `gh pr checks --watch` (Step 4) and confirming the
|
|
405
|
+
merge (Step 5). Return **only** on a confirmed `MERGED` PR (`status: "done"`),
|
|
406
|
+
an `agent::blocked` transition (`status: "blocked"`), or an unrecoverable
|
|
407
|
+
failure (`status: "failed"`).
|
|
408
|
+
|
|
409
|
+
> **No-park rule + per-status contract + handoff discipline.** For the full
|
|
410
|
+
> terminal-status contract (what each status requires), why a prose hand-off
|
|
411
|
+
> with an unconfirmed merge is the very bug this workflow prevents, and the
|
|
412
|
+
> report-state-not-process handoff discipline, see
|
|
413
|
+
> [`single-story-deliver-reference.md` § Step 7 — Return-contract detail](single-story-deliver-reference.md#step-7--return-contract-detail).
|
|
677
414
|
|
|
678
415
|
---
|
|
679
416
|
|
|
@@ -705,9 +442,6 @@ safe.
|
|
|
705
442
|
`workCwd` root — the `cd` alone does not scope the path-based tools (see
|
|
706
443
|
Step 0.5). Editing a bare main-checkout path lands the change in the wrong
|
|
707
444
|
tree; close's wrong-tree guard (Story #3364) aborts when it detects this.
|
|
708
|
-
- **Always** pass `--cwd <main-repo>` to `single-story-close.js` when
|
|
709
|
-
invoking from inside a worktree (worktree-local branch deletion fails
|
|
710
|
-
when run from inside the worktree).
|
|
711
445
|
- **Handoff discipline — report state, not process.** When you hand back to
|
|
712
446
|
your caller (the `/deliver` aggregator or the interactive operator),
|
|
713
447
|
report essential terminal state only: the Story branch, the closing commit
|
|
@@ -730,3 +464,5 @@ safe.
|
|
|
730
464
|
- [`/deliver`](deliver-stories.md) — several standalone Stories at
|
|
731
465
|
once (dependency-aware waves).
|
|
732
466
|
- [`/deliver`](deliver-epic.md) — full Epic wave loop.
|
|
467
|
+
- [`single-story-deliver-reference.md`](single-story-deliver-reference.md) —
|
|
468
|
+
lease, sweep, CI-recovery, and Status-column reference detail.
|