mandrel 2.24.0 → 2.26.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 +19 -21
- 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/baselines.md +21 -0
- 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/quality-gates.md +80 -18
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +1 -1
- package/.agents/rules/ci-remediation.md +68 -3
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/schemas/audit-rules.json +15 -0
- package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +8 -0
- package/.agents/schemas/baselines/crap.schema.json +13 -1
- package/.agents/schemas/crap-report.schema.json +37 -0
- package/.agents/schemas/model-attribution.schema.json +4 -0
- package/.agents/scripts/acceptance-eval.js +124 -15
- package/.agents/scripts/audit-baselines.js +136 -0
- package/.agents/scripts/audit-labels-bootstrap.js +16 -95
- package/.agents/scripts/audit-to-stories.js +33 -3
- package/.agents/scripts/check-arch-cycles.js +12 -93
- package/.agents/scripts/check-baseline-drift.js +16 -3
- package/.agents/scripts/check-baselines.js +19 -3
- package/.agents/scripts/check-context-budget.js +95 -14
- package/.agents/scripts/check-cyclomatic.js +214 -0
- package/.agents/scripts/check-schema-references.js +392 -0
- package/.agents/scripts/check-test-temp-hygiene.js +121 -1
- package/.agents/scripts/check-workflow-timeouts.js +291 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
- package/.agents/scripts/deliver-light.js +3 -4
- package/.agents/scripts/deliver-recover.js +13 -0
- package/.agents/scripts/diagnose-friction.js +85 -19
- package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
- package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
- package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
- package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
- package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
- package/.agents/scripts/lib/audit-baselines/read.js +87 -0
- package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
- package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
- package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
- package/.agents/scripts/lib/audit-suite/index.js +0 -5
- package/.agents/scripts/lib/audit-suite/selector.js +9 -62
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -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/baseline-schema-registry.js +13 -1
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
- package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +499 -57
- package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
- package/.agents/scripts/lib/baselines/reader.js +10 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
- package/.agents/scripts/lib/baselines/scope.js +39 -90
- package/.agents/scripts/lib/baselines/writer.js +16 -7
- package/.agents/scripts/lib/changed-files.js +8 -1
- package/.agents/scripts/lib/cli-args.js +115 -1
- package/.agents/scripts/lib/close-validation/runner.js +70 -25
- package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
- package/.agents/scripts/lib/crap-engine.js +124 -27
- package/.agents/scripts/lib/crap-method-identity.js +153 -0
- package/.agents/scripts/lib/crap-utils.js +86 -13
- package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
- package/.agents/scripts/lib/env-loader.js +46 -16
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
- 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/git-utils.js +136 -80
- package/.agents/scripts/lib/import-graph.js +156 -0
- package/.agents/scripts/lib/label-constants.js +17 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
- package/.agents/scripts/lib/observability/source-classifier.js +175 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
- 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/git-cleanup/phases/phase-drivers.js +10 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
- package/.agents/scripts/lib/orchestration/light-backstop.js +20 -3
- package/.agents/scripts/lib/orchestration/light-escalation.js +82 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +103 -5
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +216 -109
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +61 -13
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -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 +125 -37
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +267 -125
- package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
- package/.agents/scripts/lib/reserved-test-ids.js +77 -0
- package/.agents/scripts/lib/story-adjacency.js +3 -3
- package/.agents/scripts/lib/story-plan.js +137 -42
- package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
- package/.agents/scripts/lib/test-runner-contract.js +134 -0
- package/.agents/scripts/lib/test-tiers.js +11 -2
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/util/concurrent-map.js +17 -0
- package/.agents/scripts/lib/util/parse-id-list.js +103 -0
- package/.agents/scripts/lib/util/poll-loop.js +18 -2
- package/.agents/scripts/lib/wave-runner/live-probe.js +29 -13
- package/.agents/scripts/lib/wave-runner/ready-set.js +254 -43
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +10 -13
- package/.agents/scripts/lib/workers/crap-worker.js +8 -13
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
- package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
- package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
- package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
- package/.agents/scripts/lib/worktree-manager.js +14 -0
- package/.agents/scripts/plan-run-epilogue.js +17 -5
- package/.agents/scripts/pr-watch-with-update.js +568 -213
- package/.agents/scripts/providers/github/tickets.js +33 -10
- package/.agents/scripts/provision-git-hooks.js +85 -0
- package/.agents/scripts/quality-preview.js +137 -28
- package/.agents/scripts/resolve-stories.js +4 -1
- package/.agents/scripts/run-coverage.js +86 -35
- package/.agents/scripts/run-lint.js +20 -0
- package/.agents/scripts/run-tests.js +36 -36
- package/.agents/scripts/single-story-close.js +34 -2
- package/.agents/scripts/single-story-confirm-merge.js +22 -6
- package/.agents/scripts/single-story-init.js +7 -0
- package/.agents/scripts/stories-wave-tick.js +308 -47
- package/.agents/scripts/story-plan.js +65 -9
- package/.agents/scripts/update-coverage-baseline.js +34 -4
- package/.agents/scripts/update-crap-baseline.js +42 -4
- package/.agents/scripts/update-duplication-baseline.js +209 -83
- package/.agents/scripts/update-maintainability-baseline.js +31 -4
- package/.agents/scripts/validate-docs-freshness.js +1 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/templates/single-story-body.md +16 -8
- package/.agents/workflows/audit-architecture.md +69 -13
- package/.agents/workflows/audit-baselines.md +289 -0
- package/.agents/workflows/audit-navigability.md +5 -4
- package/.agents/workflows/audit-quality.md +26 -0
- package/.agents/workflows/audit-to-stories.md +30 -4
- package/.agents/workflows/deliver.md +92 -97
- package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
- package/.agents/workflows/helpers/audit-lens-core.md +25 -4
- package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
- package/.agents/workflows/helpers/deliver-digest.md +41 -21
- package/.agents/workflows/helpers/deliver-reference.md +95 -4
- package/.agents/workflows/helpers/deliver-story-reference.md +174 -52
- package/.agents/workflows/helpers/deliver-story.md +104 -119
- package/.agents/workflows/helpers/plan-reference.md +49 -5
- package/.agents/workflows/memory-consolidate.md +116 -0
- package/.agents/workflows/plan.md +62 -75
- package/README.md +13 -6
- package/docs/CHANGELOG.md +93 -0
- package/lib/cli/update.js +14 -11
- package/lib/cli/version-check.js +9 -1
- package/package.json +9 -4
- package/.agents/schemas/friction-event.schema.json +0 -56
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
|
@@ -12,8 +12,50 @@ Reference-only detail split out of [`deliver.md`](../deliver.md) so the
|
|
|
12
12
|
always-resident spine stays lean. Nothing here is a new MUST —
|
|
13
13
|
it is the mechanics an operator consults when the matching lever is engaged.
|
|
14
14
|
|
|
15
|
+
## Ranges (`4922 - 4926`) {#ranges}
|
|
16
|
+
|
|
17
|
+
A contiguous span is how an operator reads a plan run, so the dash range is a
|
|
18
|
+
first-class id shape rather than prose to interpret — `/deliver 4922 - 4926`
|
|
19
|
+
means exactly the five ids in it.
|
|
20
|
+
|
|
21
|
+
**Pass the span through; never expand it by hand.** Every id-list flag on the
|
|
22
|
+
delivery path takes range tokens — `resolve-stories.js --ids`,
|
|
23
|
+
`stories-wave-tick.js --stories` and `--dispatched`, and
|
|
24
|
+
`plan-run-epilogue.js --stories`. Normalize the operator's spacing away and hand
|
|
25
|
+
the scripts one unspaced token (`--ids 4922-4926`), mixed freely with singles
|
|
26
|
+
and commas (`--ids 4901,4922-4926`); overlaps dedupe. A hand-typed enumeration
|
|
27
|
+
is where an id gets dropped or invented, and the drop is silent.
|
|
28
|
+
|
|
29
|
+
The shared expander (`lib/util/parse-id-list.js`) refuses rather than guesses,
|
|
30
|
+
so a typo fails where it was typed instead of resolving the wrong set:
|
|
31
|
+
|
|
32
|
+
| Input | Outcome |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `4922-4926`, `4922 - 4926` | Expands to the inclusive span. En and em dashes, and a `#` on either endpoint, are accepted too. |
|
|
35
|
+
| `4926-4922` | Refused — write it low-to-high. |
|
|
36
|
+
| `1-4926` | Refused — above the 50-id span cap (`MAX_RANGE_SPAN`). |
|
|
37
|
+
| `4922-`, `-4926`, `4922-4923-4924` | Refused as a malformed token. |
|
|
38
|
+
|
|
39
|
+
The cap is per range token, not per run: a genuine 60-Story delivery is still
|
|
40
|
+
expressible as two ranges, but a slipped digit cannot fan out into a live
|
|
41
|
+
resolution sweep of thousands of issues.
|
|
42
|
+
|
|
15
43
|
## Sequencing edge cases (`stories-wave-tick.js`)
|
|
16
44
|
|
|
45
|
+
**What "discovered, not declared" means concretely.** `resolve-stories.js` reads
|
|
46
|
+
the graph from live state as the union of the Story bodies' `depends_on` edges
|
|
47
|
+
and GitHub's native `blocked_by` edges, resolving each blocker against its real
|
|
48
|
+
issue state rather than against anything you hand it. That is why there is no
|
|
49
|
+
batch label to pass and why a blocker that landed in an unrelated run is simply
|
|
50
|
+
seen as done.
|
|
51
|
+
|
|
52
|
+
**Resuming an exit-4 `blocked`.** Read the friction comment with
|
|
53
|
+
`gh issue view <id> --comments`, and resume only once the operator has
|
|
54
|
+
unblocked it:
|
|
55
|
+
`node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready`.
|
|
56
|
+
Do not poll the label yourself while waiting — the HITL pause is the operator's
|
|
57
|
+
turn, not a slow beat.
|
|
58
|
+
|
|
17
59
|
Each beat re-probes live state: it re-resolves the graph, classifies **done**
|
|
18
60
|
(`agent::done` or a closed issue — including foreign blockers that landed in
|
|
19
61
|
another run), and derives **in-flight** from live `agent::executing` /
|
|
@@ -49,6 +91,18 @@ different operator holds, unless you pass `--steal`. Assignee-based withholding
|
|
|
49
91
|
needs `github.operatorHandle` set (in `.agentrc.local.json`); without it the
|
|
50
92
|
probe logs a warning and leans on init's lease refusal alone.
|
|
51
93
|
|
|
94
|
+
**Overlapping footprints are reserved across beats, not just within one.** A
|
|
95
|
+
Story sharing a **concrete** path with a still-implementing Story is withheld
|
|
96
|
+
and named in `inFlightReservation: { available, withheld: [{ id, blockedBy,
|
|
97
|
+
reason }], note }`, where `reason` is `in-flight-earlier-beat` or
|
|
98
|
+
`foreign-lease`. Like `foreignHeld` this is neither a failure nor a wedge — the
|
|
99
|
+
Story re-admits automatically once its blocker leaves the in-flight set — and
|
|
100
|
+
it exists so an unfilled slot is explained rather than mysterious. A **glob**
|
|
101
|
+
footprint (or the UNKNOWN sentinel for an unparseable body) reserves nothing
|
|
102
|
+
across beats; it still serializes its own beat. Reservation needs the in-flight
|
|
103
|
+
Stories' footprints, so it is a `--probe-live` capability: under `--dag` the
|
|
104
|
+
report is `available: false` and selection de-conflicts within the beat only.
|
|
105
|
+
|
|
52
106
|
## Dispatch mechanics (role-scoped by default)
|
|
53
107
|
|
|
54
108
|
**A single-Story run executes inline.** Sub-agent isolation is
|
|
@@ -82,15 +136,28 @@ footprint keeps the fresh acceptance critic), and the `route::lite` label
|
|
|
82
136
|
remains a human-visible hint only, never the control signal — a lost or
|
|
83
137
|
never-written label cannot misroute delivery.
|
|
84
138
|
|
|
139
|
+
**Issue a beat's spawns in one turn.** A wave tick hands you a ready set, not a
|
|
140
|
+
queue: those Stories have no dependency edge between them (the resolver already
|
|
141
|
+
withheld any that do) and no shared write paths (each owns its own worktree and
|
|
142
|
+
branch). Dispatch them the way
|
|
143
|
+
[`parallel-tooling.md`](parallel-tooling.md) Rule 3 prescribes — **N `Agent`
|
|
144
|
+
calls issued together in a single assistant turn**, one per ready Story, not
|
|
145
|
+
`Agent` → wait → `Agent`. Serial dispatch is compliant with every other rule on
|
|
146
|
+
this page and costs the run a full Story's implementation time per sibling for
|
|
147
|
+
nothing; the wave aggregator is built for the parallel shape. Respect
|
|
148
|
+
`delivery.deliverRunner.concurrencyCap`: when the ready set exceeds it, slice
|
|
149
|
+
into batches of `cap` and dispatch each batch in its own turn.
|
|
150
|
+
|
|
85
151
|
**Dispatch each `ready` Story (role-scoped by default).** When
|
|
86
152
|
`delivery.routing.roleScopedAgents` is enabled (the **default**) and the host
|
|
87
153
|
exposes agent dispatch, spawn each ready Story as its own
|
|
88
154
|
`subagent_type: story-worker` sub-agent — it boots on the role-scoped
|
|
89
155
|
[`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
|
|
90
156
|
`CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs standalone. The
|
|
91
|
-
sub-agent executes [`deliver-story.md`](deliver-story.md)
|
|
92
|
-
(init → implement → acceptance self-eval →
|
|
93
|
-
|
|
157
|
+
sub-agent executes [`deliver-story.md`](deliver-story.md) Steps 0–2.5
|
|
158
|
+
(init → implement → acceptance self-eval → **push**) and stops there; **you**
|
|
159
|
+
own Step 3, serialized — see `/deliver` § Closing what the workers hand back.
|
|
160
|
+
Thread into its prompt: `storyId`; `docsDigestPath` (the per-run docs digest, null when
|
|
94
161
|
`project.docsContextFiles` is unset); `checklistPath` (the footprint-matched
|
|
95
162
|
write-time audit checklist, produced at dispatch, below); and the
|
|
96
163
|
**change-set discipline** — the worker computes the change set once with
|
|
@@ -220,8 +287,32 @@ A slow-CI consumer can opt the close into `"async"` mode so the merge wait
|
|
|
220
287
|
probes once for ~60s (catching an instant merge or an instantly-red required
|
|
221
288
|
check) and then returns `pending` instead of burning ~5 minutes of the host
|
|
222
289
|
tool slot polling a merge that lands after the wait would have expired anyway.
|
|
223
|
-
When a
|
|
290
|
+
When a close returns that `pending` envelope, launch its `nextCommand` as a
|
|
224
291
|
**background** invocation (host background Bash — its completion re-invokes the
|
|
225
292
|
agent) and move on to the next Story; `single-story-confirm-merge.js` is
|
|
226
293
|
idempotent and owns the whole tail. Do not foreground-poll the merge. The
|
|
227
294
|
default `"sync"` behaviour is unchanged.
|
|
295
|
+
|
|
296
|
+
**On a multi-Story run, pass `--merge-watch-mode async` on every close.** Close
|
|
297
|
+
sees one Story and cannot see run topology, so it cannot make this call for
|
|
298
|
+
itself — you can. Implementation runs in parallel but the close tail is
|
|
299
|
+
serialized one at a time, and under `sync` each of those closes holds the
|
|
300
|
+
foreground for its full merge wait before the next Story's close may start.
|
|
301
|
+
That is the run's dominant serialized cost, and it is paid per sibling:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
node <main-repo>/.agents/scripts/single-story-close.js \
|
|
305
|
+
--story <storyId> --cwd <main-repo> --merge-watch-mode async
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The flag overrides `delivery.mergeWatch.mode` for that invocation only — the
|
|
309
|
+
config default stays `"sync"`, which is right for the solo delivery that has no
|
|
310
|
+
sibling waiting behind it. It composes with `--max-wait-seconds`: pass both and
|
|
311
|
+
the explicit bound still wins over the async probe cap. An unrecognized value
|
|
312
|
+
exits non-zero before any phase runs, so a typo cannot silently drop the run
|
|
313
|
+
back onto synchronous waiting. Expect a `pending` envelope from each async
|
|
314
|
+
close — that is the designed ending here, not a failure; background its
|
|
315
|
+
`nextCommand` and move to the next Story's close immediately.
|
|
316
|
+
|
|
317
|
+
A one-Story run should keep the `sync` default: there is no sibling to unblock,
|
|
318
|
+
and the foreground wait is the cheapest path to `landed`.
|
|
@@ -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
|
|
@@ -197,6 +203,29 @@ round cap, proceed / redraft / block — not an independent additional pass
|
|
|
197
203
|
over the criteria. The M4-B floor holds: one verdict per cluster, the
|
|
198
204
|
cluster count owned by `acceptance-clusters.js` alone.
|
|
199
205
|
|
|
206
|
+
**One round = N cluster critics → ONE merged verdict → ONE gate call.** The
|
|
207
|
+
clusters are how a round is _authored_; they are not how it is _scored_.
|
|
208
|
+
Concatenate every cluster's records into a single `criteria[]` ordered by
|
|
209
|
+
`index` — exactly one per `acceptance[]` item, under one `storyId`,
|
|
210
|
+
`schemaVersion`, `round` and `commitSha` — and hand that merged file to the
|
|
211
|
+
gate once, with `--expected-criteria` set to the Story's `acceptance[]` count:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
node <main-repo>/.agents/scripts/acceptance-eval.js \
|
|
215
|
+
--story <storyId> --verdict <merged-verdict-path> \
|
|
216
|
+
--expected-criteria <acceptance[] count>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The flag is what makes the merge enforceable: `assertCriteriaCoverage` returns
|
|
220
|
+
early on the `null` default, so **omitting it leaves the guard inert** and a
|
|
221
|
+
single cluster's verdict handed over unmerged scores a fraction of the criteria
|
|
222
|
+
and still reports `proceed`. A length mismatch is rejected before scoring and
|
|
223
|
+
consumes no round. Calling the gate once per cluster instead spends a round
|
|
224
|
+
_per cluster_ — a Story past the cluster ceiling would burn its whole redraft
|
|
225
|
+
budget on cluster arithmetic — and N concurrent calls race the Story-scoped
|
|
226
|
+
round ledger. Full per-round mechanics, including the parallel dispatch and the
|
|
227
|
+
merge shape: [`acceptance-self-eval.md`](acceptance-self-eval.md).
|
|
228
|
+
|
|
200
229
|
**Critic evidence-share.** When the critic runs a `verify[]`
|
|
201
230
|
command that is byte-identical to a close gate (`lint` / `typecheck`), it
|
|
202
231
|
records the pass into the Story evidence keyspace via `--standalone` so
|
|
@@ -261,6 +290,31 @@ removed. Hard gates are untouched.
|
|
|
261
290
|
|
|
262
291
|
## Step 3 — Merge wait, async mode, and flags
|
|
263
292
|
|
|
293
|
+
**Step 3 is the orchestrator's, and it is serialized.** A dispatched
|
|
294
|
+
`story-worker` ends its turn at a pushed branch (spine § Step 2.5); the session
|
|
295
|
+
that dispatched it runs close. Two reasons, both measured rather than
|
|
296
|
+
theoretical:
|
|
297
|
+
|
|
298
|
+
1. **A sub-agent cannot resume itself.** It gets no notification when a
|
|
299
|
+
backgrounded close finishes, so a worker that backgrounds close and ends its
|
|
300
|
+
turn strands the envelope in a turn nobody reads — three of five workers in
|
|
301
|
+
one measured wave did exactly that despite an explicit foreground-close
|
|
302
|
+
instruction. Moving the seam removes the failure instead of re-wording the
|
|
303
|
+
prohibition. The parent, by contrast, is still live and _does_ observe and
|
|
304
|
+
retry its own close.
|
|
305
|
+
2. **Closes contend; implementation does not.** Close syncs from
|
|
306
|
+
`origin/<baseBranch>`, pushes, opens a PR and arms auto-merge — two of those
|
|
307
|
+
in flight race on the base branch, the merge queue and the shared checkout.
|
|
308
|
+
So implementation may fan out across the wave, but the tail runs **one Story
|
|
309
|
+
at a time**: a worker that hands back while another close is running waits in
|
|
310
|
+
the orchestrator's queue.
|
|
311
|
+
|
|
312
|
+
A worker therefore returns a hand-off report, not a terminal envelope, and that
|
|
313
|
+
is the expected shape — only close mints an envelope. Never answer a missing
|
|
314
|
+
envelope with a re-dispatch: `single-story-init.js` re-run under a live branch
|
|
315
|
+
is how one Story ends up with two closes. Close the pushed branch, or probe with
|
|
316
|
+
`deliver-recover.js` and run the one command it prints.
|
|
317
|
+
|
|
264
318
|
**What close does internally.** The script runs the close-validation gates
|
|
265
319
|
against `baseBranch`, syncs the Story branch from `origin/<baseBranch>`
|
|
266
320
|
(the parallel-race defence), pushes `story-<id>`, opens (or
|
|
@@ -280,19 +334,30 @@ separate (`delivery.mergeWatch.*`):
|
|
|
280
334
|
it when your host has no such ceiling and you want to land in one block.
|
|
281
335
|
- **`maxBudgetSeconds`** (default 3600) bounds the **cumulative** wait across
|
|
282
336
|
resumes, anchored at the PR's `createdAt` so resuming does not restart the
|
|
283
|
-
clock. Exhausting
|
|
337
|
+
clock. Exhausting _this_ is the genuine give-up → `blocked`.
|
|
284
338
|
|
|
285
339
|
The wait probes the checks every poll: a red required check fails fast as
|
|
286
340
|
`checks-failed` instead of burning the budget, and a PR that falls behind its
|
|
287
341
|
base is brought up to date within `updateAttempts` tries.
|
|
288
342
|
|
|
289
343
|
**Async merge-confirm mode (`delivery.mergeWatch.mode: "async"`).** Under
|
|
290
|
-
the default `"sync"` the merge wait runs in the foreground as
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
344
|
+
the default `"sync"` the merge wait runs in the foreground as described above.
|
|
345
|
+
|
|
346
|
+
**On a multi-Story run, async is the posture — pass `--merge-watch-mode async`
|
|
347
|
+
on every close.** This is not a slow-CI opt-in. Implementation fans out, but
|
|
348
|
+
the close tail is serialized one Story at a time, and under `sync` each close
|
|
349
|
+
holds the foreground for its full merge wait before the next may start; that
|
|
350
|
+
is the run's dominant serialized cost, paid once per sibling. Close sees one
|
|
351
|
+
Story and cannot see run topology, so the orchestrator — which can — makes the
|
|
352
|
+
call per invocation while the config default stays `"sync"`, which is right for
|
|
353
|
+
a solo delivery with no sibling waiting behind it
|
|
354
|
+
([`deliver-reference.md`](deliver-reference.md) § Async merge-confirm mode).
|
|
355
|
+
A slow-CI consumer reaches for the same mode for the separate reason that a
|
|
356
|
+
foreground wait longer than the host tool ceiling (~10 min) almost always
|
|
357
|
+
expires `pending` after burning ~5 minutes of the slot — `"async"` makes that
|
|
358
|
+
confirm a designed ending instead of an expiry accident.
|
|
359
|
+
|
|
360
|
+
In async mode the close arms auto-merge, runs one short **~60s
|
|
296
361
|
probe window** (long enough to catch an instant merge and, via the
|
|
297
362
|
head-anchored required-check predicate, an instantly-red required check),
|
|
298
363
|
then returns the standard `pending` terminal with a `nextCommand`. When you
|
|
@@ -312,7 +377,7 @@ checks pass. Under `"strict"`, the close **does not arm auto-merge** — the
|
|
|
312
377
|
PR opens and waits for an **operator merge**, exactly as `--no-auto-merge`
|
|
313
378
|
does per-run.
|
|
314
379
|
|
|
315
|
-
**When to reach for a close flag.** What each one
|
|
380
|
+
**When to reach for a close flag.** What each one _does_ is in
|
|
316
381
|
`node .agents/scripts/single-story-close.js --help`; below is only the
|
|
317
382
|
judgment that help text cannot carry.
|
|
318
383
|
|
|
@@ -331,6 +396,13 @@ judgment that help text cannot carry.
|
|
|
331
396
|
- `--max-wait-seconds <n>` — from a headless caller with no host
|
|
332
397
|
tool-invocation ceiling, to keep single-block semantics
|
|
333
398
|
without editing the consumer's config.
|
|
399
|
+
- `--merge-watch-mode <sync|async>` — the per-invocation override of
|
|
400
|
+
`delivery.mergeWatch.mode`. **Pass `async` on every close of a multi-Story
|
|
401
|
+
run** (above); leave it off for a solo delivery. It composes with
|
|
402
|
+
`--max-wait-seconds` — pass both and the explicit bound still wins over the
|
|
403
|
+
async probe cap. An unrecognized value is refused before any phase runs, so a
|
|
404
|
+
typo cannot silently drop the run back onto synchronous waiting; the refusal
|
|
405
|
+
reports a `failed` terminal envelope at `phase: init`, mutating nothing.
|
|
334
406
|
|
|
335
407
|
---
|
|
336
408
|
|
|
@@ -346,7 +418,7 @@ The `single-story-close.js` script, in order:
|
|
|
346
418
|
captured tail inline so the evidence is in front of you without opening a
|
|
347
419
|
file. Read the artifact when you need the full text — or re-run under
|
|
348
420
|
`AGENT_LOG_LEVEL=verbose` for live streaming.
|
|
349
|
-
1a. **Syncs the Story branch from `origin/<baseBranch>`** before push.
|
|
421
|
+
1a. **Syncs the Story branch from `origin/<baseBranch>`** before push.
|
|
350
422
|
Runs `git fetch origin <baseBranch>` followed by
|
|
351
423
|
`git merge --no-edit origin/<baseBranch>` inside the worktree. This
|
|
352
424
|
defends against the parallel-`/deliver-story` race: when
|
|
@@ -370,12 +442,13 @@ The `single-story-close.js` script, in order:
|
|
|
370
442
|
defence against the parallel race. Without merge queue, the sync
|
|
371
443
|
closes the PR-open-time race but a residual race remains between PR
|
|
372
444
|
open and auto-merge fire.
|
|
445
|
+
|
|
373
446
|
2. Pushes `story-<id>` to `origin`.
|
|
374
447
|
3. Probes for an existing open PR with `head = story-<id>`. If none
|
|
375
448
|
exists, opens one via `gh pr create --base <baseBranch>`. The PR
|
|
376
449
|
body carries `Closes #<storyId>` so the GitHub merge auto-closes the
|
|
377
450
|
issue.
|
|
378
|
-
3a. **Enables GitHub native auto-merge by default** via
|
|
451
|
+
3a. **Enables GitHub native auto-merge by default** via
|
|
379
452
|
`gh pr merge <prNumber> --auto --squash --delete-branch`. Once CI's
|
|
380
453
|
required checks turn green, GitHub squash-merges the PR and deletes
|
|
381
454
|
the source branch — the operator does not need to babysit the merge
|
|
@@ -385,7 +458,7 @@ The `single-story-close.js` script, in order:
|
|
|
385
458
|
pre-merge eyeball.
|
|
386
459
|
4. Flips the Story to **`agent::closing`** (NOT `agent::done`) and leaves
|
|
387
460
|
the GitHub issue **OPEN**. Auto-merge completes
|
|
388
|
-
asynchronously
|
|
461
|
+
asynchronously _after_ this script exits, so closing the issue here
|
|
389
462
|
would strand a CLOSED issue with no merged work if the PR later failed
|
|
390
463
|
CI, went `BEHIND` base, or was closed without merging. The Story rests
|
|
391
464
|
at `agent::closing` while the PR is open with auto-merge armed; the
|
|
@@ -430,9 +503,10 @@ close-validation gates pass on the dev host's environment; CI runs on a
|
|
|
430
503
|
different OS and concurrency, and coverage rounding, platform-conditional
|
|
431
504
|
branches, and timing-sensitive tests routinely drift between the two.
|
|
432
505
|
|
|
433
|
-
Fix the failure and push a new commit on `story-<storyId>` —
|
|
434
|
-
|
|
435
|
-
|
|
506
|
+
Fix the failure and push a new commit on `story-<storyId>` — the watcher
|
|
507
|
+
**disarmed native auto-merge on the first red** and re-arms it
|
|
508
|
+
only when the checks go green on a **new head SHA**, so the fix must be a real
|
|
509
|
+
commit — then resume the land with the envelope's `nextCommand`.
|
|
436
510
|
|
|
437
511
|
To watch the checks on the red path, drive `pr-watch-with-update.js` — the
|
|
438
512
|
**single CI-watch mechanism**. It polls the required checks to a
|
|
@@ -444,29 +518,54 @@ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber> --story <storyI
|
|
|
444
518
|
```
|
|
445
519
|
|
|
446
520
|
`--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
|
|
521
|
+
(`temp/story-<id>-ci-digest.{json,md}` — failing check name, the PR head SHA,
|
|
522
|
+
run id + run link, and a `gh run view --log-failed` tail). Omit it and a red
|
|
523
|
+
check writes no digest — and with no digest the no-rerun guard has nothing to
|
|
524
|
+
adjudicate the next green against, so always pass it.
|
|
449
525
|
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.
|
|
526
|
+
`maxPolls`, `maxResumes`, `attachWindowMs`); pass `--poll-interval-ms`,
|
|
527
|
+
`--max-polls`, `--max-resumes`, or `--attach-window-ms` to override for one run.
|
|
528
|
+
`attachWindowMs` (default 20 min) is how long the watch keeps re-resolving an
|
|
529
|
+
**empty** required-check set before it stops waiting for a context to attach —
|
|
530
|
+
a required context that is an aggregator job gated on every other tier is the
|
|
531
|
+
last check to appear, measured at 16m52s on this repository.
|
|
532
|
+
|
|
533
|
+
Add `--repo owner/repo` only when the cwd is not the target repository; it
|
|
534
|
+
reaches `gh` as a real flag. There is no `<owner/repo>#<number>` ref form —
|
|
535
|
+
`gh` parses that as a branch name.
|
|
452
536
|
|
|
453
537
|
When the watch exits, branch on the exit code:
|
|
454
538
|
|
|
455
539
|
- **Exit 0 (all checks ✓)** — auto-merge will fire (or has already). The Story
|
|
456
540
|
is still at `agent::closing` with its issue OPEN. **Proceed to merge
|
|
457
|
-
confirmation (§ Step 5) within the same turn** — green CI is the
|
|
541
|
+
confirmation (§ Step 5) within the same turn** — green CI is the _start_ of
|
|
458
542
|
the merge-confirm sequence, not a terminal state.
|
|
459
|
-
- **Exit 1 (a check genuinely failed
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
543
|
+
- **Exit 1 (a check genuinely failed, the green was a forbidden re-run, or the
|
|
544
|
+
PR itself could not be read)** —
|
|
545
|
+
diagnose, fix at source, and push a new commit on `story-<storyId>`, then
|
|
546
|
+
re-watch: the watcher disarmed auto-merge on the red and re-arms it only for
|
|
547
|
+
a green on a **new head SHA**. The Story stays at `agent::closing`
|
|
548
|
+
throughout, so a failed/abandoned PR never strands a CLOSED issue. If the
|
|
549
|
+
same failure class recurs, hand convergence off to a self-paced host loop
|
|
550
|
+
(`/loop`) that applies the smallest fix and pushes a new commit each pass —
|
|
551
|
+
**never** a bare re-run of the failed job. A green the guard rejects as a
|
|
552
|
+
re-run of the same commit flips the Story to `agent::blocked` with a
|
|
553
|
+
`friction` comment; clear it per
|
|
554
|
+
[`ci-remediation.md`](../../rules/ci-remediation.md) § Verifier.
|
|
555
|
+
- **Exit 2 (slow, not red)** — one of three slow conditions, **never** a
|
|
556
|
+
failure and never a green. Hand the wait off to the host's interval loop
|
|
468
557
|
rather than ending your turn: `/loop 5m` polling `gh pr checks` until the
|
|
469
|
-
checks settle.
|
|
558
|
+
checks settle. The envelope names which:
|
|
559
|
+
- **still-running** — the poll cap fired with checks still pending and the
|
|
560
|
+
watcher exhausted its resume budget with nothing red.
|
|
561
|
+
- **not-yet-started** (`notYetStarted: true`) — the attach window was spent
|
|
562
|
+
and **no** required context ever attached, while the PR kept reading back
|
|
563
|
+
fine. CI has not started; there is no failing check and no CI digest to
|
|
564
|
+
read. Do **not** treat it as red — nothing needs fixing, and re-watching
|
|
565
|
+
(or raising `attachWindowMs`) is the whole remediation.
|
|
566
|
+
- **unresolved** (`reconciliation.reconciled: false`) — every observed
|
|
567
|
+
required check is green but the repository still refuses the merge, so the
|
|
568
|
+
green verdict is withheld.
|
|
470
569
|
|
|
471
570
|
**Triage authority.** How to classify and remediate a red (or repeatedly slow)
|
|
472
571
|
check — the root-cause-only decision tree for infra/transient and flaky failures
|
|
@@ -485,7 +584,7 @@ then ends its turn with **free-form prose** — e.g. "I'll wait for the
|
|
|
485
584
|
background watch task to complete" or "the next event will be its completion
|
|
486
585
|
notification" — leaving the merge unconfirmed and the Story stranded at
|
|
487
586
|
`agent::closing`. **Do not do this.**
|
|
488
|
-
`pr-watch-with-update.js --pr <prNumber>`
|
|
587
|
+
`pr-watch-with-update.js --pr <prNumber>` _blocks the current turn_ until CI
|
|
489
588
|
resolves — that is the mechanism by which you wait. You MUST keep your turn alive
|
|
490
589
|
across the wait: watch → (fix + push + re-watch on red) → confirm the merge
|
|
491
590
|
(Step 5) → flip `agent::done` → run the post-merge steps → and only then
|
|
@@ -551,12 +650,13 @@ the watch exits clean.
|
|
|
551
650
|
`agent::blocked`, summarize the blocker on the PR, and yield to the
|
|
552
651
|
operator.
|
|
553
652
|
|
|
554
|
-
### Idempotence of the loop
|
|
653
|
+
### Idempotence of the loop {#idempotence}
|
|
555
654
|
|
|
556
655
|
- The PR stays open across retries; `gh pr create` is a one-shot at
|
|
557
656
|
close, the loop only pushes new commits.
|
|
558
|
-
- Auto-merge
|
|
559
|
-
|
|
657
|
+
- Auto-merge is disarmed by the watcher on the first red and re-armed
|
|
658
|
+
when the checks go green on a new head SHA; pushing a new commit is
|
|
659
|
+
what re-opens the merge path.
|
|
560
660
|
- If the operator manually merges or disables auto-merge mid-loop,
|
|
561
661
|
exit the loop and report.
|
|
562
662
|
|
|
@@ -612,7 +712,7 @@ no-op-safe (`no-project` / `not-on-project` exit 0).
|
|
|
612
712
|
|
|
613
713
|
The GitHub Projects v2 built-in workflows `Pull request merged` and
|
|
614
714
|
`Pull request linked to issue` are enabled by default on most boards
|
|
615
|
-
and fire ~minutes
|
|
715
|
+
and fire ~minutes _after_ auto-merge lands. They overwrite the Status
|
|
616
716
|
field as a side-effect, clobbering the `Done` value
|
|
617
717
|
`single-story-confirm-merge.js` set at the `agent::done` flip in Step 5
|
|
618
718
|
and leaving closed Stories stuck at `In Progress` on the board. The
|
|
@@ -680,7 +780,7 @@ GitHub deletes the **remote** branch on auto-merge (via the
|
|
|
680
780
|
`--delete-branch` flag `single-story-close.js` passes to `gh pr merge`).
|
|
681
781
|
The **local** `story-<storyId>` ref, however, lingers in the main
|
|
682
782
|
checkout until something prunes it — `single-story-init.js` runs a
|
|
683
|
-
merged-sweep at the start of every
|
|
783
|
+
merged-sweep at the start of every _subsequent_ `/deliver-story`
|
|
684
784
|
invocation, but that's next-run cleanup, not end-of-run cleanup. Stale
|
|
685
785
|
local refs accumulate between sessions, clutter `git branch`, and shadow
|
|
686
786
|
the lessons the sweep is meant to surface.
|
|
@@ -717,6 +817,27 @@ up").
|
|
|
717
817
|
|
|
718
818
|
---
|
|
719
819
|
|
|
820
|
+
## Idempotence and the standing constraints
|
|
821
|
+
|
|
822
|
+
Every script in the chain no-ops safely on re-run: `single-story-init.js`
|
|
823
|
+
re-prints `workCwd` for an already-initialized Story; `single-story-close.js`
|
|
824
|
+
and `single-story-confirm-merge.js` short-circuit on a closed or `agent::done`
|
|
825
|
+
Story; the PR probe reuses an open PR rather than opening a second one. That is
|
|
826
|
+
what makes the recovery router safe to walk more than once.
|
|
827
|
+
|
|
828
|
+
The four constraints the spine states without arguing for them:
|
|
829
|
+
|
|
830
|
+
- **Never push the Story branch directly to `main`.** The PR is the only merge
|
|
831
|
+
surface — a direct push bypasses required checks and the squash title
|
|
832
|
+
release-please parses.
|
|
833
|
+
- **Always prefix path-based tools with the absolute `workCwd` root.** `cd`
|
|
834
|
+
scopes Bash, not Edit/Write/Read; close's wrong-tree guard is a backstop for
|
|
835
|
+
the mistake, not a licence to make it.
|
|
836
|
+
- **Report state, not process.** Mirror the close envelope's fields; step
|
|
837
|
+
narration reads as progress while telling the caller nothing it can branch on.
|
|
838
|
+
- **Drive every `agent::*` transition through `update-ticket-state.js`** so the
|
|
839
|
+
label, the Projects Status column and the lifecycle event stay in one motion.
|
|
840
|
+
|
|
720
841
|
## Step 7 — Return-contract detail
|
|
721
842
|
|
|
722
843
|
The field-level contract is the shipped schema
|
|
@@ -724,7 +845,7 @@ The field-level contract is the shipped schema
|
|
|
724
845
|
— not this file, and not
|
|
725
846
|
[`agents/story-worker.md`](../../agents/story-worker.md). All three used to
|
|
726
847
|
carry their own prose version; the schema is now the only definition. What
|
|
727
|
-
follows is the
|
|
848
|
+
follows is the _judgement_ around it, which a schema cannot express.
|
|
728
849
|
|
|
729
850
|
### `pending` is a real status — and it is not a park
|
|
730
851
|
|
|
@@ -767,8 +888,9 @@ persists the validated envelope to
|
|
|
767
888
|
- **It is best-effort.** A failed write returns null and changes nothing about
|
|
768
889
|
the emitted envelope or the exit code — a landed PR must never become a
|
|
769
890
|
crash because a temp directory was unwritable.
|
|
770
|
-
- **It is a fallback, not a licence.**
|
|
771
|
-
envelope arrives; see
|
|
891
|
+
- **It is a fallback, not a licence.** The orchestrator running close still
|
|
892
|
+
holds its turn until the envelope arrives; see § Step 3 above and
|
|
893
|
+
[`agents/story-worker.md`](../../agents/story-worker.md).
|
|
772
894
|
|
|
773
895
|
`deliver-recover.js` reads the same artifact, plus the freshness of
|
|
774
896
|
`close-gates-<storyId>.log`, to split the one genuinely ambiguous row of its
|