mandrel 2.65.0 → 2.66.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/acceptance-critic.md +5 -5
- package/.agents/agents/auditor.md +17 -18
- package/.agents/agents/plan-critic.md +5 -5
- package/.agents/agents/story-worker.md +5 -5
- package/.agents/docs/execution-reference.md +27 -5
- package/.agents/instructions.md +10 -12
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +3 -2
- package/.agents/rules/git-conventions-reference.md +12 -3
- package/.agents/rules/git-conventions.md +9 -7
- package/.agents/rules/testing-standards.md +8 -7
- package/.agents/runtime-deps.json +1 -1
- package/.agents/scripts/bootstrap.js +94 -89
- package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +78 -78
- package/.agents/scripts/lib/cli/standard-args.js +60 -76
- package/.agents/scripts/lib/cli-args.js +26 -0
- package/.agents/scripts/lib/config/gates/shared.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
- package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
- package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
- package/.agents/scripts/lib/observability/signal-validator.js +17 -5
- package/.agents/scripts/lib/orchestration/code-review.js +22 -0
- package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +23 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +349 -263
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +327 -314
- package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
- package/.agents/scripts/lib/signals/detectors/common.js +63 -51
- package/.agents/scripts/lib/transpile.js +28 -3
- package/.agents/scripts/single-story-close.js +10 -2
- package/.agents/scripts/single-story-confirm-merge.js +267 -238
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
- package/.agents/workflows/audit-architecture.md +5 -4
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-performance.md +10 -10
- package/.agents/workflows/helpers/acceptance-self-eval.md +8 -8
- package/.agents/workflows/helpers/audit-lens-core.md +30 -57
- package/.agents/workflows/helpers/deliver-digest.md +2 -2
- package/.agents/workflows/helpers/deliver-reference.md +3 -1
- package/.agents/workflows/helpers/deliver-story.md +6 -1
- package/.agents/workflows/helpers/parallel-tooling.md +16 -18
- package/.agents/workflows/mandrel-deliver.md +1 -1
- package/.agents/workflows/mandrel-plan.md +6 -5
- package/docs/CHANGELOG.md +26 -0
- package/lib/cli/guarded-sync.js +87 -0
- package/lib/cli/sync-agents.js +9 -92
- package/lib/cli/sync-commands.js +9 -101
- package/package.json +2 -2
|
@@ -27,18 +27,18 @@ Per the core's Scope interpretation:
|
|
|
27
27
|
|
|
28
28
|
## Execution strategy
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Sequential inline execution is the fallback (see the
|
|
30
|
+
Dispatch this lens as one `subagent_type: auditor` call. Fan its resource
|
|
31
|
+
dimensions out across parallel `auditor` subagents (parallel-tooling Rule 3),
|
|
32
|
+
merging under the self-cross-check, only when the operator explicitly asks for
|
|
33
|
+
per-dimension fan-out. Sequential inline execution is the fallback (see the
|
|
34
|
+
core's Execution strategy).
|
|
34
35
|
|
|
35
36
|
> **Measurement is non-mutating, not forbidden.** This lens is read-only with
|
|
36
|
-
> respect to source, but
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
>
|
|
41
|
-
> `.claude/workflows/audit-performance.workflow.js`.
|
|
37
|
+
> respect to source, but the auditor MUST be allowed to *run* measurements. It
|
|
38
|
+
> runs only **non-mutating** commands — profilers, timers, bundle-stat and
|
|
39
|
+
> file-size probes — and never a command that writes source, installs
|
|
40
|
+
> packages, or mutates git state or labels. The one write is the report
|
|
41
|
+
> artifact.
|
|
42
42
|
|
|
43
43
|
## Step 0: Measure before you judge (mandatory)
|
|
44
44
|
|
|
@@ -32,8 +32,8 @@ per-criterion, mid-delivery, and evaluates the actual work product.
|
|
|
32
32
|
authors the Story's verdict, and it covers **every** `acceptance[]` item in
|
|
33
33
|
one file. Which pass is named by the ceremony decision
|
|
34
34
|
(`verdictOwner: 'fresh-critic' | 'inline-self-eval'` from
|
|
35
|
-
`resolveCeremonyForRisk`),
|
|
36
|
-
|
|
35
|
+
`resolveCeremonyForRisk`), which follows the **ceremony profile
|
|
36
|
+
alone**:
|
|
37
37
|
|
|
38
38
|
> ```bash
|
|
39
39
|
> node <main-repo>/.agents/scripts/ceremony-derive.js --story <storyId> --cwd <workCwd>
|
|
@@ -43,9 +43,8 @@ per-criterion, mid-delivery, and evaluates the actual work product.
|
|
|
43
43
|
> classes **for review depth**, and resolves the owner (`mode`, `reason`,
|
|
44
44
|
> `verdictOwner`): **`minimal` / `standard` → `inline`** (the default — you
|
|
45
45
|
> author the verdict yourself), **`strict` → `fresh`** (dispatch the
|
|
46
|
-
> maker-blind critic). The derived level
|
|
47
|
-
>
|
|
48
|
-
> any sensitive path.
|
|
46
|
+
> maker-blind critic). The derived level feeds `review-depth.js`, not
|
|
47
|
+
> this decision; review depth resolves `deep` for any sensitive path.
|
|
49
48
|
|
|
50
49
|
**Never run both**, and never run a preliminary self-assessment before
|
|
51
50
|
dispatching a fresh critic — the redundant pre-pass buys no measurable
|
|
@@ -72,9 +71,10 @@ per-criterion, mid-delivery, and evaluates the actual work product.
|
|
|
72
71
|
> system prompt, no entry-doc @-closure) carrying the maker-blind
|
|
73
72
|
> invariant and the verdict schema standalone. With the kill-switch off
|
|
74
73
|
> (`roleScopedAgents: false`), fall back to
|
|
75
|
-
> `subagent_type: general-purpose`.
|
|
76
|
-
>
|
|
77
|
-
> any harness that carries `Agent` into
|
|
74
|
+
> `subagent_type: general-purpose`. Under sub-agent dispatch this loop
|
|
75
|
+
> runs inside a `story-worker`, so the critic sits at nesting depth 2
|
|
76
|
+
> (depth 1 inline) — supported by any harness that carries `Agent` into
|
|
77
|
+
> sub-agents (Claude Code ≥ 2.1.202).
|
|
78
78
|
|
|
79
79
|
Whichever pass owns it, the verdict:
|
|
80
80
|
+ Inspects the **change set it was handed** — the one `files` list above —
|
|
@@ -118,14 +118,12 @@ dropped finding is indistinguishable from a finding you never wrote.
|
|
|
118
118
|
Use it instead of inventing a below-`Low` word of your own; a finding that
|
|
119
119
|
cannot clear the evidence bar below is **dropped**, not filed as `Info`.
|
|
120
120
|
|
|
121
|
-
## Self-cross-check (
|
|
121
|
+
## Self-cross-check (the false-positive bar) {#self-cross-check}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sequential single-pass path the same false-positive filter the orchestrated
|
|
128
|
-
path's independent adversarial reviewer applies.
|
|
123
|
+
A finding goes in the report only when it clears the bar and the exclusion
|
|
124
|
+
list below. The bar filters and tightens findings; it never invents new ones.
|
|
125
|
+
It is the one false-positive filter every execution path applies — no separate
|
|
126
|
+
adversarial reviewer runs after it.
|
|
129
127
|
|
|
130
128
|
### Per-finding evidence bar (keep or drop)
|
|
131
129
|
|
|
@@ -173,23 +171,18 @@ that rests on one of them:
|
|
|
173
171
|
> delivery shipped and nothing in production ever calls. When a candidate is
|
|
174
172
|
> genuinely one of the exclusions, cite the exclusion and drop it.
|
|
175
173
|
|
|
176
|
-
###
|
|
174
|
+
### Recording the outcome
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
2. Count what you kept (`k`) and what you dropped (`d`).
|
|
181
|
-
3. Record the outcome in the report's **Executive Summary** as a single line:
|
|
176
|
+
Record what you kept (`k`) and dropped (`d`) in the report's **Executive
|
|
177
|
+
Summary** as a single line:
|
|
182
178
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
When `d > 0`, name the dropped findings (title + the bar/exclusion reason)
|
|
188
|
-
in one short list under that line, so the filtering is auditable and never
|
|
189
|
-
silent.
|
|
179
|
+
```text
|
|
180
|
+
Self-cross-check: kept <k> / dropped <d>.
|
|
181
|
+
```
|
|
190
182
|
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
When `d > 0`, name the dropped findings (title + the bar/exclusion reason) in
|
|
184
|
+
one short list under that line, so the filtering is auditable and never
|
|
185
|
+
silent. A lens that keeps every finding still records `dropped 0`.
|
|
193
186
|
|
|
194
187
|
## Severity tally (mandatory, machine-readable) {#severity-tally}
|
|
195
188
|
|
|
@@ -261,55 +254,35 @@ available; every path emits the **identical** report contract (the finding-block
|
|
|
261
254
|
skeleton above), so downstream consumers (`audit-to-stories`) are agnostic to
|
|
262
255
|
which path produced it.
|
|
263
256
|
|
|
264
|
-
1. **
|
|
257
|
+
1. **One auditor per lens (the default).** Dispatch the lens as exactly one
|
|
265
258
|
`subagent_type: auditor` call — the standalone boot context in
|
|
266
259
|
[`../../agents/auditor.md`](../../agents/auditor.md) carries the read-only
|
|
267
260
|
MUSTs, the finding-block skeleton, the severity scale, and the
|
|
268
261
|
self-cross-check bar, so the child needs only the lens's own dimensions to
|
|
269
262
|
run. The subagent returns the **report path plus the Executive Summary**
|
|
270
263
|
(including the self-cross-check line); the parent never needs the full
|
|
271
|
-
findings inline.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
`
|
|
277
|
-
|
|
278
|
-
call **per dimension** in a single turn via
|
|
279
|
-
[`parallel-tooling.md`](parallel-tooling.md) Rule 3, then **merge** the
|
|
264
|
+
findings inline. One auditor reads the repo once; per-dimension agents each
|
|
265
|
+
re-read it, so the single dispatch is the cheap path, not a compromise.
|
|
266
|
+
|
|
267
|
+
- **Per-dimension fan-out (operator request only).** Fan a lens out only
|
|
268
|
+
when the operator's invocation explicitly asks for it. Then dispatch one
|
|
269
|
+
`subagent_type: auditor` call **per dimension** in a single turn via
|
|
270
|
+
[`parallel-tooling.md`](parallel-tooling.md) Rule 3, and **merge** the
|
|
280
271
|
per-dimension findings under this file's self-cross-check (the merge is
|
|
281
|
-
where cross-dimension duplicates and false positives are dropped).
|
|
282
|
-
|
|
272
|
+
where cross-dimension duplicates and false positives are dropped). Never
|
|
273
|
+
fan out on your own judgment of a lens's size.
|
|
274
|
+
- **No nested fan-out.** An auditor never dispatches sub-agents of its own.
|
|
275
|
+
The fan-out, when requested, happens once, at the caller.
|
|
283
276
|
|
|
284
277
|
2. **Sequential inline execution (documented fallback).** When subagent
|
|
285
278
|
dispatch is unavailable, run the lens's steps turn-by-turn in the current
|
|
286
279
|
context exactly as written, ending with the self-cross-check. This changes
|
|
287
280
|
nothing about the report contract.
|
|
288
281
|
|
|
289
|
-
> **Orchestrated dynamic-workflow path (optimization note).** Six lenses ship a
|
|
290
|
-
> saved project workflow at `.claude/workflows/audit-<lens>.workflow.js` that,
|
|
291
|
-
> **when Claude Code dynamic workflows are available** (runtime is Claude Code,
|
|
292
|
-
> `disableWorkflows` unset, version `>= 2.1.154`), fans the dimensions out as
|
|
293
|
-
> parallel read-only subagents and runs an independent adversarial cross-check
|
|
294
|
-
> stage before synthesising the report. It derives its per-dimension prompts
|
|
295
|
-
> from the *lens* markdown at run time — the lens stays the single source of
|
|
296
|
-
> truth. This is a performance optimization over path 1, **not** a separate
|
|
297
|
-
> contract, and it is not covered by the No-Shim / hard-cutover rule in
|
|
298
|
-
> [`../../rules/git-conventions.md`](../../rules/git-conventions.md) because
|
|
299
|
-
> there is one report contract and only the execution strategy varies — the
|
|
300
|
-
> same capability-degradation pattern the protocol endorses for live-docs
|
|
301
|
-
> fallback. **The host owns the choice.** Mandrel ships no in-repo strategy
|
|
302
|
-
> selector and no force-override env var: Claude Code launches the saved
|
|
303
|
-
> workflow when it can, and you get path 1 or 2 above when it cannot.
|
|
304
|
-
> Suppress the orchestrated path with `CLAUDE_CODE_DISABLE_WORKFLOWS=1`
|
|
305
|
-
> or `disableWorkflows: true` in `.claude/settings.json`. On the orchestrated
|
|
306
|
-
> path the analysis subagents are granted only read/search tools (`Read`,
|
|
307
|
-
> `Grep`, `Glob`) — the single write is the final report artifact.
|
|
308
|
-
|
|
309
282
|
## Parallel tooling {#parallel-tooling}
|
|
310
283
|
|
|
311
284
|
When a lens batches independent reads/greps, runs a long shell (a scanner, a
|
|
312
|
-
profiler, a suite time),
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
285
|
+
profiler, a suite time), apply [`parallel-tooling.md`](parallel-tooling.md):
|
|
286
|
+
batch independent reads in one turn (Rule 1) and run long shells via
|
|
287
|
+
`run_in_background` (Rule 2). Rule 3 applies only to the caller of
|
|
288
|
+
an operator-requested per-dimension fan-out — never inside an auditor.
|
|
@@ -68,8 +68,8 @@ same derived level, so the two cannot disagree. A sensitive footprint
|
|
|
68
68
|
therefore buys a **deep review**, not a fresh acceptance critic.
|
|
69
69
|
|
|
70
70
|
> **The ceremony rule, stated once.** The **profile alone** names the verdict
|
|
71
|
-
> owner
|
|
72
|
-
>
|
|
71
|
+
> owner: `minimal` / `standard` → `inline`, `strict` → `fresh`. Nothing else
|
|
72
|
+
> moves it — not the
|
|
73
73
|
> derived change level, not the footprint's sensitivity, and **not the
|
|
74
74
|
> dispatch mode**: an `inline` Story under `strict` still spawns the fresh
|
|
75
75
|
> maker-blind critic, one nesting level shallower than a dispatched one. The
|
|
@@ -78,7 +78,9 @@ but not a cycle.
|
|
|
78
78
|
unblocked it:
|
|
79
79
|
`node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready`.
|
|
80
80
|
Do not poll the label yourself while waiting — the HITL pause is the operator's
|
|
81
|
-
turn, not a slow beat.
|
|
81
|
+
turn, not a slow beat. Before resuming, the operator raises session effort one
|
|
82
|
+
step, and raises effort before switching models
|
|
83
|
+
([effort escalation](../../docs/execution-reference.md#session-effort-and-model)).
|
|
82
84
|
|
|
83
85
|
Each beat re-probes live state: it re-resolves the graph, classifies **done**
|
|
84
86
|
(`agent::done` or a closed issue — including foreign blockers that landed in
|
|
@@ -114,9 +114,14 @@ Do not open the PR or compose a terminal envelope.
|
|
|
114
114
|
serialized against sibling Stories:
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo>
|
|
117
|
+
node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <main-repo> \
|
|
118
|
+
[--worker-tokens <n>]
|
|
118
119
|
```
|
|
119
120
|
|
|
121
|
+
When the host reported a total-token figure for the story-worker's Agent
|
|
122
|
+
dispatch, pass it as `--worker-tokens <n>` — close records it in its
|
|
123
|
+
result's local `telemetry`; omit it when the host reports none.
|
|
124
|
+
|
|
120
125
|
**The whole delivery tail** — gates, PR, merge wait, `agent::done` flip,
|
|
121
126
|
post-land tail in one process. Never background it, never delegate it to a
|
|
122
127
|
child, and never end your turn while it is still running: "close is running"
|
|
@@ -29,15 +29,17 @@ the batch in parallel; serial calls cost N round-trips for no gain.
|
|
|
29
29
|
- **Bounded fan-out:** keep the batch ≤ 10 calls per turn. Larger batches
|
|
30
30
|
blow the context budget and obscure the failure surface if one call errors.
|
|
31
31
|
|
|
32
|
-
## Rule 2 — `run_in_background`
|
|
32
|
+
## Rule 2 — `run_in_background` for long shells
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
A shell command that can outrun the host's synchronous Bash ceiling, or that
|
|
35
|
+
would idle the turn while independent work waits (test suites, installs,
|
|
36
|
+
multi-file lints, `git fetch --all`, container builds), runs with the `Bash`
|
|
37
|
+
tool's `run_in_background: true` flag; its completion notification is the
|
|
38
|
+
signal to proceed. Attach `Monitor` only when you must act on output before
|
|
39
|
+
the command exits.
|
|
39
40
|
|
|
40
|
-
- **Tool primitives:** `Bash(run_in_background: true)`
|
|
41
|
+
- **Tool primitives:** `Bash(run_in_background: true)`; `Monitor` only when
|
|
42
|
+
mid-run output matters.
|
|
41
43
|
- **When:** `npm test`, `npm ci`, full-repo `eslint`/`biome` runs, long
|
|
42
44
|
fetches, anything you would have prefixed with `nohup` in a terminal.
|
|
43
45
|
- **Anti-pattern:** synchronous `Bash` with a 600 000 ms timeout used as a
|
|
@@ -90,17 +92,13 @@ the same shape as Rule 1 but at the sub-agent layer.
|
|
|
90
92
|
## When the rules conflict
|
|
91
93
|
|
|
92
94
|
If a unit of work is both long (Rule 2) and independent (Rule 1 or 3),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
recursive `Agent` fan-out is available to it, so the host does not need to
|
|
101
|
-
micromanage the child's shell **or** dispatch strategy. Mind the depth
|
|
102
|
-
budget and the compounding cost — every nesting level re-pays the
|
|
103
|
-
always-loaded context (see [`instructions.md` § 4](../../instructions.md)).
|
|
95
|
+
dispatch the `Agent` calls in one turn (Rule 3), and **inside** each sub-agent let it apply Rule 2 to its own
|
|
96
|
+
long-running shells. A sub-agent does **not** fan out again on its own
|
|
97
|
+
initiative: every nesting level re-pays the always-loaded context (see
|
|
98
|
+
[`instructions.md` § 4](../../instructions.md)), and the cost compounds with
|
|
99
|
+
depth. The one exception is a dispatch the sub-agent's own workflow names
|
|
100
|
+
explicitly — for example the maker-blind acceptance critic a `strict`-profile
|
|
101
|
+
Story worker spawns — which stays legal at that depth.
|
|
104
102
|
|
|
105
103
|
## Constraints
|
|
106
104
|
|
|
@@ -144,7 +144,7 @@ resume what it names.
|
|
|
144
144
|
|
|
145
145
|
**Reading the outcome.** Each close ends the Story in one schema-validated
|
|
146
146
|
envelope — `landed` | `pending` | `blocked` | `failed`; statuses, exits and
|
|
147
|
-
fields are digest §
|
|
147
|
+
fields are digest § 6. `pending` is **not** a failure — run its `nextCommand`.
|
|
148
148
|
|
|
149
149
|
**Branch model (authoritative).** `story-<id>` → PR → `main` (squash +
|
|
150
150
|
required checks), per digest § 2; dependent Stories land sequentially. The
|
|
@@ -75,11 +75,12 @@ in Key Assumptions, each a decision-made-by-default.
|
|
|
75
75
|
`duplicates[]` is non-empty (planning a duplicate of open work stays the
|
|
76
76
|
operator's call): confirm the sharpened plan intent and settle it. Otherwise
|
|
77
77
|
announce the sharpened intent and the advisory line, and continue to
|
|
78
|
-
authoring.
|
|
79
|
-
`
|
|
80
|
-
`
|
|
81
|
-
invoke it here) —
|
|
82
|
-
never reroutes the run
|
|
78
|
+
authoring. The advisory line names what the envelope surfaced — any
|
|
79
|
+
`duplicates[]` (the stop above), open `intake` rows, a truthy
|
|
80
|
+
`memoryPoolAdvisory.recommend`, a truthy `complexitySignals.uiSurface`
|
|
81
|
+
naming [`/prototype`](prototype.md) (never invoke it here) — as
|
|
82
|
+
**one advisory line** under the gate; the line itself never reroutes the run
|
|
83
|
+
([ref](helpers/plan-reference.md)).
|
|
83
84
|
Under `--yes`, auto-proceed.
|
|
84
85
|
|
|
85
86
|
### 2. Author
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,32 @@ 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.66.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.65.0...mandrel-v2.66.0) (2026-09-26)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
* record per-Story retry causes and per-provider review halts at close ([#5435](https://github.com/dsj1984/mandrel/issues/5435)) ([#5441](https://github.com/dsj1984/mandrel/issues/5441)) ([c7bfd5a](https://github.com/dsj1984/mandrel/commit/c7bfd5ab2be417889f45a85f92145704ac580b88))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
* make the quality instruments fail loudly instead of silently mis-measuring, and reclaim floor slack ([#5444](https://github.com/dsj1984/mandrel/issues/5444)) ([#5452](https://github.com/dsj1984/mandrel/issues/5452)) ([54eb9fd](https://github.com/dsj1984/mandrel/commit/54eb9fd4660a0476740e0f96daae34e7f1fc754f))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Performance
|
|
32
|
+
|
|
33
|
+
* stop tests and CLI start-up from spending real wall-clock on host state and sleeps ([#5445](https://github.com/dsj1984/mandrel/issues/5445)) ([#5451](https://github.com/dsj1984/mandrel/issues/5451)) ([8f74d51](https://github.com/dsj1984/mandrel/commit/8f74d51da489b3cc96a0c23053338a718a68708c))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
* burn down the bootstrap hotspots across CRAP, cyclomatic and dead exports ([#5447](https://github.com/dsj1984/mandrel/issues/5447)) ([#5455](https://github.com/dsj1984/mandrel/issues/5455)) ([be9e666](https://github.com/dsj1984/mandrel/commit/be9e6665011547be9396670f9a7486951ca1cd81))
|
|
39
|
+
* burn down the orchestration and signals complexity outliers ([#5449](https://github.com/dsj1984/mandrel/issues/5449)) ([#5456](https://github.com/dsj1984/mandrel/issues/5456)) ([208cede](https://github.com/dsj1984/mandrel/commit/208cede77cb8186bf479cb6e190b8e2849347bbf))
|
|
40
|
+
* collapse duplicated CLI plumbing: standard-args flag switches and the sync-agents/sync-commands clone ([#5448](https://github.com/dsj1984/mandrel/issues/5448)) ([#5454](https://github.com/dsj1984/mandrel/issues/5454)) ([5df933b](https://github.com/dsj1984/mandrel/commit/5df933b7c567730eae0ed47b172a8ac670d0831e))
|
|
41
|
+
* decompose the single-story-close and confirm-merge hot path ([#5446](https://github.com/dsj1984/mandrel/issues/5446)) ([#5453](https://github.com/dsj1984/mandrel/issues/5453)) ([3b7a5ae](https://github.com/dsj1984/mandrel/commit/3b7a5ae2b7391e16dddfd18d64e7fe4528bdc48c))
|
|
42
|
+
* trim the always-on closure and document the effort policy ([#5437](https://github.com/dsj1984/mandrel/issues/5437)) ([#5439](https://github.com/dsj1984/mandrel/issues/5439)) ([61d3c66](https://github.com/dsj1984/mandrel/commit/61d3c666637d38144cee7abcc7a50f28c332201a))
|
|
43
|
+
|
|
18
44
|
## [2.65.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.64.0...mandrel-v2.65.0) (2026-09-24)
|
|
19
45
|
|
|
20
46
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// lib/cli/guarded-sync.js
|
|
2
|
+
/**
|
|
3
|
+
* Shared body of `mandrel sync-commands` / `mandrel sync-agents`: run a
|
|
4
|
+
* `.agents/scripts/` projector in a child process, forwarding its exit code.
|
|
5
|
+
* Refuses first when `.agents/` does not match the running CLI (version
|
|
6
|
+
* marker, else the `agents-drift` check): `.claude/*` is gitignored, so a
|
|
7
|
+
* stale projection surfaces only when an agent hits a missing module.
|
|
8
|
+
* The marker is read from `cwd()`; `PROJECT_ROOT` only names this CLI's version.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import nodeFs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
import { runAgentsDrift } from './registry.js';
|
|
17
|
+
import { readVersionMarker } from './sync.js';
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
// lib/cli/ → lib/ → project root
|
|
21
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
22
|
+
|
|
23
|
+
function resolveOwnPackageVersion(fsImpl) {
|
|
24
|
+
const parsed = JSON.parse(
|
|
25
|
+
fsImpl.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'),
|
|
26
|
+
);
|
|
27
|
+
return String(parsed.version);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function refusalReason(projectRoot, { fs, ownVersion, checkAgentsDrift, cwd }) {
|
|
31
|
+
const marker = readVersionMarker(projectRoot, fs);
|
|
32
|
+
if (marker) {
|
|
33
|
+
const own = ownVersion ?? resolveOwnPackageVersion(fs);
|
|
34
|
+
if (marker === own) return null;
|
|
35
|
+
return {
|
|
36
|
+
what: `the materialized .agents/ tree is v${marker} but the running CLI is v${own}`,
|
|
37
|
+
fix: 're-materialize .agents/ to the current version',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const drift = (checkAgentsDrift ?? (() => runAgentsDrift({ cwd })))();
|
|
41
|
+
if (drift.ok) return null;
|
|
42
|
+
return {
|
|
43
|
+
what: `.agents/ appears to have drifted from the installed package payload (${drift.detail})`,
|
|
44
|
+
fix: 'restore the materialized .agents/ payload',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {{ command: string, target: string, script: string }} spec
|
|
50
|
+
* @param {string[]} _argv - Unused; reserved for future flags.
|
|
51
|
+
* @param {object} [opts] - Injectable runner, cwd, fs, version and I/O seams.
|
|
52
|
+
*/
|
|
53
|
+
export function runGuardedSync(
|
|
54
|
+
{ command, target, script },
|
|
55
|
+
_argv = [],
|
|
56
|
+
{
|
|
57
|
+
runner = spawnSync,
|
|
58
|
+
cwd = () => process.cwd(),
|
|
59
|
+
fs = nodeFs,
|
|
60
|
+
ownVersion,
|
|
61
|
+
checkAgentsDrift,
|
|
62
|
+
writeErr = (s) => process.stderr.write(s),
|
|
63
|
+
exit = (code) => process.exit(code),
|
|
64
|
+
} = {},
|
|
65
|
+
) {
|
|
66
|
+
const refusal = refusalReason(cwd(), {
|
|
67
|
+
fs,
|
|
68
|
+
ownVersion,
|
|
69
|
+
checkAgentsDrift,
|
|
70
|
+
cwd,
|
|
71
|
+
});
|
|
72
|
+
if (refusal) {
|
|
73
|
+
writeErr(
|
|
74
|
+
`mandrel ${command}: ${refusal.what} — refusing to project ${target} from a mismatched tree.\n` +
|
|
75
|
+
` → Run \`mandrel sync\` to ${refusal.fix}, then re-run.\n`,
|
|
76
|
+
);
|
|
77
|
+
exit(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const syncScript = path.join(PROJECT_ROOT, '.agents', 'scripts', script);
|
|
81
|
+
const result = runner(process.execPath, [syncScript], {
|
|
82
|
+
stdio: 'inherit',
|
|
83
|
+
env: process.env,
|
|
84
|
+
});
|
|
85
|
+
const exitCode = result.status ?? 1;
|
|
86
|
+
if (exitCode !== 0) exit(exitCode);
|
|
87
|
+
}
|
package/lib/cli/sync-agents.js
CHANGED
|
@@ -1,97 +1,14 @@
|
|
|
1
1
|
// lib/cli/sync-agents.js
|
|
2
|
-
/**
|
|
3
|
-
* `mandrel sync-agents`: project `.agents/agents/` into `.claude/agents/` via
|
|
4
|
-
* `.agents/scripts/sync-claude-agents.js`. Exact sibling of
|
|
5
|
-
* `sync-commands.js` — same child-process delegation, same marker-gated
|
|
6
|
-
* refusal and anchor rule (see that module's doc).
|
|
7
|
-
*/
|
|
2
|
+
/** `mandrel sync-agents`: project `.agents/agents/` into `.claude/agents/`. */
|
|
8
3
|
|
|
9
|
-
import {
|
|
10
|
-
import nodeFs from 'node:fs';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { runGuardedSync } from './guarded-sync.js';
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
const SPEC = {
|
|
7
|
+
command: 'sync-agents',
|
|
8
|
+
target: '.claude/agents/',
|
|
9
|
+
script: 'sync-claude-agents.js',
|
|
10
|
+
};
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
20
|
-
const SYNC_SCRIPT = path.join(
|
|
21
|
-
PROJECT_ROOT,
|
|
22
|
-
'.agents',
|
|
23
|
-
'scripts',
|
|
24
|
-
'sync-claude-agents.js',
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {typeof nodeFs} fsImpl
|
|
29
|
-
* @returns {string}
|
|
30
|
-
*/
|
|
31
|
-
function resolveOwnPackageVersion(fsImpl) {
|
|
32
|
-
const parsed = JSON.parse(
|
|
33
|
-
fsImpl.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'),
|
|
34
|
-
);
|
|
35
|
-
return String(parsed.version);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {string[]} _argv - Unused; reserved for future flags.
|
|
40
|
-
* @param {{
|
|
41
|
-
* runner?: typeof spawnSync,
|
|
42
|
-
* cwd?: () => string,
|
|
43
|
-
* fs?: typeof nodeFs,
|
|
44
|
-
* ownVersion?: string,
|
|
45
|
-
* checkAgentsDrift?: () => { ok: boolean, detail: string },
|
|
46
|
-
* writeErr?: (s: string) => void,
|
|
47
|
-
* exit?: (code: number) => void,
|
|
48
|
-
* }} [opts]
|
|
49
|
-
* @returns {void}
|
|
50
|
-
*/
|
|
51
|
-
export default function run(
|
|
52
|
-
_argv = [],
|
|
53
|
-
{
|
|
54
|
-
runner = spawnSync,
|
|
55
|
-
cwd = () => process.cwd(),
|
|
56
|
-
fs = nodeFs,
|
|
57
|
-
ownVersion,
|
|
58
|
-
checkAgentsDrift,
|
|
59
|
-
writeErr = (s) => process.stderr.write(s),
|
|
60
|
-
exit = (code) => process.exit(code),
|
|
61
|
-
} = {},
|
|
62
|
-
) {
|
|
63
|
-
const projectRoot = cwd();
|
|
64
|
-
const resolvedOwnVersion = ownVersion ?? resolveOwnPackageVersion(fs);
|
|
65
|
-
const marker = readVersionMarker(projectRoot, fs);
|
|
66
|
-
|
|
67
|
-
if (marker) {
|
|
68
|
-
if (marker !== resolvedOwnVersion) {
|
|
69
|
-
writeErr(
|
|
70
|
-
`mandrel sync-agents: the materialized .agents/ tree is v${marker} but the running CLI is v${resolvedOwnVersion} — refusing to project .claude/agents/ from a mismatched tree.\n` +
|
|
71
|
-
' → Run `mandrel sync` to re-materialize .agents/ to the current version, then re-run.\n',
|
|
72
|
-
);
|
|
73
|
-
exit(1);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
const drift = (checkAgentsDrift ?? (() => runAgentsDrift({ cwd })))();
|
|
78
|
-
if (!drift.ok) {
|
|
79
|
-
writeErr(
|
|
80
|
-
`mandrel sync-agents: .agents/ appears to have drifted from the installed package payload (${drift.detail}) — refusing to project .claude/agents/ from a mismatched tree.\n` +
|
|
81
|
-
' → Run `mandrel sync` to restore the materialized .agents/ payload, then re-run.\n',
|
|
82
|
-
);
|
|
83
|
-
exit(1);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const result = runner(process.execPath, [SYNC_SCRIPT], {
|
|
89
|
-
stdio: 'inherit',
|
|
90
|
-
env: process.env,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const exitCode = result.status ?? 1;
|
|
94
|
-
if (exitCode !== 0) {
|
|
95
|
-
exit(exitCode);
|
|
96
|
-
}
|
|
12
|
+
export default function run(argv, opts) {
|
|
13
|
+
runGuardedSync(SPEC, argv, opts);
|
|
97
14
|
}
|