mandrel 1.90.0 → 1.92.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 (115) hide show
  1. package/.agents/README.md +5 -5
  2. package/.agents/docs/SDLC.md +75 -107
  3. package/.agents/docs/configuration.md +1 -1
  4. package/.agents/docs/quality-gates.md +8 -7
  5. package/.agents/docs/workflows.md +4 -10
  6. package/.agents/instructions.md +2 -2
  7. package/.agents/schemas/agentrc.schema.json +1 -1
  8. package/.agents/schemas/lifecycle/README.md +2 -1
  9. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  10. package/.agents/schemas/risk-verdict.schema.json +14 -1
  11. package/.agents/scripts/README.md +0 -46
  12. package/.agents/scripts/analyze-execution.js +76 -1
  13. package/.agents/scripts/check-doc-links.js +3 -0
  14. package/.agents/scripts/check-workflow-cli-lint.js +208 -0
  15. package/.agents/scripts/epic-audit-recheck.js +1 -1
  16. package/.agents/scripts/epic-deliver-preflight.js +37 -1
  17. package/.agents/scripts/epic-plan-clarity.js +16 -6
  18. package/.agents/scripts/epic-plan-decompose.js +37 -59
  19. package/.agents/scripts/epic-plan-healthcheck.js +39 -6
  20. package/.agents/scripts/epic-plan-spec.js +41 -168
  21. package/.agents/scripts/epic-reconcile.js +7 -1
  22. package/.agents/scripts/lib/Logger.js +4 -4
  23. package/.agents/scripts/lib/audit-suite/index.js +4 -5
  24. package/.agents/scripts/lib/audit-suite/runner.js +6 -4
  25. package/.agents/scripts/lib/audit-suite/selector.js +2 -3
  26. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  27. package/.agents/scripts/lib/command-header.js +20 -0
  28. package/.agents/scripts/lib/config/github.js +1 -3
  29. package/.agents/scripts/lib/config-settings-schema.js +4 -3
  30. package/.agents/scripts/lib/degraded-mode.js +1 -1
  31. package/.agents/scripts/lib/label-constants.js +18 -3
  32. package/.agents/scripts/lib/label-taxonomy.js +14 -3
  33. package/.agents/scripts/lib/mutation/baseline-snapshot.js +7 -6
  34. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +18 -8
  35. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -4
  36. package/.agents/scripts/lib/orchestration/docs-digest.js +2 -2
  37. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +4 -0
  38. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +22 -12
  39. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +6 -1
  40. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +18 -2
  41. package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  42. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  43. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +32 -0
  44. package/.agents/scripts/lib/orchestration/plan-context.js +510 -0
  45. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  46. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  49. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +978 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +191 -0
  51. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  52. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  53. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/remote-verifier.js +165 -0
  55. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +10 -0
  56. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  57. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  58. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  59. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  60. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  61. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  62. package/.agents/scripts/plan-context.js +186 -0
  63. package/.agents/scripts/plan-critics.js +227 -0
  64. package/.agents/scripts/plan-persist.js +383 -0
  65. package/.agents/scripts/pr-watch-with-update.js +8 -7
  66. package/.agents/scripts/run-lint.js +10 -11
  67. package/.agents/scripts/single-story-init.js +22 -0
  68. package/.agents/scripts/story-plan.js +19 -7
  69. package/.agents/scripts/sync-claude-commands.js +10 -5
  70. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  71. package/.agents/workflows/audit-documentation.md +5 -7
  72. package/.agents/workflows/audit-lighthouse.md +1 -0
  73. package/.agents/workflows/audit-security.md +1 -0
  74. package/.agents/workflows/audit-to-stories.md +16 -5
  75. package/.agents/workflows/deliver.md +8 -0
  76. package/.agents/workflows/git-deliver.md +76 -243
  77. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  78. package/.agents/workflows/helpers/code-review.md +7 -6
  79. package/.agents/workflows/helpers/deliver-epic.md +16 -4
  80. package/.agents/workflows/helpers/plan-epic-reference.md +109 -96
  81. package/.agents/workflows/helpers/plan-epic.md +246 -999
  82. package/.agents/workflows/helpers/plan-story.md +14 -14
  83. package/.agents/workflows/helpers/scope-triage-gate.md +6 -4
  84. package/.agents/workflows/helpers/single-story-deliver.md +13 -4
  85. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  86. package/.agents/workflows/mandrel-update.md +144 -466
  87. package/.agents/workflows/plan.md +73 -143
  88. package/docs/CHANGELOG.md +20 -0
  89. package/lib/cli/registry.js +21 -3
  90. package/package.json +1 -1
  91. package/.agents/schemas/loop-unit.schema.json +0 -70
  92. package/.agents/scripts/assert-branch.js +0 -81
  93. package/.agents/scripts/check-loop-units.js +0 -204
  94. package/.agents/scripts/detect-merges.js +0 -111
  95. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  96. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  97. package/.agents/scripts/hierarchy-gate.js +0 -192
  98. package/.agents/scripts/hydrate-context.js +0 -179
  99. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  100. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  101. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  102. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  103. package/.agents/scripts/loc-delta.js +0 -205
  104. package/.agents/scripts/render-manifest.js +0 -143
  105. package/.agents/scripts/run-audit-suite.js +0 -97
  106. package/.agents/scripts/select-audits.js +0 -155
  107. package/.agents/scripts/update-mutation-baseline.js +0 -189
  108. package/.agents/workflows/explain.md +0 -118
  109. package/.agents/workflows/git-merge-pr.md +0 -377
  110. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  111. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  112. package/.agents/workflows/loops/README.md +0 -65
  113. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  114. package/.agents/workflows/loops/nightly-audit.md +0 -81
  115. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -14,237 +14,97 @@ description: >-
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
- > model (`mandrel`, #3436/#3437). This workflow wraps that CLI: it
18
- > runs `npx mandrel update`, then walks the operator through the
17
+ > model (#3436/#3437). This workflow wraps that CLI: it runs
18
+ > `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,
22
22
  > and the stage-and-commit of the staged lockfile bump.
23
23
 
24
- ## Overview
25
-
26
- `/mandrel-update` advances the consumer repo to the newest published
27
- `mandrel` release, re-materializes `.agents/`, and regenerates the
28
- flat `.claude/commands/` tree (invoked as `/<name>`) against the new workflow
29
- set then reconciles the consumer's own config, harness allowlist, and
30
- instructions against the change set the upgrade surfaced.
31
-
32
- The upgrade contract:
33
-
34
- - **The version only moves on explicit invocation.** `mandrel update`
35
- resolves the newest published version and bumps the dependency only when
36
- you run it. There is no `postinstall` hook and no background drift;
37
- teammates work against the exact `mandrel` version pinned in the
38
- consumer's `package-lock.json` until someone runs this workflow and commits
39
- the result.
40
- - **CI honours the committed lockfile.** Consumer CI runs `npm ci` against
41
- the committed `package-lock.json`, so it installs exactly the version the
42
- lockfile pins — never "whatever the registry's newest is today."
43
- - **Majors apply like any other bump.** Mandrel ships hard cutovers
44
- (`.agents/rules/git-conventions.md` § Contract Cutovers), so a major
45
- crossing is applied directly — the surfaced changelog is the migration
46
- guide.
47
- - **The CLI never commits.** The npm bump rewrites `package.json` /
48
- `package-lock.json` and leaves them **staged on disk** for operator review;
49
- `mandrel update` performs no `git add` / `git commit`. Staging and
50
- committing the bump (plus any consumer-side reconciliation) is Step 5 of
51
- this workflow.
52
- - **`.agents/workflows/` → `.claude/commands/` projection is delegated.**
53
- `mandrel update`'s sync step re-materializes `.agents/`, and the only
54
- authoritative writer of the generated flat command tree
55
- (`.claude/commands/`) is
56
- [`sync-claude-commands.js`](../scripts/sync-claude-commands.js), which
57
- prepends the `<!-- AUTO-GENERATED -->` header. Nothing else copies workflow
58
- files.
24
+ The upgrade contract, in brief: the version only moves on explicit
25
+ invocation (no `postinstall` drift — teammates track the committed
26
+ lockfile pin, and CI's `npm ci` honours it); majors apply like any other
27
+ bump (Mandrel ships hard cutovers the surfaced changelog is the
28
+ migration guide); the CLI **never commits** (the lockfile bump is left
29
+ staged for operator review); and the only authoritative writer of the
30
+ generated `.claude/commands/` tree is
31
+ [`sync-claude-commands.js`](../scripts/sync-claude-commands.js), invoked by
32
+ the CLI's sync step.
59
33
 
60
34
  > **Persona**: `devops-engineer` · **Skills**:
61
35
  > `core/ci-cd-and-automation`, `core/documentation-and-adrs`
62
36
 
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
37
  ## Step 0 — Detect the install state and pick the invocation form
75
38
 
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`. |
39
+ In a consumer project `mandrel` is usually a local devDependency and **not**
40
+ on `PATH`, so the bare `mandrel <subcommand>` form fails before any CLI
41
+ logic runs. Detect and route:
93
42
 
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`.
43
+ - `command -v mandrel` prints a path global install; the bare form works.
44
+ - `node_modules/.bin/mandrel` exists local devDependency; use
45
+ `npx mandrel <subcommand>` (the default form the rest of this workflow
46
+ uses — it is harmless on a global install too).
47
+ - Neither run `npm install -D mandrel` first, then `npx mandrel`.
99
48
 
100
49
  ## Step 0.5 — First-run preflight (before any bump)
101
50
 
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
51
  ```bash
108
52
  node .agents/scripts/mandrel-update-preflight.js
109
53
  ```
110
54
 
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.
55
+ Catches three day-0 failure modes before anything bumps, printing a JSON
56
+ envelope (`{ ok, blocked, findings[] }`) plus a human-readable report:
57
+
58
+ - **consumer-shape** **blocker** (exit 2): `package.json` lists `mandrel`
59
+ **and** `.agents/` exists. On failure, STOP — you are not in a consumer
60
+ project (wrong directory, the framework repo itself, or a project that
61
+ never ran `mandrel sync`). `cd` into the consumer repo or bootstrap one
62
+ (`npm install -D mandrel && npx mandrel sync`), then re-run.
63
+ - **dirty-index** — warn-only: pre-existing staged changes would be swept
64
+ into the Step 5 commit; `git restore --staged <path>` the unrelated ones.
65
+ - **offline** — warn-only: `npm ping` fails; restore connectivity before
66
+ the version probe.
67
+
68
+ The preflight is a workflow-layer guard; it deliberately lives outside
69
+ [`lib/cli/update.js`](../../lib/cli/update.js), which stays git-free.
145
70
 
146
71
  ## Step 1 — Run the updater
147
72
 
148
- Preview first, then apply. From the consumer repo root:
73
+ Preview first, then apply, from the consumer repo root:
149
74
 
150
75
  ```bash
151
76
  npx mandrel update --dry-run
152
77
  npx mandrel update
153
78
  ```
154
79
 
155
- `npx mandrel update --dry-run` resolves the newest published version and
156
- prints the ordered step plan (`npm-update → runSync → runMigrations → doctor →
157
- surface changelog`) without invoking any effectful seam no dependency bump,
158
- no sync, no migrations, no doctor, nothing written. Read the planned target
159
- version before applying.
160
-
161
- `npx mandrel update` (no flags) runs the live cycle:
162
-
163
- 1. **Resolve target** — the newest published `mandrel` version (via
164
- the daily freshness cache in `temp/version-check.json`) and the currently
165
- installed version.
166
- 2. **No-op short-circuit** — already on the newest version ⇒ prints
167
- `Already up to date` and exits 0.
168
- 3. **Install** — bumps the dependency (default
169
- `npm install mandrel@<target>`; pass
170
- `--install-cmd "<pm> <args>"` for a pnpm/yarn workspace). The lockfile
171
- change is left **staged** for review; the CLI never commits.
172
- 4. **runSync** — re-materializes `.agents/` from the freshly installed
173
- payload, which also regenerates the flat `.claude/commands/` tree via
174
- `sync-claude-commands.js`.
175
- 5. **runMigrations** — applies any version-keyed migration steps for the
176
- crossed range.
177
- 6. **doctor** — runs the check registry to verify the resulting install.
178
- 7. **Surface changelog** — prints the `docs/CHANGELOG.md` section(s) covering
179
- the applied range `(current, target]`. Capture this output — Step 4
180
- reconciles the consumer's own instructions against it.
181
-
182
- ## Step 2 — Expected output
183
-
184
- A successful bump ends with:
185
-
186
- ```text
187
- Updating v1.44.0 → v1.46.0…
188
- ✅ Updated to v1.46.0. The lockfile bump is staged for review.
189
-
190
- Changelog for v1.46.0:
191
- ## [1.46.0](…)
192
- ### Features
193
- * new workflow X
194
- ### Bug Fixes
195
- * tighten Y validation
196
- ```
197
-
198
- A no-op run (already on the newest version) looks like:
199
-
200
- ```text
201
- ✅ Already up to date (v1.46.0 is the newest version).
202
- ```
203
-
204
- A `--dry-run` preview looks like:
205
-
206
- ```text
207
- mandrel update — planned upgrade v1.44.0 → v1.46.0
208
- 1. npm-update
209
- 2. runSync
210
- 3. runMigrations
211
- 4. doctor
212
- 5. surface changelog
213
- Dry run: no files written, no dependency bumped.
214
- ```
80
+ The dry run resolves the newest published version and prints the ordered
81
+ step plan (`npm-update → runSync → runMigrations → doctor → surface
82
+ changelog`) without touching anything read the planned target version
83
+ before applying. The live run drives those phases in order, leaves the
84
+ lockfile bump **staged** (never committed), and finishes by printing the
85
+ `docs/CHANGELOG.md` sections covering the applied range `(current, target]`.
86
+ **Capture that changelog output** Step 4 reconciles the consumer's own
87
+ instructions against it. Already-newest is a clean no-op (`Already up to
88
+ date`, exit 0).
215
89
 
216
90
  ## Step 2.5 — Partial-upgrade recovery (**blocker — resolve before Step 5**)
217
91
 
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
92
+ The install phase stages the lockfile bump *before* the later phases run,
93
+ and by deliberate design the CLI **never rolls back the install on
94
+ failure**. So when a post-install phase (`sync` / `sync-commands` /
95
+ `migrate` / `doctor`) exits non-zero you land in a **partially-upgraded
96
+ state**: the bump is already staged while `.agents/` may be
97
+ half-materialized, the command tree out of sync, or a migration partially
98
+ applied — and the operator is one `git commit` away from recording a broken
99
+ half-upgrade as "done". **Treat any post-install phase failure as an
100
+ explicit blocker: do not proceed to Step 5 until the failed phase is
101
+ recovered and a clean re-run reports success.**
102
+
103
+ Identify the failed phase (the CLI's stderr names it) and run the matching
104
+ remedy. These commands match the hint strings
244
105
  [`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)):
106
+ single source of truth, kept in lockstep with this table by
107
+ [`tests/bootstrap/mandrel-update-recovery-drift.test.js`](../../tests/bootstrap/mandrel-update-recovery-drift.test.js):
248
108
 
249
109
  | Failed phase | Manual remedy |
250
110
  | ----------------- | ------------------------------------------------------- |
@@ -253,9 +113,7 @@ single source of truth, kept in lockstep with this table by the
253
113
  | **migrate** | `npx mandrel migrate --from <cur> --to <target>` |
254
114
  | **doctor** | `npx mandrel doctor` (then apply the per-check remedies) |
255
115
 
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:
116
+ The exact stderr the CLI prints per failed phase:
259
117
 
260
118
  - **sync** — the .agents/ materialization may be incomplete. Run `mandrel
261
119
  sync` manually to restore.
@@ -266,242 +124,103 @@ drift from what the operator actually sees:
266
124
  - **doctor** — upgraded to v\<target\> but doctor reported failures. → Run
267
125
  `mandrel doctor` for remedies.
268
126
 
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.
127
+ (`<cur>` / `<target>` are the installed and resolved-newest version strings
128
+ the failing run reported.)
295
129
 
296
- ## Step 3 Reconcile `.agentrc.json` against the new defaults
130
+ Recovery sequence: run the matching remedy, then **re-run
131
+ `npx mandrel update`** — it is idempotent (the install already landed, so a
132
+ clean re-run short-circuits the bump and re-drives the post-install
133
+ phases). Repeat until it reports success; only then proceed. A dedicated
134
+ `--resume` entrypoint was evaluated and deferred (Story #4172) — the
135
+ per-phase remedies plus the idempotent re-run already cover every recovery
136
+ case.
297
137
 
298
- A framework bump can add or reshape fields in
299
- `.agents/docs/agentrc-reference.json` (and the underlying schema). Run the
300
- reconciliation helper to verify the consumer's `.agentrc.json` still
301
- validates against the new schema, and to surface any project values that
302
- already match framework defaults (and could therefore be safely deleted):
138
+ ## Step 3 Reconcile `.agentrc.json` against the new defaults
303
139
 
304
140
  ```bash
305
141
  node .agents/scripts/sync-agentrc.js
306
142
  ```
307
143
 
308
- The helper (Story #1995) is **default-aware** and **read-only**:
309
-
310
- - The project config is **validated** against the framework schema. Any
311
- failure aborts the run with a diagnostic so the operator can fix the
312
- underlying typo / missing required key before proceeding.
313
- - Optional keys missing from the project are **never auto-filled**. The
314
- runtime layers framework defaults at read time, so writing them into
315
- `.agentrc.json` only bloats the consumer's config diff without
316
- changing behaviour.
317
- - Project values that deep-equal the framework default are flagged as
318
- `[REDUNDANT]` advisory rows — informational only; the file is never
319
- modified.
320
-
321
- Full procedure reference:
144
+ The helper (Story #1995) is default-aware and **read-only**: it validates
145
+ the consumer config against the new schema (non-zero exit → fix the
146
+ validation error and re-run before proceeding), never auto-fills missing
147
+ optional keys (the runtime layers defaults at read time), and flags
148
+ project values that deep-equal the framework default as `[REDUNDANT]`
149
+ advisory rows you may optionally delete by hand (commit alongside the bump
150
+ in Step 5). Full procedure:
322
151
  [`helpers/mandrel-sync-config.md`](helpers/mandrel-sync-config.md).
323
152
 
324
- If the helper prints `No changes required` with no advisories, the config
325
- is already in sync — carry on. If it lists `[REDUNDANT]` rows, you may
326
- optionally delete those keys from `.agentrc.json` by hand (commit
327
- alongside the bump in Step 5) for a leaner config. If it exits non-zero,
328
- fix the validation error and re-run before proceeding.
329
-
330
153
  ## Step 3.5 — Upgrade the stabilized-quality-gates surface (Epic #1386)
331
154
 
332
- A framework bump that crosses the Epic #1386 boundary requires four
333
- additive installs on the consumer project so the new gate behaviour is
334
- actually wired into the consumer's commit / push / CI surfaces. The
335
- installs share the same idempotent helpers the quality-gates phase of
336
- [`bootstrap.js`](../scripts/bootstrap.js) uses,
337
- so a project that already ran the bootstrap on a post-Epic #1386
338
- framework version sees `no-change` everywhere here.
339
-
340
- Run from the consumer repo root:
341
-
342
155
  ```bash
343
156
  node .agents/scripts/apply-quality-bootstrap.js
344
157
  ```
345
158
 
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
-
355
- The four `quality-bootstrap` outcomes:
356
-
357
- 1. **`helper`** copies
358
- [`code-quality-guardrails.md`](helpers/code-quality-guardrails.md)
359
- into the project's `.agents/workflows/helpers/`. On the npm
360
- distribution the helper is materialized into `.agents/` by
361
- `mandrel update`'s sync step, so this typically reports a `no-change`
362
- present outcome.
363
- 2. **`hook`** — installs `.husky/pre-commit` carrying the
364
- diff-scoped `quality:preview` invocation. **Custom hooks are
365
- preserved**: when a non-framework hook already exists the action is
366
- `custom-hook-skip` and the helper returns the recommended snippet
367
- the operator should append by hand. Print the notice and move on —
368
- never overwrite a custom hook silently.
369
- 3. **`scripts`** — backfills `quality:preview` and `quality:watch` in
370
- `package.json` only when the keys are absent. Existing operator
371
- values survive.
372
- 4. **`config`** — seeds `delivery.quality.codingGuardrails` and
373
- `delivery.quality.autoRefresh` defaults in `.agentrc.json`.
374
- Only missing keys are written — operator overrides survive.
375
-
376
- The `baselines-layout-migration` step relocates per-Epic snapshots
377
- into the `temp/epic/<id>/baselines/` namespace (Story #1467: ephemeral
378
- scratch state, not committed, reaped on `/deliver` merge with the
379
- rest of the per-Epic temp tree):
380
-
381
- - Loose `baselines/epic-<id>-{maintainability,crap}.json` files →
382
- moved under `temp/epic/<id>/baselines/`.
383
- - Legacy `baselines/snapshots/<id>/{maintainability,crap}.json` trees →
384
- re-keyed under `temp/epic/<id>/baselines/`.
385
- - Committed `baselines/epic/<id>/{maintainability,crap}.json` snapshots
386
- (the shape Story #1396 introduced) → moved out to
387
- `temp/epic/<id>/baselines/` and the now-empty committed tree is staged
388
- for removal via `git rm -r --quiet --ignore-unmatch baselines/epic/<id>`
389
- so the next commit prunes the tracked tree.
390
- - The main-tracked `baselines/{maintainability,crap}.json` files at
391
- the root are **not** touched — they remain the `main`-baseline
392
- contract for the framework.
393
-
394
- A second run produces `no-change` on every install path, which is the
395
- guarantee `mandrel-update`'s idempotence contract requires.
396
-
397
- ## Step 3.6 — Refresh the harness permission allowlist (`/fewer-permission-prompts`)
398
-
399
- A framework bump frequently introduces new helper scripts and `node
400
- .agents/scripts/<name>.js` invocations the consumer's
401
- `.claude/settings.json` allowlist has never seen. Left alone, the next
402
- `/deliver` or `/deliver` run trips a fresh wave of
403
- permission prompts that operators answer by hand — and those hand-tuned
404
- allowlists drift across projects.
405
-
406
- Run the harness skill that scans recent transcripts and emits an
407
- additive allowlist patch for `.claude/settings.json`:
159
+ Runs the same idempotent installs the quality-gates phase of
160
+ [`bootstrap.js`](../scripts/bootstrap.js) uses `applyQualityBootstrap`
161
+ then `migrateBaselinesLayout` — and prints a `{ quality, baselines }` JSON
162
+ envelope. The four quality-bootstrap outcomes: **helper** (materialize
163
+ [`code-quality-guardrails.md`](helpers/code-quality-guardrails.md)),
164
+ **hook** (install the `.husky/pre-commit` diff-scoped `quality:preview`
165
+ invocation a pre-existing **custom hook is never overwritten silently**;
166
+ the action is `custom-hook-skip` and the helper returns the snippet to
167
+ append by hand), **scripts** (backfill `quality:preview` /
168
+ `quality:watch` only when absent), **config** (seed missing
169
+ `delivery.quality.*` defaults — operator overrides survive). The baselines
170
+ step relocates per-Epic snapshots under `temp/epic/<id>/baselines/`; the
171
+ main-tracked root baselines are never touched. A second run reports
172
+ `no-change` on every path the idempotence contract this workflow
173
+ requires.
174
+
175
+ ## Step 3.6 — Refresh the harness permission allowlist
176
+
177
+ A framework bump frequently introduces new `node .agents/scripts/<name>.js`
178
+ invocations the consumer's `.claude/settings.json` allowlist has never
179
+ seen; left alone, the next delivery run trips a wave of hand-answered
180
+ permission prompts that drift across projects. From the same session that
181
+ ran the update, invoke the harness-supplied skill:
408
182
 
409
183
  ```text
410
184
  /fewer-permission-prompts
411
185
  ```
412
186
 
413
- The skill is supplied by the Claude Code harness (it is not a workflow
414
- in this repo); invoke it as a slash command from the same Claude Code
415
- session that just ran `mandrel update`. It:
416
-
417
- 1. Reads recent transcripts under `.claude/projects/.../`.
418
- 2. Buckets repeated read-only Bash + MCP tool calls by frequency.
419
- 3. Proposes a prioritized additive allowlist patch (project
420
- `.claude/settings.json`) — never removes existing entries.
421
-
422
- Treat the skill's output as a **PR-reviewable artifact**, not an
423
- auto-applied change:
424
-
425
- - Read every proposed entry. Reject anything that grants write
426
- permissions, network egress, or shells out to a destructive
427
- command (`rm`, `git push --force`, `gh release delete`, ...).
428
- - Accept only narrowly-scoped read-only entries
429
- (`Bash(node .agents/scripts/<name>.js *)`, `Bash(gh issue view *)`,
430
- `mcp__github__get_*`, etc.).
431
- - Apply the accepted subset by editing `.claude/settings.json` and
432
- stage it alongside the version bump in Step 5.
433
-
434
- The maintenance cadence is **once per `/mandrel-update` invocation** —
435
- the same operator who just ran `mandrel update` is the one with the
436
- freshest transcript context to review the proposed allowlist
437
- diff. Skipping the step is fine when the bump introduces no new
438
- scripts (the skill will report "no new high-frequency calls"), but the
439
- step itself is non-optional: silence-by-omission is what produces the
440
- hand-tuned drift this maintenance is meant to eliminate.
187
+ Treat its output as a **PR-reviewable artifact**, not an auto-applied
188
+ change: reject anything granting write permissions, network egress, or
189
+ destructive shell-outs; accept only narrowly-scoped read-only entries;
190
+ apply the accepted subset to `.claude/settings.json` and stage it alongside
191
+ the bump. The cadence is once per `/mandrel-update` invocation — skipping
192
+ is fine when the bump introduces no new scripts, but the review itself is
193
+ non-optional.
441
194
 
442
195
  ## Step 4 — Review the surfaced changelog and update consumer-side guidance
443
196
 
444
- Framework upgrades change behaviour the consumer project's own
445
- `AGENTS.md` (or `CLAUDE.md`) and project runbooks often encode e.g.,
446
- new validators that change what a planner is allowed to emit, new
447
- ticket-body schemas downstream agents must produce, retired flags or
448
- defaults the consumer's instructions still reference. The version bump is
449
- the right moment to reconcile those, while the diff is in front of the
450
- operator.
451
-
452
- `mandrel update` already **surfaced the changelog** for the applied range
453
- `(current, target]` in Step 1 — its final step prints every
454
- `docs/CHANGELOG.md` section newer than the installed version and no newer
455
- than the target. That printed range is your source of truth; you do not
456
- need to fetch a CHANGELOG from anywhere, since the CLI emitted it inline.
457
- If the upgrade output scrolled past, re-read the prior run's transcript or
458
- open the framework's GitHub Releases page for the version headers the
459
- bump spanned.
460
-
461
- For each changelog entry between the installed and target versions, check
462
- the consumer repo for guidance that has gone stale or guidance that should
463
- now exist:
464
-
465
- 1. **Consumer `AGENTS.md` / `CLAUDE.md`.** If the changelog entry
466
- introduces a new contract the consumer instructions must reflect
467
- (e.g., "tasks must emit a structured 4-section body", "PRs must
468
- include `audit-snapshot:`"), update the consumer instructions so a
469
- fresh agent reading them in isolation produces output that passes
470
- the framework's new validators. Conversely, remove or rewrite
471
- instructions that contradict a tightened rule.
472
- 2. **Project-specific runbooks.** If the consumer has its own runbooks
473
- (e.g., `docs/RUNBOOK.md`, `docs/delivery-runner.md`) that paraphrase
474
- framework workflows, sweep them for renamed flags / changed exit
475
- codes / removed scripts.
476
-
477
- Do not invent updates. If a changelog entry has no consumer-side
478
- implication, note that explicitly in your scratch and move on — silence
479
- is a valid review outcome. The goal is to leave the consumer
480
- instructions and runbooks *consistent* with the new framework version,
481
- not to manufacture churn.
482
-
483
- Stage every consumer-side edit alongside the staged lockfile bump so the
484
- upgrade and the reconciliation land in the same commit (Step 5). A
485
- reviewer reading the bump should be able to see, in one diff, both
486
- "the framework version moved" and "what we changed in our own files in
487
- response."
197
+ Framework upgrades change behaviour the consumer's own `AGENTS.md` /
198
+ `CLAUDE.md` and runbooks often encode. Step 1 already printed the changelog
199
+ for the applied range that output is your source of truth (re-read the
200
+ transcript or the GitHub Releases page if it scrolled past). For each entry
201
+ between the installed and target versions:
202
+
203
+ 1. **Consumer `AGENTS.md` / `CLAUDE.md`.** Update instructions so a fresh
204
+ agent reading them in isolation produces output that passes the
205
+ framework's new validators; remove or rewrite instructions that
206
+ contradict a tightened rule.
207
+ 2. **Project-specific runbooks.** Sweep any docs that paraphrase framework
208
+ workflows for renamed flags / changed exit codes / removed scripts.
209
+
210
+ Do not invent updates silence is a valid review outcome. Stage every
211
+ consumer-side edit alongside the staged lockfile bump so the upgrade and
212
+ the reconciliation land in one reviewable commit.
488
213
 
489
214
  ## Step 5 — Commit the bump
490
215
 
491
216
  > **Blocker check before you commit.** The staged lockfile bump is only safe
492
217
  > 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
-
499
- `mandrel update` leaves the dependency bump **staged on disk** but never
500
- commits. After reviewing the surfaced changelog, any `.agentrc.json`
501
- reconciliation diff from Step 3, the `.claude/settings.json` allowlist
502
- patch from Step 3.6, and the consumer instruction / runbook updates from
503
- Step 4, stage and commit the bump (plus the reconciliation and consumer
504
- edits, if any) from the consumer repo root:
218
+ > update` exited non-zero, resolve it via
219
+ > [Step 2.5 — Partial-upgrade recovery](#step-25--partial-upgrade-recovery-blocker--resolve-before-step-5)
220
+ > **before** the `git commit` below. Committing over a half-upgrade records
221
+ > a broken state as "done".
222
+
223
+ Stage and commit the bump plus everything the wraparound touched:
505
224
 
506
225
  ```bash
507
226
  git add package.json package-lock.json .agentrc.json .claude/settings.json AGENTS.md # plus any runbook files touched in Step 4
@@ -515,63 +234,22 @@ Upgraded v<OLD_VERSION> → v<NEW_VERSION> via mandrel update.
515
234
  ```
516
235
 
517
236
  Include the version range and, optionally, the surfaced changelog
518
- highlights so reviewers can see what moved without re-running the
519
- updater. Omit `.agentrc.json` from the `git add` if Step 3 reported
520
- `No changes required`; omit `.claude/settings.json` if Step 3.6 produced
521
- no accepted entries; omit the consumer-instruction paths if Step 4 was a
522
- no-op.
523
-
524
- > **Note:** `mandrel update`'s sync step also re-materializes `.agents/`
525
- > (and the flat command tree under `.claude/commands/`). On the npm
526
- > distribution `.agents/` is a
527
- > materialized directory rebuilt from the installed package — whether the
528
- > consumer commits the regenerated `.agents/` tree, or treats it as a
529
- > gitignored install artifact rebuilt by `npx mandrel sync`, depends on the
530
- > consumer's own vendoring policy. Stage the `.agents/` / `.claude/`
531
- > changes here only if the project commits its materialized tree.
532
-
533
- ## Troubleshooting
534
-
535
- - **`doctor reported failures: …`** — the dependency bumped and `.agents/`
536
- re-materialized, but a doctor check failed (and the run exited
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).
551
-
552
- - **Install command failed / `npm install … exited <n>`** — the npm
553
- install step could not bump the dependency (network hiccup, registry
554
- auth gap, or a peer-dependency conflict). Resolve the underlying npm
555
- error and re-run `npx mandrel update`; it is idempotent — a clean re-run
556
- resumes from the resolve step and short-circuits if the install already
557
- landed.
558
-
559
- - **Wrong package manager** — the default install is `npm install`. For a
560
- pnpm or yarn workspace, pass the package manager explicitly:
561
- `npx mandrel update --install-cmd "pnpm add mandrel@<target>"`.
562
- The registry probe always stays on `npm view` (a PM-agnostic query); only
563
- the install seam honours the override.
237
+ highlights. Omit any path whose step was a no-op. Whether the consumer
238
+ commits the re-materialized `.agents/` / `.claude/commands/` trees or
239
+ gitignores them as install artifacts depends on the consumer's own
240
+ vendoring policy stage them here only if the project commits its
241
+ materialized tree.
564
242
 
565
243
  ## Constraints
566
244
 
567
- - **Idempotent.** A second `mandrel update` immediately after a successful
568
- run resolves the same newest version, hits the no-op short-circuit, and
569
- prints `Already up to date` exit 0, nothing bumped.
570
- - **No auto-commit.** `mandrel update` leaves the lockfile bump staged on
571
- disk and never runs git. The operator reviews the surfaced changelog and
572
- writes the commit message (Step 5) — the CLI does not know whether the
573
- bump is release-worthy for the consumer.
245
+ - **Idempotent.** A second `mandrel update` after a successful run hits the
246
+ no-op short-circuit — exit 0, nothing bumped.
247
+ - **No auto-commit.** The CLI leaves the lockfile bump staged and never runs
248
+ git; the operator writes the commit (Step 5).
574
249
  - **No framework-side version bump.** This workflow advances the
575
- *consumer's* pinned `mandrel` version. It does not tag a release
576
- on the framework itself — that remains the framework maintainer's call via
577
- release-please.
250
+ *consumer's* pinned version; framework releases remain the maintainer's
251
+ call via release-please.
252
+ - **Wrong package manager?** The default install seam is `npm install`; for
253
+ a pnpm/yarn workspace pass
254
+ `--install-cmd "pnpm add mandrel@<target>"` (the registry probe stays on
255
+ the PM-agnostic `npm view`).