mandrel 2.54.0 → 2.56.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 +8 -2
- package/.agents/docs/configuration.md +5 -0
- package/.agents/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +34 -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 +374 -76
- 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/file-ci-gap.js +306 -0
- 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/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +80 -29
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issue-index.js +83 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +61 -115
- package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +347 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +169 -44
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- 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 +52 -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/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +42 -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/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/source-classifier.js +2 -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/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- 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 +233 -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/audit-provenance.js +197 -0
- 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 +39 -3
- 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 +63 -0
- 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 +63 -42
- 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/story-follow-ups.js +32 -20
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -14
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +30 -0
- 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/pr-watch-with-update.js +3 -2
- 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/audit-to-stories.md +63 -27
- 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 +35 -14
- package/.agents/workflows/helpers/deliver-story-reference.md +26 -8
- package/.agents/workflows/helpers/deliver-story.md +15 -12
- package/.agents/workflows/helpers/plan-reference.md +30 -0
- package/.agents/workflows/mandrel-plan.md +10 -13
- package/.agents/workflows/memory-consolidate.md +14 -9
- package/docs/CHANGELOG.md +37 -0
- package/lib/cli/registry.js +64 -21
- package/lib/cli/sync.js +27 -2
- package/package.json +7 -4
|
@@ -49,6 +49,13 @@ issue state rather than against anything you hand it. That is why there is no
|
|
|
49
49
|
batch label to pass and why a blocker that landed in an unrelated run is simply
|
|
50
50
|
seen as done.
|
|
51
51
|
|
|
52
|
+
**A Story with no `agent::*` label is refused.** The audit sweep files Stories
|
|
53
|
+
deliberately without one — their bodies are audit prose, not a scoped change
|
|
54
|
+
with verifiable acceptance criteria — so resolving one means dispatching a
|
|
55
|
+
worker at an unenriched body, after taking its lease. Route it through
|
|
56
|
+
`/mandrel-plan` first, which applies `agent::ready` at the end of planning.
|
|
57
|
+
`--allow-unlabelled` is the deliberate escape hatch.
|
|
58
|
+
|
|
52
59
|
**The non-zero exit codes.** **2** — `cycleError`: the graph is
|
|
53
60
|
self-referential; fix `depends_on`, do not retry. **3** — `wedged`: nothing
|
|
54
61
|
dispatchable and nothing in flight, with the undone Stories and their unmet
|
|
@@ -292,10 +299,10 @@ This executes, in order:
|
|
|
292
299
|
(files issues when auto-file is on; posts `follow-ups`).
|
|
293
300
|
- `sibling-coherence` — Spec/Acceptance coherence check across sibling bodies
|
|
294
301
|
(`plan-run-sibling-coherence`).
|
|
295
|
-
- `epic-close` — **reports** which container Epics
|
|
296
|
-
are still
|
|
297
|
-
|
|
298
|
-
a
|
|
302
|
+
- `epic-close` — **reports** which of the run's container Epics its land tails
|
|
303
|
+
left closed and which are still open. **Read-only** — it derives nothing:
|
|
304
|
+
every child state change is already a rollup edge, so the container was
|
|
305
|
+
derived from a complete child set by the last Story's own land tail.
|
|
299
306
|
|
|
300
307
|
A single-Story run skips the epilogue — follow-ups are captured on merge
|
|
301
308
|
confirm instead (`captureStoryFollowUps`).
|
|
@@ -304,9 +311,12 @@ confirm instead (`captureStoryFollowUps`).
|
|
|
304
311
|
|
|
305
312
|
A container Epic is never delivered, so nothing used to write to it during
|
|
306
313
|
the run it was the subject of. `epic-rollup.js` derives its state from its
|
|
307
|
-
children at
|
|
308
|
-
`single-story-init.js
|
|
309
|
-
`epicRollup` step)
|
|
314
|
+
children at **every edge that changes a child's state** — the
|
|
315
|
+
`agent::executing` flip in `single-story-init.js`, the post-land tail
|
|
316
|
+
(reported as the tail's `epicRollup` step), and `plan-persist`'s supersede
|
|
317
|
+
close (reported as `supersede.epicRollup`) — which is why it holds at **N=1**,
|
|
318
|
+
where no epilogue runs, and why a cohort superseded by a re-plan no longer
|
|
319
|
+
strands its container open above finished work.
|
|
310
320
|
|
|
311
321
|
- **Status** follows the children's composition (`deriveParentState` mapped
|
|
312
322
|
onto the board's three options): any **open** child executing or blocked →
|
|
@@ -319,13 +329,24 @@ children at both per-Story lifecycle edges — the `agent::executing` flip in
|
|
|
319
329
|
ready list.
|
|
320
330
|
- **Owner** — `github.operatorHandle` is added to the Epic while any child is
|
|
321
331
|
in flight, through the additive assignees endpoint, and is never removed.
|
|
322
|
-
- **Closure** is one-way:
|
|
323
|
-
`completed
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
332
|
+
- **Closure** is one-way: a container whose children are all finished closes,
|
|
333
|
+
as `completed` when at least one child landed and as `not_planned` when none
|
|
334
|
+
did (a cohort superseded by a re-plan is finished, but nothing merged). A
|
|
335
|
+
reopened child moves Status back to `In Progress` and does **not** reopen the
|
|
336
|
+
issue — which is why the lookup reads `state: 'all'`, since an open-only
|
|
337
|
+
listing cannot see the container it would have to correct.
|
|
338
|
+
- The parent lookup resolves the native parent edge in **one** call
|
|
339
|
+
(`getParentIssue`), because linkage is parent→child only; it falls back to a
|
|
340
|
+
`type::epic` scan for a child linked by checklist alone. Children are read as
|
|
341
|
+
the body checklist **union** the native sub-issue edges — literally the same
|
|
342
|
+
reader `/mandrel-deliver`'s expansion uses, so an Epic can never be
|
|
343
|
+
expandable but unclosable — bounded at 5 concurrent reads.
|
|
344
|
+
- A checklist row citing an id that resolves to nothing is **dropped with a
|
|
345
|
+
warning** when the native read succeeded: hand-edited prose can cite a
|
|
346
|
+
deleted or mistyped issue, and no re-run will make it resolve. An
|
|
347
|
+
unresolvable *native* edge still fails the read. An Epic-typed child is
|
|
348
|
+
refused by name (`epic-typed-child`) and neither blocks nor advances the
|
|
349
|
+
parent.
|
|
329
350
|
- Every step is best-effort and never throws: a stale container costs
|
|
330
351
|
tidiness, not a landed Story's envelope.
|
|
331
352
|
|
|
@@ -240,9 +240,10 @@ discovers them only after the whole close pipeline has run, at several times
|
|
|
240
240
|
the cost of one full-suite run in the worktree.
|
|
241
241
|
|
|
242
242
|
**Run it once, last, so close can credit it.** The run belongs **after** the
|
|
243
|
-
self-eval loop's last fix commit and
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
self-eval loop's last fix commit and **after** the hand-off push — the credit
|
|
244
|
+
is keyed on the tree, not on push state, so pushing first keeps the stamp and
|
|
245
|
+
buys the ordering Step 2.5 needs (the capture is backgrounded, and its
|
|
246
|
+
completion ends the turn); redraft rounds run scoped tests. Close skips a gate that already passed at the current HEAD, but a bare
|
|
246
247
|
`npm test` deposits no such record — the suite then runs twice per delivery,
|
|
247
248
|
once here and once in the close gate chain. Pick the invocation by the same
|
|
248
249
|
predicate `close-validation/gates.js` uses to choose its test gate:
|
|
@@ -262,7 +263,9 @@ The credit expires the moment it stops describing the tree: evidence is keyed
|
|
|
262
263
|
on HEAD, the capture stamp on a content digest of `crap.targetDirs`. A
|
|
263
264
|
self-eval fix — or any commit — invalidates it and close re-runs the suite for
|
|
264
265
|
real, so this never trades away the gate. That keying is exactly why the run
|
|
265
|
-
comes last.
|
|
266
|
+
comes last, and why the push before it is free. Close's own base-sync can
|
|
267
|
+
spend the stamp too when it lands base commits; it now says so out loud rather
|
|
268
|
+
than silently re-running the suite.
|
|
266
269
|
|
|
267
270
|
**`verify[]` reuses the same stamp.** A `verify[]` entry that is itself a
|
|
268
271
|
full-suite command is reported **credited** against that stamp rather than
|
|
@@ -679,13 +682,28 @@ When the watch exits, branch on the exit code:
|
|
|
679
682
|
|
|
680
683
|
**Triage authority.** How to classify and remediate a red (or repeatedly slow)
|
|
681
684
|
check — the root-cause-only decision tree for infra/transient and flaky failures
|
|
682
|
-
(reproduce → check `main` → bisect env vs code → fix in-scope or
|
|
683
|
-
|
|
684
|
-
and the escalation criteria (three-strikes, the 30-minute
|
|
685
|
-
and the clearly-environmental fast path) — is defined once in
|
|
685
|
+
(reproduce → check `main` → bisect env vs code → fix in-scope, or reach an
|
|
686
|
+
Option-2 verdict and file the intake issue), the never-rerun / never-quarantine
|
|
687
|
+
prohibitions, and the escalation criteria (three-strikes, the 30-minute
|
|
688
|
+
wall-clock timebox, and the clearly-environmental fast path) — is defined once in
|
|
686
689
|
[`.agents/rules/ci-remediation.md`](../../rules/ci-remediation.md). Read it
|
|
687
690
|
before remediating a red check.
|
|
688
691
|
|
|
692
|
+
**Filing an out-of-scope root cause is one command, never a hand-run `gh issue
|
|
693
|
+
create`:**
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
node <agentRoot>/scripts/file-ci-gap.js --story <storyId> \
|
|
697
|
+
--verdict <pre-existing|capacity|unreproducible-tier> \
|
|
698
|
+
--owner <consumer|framework|platform> --evidence "<proof reading>" [--block]
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
It reads the digest, routes the filing to the repo that owns the fault, updates
|
|
702
|
+
the existing ticket when the signature is a repeat, posts the `friction`
|
|
703
|
+
comment, and with `--block` flips the Story. What it files is an **intake**
|
|
704
|
+
issue, not a Story — `/mandrel-plan <issue number>` graduates it on the next
|
|
705
|
+
planning pass, so the delivery never waits on planning.
|
|
706
|
+
|
|
689
707
|
### The auto-merge wait is an internally-blocking step
|
|
690
708
|
|
|
691
709
|
This is the single most important contract of this workflow, and the seam
|
|
@@ -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,36 @@ 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
|
+
|
|
102
|
+
## Gate #1 → graduating a CI-gap intake filing (`intake`)
|
|
103
|
+
|
|
104
|
+
The envelope's `priorFeedback` arrays carry the open `meta::*` feedback issues.
|
|
105
|
+
A row flagged `intake: true` is a **CI-gap intake filing** — written by
|
|
106
|
+
[`file-ci-gap.js`](../../scripts/file-ci-gap.js) when a delivery reached an
|
|
107
|
+
Option-2 verdict in [`ci-remediation.md`](../../rules/ci-remediation.md) and the
|
|
108
|
+
root cause was outside its scope. It carries evidence (failure signature, run
|
|
109
|
+
link, occurrence history, ownership routing) but **no `## Spec`, no
|
|
110
|
+
`acceptance[]` / `verify[]` and no `agent::*` label**, so `/mandrel-deliver`
|
|
111
|
+
cannot take it: it is intake awaiting graduation, by design. Delivery files it
|
|
112
|
+
and moves on rather than blocking on a planning pass nobody is present for.
|
|
113
|
+
|
|
114
|
+
Graduating one is exactly **tickets mode**: `/mandrel-plan <issue number>`
|
|
115
|
+
rewrites it into a Story, `supersedes[]` claims it, and persist closes it.
|
|
116
|
+
|
|
117
|
+
At Gate #1, in **ask** and **seed** mode, name any open intake rows and offer
|
|
118
|
+
that instead of the seed in front of you — a filing that keeps recurring
|
|
119
|
+
(its `## Occurrences` table is the count) is usually the better next Story than
|
|
120
|
+
whatever prompted this run. It is **advisory**: never reroute automatically, and
|
|
121
|
+
skip the offer entirely under `--yes`, where nobody is at the keyboard to take
|
|
122
|
+
it. A `platformGaps[]` row is the same shape with a different owner — the
|
|
123
|
+
Story it graduates into may well be a config or runbook change rather than code.
|
|
124
|
+
|
|
95
125
|
## Gate #1 → the light path (in-session handoff)
|
|
96
126
|
|
|
97
127
|
On a confirmed `deliverLightSuggestion`, `/mandrel-plan` routes into
|
|
@@ -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
|
|
|
@@ -59,10 +58,9 @@ and derives source ids from its `sourceTickets[]`; it also writes
|
|
|
59
58
|
|
|
60
59
|
The envelope carries docs context, the story-author prompt, `sourceTickets[]`,
|
|
61
60
|
`duplicates[]` (open **Stories**, never Epics), `epicCandidates[]` +
|
|
62
|
-
`dependencyCandidates[]` (Gate #3; path collisions) and
|
|
63
|
-
`complexitySignals` (**no routing authority**). A trivial scope
|
|
64
|
-
lite route at persist
|
|
65
|
-
([ref](helpers/plan-reference.md)).
|
|
61
|
+
`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`.
|
|
66
64
|
|
|
67
65
|
**Triage each unknown by resolver** ([ref](helpers/plan-reference.md)): an
|
|
68
66
|
**AFK** unknown (research settles it) is resolved before authoring, never
|
|
@@ -70,12 +68,9 @@ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
|
|
|
70
68
|
operator questions — AFK unknowns are still researched; only HITL unknowns land
|
|
71
69
|
in Key Assumptions, each a decision-made-by-default.
|
|
72
70
|
|
|
73
|
-
**Gate #1** — STOP to confirm the sharpened plan intent
|
|
74
|
-
duplicate-candidate review
|
|
75
|
-
|
|
76
|
-
On a truthy `memoryPoolAdvisory.recommend`, name
|
|
77
|
-
[`/memory-consolidate`](memory-consolidate.md) quoting its `reasons[]`;
|
|
78
|
-
advisory.
|
|
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.
|
|
79
74
|
|
|
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
|
|
@@ -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,43 @@ 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.56.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.55.0...mandrel-v2.56.0) (2026-09-11)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
* audit-to-stories: dedup against a host-supplied issue index, so a sweep on a gh-less host stops re-filing what it already filed ([#5301](https://github.com/dsj1984/mandrel/issues/5301)) ([#5304](https://github.com/dsj1984/mandrel/issues/5304)) ([1b8abf0](https://github.com/dsj1984/mandrel/commit/1b8abf072a05db65e54a63cb8ec6cf6f06bc80ab))
|
|
24
|
+
* audit-to-stories: make Phase 5a filings visible to the next sweep — record the ledger from plan-persist and stamp the audit labels the corpus is listed by ([#5307](https://github.com/dsj1984/mandrel/issues/5307)) ([#5308](https://github.com/dsj1984/mandrel/issues/5308)) ([094ba8a](https://github.com/dsj1984/mandrel/commit/094ba8ae6e2640ba3ac7cb5a4aeb9309aa72e42d))
|
|
25
|
+
* audit-to-stories: record filed Issues in the cross-run ledger, so its suppression branch stops being unreachable ([#5305](https://github.com/dsj1984/mandrel/issues/5305)) ([#5306](https://github.com/dsj1984/mandrel/issues/5306)) ([d5e36ab](https://github.com/dsj1984/mandrel/commit/d5e36ab796f6946bf93d7161efa1470cad8a0a2b))
|
|
26
|
+
* cI-gap intake: script-backed, repo-routed, deduped meta filings that /mandrel-plan can graduate ([#5300](https://github.com/dsj1984/mandrel/issues/5300)) ([#5302](https://github.com/dsj1984/mandrel/issues/5302)) ([37bc287](https://github.com/dsj1984/mandrel/commit/37bc2875ed8c98298afd90c72ece4f318e2c233d))
|
|
27
|
+
|
|
28
|
+
## [2.55.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.54.0...mandrel-v2.55.0) (2026-09-11)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
* 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))
|
|
34
|
+
* 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))
|
|
35
|
+
* 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))
|
|
36
|
+
* 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))
|
|
37
|
+
* 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))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
* 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))
|
|
43
|
+
* 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))
|
|
44
|
+
* 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))
|
|
45
|
+
* 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))
|
|
46
|
+
* 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))
|
|
47
|
+
* **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))
|
|
48
|
+
* 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))
|
|
49
|
+
* 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))
|
|
50
|
+
* 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))
|
|
51
|
+
* **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))
|
|
52
|
+
* **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))
|
|
53
|
+
* **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))
|
|
54
|
+
|
|
18
55
|
## [2.54.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.53.0...mandrel-v2.54.0) (2026-09-09)
|
|
19
56
|
|
|
20
57
|
|
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.56.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
|
}
|