mandrel 2.56.0 → 2.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -34
  3. package/.agents/docs/agentrc-reference.json +0 -30
  4. package/.agents/docs/configuration.md +8 -28
  5. package/.agents/docs/execution-reference.md +5 -5
  6. package/.agents/docs/quality-gates.md +8 -7
  7. package/.agents/instructions.md +9 -10
  8. package/.agents/schemas/agentrc.schema.json +9 -185
  9. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  10. package/.agents/scripts/acceptance-eval.js +107 -17
  11. package/.agents/scripts/ceremony-derive.js +191 -0
  12. package/.agents/scripts/check-context-budget.js +28 -33
  13. package/.agents/scripts/check-cyclomatic.js +4 -3
  14. package/.agents/scripts/deliver-light.js +31 -94
  15. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  16. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  17. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  18. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  19. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  20. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  21. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  22. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  23. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  24. package/.agents/scripts/lib/config/explain.js +0 -19
  25. package/.agents/scripts/lib/config/limits.js +18 -78
  26. package/.agents/scripts/lib/config/quality.js +6 -3
  27. package/.agents/scripts/lib/config/runners.js +3 -2
  28. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  29. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  30. package/.agents/scripts/lib/config-settings-schema.js +16 -143
  31. package/.agents/scripts/lib/crap-engine.js +35 -4
  32. package/.agents/scripts/lib/crap-utils.js +17 -1
  33. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  34. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  35. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  36. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  37. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  38. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  39. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  40. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  41. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  42. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  43. package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
  44. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  45. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  46. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
  48. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +118 -297
  49. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  51. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  52. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
  53. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  54. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  55. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  56. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  57. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  58. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  59. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  60. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  61. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  62. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  63. package/.agents/scripts/lib/story-body/story-body.js +17 -237
  64. package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
  65. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  66. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  67. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  68. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  69. package/.agents/scripts/lib/test-run-credit.js +266 -0
  70. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  71. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  72. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  73. package/.agents/scripts/plan-context.js +7 -9
  74. package/.agents/scripts/plan-critics.js +28 -54
  75. package/.agents/scripts/plan-persist.js +25 -68
  76. package/.agents/scripts/quality-preview.js +51 -0
  77. package/.agents/scripts/run-tests.js +12 -0
  78. package/.agents/scripts/stories-wave-tick.js +23 -45
  79. package/.agents/scripts/test-isolate.js +13 -180
  80. package/.agents/scripts/update-coverage-baseline.js +25 -70
  81. package/.agents/scripts/update-crap-baseline.js +19 -123
  82. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  83. package/.agents/workflows/audit-clean-code.md +4 -3
  84. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  85. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  86. package/.agents/workflows/helpers/code-review.md +2 -3
  87. package/.agents/workflows/helpers/deliver-digest.md +41 -57
  88. package/.agents/workflows/helpers/deliver-light.md +40 -105
  89. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  90. package/.agents/workflows/helpers/deliver-story-reference.md +37 -58
  91. package/.agents/workflows/helpers/deliver-story.md +9 -13
  92. package/.agents/workflows/helpers/plan-reference.md +132 -219
  93. package/.agents/workflows/mandrel-plan.md +27 -40
  94. package/.agents/workflows/memory-consolidate.md +9 -13
  95. package/docs/CHANGELOG.md +23 -0
  96. package/lib/migrations/index.js +4 -0
  97. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  98. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  99. package/package.json +1 -1
  100. package/.agents/scripts/lib/framework-version.js +0 -39
  101. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  102. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  103. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  104. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  105. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  106. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -56,11 +56,13 @@ node .agents/scripts/plan-context.js --seed "<seed>" \
56
56
  and derives source ids from its `sourceTickets[]`; it also writes
57
57
  **`stories.template.json`**, step 2's skeleton.
58
58
 
59
- The envelope carries docs context, the story-author prompt, `sourceTickets[]`,
59
+ The envelope carries docs context, the story-author prompt (`systemPrompts.story`,
60
+ plus `systemPrompts.storySplitRules` for an N>1 draft), `sourceTickets[]`,
60
61
  `duplicates[]` (open **Stories**, never Epics), `epicCandidates[]` +
61
62
  `dependencyCandidates[]` (Gate #3; path collisions), `priorFeedback` and
62
- advisory `complexitySignals` (**no routing authority**). A trivial scope claims
63
- the lite route at persist, failing closed to `full`.
63
+ advisory `complexitySignals` (**no routing authority**). An envelope over the
64
+ planner-context ceiling is written truncated with a `truncated` note, never
65
+ refused.
64
66
 
65
67
  **Triage each unknown by resolver** ([ref](helpers/plan-reference.md)): an
66
68
  **AFK** unknown (research settles it) is resolved before authoring, never
@@ -68,16 +70,13 @@ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
68
70
  operator questions — AFK unknowns are still researched; only HITL unknowns land
69
71
  in Key Assumptions, each a decision-made-by-default.
70
72
 
71
- **Gate #1** — STOP to confirm the sharpened plan intent, any
72
- duplicate-candidate review, and any `intake` row worth graduating
73
- ([ref](helpers/plan-reference.md)). Under `--yes`, auto-proceed.
74
-
75
- On a truthy `deliverLightSuggestion.suggested`, offer — advisory, never an
76
- automatic reroute — to deliver the seed instead; on confirm route **in this
77
- session** into [`helpers/deliver-light.md`](helpers/deliver-light.md), its gate
78
- filled from this envelope. A truthy `complexitySignals.uiSurface` names
79
- [`/prototype`](prototype.md); never invoke it here.
80
- [Both](helpers/plan-reference.md).
73
+ **Gate #1** — STOP for exactly two things: confirm the sharpened plan intent,
74
+ and settle any HITL unknown the operator owns. Everything else the envelope
75
+ surfaced — `duplicates[]`, open `intake` rows, a truthy
76
+ `memoryPoolAdvisory.recommend`, a truthy `complexitySignals.uiSurface` naming
77
+ [`/prototype`](prototype.md) (never invoke it here) — collapses to
78
+ **one advisory line** under the gate; none of it stops the run or reroutes
79
+ it ([ref](helpers/plan-reference.md)). Under `--yes`, auto-proceed.
81
80
 
82
81
  ### 2. Author
83
82
 
@@ -93,34 +92,19 @@ included. One rescue: a **never-tracked** one normalises to `creates`. Fields:
93
92
  [ref](helpers/plan-reference.md).
94
93
 
95
94
  Artifacts under `temp/plan-<slug>/`: `stories.json` (**length 1 by default**;
96
- over-budget Specs fail closed — split or tighten, never under `docs/`); optional
95
+ a Spec is as long as the work needs, inline, never under `docs/`); optional
97
96
  `techspec.md` (**N===1 only**, folded into `## Spec`) and
98
97
  `acceptance-manifest.json` (N>1 — `--plan-acceptance`). Use the envelope
99
- `systemPrompts.story`; split only under the policy above.
98
+ `systemPrompts.story`; split only under the policy above, and when you do,
99
+ read `systemPrompts.storySplitRules` too — it carries the schedule and
100
+ partition rules the core omits.
100
101
 
101
102
  **Tickets mode:** every Story authors a top-level `supersedes[]`; persist
102
103
  refuses a partial map ([shape](helpers/plan-reference.md)).
103
104
 
104
- ### 2.5 Critics
105
-
106
- ```bash
107
- node .agents/scripts/plan-critics.js \
108
- --stories temp/plan-<slug>/stories.json \
109
- [--tech-spec temp/plan-<slug>/techspec.md]
110
- ```
111
-
112
- Run **before** persist — the last point a finding folds into a re-author. It
113
- exits 0 on **any** verdict (verdicts route work, they do not gate) and exits
114
- **1** only on a usage/IO error — no critic ran: **do not proceed to Persist**,
115
- fix and re-run.
116
-
117
- - **Both `dispatch: false`** — proceed to Persist (each skip is ledgered).
118
- - **Either `dispatch: true`** — dispatch **one fresh-context, maker-blind
119
- sub-agent per firing critic** (hand it only the draft artifacts, never the
120
- authoring transcript), fold findings into Gate #2 or a re-author round, re-run
121
- this step. Pre-mortem triggers (incl. the external-dependency probe), the
122
- advisory-only `textHygiene.findings[]` lints and dispatch shape:
123
- [reference](helpers/plan-reference.md).
105
+ The maker-blind **pre-mortem** critic is not a step of this spine: run
106
+ `plan-critics.js` only when the operator asks for it
107
+ ([how](helpers/plan-reference.md)).
124
108
 
125
109
  ### 3. Persist
126
110
 
@@ -132,7 +116,11 @@ to review (`--force-review`). Under `--yes`, auto-proceed.
132
116
  `--epic-goal`). Never unasked ([ref](helpers/plan-reference.md)).
133
117
 
134
118
  Run persist `--dry-run` **first** — same command, writes suppressed; every gate
135
- runs before the first `createIssue` ([list](helpers/plan-reference.md)):
119
+ runs before the first `createIssue`, and the run **lists its warnings**
120
+ (a `creates` / `refactors-existing` the base branch disagrees with, a goal or
121
+ acceptance path absent at base, an open question in a body) and the
122
+ `changes[]` repairs it applied ([list](helpers/plan-reference.md)). Read
123
+ them; they never stop the persist:
136
124
 
137
125
  ```bash
138
126
  node .agents/scripts/plan-persist.js \
@@ -144,8 +132,8 @@ node .agents/scripts/plan-persist.js \
144
132
  [--epic <id> | --epic-title "<name>" --epic-goal "<one paragraph>"]
145
133
  ```
146
134
 
147
- At lite shape `--chain-on-clean` folds a clean dry-run into the persist; a full
148
- plan keeps its review trip.
135
+ `--chain-on-clean` folds a clean dry-run into the persist for **any** plan —
136
+ the dry-run's warning list is the review.
149
137
 
150
138
  Persist creates `type::story` issue(s), a **metadata-only** `plan-run::<id>`
151
139
  label, `blocked by #<id>` footers for every `depends_on` edge, and on a Gate #3
@@ -155,8 +143,7 @@ also comments on and closes each source id ([ref](helpers/plan-reference.md)).
155
143
 
156
144
  ## Constraints
157
145
 
158
- - `/mandrel-plan` starts delivery **only** through a confirmed Gate #1 light
159
- route — never off its Stories, which land via
146
+ - `/mandrel-plan` never starts delivery — its Stories land via
160
147
  [`/mandrel-deliver`](mandrel-deliver.md).
161
148
  - Duplicate search targets open Stories (`type::story`), not Epics; and
162
149
  deterministic gates still fail closed under `--yes`.
@@ -97,17 +97,14 @@ Then write the receipt to `.consolidation-stamp.json` in the pool root:
97
97
  ```
98
98
 
99
99
  `entryCount` is the surviving non-index `*.md` count **after** the rewrite —
100
- count the directory, never the plan. It is the baseline the next run measures
101
- growth against, so a wrong number silently mis-arms the nudge.
102
-
103
- The `/mandrel-plan` Phase 0 advisory re-arms on exactly three conditions: the
104
- stamp aging past `planning.memoryPool.staleAfterDays` (30),
105
- `planning.memoryPool.growthDelta` (25) entries written since that count, or
106
- `MEMORY.md` exceeding `planning.memoryPool.indexByteCeiling` (24576) bytes.
107
- Pool size alone never triggers it — a pass that keeps every entry still quiets
108
- the first two arms. A stamp with no `entryCount` leaves growth unmeasured, and
109
- only the age and index arms can speak until the next pass writes one; a stamp
110
- dated in the future reads as no stamp at all.
100
+ count the directory, never the plan. The stamp is the operator's record of
101
+ the pass; nothing in the framework reads it back.
102
+
103
+ The `/mandrel-plan` Phase 0 advisory re-arms on exactly one condition:
104
+ `MEMORY.md` exceeding `planning.memoryPool.indexByteCeiling` (24576) bytes —
105
+ the harness cap past which the index it loads is truncated. Pool size and
106
+ stamp age never trigger it (Story #5312 retired those arms): a pass that
107
+ rewrites long index lines short quiets it without pruning an entry.
111
108
 
112
109
  Write it **only** after Gate #2 — the stamp asserts an operator reviewed the
113
110
  pass, so writing it early makes it a lie.
@@ -116,8 +113,7 @@ Close with counts: entries read, corrected, merged, pruned, the new total, and
116
113
  **the rewritten `MEMORY.md`'s size in bytes beside that count** — the index is
117
114
  truncated at the byte ceiling, so a pass that pruned entries but left the
118
115
  index over the cap has not fixed the loss, and the number is the only way the
119
- operator can see that. Then the forecast the operator would otherwise derive
120
- by hand: when the advisory next fires, and which arm reaches it first.
116
+ operator can see that.
121
117
 
122
118
  ## Constraints
123
119
 
package/docs/CHANGELOG.md CHANGED
@@ -15,6 +15,29 @@ All notable changes to this project will be documented in this file.
15
15
  -->
16
16
  <!-- markdownlint-disable-file MD004 MD012 MD037 -->
17
17
 
18
+ ## [2.57.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.56.0...mandrel-v2.57.0) (2026-09-12)
19
+
20
+
21
+ ### ⚠ BREAKING CHANGES
22
+
23
+ * the .agentrc keys delivery.routing.freshCriticSampleRate, delivery.codeReview.maxFixScopeFiles, delivery.signals and delivery.quality.codingGuardrails.cyclomaticMustFix are removed; a 2.57.0 migration step strips them on upgrade.
24
+ * the .agentrc.json keys planning.complexityGate, planning.riskHeuristics, planning.failOnSharedEditors, planning.requireExplicitCrossStoryDeps, planning.failOnRegistryConflicts, planning.failOnLargeFanOut, planning.largeFanOutThreshold, planning.crossCuttingRegistries, planning.memoryPool.staleAfterDays and planning.memoryPool.growthDelta are removed; the planning block is strict, so a config still carrying one fails validation until the 2.57.0 migration step strips it on `mandrel update`. plan-persist.js no longer accepts --route-downgrade-reason, --allow-over-budget or --allow-large-fan-out, and a verify[] entry is a bare command with no tier suffix.
25
+
26
+ ### Added
27
+
28
+ * delivery diet: drop the sampling, round-ceiling, fix-scope, signal and cyclomatic knobs, script the ceremony derivation, let a bare test run earn credit, and make the light gate and footprint guard read the diff ([#5313](https://github.com/dsj1984/mandrel/issues/5313)) ([#5319](https://github.com/dsj1984/mandrel/issues/5319)) ([33fa9db](https://github.com/dsj1984/mandrel/commit/33fa9db4d01d8932bd886d380fe32b29db9a7bce))
29
+ * planning diet: delete the sizing, spec, lite-route and verify-tier limits, demote the footprint probes to warnings, and render the author prompt for the one-Story default ([#5312](https://github.com/dsj1984/mandrel/issues/5312)) ([#5318](https://github.com/dsj1984/mandrel/issues/5318)) ([cbcfdac](https://github.com/dsj1984/mandrel/commit/cbcfdacbca2cce9731e12c6cdfdab095d23b3e12))
30
+
31
+
32
+ ### Fixed
33
+
34
+ * score the CRAP worker path through the escomplex AST shim, and report an unscorable file as unscorable ([#5311](https://github.com/dsj1984/mandrel/issues/5311)) ([#5314](https://github.com/dsj1984/mandrel/issues/5314)) ([0a8e8c8](https://github.com/dsj1984/mandrel/commit/0a8e8c8cd8afb7af88b3d0d5a49e6359a91f3524))
35
+
36
+
37
+ ### Changed
38
+
39
+ * free the CLI entrypoints' logic from `main` so it can be tested, and tighten the CRAP floor back to 2 ([#5316](https://github.com/dsj1984/mandrel/issues/5316)) ([#5317](https://github.com/dsj1984/mandrel/issues/5317)) ([f67cfb3](https://github.com/dsj1984/mandrel/commit/f67cfb3b6e531c38300690e4dcd16a4de7dd18f8))
40
+
18
41
  ## [2.56.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.55.0...mandrel-v2.56.0) (2026-09-11)
19
42
 
20
43
 
@@ -59,6 +59,8 @@ import { retireEpicAcTags } from './steps/2.2.0-retire-epic-ac-tags.js';
59
59
  import { retireMaxSeedWords } from './steps/2.11.0-retire-max-seed-words.js';
60
60
  import { retireCodebaseSnapshot } from './steps/2.20.0-retire-codebase-snapshot.js';
61
61
  import { retireLintBaselineCommand } from './steps/2.32.0-retire-lint-baseline-command.js';
62
+ import { retireDeliveryLimitKnobs } from './steps/2.57.0-retire-delivery-limit-knobs.js';
63
+ import { retirePlanningLimitKnobs } from './steps/2.57.0-retire-planning-limit-knobs.js';
62
64
 
63
65
  /**
64
66
  * Ordered registry of migration steps. MUST stay sorted ascending by
@@ -78,6 +80,8 @@ export const migrations = [
78
80
  retireMaxSeedWords,
79
81
  retireCodebaseSnapshot,
80
82
  retireLintBaselineCommand,
83
+ retirePlanningLimitKnobs,
84
+ retireDeliveryLimitKnobs,
81
85
  ];
82
86
 
83
87
  /**
@@ -0,0 +1,45 @@
1
+ // lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js
2
+ /**
3
+ * Story #5313 — the delivery diet. Strip the retired `delivery.*` keys from a
4
+ * consumer's config:
5
+ *
6
+ * - `delivery.routing.freshCriticSampleRate` — the maker-checker sampling
7
+ * floor is gone; the standard profile routes purely off the derived
8
+ * change level (high or underivable → fresh critic, low → inline).
9
+ * - `delivery.codeReview.maxFixScopeFiles` — the auto-fix file-count
10
+ * ceiling bounded remediation by count rather than by risk.
11
+ * - `delivery.signals` (`rework.editsPerFile`, `retry.repeatCount`) — the
12
+ * detector thresholds and `SIGNALS_DEFAULTS` are retired wholesale.
13
+ * - `delivery.quality.codingGuardrails.cyclomaticMustFix` — the cyclomatic
14
+ * ratchet keeps its fixed ceiling of 12; `cyclomaticFlag` stays advisory.
15
+ *
16
+ * Every affected block carries `additionalProperties: false`, so a config
17
+ * still setting any of them fails validation on upgrade rather than warning.
18
+ * It sweeps **both** config surfaces (`.agentrc.json` and the gitignored
19
+ * `.agentrc.local.json`), because `config-resolver.js` deep-merges the
20
+ * overlay before the AJV gate runs.
21
+ *
22
+ * Pruning: `delivery.signals` is removed whole (`pruneDepth: 1` prunes an
23
+ * emptied `delivery`); the nested keys prune their emptied ancestors up to
24
+ * `delivery` itself. `delivery` is optional, so an emptied block is removed
25
+ * rather than left as `{}`. A sibling key that survives keeps its block.
26
+ */
27
+
28
+ import { createRetireAgentrcKeyStep } from '../helpers/retire-agentrc-key.js';
29
+
30
+ export const retireDeliveryLimitKnobs = createRetireAgentrcKeyStep({
31
+ version: '2.57.0',
32
+ description:
33
+ 'strip the retired delivery.* limit knobs from .agentrc.json — ' +
34
+ 'routing.freshCriticSampleRate, codeReview.maxFixScopeFiles, signals, ' +
35
+ 'and quality.codingGuardrails.cyclomaticMustFix (Story #5313)',
36
+ keys: [
37
+ { path: ['delivery', 'routing', 'freshCriticSampleRate'], pruneDepth: 2 },
38
+ { path: ['delivery', 'codeReview', 'maxFixScopeFiles'], pruneDepth: 2 },
39
+ { path: ['delivery', 'signals'], pruneDepth: 1 },
40
+ {
41
+ path: ['delivery', 'quality', 'codingGuardrails', 'cyclomaticMustFix'],
42
+ pruneDepth: 3,
43
+ },
44
+ ],
45
+ });
@@ -0,0 +1,59 @@
1
+ // lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js
2
+ /**
3
+ * Story #5312 — the planning diet. Strip the ten retired `planning.*` keys
4
+ * from a consumer's config:
5
+ *
6
+ * - `planning.complexityGate` — the plan-side lite claim and its persist
7
+ * backstop are gone; nothing reads the switch.
8
+ * - `planning.riskHeuristics` — the phrase list was empty in every consumer
9
+ * that resolved it, and the critic trigger that matched it went with the
10
+ * consolidation critic.
11
+ * - `planning.failOnSharedEditors`, `planning.requireExplicitCrossStoryDeps`,
12
+ * `planning.failOnRegistryConflicts`, `planning.failOnLargeFanOut`,
13
+ * `planning.largeFanOutThreshold`, `planning.crossCuttingRegistries` —
14
+ * every conflict finding is advisory now, and the registry and fan-out
15
+ * findings no longer exist.
16
+ * - `planning.memoryPool.staleAfterDays`, `planning.memoryPool.growthDelta`
17
+ * — the memory-hygiene advisory keeps only its index-byte arm.
18
+ *
19
+ * The `planning` block carries `additionalProperties: false`, so a config
20
+ * still setting any of them fails validation on upgrade rather than warning.
21
+ * It sweeps **both** config surfaces (`.agentrc.json` and the gitignored
22
+ * `.agentrc.local.json`), because `config-resolver.js` deep-merges the
23
+ * overlay before the AJV gate runs.
24
+ *
25
+ * Pruning: the two `memoryPool` keys prune the `memoryPool` object when it
26
+ * empties and then `planning` itself (`pruneDepth: 2`); the top-level keys
27
+ * prune `planning` (`pruneDepth: 1`). `planning` is optional, so an emptied
28
+ * block is removed rather than left as `{}`. A `memoryPool` that still
29
+ * carries `indexByteCeiling` survives untouched.
30
+ */
31
+
32
+ import { createRetireAgentrcKeyStep } from '../helpers/retire-agentrc-key.js';
33
+
34
+ const TOP_LEVEL_KEYS = [
35
+ 'complexityGate',
36
+ 'riskHeuristics',
37
+ 'failOnSharedEditors',
38
+ 'requireExplicitCrossStoryDeps',
39
+ 'failOnRegistryConflicts',
40
+ 'failOnLargeFanOut',
41
+ 'largeFanOutThreshold',
42
+ 'crossCuttingRegistries',
43
+ ];
44
+
45
+ export const retirePlanningLimitKnobs = createRetireAgentrcKeyStep({
46
+ version: '2.57.0',
47
+ description:
48
+ 'strip the retired planning.* limit knobs from .agentrc.json — ' +
49
+ 'complexityGate, riskHeuristics, the conflict-severity and fan-out ' +
50
+ 'knobs, and memoryPool.{staleAfterDays, growthDelta} (Story #5312)',
51
+ keys: [
52
+ ...TOP_LEVEL_KEYS.map((key) => ({
53
+ path: ['planning', key],
54
+ pruneDepth: 1,
55
+ })),
56
+ { path: ['planning', 'memoryPool', 'staleAfterDays'], pruneDepth: 2 },
57
+ { path: ['planning', 'memoryPool', 'growthDelta'], pruneDepth: 2 },
58
+ ],
59
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.56.0",
3
+ "version": "2.57.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",
@@ -1,39 +0,0 @@
1
- // .agents/scripts/lib/framework-version.js
2
- /**
3
- * framework-version.js — the visible authoring-marker surface for the legacy
4
- * ticket-body provenance stamp.
5
- *
6
- * Tickets authored under v1 carry a hybrid stamp: a hidden
7
- * `<!-- meta: {"mandrel_version":"…","authored_at":"…"} -->` block plus a
8
- * single visible footer line
9
- * `> 🏷️ Authored with Mandrel v<version> · <YYYY-MM-DD>`. The producer of
10
- * new stamps was retired with the Epic write surface (#4574) — nothing stamps
11
- * a new ticket — but bodies already stamped are live in the backlog, so the
12
- * Story-body serializer/parser must keep round-tripping them:
13
- *
14
- * - {@link AUTHORED_MARKER_LINE_RE} lets the parser skip the marker line during
15
- * section parsing so it never pollutes the last structured section.
16
- * - {@link authoredMarkerLine} lets the serializer re-emit a byte-identical
17
- * marker line for a stamp it parsed, preserving provenance verbatim.
18
- *
19
- * This module imports nothing so it can be pulled in from the story-body
20
- * serializer without risking an import cycle.
21
- */
22
-
23
- /**
24
- * The visible authoring marker line. A blockquote so GitHub renders it as a
25
- * callout. Used in the Story-body parser to skip the line during section
26
- * parsing so it never pollutes the last structured section.
27
- */
28
- export const AUTHORED_MARKER_LINE_RE = /^\s*>\s*🏷️\s+Authored with Mandrel\b/;
29
-
30
- /**
31
- * Build the visible authoring marker line for a given stamp. The Story-body
32
- * serializer uses this to re-emit a legacy stamp it parsed, byte-identically.
33
- *
34
- * @param {{ version: string, authoredAt: string }} stamp
35
- * @returns {string}
36
- */
37
- export function authoredMarkerLine({ version, authoredAt }) {
38
- return `> 🏷️ Authored with Mandrel v${version} · ${authoredAt}`;
39
- }
@@ -1,223 +0,0 @@
1
- /**
2
- * consolidation-precondition.js — deterministic dispatch gate for the Phase
3
- * 8.3 Holistic Consolidation sub-agent (Story #4431, Epic #4429).
4
- *
5
- * The Phase 8.3 consolidation critic (`epic-plan-consolidate`) is a genuine
6
- * fresh-context `Agent` dispatch — every call re-pays the full always-loaded
7
- * context (`.agents/instructions.md` and its always-on rules, § 4). When the
8
- * decomposer's draft `tickets.json` already matches the Tech Spec's `##
9
- * Delivery Slicing` target 1:1 (same shippable-Story count, and the
10
- * `depends_on` shape already agrees with each slice's declared
11
- * "Independent?" answer), there is nothing left for the critic to
12
- * reconcile — dispatching it is pure token spend for a no-op. This module
13
- * computes that decision **deterministically**, off the same two inputs the
14
- * critic itself reads (the draft array and the Epic body's Delivery Slicing
15
- * table), so the planning workflow can skip the sub-agent
16
- * dispatch when it is provably safe to.
17
- *
18
- * **Fail-open by design.** Every ambiguous case — a missing or unparseable
19
- * Delivery Slicing section, an unparseable "Independent?" cell — resolves to
20
- * `dispatch: true`. This gate can only ever *save* a dispatch when it is
21
- * confident the critic has nothing to do; it never disables the critic's
22
- * ability to catch a real divergence. Since Epic #4474 PR6 this precondition
23
- * is one input to the risk/size-conditional dispatch layer
24
- * (`plan-critic-conditions.js`): reachability (8.4) is a deterministic
25
- * persist-side check (`plan-reachability.js`) and the pre-mortem critic
26
- * (8.5) is risk/size-gated; the deterministic ticket validator remains
27
- * unconditional.
28
- *
29
- * Pure, synchronous, no I/O — callers own reading `tickets.json` and the
30
- * Epic body off disk / the GitHub API.
31
- */
32
-
33
- import { DELIVERY_SLICING_RE as DELIVERY_SLICING_HEADING_RE } from '../ticket-body-sections.js';
34
-
35
- /** A row is a markdown table line: starts with `|` once trimmed. */
36
- const TABLE_ROW_RE = /^\|/;
37
-
38
- /** A markdown table separator row: `|---|:---:|---:|` (dashes, colons, pipes only). */
39
- const TABLE_SEPARATOR_RE = /^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?$/;
40
-
41
- /** The literal goal-section token that marks the wave-0 BDD scaffold Story. */
42
- const BDD_SCAFFOLD_GOAL_TOKEN = 'bdd-scaffold';
43
-
44
- /**
45
- * Split one markdown table row into trimmed cell strings.
46
- *
47
- * @param {string} line
48
- * @returns {string[]}
49
- */
50
- function splitTableRow(line) {
51
- let trimmed = line.trim();
52
- if (trimmed.startsWith('|')) trimmed = trimmed.slice(1);
53
- if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1);
54
- return trimmed.split('|').map((cell) => cell.trim());
55
- }
56
-
57
- /**
58
- * Parse an "Independent?" cell per the pinned rule: match the cell's
59
- * leading word case-insensitively as `Yes` or `No`. Any other leading word
60
- * (or an empty cell) is unparseable and returns `null` — the caller must
61
- * fail open (`dispatch: true`) rather than guess.
62
- *
63
- * @param {string} cell
64
- * @returns {boolean|null} `true` for Yes, `false` for No, `null` when unparseable.
65
- */
66
- function parseIndependentCell(cell) {
67
- const match = String(cell ?? '')
68
- .trim()
69
- .match(/^[A-Za-z]+/);
70
- if (!match) return null;
71
- const word = match[0].toLowerCase();
72
- if (word === 'yes') return true;
73
- if (word === 'no') return false;
74
- return null;
75
- }
76
-
77
- /**
78
- * Locate and parse the `## Delivery Slicing` markdown table out of the Epic
79
- * body (which carries the folded Tech Spec sections — Story #4324). Returns
80
- * `null` when the heading is absent, no table follows it, the table has no
81
- * "Independent?" column, or any data row's "Independent?" cell is
82
- * unparseable — every one of those is a fail-open signal for the caller.
83
- *
84
- * @param {string} epicBody
85
- * @returns {{ slice: string, independent: boolean }[] | null}
86
- */
87
- export function parseDeliverySlicingTable(epicBody) {
88
- if (typeof epicBody !== 'string' || epicBody.length === 0) return null;
89
-
90
- const lines = epicBody.split(/\r?\n/);
91
- const headingIdx = lines.findIndex((line) =>
92
- DELIVERY_SLICING_HEADING_RE.test(line.trim()),
93
- );
94
- if (headingIdx === -1) return null;
95
-
96
- let i = headingIdx + 1;
97
- while (i < lines.length && lines[i].trim() === '') i++;
98
- if (i >= lines.length || !TABLE_ROW_RE.test(lines[i].trim())) return null;
99
-
100
- const headerCells = splitTableRow(lines[i]);
101
- i++;
102
- if (i >= lines.length || !TABLE_SEPARATOR_RE.test(lines[i].trim())) {
103
- return null;
104
- }
105
- i++;
106
-
107
- const independentIdx = headerCells.findIndex((cell) =>
108
- /independent/i.test(cell),
109
- );
110
- if (independentIdx === -1) return null;
111
-
112
- const rows = [];
113
- while (i < lines.length && TABLE_ROW_RE.test(lines[i].trim())) {
114
- const cells = splitTableRow(lines[i]);
115
- const independent = parseIndependentCell(cells[independentIdx]);
116
- if (independent === null) return null; // unparseable cell → fail open
117
- rows.push({ slice: (cells[0] ?? '').trim(), independent });
118
- i++;
119
- }
120
-
121
- return rows.length > 0 ? rows : null;
122
- }
123
-
124
- /**
125
- * True when `story` is the recognized wave-0 BDD scaffold Story — identified
126
- * by the literal `bdd-scaffold` goal token the decomposer prompt
127
- * skill's WAVE-0 BDD SCAFFOLD STORY section requires. Scaffold Stories are
128
- * not a Delivery Slicing slice, so they are excluded from the count
129
- * comparison — BDD-adopting consumer repos still benefit from the
130
- * precondition gate rather than always paying the 8.3 dispatch.
131
- *
132
- * @param {{ body?: unknown }} story
133
- * @returns {boolean}
134
- */
135
- function isBddScaffoldStory(story) {
136
- const body = story?.body;
137
- return (
138
- typeof body === 'string' &&
139
- body.toLowerCase().includes(BDD_SCAFFOLD_GOAL_TOKEN)
140
- );
141
- }
142
-
143
- /**
144
- * Evaluate whether the Phase 8.3 consolidation sub-agent needs to run.
145
- *
146
- * @param {object} input
147
- * @param {object[]} input.draftStories - The draft `tickets.json` array
148
- * (the decomposer's output) — raw Story ticket objects with
149
- * top-level `slug` / `depends_on` / `body` (serialized string).
150
- * @param {string} input.epicBody - The Epic body carrying the folded Tech
151
- * Spec sections (`## Delivery Slicing` onward).
152
- * @returns {{ dispatch: boolean, cause: 'match'|'divergence'|'fail-open', reasons: string[] }}
153
- * `dispatch: false` only when the draft matches the Delivery Slicing table
154
- * 1:1 in count and dependency shape; `dispatch: true` (with `reasons`)
155
- * otherwise, including every fail-open case. `cause` distinguishes a
156
- * **confirmed** divergence (count or dependency-shape mismatch) from the
157
- * fail-open ambiguity (missing/unparseable table) — the #4474 PR6
158
- * conditional-dispatch layer treats only the former as a firing condition
159
- * on small drafts.
160
- */
161
- export function evaluateConsolidationPrecondition({ draftStories, epicBody }) {
162
- if (!Array.isArray(draftStories)) {
163
- throw new TypeError(
164
- 'evaluateConsolidationPrecondition: draftStories must be an array',
165
- );
166
- }
167
-
168
- const slicing = parseDeliverySlicingTable(epicBody);
169
- if (!slicing) {
170
- return {
171
- dispatch: true,
172
- cause: 'fail-open',
173
- reasons: [
174
- 'Delivery Slicing section is missing or unparseable — fail-open to the critic.',
175
- ],
176
- };
177
- }
178
-
179
- const slicedStories = draftStories.filter(
180
- (story) => !isBddScaffoldStory(story),
181
- );
182
-
183
- if (slicedStories.length !== slicing.length) {
184
- return {
185
- dispatch: true,
186
- cause: 'divergence',
187
- reasons: [
188
- `Story count diverges from Delivery Slicing: ${slicing.length} proposed slice(s) vs ${slicedStories.length} non-scaffold draft Story(ies).`,
189
- ],
190
- };
191
- }
192
-
193
- const reasons = [];
194
- for (let idx = 0; idx < slicing.length; idx++) {
195
- const slice = slicing[idx];
196
- const story = slicedStories[idx];
197
- const dependsOn = Array.isArray(story?.depends_on) ? story.depends_on : [];
198
- const hasDeps = dependsOn.length > 0;
199
- const storyLabel = story?.slug ?? story?.title ?? `<story ${idx + 1}>`;
200
-
201
- if (slice.independent === false && !hasDeps) {
202
- reasons.push(
203
- `Slice "${slice.slice}" (position ${idx + 1}) is marked Independent: No but draft Story "${storyLabel}" declares no depends_on.`,
204
- );
205
- } else if (slice.independent === true && hasDeps) {
206
- reasons.push(
207
- `Slice "${slice.slice}" (position ${idx + 1}) is marked Independent: Yes but draft Story "${storyLabel}" declares depends_on [${dependsOn.join(', ')}].`,
208
- );
209
- }
210
- }
211
-
212
- if (reasons.length > 0) {
213
- return { dispatch: true, cause: 'divergence', reasons };
214
- }
215
-
216
- return {
217
- dispatch: false,
218
- cause: 'match',
219
- reasons: [
220
- `Draft matches Delivery Slicing 1:1 in count and dependency shape (${slicing.length} slice(s)) — skipping the 8.3 consolidation dispatch.`,
221
- ],
222
- };
223
- }