mandrel 2.63.0 → 2.65.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 (70) hide show
  1. package/.agents/agents/acceptance-critic.md +3 -2
  2. package/.agents/agents/auditor.md +3 -2
  3. package/.agents/agents/plan-critic.md +3 -2
  4. package/.agents/agents/story-worker.md +2 -2
  5. package/.agents/audit-checklists/quality.md +3 -0
  6. package/.agents/docs/agentrc-reference.json +1 -9
  7. package/.agents/docs/configuration.md +8 -7
  8. package/.agents/schemas/agentrc.schema.json +6 -13
  9. package/.agents/schemas/audit-rules.schema.json +1 -1
  10. package/.agents/schemas/story-deliver-terminal.schema.json +5 -0
  11. package/.agents/scripts/bootstrap.js +8 -2
  12. package/.agents/scripts/check-context-budget.js +1 -1
  13. package/.agents/scripts/lib/ITicketingProvider.js +1 -3
  14. package/.agents/scripts/lib/audit-suite/findings.js +1 -17
  15. package/.agents/scripts/lib/audit-suite/frontmatter.js +0 -28
  16. package/.agents/scripts/lib/audit-suite/index.js +0 -6
  17. package/.agents/scripts/lib/audit-suite/selector.js +0 -31
  18. package/.agents/scripts/lib/bootstrap/agents-md-fold.js +156 -0
  19. package/.agents/scripts/lib/bootstrap/commit-push.js +1 -1
  20. package/.agents/scripts/lib/bootstrap/manifest.js +2 -2
  21. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +13 -29
  22. package/.agents/scripts/lib/config/review-chain-default.js +13 -0
  23. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  24. package/.agents/scripts/lib/config-settings-schema-quality.js +11 -13
  25. package/.agents/scripts/lib/doc-tiers.js +25 -6
  26. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  27. package/.agents/scripts/lib/observability/metrics-ledger.js +0 -72
  28. package/.agents/scripts/lib/orchestration/ci-red-handling.js +73 -0
  29. package/.agents/scripts/lib/orchestration/code-review.js +11 -6
  30. package/.agents/scripts/lib/orchestration/deliver-recover.js +56 -11
  31. package/.agents/scripts/lib/orchestration/epic-rollup.js +29 -12
  32. package/.agents/scripts/lib/orchestration/merge-block-class.js +20 -4
  33. package/.agents/scripts/lib/orchestration/merge-poll.js +41 -22
  34. package/.agents/scripts/lib/orchestration/required-checks.js +147 -0
  35. package/.agents/scripts/lib/orchestration/review-providers/code-review.js +203 -0
  36. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +6 -4
  37. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -2
  38. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +1 -0
  39. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +0 -12
  40. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +135 -20
  41. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +112 -82
  42. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +72 -5
  43. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +12 -87
  44. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +3 -0
  45. package/.agents/scripts/lib/templates/decomposer-prompts.js +5 -24
  46. package/.agents/scripts/pr-watch-with-update.js +13 -19
  47. package/.agents/scripts/providers/github/issues.js +14 -23
  48. package/.agents/scripts/sync-claude-agents.js +1 -1
  49. package/.agents/workflows/audit-quality.md +42 -7
  50. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  51. package/.agents/workflows/helpers/code-review.md +15 -38
  52. package/.agents/workflows/helpers/deliver-reference.md +4 -2
  53. package/.agents/workflows/helpers/deliver-story.md +3 -0
  54. package/.agents/workflows/helpers/plan-reference.md +9 -8
  55. package/.agents/workflows/mandrel-deliver.md +2 -1
  56. package/.agents/workflows/mandrel-plan.md +10 -7
  57. package/.agents/workflows/mandrel-update.md +5 -3
  58. package/docs/CHANGELOG.md +38 -0
  59. package/lib/cli/claude-code-version.js +73 -0
  60. package/lib/cli/doctor.js +2 -2
  61. package/lib/cli/registry.js +9 -0
  62. package/lib/cli/uninstall.js +37 -9
  63. package/lib/migrations/index.js +2 -0
  64. package/lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js +38 -0
  65. package/package.json +2 -1
  66. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +0 -99
  67. package/.agents/scripts/lib/audit-suite/runner.js +0 -205
  68. package/.agents/scripts/lib/audit-suite/substitutions.js +0 -96
  69. package/.agents/scripts/lib/audit-suite/workflow-loader.js +0 -37
  70. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +0 -234
@@ -138,38 +138,29 @@ export class IssuesGateway {
138
138
 
139
139
  /**
140
140
  * Resolve an issue's container parent in one request via `Issue.parent`.
141
- * Never throws: no parent, an odd shape, or sub-issues being unavailable all
142
- * return `null`, leaving the caller's checklist fallback to run.
141
+ * `null` means "no parent"; a degraded lookup throws after retries.
143
142
  *
144
143
  * @param {number} number Issue number whose parent to resolve.
145
144
  * @returns {Promise<object|null>} Mapped parent ticket, or null.
145
+ * @throws {Error} When the lookup degrades.
146
146
  * @field-manifest GraphQL Issue.parent: number, id, title, body, state,
147
147
  * labels.nodes.name, assignees.nodes.login
148
148
  */
149
149
  async getParentIssue(number) {
150
150
  const issueNumber = Number(number);
151
151
  if (!Number.isInteger(issueNumber) || issueNumber <= 0) return null;
152
- let data;
153
- try {
154
- data = await withTransientRetry(
155
- () =>
156
- this.ghGraphql(
157
- PARENT_ISSUE_QUERY,
158
- { owner: this.owner, repo: this.repo, number: issueNumber },
159
- { headers: { 'GraphQL-Features': 'sub_issues' } },
160
- ),
161
- {
162
- label: `getParentIssue #${issueNumber}`,
163
- onRetry: defaultRetryWarn,
164
- },
165
- );
166
- } catch (err) {
167
- Logger.warn(
168
- `[GitHubProvider] parent lookup for #${issueNumber} degraded to none ` +
169
- `(${err?.message ?? err}).`,
170
- );
171
- return null;
172
- }
152
+ const data = await withTransientRetry(
153
+ () =>
154
+ this.ghGraphql(
155
+ PARENT_ISSUE_QUERY,
156
+ { owner: this.owner, repo: this.repo, number: issueNumber },
157
+ { headers: { 'GraphQL-Features': 'sub_issues' } },
158
+ ),
159
+ {
160
+ label: `getParentIssue #${issueNumber}`,
161
+ onRetry: defaultRetryWarn,
162
+ },
163
+ );
173
164
  return subIssueNodeToTicket(data?.repository?.issue?.parent ?? null);
174
165
  }
175
166
 
@@ -4,7 +4,7 @@
4
4
  * Project `.agents/agents/` and `.agents/local/agents/` into a flat
5
5
  * `.claude/agents/` tree — the sibling of `sync-claude-commands.js`, with the
6
6
  * same payload-wins shadowing and orphan-reap. A role agent runs on its own
7
- * system prompt (no `CLAUDE.md` closure), which is the point of routing to it.
7
+ * system prompt (no entry-doc `@`-import closure), which is the point of routing to it.
8
8
  */
9
9
 
10
10
  // cli-opt-out: top-level-await script with no main() function — runAsCli wraps an async main, which doesn't apply here.
@@ -12,7 +12,8 @@ the Story under audit. The shared lens machinery lives in
12
12
  `{{auditOutputDir}}/audit-quality-results.md`. Each finding carries a
13
13
  **Category:** (`Flakiness | Coverage | Performance | Mocking | Test Plans`); the
14
14
  report adds a **Test Strategy Assessment** table (Unit / Integration / E2E /
15
- Test Plans: Healthy / Needs Work / Missing).
15
+ Test Plans / Property-Based Testing: Healthy / Needs Work / Missing, or `N/A`
16
+ for Property-Based Testing when no module is a candidate).
16
17
 
17
18
  ## Scope
18
19
 
@@ -130,6 +131,36 @@ Evaluate the gathered context against the following test quality dimensions:
130
131
  finding here. Route the *architectural* framing of the same defect to
131
132
  [`audit-architecture`](audit-architecture.md)'s Shipped-But-Never-Wired
132
133
  dimension; this lens owns the **missing-test** framing.
134
+ 8. **Property-Based Coverage — Invariants Tested Only by Examples.** Flag a
135
+ module whose correctness rests on an invariant but whose tests are all
136
+ hand-picked examples, which structurally cannot reach the inputs nobody
137
+ thought to pick. A module is a candidate **only** on code evidence: a
138
+ documented invariant or "never"/"always" claim in a header comment; an
139
+ explicit state machine or status/label transition table; an
140
+ encode/decode, parse/serialize or normalise pair (round-trip); a
141
+ merge/dedup/sort/scheduler function; bounded-concurrency or retry
142
+ coordination over async I/O; or an idempotency claim. A module with no
143
+ stated or implied invariant is **never** a finding. Rank candidates by
144
+ Step 0's churn × coverage gap and cite their `baselines/` coverage/CRAP row
145
+ where one exists.
146
+
147
+ - **Toolchain by ecosystem, never one library.** Detect an existing
148
+ property-testing library from the consumer's manifests (e.g.
149
+ `fast-check` for JS/TS, `hypothesis` for Python, `proptest`/`quickcheck`
150
+ for Rust, `jqwik` for the JVM, `rapid`/`gopter` for Go); recommend the
151
+ ecosystem-idiomatic one only when none is present.
152
+ - Severity: an async/concurrency coordinator, or a guard whose
153
+ invariant protects an irreversible write, tested only by examples →
154
+ **High**; any other invariant-bearing module with example-only tests →
155
+ **Medium**; toolchain absent with no High/Medium candidate → **one Low**
156
+ roll-up finding, not one per module.
157
+ - Category: file under `Coverage`. A property test whose seed is
158
+ neither pinned nor printed on failure goes under `Flakiness`: a red that
159
+ cannot be reproduced breaks the reproducibility the rubric demands.
160
+ - **Name the property.** Each finding states the invariant as a testable
161
+ property (e.g. `decode(encode(x)) === x`; "no transition leaves a
162
+ terminal state") plus its generator shape (the input domain to draw
163
+ from) — never a bare "add property tests".
133
164
 
134
165
  ## Constraint (lens-specific carve-out)
135
166
 
@@ -150,10 +181,14 @@ table:
150
181
 
151
182
  ## Test Strategy Assessment
152
183
 
153
- | Layer | Status | Notes |
154
- | ------------------- | -------------------------------- | -------------- |
155
- | Unit Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
156
- | Integration Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
157
- | E2E Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
158
- | Test Plans | [Healthy / Needs Work / Missing] | [Brief reason] |
184
+ | Layer | Status | Notes |
185
+ | ---------------------- | -------------------------------------- | -------------- |
186
+ | Unit Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
187
+ | Integration Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
188
+ | E2E Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
189
+ | Test Plans | [Healthy / Needs Work / Missing] | [Brief reason] |
190
+ | Property-Based Testing | [Healthy / Needs Work / Missing / N/A] | [Brief reason] |
159
191
  ```
192
+
193
+ `Property-Based Testing` reads `N/A` when the repo has no candidate module
194
+ (dimension 8), so a repo with no invariant-bearing code is not nagged.
@@ -69,7 +69,7 @@ per-criterion, mid-delivery, and evaluates the actual work product.
69
69
  > `delivery.routing.roleScopedAgents` is enabled (the **default**), use
70
70
  > `subagent_type: acceptance-critic`: it boots on the role-scoped
71
71
  > [`acceptance-critic`](../../agents/acceptance-critic.md) context (its own
72
- > system prompt, no `CLAUDE.md` @-closure) carrying the maker-blind
72
+ > system prompt, no entry-doc @-closure) carrying the maker-blind
73
73
  > invariant and the verdict schema standalone. With the kill-switch off
74
74
  > (`roleScopedAgents: false`), fall back to
75
75
  > `subagent_type: general-purpose`. This loop already runs inside a Story
@@ -74,8 +74,8 @@ How each tier changes the review protocol:
74
74
  adversarial pass over the diff hunting for integration regressions and
75
75
  security-relevant edges before findings are finalized.
76
76
 
77
- The LLM-backed review providers (codex, security-review, ultrareview) render
78
- the resolved `depth` into the prompt/instructions they emit so the underlying
77
+ The LLM-backed review providers (code-review, codex, security-review,
78
+ ultrareview) render the resolved `depth` into the prompt/instructions they emit so the underlying
79
79
  model actually changes thoroughness. The native provider deliberately ignores
80
80
  `depth` — its mechanical lint + maintainability sweep already scales with diff
81
81
  size, and there is no "review harder" knob a deterministic scorer can turn (its
@@ -110,44 +110,21 @@ The pipeline will:
110
110
  - Run a focused lint check on the change set.
111
111
  - Post a structured summary report to the `[TICKET_ID]` issue.
112
112
 
113
- ### Step 1a — Story-scope local-lens pass (`scope: story` only)
114
-
115
- When `scope === 'story'`, the shared review spine
116
- [`runStoryReviewCore`](../../scripts/lib/orchestration/story-close/phases/review-core.js)
117
- runs a **shift-left local-lens pass** in the same close subprocess, *before*
118
- returning the review envelope. It:
119
-
120
- 1. Enumerates the actual Story diff (`baseRef...headRef` via
121
- `git diff --name-only`).
122
- 2. Selects the **local-tier** lenses that own a concern decidable from a single
123
- Story's diff — `resolveLensTier(lens) === 'local'` **plus** the pure
124
- `matchesAnyFilePattern` matcher against the diff (the audit-suite SDK's
125
- [`selectLocalLenses`](../../scripts/lib/audit-suite/selector.js)). This is
126
- deliberately **not** `selectAudits`: `selectAudits` unions in keyword and
127
- gate matches and has no per-tier gate, so it would widen the roster past the
128
- footprint-matched local set this tier owns.
129
- 3. Materializes the matched roster at **`light`** depth
130
- (`STORY_SCOPE_LENS_DEPTH`) via `runAuditSuite`, surfacing the outcome on the
131
- review envelope's `localLensReview` field.
132
-
133
- A diff that matches no local lens adds **no** lens work (the roster is empty and
134
- `runAuditSuite` is never invoked). The pass is advisory and best-effort: a git
135
- or materialization failure degrades to a skipped envelope and never blocks the
136
- close.
137
-
138
- The live close entry point —
139
- [`runStoryScopeReview`](../../scripts/lib/orchestration/single-story-close/phases/code-review.js)
140
- — reaches this pass through the shared `runStoryReviewCore` spine. Because
141
- the pass lives inside the close subprocess (invoked after the delivering
142
- child exits), it honors the maker-blind invariant above: a maker never runs
143
- its own local-lens review.
113
+ ### Story scope runs no lens pass
114
+
115
+ Close runs **no** audit-lens pass of its own (Story #5416 retired it: it
116
+ materialized prompt files no workflow read, then armed auto-merge without
117
+ waiting). The Story-scope review is this pipeline plus CI. Local-tier lens
118
+ concerns are covered shift-left by the write-time authoring checklists
119
+ threaded into the Story prompt; the on-demand `/audit-*` workflows remain the
120
+ way to run a full lens over a change.
144
121
 
145
122
  ## Step 2 — Review Pillars
146
123
 
147
124
  For each changed file, execute a strict review against four pillars. The
148
125
  second pillar (**Integration Review**) deliberately defers the security /
149
126
  performance / quality / coverage sweeps to the change-set-scoped lenses —
150
- those ran shift-left in the Story-scope local-lens pass (Step 1a).
127
+ those are covered shift-left by the write-time lens checklists.
151
128
  Re-walking those sweeps a second time in this pillar is duplication, not
152
129
  defense-in-depth.
153
130
 
@@ -173,9 +150,9 @@ Does the implementation match the Story's acceptance criteria and folded Spec?
173
150
 
174
151
  The diff under review is `baseRef..headRef`
175
152
  (`main..story-<storyId>`, or the configured base branch to the Story
176
- branch). The Story-scope local-lens pass (Step 1a) has already covered the
177
- local-tier concerns. Lens findings and pillar findings share the single
178
- `verification-results` comment this pass posts. The
153
+ branch). The write-time lens checklists have already covered the local-tier
154
+ concerns. Pillar findings land in the single `verification-results` comment
155
+ this pass posts. The
179
156
  integration view here focuses on cross-cutting ripple within the Story and
180
157
  contract drift against the base branch. Look for:
181
158
 
@@ -260,7 +237,7 @@ prior baseline before merging.
260
237
 
261
238
  Findings are **persisted as a `verification-results` structured comment on
262
239
  the `[TICKET_ID]` issue** by `runCodeReview` (the unified findings contract —
263
- this single comment carries the Story-scope lens findings). The target
240
+ this single comment carries the Story-scope review findings). The target
264
241
  ticket is the Story. The comment
265
242
  is idempotent — re-runs replace the prior one — and its body includes
266
243
  severity-tier counts plus the full findings list so downstream workflows
@@ -175,7 +175,7 @@ into batches of `cap` and dispatch each batch in its own turn.
175
175
  exposes agent dispatch, spawn each ready Story as its own
176
176
  `subagent_type: story-worker` sub-agent — it boots on the role-scoped
177
177
  [`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
178
- `CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs standalone. The
178
+ entry-doc @-closure) carrying the load-bearing delivery MUSTs standalone. The
179
179
  sub-agent executes [`deliver-story.md`](deliver-story.md) Steps 0–2.5
180
180
  (init → implement → acceptance self-eval → **push**) and stops there; **you**
181
181
  own Step 3, serialized — see `/mandrel-deliver` § Closing what the workers hand back.
@@ -291,7 +291,9 @@ confirm instead (`captureStoryFollowUps`).
291
291
  reopen the issue.
292
292
  - The parent lookup resolves the native parent edge in **one** call
293
293
  (`getParentIssue`), falling back to a `type::epic` scan for a child linked by
294
- checklist alone. Children are the body checklist **union** the native
294
+ checklist alone. An authoritative "no parent" narrows that scan to Epics
295
+ whose body checklist names the Story (no per-Epic native read); only a
296
+ degraded lookup reads every scanned Epic's native children. Children are the body checklist **union** the native
295
297
  sub-issue edges — the same reader `/mandrel-deliver`'s expansion uses.
296
298
  - A checklist row citing an id that resolves to nothing is **dropped with a
297
299
  warning** when the native read succeeded; an unresolvable *native* edge
@@ -72,6 +72,9 @@ One branch, one PR to `main`, commits against the inline `acceptance[]` /
72
72
  1. Read the Story body; its acceptance criteria are the contract. Docs are
73
73
  digest-first; read a caller-provided `checklistPath` first, and walk any
74
74
  `## Slicing` rows as **intra-session checkpoints** (reference § Step 1).
75
+ After a context summary, re-derive progress from `git log` on
76
+ `story-<id>` against the `## Slicing` rows (each checkpoint is a commit
77
+ boundary) before continuing.
75
78
  2. Implement and commit on the Story branch, iterating with quick advisory
76
79
  gates (`typecheck`, `lint`, scoped tests) — the full chain runs in Step 3,
77
80
  and the **one** full-suite run at Step 2.5.
@@ -100,19 +100,20 @@ not re-deriving which assumptions were really the agent's to make.
100
100
 
101
101
  ## Gate #1 → the one advisory line
102
102
 
103
- Gate #1 stops for exactly two things — the sharpened plan intent and any HITL
104
- unknown — and everything else the envelope surfaced collapses to **one
105
- advisory line** beneath it. Nothing on that line stops the run,
106
- reroutes it, or is invoked by `/mandrel-plan`; each item names something the
107
- operator may prefer to do instead, and the run proceeds either way. Under
103
+ Gate #1 stops only when there is at least one HITL unknown or
104
+ `duplicates[]` is non-empty; otherwise the run announces the sharpened plan
105
+ intent plus the advisory line and continues to authoring. Everything else the
106
+ envelope surfaced collapses to **one advisory line** beneath the gate.
107
+ Nothing on that line reroutes the run or is invoked by `/mandrel-plan`; each
108
+ item names something the operator may prefer to do instead. Under
108
109
  `--yes` the line is recorded and planning continues — an unattended run has
109
110
  nobody to take an offer.
110
111
 
111
112
  The line names, in order, whichever of these the envelope carries:
112
113
 
113
114
  - **`duplicates[]`** — open Stories the seed resembles (never Epics). Name
114
- the top one or two by id and title; a plan that duplicates open work is
115
- still the operator's call.
115
+ the top one or two by id and title. A plan that duplicates open work is
116
+ still the operator's call, so a non-empty list also stops Gate #1.
116
117
  - **Open `intake` rows** (`priorFeedback`) — CI-gap intake filings written by
117
118
  [`file-ci-gap.js`](../../scripts/file-ci-gap.js) when a delivery reached an
118
119
  Option-2 verdict in [`ci-remediation.md`](../../rules/ci-remediation.md).
@@ -339,7 +340,7 @@ On `dispatch: true`, dispatch **one fresh-context, maker-blind sub-agent**.
339
340
  When `delivery.routing.roleScopedAgents` is enabled (the **default**), use
340
341
  `subagent_type: plan-critic` — it boots on the role-scoped
341
342
  [`plan-critic`](../../agents/plan-critic.md) context (its own system prompt,
342
- no `CLAUDE.md` @-closure) that carries the maker-blind invariant, the
343
+ no entry-doc @-closure) that carries the maker-blind invariant, the
343
344
  `pre-mortem` charter, and the output shape standalone. When the kill-switch
344
345
  is off (`roleScopedAgents: false`) or the host cannot spawn at this depth,
345
346
  fall back to a generic sub-agent and hand it the same charter. Either way the
@@ -76,7 +76,8 @@ to an attended run.
76
76
  it cannot read — a missing gate would co-dispatch against an unlanded
77
77
  blocker.
78
78
 
79
- 2. **Confirm (N>1).** Present the order; wait unless `--yes`.
79
+ 2. **Announce (N>1).** Present the resolved order and proceed — do not wait
80
+ for confirmation; the operator can interject mid-run to change it.
80
81
 
81
82
  3. **Run the beat.** One command per beat, repeated until the envelope reports
82
83
  the run `done`:
@@ -71,13 +71,16 @@ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
71
71
  operator questions — AFK unknowns are still researched; only HITL unknowns land
72
72
  in Key Assumptions, each a decision-made-by-default.
73
73
 
74
- **Gate #1** — STOP for exactly two things: confirm the sharpened plan intent,
75
- and settle any HITL unknown the operator owns. Everything else the envelope
76
- surfaced — `duplicates[]`, open `intake` rows, a truthy
77
- `memoryPoolAdvisory.recommend`, a truthy `complexitySignals.uiSurface` naming
78
- [`/prototype`](prototype.md) (never invoke it here) — collapses to
79
- **one advisory line** under the gate; none of it stops the run or reroutes
80
- it ([ref](helpers/plan-reference.md)). Under `--yes`, auto-proceed.
74
+ **Gate #1** — STOP only when a HITL unknown the operator owns exists or
75
+ `duplicates[]` is non-empty (planning a duplicate of open work stays the
76
+ operator's call): confirm the sharpened plan intent and settle it. Otherwise
77
+ announce the sharpened intent and the advisory line, and continue to
78
+ authoring. Everything else the envelope surfaced — `duplicates[]`, open
79
+ `intake` rows, a truthy `memoryPoolAdvisory.recommend`, a truthy
80
+ `complexitySignals.uiSurface` naming [`/prototype`](prototype.md) (never
81
+ invoke it here) — collapses to **one advisory line** under the gate, which
82
+ never reroutes the run ([ref](helpers/plan-reference.md)).
83
+ Under `--yes`, auto-proceed.
81
84
 
82
85
  ### 2. Author
83
86
 
@@ -200,13 +200,15 @@ non-optional.
200
200
 
201
201
  ## Step 4 — Review the surfaced changelog and update consumer-side guidance
202
202
 
203
- Framework upgrades change behaviour the consumer's own `AGENTS.md` /
204
- `CLAUDE.md` and runbooks often encode. Step 1 already printed the changelog
203
+ Framework upgrades change behaviour the consumer's own `AGENTS.md` and
204
+ runbooks often encode. Step 1 already printed the changelog
205
205
  for the applied range — that output is your source of truth (re-read the
206
206
  transcript or the GitHub Releases page if it scrolled past). For each entry
207
207
  between the installed and target versions:
208
208
 
209
- 1. **Consumer `AGENTS.md` / `CLAUDE.md`.** Update instructions so a fresh
209
+ 1. **Consumer `AGENTS.md`.** It is the entry doc — `mandrel update` folds a
210
+ root `CLAUDE.md` into it and deletes `CLAUDE.md`, so reconcile the folded
211
+ content here. Update instructions so a fresh
210
212
  agent reading them in isolation produces output that passes the
211
213
  framework's new validators; remove or rewrite instructions that
212
214
  contradict a tightened rule.
package/docs/CHANGELOG.md CHANGED
@@ -15,6 +15,44 @@ All notable changes to this project will be documented in this file.
15
15
  -->
16
16
  <!-- markdownlint-disable-file MD004 MD012 MD037 -->
17
17
 
18
+ ## [2.65.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.64.0...mandrel-v2.65.0) (2026-09-24)
19
+
20
+
21
+ ### ⚠ BREAKING CHANGES
22
+
23
+ * consumers must run Claude Code >= 2.1.277, which loads a root AGENTS.md; `mandrel update` folds the root CLAUDE.md into AGENTS.md and deletes CLAUDE.md.
24
+
25
+ ### Added
26
+
27
+ * add a low-effort model bug review to close and pin role-agent effort ([#5426](https://github.com/dsj1984/mandrel/issues/5426)) ([#5431](https://github.com/dsj1984/mandrel/issues/5431)) ([ec39643](https://github.com/dsj1984/mandrel/commit/ec3964390210e140ebaa324bbfe27b67fd470fa4))
28
+ * **audit-quality:** add a property-based coverage dimension (refs [#5425](https://github.com/dsj1984/mandrel/issues/5425)) ([#5428](https://github.com/dsj1984/mandrel/issues/5428)) ([f78fd62](https://github.com/dsj1984/mandrel/commit/f78fd62edda9060580b1cbacaec1cc82875040d1))
29
+ * cut consumers over to AGENTS.md: bootstrap wiring, CLAUDE.md fold migration, uninstall revert, Claude Code version doctor check ([#5410](https://github.com/dsj1984/mandrel/issues/5410)) ([#5411](https://github.com/dsj1984/mandrel/issues/5411)) ([df69d59](https://github.com/dsj1984/mandrel/commit/df69d59e7ced7342f321b8435ab50b317655f7f3))
30
+ * make AGENTS.md mandrel's always-loaded entry doc: host-faithful closure resolver, delete CLAUDE.md, rewrite AGENTS.md ([#5409](https://github.com/dsj1984/mandrel/issues/5409)) ([#5413](https://github.com/dsj1984/mandrel/issues/5413)) ([929e5bb](https://github.com/dsj1984/mandrel/commit/929e5bb6834e1a25152163ac34a87d327034ce05))
31
+ * trim legacy story-author scaffolding and drop two needless workflow stops ([#5427](https://github.com/dsj1984/mandrel/issues/5427)) ([#5429](https://github.com/dsj1984/mandrel/issues/5429)) ([7b39d36](https://github.com/dsj1984/mandrel/commit/7b39d3637b4911ad7122d6cbaef3c19c88704101))
32
+
33
+
34
+ ### Fixed
35
+
36
+ * merge wait: fail fast on a red required check without waiting for unrelated jobs ([#5415](https://github.com/dsj1984/mandrel/issues/5415)) ([#5420](https://github.com/dsj1984/mandrel/issues/5420)) ([381d8ff](https://github.com/dsj1984/mandrel/commit/381d8ff4145ec3210d984936904368d51cde426a))
37
+
38
+
39
+ ### Performance
40
+
41
+ * close: per-phase timing, faster merge observation, concurrent post-land GitHub steps ([#5417](https://github.com/dsj1984/mandrel/issues/5417)) ([#5423](https://github.com/dsj1984/mandrel/issues/5423)) ([2d3c537](https://github.com/dsj1984/mandrel/commit/2d3c5373402ad0e212951b70ca2880204da3a8f3))
42
+ * epic rollup: stop reading every Epic's sub-issues when a Story has no native parent ([#5414](https://github.com/dsj1984/mandrel/issues/5414)) ([#5419](https://github.com/dsj1984/mandrel/issues/5419)) ([025c650](https://github.com/dsj1984/mandrel/commit/025c650409a757b47be9847a4584e5e45fdef8e8))
43
+
44
+
45
+ ### Changed
46
+
47
+ * retire the Story-scope local-lens pass from close ([#5416](https://github.com/dsj1984/mandrel/issues/5416)) ([#5422](https://github.com/dsj1984/mandrel/issues/5422)) ([ecd86da](https://github.com/dsj1984/mandrel/commit/ecd86daa1a9d1ddff7f58e7f832e491a9c58cb68))
48
+
49
+ ## [2.64.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.63.0...mandrel-v2.64.0) (2026-09-19)
50
+
51
+
52
+ ### Fixed
53
+
54
+ * close-and-land: a checks-failed stop disarms auto-merge and writes the CI digest ([#5405](https://github.com/dsj1984/mandrel/issues/5405)) ([#5406](https://github.com/dsj1984/mandrel/issues/5406)) ([5819d52](https://github.com/dsj1984/mandrel/commit/5819d52a834a6973b3e138961d9735e477f6cb48))
55
+
18
56
  ## [2.63.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.62.0...mandrel-v2.63.0) (2026-09-18)
19
57
 
20
58
 
@@ -0,0 +1,73 @@
1
+ // lib/cli/claude-code-version.js
2
+ /**
3
+ * `mandrel doctor` check `claude-code-version`: an AGENTS.md-only project
4
+ * needs a Claude Code host that loads AGENTS.md at all. Below the floor the
5
+ * framework never hydrates, so the check fails. A missing `claude` binary,
6
+ * unparseable output, or a project still carrying CLAUDE.md skips with
7
+ * `ok: true` so headless/CI hosts are never blocked. The `claude --version`
8
+ * spawn is injected by the registry (fixed argv, never user input); with no
9
+ * runner the check skips.
10
+ *
11
+ * @module cli/claude-code-version
12
+ */
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+
17
+ import { compareVersions } from './version-helpers.js';
18
+
19
+ /** Oldest Claude Code release that loads a root AGENTS.md. */
20
+ export const CLAUDE_CODE_AGENTS_MD_FLOOR = '2.1.277';
21
+
22
+ /**
23
+ * @param {string} text
24
+ * @returns {string|null} the leading `x.y.z`, or null
25
+ */
26
+ export function parseClaudeVersion(text) {
27
+ const match = /^\s*v?(\d+\.\d+\.\d+)/.exec(text ?? '');
28
+ return match ? match[1] : null;
29
+ }
30
+
31
+ /**
32
+ * @param {{
33
+ * projectRoot?: string,
34
+ * cwd?: () => string,
35
+ * existsSync?: (p: string) => boolean,
36
+ * runClaude?: () => { status: number|null, stdout: string, error?: NodeJS.ErrnoException },
37
+ * }} [opts]
38
+ * @returns {{ ok: boolean, detail: string, remedy?: string }}
39
+ */
40
+ export function runClaudeCodeVersion({
41
+ projectRoot,
42
+ cwd = () => process.cwd(),
43
+ existsSync = fs.existsSync,
44
+ runClaude = () => ({ status: null, stdout: '', error: new Error('none') }),
45
+ } = {}) {
46
+ const root = projectRoot ?? cwd();
47
+ if (existsSync(path.join(root, 'CLAUDE.md'))) {
48
+ return { ok: true, detail: 'skipped: CLAUDE.md present' };
49
+ }
50
+ if (!existsSync(path.join(root, 'AGENTS.md'))) {
51
+ return { ok: true, detail: 'skipped: no AGENTS.md' };
52
+ }
53
+ const r = runClaude();
54
+ if (r.error || r.status !== 0) {
55
+ return { ok: true, detail: 'skipped: claude not found on PATH' };
56
+ }
57
+ const version = parseClaudeVersion(r.stdout);
58
+ if (!version) {
59
+ return {
60
+ ok: true,
61
+ detail: 'skipped: unparseable `claude --version` output',
62
+ };
63
+ }
64
+ const detail = `Claude Code ${version} (required >=${CLAUDE_CODE_AGENTS_MD_FLOOR} to load AGENTS.md)`;
65
+ if (compareVersions(version, CLAUDE_CODE_AGENTS_MD_FLOOR) >= 0) {
66
+ return { ok: true, detail };
67
+ }
68
+ return {
69
+ ok: false,
70
+ detail,
71
+ remedy: `Upgrade Claude Code to >=${CLAUDE_CODE_AGENTS_MD_FLOOR} (e.g. \`claude update\`) — older hosts do not load AGENTS.md, so the framework never hydrates.`,
72
+ };
73
+ }
package/lib/cli/doctor.js CHANGED
@@ -54,7 +54,7 @@ function formatSummary(passed, total) {
54
54
  }
55
55
 
56
56
  /**
57
- * Informational line for the `CLAUDE.md` always-loaded closure (file count,
57
+ * Informational line for the host entry doc's always-loaded closure (file count,
58
58
  * KB). Never counted toward the verdict; a resolve failure degrades to a
59
59
  * neutral line.
60
60
  *
@@ -73,7 +73,7 @@ export function formatClosureReport({
73
73
  return `ℹ ${label} always-loaded closure unavailable\n`;
74
74
  }
75
75
  if (files.length === 0) {
76
- return `ℹ ${label} no CLAUDE.md closure found\n`;
76
+ return `ℹ ${label} no entry-doc closure found (CLAUDE.md or AGENTS.md)\n`;
77
77
  }
78
78
  const kb = (tierTotalBytes(files) / 1024).toFixed(1);
79
79
  return `ℹ ${label} ${files.length} file(s), ${kb} KB always-loaded\n`;
@@ -28,6 +28,7 @@ import { isCommandExcluded } from '../../.agents/scripts/lib/command-header.js';
28
28
  import { getDeliveryRouting } from '../../.agents/scripts/lib/config/delivery-routing.js';
29
29
  import { isResolvable } from '../../.agents/scripts/lib/runtime-deps/dep-resolution.js';
30
30
  import { describeParserMajorError } from '../../.agents/scripts/lib/runtime-deps/parser-major.js';
31
+ import { runClaudeCodeVersion } from './claude-code-version.js';
31
32
  import {
32
33
  defaultResolvePackageRoot,
33
34
  listFiles as listPayloadFiles,
@@ -958,6 +959,14 @@ export const registry = [
958
959
  name: 'agents-drift',
959
960
  run: (opts) => runAgentsDrift(opts),
960
961
  },
962
+ {
963
+ name: 'claude-code-version',
964
+ run: (opts) =>
965
+ runClaudeCodeVersion({
966
+ runClaude: () => spawn('claude', ['--version'], { timeout: 10_000 }),
967
+ ...opts,
968
+ }),
969
+ },
961
970
  {
962
971
  name: 'merge-driver',
963
972
  run: (opts) => runMergeDriver(opts),
@@ -21,8 +21,8 @@ import {
21
21
  BOOTSTRAP_COMMAND,
22
22
  GITIGNORE_BLOCKS,
23
23
  SYNC_COMMAND,
24
+ SYSTEM_PROMPT_AGENTS_MD,
24
25
  SYSTEM_PROMPT_BLOCK,
25
- SYSTEM_PROMPT_CLAUDE_MD,
26
26
  SYSTEM_PROMPT_IMPORT,
27
27
  } from '../../.agents/scripts/lib/bootstrap/project-bootstrap.js';
28
28
  import {
@@ -60,17 +60,17 @@ const FRAMEWORK_NPM_SCRIPTS = Object.freeze({
60
60
  */
61
61
 
62
62
  /**
63
- * Strip the system-prompt import block from `CLAUDE.md`. The file is deleted
64
- * only when byte-identical to the install template — a heading-only heuristic
65
- * would delete an operator's all-headings file.
63
+ * Strip the system-prompt import block from a root entry doc. The file is
64
+ * deleted only when byte-identical to the install template — a heading-only
65
+ * heuristic would delete an operator's all-headings file.
66
66
  *
67
+ * @param {string} rel - `AGENTS.md`, or a legacy un-migrated `CLAUDE.md`.
67
68
  * @param {string} projectRoot
68
69
  * @param {typeof fs} fsImpl
69
70
  * @returns {ReversalOutcome}
70
71
  */
71
- function revertClaudeMd(projectRoot, fsImpl) {
72
- const target = path.join(projectRoot, 'CLAUDE.md');
73
- const rel = 'CLAUDE.md';
72
+ function revertEntryDoc(rel, projectRoot, fsImpl) {
73
+ const target = path.join(projectRoot, rel);
74
74
  if (!fsImpl.existsSync(target)) {
75
75
  return { kind: 'skipped', target: rel, detail: 'file absent' };
76
76
  }
@@ -78,12 +78,12 @@ function revertClaudeMd(projectRoot, fsImpl) {
78
78
  if (!original.includes(SYSTEM_PROMPT_IMPORT)) {
79
79
  return { kind: 'skipped', target: rel, detail: 'import already absent' };
80
80
  }
81
- if (original.trim() === SYSTEM_PROMPT_CLAUDE_MD.trim()) {
81
+ if (original.trim() === SYSTEM_PROMPT_AGENTS_MD.trim()) {
82
82
  fsImpl.rmSync(target, { force: true });
83
83
  return {
84
84
  kind: 'reverted',
85
85
  target: rel,
86
- detail: 'removed install-created CLAUDE.md',
86
+ detail: `removed install-created ${rel}`,
87
87
  };
88
88
  }
89
89
  // Full block first, then the bare import line for a hand-edited block.
@@ -109,6 +109,33 @@ function revertClaudeMd(projectRoot, fsImpl) {
109
109
  };
110
110
  }
111
111
 
112
+ /**
113
+ * @param {string} projectRoot
114
+ * @param {typeof fs} fsImpl
115
+ * @returns {ReversalOutcome}
116
+ */
117
+ function revertAgentsMd(projectRoot, fsImpl) {
118
+ return revertEntryDoc('AGENTS.md', projectRoot, fsImpl);
119
+ }
120
+
121
+ /**
122
+ * Pre-2.65 ledgers name CLAUDE.md. An un-migrated CLAUDE.md reverts as
123
+ * before; once the update migration folded it away, the wiring lives in
124
+ * AGENTS.md, so that is reverted instead. CLAUDE.md is never recreated.
125
+ *
126
+ * @param {string} projectRoot
127
+ * @param {typeof fs} fsImpl
128
+ * @returns {ReversalOutcome}
129
+ */
130
+ function revertClaudeMd(projectRoot, fsImpl) {
131
+ const legacy = fsImpl.existsSync(path.join(projectRoot, 'CLAUDE.md'));
132
+ return revertEntryDoc(
133
+ legacy ? 'CLAUDE.md' : 'AGENTS.md',
134
+ projectRoot,
135
+ fsImpl,
136
+ );
137
+ }
138
+
112
139
  /**
113
140
  * Splice the sync hook and any legacy plugin-enablement keys out of
114
141
  * `.claude/settings.json`, keeping everything else; delete the file if empty.
@@ -405,6 +432,7 @@ function revertPreCommitHook(projectRoot, fsImpl) {
405
432
  * @type {Readonly<Record<string, (root: string, fsImpl: typeof fs, executedAction?: string) => ReversalOutcome>>}
406
433
  */
407
434
  const REVERSAL_BY_TARGET = Object.freeze({
435
+ 'AGENTS.md': revertAgentsMd,
408
436
  'CLAUDE.md': revertClaudeMd,
409
437
  '.claude/settings.json': revertClaudeSettings,
410
438
  '.claude/plugins/mandrel': revertClaudeCommands,
@@ -16,6 +16,7 @@ import { retireDeliveryLimitKnobs } from './steps/2.57.0-retire-delivery-limit-k
16
16
  import { retirePlanningLimitKnobs } from './steps/2.57.0-retire-planning-limit-knobs.js';
17
17
  import { retireAuditResultsAutoFile } from './steps/2.60.0-retire-audit-results-autofile.js';
18
18
  import { baselineMergeQueueShape } from './steps/2.63.0-baseline-merge-queue-shape.js';
19
+ import { foldClaudeMdIntoAgentsMdStep } from './steps/2.65.0-fold-claude-md-into-agents-md.js';
19
20
  import { stripRemovedAgentrcKeys } from './steps/strip-removed-agentrc-keys.js';
20
21
 
21
22
  /**
@@ -40,6 +41,7 @@ export const migrations = [
40
41
  retireAuditResultsAutoFile,
41
42
  stripRemovedAgentrcKeys,
42
43
  baselineMergeQueueShape,
44
+ foldClaudeMdIntoAgentsMdStep,
43
45
  ];
44
46
 
45
47
  export { compareVersions };