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.
- package/.agents/agents/acceptance-critic.md +13 -19
- package/.agents/agents/auditor.md +16 -19
- package/.agents/agents/story-worker.md +41 -54
- package/.agents/audit-checklists/architecture.md +5 -1
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- package/.agents/docs/workflows.md +1 -1
- package/.agents/rules/ci-remediation.md +68 -3
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/schemas/baselines/baseline-envelope.schema.json +4 -0
- package/.agents/schemas/baselines/crap.schema.json +5 -1
- package/.agents/schemas/crap-report.schema.json +37 -0
- package/.agents/schemas/story-deliver-terminal.schema.json +6 -1
- package/.agents/scripts/acceptance-eval.js +35 -9
- package/.agents/scripts/audit-labels-bootstrap.js +16 -95
- package/.agents/scripts/audit-to-stories.js +33 -3
- package/.agents/scripts/check-context-budget.js +95 -14
- package/.agents/scripts/check-test-temp-hygiene.js +83 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
- package/.agents/scripts/deliver-light.js +22 -45
- package/.agents/scripts/deliver-recover.js +13 -0
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +10 -25
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
- package/.agents/scripts/lib/baseline-loader.js +0 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +396 -51
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +20 -32
- package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
- package/.agents/scripts/lib/baselines/reader.js +10 -0
- package/.agents/scripts/lib/baselines/writer.js +7 -3
- package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
- package/.agents/scripts/lib/crap-engine.js +97 -19
- package/.agents/scripts/lib/crap-utils.js +73 -13
- package/.agents/scripts/lib/env-loader.js +46 -16
- package/.agents/scripts/lib/escomplex-ast-compat.js +360 -0
- package/.agents/scripts/lib/findings/route-finding.js +123 -17
- package/.agents/scripts/lib/findings/severity.js +80 -2
- package/.agents/scripts/lib/git/cached-fetch.js +0 -0
- package/.agents/scripts/lib/label-constants.js +17 -0
- package/.agents/scripts/lib/maintainability-engine.js +83 -11
- package/.agents/scripts/lib/maintainability-unscorable.js +60 -0
- package/.agents/scripts/lib/maintainability-utils.js +14 -5
- package/.agents/scripts/lib/observability/runtime-friction.js +8 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
- package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +283 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
- package/.agents/scripts/lib/orchestration/light-backstop.js +124 -0
- package/.agents/scripts/lib/orchestration/light-escalation.js +248 -0
- package/.agents/scripts/lib/orchestration/light-suitability.js +254 -51
- package/.agents/scripts/lib/orchestration/plan-context.js +12 -13
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +7 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +13 -1
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +96 -34
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +70 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +73 -18
- package/.agents/scripts/lib/reserved-test-ids.js +77 -0
- package/.agents/scripts/lib/story-plan.js +137 -42
- package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/util/poll-loop.js +18 -2
- package/.agents/scripts/lib/wave-runner/live-probe.js +7 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +73 -9
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +6 -3
- package/.agents/scripts/lib/workers/crap-worker.js +6 -3
- package/.agents/scripts/lib/workers/maintainability-worker.js +14 -9
- package/.agents/scripts/pr-watch-with-update.js +568 -213
- package/.agents/scripts/quality-preview.js +25 -0
- package/.agents/scripts/run-tests.js +10 -0
- package/.agents/scripts/single-story-close.js +6 -0
- package/.agents/scripts/single-story-init.js +7 -0
- package/.agents/scripts/stories-wave-tick.js +95 -10
- package/.agents/scripts/story-plan.js +65 -9
- package/.agents/scripts/update-crap-baseline.js +42 -4
- package/.agents/scripts/update-maintainability-baseline.js +31 -4
- package/.agents/templates/single-story-body.md +16 -8
- package/.agents/workflows/audit-architecture.md +69 -13
- package/.agents/workflows/audit-quality.md +26 -0
- package/.agents/workflows/audit-to-stories.md +30 -4
- package/.agents/workflows/deliver.md +81 -95
- package/.agents/workflows/helpers/audit-lens-core.md +25 -4
- package/.agents/workflows/helpers/deliver-light.md +21 -4
- package/.agents/workflows/helpers/deliver-reference.md +18 -3
- package/.agents/workflows/helpers/deliver-story-reference.md +127 -46
- package/.agents/workflows/helpers/deliver-story.md +104 -119
- package/.agents/workflows/helpers/plan-reference.md +34 -0
- package/.agents/workflows/plan.md +60 -76
- package/docs/CHANGELOG.md +38 -0
- package/lib/cli/update.js +14 -11
- package/lib/cli/version-check.js +9 -1
- 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
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
120
|
-
|
|
|
121
|
-
| Ticket type
|
|
122
|
-
| Branch
|
|
123
|
-
| Merge target
|
|
124
|
-
| Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session
|
|
125
|
-
| Ceremony
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>` —
|
|
434
|
-
|
|
435
|
-
|
|
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,
|
|
448
|
-
`gh run view --log-failed` tail). Omit it and a red
|
|
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`,
|
|
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
|
|
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
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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>`
|
|
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
|
|
559
|
-
|
|
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
|
|
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
|
|
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
|
|
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.**
|
|
771
|
-
envelope arrives; see
|
|
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
|
|
4
|
-
|
|
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
|
|
12
|
-
>
|
|
13
|
-
> [
|
|
14
|
-
>
|
|
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
|
|
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
|
-
|
|
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.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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`,
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
`
|
|
57
|
-
|
|
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
|
|
62
|
-
those tools (reference § Worktree scope
|
|
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[]`
|
|
68
|
-
|
|
69
|
-
1. Read the Story body;
|
|
70
|
-
digest-first; read a caller-provided `checklistPath`
|
|
71
|
-
(reference § Step 1).
|
|
72
|
-
2. Implement
|
|
73
|
-
(
|
|
74
|
-
3.
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
144
|
-
(
|
|
145
|
-
|
|
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
|
-
##
|
|
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
|
|
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 (
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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).
|