mandrel 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.agents/agents/acceptance-critic.md +13 -19
  2. package/.agents/agents/auditor.md +16 -19
  3. package/.agents/agents/story-worker.md +41 -54
  4. package/.agents/audit-checklists/architecture.md +5 -1
  5. package/.agents/audit-checklists/quality.md +3 -0
  6. package/.agents/docs/agentrc-reference.json +2 -1
  7. package/.agents/docs/configuration.md +2 -1
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/rules/ci-remediation.md +68 -3
  10. package/.agents/schemas/agentrc.schema.json +6 -1
  11. package/.agents/schemas/baselines/baseline-envelope.schema.json +4 -0
  12. package/.agents/schemas/baselines/crap.schema.json +5 -1
  13. package/.agents/schemas/crap-report.schema.json +37 -0
  14. package/.agents/schemas/story-deliver-terminal.schema.json +6 -1
  15. package/.agents/scripts/acceptance-eval.js +35 -9
  16. package/.agents/scripts/audit-labels-bootstrap.js +16 -95
  17. package/.agents/scripts/audit-to-stories.js +33 -3
  18. package/.agents/scripts/check-context-budget.js +95 -14
  19. package/.agents/scripts/check-test-temp-hygiene.js +83 -0
  20. package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
  21. package/.agents/scripts/deliver-light.js +22 -45
  22. package/.agents/scripts/deliver-recover.js +13 -0
  23. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +10 -25
  24. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
  25. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
  26. package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
  27. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
  28. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
  29. package/.agents/scripts/lib/baseline-loader.js +0 -0
  30. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  31. package/.agents/scripts/lib/baselines/kinds/crap.js +396 -51
  32. package/.agents/scripts/lib/baselines/kinds/maintainability.js +20 -32
  33. package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
  34. package/.agents/scripts/lib/baselines/reader.js +10 -0
  35. package/.agents/scripts/lib/baselines/writer.js +7 -3
  36. package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
  37. package/.agents/scripts/lib/crap-engine.js +97 -19
  38. package/.agents/scripts/lib/crap-utils.js +73 -13
  39. package/.agents/scripts/lib/env-loader.js +46 -16
  40. package/.agents/scripts/lib/escomplex-ast-compat.js +360 -0
  41. package/.agents/scripts/lib/findings/route-finding.js +123 -17
  42. package/.agents/scripts/lib/findings/severity.js +80 -2
  43. package/.agents/scripts/lib/git/cached-fetch.js +0 -0
  44. package/.agents/scripts/lib/label-constants.js +17 -0
  45. package/.agents/scripts/lib/maintainability-engine.js +83 -11
  46. package/.agents/scripts/lib/maintainability-unscorable.js +60 -0
  47. package/.agents/scripts/lib/maintainability-utils.js +14 -5
  48. package/.agents/scripts/lib/observability/runtime-friction.js +8 -0
  49. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
  50. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
  51. package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
  52. package/.agents/scripts/lib/orchestration/diff-magnitude.js +283 -0
  53. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
  54. package/.agents/scripts/lib/orchestration/light-backstop.js +124 -0
  55. package/.agents/scripts/lib/orchestration/light-escalation.js +248 -0
  56. package/.agents/scripts/lib/orchestration/light-suitability.js +254 -51
  57. package/.agents/scripts/lib/orchestration/plan-context.js +12 -13
  58. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +7 -0
  59. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +13 -1
  60. package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
  61. package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
  62. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
  63. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +96 -34
  64. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +70 -2
  65. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
  66. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +73 -18
  67. package/.agents/scripts/lib/reserved-test-ids.js +77 -0
  68. package/.agents/scripts/lib/story-plan.js +137 -42
  69. package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
  70. package/.agents/scripts/lib/ticket-body-sections.js +1 -1
  71. package/.agents/scripts/lib/util/poll-loop.js +18 -2
  72. package/.agents/scripts/lib/wave-runner/live-probe.js +7 -1
  73. package/.agents/scripts/lib/wave-runner/ready-set.js +73 -9
  74. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +6 -3
  75. package/.agents/scripts/lib/workers/crap-worker.js +6 -3
  76. package/.agents/scripts/lib/workers/maintainability-worker.js +14 -9
  77. package/.agents/scripts/pr-watch-with-update.js +568 -213
  78. package/.agents/scripts/quality-preview.js +25 -0
  79. package/.agents/scripts/run-tests.js +10 -0
  80. package/.agents/scripts/single-story-close.js +6 -0
  81. package/.agents/scripts/single-story-init.js +7 -0
  82. package/.agents/scripts/stories-wave-tick.js +95 -10
  83. package/.agents/scripts/story-plan.js +65 -9
  84. package/.agents/scripts/update-crap-baseline.js +42 -4
  85. package/.agents/scripts/update-maintainability-baseline.js +31 -4
  86. package/.agents/templates/single-story-body.md +16 -8
  87. package/.agents/workflows/audit-architecture.md +69 -13
  88. package/.agents/workflows/audit-quality.md +26 -0
  89. package/.agents/workflows/audit-to-stories.md +30 -4
  90. package/.agents/workflows/deliver.md +81 -95
  91. package/.agents/workflows/helpers/audit-lens-core.md +25 -4
  92. package/.agents/workflows/helpers/deliver-light.md +21 -4
  93. package/.agents/workflows/helpers/deliver-reference.md +18 -3
  94. package/.agents/workflows/helpers/deliver-story-reference.md +127 -46
  95. package/.agents/workflows/helpers/deliver-story.md +104 -119
  96. package/.agents/workflows/helpers/plan-reference.md +34 -0
  97. package/.agents/workflows/plan.md +60 -76
  98. package/docs/CHANGELOG.md +38 -0
  99. package/lib/cli/update.js +14 -11
  100. package/lib/cli/version-check.js +9 -1
  101. package/package.json +1 -1
@@ -27,7 +27,7 @@ caller: helpers/deliver-story.md
27
27
 
28
28
  Before any git mutation, init takes an exclusive, time-bounded **lease** on
29
29
  the Story ticket via the assignee-as-lease primitive
30
- (`lib/orchestration/ticket-lease.js`). The single assignee *is* the lease
30
+ (`lib/orchestration/ticket-lease.js`). The single assignee _is_ the lease
31
31
  owner (resolved from `github.operatorHandle`). The standalone path has no
32
32
  Epic-scoped dispatch manifest to serialise two operators driving the same
33
33
  Story, so this lease is the only guard against a concurrent
@@ -38,7 +38,7 @@ has **no Epic-scoped lifecycle ledger** to read a per-owner
38
38
  `story.heartbeat` from, so there is no live-heartbeat source to decide
39
39
  whether a foreign claim is stale. Rather than silently reclaim every
40
40
  foreign assignee (which would leave the guard inert), the standalone lease
41
- **fails closed**: a foreign assignee is treated as a *live* claim. Outcomes:
41
+ **fails closed**: a foreign assignee is treated as a _live_ claim. Outcomes:
42
42
 
43
43
  - **Unclaimed / self-held** → init proceeds (a self-held claim is
44
44
  re-affirmed without re-writing assignees).
@@ -80,9 +80,9 @@ The sweep applies two hardening layers:
80
80
  changes.
81
81
  - `ticket-not-done` — the parent Story ticket isn't closed and
82
82
  doesn't carry `agent::done`.
83
- Protected candidates are skipped, listed in the sweep result envelope
84
- under `protected[]`, and named in the `CLEANUP` log line so the
85
- operator can see what was preserved.
83
+ Protected candidates are skipped, listed in the sweep result envelope
84
+ under `protected[]`, and named in the `CLEANUP` log line so the
85
+ operator can see what was preserved.
86
86
  - **Cross-session lock.** The sweep acquires a process-scoped lockfile
87
87
  at `<tempRoot>/single-story-sweep.lock` before planning. On
88
88
  contention (another `/deliver-story` already in the sweep
@@ -114,18 +114,24 @@ not a substitute for prefixing paths correctly.
114
114
 
115
115
  ## Engine invariants and the lite route
116
116
 
117
+ **Prerequisites before Step 0.** A `type::story` issue, a clean
118
+ `gh auth status`, and `project.baseBranch` present both locally and on
119
+ `origin` — init seeds the Story branch from the base branch and probes the
120
+ remote, so a missing or unauthenticated remote surfaces as a
121
+ `remoteVerified: false` block rather than a useful error.
122
+
117
123
  The v2 engine's trait table:
118
124
 
119
- | Trait | v2 `/deliver-story` |
120
- | --- | --- |
121
- | Ticket type | `type::story` only |
122
- | Branch | `story-<id>` seeded from `project.baseBranch` (`main`) |
123
- | Merge target | `main` via PR (squash + required checks) |
124
- | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
125
- | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
125
+ | Trait | v2 `/deliver-story` |
126
+ | ------------- | ------------------------------------------------------------------------ |
127
+ | Ticket type | `type::story` only |
128
+ | Branch | `story-<id>` seeded from `project.baseBranch` (`main`) |
129
+ | Merge target | `main` via PR (squash + required checks) |
130
+ | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
131
+ | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
126
132
 
127
133
  **Ceremony-lite Stories still land through this engine unchanged.** A
128
- lite-routed Story collapses only the *advisory* plan/deliver
134
+ lite-routed Story collapses only the _advisory_ plan/deliver
129
135
  ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
130
136
  not earn. It does **not** get a cheaper landing: the close-validation gates
131
137
  (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
@@ -135,7 +141,7 @@ record of those non-negotiables; there is no lite-specific gate bypass.
135
141
 
136
142
  **Deliver derives the route from the Story body's shape — and the
137
143
  dispatch mode from the run.** Persist stamps a lite cohort's Stories with the
138
- `route::lite` label as a *human-visible hint only* (and ledgers the authored
144
+ `route::lite` label as a _human-visible hint only_ (and ledgers the authored
139
145
  verdict — recorded reason plus per-Story shape evidence — on the
140
146
  `story-plan-state` checkpoint); the label is never the control signal.
141
147
  `/deliver` computes the route from the fetched Story body via
@@ -261,6 +267,31 @@ removed. Hard gates are untouched.
261
267
 
262
268
  ## Step 3 — Merge wait, async mode, and flags
263
269
 
270
+ **Step 3 is the orchestrator's, and it is serialized.** A dispatched
271
+ `story-worker` ends its turn at a pushed branch (spine § Step 2.5); the session
272
+ that dispatched it runs close. Two reasons, both measured rather than
273
+ theoretical:
274
+
275
+ 1. **A sub-agent cannot resume itself.** It gets no notification when a
276
+ backgrounded close finishes, so a worker that backgrounds close and ends its
277
+ turn strands the envelope in a turn nobody reads — three of five workers in
278
+ one measured wave did exactly that despite an explicit foreground-close
279
+ instruction. Moving the seam removes the failure instead of re-wording the
280
+ prohibition. The parent, by contrast, is still live and _does_ observe and
281
+ retry its own close.
282
+ 2. **Closes contend; implementation does not.** Close syncs from
283
+ `origin/<baseBranch>`, pushes, opens a PR and arms auto-merge — two of those
284
+ in flight race on the base branch, the merge queue and the shared checkout.
285
+ So implementation may fan out across the wave, but the tail runs **one Story
286
+ at a time**: a worker that hands back while another close is running waits in
287
+ the orchestrator's queue.
288
+
289
+ A worker therefore returns a hand-off report, not a terminal envelope, and that
290
+ is the expected shape — only close mints an envelope. Never answer a missing
291
+ envelope with a re-dispatch: `single-story-init.js` re-run under a live branch
292
+ is how one Story ends up with two closes. Close the pushed branch, or probe with
293
+ `deliver-recover.js` and run the one command it prints.
294
+
264
295
  **What close does internally.** The script runs the close-validation gates
265
296
  against `baseBranch`, syncs the Story branch from `origin/<baseBranch>`
266
297
  (the parallel-race defence), pushes `story-<id>`, opens (or
@@ -280,7 +311,7 @@ separate (`delivery.mergeWatch.*`):
280
311
  it when your host has no such ceiling and you want to land in one block.
281
312
  - **`maxBudgetSeconds`** (default 3600) bounds the **cumulative** wait across
282
313
  resumes, anchored at the PR's `createdAt` so resuming does not restart the
283
- clock. Exhausting *this* is the genuine give-up → `blocked`.
314
+ clock. Exhausting _this_ is the genuine give-up → `blocked`.
284
315
 
285
316
  The wait probes the checks every poll: a red required check fails fast as
286
317
  `checks-failed` instead of burning the budget, and a PR that falls behind its
@@ -312,7 +343,7 @@ checks pass. Under `"strict"`, the close **does not arm auto-merge** — the
312
343
  PR opens and waits for an **operator merge**, exactly as `--no-auto-merge`
313
344
  does per-run.
314
345
 
315
- **When to reach for a close flag.** What each one *does* is in
346
+ **When to reach for a close flag.** What each one _does_ is in
316
347
  `node .agents/scripts/single-story-close.js --help`; below is only the
317
348
  judgment that help text cannot carry.
318
349
 
@@ -346,7 +377,7 @@ The `single-story-close.js` script, in order:
346
377
  captured tail inline so the evidence is in front of you without opening a
347
378
  file. Read the artifact when you need the full text — or re-run under
348
379
  `AGENT_LOG_LEVEL=verbose` for live streaming.
349
- 1a. **Syncs the Story branch from `origin/<baseBranch>`** before push.
380
+ 1a. **Syncs the Story branch from `origin/<baseBranch>`** before push.
350
381
  Runs `git fetch origin <baseBranch>` followed by
351
382
  `git merge --no-edit origin/<baseBranch>` inside the worktree. This
352
383
  defends against the parallel-`/deliver-story` race: when
@@ -370,12 +401,13 @@ The `single-story-close.js` script, in order:
370
401
  defence against the parallel race. Without merge queue, the sync
371
402
  closes the PR-open-time race but a residual race remains between PR
372
403
  open and auto-merge fire.
404
+
373
405
  2. Pushes `story-<id>` to `origin`.
374
406
  3. Probes for an existing open PR with `head = story-<id>`. If none
375
407
  exists, opens one via `gh pr create --base <baseBranch>`. The PR
376
408
  body carries `Closes #<storyId>` so the GitHub merge auto-closes the
377
409
  issue.
378
- 3a. **Enables GitHub native auto-merge by default** via
410
+ 3a. **Enables GitHub native auto-merge by default** via
379
411
  `gh pr merge <prNumber> --auto --squash --delete-branch`. Once CI's
380
412
  required checks turn green, GitHub squash-merges the PR and deletes
381
413
  the source branch — the operator does not need to babysit the merge
@@ -385,7 +417,7 @@ The `single-story-close.js` script, in order:
385
417
  pre-merge eyeball.
386
418
  4. Flips the Story to **`agent::closing`** (NOT `agent::done`) and leaves
387
419
  the GitHub issue **OPEN**. Auto-merge completes
388
- asynchronously *after* this script exits, so closing the issue here
420
+ asynchronously _after_ this script exits, so closing the issue here
389
421
  would strand a CLOSED issue with no merged work if the PR later failed
390
422
  CI, went `BEHIND` base, or was closed without merging. The Story rests
391
423
  at `agent::closing` while the PR is open with auto-merge armed; the
@@ -430,9 +462,10 @@ close-validation gates pass on the dev host's environment; CI runs on a
430
462
  different OS and concurrency, and coverage rounding, platform-conditional
431
463
  branches, and timing-sensitive tests routinely drift between the two.
432
464
 
433
- Fix the failure and push a new commit on `story-<storyId>` — auto-merge stays
434
- armed across retries, so you do not re-arm — then resume the land with the
435
- envelope's `nextCommand`.
465
+ Fix the failure and push a new commit on `story-<storyId>` — the watcher
466
+ **disarmed native auto-merge on the first red** and re-arms it
467
+ only when the checks go green on a **new head SHA**, so the fix must be a real
468
+ commit — then resume the land with the envelope's `nextCommand`.
436
469
 
437
470
  To watch the checks on the red path, drive `pr-watch-with-update.js` — the
438
471
  **single CI-watch mechanism**. It polls the required checks to a
@@ -444,29 +477,54 @@ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber> --story <storyI
444
477
  ```
445
478
 
446
479
  `--story` is what keys the red-path CI digest
447
- (`temp/story-<id>-ci-digest.{json,md}` — failing check name, run id, and a
448
- `gh run view --log-failed` tail). Omit it and a red check writes no digest.
480
+ (`temp/story-<id>-ci-digest.{json,md}` — failing check name, the PR head SHA,
481
+ run id + run link, and a `gh run view --log-failed` tail). Omit it and a red
482
+ check writes no digest — and with no digest the no-rerun guard has nothing to
483
+ adjudicate the next green against, so always pass it.
449
484
  Poll cadence and caps come from `delivery.ci.watch.*` (`pollIntervalMs`,
450
- `maxPolls`, `maxResumes`); pass `--poll-interval-ms`, `--max-polls`, or
451
- `--max-resumes` to override for one run.
485
+ `maxPolls`, `maxResumes`, `attachWindowMs`); pass `--poll-interval-ms`,
486
+ `--max-polls`, `--max-resumes`, or `--attach-window-ms` to override for one run.
487
+ `attachWindowMs` (default 20 min) is how long the watch keeps re-resolving an
488
+ **empty** required-check set before it stops waiting for a context to attach —
489
+ a required context that is an aggregator job gated on every other tier is the
490
+ last check to appear, measured at 16m52s on this repository.
491
+
492
+ Add `--repo owner/repo` only when the cwd is not the target repository; it
493
+ reaches `gh` as a real flag. There is no `<owner/repo>#<number>` ref form —
494
+ `gh` parses that as a branch name.
452
495
 
453
496
  When the watch exits, branch on the exit code:
454
497
 
455
498
  - **Exit 0 (all checks ✓)** — auto-merge will fire (or has already). The Story
456
499
  is still at `agent::closing` with its issue OPEN. **Proceed to merge
457
- confirmation (§ Step 5) within the same turn** — green CI is the *start* of
500
+ confirmation (§ Step 5) within the same turn** — green CI is the _start_ of
458
501
  the merge-confirm sequence, not a terminal state.
459
- - **Exit 1 (a check genuinely failed)** — diagnose, fix, and push a new commit
460
- on `story-<storyId>`, then re-watch. Auto-merge stays enabled across retries;
461
- no need to re-arm it. The Story stays at `agent::closing` throughout, so a
462
- failed/abandoned PR never strands a CLOSED issue. If the same failure class
463
- recurs, hand convergence off to a self-paced host loop (`/loop`) that re-runs
464
- the failing check and applies the smallest fix until it exits green.
465
- - **Exit 2 (still-running — slow CI, not red)** the poll cap fired with checks
466
- still pending and the watcher exhausted its resume budget with nothing red.
467
- This is **never** a failure. Hand the wait off to the host's interval loop
502
+ - **Exit 1 (a check genuinely failed, the green was a forbidden re-run, or the
503
+ PR itself could not be read)**
504
+ diagnose, fix at source, and push a new commit on `story-<storyId>`, then
505
+ re-watch: the watcher disarmed auto-merge on the red and re-arms it only for
506
+ a green on a **new head SHA**. The Story stays at `agent::closing`
507
+ throughout, so a failed/abandoned PR never strands a CLOSED issue. If the
508
+ same failure class recurs, hand convergence off to a self-paced host loop
509
+ (`/loop`) that applies the smallest fix and pushes a new commit each pass —
510
+ **never** a bare re-run of the failed job. A green the guard rejects as a
511
+ re-run of the same commit flips the Story to `agent::blocked` with a
512
+ `friction` comment; clear it per
513
+ [`ci-remediation.md`](../../rules/ci-remediation.md) § Verifier.
514
+ - **Exit 2 (slow, not red)** — one of three slow conditions, **never** a
515
+ failure and never a green. Hand the wait off to the host's interval loop
468
516
  rather than ending your turn: `/loop 5m` polling `gh pr checks` until the
469
- checks settle.
517
+ checks settle. The envelope names which:
518
+ - **still-running** — the poll cap fired with checks still pending and the
519
+ watcher exhausted its resume budget with nothing red.
520
+ - **not-yet-started** (`notYetStarted: true`) — the attach window was spent
521
+ and **no** required context ever attached, while the PR kept reading back
522
+ fine. CI has not started; there is no failing check and no CI digest to
523
+ read. Do **not** treat it as red — nothing needs fixing, and re-watching
524
+ (or raising `attachWindowMs`) is the whole remediation.
525
+ - **unresolved** (`reconciliation.reconciled: false`) — every observed
526
+ required check is green but the repository still refuses the merge, so the
527
+ green verdict is withheld.
470
528
 
471
529
  **Triage authority.** How to classify and remediate a red (or repeatedly slow)
472
530
  check — the root-cause-only decision tree for infra/transient and flaky failures
@@ -485,7 +543,7 @@ then ends its turn with **free-form prose** — e.g. "I'll wait for the
485
543
  background watch task to complete" or "the next event will be its completion
486
544
  notification" — leaving the merge unconfirmed and the Story stranded at
487
545
  `agent::closing`. **Do not do this.**
488
- `pr-watch-with-update.js --pr <prNumber>` *blocks the current turn* until CI
546
+ `pr-watch-with-update.js --pr <prNumber>` _blocks the current turn_ until CI
489
547
  resolves — that is the mechanism by which you wait. You MUST keep your turn alive
490
548
  across the wait: watch → (fix + push + re-watch on red) → confirm the merge
491
549
  (Step 5) → flip `agent::done` → run the post-merge steps → and only then
@@ -551,12 +609,13 @@ the watch exits clean.
551
609
  `agent::blocked`, summarize the blocker on the PR, and yield to the
552
610
  operator.
553
611
 
554
- ### Idempotence of the loop
612
+ ### Idempotence of the loop {#idempotence}
555
613
 
556
614
  - The PR stays open across retries; `gh pr create` is a one-shot at
557
615
  close, the loop only pushes new commits.
558
- - Auto-merge stays armed across retries pushing a new commit does
559
- not disarm `gh pr merge --auto`.
616
+ - Auto-merge is disarmed by the watcher on the first red and re-armed
617
+ when the checks go green on a new head SHA; pushing a new commit is
618
+ what re-opens the merge path.
560
619
  - If the operator manually merges or disables auto-merge mid-loop,
561
620
  exit the loop and report.
562
621
 
@@ -612,7 +671,7 @@ no-op-safe (`no-project` / `not-on-project` exit 0).
612
671
 
613
672
  The GitHub Projects v2 built-in workflows `Pull request merged` and
614
673
  `Pull request linked to issue` are enabled by default on most boards
615
- and fire ~minutes *after* auto-merge lands. They overwrite the Status
674
+ and fire ~minutes _after_ auto-merge lands. They overwrite the Status
616
675
  field as a side-effect, clobbering the `Done` value
617
676
  `single-story-confirm-merge.js` set at the `agent::done` flip in Step 5
618
677
  and leaving closed Stories stuck at `In Progress` on the board. The
@@ -680,7 +739,7 @@ GitHub deletes the **remote** branch on auto-merge (via the
680
739
  `--delete-branch` flag `single-story-close.js` passes to `gh pr merge`).
681
740
  The **local** `story-<storyId>` ref, however, lingers in the main
682
741
  checkout until something prunes it — `single-story-init.js` runs a
683
- merged-sweep at the start of every *subsequent* `/deliver-story`
742
+ merged-sweep at the start of every _subsequent_ `/deliver-story`
684
743
  invocation, but that's next-run cleanup, not end-of-run cleanup. Stale
685
744
  local refs accumulate between sessions, clutter `git branch`, and shadow
686
745
  the lessons the sweep is meant to surface.
@@ -717,6 +776,27 @@ up").
717
776
 
718
777
  ---
719
778
 
779
+ ## Idempotence and the standing constraints
780
+
781
+ Every script in the chain no-ops safely on re-run: `single-story-init.js`
782
+ re-prints `workCwd` for an already-initialized Story; `single-story-close.js`
783
+ and `single-story-confirm-merge.js` short-circuit on a closed or `agent::done`
784
+ Story; the PR probe reuses an open PR rather than opening a second one. That is
785
+ what makes the recovery router safe to walk more than once.
786
+
787
+ The four constraints the spine states without arguing for them:
788
+
789
+ - **Never push the Story branch directly to `main`.** The PR is the only merge
790
+ surface — a direct push bypasses required checks and the squash title
791
+ release-please parses.
792
+ - **Always prefix path-based tools with the absolute `workCwd` root.** `cd`
793
+ scopes Bash, not Edit/Write/Read; close's wrong-tree guard is a backstop for
794
+ the mistake, not a licence to make it.
795
+ - **Report state, not process.** Mirror the close envelope's fields; step
796
+ narration reads as progress while telling the caller nothing it can branch on.
797
+ - **Drive every `agent::*` transition through `update-ticket-state.js`** so the
798
+ label, the Projects Status column and the lifecycle event stay in one motion.
799
+
720
800
  ## Step 7 — Return-contract detail
721
801
 
722
802
  The field-level contract is the shipped schema
@@ -724,7 +804,7 @@ The field-level contract is the shipped schema
724
804
  — not this file, and not
725
805
  [`agents/story-worker.md`](../../agents/story-worker.md). All three used to
726
806
  carry their own prose version; the schema is now the only definition. What
727
- follows is the *judgement* around it, which a schema cannot express.
807
+ follows is the _judgement_ around it, which a schema cannot express.
728
808
 
729
809
  ### `pending` is a real status — and it is not a park
730
810
 
@@ -767,8 +847,9 @@ persists the validated envelope to
767
847
  - **It is best-effort.** A failed write returns null and changes nothing about
768
848
  the emitted envelope or the exit code — a landed PR must never become a
769
849
  crash because a temp directory was unwritable.
770
- - **It is a fallback, not a licence.** A worker still holds its turn until the
771
- envelope arrives; see [`agents/story-worker.md`](../../agents/story-worker.md).
850
+ - **It is a fallback, not a licence.** The orchestrator running close still
851
+ holds its turn until the envelope arrives; see § Step 3 above and
852
+ [`agents/story-worker.md`](../../agents/story-worker.md).
772
853
 
773
854
  `deliver-recover.js` reads the same artifact, plus the freshness of
774
855
  `close-gates-<storyId>.log`, to split the one genuinely ambiguous row of its
@@ -1,179 +1,164 @@
1
1
  ---
2
2
  description:
3
- Execute one Story end-to-end. Creates story-<id> from main, implements in a
4
- worktree (optional ## Slicing checkpoints), runs derived-level ceremony,
5
- opens a PR against main, and lands.
3
+ Execute one Story end-to-end: story-<id> from main, implemented in a worktree
4
+ (optional ## Slicing checkpoints), derived-level ceremony, PR against main.
6
5
  mandatoryReads: [deliver-digest.md]
7
6
  ---
8
7
 
9
8
  # /deliver-story #[Story ID]
10
9
 
11
- > **Lean spine.** Happy path + gate list; edge-case, recovery, and
12
- > reference detail lives in
13
- > [`deliver-story-reference.md`](deliver-story-reference.md) ("reference"
14
- > below); consult on demand. Invoked by [`/deliver`](../deliver.md).
15
- >
16
- > **Read [`deliver-digest.md`](deliver-digest.md) once, first.** It is the
17
- > one bundled read of what every delivery needs — dispatch decision, engine
18
- > invariants, the change-set/ceremony incantation, the acceptance-eval gate,
19
- > and the terminal-envelope contract — replacing the per-session re-reads of
20
- > the helper set and `story-deliver-terminal.schema.json`. The steps below
21
- > cite it as "digest § N" rather than restating it.
10
+ > **Lean spine.** Happy path + gate list; edge-case, recovery and reference
11
+ > detail lives in [`deliver-story-reference.md`](deliver-story-reference.md)
12
+ > ("reference" below). Invoked by [`/deliver`](../deliver.md).
13
+ > **Read [`deliver-digest.md`](deliver-digest.md) once, first** — the one
14
+ > bundled read every delivery needs. Steps cite it as "digest § N".
22
15
 
23
16
  ## Overview
24
17
 
25
- The **one** delivery engine in v2 — every Story (a **one-Story** run
26
- goes inline with inline critics; engine, gates, envelope byte-identical):
18
+ The **one** delivery engine in v2:
27
19
 
28
20
  ```text
29
- single-story-init.js → implement + commits → derived-level ceremony
30
- single-story-close.js (gates, push, PR → main, agent::closing)
21
+ single-story-init.js → implement + commits → derived-level ceremony → push
22
+ ──hand-off──▶ single-story-close.js (gates, PR → main, agent::closing)
31
23
  → CI watch + merge → single-story-confirm-merge.js (agent::done)
32
24
  ```
33
25
 
34
- An `Epic: #N` reference marks a v1 ticket — **stop** and re-plan. The
35
- engine's trait table is reference § Engine invariants. Prerequisites: a
36
- `type::story` issue, clean `gh auth status`, and `project.baseBranch` on
37
- local and `origin`.
26
+ An `Epic: #N` reference marks a v1 ticket — **stop** and re-plan. Engine
27
+ traits and prerequisites: reference § Engine invariants.
28
+
29
+ ## Who owns which step
30
+
31
+ Steps 0–2 belong to whoever implements. **Step 3 belongs to the orchestrator
32
+ that dispatched the work**, never to a spawned worker.
33
+
34
+ - **Inline dispatch** (a one-Story run — digest § 1): one session is both
35
+ roles and walks Steps 0→7, no hand-off.
36
+ - **Sub-agent dispatch**: the `story-worker` stops at Step 2.5 with the branch
37
+ pushed and returns a hand-off; the dispatching `/deliver` session runs Step 3
38
+ **in its own turn** and **serializes the tail — one close at a time across
39
+ the run**, even though implementation ran in parallel (reference § Step 3).
40
+
41
+ **A worker returning no terminal envelope is expected, not a failure.** Only
42
+ Step 3 mints one, so a hand-off is the normal sub-agent return. Never
43
+ re-dispatch the Story on it — the branch exists, and re-running Step 0 under
44
+ live work is how one Story gets two closes. Resume per § Recovery instead.
38
45
 
39
46
  ## Step 0 — Initialize (`single-story-init.js`)
40
47
 
41
- From the **main checkout**, **synchronously** with the maximum Bash
42
- timeout — the per-tree install can take minutes; never `run_in_background`:
48
+ From the **main checkout**, **synchronously** with the maximum Bash timeout —
49
+ the per-tree install can take minutes; never `run_in_background`:
43
50
 
44
51
  ```bash
45
52
  node .agents/scripts/single-story-init.js --story <storyId>
46
53
  ```
47
54
 
48
55
  Every script below documents its own flags — run it with `--help`.
49
- It validates `type::story`, **acquires the Story lease**
50
- (fails closed on a foreign assignee), fetches `origin`, seeds `story-<id>`
51
- from `baseBranch` (idempotent reuse), materializes a worktree, runs a
52
- guarded merged-`story-*` sweep, and flips `agent::executing` (reference
53
- § Step 0). Capture `workCwd` from the result envelope.
56
+ It validates `type::story`, takes the Story lease, seeds `story-<id>` from
57
+ `baseBranch`, materializes a worktree and flips `agent::executing` (reference
58
+ § Step 0). Capture `workCwd` from the envelope.
54
59
 
55
- **Land or block.** `remoteVerified: false` → flip
56
- `agent::blocked` quoting `remoteProbe.detail` and stop. Implementing outside
57
- the worktree/branch/PR path or committing to local `main` is forbidden
58
- close's push is the only sanctioned landing.
60
+ **Land or block.** `remoteVerified: false` → flip `agent::blocked` quoting
61
+ `remoteProbe.detail` and stop. Implementing outside the worktree/branch/PR path,
62
+ or committing to local `main`, is forbidden.
59
63
 
60
64
  **Step 0.5 — `cd "<workCwd>"`**, and prefix every path-based
61
- Edit/Write/Read with that absolute root — the `cd` alone does not scope
62
- those tools (reference § Worktree scope is not just the Bash cwd).
65
+ Edit/Write/Read with that absolute worktree root — the `cd` alone does not
66
+ scope those tools (reference § Worktree scope).
63
67
 
64
68
  ## Step 1 — Implementation
65
69
 
66
70
  One branch, one PR to `main`, commits against the inline `acceptance[]` /
67
- `verify[]` (and `## Spec`):
68
-
69
- 1. Read the Story body; the acceptance criteria are the contract. Docs are
70
- digest-first; read a caller-provided `checklistPath` before writing
71
- (reference § Step 1).
72
- 2. Implement. Walk any `## Slicing` rows as **intra-session checkpoints**
73
- (commit + flip each row) — never sibling tickets.
74
- 3. Commit on the Story branch; iterate with quick advisory gates
75
- (`typecheck`, `lint`, scoped tests) the full close chain runs in
76
- Step 3.
77
- 4. Run the **full test command** once in the worktree (`npm test`) **before
78
- Step 1a** — repo-invariant guards outside the Story's scoped greps are
79
- the failure class that bounces deliveries. Fix and commit first.
80
- 5. Run the self-eval loop (Step 1a).
71
+ `verify[]` and `## Spec`:
72
+
73
+ 1. Read the Story body; its acceptance criteria are the contract. Docs are
74
+ digest-first; read a caller-provided `checklistPath` first, and walk any
75
+ `## Slicing` rows as **intra-session checkpoints** (reference § Step 1).
76
+ 2. Implement and commit on the Story branch, iterating with quick advisory
77
+ gates (`typecheck`, `lint`, scoped tests) — the full chain runs in Step 3.
78
+ 3. Run `npm test` once in the worktree **before Step 1a**: repo-invariant
79
+ guards outside the Story's scoped greps are the failure class that bounces
80
+ deliveries. Fix and commit first, then run the self-eval loop.
81
81
 
82
82
  ### Step 1a — Bounded acceptance self-eval loop (**required**)
83
83
 
84
84
  Run the loop and score it with `acceptance-eval.js` — **digest § 4** carries
85
- the invocation and the proceed / redraft / block contract; per-round critic
86
- mechanics live in the single-homed include
87
- [`acceptance-self-eval.md`](acceptance-self-eval.md).
88
-
89
- **`proceed`** → Step 2 then Step 3. **`block`** → **do not close**: post a
90
- `friction` comment and flip `agent::blocked` — commands and the
91
- `evidence-gate.js --standalone` evidence-share mechanic: reference
92
- § Step 1a.
85
+ the invocation and the proceed / redraft / block contract; per-round mechanics
86
+ live in [`acceptance-self-eval.md`](acceptance-self-eval.md). **`proceed`** →
87
+ Step 2. **`block`** → **do not close**: post a `friction` comment and flip
88
+ `agent::blocked` (reference § Step 1a).
93
89
 
94
90
  ## Step 2 — Ceremony (profile + derived level)
95
91
 
96
- Ceremony is `delivery.routing.ceremonyProfile` × the **derived change
97
- level** — never a planner-authored verdict. **Digest § 3** is the
98
- incantation: compute the change set once, derive the level, resolve
99
- fresh-vs-inline critics with `ceremony-routing.js`; a lite Story runs
100
- inline regardless (routing edge cases: reference § Step 2). Hard gates always
101
- run in Step 3 — the derived level never disables them; do **not** pre-run the
102
- close chain here.
92
+ Ceremony is `delivery.routing.ceremonyProfile` × the **derived change level**,
93
+ never a planner-authored verdict. **Digest § 3** is the incantation (change set
94
+ once, derive the level, resolve critics with `ceremony-routing.js`); edge cases
95
+ are reference § Step 2. Hard gates always run in Step 3 — the derived level
96
+ never disables them; do **not** pre-run the chain here.
97
+
98
+ ### Step 2.5 — Push and hand off (sub-agent dispatch only)
99
+
100
+ Push `story-<storyId>` to `origin` and confirm the remote ref moved. Return the
101
+ hand-off — Story id, `workCwd`, branch, pushed head SHA, self-eval verdict,
102
+ `verify[]` evidence — then stop. Do not open the PR; do not compose a terminal
103
+ envelope. An inline run skips this.
103
104
 
104
105
  ## Step 3 — Close and land (`single-story-close.js`)
105
106
 
107
+ **The orchestrator's step** (§ Who owns which step), run in the **foreground**,
108
+ serialized against sibling Stories:
109
+
106
110
  ```bash
107
111
  node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo>
108
112
  ```
109
113
 
110
114
  **The whole delivery tail** — gates, PR, merge wait, `agent::done` flip,
111
- post-land tail in one process. Run it and **branch on the terminal envelope's
112
- `status`** per the table in **digest § 5** (`landed` Step 7; `pending` → run
113
- `nextCommand`; `blocked`/`checks-failed` Step 4; `failed` diagnose and
114
- re-run). Gate output is captured to
115
- `temp/orchestration/close-gates-<storyId>.log` a clean run prints a digest
116
- line, a red gate replays its tail inline.
117
-
118
- Internals (gate order, base-sync, auto-merge arming), the merge-wait
119
- budgets, the slow-CI **async** confirm mode (launch the `pending`
120
- envelope's `nextCommand` as **background** Bash, never a foreground
121
- poll), the `autoMerge` policy, and every close flag: reference
122
- § Step 3 Merge wait, async mode, and flags.
123
-
124
- ## Steps 4–6 — Recovery router (**recovery-only**)
125
-
126
- A `landed` envelope means everything ran — go straight to Step 7. Enter a
127
- recovery path **only** when the envelope routes you there:
128
-
129
- - **`blocked` / `checks-failed`** → fix, push a new commit (auto-merge stays
130
- armed), resume via `nextCommand`; triage per
131
- [`rules/ci-remediation.md`](../../rules/ci-remediation.md). The watch is
132
- internally blocking — never end a turn with prose and an unconfirmed
133
- merge. Reference § Step 4.
134
- - **`pending`** → run `nextCommand` (`single-story-confirm-merge.js`) until
135
- resolved. Reference § Step 5.
136
- - **`tail.statusResync: false`** → reference § Step 5.5;
137
- **`tail.refCleanup` / `tail.baseFastForward: false`** → reference § Step 6.
138
-
139
- ## Step 7 — Return contract (**required as a sub-agent**) {#return-contract}
140
-
141
- End your turn by relaying the validated envelope close emits between its
115
+ post-land tail in one process. Never background it, never delegate it to a
116
+ child, and never end your turn while it is still running: "close is running"
117
+ is not a return value. Branch on the envelope's `status` per **digest § 5**
118
+ (`landed` Step 7; `pending` → run `nextCommand`; `blocked`/`checks-failed`
119
+ → Step 4; `failed` diagnose, re-run). Gate output is captured.
120
+
121
+ Internals, merge-wait budgets, the slow-CI **async** confirm mode, the
122
+ `autoMerge` policy and every close flag: reference § Step 3.
123
+
124
+ ## Step 7 Return contract {#return-contract}
125
+
126
+ Relay the validated envelope close emits between its
142
127
  `--- STORY DELIVER TERMINAL ---` markers — never free-form prose, never a
143
- hand-composed object. Statuses, exits, and required fields: **digest § 5**
144
- (whose SSOT is the shipped
145
- [schema](../../schemas/story-deliver-terminal.schema.json)).
146
- `pending` is the only sanctioned no-merge ending, returned only when your own
147
- budget is exhausted. Reference § Step 7.
128
+ hand-composed object. Statuses, exits and fields: **digest § 5** (SSOT: the
129
+ shipped [schema](../../schemas/story-deliver-terminal.schema.json)).
130
+ `pending` is the only sanctioned no-merge ending.
148
131
 
149
- ## Recovering a stranded Story {#recover}
132
+ ## Steps 4–6 Recovery (**recovery-only**) {#recover}
133
+
134
+ A `landed` envelope means everything ran — go to Step 7. Enter recovery **only**
135
+ when the envelope routes you there; procedures are reference §§ Step 4–6.
136
+ Two rules the spine keeps: a red **disarms auto-merge**,
137
+ so only a green on a NEW head SHA re-arms it — a re-run is refused; fix at source
138
+ and push ([`rules/ci-remediation.md`](../../rules/ci-remediation.md)). And a
139
+ `tail.*: false` degrades the report, never the land.
140
+
141
+ **Watch exit codes** — `pr-watch-with-update.js` exits 0 green, 1 only when a
142
+ required check genuinely failed (or the PR is unreadable), 2 slow-but-not-red:
143
+ still-running, unresolved, **or** `notYetStarted` — no required context attached
144
+ inside `attachWindowMs`. Never route a 2 onto the red path; nothing is broken
145
+ and no digest exists to read.
150
146
 
151
147
  **Lost envelope first: read it off disk.** Close persists each to
152
148
  `temp/orchestration/story-deliver-terminal-<storyId>.json`; branch on it per
153
- digest § 5. Otherwise (killed run, re-run refusal, merged-but-label-stale)
154
- do not guess — probe **read-only** with
149
+ digest § 5. Otherwise do not guess probe **read-only** with
155
150
  `node .agents/scripts/deliver-recover.js --story <storyId>`; it prints the
156
151
  **one** next command with its evidence, never a menu. A live close answers
157
152
  `close-in-flight`: wait, never re-init underneath it.
158
153
 
159
154
  ## Idempotence & constraints
160
155
 
161
- Every script no-ops safely on re-run (init re-prints `workCwd`; close and
162
- confirm-merge short-circuit on a closed / `agent::done` Story; the PR probe
163
- reuses an open PR).
164
-
165
- - **Never** push the Story branch directly to `main` — the PR is the only
166
- merge surface.
167
- - **Always** prefix path-based tools with the absolute `workCwd` root
168
- (Step 0.5); close's wrong-tree guard is a backstop.
169
- - **Report state, not process** — mirror the close envelope's fields; no
170
- step narration.
171
- - Drive every `agent::*` transition through
172
- `update-ticket-state.js --ticket <id> --state <state>`.
156
+ Every script no-ops safely on re-run (reference § Idempotence). **Never** push
157
+ the Story branch to `main` the PR is the only merge surface. Report state,
158
+ not process. Drive `agent::*` through
159
+ `update-ticket-state.js --ticket <id> --state <state>`.
173
160
 
174
161
  ## See also
175
162
 
176
- - [`deliver-digest.md`](deliver-digest.md) — the one bundled framework read.
177
- - [`/deliver`](../deliver.md) — unified entry point.
178
- - [`deliver-story-reference.md`](deliver-story-reference.md) — all on-demand
179
- detail.
163
+ [`deliver-digest.md`](deliver-digest.md), [`/deliver`](../deliver.md),
164
+ [`deliver-story-reference.md`](deliver-story-reference.md).