mandrel 2.53.0 → 2.55.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/story-worker.md +24 -23
- package/.agents/audit-checklists/accessibility.md +0 -3
- package/.agents/audit-checklists/mobile.md +0 -4
- package/.agents/docs/agentrc-reference.json +4 -2
- package/.agents/docs/configuration.md +2 -0
- package/.agents/schemas/agentrc.schema.json +15 -1
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +2 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -0
- package/.agents/scripts/audit-to-stories.js +158 -7
- package/.agents/scripts/check-audit-attribution.js +119 -62
- package/.agents/scripts/check-test-portability.js +512 -0
- package/.agents/scripts/coverage-capture.js +17 -10
- package/.agents/scripts/evidence-gate.js +31 -4
- package/.agents/scripts/generate-workflows-doc.js +65 -14
- package/.agents/scripts/git-cleanup.js +4 -0
- package/.agents/scripts/lib/ITicketingProvider.js +78 -0
- package/.agents/scripts/lib/audit-advisories.js +195 -0
- package/.agents/scripts/lib/audit-attribution.js +22 -0
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +68 -5
- package/.agents/scripts/lib/audit-to-stories/issue-index.js +83 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +60 -114
- package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +347 -0
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +169 -44
- package/.agents/scripts/lib/baselines/merge-envelopes.js +298 -32
- package/.agents/scripts/lib/bootstrap/baseline-merge-driver.js +180 -14
- package/.agents/scripts/lib/cli-args.js +26 -0
- package/.agents/scripts/lib/close-validation/gates.js +113 -7
- package/.agents/scripts/lib/close-validation/process.js +7 -3
- package/.agents/scripts/lib/close-validation/runner.js +62 -11
- package/.agents/scripts/lib/config/ci.js +28 -9
- package/.agents/scripts/lib/config-settings-schema-delivery.js +7 -0
- package/.agents/scripts/lib/config-settings-schema.js +19 -1
- package/.agents/scripts/lib/coverage-capture-fullscope.js +23 -11
- package/.agents/scripts/lib/coverage-capture-incremental.js +22 -16
- package/.agents/scripts/lib/coverage-capture-usage.js +5 -1
- package/.agents/scripts/lib/coverage-capture.js +77 -3
- package/.agents/scripts/lib/findings/route-finding.js +4 -2
- package/.agents/scripts/lib/full-suite-lock.js +232 -6
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/git/sync-from-base.js +130 -13
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +10 -2
- package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +75 -15
- package/.agents/scripts/lib/orchestration/deliver-recover.js +82 -43
- package/.agents/scripts/lib/orchestration/dependency-candidates.js +8 -4
- package/.agents/scripts/lib/orchestration/epic-candidates.js +9 -4
- package/.agents/scripts/lib/orchestration/epic-container.js +66 -4
- package/.agents/scripts/lib/orchestration/epic-rollup.js +241 -84
- package/.agents/scripts/lib/orchestration/file-assumptions.js +218 -16
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +93 -7
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +22 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +26 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +13 -2
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +35 -5
- package/.agents/scripts/lib/orchestration/merge-block-class.js +18 -3
- package/.agents/scripts/lib/orchestration/merge-poll.js +284 -40
- package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +49 -2
- package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +43 -7
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +24 -1
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +5 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -0
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +119 -6
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +110 -0
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +130 -40
- package/.agents/scripts/lib/orchestration/resolve-stories.js +44 -1
- package/.agents/scripts/lib/orchestration/review-providers/native.js +31 -11
- package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +27 -24
- package/.agents/scripts/lib/orchestration/run-epilogue.js +59 -38
- package/.agents/scripts/lib/orchestration/single-story-close/close-note.js +81 -0
- package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +40 -51
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +10 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +101 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +351 -28
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +27 -6
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +117 -22
- package/.agents/scripts/lib/orchestration/story-close/baseline-upward-writeback.js +94 -12
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +6 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -14
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +70 -6
- package/.agents/scripts/lib/orchestration/verify-credit.js +37 -0
- package/.agents/scripts/lib/pinned-override-notes.js +41 -53
- package/.agents/scripts/lib/pinned-override-resolve.js +212 -0
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +18 -0
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +173 -9
- package/.agents/scripts/lib/skills/walk-skill-files.js +24 -7
- package/.agents/scripts/lib/test-temp.js +167 -30
- package/.agents/scripts/lib/validation-evidence.js +37 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +167 -14
- package/.agents/scripts/lib/wave-runner/live-probe.js +7 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
- package/.agents/scripts/merge-baseline.js +175 -21
- package/.agents/scripts/providers/github/errors.js +22 -1
- package/.agents/scripts/providers/github/issues.js +106 -1
- package/.agents/scripts/providers/github/sub-issue-add.js +18 -1
- package/.agents/scripts/providers/github.js +6 -0
- package/.agents/scripts/resolve-stories.js +44 -34
- package/.agents/scripts/single-story-close.js +5 -0
- package/.agents/scripts/stories-wave-tick.js +37 -13
- package/.agents/templates/docs/audit-sweep-runbook.md +41 -7
- package/.agents/workflows/audit-accessibility.md +16 -31
- package/.agents/workflows/audit-mobile.md +20 -37
- package/.agents/workflows/git-cleanup.md +17 -3
- package/.agents/workflows/helpers/audit-lens-core.md +45 -0
- package/.agents/workflows/helpers/deliver-digest.md +7 -6
- package/.agents/workflows/helpers/deliver-reference.md +40 -16
- package/.agents/workflows/helpers/deliver-story-reference.md +7 -4
- package/.agents/workflows/helpers/deliver-story.md +15 -12
- package/.agents/workflows/helpers/plan-reference.md +8 -1
- package/.agents/workflows/mandrel-plan.md +4 -7
- package/.agents/workflows/memory-consolidate.md +14 -9
- package/docs/CHANGELOG.md +34 -0
- package/lib/cli/registry.js +64 -21
- package/lib/cli/sync.js +27 -2
- package/package.json +7 -4
|
@@ -93,18 +93,21 @@ are reference § Step 2. Hard gates always run in Step 3 — the derived level
|
|
|
93
93
|
never disables them; do **not** pre-run the chain here — Step 2.5's credited
|
|
94
94
|
suite run is the sole exception.
|
|
95
95
|
|
|
96
|
-
### Step 2.5 —
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
### Step 2.5 — Push, then the creditable full-suite run, then hand off
|
|
97
|
+
|
|
98
|
+
**Push first.** After the self-eval loop's last fix commit, push
|
|
99
|
+
`story-<storyId>` to `origin`, confirming the remote ref moved: the capture
|
|
100
|
+
below is backgrounded, so *its* completion ends the turn.
|
|
101
|
+
|
|
102
|
+
Then run the full suite **once**, after the push, in the shape close credits
|
|
103
|
+
(**digest § 5**): the credit is keyed on the tree, not on push state, so only
|
|
104
|
+
a *later* commit invalidates it; a bare `npm test` deposits none. Red →
|
|
105
|
+
fix, commit, push, re-capture.
|
|
106
|
+
|
|
107
|
+
Then (sub-agent dispatch only) return the hand-off — Story id, `workCwd`,
|
|
108
|
+
branch, pushed head SHA, self-eval verdict, `verify[]` evidence — and stop.
|
|
109
|
+
Do not open the PR or compose a terminal envelope. An inline run captures
|
|
110
|
+
before Step 3.
|
|
108
111
|
|
|
109
112
|
## Step 3 — Close and land (`single-story-close.js`)
|
|
110
113
|
|
|
@@ -92,6 +92,13 @@ The marker keeps the operator's undelegated decisions findable after the
|
|
|
92
92
|
fact: reviewing a `--yes` plan means scanning its decisions-made-by-default,
|
|
93
93
|
not re-deriving which assumptions were really the agent's to make.
|
|
94
94
|
|
|
95
|
+
## Gate #1 → the memory-pool advisory (`memoryPoolAdvisory`)
|
|
96
|
+
|
|
97
|
+
On a truthy `memoryPoolAdvisory.recommend`, name
|
|
98
|
+
[`/memory-consolidate`](../memory-consolidate.md) at Gate #1, quoting its
|
|
99
|
+
`reasons[]`. Purely advisory: a stale pool degrades recall, it does not make
|
|
100
|
+
the plan wrong, so it never blocks and never reroutes.
|
|
101
|
+
|
|
95
102
|
## Gate #1 → the light path (in-session handoff)
|
|
96
103
|
|
|
97
104
|
On a confirmed `deliverLightSuggestion`, `/mandrel-plan` routes into
|
|
@@ -598,7 +605,7 @@ link that makes the history readable.
|
|
|
598
605
|
|
|
599
606
|
| Behaviour | Contract |
|
|
600
607
|
| --- | --- |
|
|
601
|
-
| Default | Comment + close every source ticket as `not_planned
|
|
608
|
+
| Default | Comment + close every source ticket as `not_planned`, **clearing its `agent::*` label** in the same write — a retired ticket has no agent state, and `agent::done` would claim a delivery that never happened. |
|
|
602
609
|
| `--no-close-superseded` | Skips all commenting and closing. Story creation is unchanged. Use it for a genuinely partial supersede — when the plan folded in only *part* of an issue and the remainder must stay open. |
|
|
603
610
|
| `--dry-run` | Posts no comment and closes nothing; reports what it would have done. |
|
|
604
611
|
| Re-run | Idempotent — the comment is keyed off a `superseded-by` structured-comment marker, and an already-closed source is skipped. |
|
|
@@ -25,8 +25,7 @@ mode from what the operator typed, announce it, act**:
|
|
|
25
25
|
|
|
26
26
|
**Resolving a bare id.** Read live state rather than asking: `agent::done` can
|
|
27
27
|
only be amended, an open unplanned issue only planned. **Announce the
|
|
28
|
-
derivation
|
|
29
|
-
Story already at `agent::ready`.
|
|
28
|
+
derivation**. Ask **only** for an open Story already at `agent::ready`.
|
|
30
29
|
|
|
31
30
|
## Saying what you want
|
|
32
31
|
|
|
@@ -73,10 +72,6 @@ in Key Assumptions, each a decision-made-by-default.
|
|
|
73
72
|
**Gate #1** — STOP to confirm the sharpened plan intent and any
|
|
74
73
|
duplicate-candidate review. Under `--yes`, auto-proceed.
|
|
75
74
|
|
|
76
|
-
On a truthy `memoryPoolAdvisory.recommend`, name
|
|
77
|
-
[`/memory-consolidate`](memory-consolidate.md) quoting its `reasons[]`;
|
|
78
|
-
advisory.
|
|
79
|
-
|
|
80
75
|
On a truthy `deliverLightSuggestion.suggested`, offer — advisory, never an
|
|
81
76
|
automatic reroute — to deliver the seed instead; on confirm route **in this
|
|
82
77
|
session** into [`helpers/deliver-light.md`](helpers/deliver-light.md), its gate
|
|
@@ -93,7 +88,9 @@ persist parses either, serializes canonical markdown and syncs top-level
|
|
|
93
88
|
|
|
94
89
|
**Grounding = your reads + Phase 8.** Nothing inventories the repo: read each
|
|
95
90
|
file you cite; persist hard-errors on any `{path, assumption}` absent from the
|
|
96
|
-
tree
|
|
91
|
+
tree — a `refactors-existing` on a path the base branch **deleted or renamed**
|
|
92
|
+
included. One rescue: a **never-tracked** one normalises to `creates`. Fields:
|
|
93
|
+
[ref](helpers/plan-reference.md).
|
|
97
94
|
|
|
98
95
|
Artifacts under `temp/plan-<slug>/`: `stories.json` (**length 1 by default**;
|
|
99
96
|
over-budget Specs fail closed — split or tighten, never under `docs/`); optional
|
|
@@ -100,19 +100,24 @@ Then write the receipt to `.consolidation-stamp.json` in the pool root:
|
|
|
100
100
|
count the directory, never the plan. It is the baseline the next run measures
|
|
101
101
|
growth against, so a wrong number silently mis-arms the nudge.
|
|
102
102
|
|
|
103
|
-
The `/mandrel-plan` Phase 0 advisory re-arms on exactly
|
|
104
|
-
stamp aging past `planning.memoryPool.staleAfterDays` (30),
|
|
105
|
-
`planning.memoryPool.growthDelta` (25) entries written since that count
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
109
111
|
|
|
110
112
|
Write it **only** after Gate #2 — the stamp asserts an operator reviewed the
|
|
111
113
|
pass, so writing it early makes it a lie.
|
|
112
114
|
|
|
113
|
-
Close with counts: entries read, corrected, merged, pruned,
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
Close with counts: entries read, corrected, merged, pruned, the new total, and
|
|
116
|
+
**the rewritten `MEMORY.md`'s size in bytes beside that count** — the index is
|
|
117
|
+
truncated at the byte ceiling, so a pass that pruned entries but left the
|
|
118
|
+
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
121
|
|
|
117
122
|
## Constraints
|
|
118
123
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,40 @@ 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.55.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.54.0...mandrel-v2.55.0) (2026-09-11)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
* a backgrounded capture ends the worker's turn on an unpushed branch, and close's base-sync silently spends the credit that capture was earning ([#5267](https://github.com/dsj1984/mandrel/issues/5267)) ([#5273](https://github.com/dsj1984/mandrel/issues/5273)) ([8d2bed1](https://github.com/dsj1984/mandrel/commit/8d2bed1e2132bc8b8901b58a33231b2bda5fd61c))
|
|
24
|
+
* epic rollup: every child state change is an edge, the parent is resolved in one call, and provider reads carry one declared shape ([#5280](https://github.com/dsj1984/mandrel/issues/5280)) ([#5294](https://github.com/dsj1984/mandrel/issues/5294)) ([c2c9b96](https://github.com/dsj1984/mandrel/commit/c2c9b9654497404c20105bc3b7edcdbd45fb307e))
|
|
25
|
+
* full-suite credit: a deposit path exists, the lock is liveness-aware, and stamps and evidence are keyed on content ([#5278](https://github.com/dsj1984/mandrel/issues/5278)) ([#5296](https://github.com/dsj1984/mandrel/issues/5296)) ([96c3240](https://github.com/dsj1984/mandrel/commit/96c32405025768de96d2e12680a54ed326787345))
|
|
26
|
+
* memory advisory: measure the index against the harness byte cap, reject future-dated stamps, tighten the skill-id schema, and correct the rename cutover text ([#5285](https://github.com/dsj1984/mandrel/issues/5285)) ([#5289](https://github.com/dsj1984/mandrel/issues/5289)) ([22389a1](https://github.com/dsj1984/mandrel/commit/22389a106cb8e3ce42b0fde288ee473b88e49b99))
|
|
27
|
+
* tests: a portability lint catches the Windows-only shapes before they land, and a vanished temp root is recorded instead of healed ([#5284](https://github.com/dsj1984/mandrel/issues/5284)) ([#5295](https://github.com/dsj1984/mandrel/issues/5295)) ([3358eef](https://github.com/dsj1984/mandrel/commit/3358eefeb0e5f36446b84a4f995e4b90641f64dc))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
* a test fixture that loses its temp root mid-run fails with an unreadable ENOENT, and every later makeTempDir in that process fails with it ([#5274](https://github.com/dsj1984/mandrel/issues/5274)) ([#5275](https://github.com/dsj1984/mandrel/issues/5275)) ([5ac8699](https://github.com/dsj1984/mandrel/commit/5ac86995b92537b33bb209492cf9c3d9a064f792))
|
|
33
|
+
* audit sweep: empty groups parse, the ledger commit is re-runnable from the remote base, SCA attribution is per advisory, and label-less audit Stories cannot be dispatched ([#5281](https://github.com/dsj1984/mandrel/issues/5281)) ([#5290](https://github.com/dsj1984/mandrel/issues/5290)) ([e229b0f](https://github.com/dsj1984/mandrel/commit/e229b0fb07e37e251b713a35646105cc54e791c9))
|
|
34
|
+
* baselines: the merge driver is installed wherever base-sync runs, the refresh ack is scoped to the tagged commit's diff, and the land-time write-back cannot launder a regression ([#5277](https://github.com/dsj1984/mandrel/issues/5277)) ([#5292](https://github.com/dsj1984/mandrel/issues/5292)) ([9354e93](https://github.com/dsj1984/mandrel/commit/9354e93f7e74f263ffaf64c6e72056784a9b74d4))
|
|
35
|
+
* close result: the merged flag is derived from what the run observed, failed closes report only registered gates, and advisory blocks carry their own remedy ([#5279](https://github.com/dsj1984/mandrel/issues/5279)) ([#5293](https://github.com/dsj1984/mandrel/issues/5293)) ([93414ac](https://github.com/dsj1984/mandrel/commit/93414ac756bd3f3300f869d46c28f680883edce4))
|
|
36
|
+
* close's result note claims a merge it never confirmed, and a timed-out advisory scan blocks with the same class and remedies as a real violation ([#5266](https://github.com/dsj1984/mandrel/issues/5266)) ([#5271](https://github.com/dsj1984/mandrel/issues/5271)) ([585a2c3](https://github.com/dsj1984/mandrel/commit/585a2c340d96b972028d2d2f469e2f6b287f7ac2))
|
|
37
|
+
* **deps:** override smol-toml past the GHSA-7w5x-hrqm-74c2 DoS advisory (refs [#5264](https://github.com/dsj1984/mandrel/issues/5264)) ([#5268](https://github.com/dsj1984/mandrel/issues/5268)) ([8db060c](https://github.com/dsj1984/mandrel/commit/8db060ca23a5242c1d13e4a049647be86e853504))
|
|
38
|
+
* git-cleanup: weak-signal remote deletes need an explicit opt-in under --yes, and the bulk PR index short-circuits its per-branch fallback ([#5283](https://github.com/dsj1984/mandrel/issues/5283)) ([#5288](https://github.com/dsj1984/mandrel/issues/5288)) ([8346932](https://github.com/dsj1984/mandrel/commit/83469327b63488ea41a2bd9fb53a37bc2f57d1c0))
|
|
39
|
+
* plan-persist reports two things the tree contradicts: a refactors-existing path deleted weeks ago, and a wave table promising parallelism the dispatch guard refuses ([#5265](https://github.com/dsj1984/mandrel/issues/5265)) ([#5270](https://github.com/dsj1984/mandrel/issues/5270)) ([1b5d1ff](https://github.com/dsj1984/mandrel/commit/1b5d1ffdd6e8821c833d320e93381f7804cb61c8))
|
|
40
|
+
* scoped lint: a degraded surface no longer discards the other surface's findings ([#5282](https://github.com/dsj1984/mandrel/issues/5282)) ([#5287](https://github.com/dsj1984/mandrel/issues/5287)) ([75005ee](https://github.com/dsj1984/mandrel/commit/75005ee925d463d46f5cb24cb3788647e08a37b2))
|
|
41
|
+
* **tests:** a RegExp built from a raw path can never match on Windows (refs [#5274](https://github.com/dsj1984/mandrel/issues/5274)) ([#5276](https://github.com/dsj1984/mandrel/issues/5276)) ([8d5efb2](https://github.com/dsj1984/mandrel/commit/8d5efb256d936f7dd955eff55c68d70d8abbfb9c))
|
|
42
|
+
* **tests:** close the two Windows-only breaks Epic [#5286](https://github.com/dsj1984/mandrel/issues/5286) left on main, and the class behind them ([#5297](https://github.com/dsj1984/mandrel/issues/5297)) ([ec419c7](https://github.com/dsj1984/mandrel/commit/ec419c7b7462f4e6e0adf76805c22735ac2e9673))
|
|
43
|
+
* **tests:** prove the workflows-doc drift gate on a fixture root, not the live checkout ([#5291](https://github.com/dsj1984/mandrel/issues/5291)) ([e44aa99](https://github.com/dsj1984/mandrel/commit/e44aa991027859c5d94d9dc875e80d41e1572c00))
|
|
44
|
+
|
|
45
|
+
## [2.54.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.53.0...mandrel-v2.54.0) (2026-09-09)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
* a closed child pins its container Epic open forever: the rollup reads its stale agent::* label, and the supersede close is what writes one ([#5255](https://github.com/dsj1984/mandrel/issues/5255)) ([#5256](https://github.com/dsj1984/mandrel/issues/5256)) ([672428f](https://github.com/dsj1984/mandrel/commit/672428f1e0fba1388a1a001d2840e7eb827b9373))
|
|
51
|
+
|
|
18
52
|
## [2.53.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.52.0...mandrel-v2.53.0) (2026-09-09)
|
|
19
53
|
|
|
20
54
|
|
package/lib/cli/registry.js
CHANGED
|
@@ -28,7 +28,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
28
|
import {
|
|
29
29
|
BASELINE_MERGE_DRIVER_CONFIG_KEY,
|
|
30
30
|
BASELINE_MERGE_DRIVER_REMEDY,
|
|
31
|
-
|
|
31
|
+
parseBaselineMergeDriverCommand,
|
|
32
|
+
probeBaselineMergeDriver,
|
|
32
33
|
} from '../../.agents/scripts/lib/bootstrap/baseline-merge-driver.js';
|
|
33
34
|
import {
|
|
34
35
|
REQUIRED_NODE_CEILING_MAJOR,
|
|
@@ -59,8 +60,8 @@ import {
|
|
|
59
60
|
* @param {string[]} args
|
|
60
61
|
* @returns {{ status: number|null, stdout: string, stderr: string, error?: NodeJS.ErrnoException }}
|
|
61
62
|
*/
|
|
62
|
-
function spawn(cmd, args) {
|
|
63
|
-
const r = spawnSync(cmd, args, { encoding: 'utf8' });
|
|
63
|
+
function spawn(cmd, args, opts = {}) {
|
|
64
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
64
65
|
return {
|
|
65
66
|
status: r.status,
|
|
66
67
|
stdout: typeof r.stdout === 'string' ? r.stdout : '',
|
|
@@ -1101,34 +1102,76 @@ function runVersionCurrent({ cachePath, installedVersion, fsImpl = fs } = {}) {
|
|
|
1101
1102
|
*/
|
|
1102
1103
|
export function runMergeDriver({ cwd, fsImpl = fs, runner = spawn } = {}) {
|
|
1103
1104
|
const projectRoot = (cwd ?? (() => process.cwd()))();
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1112
|
-
if (!
|
|
1105
|
+
// Both git calls run from `projectRoot`: the config key is per-clone, and
|
|
1106
|
+
// reading it from wherever `mandrel doctor` was typed answers about a
|
|
1107
|
+
// different repository (or none).
|
|
1108
|
+
const { declared, command } = probeBaselineMergeDriver({
|
|
1109
|
+
projectRoot,
|
|
1110
|
+
fsImpl,
|
|
1111
|
+
runGit: (args) => runner('git', args, { cwd: projectRoot }),
|
|
1112
|
+
});
|
|
1113
|
+
if (!declared) {
|
|
1113
1114
|
return {
|
|
1114
1115
|
ok: true,
|
|
1115
1116
|
detail:
|
|
1116
1117
|
'skipped — .gitattributes does not route baselines/*.json through the mandrel merge driver',
|
|
1117
1118
|
};
|
|
1118
1119
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (configured.status === 0 && configured.stdout.trim() !== '') {
|
|
1126
|
-
return { ok: true, detail: configured.stdout.trim() };
|
|
1120
|
+
if (command === '') {
|
|
1121
|
+
return {
|
|
1122
|
+
ok: false,
|
|
1123
|
+
detail: `${BASELINE_MERGE_DRIVER_CONFIG_KEY} is unset — baselines/*.json will fall back to git's text merge, which conflicts on the generatedAt stamp and can splice rows neither branch scored`,
|
|
1124
|
+
remedy: BASELINE_MERGE_DRIVER_REMEDY,
|
|
1125
|
+
};
|
|
1127
1126
|
}
|
|
1127
|
+
return probeConfiguredDriver(command, runner, projectRoot);
|
|
1128
|
+
}
|
|
1128
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* Does the configured driver command actually RUN? (Story #5277)
|
|
1132
|
+
*
|
|
1133
|
+
* A set-but-broken key is the failure mode a presence check cannot see, and it
|
|
1134
|
+
* is the common one: the command names an absolute node binary, and an nvm or
|
|
1135
|
+
* volta version bump moves that binary out from under it months after the
|
|
1136
|
+
* driver was installed. Git's behaviour then is the same silence a missing key
|
|
1137
|
+
* produces — the driver exits non-zero, git leaves the file conflicted, and the
|
|
1138
|
+
* operator reads it as "baselines conflict again" rather than as a broken
|
|
1139
|
+
* registration. So the check executes what git would execute.
|
|
1140
|
+
*
|
|
1141
|
+
* `--help` is the probe because the driver's real invocation mutates `%A` in
|
|
1142
|
+
* place; the shipped CLI answers `--help` with exit 0 and touches nothing.
|
|
1143
|
+
*
|
|
1144
|
+
* The command is tokenised, never handed to a shell
|
|
1145
|
+
* ({@link parseBaselineMergeDriverCommand}) — it is a config value, and running
|
|
1146
|
+
* it through `shell: true` would make any write to that key arbitrary command
|
|
1147
|
+
* execution (security-baseline § Output & Rendering).
|
|
1148
|
+
*
|
|
1149
|
+
* The probe runs from `projectRoot`, because the command's script path is
|
|
1150
|
+
* relative to the worktree root — which is where git invokes a merge driver
|
|
1151
|
+
* from, and is not necessarily where `mandrel doctor` was typed.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {string} command
|
|
1154
|
+
* @param {typeof spawn} runner
|
|
1155
|
+
* @param {string} projectRoot
|
|
1156
|
+
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
1157
|
+
*/
|
|
1158
|
+
function probeConfiguredDriver(command, runner, projectRoot) {
|
|
1159
|
+
const parsed = parseBaselineMergeDriverCommand(command);
|
|
1160
|
+
if (!parsed) {
|
|
1161
|
+
return {
|
|
1162
|
+
ok: false,
|
|
1163
|
+
detail: `${BASELINE_MERGE_DRIVER_CONFIG_KEY} is set to ${JSON.stringify(command)}, which has no runnable command in it`,
|
|
1164
|
+
remedy: BASELINE_MERGE_DRIVER_REMEDY,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
const probe = runner(parsed.file, [...parsed.args, '--help'], {
|
|
1168
|
+
cwd: projectRoot,
|
|
1169
|
+
});
|
|
1170
|
+
if (probe.status === 0) return { ok: true, detail: command };
|
|
1171
|
+
const why = probe.error?.message ?? `exit ${probe.status}`;
|
|
1129
1172
|
return {
|
|
1130
1173
|
ok: false,
|
|
1131
|
-
detail: `${BASELINE_MERGE_DRIVER_CONFIG_KEY} is
|
|
1174
|
+
detail: `${BASELINE_MERGE_DRIVER_CONFIG_KEY} is set to ${JSON.stringify(command)} but running it failed (${why}) — git would fall back to text-merging baselines/*.json exactly as if the key were unset`,
|
|
1132
1175
|
remedy: BASELINE_MERGE_DRIVER_REMEDY,
|
|
1133
1176
|
};
|
|
1134
1177
|
}
|
package/lib/cli/sync.js
CHANGED
|
@@ -38,14 +38,24 @@
|
|
|
38
38
|
* {@link readVersionMarker} for how callers consume it.
|
|
39
39
|
*
|
|
40
40
|
* Security (Tech Spec #3459 "Postinstall safety"):
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
41
|
+
* - No network, and no shell: every child process is spawned with argv
|
|
42
|
+
* tokens and `shell: false`.
|
|
43
43
|
* - Logs only paths and counts, never file contents or environment values.
|
|
44
|
+
* - One write outside `./.agents/` (Story #5277): the per-clone
|
|
45
|
+
* `merge.mandrel-baseline.driver` git config, and only when the project's
|
|
46
|
+
* own tracked `.gitattributes` already declares the attribute that needs
|
|
47
|
+
* it. That half of the registration cannot ship with the repository — git
|
|
48
|
+
* will not execute a command chosen by whoever wrote it — so every fresh
|
|
49
|
+
* clone silently text-merges generated baselines until something installs
|
|
50
|
+
* it locally, and `sync` is the one command every consumer runs. It never
|
|
51
|
+
* creates or edits `.gitattributes`; a project that has not opted into the
|
|
52
|
+
* quality surface is left untouched.
|
|
44
53
|
*
|
|
45
54
|
* Injectable seams (used by lib/cli/__tests__/sync.test.js):
|
|
46
55
|
* - `resolvePackageRoot` — replaces real `mandrel` resolution
|
|
47
56
|
* - `fs` — replaces the node:fs surface used here
|
|
48
57
|
* - `cwd` — replaces process.cwd()
|
|
58
|
+
* - `ensureMergeDriver` — replaces the git-config merge-driver install
|
|
49
59
|
* - `write` — replaces process.stdout.write
|
|
50
60
|
* - `writeErr` — replaces process.stderr.write
|
|
51
61
|
* - `exit` — replaces process.exit
|
|
@@ -55,6 +65,7 @@ import nodeFs from 'node:fs';
|
|
|
55
65
|
import { createRequire } from 'node:module';
|
|
56
66
|
import path from 'node:path';
|
|
57
67
|
|
|
68
|
+
import { ensureBaselineMergeDriver } from '../../.agents/scripts/lib/bootstrap/baseline-merge-driver.js';
|
|
58
69
|
import { LEDGER_RELATIVE_PATH } from '../../.agents/scripts/lib/bootstrap/install-ledger.js';
|
|
59
70
|
|
|
60
71
|
export const PACKAGE_NAME = 'mandrel';
|
|
@@ -337,6 +348,7 @@ function listDestFiles(dir, fsImpl, prefix = '') {
|
|
|
337
348
|
* write?: (s: string) => void,
|
|
338
349
|
* writeErr?: (s: string) => void,
|
|
339
350
|
* exit?: (code: number) => void,
|
|
351
|
+
* ensureMergeDriver?: typeof ensureBaselineMergeDriver,
|
|
340
352
|
* }} [opts]
|
|
341
353
|
* @returns {{ copied: number, planned: number, pruned: number, dryRun: boolean }}
|
|
342
354
|
* Summary (also returned in dry-run / error paths for testability).
|
|
@@ -344,6 +356,7 @@ function listDestFiles(dir, fsImpl, prefix = '') {
|
|
|
344
356
|
export function runSync({
|
|
345
357
|
argv = [],
|
|
346
358
|
resolvePackageRoot = defaultResolvePackageRoot,
|
|
359
|
+
ensureMergeDriver = ensureBaselineMergeDriver,
|
|
347
360
|
fs = nodeFs,
|
|
348
361
|
cwd = () => process.cwd(),
|
|
349
362
|
write = (s) => process.stdout.write(s),
|
|
@@ -435,6 +448,18 @@ export function runSync({
|
|
|
435
448
|
`${packageVersion}\n`,
|
|
436
449
|
);
|
|
437
450
|
|
|
451
|
+
// Baseline merge driver (Story #5277) — config half only; see the module
|
|
452
|
+
// preamble's security note. Never allowed to fail the sync: a consumer
|
|
453
|
+
// without git, or with a read-only config, still gets their `.agents/` tree.
|
|
454
|
+
const driver = ensureMergeDriver({
|
|
455
|
+
projectRoot,
|
|
456
|
+
configOnly: true,
|
|
457
|
+
fsImpl: fs,
|
|
458
|
+
});
|
|
459
|
+
if (driver?.action === 'updated') {
|
|
460
|
+
write('✅ Registered the baselines/*.json merge driver for this clone\n');
|
|
461
|
+
}
|
|
462
|
+
|
|
438
463
|
if (staleFiles.length > 0) {
|
|
439
464
|
write(
|
|
440
465
|
`✅ Installed ${payloadFiles.length} file(s) into ./.agents/ (pruned ${staleFiles.length} stale file(s))\n`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.55.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/",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"baseline:agents-loc": "node baselines/agents-loc-baseline.mjs",
|
|
33
33
|
"baselines:scope": "node .agents/scripts/check-baseline-scope.js",
|
|
34
34
|
"baselines:prune": "node .agents/scripts/prune-baseline-orphans.js",
|
|
35
|
+
"baselines:merge-driver": "node .agents/scripts/merge-baseline.js --install",
|
|
35
36
|
"lint:md": "markdownlint-cli2 \".agents/**/*.md\" \"*.md\" \"!node_modules/**\" \"!.worktrees/**\"",
|
|
36
37
|
"lint": "node .agents/scripts/run-lint.js && node .agents/scripts/check-generated-validator.js --check && node .agents/scripts/check-pinned-override-notes.js && npm run docs:check",
|
|
37
38
|
"docs:gen": "node .agents/scripts/generate-config-docs.js && node .agents/scripts/generate-workflows-doc.js && node .agents/scripts/generate-lens-checklists.js",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"quality:watch": "node .agents/scripts/quality-watch.js",
|
|
64
65
|
"sync:commands": "node bin/mandrel.js sync-commands",
|
|
65
66
|
"sync:agents": "node .agents/scripts/sync-claude-agents.js",
|
|
66
|
-
"prepare": "husky && npm run sync:commands && npm run sync:agents",
|
|
67
|
+
"prepare": "husky && npm run sync:commands && npm run sync:agents && npm run baselines:merge-driver",
|
|
67
68
|
"postinstall": "node bin/postinstall.js mandrel sync"
|
|
68
69
|
},
|
|
69
70
|
"repository": {
|
|
@@ -130,10 +131,12 @@
|
|
|
130
131
|
"//": {
|
|
131
132
|
"peerDependencies.@cucumber/gherkin": "OPTIONAL peer, mirroring the `typescript` precedent, and deliberately NOT a runtime dependency. `check-gherkin-corpus.js` is the only consumer and it is opt-in behind `qa.gherkinLint`, so a consumer with no BDD tier must gain nothing from an upgrade. The devDependency alongside it is what lets this repository's own suite drive the real parser. The gate resolves it through a require path anchored at the consumer project — `.agents/` reaches a consumer by plain file copy, so a bare specifier would resolve against the consumer's module chain, which under a non-hoisting linker need not hold it.",
|
|
132
133
|
"overrides.js-yaml": "COUPLED to devDependencies.markdownlint-cli2 — do not bump either alone, and do NOT drop this override. It is load-bearing: markdownlint-cli2 0.22.x pulls js-yaml 4.1.1, which carries GHSA-52cp-r559-cp3m (high) and GHSA-h67p-54hq-rp68 (moderate); removing the override was measured to reintroduce both (1 high + 1 moderate), while with it in place `npm audit` is clean. An npm override also wins over a transitive package's own pin, so this tree-wide ^4.3.2 is imposed on every js-yaml consumer regardless of what they declare — and markdownlint-cli2 0.23.x declares an exact js-yaml 5.2.1. A dry-run bump confirmed the trap: markdownlint-cli2 0.23.1 resolves against js-yaml 4.3.0, two majors off what it declares, silently. The only safe move is to raise this override and bump markdownlint-cli2 in ONE reviewed commit (first confirming cosmiconfig, under @commitlint/cli, tolerates the same major). renovate.json excludes markdownlint-cli2 from devDependency auto-merge so that pair cannot drift apart unattended. The floor has since been raised twice for advisories against the pinned range itself (most recently to ^4.3.2 for GHSA-2883-xcg3-v3hh, whose fix is 4.3.2) — raising it is safe and does NOT touch the coupling above; check-pinned-override-notes.js now fails the build if this sentence and the pin disagree.",
|
|
133
|
-
"dependencies.js-yaml": "States the SAME range as overrides.js-yaml above. The two are deliberate duplicates — npm has no way to reference the direct range from the overrides block — so they MUST move in lockstep; changing one without the other silently splits the direct and transitive resolutions."
|
|
134
|
+
"dependencies.js-yaml": "States the SAME range as overrides.js-yaml above. The two are deliberate duplicates — npm has no way to reference the direct range from the overrides block — so they MUST move in lockstep; changing one without the other silently splits the direct and transitive resolutions.",
|
|
135
|
+
"overrides.smol-toml": "Load-bearing: markdownlint-cli2 0.22.1 declares an EXACT smol-toml 1.6.1, which carries GHSA-7w5x-hrqm-74c2 (high — denial of service via malformed TOML). The advisory’s vulnerable range is everything <= 1.7.0, so no lockfile-only resolution reaches a patched version and this tree-wide ^1.7.1 pin is what clears it. npm’s own `audit fix --force` instead proposes markdownlint-cli2 0.21.0 — a DOWNGRADE presented as a breaking change; do not take it. An npm override wins over a transitive package’s own exact pin, so markdownlint-cli2 runs against a minor it never declared: `npm run lint` was confirmed green under the forced version and is what must be re-run if this pin moves. knip declares ^1.6.1 and is satisfied natively. smol-toml is NOT a direct dependency, so unlike overrides.js-yaml there is no lockstep duplicate range to keep in sync."
|
|
134
136
|
},
|
|
135
137
|
"overrides": {
|
|
136
138
|
"js-yaml": "^4.3.2",
|
|
137
|
-
"markdown-it": "^14.2.0"
|
|
139
|
+
"markdown-it": "^14.2.0",
|
|
140
|
+
"smol-toml": "^1.7.1"
|
|
138
141
|
}
|
|
139
142
|
}
|