mandrel 1.69.0 → 1.71.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/README.md +7 -7
- package/.agents/docs/SDLC.md +4 -5
- package/.agents/docs/configuration.md +9 -9
- package/.agents/docs/workflows.md +4 -6
- package/.agents/schemas/qa-finding.schema.json +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +79 -0
- package/.agents/scripts/audit-labels-bootstrap.js +52 -30
- package/.agents/scripts/audit-to-stories.js +54 -0
- package/.agents/scripts/bootstrap.js +13 -3
- package/.agents/scripts/generate-config-docs.js +189 -94
- package/.agents/scripts/lib/audit-suite/findings.js +0 -4
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +99 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +13 -5
- package/.agents/scripts/lib/baseline-snapshot.js +163 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +0 -4
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +1 -1
- package/.agents/scripts/lib/config/baselines.js +0 -20
- package/.agents/scripts/lib/config/defaults.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +0 -31
- package/.agents/scripts/lib/config-resolver.js +1 -1
- package/.agents/scripts/lib/crap-utils.js +281 -0
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -2
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -84
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +3 -4
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -4
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +101 -70
- package/.agents/scripts/lib/orchestration/spec-renderer.js +42 -14
- package/.agents/scripts/lib/orchestration/ticket-lease.js +3 -0
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +1 -1
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +1 -1
- package/.agents/scripts/lib/story-body/story-body.js +110 -65
- package/.agents/scripts/lib/test-tiers.js +13 -7
- package/.agents/scripts/lib/wave-runner/tick.js +177 -53
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +226 -0
- package/.agents/scripts/mandrel-update-preflight.js +235 -0
- package/.agents/scripts/providers/github/issues.js +48 -0
- package/.agents/scripts/providers/github.js +1 -0
- package/.agents/scripts/sync-agentrc.js +2 -2
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +3 -3
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +4 -4
- package/.agents/workflows/git-deliver.md +298 -0
- package/.agents/workflows/helpers/epic-testing.md +6 -6
- package/.agents/workflows/helpers/{agents-sync-config.md → mandrel-sync-config.md} +5 -4
- package/.agents/workflows/{agents-update.md → mandrel-update.md} +210 -33
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/{qa-run-harness.md → qa-run.md} +5 -5
- package/README.md +40 -0
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/registry.js +49 -6
- package/lib/cli/update.js +335 -332
- package/package.json +16 -11
- package/.agents/workflows/git-commit-all.md +0 -15
- package/.agents/workflows/git-pr-all.md +0 -281
- package/.agents/workflows/git-push.md +0 -63
|
@@ -6,12 +6,12 @@ description: QA Epic-testing workflow — ingest the agent-driven QA harness swe
|
|
|
6
6
|
|
|
7
7
|
> **Helper module.** Not a slash command. Invoked from the QA gate during
|
|
8
8
|
> `/deliver` or directly by an operator when the Epic-testing ticket
|
|
9
|
-
> needs refreshed evidence. For ad-hoc acceptance runs use `/qa-run
|
|
9
|
+
> needs refreshed evidence. For ad-hoc acceptance runs use `/qa-run` —
|
|
10
10
|
> this helper owns the Epic-evidence ticket lifecycle on top of it.
|
|
11
11
|
|
|
12
12
|
Gather and attach the acceptance-suite evidence that gates Epic closure. The
|
|
13
13
|
evidence artifact is the **agent-driven QA harness sweep report** produced by
|
|
14
|
-
`/qa-run
|
|
14
|
+
`/qa-run` (scenario pass/fail/blocked totals plus structured
|
|
15
15
|
findings), **not** a hand-ticked markdown checklist.
|
|
16
16
|
|
|
17
17
|
> **When to run**: During the QA phase of an Epic, after all Story merges
|
|
@@ -38,13 +38,13 @@ findings), **not** a hand-ticked markdown checklist.
|
|
|
38
38
|
|
|
39
39
|
## Step 1 — Execute the QA Harness Sweep
|
|
40
40
|
|
|
41
|
-
Invoke `/qa-run
|
|
41
|
+
Invoke `/qa-run` with the chosen selector:
|
|
42
42
|
|
|
43
43
|
```text
|
|
44
|
-
/qa-run
|
|
44
|
+
/qa-run "tag:@smoke and @risk-high"
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
The `/qa-run
|
|
47
|
+
The `/qa-run` workflow (`.agents/workflows/qa-run.md`) owns the
|
|
48
48
|
execution mechanics — `qa` contract resolution, scenario selection, browser
|
|
49
49
|
navigation, and finding capture. This workflow consumes its output.
|
|
50
50
|
|
|
@@ -117,7 +117,7 @@ and deleting the checklist in the same change.
|
|
|
117
117
|
|
|
118
118
|
## Cross-References
|
|
119
119
|
|
|
120
|
-
- Execution mechanics: `.agents/workflows/qa-run
|
|
120
|
+
- Execution mechanics: `.agents/workflows/qa-run.md`.
|
|
121
121
|
- Scenario authoring rules: `.agents/rules/gherkin-standards.md`.
|
|
122
122
|
- Runner / fixture / trace conventions:
|
|
123
123
|
`.agents/skills/stack/qa/playwright-bdd/SKILL.md`.
|
|
@@ -5,15 +5,16 @@ description: >-
|
|
|
5
5
|
surfacing redundant keys (project values that already match framework
|
|
6
6
|
defaults). The runtime layers defaults at read time, so the helper never
|
|
7
7
|
auto-fills optional keys from the template. Invoked by reference from
|
|
8
|
-
/
|
|
8
|
+
/mandrel-update.
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
# mandrel-sync-config (helper)
|
|
12
12
|
|
|
13
13
|
> **Not a slash command.** Lives under `.agents/workflows/helpers/` so it is
|
|
14
14
|
> not projected into the mandrel plugin command tree. Invoked by reference from
|
|
15
|
-
> [`/
|
|
16
|
-
> shipped as `/agents-sync-config
|
|
15
|
+
> [`/mandrel-update`](../mandrel-update.md) after a framework update; previously
|
|
16
|
+
> shipped as the standalone `/agents-sync-config` command (later demoted to a
|
|
17
|
+
> helper, then renamed alongside `/mandrel-update`). The reconciliation runs as part of the
|
|
17
18
|
> `mandrel update` upgrade path (bump → sync → migrate → doctor).
|
|
18
19
|
>
|
|
19
20
|
> **Configuration reference.** The full set of configurable keys, defaults,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: >-
|
|
3
|
-
npm-era upgrade wraparound for a Mandrel consumer. Runs `mandrel update`
|
|
3
|
+
npm-era upgrade wraparound for a Mandrel consumer. Runs `npx mandrel update`
|
|
4
4
|
(resolve newest published version → install → re-materialize `.agents/` →
|
|
5
5
|
migrate → doctor → surface changelog) as the single mechanical step, then
|
|
6
6
|
walks the operator through the judgment wraparound the CLI deliberately
|
|
@@ -10,12 +10,12 @@ description: >-
|
|
|
10
10
|
stage + commit the staged lockfile bump.
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
# /
|
|
13
|
+
# /mandrel-update
|
|
14
14
|
|
|
15
15
|
> **Upgrade owner.** The mechanical upgrade is owned end to end by the
|
|
16
16
|
> [`mandrel update`](../../lib/cli/update.js) CLI under the npm distribution
|
|
17
17
|
> model (`mandrel`, #3436/#3437). This workflow wraps that CLI: it
|
|
18
|
-
> runs `mandrel update`, then walks the operator through the
|
|
18
|
+
> runs `npx mandrel update`, then walks the operator through the
|
|
19
19
|
> **distribution-agnostic judgment steps** the CLI deliberately does **not**
|
|
20
20
|
> perform — config reconciliation, the Epic #1386 quality-gate installs, the
|
|
21
21
|
> permission-allowlist refresh, the consumer-side changelog reconciliation,
|
|
@@ -23,7 +23,7 @@ description: >-
|
|
|
23
23
|
|
|
24
24
|
## Overview
|
|
25
25
|
|
|
26
|
-
`/
|
|
26
|
+
`/mandrel-update` advances the consumer repo to the newest published
|
|
27
27
|
`mandrel` release, re-materializes `.agents/`, and regenerates the
|
|
28
28
|
flat `.claude/commands/` tree (invoked as `/<name>`) against the new workflow
|
|
29
29
|
set — then reconciles the consumer's own config, harness allowlist, and
|
|
@@ -60,22 +60,105 @@ The upgrade contract:
|
|
|
60
60
|
> **Persona**: `devops-engineer` · **Skills**:
|
|
61
61
|
> `core/ci-cd-and-automation`, `core/documentation-and-adrs`
|
|
62
62
|
|
|
63
|
+
**Invocation form.** In a consumer project `mandrel` is a local
|
|
64
|
+
devDependency at `node_modules/.bin/mandrel` and is **not** on `PATH`, so a
|
|
65
|
+
bare `mandrel <subcommand>` fails with `command not found` before any of the
|
|
66
|
+
hardened CLI logic ([`lib/cli/update.js`](../../lib/cli/update.js)) runs.
|
|
67
|
+
Every **runnable** command in this workflow therefore uses the
|
|
68
|
+
`npx mandrel <subcommand>` form, matching [`README.md`](../../README.md).
|
|
69
|
+
Prose that names the CLI as a noun (e.g. "`mandrel update`'s sync step")
|
|
70
|
+
refers to the binary by name, not as a command to type — run it via the form
|
|
71
|
+
Step 0 selects. The exception is a project where `mandrel` is installed
|
|
72
|
+
**globally**: there, the bare form works and Step 0 says so.
|
|
73
|
+
|
|
74
|
+
## Step 0 — Detect the install state and pick the invocation form
|
|
75
|
+
|
|
76
|
+
Before running the updater, detect how `mandrel` resolves in this project and
|
|
77
|
+
route to the matching invocation form. Run from the consumer repo root:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. Globally installed and on PATH?
|
|
81
|
+
command -v mandrel
|
|
82
|
+
# 2. Installed as a local devDependency?
|
|
83
|
+
ls node_modules/.bin/mandrel 2>/dev/null || npm ls mandrel
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Three real states, three routes:
|
|
87
|
+
|
|
88
|
+
| State | Detection | Invocation form |
|
|
89
|
+
| --------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
90
|
+
| **Global install (on `PATH`)** | `command -v mandrel` prints a path | Bare `mandrel <subcommand>` works. |
|
|
91
|
+
| **Local devDependency (not on `PATH`)** | `command -v mandrel` is empty; `node_modules/.bin/mandrel` exists | Use `npx mandrel <subcommand>` (resolves the local bin). |
|
|
92
|
+
| **Not installed** | `command -v mandrel` empty **and** `node_modules/.bin/mandrel` absent | Run `npm install -D mandrel` first, then `npx mandrel`. |
|
|
93
|
+
|
|
94
|
+
The common consumer case is **local devDependency** — `npx mandrel` is the
|
|
95
|
+
default form the rest of this workflow uses. On a global install you may drop
|
|
96
|
+
the `npx` prefix; on a fresh project, install the package first. The `npx`
|
|
97
|
+
form is harmless on a global install too (it prefers the local bin and falls
|
|
98
|
+
back to a one-off fetch), so when unsure, use `npx mandrel`.
|
|
99
|
+
|
|
100
|
+
## Step 0.5 — First-run preflight (before any bump)
|
|
101
|
+
|
|
102
|
+
Before running the updater, run the first-run preflight. It catches three
|
|
103
|
+
day-0 failure modes — **wrong project**, a **dirty git index**, and being
|
|
104
|
+
**offline** — before `npx mandrel update` bumps anything. Run from the
|
|
105
|
+
consumer repo root:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
node .agents/scripts/mandrel-update-preflight.js
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The preflight runs three checks and prints a JSON envelope
|
|
112
|
+
(`{ ok, blocked, findings[] }`) on stdout plus a human-readable report:
|
|
113
|
+
|
|
114
|
+
| Check | Severity | What it verifies |
|
|
115
|
+
| ------------------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| **consumer-shape** | **blocker** (exit 2) | `package.json` lists `mandrel` as a dependency **and** a `.agents/` directory exists. Hard-stops in the framework repo itself or any non-consumer project. |
|
|
117
|
+
| **dirty-index** | warn-only | The git index has no pre-existing staged changes. `mandrel update` leaves the lockfile staged and Step 5 would otherwise sweep unrelated staged files into the commit. |
|
|
118
|
+
| **offline** | warn-only | The npm registry is reachable (`npm ping`), so the version probe in Step 1 will not fail with a confusing offline error. |
|
|
119
|
+
|
|
120
|
+
Severity follows framework preflight conventions (cf.
|
|
121
|
+
[`epic-deliver-preflight.js`](../scripts/epic-deliver-preflight.js), the
|
|
122
|
+
`story-close` preflight): the **consumer-shape** check is a hard stop — the
|
|
123
|
+
script exits `2` and you MUST NOT proceed until it is resolved; **dirty-index**
|
|
124
|
+
and **offline** are warn-only and never block the run.
|
|
125
|
+
|
|
126
|
+
Routing:
|
|
127
|
+
|
|
128
|
+
- **Exit 0, `ok: true`** — all checks passed; proceed to Step 1.
|
|
129
|
+
- **Exit 0 with warnings** (`blocked: false`, non-empty `findings[]`) —
|
|
130
|
+
review the warnings. For **dirty-index**, unstage unrelated changes
|
|
131
|
+
(`git restore --staged <path>`) so they are not swept into the
|
|
132
|
+
`chore: update mandrel` commit. For **offline**, restore connectivity
|
|
133
|
+
before the version probe. Then proceed.
|
|
134
|
+
- **Exit 2, `blocked: true`** — the consumer-shape check failed. Stop. You
|
|
135
|
+
are not in a Mandrel consumer project (wrong directory, the framework repo
|
|
136
|
+
itself, or a project that never ran `mandrel sync`). `cd` into the consumer
|
|
137
|
+
repo, or bootstrap one with `npm install -D mandrel && npx mandrel sync`,
|
|
138
|
+
then re-run the preflight.
|
|
139
|
+
|
|
140
|
+
> **Scope.** The preflight is a **workflow-layer** guard; it deliberately
|
|
141
|
+
> lives outside [`lib/cli/update.js`](../../lib/cli/update.js), which stays
|
|
142
|
+
> git-free and side-effect-scoped. It composes cleanly with the Step 0
|
|
143
|
+
> invocation-form detection above — Step 0 picks *how* to call the updater;
|
|
144
|
+
> Step 0.5 verifies it is *safe* to call it at all.
|
|
145
|
+
|
|
63
146
|
## Step 1 — Run the updater
|
|
64
147
|
|
|
65
148
|
Preview first, then apply. From the consumer repo root:
|
|
66
149
|
|
|
67
150
|
```bash
|
|
68
|
-
mandrel update --dry-run
|
|
69
|
-
mandrel update
|
|
151
|
+
npx mandrel update --dry-run
|
|
152
|
+
npx mandrel update
|
|
70
153
|
```
|
|
71
154
|
|
|
72
|
-
`mandrel update --dry-run` resolves the newest published version and
|
|
73
|
-
the ordered step plan (`npm-update → runSync → runMigrations → doctor →
|
|
155
|
+
`npx mandrel update --dry-run` resolves the newest published version and
|
|
156
|
+
prints the ordered step plan (`npm-update → runSync → runMigrations → doctor →
|
|
74
157
|
surface changelog`) without invoking any effectful seam — no dependency bump,
|
|
75
158
|
no sync, no migrations, no doctor, nothing written. Read the planned target
|
|
76
159
|
version before applying.
|
|
77
160
|
|
|
78
|
-
`mandrel update` (no flags) runs the live cycle:
|
|
161
|
+
`npx mandrel update` (no flags) runs the live cycle:
|
|
79
162
|
|
|
80
163
|
1. **Resolve target** — the newest published `mandrel` version (via
|
|
81
164
|
the daily freshness cache in `temp/version-check.json`) and the currently
|
|
@@ -130,6 +213,86 @@ mandrel update — planned upgrade v1.44.0 → v1.46.0
|
|
|
130
213
|
Dry run: no files written, no dependency bumped.
|
|
131
214
|
```
|
|
132
215
|
|
|
216
|
+
## Step 2.5 — Partial-upgrade recovery (**blocker — resolve before Step 5**)
|
|
217
|
+
|
|
218
|
+
`mandrel update` runs its post-install phases in order — **install** →
|
|
219
|
+
**sync** → **sync-commands** → **migrate** → **doctor** — and the install
|
|
220
|
+
phase bumps `package.json` / `package-lock.json` and leaves the change
|
|
221
|
+
**staged on disk** *before* any of the later phases run. By deliberate
|
|
222
|
+
design the CLI **never rolls back the install on failure** (the lockfile
|
|
223
|
+
bump is left staged for the operator — see the Out-of-Scope note in
|
|
224
|
+
[`lib/cli/update.js`](../../lib/cli/update.js)). So when a post-install
|
|
225
|
+
phase exits non-zero, you land in a **partially-upgraded state**:
|
|
226
|
+
|
|
227
|
+
- The lockfile bump to the new version is **already staged**, *and*
|
|
228
|
+
- `.agents/` may be **half-materialized** (sync failed midway), the flat
|
|
229
|
+
`.claude/commands/` tree may be **out of sync** (sync-commands failed), a
|
|
230
|
+
version-keyed migration may have **partially applied** (migrate failed), or
|
|
231
|
+
the post-upgrade state failed validation (doctor failed).
|
|
232
|
+
|
|
233
|
+
This is the dangerous case the whole workflow exists to guard: the operator
|
|
234
|
+
is now **one `git commit` away** (Step 5) from recording a broken
|
|
235
|
+
half-upgrade as "done". `mandrel update` prints the per-phase manual remedy
|
|
236
|
+
to **stderr**, but a line buried in stderr is easy to scroll past and commit
|
|
237
|
+
right over. **Treat any post-install phase failure as an explicit blocker:
|
|
238
|
+
do not proceed to Step 5 (commit) until the failed phase is recovered and a
|
|
239
|
+
clean re-run reports success.**
|
|
240
|
+
|
|
241
|
+
When `npx mandrel update` exits non-zero, identify which phase failed (the
|
|
242
|
+
CLI's stderr names it) and run the matching manual remedy from the consumer
|
|
243
|
+
repo root. These commands match the hint strings
|
|
244
|
+
[`lib/cli/update.js`](../../lib/cli/update.js) emits verbatim — it is the
|
|
245
|
+
single source of truth, kept in lockstep with this table by the
|
|
246
|
+
`mandrel-update-recovery-drift` contract test
|
|
247
|
+
([`tests/bootstrap/mandrel-update-recovery-drift.test.js`](../../tests/bootstrap/mandrel-update-recovery-drift.test.js)):
|
|
248
|
+
|
|
249
|
+
| Failed phase | Manual remedy |
|
|
250
|
+
| ----------------- | ------------------------------------------------------- |
|
|
251
|
+
| **sync** | `npx mandrel sync` |
|
|
252
|
+
| **sync-commands** | `npm run sync:commands` |
|
|
253
|
+
| **migrate** | `npx mandrel migrate --from <cur> --to <target>` |
|
|
254
|
+
| **doctor** | `npx mandrel doctor` (then apply the per-check remedies) |
|
|
255
|
+
|
|
256
|
+
The exact stderr the CLI prints per failed phase — quoted verbatim from
|
|
257
|
+
[`lib/cli/update.js`](../../lib/cli/update.js) so the table above can never
|
|
258
|
+
drift from what the operator actually sees:
|
|
259
|
+
|
|
260
|
+
- **sync** — the .agents/ materialization may be incomplete. Run `mandrel
|
|
261
|
+
sync` manually to restore.
|
|
262
|
+
- **sync-commands** — the .claude/commands/ tree may be out of sync. Run `npm
|
|
263
|
+
run sync:commands` manually to restore.
|
|
264
|
+
- **migrate** — some migrations for v\<cur\> → v\<target\> may not have
|
|
265
|
+
applied. Run `mandrel migrate --from <cur> --to <target>` manually to retry.
|
|
266
|
+
- **doctor** — upgraded to v\<target\> but doctor reported failures. → Run
|
|
267
|
+
`mandrel doctor` for remedies.
|
|
268
|
+
|
|
269
|
+
> **`<cur>` / `<target>`** are the installed and resolved-newest version
|
|
270
|
+
> strings the CLI printed in Step 1 (e.g. `--from 1.44.0 --to 1.46.0`).
|
|
271
|
+
> Substitute the real values the failing run reported.
|
|
272
|
+
|
|
273
|
+
Recovery sequence:
|
|
274
|
+
|
|
275
|
+
1. **Run the matching remedy** for the failed phase from the table above.
|
|
276
|
+
2. **Re-run `npx mandrel update`.** It is idempotent — the install already
|
|
277
|
+
landed, so a clean re-run short-circuits the bump and re-drives the
|
|
278
|
+
post-install phases. Repeat the per-phase remedy until the run reports
|
|
279
|
+
`✅ Updated to v<target>. The lockfile bump is staged for review.` (or
|
|
280
|
+
`✅ Already up to date`).
|
|
281
|
+
3. **Only then proceed** to Step 3. The staged lockfile bump is safe to
|
|
282
|
+
commit (Step 5) once — and only once — the post-install phases have all
|
|
283
|
+
gone green.
|
|
284
|
+
|
|
285
|
+
> **Why not auto-rollback / `mandrel update --resume`?** A `--resume` flag
|
|
286
|
+
> that re-enters the cycle at the failed phase was **evaluated and
|
|
287
|
+
> deferred** (Story #4172, Out of Scope). The per-phase manual remedies
|
|
288
|
+
> above fully cover recovery: each failed phase has an exact, idempotent
|
|
289
|
+
> command, and re-running `npx mandrel update` already short-circuits the
|
|
290
|
+
> completed install and re-drives the remaining phases — so a dedicated
|
|
291
|
+
> resume entrypoint would add a parallel code path without covering any
|
|
292
|
+
> recovery case the manual remedies miss. If a future change makes the
|
|
293
|
+
> phases expensive enough that re-driving completed ones is wasteful,
|
|
294
|
+
> revisit `--resume` then; today it is unnecessary.
|
|
295
|
+
|
|
133
296
|
## Step 3 — Reconcile `.agentrc.json` against the new defaults
|
|
134
297
|
|
|
135
298
|
A framework bump can add or reshape fields in
|
|
@@ -156,7 +319,7 @@ The helper (Story #1995) is **default-aware** and **read-only**:
|
|
|
156
319
|
modified.
|
|
157
320
|
|
|
158
321
|
Full procedure reference:
|
|
159
|
-
[`helpers/
|
|
322
|
+
[`helpers/mandrel-sync-config.md`](helpers/mandrel-sync-config.md).
|
|
160
323
|
|
|
161
324
|
If the helper prints `No changes required` with no advisories, the config
|
|
162
325
|
is already in sync — carry on. If it lists `[REDUNDANT]` rows, you may
|
|
@@ -177,22 +340,18 @@ framework version sees `no-change` everywhere here.
|
|
|
177
340
|
Run from the consumer repo root:
|
|
178
341
|
|
|
179
342
|
```bash
|
|
180
|
-
node -
|
|
181
|
-
Promise.all([
|
|
182
|
-
import('./.agents/scripts/lib/bootstrap/quality-bootstrap.js'),
|
|
183
|
-
import('./.agents/scripts/lib/bootstrap/baselines-layout-migration.js'),
|
|
184
|
-
]).then(([qb, bm]) => {
|
|
185
|
-
const root = process.cwd();
|
|
186
|
-
const quality = qb.applyQualityBootstrap({ projectRoot: root });
|
|
187
|
-
const baselines = bm.migrateBaselinesLayout({
|
|
188
|
-
baselinesDir: require('node:path').join(root, 'baselines'),
|
|
189
|
-
repoRoot: root,
|
|
190
|
-
});
|
|
191
|
-
console.log(JSON.stringify({ quality, baselines }, null, 2));
|
|
192
|
-
});
|
|
193
|
-
"
|
|
343
|
+
node .agents/scripts/apply-quality-bootstrap.js
|
|
194
344
|
```
|
|
195
345
|
|
|
346
|
+
The script (Story #4171) replaced the prior inline `node -e` heredoc — a
|
|
347
|
+
shell-fragile, untested block that silently drifted whenever the two helper
|
|
348
|
+
signatures moved (see
|
|
349
|
+
[`apply-quality-bootstrap.js`](../scripts/apply-quality-bootstrap.js)). It
|
|
350
|
+
runs the same two installs in order against `process.cwd()` —
|
|
351
|
+
`applyQualityBootstrap` then `migrateBaselinesLayout` — and prints the same
|
|
352
|
+
`{ quality, baselines }` JSON envelope to stdout. It is idempotent: a second
|
|
353
|
+
run is a no-op beyond reporting `no-change` on every install path.
|
|
354
|
+
|
|
196
355
|
The four `quality-bootstrap` outcomes:
|
|
197
356
|
|
|
198
357
|
1. **`helper`** — copies
|
|
@@ -233,7 +392,7 @@ rest of the per-Epic temp tree):
|
|
|
233
392
|
contract for the framework.
|
|
234
393
|
|
|
235
394
|
A second run produces `no-change` on every install path, which is the
|
|
236
|
-
guarantee `
|
|
395
|
+
guarantee `mandrel-update`'s idempotence contract requires.
|
|
237
396
|
|
|
238
397
|
## Step 3.6 — Refresh the harness permission allowlist (`/fewer-permission-prompts`)
|
|
239
398
|
|
|
@@ -272,7 +431,7 @@ auto-applied change:
|
|
|
272
431
|
- Apply the accepted subset by editing `.claude/settings.json` and
|
|
273
432
|
stage it alongside the version bump in Step 5.
|
|
274
433
|
|
|
275
|
-
The maintenance cadence is **once per `/
|
|
434
|
+
The maintenance cadence is **once per `/mandrel-update` invocation** —
|
|
276
435
|
the same operator who just ran `mandrel update` is the one with the
|
|
277
436
|
freshest transcript context to review the proposed allowlist
|
|
278
437
|
diff. Skipping the step is fine when the bump introduces no new
|
|
@@ -329,6 +488,14 @@ response."
|
|
|
329
488
|
|
|
330
489
|
## Step 5 — Commit the bump
|
|
331
490
|
|
|
491
|
+
> **Blocker check before you commit.** The staged lockfile bump is only safe
|
|
492
|
+
> to commit once every post-install phase has gone green. If `npx mandrel
|
|
493
|
+
> update` exited non-zero, you are in a partially-upgraded state — resolve it
|
|
494
|
+
> via [Step 2.5 — Partial-upgrade recovery](#step-25--partial-upgrade-recovery-blocker--resolve-before-step-5)
|
|
495
|
+
> (run the per-phase remedy, re-run the updater to success) **before** running
|
|
496
|
+
> the `git commit` below. Committing over a half-upgrade records a broken
|
|
497
|
+
> state as "done".
|
|
498
|
+
|
|
332
499
|
`mandrel update` leaves the dependency bump **staged on disk** but never
|
|
333
500
|
commits. After reviewing the surfaced changelog, any `.agentrc.json`
|
|
334
501
|
reconciliation diff from Step 3, the `.claude/settings.json` allowlist
|
|
@@ -359,7 +526,7 @@ no-op.
|
|
|
359
526
|
> distribution `.agents/` is a
|
|
360
527
|
> materialized directory rebuilt from the installed package — whether the
|
|
361
528
|
> consumer commits the regenerated `.agents/` tree, or treats it as a
|
|
362
|
-
> gitignored install artifact rebuilt by `mandrel sync`, depends on the
|
|
529
|
+
> gitignored install artifact rebuilt by `npx mandrel sync`, depends on the
|
|
363
530
|
> consumer's own vendoring policy. Stage the `.agents/` / `.claude/`
|
|
364
531
|
> changes here only if the project commits its materialized tree.
|
|
365
532
|
|
|
@@ -367,21 +534,31 @@ no-op.
|
|
|
367
534
|
|
|
368
535
|
- **`doctor reported failures: …`** — the dependency bumped and `.agents/`
|
|
369
536
|
re-materialized, but a doctor check failed (and the run exited
|
|
370
|
-
non-zero).
|
|
371
|
-
bump is already staged
|
|
372
|
-
|
|
373
|
-
|
|
537
|
+
non-zero). This is one shape of the **partial-upgrade** failure mode —
|
|
538
|
+
the lockfile bump is already staged, so it is a **blocker** you MUST
|
|
539
|
+
resolve before the commit step (see
|
|
540
|
+
[Step 2.5 — Partial-upgrade recovery](#step-25--partial-upgrade-recovery-blocker--resolve-before-step-5)).
|
|
541
|
+
Run `npx mandrel doctor` for the per-check remedies; fix the doctor
|
|
542
|
+
finding (often a missing bootstrap install — Step 3.5 — or a stale
|
|
543
|
+
`.agentrc.json` — Step 3), re-run `npx mandrel update` until it reports
|
|
544
|
+
success, and only then commit in Step 5.
|
|
545
|
+
|
|
546
|
+
- **A post-install phase failed (`sync` / `sync-commands` / `migrate`)** —
|
|
547
|
+
the install bumped the lockfile but a later phase exited non-zero, leaving
|
|
548
|
+
a partially-upgraded tree. Do not commit. Run the matching per-phase
|
|
549
|
+
remedy and re-run the updater per
|
|
550
|
+
[Step 2.5 — Partial-upgrade recovery](#step-25--partial-upgrade-recovery-blocker--resolve-before-step-5).
|
|
374
551
|
|
|
375
552
|
- **Install command failed / `npm install … exited <n>`** — the npm
|
|
376
553
|
install step could not bump the dependency (network hiccup, registry
|
|
377
554
|
auth gap, or a peer-dependency conflict). Resolve the underlying npm
|
|
378
|
-
error and re-run `mandrel update`; it is idempotent — a clean re-run
|
|
555
|
+
error and re-run `npx mandrel update`; it is idempotent — a clean re-run
|
|
379
556
|
resumes from the resolve step and short-circuits if the install already
|
|
380
557
|
landed.
|
|
381
558
|
|
|
382
559
|
- **Wrong package manager** — the default install is `npm install`. For a
|
|
383
560
|
pnpm or yarn workspace, pass the package manager explicitly:
|
|
384
|
-
`mandrel update --install-cmd "pnpm add mandrel@<target>"`.
|
|
561
|
+
`npx mandrel update --install-cmd "pnpm add mandrel@<target>"`.
|
|
385
562
|
The registry probe always stays on `npm view` (a PM-agnostic query); only
|
|
386
563
|
the install seam honours the override.
|
|
387
564
|
|
|
@@ -19,7 +19,7 @@ operator watches and gates.** Its human-led sibling is
|
|
|
19
19
|
the agent scribes/enriches. No human-driven flow lives in `/qa-explore`; if you
|
|
20
20
|
want to capture something *you* observed, use `/qa-assist` instead.
|
|
21
21
|
|
|
22
|
-
Unlike [`/qa-run
|
|
22
|
+
Unlike [`/qa-run`](qa-run.md) (which steps a known set of
|
|
23
23
|
Gherkin `.feature` scenarios through a browser), `/qa-explore` is **open-ended
|
|
24
24
|
exploration**: the agent probes the surface for product bugs, environment-setup
|
|
25
25
|
friction, tooling/DX gaps, missing tests, and enhancement ideas — each captured
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Drive Gherkin scenarios through a real browser as an agent-driven QA sweep
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# /qa-run
|
|
5
|
+
# /qa-run
|
|
6
6
|
|
|
7
7
|
Execute a consumer's Gherkin `.feature` scenarios through a **real browser**
|
|
8
8
|
(the chrome-devtools MCP surface), with the agent acting as the step executor
|
|
@@ -31,7 +31,7 @@ console filtering.
|
|
|
31
31
|
## Slash Command
|
|
32
32
|
|
|
33
33
|
```text
|
|
34
|
-
/qa-run
|
|
34
|
+
/qa-run <selector>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
### Arguments
|
|
@@ -58,9 +58,9 @@ deterministic, `(file, line)`-sorted scenario set under the contract's
|
|
|
58
58
|
### Examples
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
/qa-run
|
|
62
|
-
/qa-run
|
|
63
|
-
/qa-run
|
|
61
|
+
/qa-run feature:login
|
|
62
|
+
/qa-run "tag:@smoke and not @wip"
|
|
63
|
+
/qa-run domain:billing
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
The canonical tag taxonomy — `@smoke`, `@risk-high`, `@platform-web`,
|
package/README.md
CHANGED
|
@@ -69,6 +69,26 @@ explicit `npx mandrel sync` above is the belt-and-suspenders step for
|
|
|
69
69
|
`--ignore-scripts` or sandboxed-CI installs. Run `npx mandrel doctor` any
|
|
70
70
|
time to confirm the install is healthy.
|
|
71
71
|
|
|
72
|
+
> **pnpm users — hoist mandrel's runtime deps.** The materialized
|
|
73
|
+
> `./.agents/scripts/*.js` run from your project root and resolve their
|
|
74
|
+
> third-party deps (ajv, js-yaml, …) from your top-level `node_modules`.
|
|
75
|
+
> npm and yarn hoist transitive deps there automatically; pnpm's default
|
|
76
|
+
> isolated layout does **not** — it keeps them in the `.pnpm` virtual store,
|
|
77
|
+
> so the framework scripts (and `mandrel doctor`'s `runtime-deps` check)
|
|
78
|
+
> cannot see them. Add the following to your `.npmrc` before installing:
|
|
79
|
+
>
|
|
80
|
+
> ```ini
|
|
81
|
+
> # Lift mandrel's runtime deps to the top-level node_modules so the
|
|
82
|
+
> # materialized .agents/scripts can resolve them.
|
|
83
|
+
> shamefully-hoist=true
|
|
84
|
+
> ```
|
|
85
|
+
>
|
|
86
|
+
> Prefer a surgical alternative? Replace `shamefully-hoist` with a scoped
|
|
87
|
+
> `public-hoist-pattern[]=` line per package listed in
|
|
88
|
+
> `.agents/runtime-deps.json` (`ajv`, `ajv-formats`, `js-yaml`, `minimatch`,
|
|
89
|
+
> `picomatch`, `string-argv`, `typhonjs-escomplex`). If `mandrel doctor`
|
|
90
|
+
> reports `runtime-deps missing: …`, this is the fix.
|
|
91
|
+
|
|
72
92
|
`bootstrap.js` is interactive on a TTY and auto-accepts the
|
|
73
93
|
owner/repo/base branch/operator handle it can infer from your local
|
|
74
94
|
`git remote` and `git config user.name` — you only get prompted for
|
|
@@ -138,6 +158,26 @@ npx mandrel sync # re-materialize ./.agents/
|
|
|
138
158
|
npx mandrel doctor # verify the install
|
|
139
159
|
```
|
|
140
160
|
|
|
161
|
+
## Benchmarking
|
|
162
|
+
|
|
163
|
+
Mandrel's effectiveness is measured by a separate companion repo,
|
|
164
|
+
**[mandrel-bench](https://github.com/dsj1984/mandrel-bench)** — a *consumer* of
|
|
165
|
+
the published `mandrel` package. It pins a specific framework version,
|
|
166
|
+
materializes it via `mandrel sync`, and drives Mandrel's own `/plan`→`/deliver`
|
|
167
|
+
pipeline (plus a bare-model control) over a scenario corpus. Each run is scored
|
|
168
|
+
across five dimensions — Quality, Planning fidelity, and Autonomy (what the
|
|
169
|
+
scaffolding *buys*) versus Efficiency and Overhead ratio (what it *costs*) —
|
|
170
|
+
reported as distributions with a noise-band, tracking the framework's
|
|
171
|
+
**value-add over the bare-model baseline** across versions and models.
|
|
172
|
+
|
|
173
|
+
The benchmark lives in its own repo on purpose: holding the harness fixed while
|
|
174
|
+
varying the pinned `mandrel` version cleanly decouples harness-version from
|
|
175
|
+
framework-version, and running through the published-package + `mandrel sync`
|
|
176
|
+
path exercises the real consumer contract. The dependency is one-directional —
|
|
177
|
+
`mandrel-bench` depends on `mandrel`, never the reverse. See the
|
|
178
|
+
[mandrel-bench README](https://github.com/dsj1984/mandrel-bench#readme) for the
|
|
179
|
+
dimensions, run model, and how to benchmark a new version.
|
|
180
|
+
|
|
141
181
|
## Contributors
|
|
142
182
|
|
|
143
183
|
Only `.agents/` is distributed to consumers — it ships inside the
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.71.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.70.0...mandrel-v1.71.0) (2026-06-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* **workflows:** three operator-facing slash commands are renamed and three git commands are consolidated. Consumers must update muscle memory and any scripts: `/agents-update`→`/mandrel-update`, `/qa-run-harness`→`/qa-run`, and `/git-commit-all` / `/git-push` / `/git-pr-all`→`/git-deliver` (detection picks the old behavior by default; `--no-push` reproduces `/git-commit-all`, a feature-branch run reproduces `/git-push`, and a base-branch run or `--pr` reproduces `/git-pr-all`).
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* **workflows:** rename agents-update→mandrel-update, qa-run-harness→qa-run; collapse 3 git commands into git-deliver ([#4209](https://github.com/dsj1984/mandrel/issues/4209)) ([fc30a3d](https://github.com/dsj1984/mandrel/commit/fc30a3d579d80fd854217e066f97899b813c6be9))
|
|
15
|
+
|
|
16
|
+
## [1.70.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.69.0...mandrel-v1.70.0) (2026-06-16)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
* **agents-update:** add a first-run preflight before the updater (refs [#4170](https://github.com/dsj1984/mandrel/issues/4170)) ([#4177](https://github.com/dsj1984/mandrel/issues/4177)) ([82d30c6](https://github.com/dsj1984/mandrel/commit/82d30c6877d567d8272e1f433da00797a71bdeb9))
|
|
22
|
+
* **agents-update:** make partial-upgrade recovery a first-class workflow step (refs [#4172](https://github.com/dsj1984/mandrel/issues/4172)) ([#4179](https://github.com/dsj1984/mandrel/issues/4179)) ([9959fd8](https://github.com/dsj1984/mandrel/commit/9959fd8e480067b1c35d25c9e37f8be0908f9a9e))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
* /audit-to-stories: fix silent-no-op dedup (GitHubProvider.searchIssues missing) + junk audit:: label derivation ([#4195](https://github.com/dsj1984/mandrel/issues/4195)) ([#4207](https://github.com/dsj1984/mandrel/issues/4207)) ([d4fed9b](https://github.com/dsj1984/mandrel/commit/d4fed9b6a3077c6c9371ec00debae5c422ac89c6))
|
|
28
|
+
* **agents-update:** invoke mandrel via npx and detect install state (refs [#4169](https://github.com/dsj1984/mandrel/issues/4169)) ([#4174](https://github.com/dsj1984/mandrel/issues/4174)) ([9aab7ba](https://github.com/dsj1984/mandrel/commit/9aab7ba3f2c425a5ecaf16e1008078538093a985))
|
|
29
|
+
* **bootstrap:** relax owner/repo under --skip-github so non-interactive init works in a fresh dir ([#4181](https://github.com/dsj1984/mandrel/issues/4181)) ([e560116](https://github.com/dsj1984/mandrel/commit/e560116aa33178f182752558e7d646c2cb7cdeb4))
|
|
30
|
+
* **deps:** bump markdownlint-cli2 0.18.1 -> 0.22.1, force patched js-yaml/markdown-it (refs [#4187](https://github.com/dsj1984/mandrel/issues/4187)) ([#4200](https://github.com/dsj1984/mandrel/issues/4200)) ([9fe1e09](https://github.com/dsj1984/mandrel/commit/9fe1e09daf91fab0ca5d44d5a701cd2576406a5b))
|
|
31
|
+
* **install-matrix:** hoist mandrel runtime deps in pnpm legs so doctor is honest ([#4180](https://github.com/dsj1984/mandrel/issues/4180)) ([5e7aca7](https://github.com/dsj1984/mandrel/commit/5e7aca7d48b9ab9d0ddd5c157a28bea8978fb90a))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Performance
|
|
35
|
+
|
|
36
|
+
* **baselines:** collapse the two escomplex passes in full-tree regen into one (refs [#4192](https://github.com/dsj1984/mandrel/issues/4192)) ([#4205](https://github.com/dsj1984/mandrel/issues/4205)) ([4f971c3](https://github.com/dsj1984/mandrel/commit/4f971c36723521058322b346980d6e1822c9d35d))
|
|
37
|
+
* **doctor:** short-circuit payload-drift on statSync size mismatch (refs [#4193](https://github.com/dsj1984/mandrel/issues/4193)) ([#4204](https://github.com/dsj1984/mandrel/issues/4204)) ([a85c050](https://github.com/dsj1984/mandrel/commit/a85c0501a02ff346024fa9d8c9aa48046999f08c))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
* **agents-update:** extract quality-bootstrap heredoc into a tested script (refs [#4171](https://github.com/dsj1984/mandrel/issues/4171)) ([#4178](https://github.com/dsj1984/mandrel/issues/4178)) ([032ecf7](https://github.com/dsj1984/mandrel/commit/032ecf70f5840ad5291c0bcdb6b11a9d23bec1be))
|
|
43
|
+
* **baselines:** triage dead-export allowlist 198 -> 185 (refs [#4184](https://github.com/dsj1984/mandrel/issues/4184)) ([#4197](https://github.com/dsj1984/mandrel/issues/4197)) ([2a39e61](https://github.com/dsj1984/mandrel/commit/2a39e616bb9a843325bf151d8f057f85a43fcd95))
|
|
44
|
+
* **renderers:** table-drive flat section ladders to lower CC ceilings (refs [#4186](https://github.com/dsj1984/mandrel/issues/4186)) ([#4201](https://github.com/dsj1984/mandrel/issues/4201)) ([36df937](https://github.com/dsj1984/mandrel/commit/36df9370d8e9cdef8f6bc47d0341ef8425314222))
|
|
45
|
+
* **update:** retire No-Shim in-process path and extract pure planUpdate (refs [#4182](https://github.com/dsj1984/mandrel/issues/4182)) ([#4198](https://github.com/dsj1984/mandrel/issues/4198)) ([d5ed325](https://github.com/dsj1984/mandrel/commit/d5ed32560d98fa43d6103789efac530f16244a91))
|
|
46
|
+
* **wave-runner:** decompose tick() into coordinator + phases (refs [#4183](https://github.com/dsj1984/mandrel/issues/4183)) ([#4196](https://github.com/dsj1984/mandrel/issues/4196)) ([0d7a6b4](https://github.com/dsj1984/mandrel/commit/0d7a6b4326b60ec3f2dd58a0798c8a37b6117d9c))
|
|
47
|
+
|
|
5
48
|
## [1.69.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.68.0...mandrel-v1.69.0) (2026-06-16)
|
|
6
49
|
|
|
7
50
|
|
package/lib/cli/registry.js
CHANGED
|
@@ -371,7 +371,14 @@ function runRuntimeDeps({
|
|
|
371
371
|
return {
|
|
372
372
|
ok: false,
|
|
373
373
|
detail: `missing: ${missing.join(', ')}`,
|
|
374
|
-
remedy:
|
|
374
|
+
remedy:
|
|
375
|
+
'The framework runtime deps are not resolvable from the consumer root, ' +
|
|
376
|
+
'where the materialized .agents/scripts/*.js run. npm and yarn hoist ' +
|
|
377
|
+
"them automatically — run the installer if you haven't. pnpm's default " +
|
|
378
|
+
'isolated layout does NOT hoist transitive deps to the top level: add ' +
|
|
379
|
+
'`shamefully-hoist=true` (or a scoped `public-hoist-pattern[]` for ' +
|
|
380
|
+
'each dep) to your `.npmrc` and reinstall. ' +
|
|
381
|
+
`Missing: ${missing.join(', ')}`,
|
|
375
382
|
};
|
|
376
383
|
}
|
|
377
384
|
|
|
@@ -457,6 +464,39 @@ function runAgentsMaterialized({ cwd, existsSync, resolvePackage } = {}) {
|
|
|
457
464
|
// from sync.js) are imported from `lib/cli/sync.js` at the top of this file
|
|
458
465
|
// (Story #4048 B3 — no mirror copies).
|
|
459
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Decide whether a single materialized payload file has drifted from its
|
|
469
|
+
* package-payload source.
|
|
470
|
+
*
|
|
471
|
+
* Size short-circuit (Story #4193): when the injected `fsImpl` exposes
|
|
472
|
+
* `statSync`, a `statSync(src).size !== statSync(dest).size` mismatch is
|
|
473
|
+
* sufficient proof of drift, so it returns `true` on two `stat` syscalls
|
|
474
|
+
* **without reading either file's contents** — the overwhelmingly common
|
|
475
|
+
* drift shape after a stale `sync` is a size change. Equal sizes do not
|
|
476
|
+
* guarantee equal content, so an equal-size pair falls through to the
|
|
477
|
+
* `readFileSync` + `Buffer.equals` byte comparison. The short-circuit is
|
|
478
|
+
* opt-in on `statSync` being present; a seam that omits it (e.g. a legacy
|
|
479
|
+
* test double) transparently uses the byte-read path, preserving the prior
|
|
480
|
+
* behaviour exactly.
|
|
481
|
+
*
|
|
482
|
+
* Security: returns only a boolean — it never surfaces file contents to the
|
|
483
|
+
* caller (security baseline §5 — Data Leakage & Logging).
|
|
484
|
+
*
|
|
485
|
+
* @param {string} src - absolute path to the package-payload file
|
|
486
|
+
* @param {string} dest - absolute path to the materialized file
|
|
487
|
+
* @param {{ readFileSync: Function, statSync?: Function }} fsImpl
|
|
488
|
+
* @returns {boolean} `true` when the files differ
|
|
489
|
+
*/
|
|
490
|
+
function payloadFileDrifted(src, dest, fsImpl) {
|
|
491
|
+
if (
|
|
492
|
+
typeof fsImpl.statSync === 'function' &&
|
|
493
|
+
fsImpl.statSync(src).size !== fsImpl.statSync(dest).size
|
|
494
|
+
) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
return !fsImpl.readFileSync(src).equals(fsImpl.readFileSync(dest));
|
|
498
|
+
}
|
|
499
|
+
|
|
460
500
|
/**
|
|
461
501
|
* Compare the consumer's materialized `./.agents/<f>` bytes against the
|
|
462
502
|
* installed package payload (`node_modules/mandrel/.agents/<f>`),
|
|
@@ -469,16 +509,21 @@ function runAgentsMaterialized({ cwd, existsSync, resolvePackage } = {}) {
|
|
|
469
509
|
* §5 — Data Leakage & Logging). The comparison is short-circuiting and never
|
|
470
510
|
* accumulates file contents.
|
|
471
511
|
*
|
|
512
|
+
* Performance: per-file drift is decided by `payloadFileDrifted`, which gates
|
|
513
|
+
* the byte read behind a cheap `statSync` size comparison (Story #4193) so a
|
|
514
|
+
* size-changed file — the common drift shape after a stale `sync` — reports
|
|
515
|
+
* without reading either file's contents.
|
|
516
|
+
*
|
|
472
517
|
* Injectable seams (used by tests so no real filesystem or package is needed):
|
|
473
518
|
* - `cwd()` — replaces `process.cwd`.
|
|
474
519
|
* - `fsImpl` — replaces the `node:fs` surface (`existsSync`, `readdirSync`,
|
|
475
|
-
* `readFileSync`).
|
|
520
|
+
* `readFileSync`, and optionally `statSync` for the size short-circuit).
|
|
476
521
|
* - `resolvePackageRoot(fromDir)` — replaces `mandrel` resolution;
|
|
477
522
|
* throws when the package is not installed.
|
|
478
523
|
*
|
|
479
524
|
* @param {{
|
|
480
525
|
* cwd?: () => string,
|
|
481
|
-
* fsImpl?: { existsSync: (p: string) => boolean, readdirSync: Function, readFileSync: Function },
|
|
526
|
+
* fsImpl?: { existsSync: (p: string) => boolean, readdirSync: Function, readFileSync: Function, statSync?: Function },
|
|
482
527
|
* resolvePackageRoot?: (fromDir: string) => string,
|
|
483
528
|
* }} [opts]
|
|
484
529
|
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
@@ -534,10 +579,8 @@ export function runAgentsDrift({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
|
|
|
534
579
|
};
|
|
535
580
|
}
|
|
536
581
|
|
|
537
|
-
const srcBytes = fsImpl.readFileSync(src);
|
|
538
|
-
const destBytes = fsImpl.readFileSync(dest);
|
|
539
582
|
comparedCount += 1;
|
|
540
|
-
if (
|
|
583
|
+
if (payloadFileDrifted(src, dest, fsImpl)) {
|
|
541
584
|
return {
|
|
542
585
|
ok: false,
|
|
543
586
|
detail: `${relLabel} differs from the installed package payload`,
|