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.
- package/.agents/agents/plan-critic.md +13 -18
- package/.agents/agents/story-worker.md +25 -34
- package/.agents/docs/agentrc-reference.json +0 -30
- package/.agents/docs/configuration.md +8 -28
- package/.agents/docs/execution-reference.md +5 -5
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/instructions.md +9 -10
- package/.agents/schemas/agentrc.schema.json +9 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/ceremony-derive.js +191 -0
- package/.agents/scripts/check-context-budget.js +28 -33
- package/.agents/scripts/check-cyclomatic.js +4 -3
- package/.agents/scripts/deliver-light.js +31 -94
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
- package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
- package/.agents/scripts/lib/close-validation/gates.js +52 -1
- package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
- package/.agents/scripts/lib/config/delivery-routing.js +7 -33
- package/.agents/scripts/lib/config/explain.js +0 -19
- package/.agents/scripts/lib/config/limits.js +18 -78
- package/.agents/scripts/lib/config/quality.js +6 -3
- package/.agents/scripts/lib/config/runners.js +3 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
- package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
- package/.agents/scripts/lib/config-settings-schema.js +16 -143
- package/.agents/scripts/lib/crap-engine.js +35 -4
- package/.agents/scripts/lib/crap-utils.js +17 -1
- package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
- package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
- package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
- package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
- package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +118 -297
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
- package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
- package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
- package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
- package/.agents/scripts/lib/story-body/story-body.js +17 -237
- package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
- package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
- package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
- package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
- package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
- package/.agents/scripts/lib/test-run-credit.js +266 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
- package/.agents/scripts/lib/workers/crap-worker.js +32 -41
- package/.agents/scripts/plan-context.js +7 -9
- package/.agents/scripts/plan-critics.js +28 -54
- package/.agents/scripts/plan-persist.js +25 -68
- package/.agents/scripts/quality-preview.js +51 -0
- package/.agents/scripts/run-tests.js +12 -0
- package/.agents/scripts/stories-wave-tick.js +23 -45
- package/.agents/scripts/test-isolate.js +13 -180
- package/.agents/scripts/update-coverage-baseline.js +25 -70
- package/.agents/scripts/update-crap-baseline.js +19 -123
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/workflows/audit-clean-code.md +4 -3
- package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
- package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
- package/.agents/workflows/helpers/code-review.md +2 -3
- package/.agents/workflows/helpers/deliver-digest.md +41 -57
- package/.agents/workflows/helpers/deliver-light.md +40 -105
- package/.agents/workflows/helpers/deliver-reference.md +1 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +37 -58
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -219
- package/.agents/workflows/mandrel-plan.md +27 -40
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +23 -0
- package/lib/migrations/index.js +4 -0
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/framework-version.js +0 -39
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
- package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
- package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
- 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
|
|
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**).
|
|
63
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
The `/mandrel-plan` Phase 0 advisory re-arms on exactly
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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.
|
|
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
|
|
package/lib/migrations/index.js
CHANGED
|
@@ -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,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
|
-
}
|