mandrel 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/.markdownlint.json +4 -0
- package/.agents/agents/acceptance-critic.md +30 -5
- package/.agents/agents/auditor.md +36 -19
- package/.agents/agents/plan-critic.md +31 -5
- package/.agents/agents/story-worker.md +91 -100
- package/.agents/docs/configuration.md +39 -25
- package/.agents/docs/execution-reference.md +13 -0
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +131 -265
- package/.agents/rules/git-conventions.md +47 -83
- package/.agents/rules/orchestration-error-handling.md +28 -0
- package/.agents/schemas/agentrc.schema.json +36 -9
- package/.agents/schemas/validation-evidence.schema.json +3 -1
- package/.agents/scripts/acceptance-eval.js +10 -6
- package/.agents/scripts/apply-quality-bootstrap.js +1 -1
- package/.agents/scripts/check-test-temp-hygiene.js +438 -0
- package/.agents/scripts/deliver-recover.js +23 -6
- package/.agents/scripts/lib/audit-suite/index.js +5 -0
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/baselines/env-overrides.js +33 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
- package/.agents/scripts/lib/config/quality.js +13 -0
- package/.agents/scripts/lib/config/temp-paths.js +121 -1
- package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
- package/.agents/scripts/lib/config-settings-schema.js +12 -16
- package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
- package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
- package/.agents/scripts/lib/orchestration/complexity-gate.js +533 -93
- package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
- package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
- package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -3
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
- package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +153 -2
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +221 -14
- package/.agents/scripts/lib/orchestration/resolve-stories.js +12 -1
- package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
- package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
- package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
- package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
- package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
- package/.agents/scripts/lib/test-env.js +65 -0
- package/.agents/scripts/plan-context.js +84 -9
- package/.agents/scripts/plan-critics.js +115 -3
- package/.agents/scripts/plan-persist.js +11 -1
- package/.agents/scripts/plan-run-epilogue.js +1 -1
- package/.agents/scripts/resolve-stories.js +2 -0
- package/.agents/scripts/single-story-confirm-merge.js +65 -5
- package/.agents/scripts/stories-wave-tick.js +1 -1
- package/.agents/workflows/deliver.md +88 -230
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
- package/.agents/workflows/helpers/deliver-reference.md +171 -0
- package/.agents/workflows/helpers/deliver-story-reference.md +223 -0
- package/.agents/workflows/helpers/deliver-story.md +115 -432
- package/.agents/workflows/helpers/plan-reference.md +246 -0
- package/.agents/workflows/plan.md +108 -304
- package/docs/CHANGELOG.md +46 -0
- package/lib/cli/registry.js +31 -14
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
- package/package.json +1 -1
|
@@ -1,64 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
description:
|
|
3
3
|
Unified planning entry point. Interrogate → author → persist. Emits one
|
|
4
|
-
Story by default
|
|
5
|
-
|
|
4
|
+
Story by default; splits into N>1 only under the default-single split
|
|
5
|
+
policy.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /plan --seed "<text>" | --seed-file <path> | --tickets <ids>
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Epic/Story router, no scope-triage `epic|story` verdict, and no
|
|
14
|
-
`deliveryShape`. Two operator modes only:
|
|
15
|
-
|
|
16
|
-
1. **Text** — seed from chat or a file.
|
|
17
|
-
2. **Tickets** — analyze existing issue(s) into proper Stories.
|
|
18
|
-
|
|
19
|
-
Audit findings become Stories via [`/audit-to-stories`](audit-to-stories.md)
|
|
20
|
-
(separate workflow), which hands off with `--emit-plan-seed` →
|
|
21
|
-
`/plan --seed-file <path>`.
|
|
10
|
+
> **Lean spine.** Happy path + gate list; edge-case and reference detail
|
|
11
|
+
> lives in the on-demand
|
|
12
|
+
> [`helpers/plan-reference.md`](helpers/plan-reference.md).
|
|
22
13
|
|
|
23
14
|
## Inputs
|
|
24
15
|
|
|
16
|
+
Single planning path — there is no
|
|
17
|
+
Epic/Story router, no scope-triage `epic|story` verdict:
|
|
18
|
+
|
|
25
19
|
| Invocation | Behavior |
|
|
26
20
|
| --- | --- |
|
|
27
|
-
| `/plan --seed "<text>"` | Ideation from chat text: interrogate → author **one Story by default** → persist. |
|
|
28
|
-
| `/plan --seed-file <path>` | Author from on-disk notes / plan seed (e.g. audit-to-stories handoff). |
|
|
21
|
+
| `/plan --seed "<text>"` / `--seed-file <path>` | Ideation from chat text or on-disk notes: interrogate → author **one Story by default** → persist. |
|
|
29
22
|
| `/plan --tickets 123[,456…]` | Fetch issue(s), analyze into proper Stories (prefer N=1 rewrite). |
|
|
30
23
|
|
|
31
|
-
`--body` is **not** a `/plan` entry
|
|
32
|
-
`plan-persist.js
|
|
24
|
+
`--body` is **not** a `/plan` entry; persist always goes through
|
|
25
|
+
`plan-persist.js`.
|
|
33
26
|
|
|
34
27
|
## Flags
|
|
35
28
|
|
|
36
29
|
| Flag | Meaning |
|
|
37
30
|
| --- | --- |
|
|
38
|
-
| `--seed "<text>"` | Seed text
|
|
39
|
-
| `--
|
|
40
|
-
| `--
|
|
41
|
-
| `--
|
|
42
|
-
| `--
|
|
43
|
-
| `--allow-over-budget` | Permit a plan
|
|
31
|
+
| `--seed "<text>"` / `--seed-file <path>` | Seed text / pre-authored notes path. |
|
|
32
|
+
| `--tickets <ids>` | Issue ids to analyze; closed as superseded at persist. |
|
|
33
|
+
| `--no-close-superseded` | Keep the source issues open — no supersede comment, no close. |
|
|
34
|
+
| `--force-review` | STOP at gate #2 for operator review — the only review gate (Story #4542). |
|
|
35
|
+
| `--route-downgrade-reason "<text>"` | Authored `lite` verdict + reason (Story #4722), ledgered per Story; shape-validated, fails closed to `full`. |
|
|
36
|
+
| `--allow-over-budget` | Permit a plan exceeding `maxTickets`. |
|
|
44
37
|
| `--yes` | Non-interactive: auto-proceed gate #1 and gate #2 HITL waits. |
|
|
45
|
-
| `--dry-run` | Author + validate without GitHub writes
|
|
38
|
+
| `--dry-run` | Author + validate without GitHub writes; run as a pre-pass. |
|
|
46
39
|
|
|
47
40
|
## Default-single split policy
|
|
48
41
|
|
|
49
|
-
Author **one Story** unless
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`assertAcceptancePartition` and refuses coupled splits.
|
|
58
|
-
|
|
59
|
-
**N=1 is the lean path:** one authoring prompt, folded `## Spec` in the
|
|
60
|
-
Story body, light risk/critic profile. Do not run Epic-scale decompose,
|
|
61
|
-
clarity, or reconciler ceremony for a single Story.
|
|
42
|
+
Author **one Story** unless (1) the pieces have **near-zero overlap**
|
|
43
|
+
(genuinely independent capabilities), or (2) there is an **architectural
|
|
44
|
+
seam** (different deployables, migration vs consumer). Coupled work stays
|
|
45
|
+
one Story — decompose it inside `## Slicing` as intra-session checkpoints,
|
|
46
|
+
not sibling tickets. When N>1, every acceptance criterion belongs to exactly
|
|
47
|
+
one Story (`assertAcceptancePartition` refuses coupled splits). **N=1 is the
|
|
48
|
+
lean path:** one authoring prompt, folded `## Spec`, light risk/critic
|
|
49
|
+
profile — no Epic-scale ceremony.
|
|
62
50
|
|
|
63
51
|
## Procedure
|
|
64
52
|
|
|
@@ -71,315 +59,131 @@ node .agents/scripts/plan-context.js --seed "<seed>" \
|
|
|
71
59
|
# or: --tickets 123,456
|
|
72
60
|
```
|
|
73
61
|
|
|
74
|
-
**Always pass `--out
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
62
|
+
**Always pass `--out`.** Persist auto-discovers that envelope from
|
|
63
|
+
`--plan-dir` and derives source-ticket ids from its `sourceTickets[]`
|
|
64
|
+
(Story #4554); the CLI also writes **`stories.template.json`** — the
|
|
65
|
+
authoring skeleton step 2 starts from.
|
|
66
|
+
|
|
67
|
+
The envelope carries docs context, codebase snapshot, the story-author
|
|
68
|
+
prompt, `sourceTickets[]`, `duplicates[]` (open **Stories** overlapping the
|
|
69
|
+
seed — never Epics), and advisory `complexitySignals` (**no routing
|
|
70
|
+
authority**, Story #4722). A genuinely trivial scope earns
|
|
71
|
+
`--route-downgrade-reason "<why>"` at persist — shape-validated, failing
|
|
72
|
+
closed to `full`. Detail:
|
|
73
|
+
[`helpers/plan-reference.md` § Shape-derived routing](helpers/plan-reference.md).
|
|
84
74
|
Under `--yes`, do not ask free-form operator questions — unresolved
|
|
85
75
|
unknowns land in Key Assumptions.
|
|
86
76
|
|
|
87
|
-
#### Ceremony-lite complexity gate (`complexityRoute`)
|
|
88
|
-
|
|
89
|
-
The envelope's `complexityRoute` field is a **deterministic, conservative**
|
|
90
|
-
plan-time gate (Story #4683) that routes a genuinely trivial single-artifact
|
|
91
|
-
seed onto a collapsed path so it stops paying the full two-session
|
|
92
|
-
plan/deliver ceremony that measurably buys no quality at that size:
|
|
93
|
-
|
|
94
|
-
- **`route: "lite"`** — a trivial scope (seed ≤ `maxSeedWords` words **and** ≤
|
|
95
|
-
`maxArtifacts` enumerated items). Collapse the ceremony: author **one minimal
|
|
96
|
-
Story** and skip the fresh-critic / Tech-Spec ceremony a one-artifact scope
|
|
97
|
-
does not earn. The lite route is **not** licence to drop a non-negotiable —
|
|
98
|
-
its `preserves` field enumerates exactly what still holds: the Story ticket,
|
|
99
|
-
the PR-to-`main` landing, every repo quality gate, and the security baseline.
|
|
100
|
-
Those gates still run in `single-story-close.js` regardless of route.
|
|
101
|
-
- **`route: "full"`** — everything else. The gate fails toward `full` on any
|
|
102
|
-
doubt (empty seed, over the word ceiling, a multi-capability enumeration, or
|
|
103
|
-
the gate disabled via `planning.complexityGate.enabled=false`), so a real
|
|
104
|
-
capability slice never loses ceremony. Author normally under the split policy.
|
|
105
|
-
|
|
106
|
-
The threshold and its override knob (`planning.complexityGate.{enabled,
|
|
107
|
-
maxSeedWords, maxArtifacts}`) are documented in
|
|
108
|
-
[`.agents/docs/configuration.md`](../docs/configuration.md) under `### planning`;
|
|
109
|
-
the defaults live on `DEFAULT_COMPLEXITY_GATE` in
|
|
110
|
-
[`lib/orchestration/complexity-gate.js`](../scripts/lib/orchestration/complexity-gate.js).
|
|
111
|
-
|
|
112
77
|
**Gate #1** — STOP to confirm the sharpened plan intent and any
|
|
113
78
|
duplicate-candidate review. Under `--yes`, auto-proceed.
|
|
114
79
|
|
|
115
80
|
### 2. Author
|
|
116
81
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
approach prose in `## Spec`, binding criteria in top-level
|
|
124
|
-
`acceptance[]` / `verify[]` (persist syncs them into the body). Do not
|
|
125
|
-
restate Goal/Acceptance inside Spec. Over-budget Specs fail closed —
|
|
126
|
-
split the Story or tighten Spec; never write Specs under `docs/`.
|
|
127
|
-
- optional `techspec.md` — **N===1 only** convenience when Spec was authored
|
|
128
|
-
outside the Story JSON; persist folds it into that Story's `## Spec`.
|
|
129
|
-
Forbidden for N>1 (each Story must carry its own Spec).
|
|
130
|
-
- optional `acceptance-manifest.json` — plan-level AC list (a JSON
|
|
131
|
-
`string[]`) for partition coverage when N>1. Pass it to persist as
|
|
132
|
-
`--plan-acceptance` or it is not read.
|
|
133
|
-
|
|
134
|
-
For N=1, use the envelope `systemPrompts.story` and emit one cohesive
|
|
135
|
-
Story. Split only under the policy above.
|
|
136
|
-
|
|
137
|
-
**Tickets mode — author `supersedes[]` on every Story.** In `--tickets`
|
|
138
|
-
mode each Story carries a top-level `supersedes` array claiming the source
|
|
139
|
-
issues it replaces. It is bookkeeping, not part of the Story body, so it is
|
|
140
|
-
never serialized into the markdown:
|
|
82
|
+
**One-shot authoring (Story #4707).** Start from `stories.template.json`;
|
|
83
|
+
author `stories.json` in one pass. Entries are pre-resolved (#4723); keep
|
|
84
|
+
tiers/assumptions valid. `body` is a
|
|
85
|
+
markdown string **or** a structured object; persist parses either,
|
|
86
|
+
serializes the canonical markdown, and syncs the top-level `acceptance[]` /
|
|
87
|
+
`verify[]` into the body — never dual-author those lists.
|
|
141
88
|
|
|
142
89
|
```jsonc
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
90
|
+
// temp/plan-<slug>/stories.json
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
"slug": "hyphen-case-slug", // ^[a-z0-9][a-z0-9-]*$
|
|
94
|
+
"type": "story",
|
|
95
|
+
"title": "Short descriptive title",
|
|
96
|
+
"body": {
|
|
97
|
+
"goal": "One sentence: why this Story exists.",
|
|
98
|
+
"spec": "Optional — contract and invariants.",
|
|
99
|
+
"changes": [{ "path": "path/to/file.ext", "assumption": "refactors-existing" }], // creates | refactors-existing | deletes
|
|
100
|
+
"non_goals": [],
|
|
101
|
+
"reason_to_exist": "One coherent reason this Story exists."
|
|
102
|
+
},
|
|
103
|
+
"acceptance": ["A testable, observable criterion"],
|
|
104
|
+
"verify": ["exact command (unit|contract|e2e|validate)"],
|
|
105
|
+
"depends_on": [] // sibling Story slugs, N>1 only
|
|
106
|
+
}
|
|
107
|
+
]
|
|
150
108
|
```
|
|
151
109
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
110
|
+
Artifacts under `temp/plan-<slug>/`: `stories.json`
|
|
111
|
+
(**length 1 by default**; over-budget Specs fail closed — split or tighten,
|
|
112
|
+
never write Specs under `docs/`); optional `techspec.md` (**N===1 only** —
|
|
113
|
+
folded into `## Spec`); optional `acceptance-manifest.json` (N>1 partition
|
|
114
|
+
list — pass as `--plan-acceptance` or it is not read). For N=1,
|
|
115
|
+
use the envelope `systemPrompts.story` and emit one cohesive Story.
|
|
116
|
+
Split only under the policy above.
|
|
158
117
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`plan-
|
|
162
|
-
Story (mirroring `assertAcceptancePartition`): every id passed to
|
|
163
|
-
`--tickets` must be claimed by **exactly one** Story, and no Story may
|
|
164
|
-
claim an id that was not a source ticket. With N>1 the mapping is not
|
|
165
|
-
total by default — an authored map is the only thing that can say
|
|
166
|
-
`#4525-#4528 → #4530` while `#4529 → #4531`, which a blanket "superseded by
|
|
167
|
-
this plan-run" reference could not.
|
|
118
|
+
**Tickets mode:** every Story authors a top-level `supersedes[]` claiming
|
|
119
|
+
the source issues it replaces; persist refuses a partial map (shape:
|
|
120
|
+
[`helpers/plan-reference.md` § Tickets mode](helpers/plan-reference.md)).
|
|
168
121
|
|
|
169
122
|
### 2.5 Critics
|
|
170
123
|
|
|
171
|
-
Evaluate the critic-dispatch conditions against the authored draft — here,
|
|
172
|
-
**before** persist, because this is the last point where a finding can still
|
|
173
|
-
be folded into a re-author round rather than into live issues:
|
|
174
|
-
|
|
175
124
|
```bash
|
|
176
125
|
node .agents/scripts/plan-critics.js \
|
|
177
126
|
--stories temp/plan-<slug>/stories.json \
|
|
178
127
|
[--tech-spec temp/plan-<slug>/techspec.md]
|
|
179
128
|
```
|
|
180
129
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
not proceed to Persist** — fix the path and re-run:
|
|
186
|
-
|
|
187
|
-
```jsonc
|
|
188
|
-
{
|
|
189
|
-
"consolidation": { "critic": "consolidation", "dispatch": false, "reasons": ["…"] },
|
|
190
|
-
"premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] },
|
|
191
|
-
"textHygiene": { "critic": "text-hygiene", "findings": [] }
|
|
192
|
-
}
|
|
193
|
-
```
|
|
130
|
+
Run **before** persist — the last point a finding folds into a re-author
|
|
131
|
+
round. It exits 0 on **any** verdict (verdicts route work, they do not
|
|
132
|
+
gate) and exits **1** only on a usage/IO error — no critic ran, no skip
|
|
133
|
+
ledgered: **do not proceed to Persist**; fix and re-run.
|
|
194
134
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
`
|
|
203
|
-
|
|
204
|
-
- **Both `dispatch: false`** — proceed straight to Persist. The conditions
|
|
205
|
-
provably have nothing for a critic to find, and each skip is recorded on the
|
|
206
|
-
plan-metrics ledger so under-firing stays auditable.
|
|
207
|
-
- **Either `dispatch: true`** — dispatch **one fresh-context sub-agent per
|
|
208
|
-
firing critic**, then fold its findings into the Gate #2 view or a re-author
|
|
209
|
-
round before persist. When `delivery.routing.roleScopedAgents` is enabled
|
|
210
|
-
(the **default**), dispatch each firing critic with `subagent_type:
|
|
211
|
-
plan-critic` — it boots on the role-scoped
|
|
212
|
-
[`plan-critic`](../agents/plan-critic.md) context (its own system prompt, no
|
|
213
|
-
`CLAUDE.md` @-closure) that carries the maker-blind invariant, the
|
|
214
|
-
`consolidation` and `pre-mortem` charters, and the output shape standalone.
|
|
215
|
-
When the kill-switch is off (`roleScopedAgents: false`) or the host cannot
|
|
216
|
-
spawn at this depth, fall back to a generic sub-agent and hand it the same
|
|
217
|
-
charter (the `consolidation` / `pre-mortem` definitions in
|
|
218
|
-
[`plan-critic.md`](../agents/plan-critic.md)). Either way the critic is
|
|
219
|
-
**maker-blind**: hand it the draft artifacts (`stories.json`, and
|
|
220
|
-
`techspec.md` when present) — never the authoring transcript or the reasons
|
|
221
|
-
the planner believed its own draft is sound. A critic that reads the maker's
|
|
222
|
-
case grades the case, not the draft.
|
|
223
|
-
|
|
224
|
-
Fold what survives back into `stories.json` and re-run this step. Findings are
|
|
225
|
-
advisory input to the operator's Gate #2 decision, not an automatic re-author
|
|
226
|
-
mandate.
|
|
135
|
+
- **Both `dispatch: false`** — proceed to Persist (each skip is ledgered).
|
|
136
|
+
- **Either `dispatch: true`** — dispatch **one fresh-context, maker-blind
|
|
137
|
+
sub-agent per firing critic** (hand it only the draft artifacts,
|
|
138
|
+
never the authoring transcript), fold findings into Gate #2 or a
|
|
139
|
+
re-author round, re-run this step. Pre-mortem triggers (incl. the
|
|
140
|
+
external-dependency probe, #4700), folding the advisory-only
|
|
141
|
+
`textHygiene.findings[]` lints, and the role-scoped dispatch shape:
|
|
142
|
+
[`helpers/plan-reference.md` § Critic dispatch detail](helpers/plan-reference.md).
|
|
227
143
|
|
|
228
144
|
### 3. Persist
|
|
229
145
|
|
|
230
|
-
**Gate #2** —
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`--force-review` is the **only** thing that raises this gate — there is no
|
|
234
|
-
risk-derived routing (Story #4542): a gate a plan can lower for itself is not a
|
|
235
|
-
gate.
|
|
146
|
+
**Gate #2** — with `--force-review`, STOP for approval before persist (the
|
|
147
|
+
**only** trigger). Under `--yes`, auto-proceed.
|
|
236
148
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
149
|
+
Run persist with `--dry-run` **first** — same command, GitHub writes
|
|
150
|
+
suppressed; every gate (ticket validator, body parse, DAG, capacity,
|
|
151
|
+
budget, reachability, split-policy and supersede partitions, Spec fold)
|
|
152
|
+
runs before the first `createIssue`. Then:
|
|
241
153
|
|
|
242
154
|
```bash
|
|
243
155
|
node .agents/scripts/plan-persist.js \
|
|
244
156
|
--stories temp/plan-<slug>/stories.json \
|
|
245
|
-
[--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
|
|
246
|
-
[--tech-spec temp/plan-<slug>/techspec.md] \
|
|
247
157
|
--plan-dir temp/plan-<slug> \
|
|
248
|
-
--dry-run
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Every gate — ticket validator, body parse, subject-prefix, DAG, capacity,
|
|
252
|
-
budget, reachability, split-policy and supersede partitions, Spec fold —
|
|
253
|
-
runs **before** the first `createIssue`, so a dry-run exercises all of them
|
|
254
|
-
write-free. An authoring mistake surfaces here, where the fix costs one
|
|
255
|
-
re-author, instead of after `k` of `N` Stories are already live.
|
|
256
|
-
|
|
257
|
-
#### The real persist
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
node .agents/scripts/plan-persist.js \
|
|
261
|
-
--stories temp/plan-<slug>/stories.json \
|
|
262
158
|
[--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
|
|
263
159
|
[--tech-spec temp/plan-<slug>/techspec.md] \
|
|
264
|
-
[--
|
|
265
|
-
[--plan-context temp/plan-<slug>/plan-context.json] \
|
|
266
|
-
[--source-tickets 123,456] \
|
|
267
|
-
[--no-close-superseded] \
|
|
268
|
-
[--force-review] \
|
|
269
|
-
[--allow-over-budget]
|
|
160
|
+
[--source-tickets 123,456] [...flags from the table above]
|
|
270
161
|
```
|
|
271
162
|
|
|
272
|
-
|
|
273
|
-
—
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
into the sibling's body as a `blocked by #<id>` footer — the ordering
|
|
278
|
-
`/deliver` resolves from. No batch label is applied (Story #4540 retired
|
|
279
|
-
`plan-run::<id>`). Ends by naming the exact command:
|
|
280
|
-
`/deliver <storyId> [<storyId> ...]`.
|
|
281
|
-
|
|
282
|
-
stdout is a pure JSON result; all log lines go to stderr, so a headless
|
|
283
|
-
driver can `JSON.parse` the stdout stream directly.
|
|
284
|
-
|
|
285
|
-
#### Ready means fully persisted
|
|
286
|
-
|
|
287
|
-
`agent::ready` is the **terminal** step, not part of the creating POST
|
|
288
|
-
(Story #4541). The order is: create unlabelled → upsert `story-plan-state` on
|
|
289
|
-
every Story → upsert `plan-summary` on the primary → flip every Story to
|
|
290
|
-
`agent::ready`.
|
|
291
|
-
|
|
292
|
-
This is what lets `/deliver` trust the label: a Story carrying
|
|
293
|
-
`agent::ready` always has its persist receipt on the ticket, so nothing can
|
|
294
|
-
pick it up mid-write and read a half-persisted plan.
|
|
295
|
-
|
|
296
|
-
#### Resuming a failed persist
|
|
297
|
-
|
|
298
|
-
Persist is **idempotent over the same authored artifacts**. Each created body
|
|
299
|
-
carries an invisible plan fingerprint (derived from the Story's slug +
|
|
300
|
-
title), and persist indexes the open `type::story` backlog by it before
|
|
301
|
-
creating anything.
|
|
302
|
-
|
|
303
|
-
So if a transient GitHub failure strands the run at Story `k` of `N`:
|
|
304
|
-
|
|
305
|
-
| | Behaviour |
|
|
306
|
-
| --- | --- |
|
|
307
|
-
| The `1..k-1` Stories | Live, but **not** `agent::ready` — invisible to `/deliver`, not half-delivered. |
|
|
308
|
-
| Re-running persist | Adopts them by fingerprint, creates only the missing ones, then flips the whole cohort ready. |
|
|
309
|
-
| Editing `stories.json` first | Changing a slug or title changes the fingerprint — the old issue is orphaned rather than adopted. Close it by hand. |
|
|
310
|
-
|
|
311
|
-
Just re-run the same command. Do not hand-delete the stranded issues first.
|
|
312
|
-
|
|
313
|
-
#### Temp hygiene
|
|
314
|
-
|
|
315
|
-
A terminal-success run deletes its own `--plan-dir`. Every persist also reaps
|
|
316
|
-
abandoned `temp/plan-*` directories older than 7 days, so dry-runs, failed
|
|
317
|
-
gates, and abandoned authoring sessions do not accumulate under `temp/`.
|
|
318
|
-
|
|
319
|
-
### How the source ids reach persist
|
|
320
|
-
|
|
321
|
-
In `--tickets` mode persist needs to know which ids were fetched. It resolves
|
|
322
|
-
them **envelope-first** (Story #4554):
|
|
323
|
-
|
|
324
|
-
| Channel | When it wins |
|
|
325
|
-
| --- | --- |
|
|
326
|
-
| Envelope `sourceTickets[]` | **The normal path.** Written by step 1's `--out`, then read from `--plan-context <file>` or auto-discovered at `<plan-dir>/plan-context.json`. No ids to re-type. |
|
|
327
|
-
| `--source-tickets <ids>` | Explicit **override** for hand-driven runs (no captured envelope, or deliberately narrowing the set). Wins over the envelope; a disagreement is warned about, not silently reconciled. |
|
|
328
|
-
|
|
329
|
-
The result envelope's `supersede.sourceTicketOrigin` reports which channel was
|
|
330
|
-
used (`envelope` \| `flag` \| `none`).
|
|
331
|
-
|
|
332
|
-
Every path with no envelope is **audible** — persist cannot tell a legitimate
|
|
333
|
-
`--seed` run from a `--tickets` run whose envelope was never captured, so it
|
|
334
|
-
says so rather than deciding silently:
|
|
335
|
-
|
|
336
|
-
| Situation | Behaviour |
|
|
337
|
-
| --- | --- |
|
|
338
|
-
| Neither `--plan-dir` nor `--plan-context` | **Warn** — nothing was read; only `--source-tickets` can supply ids. |
|
|
339
|
-
| Auto-discovered `<plan-dir>/plan-context.json` absent | **Warn** — degrade to `--source-tickets`; a `--seed` run legitimately has none. |
|
|
340
|
-
| Explicit `--plan-context` missing | **Fatal** — the operator named a file and meant it. |
|
|
341
|
-
| Envelope present but unparseable | **Fatal** — a corrupt envelope is not "no source tickets"; treating it as such is how a `--tickets` run used to report success having superseded nothing. |
|
|
342
|
-
|
|
343
|
-
Whichever channel supplies them, the supersede-map partition above still
|
|
344
|
-
fail-closes: a `--tickets` run whose Stories forgot `supersedes[]` is now
|
|
345
|
-
**caught** (`source ticket #N is not claimed by any Story`) instead of
|
|
346
|
-
partitioning an empty set and passing vacuously.
|
|
347
|
-
|
|
348
|
-
### Closing superseded source tickets
|
|
349
|
-
|
|
350
|
-
**Default on.** After the Stories exist, persist comments on each source
|
|
351
|
-
issue naming the specific Story that claims it — plus that Story's optional
|
|
352
|
-
per-supersede `note` — and closes it with reason **`not_planned`**
|
|
353
|
-
(`state_reason`). Nothing has shipped at persist time and the issue will not
|
|
354
|
-
be actioned in its own right, so `not_planned` is the honest reason;
|
|
355
|
-
`completed` would be a lie. This is what keeps the tracker from asserting
|
|
356
|
-
that already-planned work is still unowned, and it writes down the supersede
|
|
357
|
-
link that makes the history readable.
|
|
358
|
-
|
|
359
|
-
| Behaviour | Contract |
|
|
360
|
-
| --- | --- |
|
|
361
|
-
| Default | Comment + close every source ticket as `not_planned`. |
|
|
362
|
-
| `--no-close-superseded` | Skips all commenting and closing. Story creation is unchanged. Use it for a genuinely partial supersede — when the plan folded in only *part* of an issue and the remainder must stay open. |
|
|
363
|
-
| `--dry-run` | Posts no comment and closes nothing; reports what it would have done. |
|
|
364
|
-
| Re-run | Idempotent — the comment is keyed off a `superseded-by` structured-comment marker, and an already-closed source is skipped. |
|
|
365
|
-
| Already closed / deleted / inaccessible | Skipped and reported. Never throws. |
|
|
366
|
-
| Close-phase failure | **Never fails the run.** Stories stay created; the result envelope's `supersede` report names which tickets were and were not closed so the operator can finish by hand. |
|
|
163
|
+
Persist creates Story issue(s) with `type::story` plus a `plan-run::<id>`
|
|
164
|
+
grouping label (**metadata only** — never a delivery-resolution input, Story #4692); N>1 `depends_on` edges become `blocked by #<id>` body footers.
|
|
165
|
+
`agent::ready` is the **terminal** flip, after all receipts are upserted — a
|
|
166
|
+
ready Story is always fully persisted (Story #4541). stdout is pure JSON
|
|
167
|
+
(logs on stderr).
|
|
367
168
|
|
|
368
|
-
|
|
369
|
-
|
|
169
|
+
In `--tickets` mode persist resolves source ids **envelope-first** and
|
|
170
|
+
closes each superseded source as `not_planned` with a comment (default on).
|
|
171
|
+
Detail (channels, close contract, resume, temp hygiene):
|
|
172
|
+
[`helpers/plan-reference.md`](helpers/plan-reference.md) — on a stranded
|
|
173
|
+
persist, re-run the same command; never hand-delete issues.
|
|
370
174
|
|
|
371
175
|
## Constraints
|
|
372
176
|
|
|
373
|
-
- `/plan` never starts delivery.
|
|
374
|
-
|
|
177
|
+
- `/plan` never starts delivery. No Epic ticket, no reconciler, no
|
|
178
|
+
`delivery::single` marker.
|
|
375
179
|
- Duplicate search targets open Stories (`type::story`), not Epics.
|
|
376
|
-
- Deterministic gates
|
|
377
|
-
still fail closed under `--yes`.
|
|
180
|
+
- Deterministic gates still fail closed under `--yes`.
|
|
378
181
|
|
|
379
182
|
## See also
|
|
380
183
|
|
|
381
|
-
- [`/deliver`](deliver.md) — delivery entry point
|
|
382
|
-
- [`/audit-to-stories`](audit-to-stories.md) — audit findings → plan seed
|
|
383
|
-
|
|
184
|
+
- [`/deliver`](deliver.md) — delivery entry point.
|
|
185
|
+
- [`/audit-to-stories`](audit-to-stories.md) — audit findings → plan seed.
|
|
186
|
+
- [`helpers/plan-reference.md`](helpers/plan-reference.md) — on-demand
|
|
187
|
+
detail.
|
|
384
188
|
- [`core/scope-triage`](../skills/core/scope-triage/SKILL.md) — optional
|
|
385
189
|
split-advisory notes only (no routing verdict).
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.11.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.10.0...mandrel-v2.11.0) (2026-07-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* **migration:** strip retired planning.complexityGate.maxSeedWords on consumer upgrade ([#4729](https://github.com/dsj1984/mandrel/issues/4729)) ([fd75b54](https://github.com/dsj1984/mandrel/commit/fd75b54b0f7390a3d562a25e90cbe6c5911b4f66))
|
|
11
|
+
* route on the work, not the words — shape-derived complexity routing honored end-to-end (refs [#4722](https://github.com/dsj1984/mandrel/issues/4722)) ([#4725](https://github.com/dsj1984/mandrel/issues/4725)) ([f166ca1](https://github.com/dsj1984/mandrel/commit/f166ca14785bad97657fdda0f41e282e47db2ea3))
|
|
12
|
+
* single-owner acceptance verification + plan-template hardening ([#4723](https://github.com/dsj1984/mandrel/issues/4723)) ([#4728](https://github.com/dsj1984/mandrel/issues/4728)) ([954f55a](https://github.com/dsj1984/mandrel/commit/954f55aedda3199d1fdd48b11884a9513012e2c4))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* **doctor:** include .agents/local/workflows/ in the commands-in-sync expected set (refs [#4724](https://github.com/dsj1984/mandrel/issues/4724)) ([#4726](https://github.com/dsj1984/mandrel/issues/4726)) ([b96dcd4](https://github.com/dsj1984/mandrel/commit/b96dcd446879a9da28e8d86c1a493c2bae62d2c8))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
* **orchestration:** extract spec-budget and story-body parse gate from ticket-validator ([#4730](https://github.com/dsj1984/mandrel/issues/4730)) ([fb0c3c3](https://github.com/dsj1984/mandrel/commit/fb0c3c31795f6178b2ba740b2fb00760921e1178))
|
|
23
|
+
|
|
24
|
+
## [2.10.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.9.0...mandrel-v2.10.0) (2026-07-23)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
* ceremony-proportional lite route: end-to-end lite delivery, one-shot authoring, contract-level Story prose ([#4707](https://github.com/dsj1984/mandrel/issues/4707)) ([#4709](https://github.com/dsj1984/mandrel/issues/4709)) ([d9d543a](https://github.com/dsj1984/mandrel/commit/d9d543a40ec55c063e8b3bff143782b053f8ef52))
|
|
30
|
+
* **ceremony:** findings-yield instrumentation + pre-eval full-suite discipline + lens diff-floor ([#4699](https://github.com/dsj1984/mandrel/issues/4699)) ([#4706](https://github.com/dsj1984/mandrel/issues/4706)) ([f7a6582](https://github.com/dsj1984/mandrel/commit/f7a658218f8a42997cb5bdbfe018c8b27e211b94))
|
|
31
|
+
* **close:** async merge-confirm mode — probe once, return pending, land via background confirm ([#4698](https://github.com/dsj1984/mandrel/issues/4698)) ([#4705](https://github.com/dsj1984/mandrel/issues/4705)) ([79360ce](https://github.com/dsj1984/mandrel/commit/79360ce224d48896050494e15b227c68d430e003))
|
|
32
|
+
* **plan:** external-dependency pre-mortem trigger so N=1 plans get a viability check ([#4700](https://github.com/dsj1984/mandrel/issues/4700)) ([#4702](https://github.com/dsj1984/mandrel/issues/4702)) ([4874890](https://github.com/dsj1984/mandrel/commit/4874890fab5ae346c2daf43b873ac374dc4a8df8))
|
|
33
|
+
* **plan:** reintroduce a per-plan-run grouping label on persisted Stories (metadata-only) ([#4692](https://github.com/dsj1984/mandrel/issues/4692)) ([#4693](https://github.com/dsj1984/mandrel/issues/4693)) ([1ca7d75](https://github.com/dsj1984/mandrel/commit/1ca7d7582972585a414268b242fb88cfcc94953e))
|
|
34
|
+
* residency & cache topology: always-on diet, shared-prefix subagent boots, lean-spine workflows ([#4708](https://github.com/dsj1984/mandrel/issues/4708)) ([#4718](https://github.com/dsj1984/mandrel/issues/4718)) ([8ba40d8](https://github.com/dsj1984/mandrel/commit/8ba40d84982a046a2f11e4be5e2a562b74c8f67e))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
* **close:** harden the merge wait — gh subprocess timeouts, extracted fail-fast tree, resume-CLI wait override, honest required-run evidence ([#4710](https://github.com/dsj1984/mandrel/issues/4710)) ([#4717](https://github.com/dsj1984/mandrel/issues/4717)) ([3edc9b5](https://github.com/dsj1984/mandrel/commit/3edc9b53b8176aa2386e0d12023bf69ab28bd87f))
|
|
40
|
+
* **close:** require head-anchored required-check evidence before the checks-failed fail-fast (refs [#4695](https://github.com/dsj1984/mandrel/issues/4695)) ([#4703](https://github.com/dsj1984/mandrel/issues/4703)) ([6b0b4db](https://github.com/dsj1984/mandrel/commit/6b0b4db021eb11ef46aa65fe6ed906f567ffef05))
|
|
41
|
+
* **evidence:** extend gateName enum to the real close-gate set (refs [#4697](https://github.com/dsj1984/mandrel/issues/4697)) ([#4701](https://github.com/dsj1984/mandrel/issues/4701)) ([49be21b](https://github.com/dsj1984/mandrel/commit/49be21bc9556de5db60871e5140e674e02bb9667))
|
|
42
|
+
* **observability:** isolate test-suite emissions from real telemetry streams and guard CI against fixture pollution ([#4696](https://github.com/dsj1984/mandrel/issues/4696)) ([#4704](https://github.com/dsj1984/mandrel/issues/4704)) ([992a5a2](https://github.com/dsj1984/mandrel/commit/992a5a2fa31091e9ae021c50c5ae8a7ec323ccd7))
|
|
43
|
+
* **observability:** make the test-temp hygiene guard airtight (refs [#4711](https://github.com/dsj1984/mandrel/issues/4711)) ([#4716](https://github.com/dsj1984/mandrel/issues/4716)) ([7520cfb](https://github.com/dsj1984/mandrel/commit/7520cfb66fae3aa5c6802af109becbc2b2795b56))
|
|
44
|
+
* **recover:** stability re-probe before trusting a mid-flight strand verdict ([#4719](https://github.com/dsj1984/mandrel/issues/4719)) ([a2edc9f](https://github.com/dsj1984/mandrel/commit/a2edc9f4465ca130b025deaaf12fb87174b242a4))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
* **metrics:** single ledger-append implementation and clean close boundary (refs [#4712](https://github.com/dsj1984/mandrel/issues/4712)) ([#4715](https://github.com/dsj1984/mandrel/issues/4715)) ([a9b019b](https://github.com/dsj1984/mandrel/commit/a9b019b6c68e3874950728578e535e4614751553))
|
|
50
|
+
|
|
5
51
|
## [2.9.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.8.0...mandrel-v2.9.0) (2026-07-21)
|
|
6
52
|
|
|
7
53
|
|
package/lib/cli/registry.js
CHANGED
|
@@ -225,10 +225,20 @@ function runGhAuth({ runner = spawn, env = process.env } = {}) {
|
|
|
225
225
|
// ---------------------------------------------------------------------------
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
|
-
* Dry-run the sync-claude-commands logic: compare
|
|
229
|
-
* sources
|
|
230
|
-
*
|
|
231
|
-
*
|
|
228
|
+
* Dry-run the sync-claude-commands logic: compare the union of the two
|
|
229
|
+
* projection sources — `.agents/workflows/*.md` (the installed payload) and
|
|
230
|
+
* `.agents/local/workflows/*.md` (consumer-authored, prune-exempt, projected
|
|
231
|
+
* since 1.75.0 / #4244) — to the generated flat command tree
|
|
232
|
+
* `.claude/commands/*.md` destinations and report parity (the projection is
|
|
233
|
+
* a flat `/<name>` command surface; the #3576 plugin projection was
|
|
234
|
+
* reverted). A dest command backed only by a local workflow is in sync, not
|
|
235
|
+
* stale (#4721).
|
|
236
|
+
*
|
|
237
|
+
* Union semantics mirror the sync script's payload-wins shadowing (`byRel`):
|
|
238
|
+
* a basename projects iff at least one source's copy is non-excluded — an
|
|
239
|
+
* excluded payload copy never enters `byRel`, so it does not shadow a
|
|
240
|
+
* projectable local copy. Filtering each source dir independently and
|
|
241
|
+
* unioning the surviving basenames reproduces that exactly.
|
|
232
242
|
*
|
|
233
243
|
* Resolution anchor (Story #3588): the root defaults to `process.cwd()` —
|
|
234
244
|
* the consumer project directory where `mandrel sync` materializes both
|
|
@@ -272,28 +282,35 @@ function runCommandsInSync({ projectRoot, cwd, readDir, readFile } = {}) {
|
|
|
272
282
|
}
|
|
273
283
|
});
|
|
274
284
|
|
|
275
|
-
const
|
|
285
|
+
const srcDirs = [
|
|
286
|
+
path.join(root, '.agents', 'workflows'),
|
|
287
|
+
path.join(root, '.agents', 'local', 'workflows'),
|
|
288
|
+
];
|
|
276
289
|
const destDir = path.join(root, '.claude', 'commands');
|
|
277
290
|
|
|
278
291
|
// Only top-level .md files are synced (helpers/ subdirectory excluded by
|
|
279
292
|
// the sync script — they are path-included modules, not slash commands).
|
|
280
293
|
// Workflows whose frontmatter carries `command: false` (#4482) opt out of
|
|
281
|
-
// projection and must not count toward the expected command set.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
294
|
+
// projection and must not count toward the expected command set. An absent
|
|
295
|
+
// local dir degrades to [] via the listDir catch, so payload-only
|
|
296
|
+
// consumers are unchanged.
|
|
297
|
+
const expected = new Set();
|
|
298
|
+
for (const srcDir of srcDirs) {
|
|
299
|
+
for (const f of listDir(srcDir)) {
|
|
300
|
+
if (f.startsWith('.')) continue;
|
|
285
301
|
const content = readSource(path.join(srcDir, f));
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
302
|
+
if (content != null && isCommandExcluded(content)) continue;
|
|
303
|
+
expected.add(f);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const sources = [...expected].sort();
|
|
289
307
|
const dests = listDir(destDir)
|
|
290
308
|
.filter((f) => !f.startsWith('.'))
|
|
291
309
|
.sort();
|
|
292
310
|
|
|
293
|
-
const srcSet = new Set(sources);
|
|
294
311
|
const dstSet = new Set(dests);
|
|
295
312
|
const missing = sources.filter((f) => !dstSet.has(f));
|
|
296
|
-
const extra = dests.filter((f) => !
|
|
313
|
+
const extra = dests.filter((f) => !expected.has(f));
|
|
297
314
|
|
|
298
315
|
if (missing.length === 0 && extra.length === 0) {
|
|
299
316
|
return { ok: true, detail: `${sources.length} commands up to date` };
|