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.
Files changed (57) hide show
  1. package/.agents/README.md +7 -7
  2. package/.agents/docs/SDLC.md +4 -5
  3. package/.agents/docs/configuration.md +9 -9
  4. package/.agents/docs/workflows.md +4 -6
  5. package/.agents/schemas/qa-finding.schema.json +1 -1
  6. package/.agents/scripts/apply-quality-bootstrap.js +79 -0
  7. package/.agents/scripts/audit-labels-bootstrap.js +52 -30
  8. package/.agents/scripts/audit-to-stories.js +54 -0
  9. package/.agents/scripts/bootstrap.js +13 -3
  10. package/.agents/scripts/generate-config-docs.js +189 -94
  11. package/.agents/scripts/lib/audit-suite/findings.js +0 -4
  12. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +99 -0
  13. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +13 -5
  14. package/.agents/scripts/lib/baseline-snapshot.js +163 -4
  15. package/.agents/scripts/lib/baselines/refresh-service.js +0 -4
  16. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  17. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +1 -1
  18. package/.agents/scripts/lib/config/baselines.js +0 -20
  19. package/.agents/scripts/lib/config/defaults.js +1 -1
  20. package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
  21. package/.agents/scripts/lib/config/temp-paths.js +0 -31
  22. package/.agents/scripts/lib/config-resolver.js +1 -1
  23. package/.agents/scripts/lib/crap-utils.js +281 -0
  24. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -2
  25. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -84
  26. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +3 -4
  27. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -4
  28. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +101 -70
  29. package/.agents/scripts/lib/orchestration/spec-renderer.js +42 -14
  30. package/.agents/scripts/lib/orchestration/ticket-lease.js +3 -0
  31. package/.agents/scripts/lib/qa/qa-context-hydrator.js +1 -1
  32. package/.agents/scripts/lib/qa/resolve-qa-contract.js +1 -1
  33. package/.agents/scripts/lib/story-body/story-body.js +110 -65
  34. package/.agents/scripts/lib/test-tiers.js +13 -7
  35. package/.agents/scripts/lib/wave-runner/tick.js +177 -53
  36. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +226 -0
  37. package/.agents/scripts/mandrel-update-preflight.js +235 -0
  38. package/.agents/scripts/providers/github/issues.js +48 -0
  39. package/.agents/scripts/providers/github.js +1 -0
  40. package/.agents/scripts/sync-agentrc.js +2 -2
  41. package/.agents/skills/skills.index.json +2 -2
  42. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +3 -3
  43. package/.agents/skills/stack/qa/qa-harness/SKILL.md +4 -4
  44. package/.agents/workflows/git-deliver.md +298 -0
  45. package/.agents/workflows/helpers/epic-testing.md +6 -6
  46. package/.agents/workflows/helpers/{agents-sync-config.md → mandrel-sync-config.md} +5 -4
  47. package/.agents/workflows/{agents-update.md → mandrel-update.md} +210 -33
  48. package/.agents/workflows/qa-explore.md +1 -1
  49. package/.agents/workflows/{qa-run-harness.md → qa-run.md} +5 -5
  50. package/README.md +40 -0
  51. package/docs/CHANGELOG.md +43 -0
  52. package/lib/cli/registry.js +49 -6
  53. package/lib/cli/update.js +335 -332
  54. package/package.json +16 -11
  55. package/.agents/workflows/git-commit-all.md +0 -15
  56. package/.agents/workflows/git-pr-all.md +0 -281
  57. 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-harness` —
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-harness` (scenario pass/fail/blocked totals plus structured
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-harness` with the chosen selector:
41
+ Invoke `/qa-run` with the chosen selector:
42
42
 
43
43
  ```text
44
- /qa-run-harness "tag:@smoke and @risk-high"
44
+ /qa-run "tag:@smoke and @risk-high"
45
45
  ```
46
46
 
47
- The `/qa-run-harness` workflow (`.agents/workflows/qa-run-harness.md`) owns the
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-harness.md`.
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
- /agents-update.
8
+ /mandrel-update.
9
9
  ---
10
10
 
11
- # agents-sync-config (helper)
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
- > [`/agents-update`](../agents-update.md) after a framework update; previously
16
- > shipped as `/agents-sync-config`. The reconciliation runs as part of the
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
- # /agents-update
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
- `/agents-update` advances the consumer repo to the newest published
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 prints
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/agents-sync-config.md`](helpers/agents-sync-config.md).
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 -e "
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 `agents-update`'s idempotence contract requires.
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 `/agents-update` invocation** —
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). Run `mandrel doctor` for the per-check remedies. The lockfile
371
- bump is already staged; fix the doctor finding (often a missing
372
- bootstrap install Step 3.5 — or a stale `.agentrc.json` — Step 3)
373
- before committing in Step 5.
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-harness`](qa-run-harness.md) (which steps a known set of
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-harness
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-harness <selector>
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-harness feature:login
62
- /qa-run-harness "tag:@smoke and not @wip"
63
- /qa-run-harness domain:billing
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 -&gt; 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 -&gt; 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
 
@@ -371,7 +371,14 @@ function runRuntimeDeps({
371
371
  return {
372
372
  ok: false,
373
373
  detail: `missing: ${missing.join(', ')}`,
374
- remedy: `Run \`npm install\` in the repository root to install missing packages: ${missing.join(', ')}`,
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 (!srcBytes.equals(destBytes)) {
583
+ if (payloadFileDrifted(src, dest, fsImpl)) {
541
584
  return {
542
585
  ok: false,
543
586
  detail: `${relLabel} differs from the installed package payload`,