mandrel 2.10.0 → 2.12.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/docs/configuration.md +35 -33
- package/.agents/rules/orchestration-error-handling.md +9 -1
- package/.agents/schemas/agentrc.schema.json +13 -8
- package/.agents/scripts/acceptance-eval.js +9 -5
- package/.agents/scripts/lib/audit-suite/audit-rules-reader.js +48 -0
- package/.agents/scripts/lib/audit-suite/selector.js +1 -26
- 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-settings-schema.js +12 -16
- 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 +561 -184
- package/.agents/scripts/lib/orchestration/plan-context.js +69 -10
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +117 -60
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +21 -15
- package/.agents/scripts/lib/orchestration/resolve-stories.js +28 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
- package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +186 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +21 -3
- 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/ticket-validator-conflicts.js +6 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
- package/.agents/scripts/plan-context.js +23 -5
- package/.agents/scripts/resolve-stories.js +2 -0
- package/.agents/workflows/deliver.md +28 -28
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
- package/.agents/workflows/helpers/deliver-digest.md +126 -0
- package/.agents/workflows/helpers/deliver-reference.md +30 -5
- package/.agents/workflows/helpers/deliver-story-reference.md +38 -12
- package/.agents/workflows/helpers/deliver-story.md +34 -37
- package/.agents/workflows/helpers/plan-reference.md +79 -44
- package/.agents/workflows/plan.md +11 -10
- package/docs/CHANGELOG.md +31 -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,52 +1,87 @@
|
|
|
1
1
|
# /plan — on-demand reference appendix
|
|
2
2
|
|
|
3
3
|
> **Applies when:** you are executing [`/plan`](../plan.md) and hit one of the
|
|
4
|
-
> situations below —
|
|
5
|
-
> critic dispatch detail, a failed persist, or source-id
|
|
6
|
-
> stays resident; this file is read on demand.
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
`
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
the
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Story
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
`
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
4
|
+
> situations below — shape-derived complexity routing, `--tickets` supersede
|
|
5
|
+
> authoring, critic dispatch detail, a failed persist, or source-id
|
|
6
|
+
> resolution. The spine stays resident; this file is read on demand.
|
|
7
|
+
|
|
8
|
+
## Shape-derived complexity routing (`complexitySignals`)
|
|
9
|
+
|
|
10
|
+
Complexity routes on the **objective shape of the authored work**, never on
|
|
11
|
+
seed word count (Story #4722 — a detailed prompt can describe trivial work, a
|
|
12
|
+
terse one complex work; `maxSeedWords` is removed). The pipeline stages the
|
|
13
|
+
decision:
|
|
14
|
+
|
|
15
|
+
- **Signals, not routing.** The envelope's `complexitySignals` field is
|
|
16
|
+
advisory only (`routingAuthority: false`): enumerated-artifact count (with
|
|
17
|
+
the configured `maxArtifacts` threshold beside it as one input),
|
|
18
|
+
`planning.riskHeuristics` phrases present in the seed, the repo state of
|
|
19
|
+
predicted paths (existing paths predict refactors; missing predict
|
|
20
|
+
creates), and the `audit-rules.json` sensitive-path classes the predicted
|
|
21
|
+
footprint intersects.
|
|
22
|
+
- **You author the verdict.** Judge the signals: a genuinely trivial scope
|
|
23
|
+
(small additive footprint, no risk hits, no sensitive class) earns a `lite`
|
|
24
|
+
claim via `plan-persist.js --route-downgrade-reason "<why>"`. The reason is
|
|
25
|
+
recorded on every created Story's `story-plan-state` checkpoint, making the
|
|
26
|
+
judgment auditable; without a recorded reason the conservative default
|
|
27
|
+
(`full`) stands.
|
|
28
|
+
- **Persist backstops the claim deterministically.** After authoring, the
|
|
29
|
+
work has measurable shape, so persist validates the `lite` claim against
|
|
30
|
+
each Story's own shape — `changes[]` count, acceptance-criteria count,
|
|
31
|
+
creates-vs-refactors mix, glob-free footprint, and sensitive-path classes,
|
|
32
|
+
against the framework `STORY_SHAPE_CEILINGS` — and **fails closed to
|
|
33
|
+
`full`** when any Story exceeds them (the refusal is ledgered on the
|
|
34
|
+
checkpoint too). The lite route is **not** licence to drop a
|
|
35
|
+
non-negotiable — every decision's `preserves` field enumerates what still
|
|
36
|
+
holds: the Story ticket, the PR-to-`main` landing, every repo quality gate,
|
|
37
|
+
and the security baseline. Those gates run in `single-story-close.js`
|
|
38
|
+
regardless of route.
|
|
39
|
+
|
|
40
|
+
**The label is a hint; deliver re-derives (Story #4722).** Persist labels a
|
|
41
|
+
lite cohort's Stories with **`route::lite`** as a *human-visible hint only* —
|
|
42
|
+
`/deliver` computes the route from each fetched Story body via the same shape
|
|
43
|
+
function at dispatch, so neither a lost label nor an unread marker can
|
|
44
|
+
misroute delivery: a lite-shaped Story executes **inline** (no story-worker
|
|
45
|
+
or acceptance-critic sub-agent boots) even with the label absent, and a
|
|
46
|
+
sensitive-footprint Story routes `full` and keeps its fresh critic even with
|
|
47
|
+
the label present. The `route::*` axis stays runtime-derived: hand-authored
|
|
48
|
+
`route::*` entries in `labels[]` are dropped by persist.
|
|
49
|
+
|
|
50
|
+
The knobs (`planning.complexityGate.{enabled, maxArtifacts}`) are documented
|
|
51
|
+
in [`.agents/docs/configuration.md`](../../docs/configuration.md) under
|
|
52
|
+
`### planning`; the defaults live on `DEFAULT_COMPLEXITY_GATE` and the shape
|
|
53
|
+
ceilings on `STORY_SHAPE_CEILINGS` in
|
|
48
54
|
[`lib/orchestration/complexity-gate.js`](../../scripts/lib/orchestration/complexity-gate.js).
|
|
49
55
|
|
|
56
|
+
## Correct-by-construction authoring template (Story #4723)
|
|
57
|
+
|
|
58
|
+
`plan-context.js --out` writes `stories.template.json` as a
|
|
59
|
+
**correct-by-construction** skeleton, built from the same repo snapshot the
|
|
60
|
+
`complexitySignals` probed:
|
|
61
|
+
|
|
62
|
+
- **`verify[]` placeholders already end with a valid `(tier)` tag.** Keep
|
|
63
|
+
every filled entry's trailing tag one of `(unit)` / `(contract)` /
|
|
64
|
+
`(e2e)` / `(validate)` (or use the `manual:<reason>` escape) — a tierless
|
|
65
|
+
entry is exactly the mechanical persist round-trip the template exists to
|
|
66
|
+
prevent.
|
|
67
|
+
- **`changes[]` arrive pre-resolved to creates-vs-refactors.** Every path
|
|
68
|
+
the seed predicted is probed against the repo: an existing path is
|
|
69
|
+
emitted with `assumption: "refactors-existing"`, a missing one with
|
|
70
|
+
`assumption: "creates"`. Trust the pre-resolved assumption — verify
|
|
71
|
+
against the repo before overriding one (authoring `creates` for a file
|
|
72
|
+
that exists at base is a validator rejection). The persist gates stay
|
|
73
|
+
authoritative: they probe the base branch ref, not the working tree.
|
|
74
|
+
- **Keep `## Spec` near contract-level prose.** Persist emits an
|
|
75
|
+
**advisory** warning past ~250 words (`SPEC_SOFT_WORD_BUDGET`) — it never
|
|
76
|
+
fails the persist, but it is the nudge toward the #4707 contract-level
|
|
77
|
+
Spec (interfaces, invariants, load-bearing constraints; no per-file
|
|
78
|
+
behavior narration). The hard fail-closed ceiling (~1500 tokens,
|
|
79
|
+
`spec-spill.js`) is unchanged.
|
|
80
|
+
|
|
81
|
+
A faithfully-filled skeleton — placeholders replaced, pre-resolved entries
|
|
82
|
+
kept, tags valid — passes the persist ticket validators with no
|
|
83
|
+
round-trip.
|
|
84
|
+
|
|
50
85
|
## Tickets mode — authoring `supersedes[]`
|
|
51
86
|
|
|
52
87
|
In `--tickets` mode each Story carries a top-level `supersedes` array claiming
|
|
@@ -32,7 +32,7 @@ Epic/Story router, no scope-triage `epic|story` verdict:
|
|
|
32
32
|
| `--tickets <ids>` | Issue ids to analyze; closed as superseded at persist. |
|
|
33
33
|
| `--no-close-superseded` | Keep the source issues open — no supersede comment, no close. |
|
|
34
34
|
| `--force-review` | STOP at gate #2 for operator review — the only review gate (Story #4542). |
|
|
35
|
-
| `--route-downgrade-reason "<text>"` |
|
|
35
|
+
| `--route-downgrade-reason "<text>"` | Authored `lite` verdict + reason (Story #4722), ledgered per Story; shape-validated, fails closed to `full`. |
|
|
36
36
|
| `--allow-over-budget` | Permit a plan exceeding `maxTickets`. |
|
|
37
37
|
| `--yes` | Non-interactive: auto-proceed gate #1 and gate #2 HITL waits. |
|
|
38
38
|
| `--dry-run` | Author + validate without GitHub writes; run as a pre-pass. |
|
|
@@ -66,11 +66,11 @@ authoring skeleton step 2 starts from.
|
|
|
66
66
|
|
|
67
67
|
The envelope carries docs context, codebase snapshot, the story-author
|
|
68
68
|
prompt, `sourceTickets[]`, `duplicates[]` (open **Stories** overlapping the
|
|
69
|
-
seed — never Epics), and
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[`helpers/plan-reference.md` §
|
|
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).
|
|
74
74
|
Under `--yes`, do not ask free-form operator questions — unresolved
|
|
75
75
|
unknowns land in Key Assumptions.
|
|
76
76
|
|
|
@@ -79,8 +79,9 @@ duplicate-candidate review. Under `--yes`, auto-proceed.
|
|
|
79
79
|
|
|
80
80
|
### 2. Author
|
|
81
81
|
|
|
82
|
-
**One-shot authoring (Story #4707).** Start from `stories.template.json
|
|
83
|
-
|
|
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
|
|
84
85
|
markdown string **or** a structured object; persist parses either,
|
|
85
86
|
serializes the canonical markdown, and syncs the top-level `acceptance[]` /
|
|
86
87
|
`verify[]` into the body — never dual-author those lists.
|
|
@@ -182,7 +183,7 @@ persist, re-run the same command; never hand-delete issues.
|
|
|
182
183
|
|
|
183
184
|
- [`/deliver`](deliver.md) — delivery entry point.
|
|
184
185
|
- [`/audit-to-stories`](audit-to-stories.md) — audit findings → plan seed.
|
|
185
|
-
- [`helpers/plan-reference.md`](helpers/plan-reference.md) —
|
|
186
|
-
|
|
186
|
+
- [`helpers/plan-reference.md`](helpers/plan-reference.md) — on-demand
|
|
187
|
+
detail.
|
|
187
188
|
- [`core/scope-triage`](../skills/core/scope-triage/SKILL.md) — optional
|
|
188
189
|
split-advisory notes only (no routing verdict).
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.12.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.11.0...mandrel-v2.12.0) (2026-07-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Performance
|
|
9
|
+
|
|
10
|
+
* **orchestration:** cut deliver-path cost — inline single-Story dispatch, bounded close output, bundled reads (refs [#4736](https://github.com/dsj1984/mandrel/issues/4736)) ([#4737](https://github.com/dsj1984/mandrel/issues/4737)) ([81e62a8](https://github.com/dsj1984/mandrel/commit/81e62a87ca49add2aeb5b5a6b9ac11014edc3374))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* **orchestration:** thread injectedRules seam through route composition layers ([#4734](https://github.com/dsj1984/mandrel/issues/4734)) ([6e1daad](https://github.com/dsj1984/mandrel/commit/6e1daadbc24210fdb34e4892c0077256f2ccd051))
|
|
16
|
+
|
|
17
|
+
## [2.11.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.10.0...mandrel-v2.11.0) (2026-07-23)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
* **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))
|
|
23
|
+
* 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))
|
|
24
|
+
* 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))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
* **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))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
* **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))
|
|
35
|
+
|
|
5
36
|
## [2.10.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.9.0...mandrel-v2.10.0) (2026-07-23)
|
|
6
37
|
|
|
7
38
|
|
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` };
|
package/lib/migrations/index.js
CHANGED
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
import { retireMiDropKnobs } from './steps/2.1.0-retire-mi-drop-knobs.js';
|
|
56
56
|
import { retireVerifyConcurrencyCap } from './steps/2.1.0-retire-verify-concurrency-cap.js';
|
|
57
57
|
import { retireEpicAcTags } from './steps/2.2.0-retire-epic-ac-tags.js';
|
|
58
|
+
import { retireMaxSeedWords } from './steps/2.11.0-retire-max-seed-words.js';
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Ordered registry of migration steps. MUST stay sorted ascending by
|
|
@@ -71,6 +72,7 @@ export const migrations = [
|
|
|
71
72
|
retireMiDropKnobs,
|
|
72
73
|
retireVerifyConcurrencyCap,
|
|
73
74
|
retireEpicAcTags,
|
|
75
|
+
retireMaxSeedWords,
|
|
74
76
|
];
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// lib/migrations/steps/2.11.0-retire-max-seed-words.js
|
|
2
|
+
/**
|
|
3
|
+
* Story #4722 follow-up — strip the retired
|
|
4
|
+
* `planning.complexityGate.maxSeedWords` knob from a consumer's
|
|
5
|
+
* `.agentrc.json`.
|
|
6
|
+
*
|
|
7
|
+
* #4722 (PR #4725) hard-cutover-removed word-count complexity routing:
|
|
8
|
+
* the route derives from the authored Story's shape, never from seed word
|
|
9
|
+
* count, and `maxSeedWords` was dropped from the runtime AJV schema and
|
|
10
|
+
* the published mirror. The `complexityGate` block carries
|
|
11
|
+
* `additionalProperties: false`, so a consumer whose config still sets
|
|
12
|
+
* `maxSeedWords` hits a hard validation failure on upgrade, not a
|
|
13
|
+
* warning. This step strips the key before that check runs — the same
|
|
14
|
+
* contract-cutover pattern as `2.1.0-retire-mi-drop-knobs.js`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import nodeFs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
const AGENTRC_FILENAME = '.agentrc.json';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {unknown} ctx
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function resolveAgentrcPath(ctx) {
|
|
27
|
+
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
28
|
+
return path.join(projectRoot, AGENTRC_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {unknown} ctx
|
|
33
|
+
* @param {typeof nodeFs} fsImpl
|
|
34
|
+
* @returns {object | null}
|
|
35
|
+
*/
|
|
36
|
+
function readAgentrcConfig(ctx, fsImpl) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx), 'utf8');
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {object | null} config
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
function hasRetiredKey(config) {
|
|
50
|
+
const gate = config?.planning?.complexityGate;
|
|
51
|
+
return Boolean(gate) && Object.hasOwn(gate, 'maxSeedWords');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const retireMaxSeedWords = {
|
|
55
|
+
version: '2.11.0',
|
|
56
|
+
description:
|
|
57
|
+
'strip retired planning.complexityGate.maxSeedWords from .agentrc.json ' +
|
|
58
|
+
'(complexity routes on Story shape, never seed word count — Story #4722)',
|
|
59
|
+
/**
|
|
60
|
+
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
detect(ctx) {
|
|
64
|
+
const fsImpl = ctx?.fs ?? nodeFs;
|
|
65
|
+
return hasRetiredKey(readAgentrcConfig(ctx, fsImpl));
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
apply(ctx) {
|
|
72
|
+
const fsImpl = ctx?.fs ?? nodeFs;
|
|
73
|
+
const config = readAgentrcConfig(ctx, fsImpl);
|
|
74
|
+
if (!config) return;
|
|
75
|
+
|
|
76
|
+
const gate = config.planning?.complexityGate;
|
|
77
|
+
if (gate && Object.hasOwn(gate, 'maxSeedWords')) {
|
|
78
|
+
delete gate.maxSeedWords;
|
|
79
|
+
if (Object.keys(gate).length === 0) {
|
|
80
|
+
delete config.planning.complexityGate;
|
|
81
|
+
if (Object.keys(config.planning).length === 0) {
|
|
82
|
+
delete config.planning;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fsImpl.writeFileSync(
|
|
88
|
+
resolveAgentrcPath(ctx),
|
|
89
|
+
`${JSON.stringify(config, null, 2)}\n`,
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
package/package.json
CHANGED