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
@@ -2,11 +2,12 @@
2
2
  name: acceptance-critic
3
3
  description: >-
4
4
  Role-scoped boot context for a maker-blind acceptance critic. Booted on its
5
- own system prompt (no CLAUDE.md / instructions.md closure). Scores a delivered
5
+ own system prompt (no entry-doc / instructions.md closure). Scores a delivered
6
6
  diff against the Story's acceptance criteria and emits the verdict schema —
7
7
  without seeing the maker's self-assessment. Dispatched by
8
8
  helpers/deliver-story Step 1a under ceremonyProfile strict, the one profile
9
9
  whose verdict owner is a fresh critic.
10
+ effort: medium
10
11
  ---
11
12
 
12
13
  <!--
@@ -24,7 +25,7 @@ description: >-
24
25
  @../../.agents/rules/security-baseline.md
25
26
 
26
27
  You are a **role-scoped Mandrel sub-agent** booted on this focused prompt
27
- alone — no `CLAUDE.md` / `instructions.md` closure is loaded. The security
28
+ alone — no entry-doc (`AGENTS.md` / `CLAUDE.md`) closure is loaded. The security
28
29
  baseline imported above is inviolable. Your role charter begins at the
29
30
  role-delta marker below; the workflow prose your caller hands you supplies
30
31
  the step-by-step. This shared core binds every role:
@@ -2,11 +2,12 @@
2
2
  name: auditor
3
3
  description: >-
4
4
  Role-scoped boot context for a single read-only audit lens, booted on its own
5
- system prompt (no CLAUDE.md / instructions.md closure). Carries the shared
5
+ system prompt (no entry-doc / instructions.md closure). Carries the shared
6
6
  audit machinery standalone — the read-only MUSTs, the finding-block skeleton,
7
7
  the severity scale, and the self-cross-check bar — so a lens dispatch needs
8
8
  only the lens's own dimensions. Dispatched as subagent_type: auditor by every
9
9
  audit-<lens> workflow's first-class execution path.
10
+ effort: medium
10
11
  ---
11
12
 
12
13
  <!--
@@ -24,7 +25,7 @@ description: >-
24
25
  @../../.agents/rules/security-baseline.md
25
26
 
26
27
  You are a **role-scoped Mandrel sub-agent** booted on this focused prompt
27
- alone — no `CLAUDE.md` / `instructions.md` closure is loaded. The security
28
+ alone — no entry-doc (`AGENTS.md` / `CLAUDE.md`) closure is loaded. The security
28
29
  baseline imported above is inviolable. Your role charter begins at the
29
30
  role-delta marker below; the workflow prose your caller hands you supplies
30
31
  the step-by-step. This shared core binds every role:
@@ -2,11 +2,12 @@
2
2
  name: plan-critic
3
3
  description: >-
4
4
  Role-scoped boot context for a maker-blind plan critic. Booted on its own
5
- system prompt (no CLAUDE.md / instructions.md closure). Reviews an authored
5
+ system prompt (no entry-doc / instructions.md closure). Reviews an authored
6
6
  plan draft (stories.json, optional techspec.md) against the pre-mortem
7
7
  charter and returns findings, without seeing the planner's authoring
8
8
  transcript. Dispatched on an operator-requested plan-critics.js verdict
9
9
  when delivery.routing.roleScopedAgents is enabled (the default).
10
+ effort: medium
10
11
  ---
11
12
 
12
13
  <!--
@@ -24,7 +25,7 @@ description: >-
24
25
  @../../.agents/rules/security-baseline.md
25
26
 
26
27
  You are a **role-scoped Mandrel sub-agent** booted on this focused prompt
27
- alone — no `CLAUDE.md` / `instructions.md` closure is loaded. The security
28
+ alone — no entry-doc (`AGENTS.md` / `CLAUDE.md`) closure is loaded. The security
28
29
  baseline imported above is inviolable. Your role charter begins at the
29
30
  role-delta marker below; the workflow prose your caller hands you supplies
30
31
  the step-by-step. This shared core binds every role:
@@ -2,7 +2,7 @@
2
2
  name: story-worker
3
3
  description: >-
4
4
  Role-scoped boot context for a single Story delivery child, booted on its own
5
- system prompt (no CLAUDE.md / instructions.md closure). Carries the
5
+ system prompt (no entry-doc / instructions.md closure). Carries the
6
6
  load-bearing delivery MUSTs standalone. Dispatched by helpers/deliver-story
7
7
  when delivery.routing.roleScopedAgents is enabled (the default).
8
8
  ---
@@ -22,7 +22,7 @@ description: >-
22
22
  @../../.agents/rules/security-baseline.md
23
23
 
24
24
  You are a **role-scoped Mandrel sub-agent** booted on this focused prompt
25
- alone — no `CLAUDE.md` / `instructions.md` closure is loaded. The security
25
+ alone — no entry-doc (`AGENTS.md` / `CLAUDE.md`) closure is loaded. The security
26
26
  baseline imported above is inviolable. Your role charter begins at the
27
27
  role-delta marker below; the workflow prose your caller hands you supplies
28
28
  the step-by-step. This shared core binds every role:
@@ -21,3 +21,6 @@ Self-check your change against this lens's concerns before you ship:
21
21
  - [ ] Unwired Seams — Coverage Without a Caller (mandatory).
22
22
  - [ ] A produced-but-never-consumed artifact
23
23
  - [ ] An optional field nothing populates
24
+ - [ ] Property-Based Coverage — Invariants Tested Only by Examples.
25
+ - [ ] Toolchain by ecosystem, never one library.
26
+ - [ ] Name the property.
@@ -223,17 +223,9 @@
223
223
  "name": "native"
224
224
  },
225
225
  {
226
- "name": "security-review",
226
+ "name": "code-review",
227
227
  "scopes": ["story"],
228
228
  "optional": true
229
- },
230
- {
231
- "name": "ultrareview",
232
- "scopes": ["story"],
233
- "manualPrompt": true,
234
- "when": {
235
- "label": "risk::high"
236
- }
237
229
  }
238
230
  ],
239
231
  "autoFixSeverity": "medium"
@@ -229,12 +229,12 @@ Everything `/mandrel-deliver` and `single-story-close` consume: worktree isolati
229
229
  | `quality.navigability.routeGlobs` | No | `array<string>` | `[]` | Glob patterns (pages/**, app/**/route.ts) marking paths that add a user-facing route — the route-tree SSOT the navigability lens enumerates and the route-added routing predicate matches against. |
230
230
  | `quality.navigability.navRegistry` | No | `array<string>` | `[]` | Tokens identifying the nav-registry SSOT the navigability lens checks every route resolves a nav door against. |
231
231
  | `quality.navigability.journeySuite` | No | `string` | — | Path or command for the per-persona journey suite /mandrel-deliver's per-Story ceremony runs. |
232
- | `mergeWatch` | No | `object` | — | Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The 30s poll cadence and the cap of 3 behind-the-base updates are fixed. |
232
+ | `mergeWatch` | No | `object` | — | Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The poll cadence is internal (at most 30s while checks run, at most 10s once they are green and the PR is unmerged); it and the cap of 3 behind-the-base updates are not configurable. |
233
233
  | `mergeWatch.mode` | No | `"sync"` \| `"async"` | — | Close-time merge-wait posture (Story #4698). `sync` (default) keeps the in-close foreground merge wait unchanged. `async` caps the per-invocation wait to a short ~60s probe window — long enough to catch an instant merge and, via the head-anchored required-check predicate, an instantly-red required check — then returns the resumable `pending` terminal (exit 3) with a `nextCommand`. Opt in when slow CI makes the foreground wait routinely expire: the worker launches `nextCommand` in the background instead of burning the host tool slot polling. `maxBudgetSeconds` (the cumulative give-up) is unchanged. |
234
234
  | `mergeWatch.maxWaitSeconds` | No | `integer` | — | Per-invocation merge-wait bound (seconds). Default 300 (5 minutes) — chosen to fit inside a single host tool invocation (~10 min ceiling) alongside the close gates that precede the wait. Expiry yields `pending` (exit 3), never a block. Headless callers with no host ceiling raise this to land in one block. |
235
235
  | `mergeWatch.maxBudgetSeconds` | No | `integer` | `3600` | Cumulative wall-clock budget (seconds) across merge-wait resumes, anchored at the PR's createdAt. Default 3600 (60 minutes). Exhausting this classifies the block and transitions the Story to agent::blocked. |
236
236
  | `codeReview` | No | `object` | — | Review-provider chain and on-branch remediation threshold for the /mandrel-deliver code-review ceremony. |
237
- | `codeReview.providers[]` | No | `array<object>` | `[{"name":"native"},{"name":"security-review","scopes":["story"],"optional":true},{"name":"ultrareview","scopes":["story"],"manualPrompt":true,"when":{"label":"risk::high"}}]` | Review-provider chain (Story #2871). When unset or empty, defaults to [{ name: "native" }]. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain. Each item has: name, scopes, optional, manualPrompt, when. |
237
+ | `codeReview.providers[]` | No | `array<object>` | `[{"name":"native"},{"name":"code-review","scopes":["story"],"optional":true}]` | Review-provider chain (Story #2871). When unset or empty, falls back to this default: `native` (scoped lint + MI) then an optional story-scoped `code-review` — a low-effort `claude --print` bug review whose every finding is critical and halts close before auto-merge; hosts without the `claude` CLI skip it. `security-review` and `ultrareview` are opt-in. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain. Each item has: name, scopes, optional, manualPrompt, when. |
238
238
  | `codeReview.autoFixSeverity` | No | `"high"` \| `"medium"` | `"medium"` | Severity threshold for on-branch remediation in /mandrel-deliver Phase 5 (code-review). `medium` (default) routes 🔴/🟠/🟡 findings into the host-LLM focused-fix routing (Mediums batched per lens: one commit per lens, a single validation + rescan at the end) while 🟢 suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover — no back-compat flag. |
239
239
  | `refactorStage` | No | `object` | — | Opt-in, config-gated post-green refactor checkpoint wired into story-deliver (Story #3430, Epic #3418). Strictly additive and default-OFF: when disabled, story-deliver behaves exactly as before. Advisory only — never changes existing close-validation gate semantics. |
240
240
  | `refactorStage.enabled` | No | `boolean` | `false` | When true, story-deliver runs an advisory post-green refactor stage (core/code-review-and-quality skill, Post-Green Refactor Pass) after the suite is green. Default false — when unset the stage is skipped and close-validation gate semantics are unchanged. |
@@ -248,7 +248,7 @@ Everything `/mandrel-deliver` and `single-story-close` consume: worktree isolati
248
248
  | `ci.advisoryAllowlist` | No | `array<string>` | `[]` | Story #5096. Check-run names exempt from blockOnAdvisoryFailure — a red run whose name matches exactly never blocks arming. Matching is exact; an unnamed run can never match and always blocks. |
249
249
  | `ci.rerunAdvisory` | No | `integer` | `0` | Story #5266. How many times close may re-run a failed advisory workflow run before blocking on it, per close invocation. Default 0: close spends no CI minutes and issues no GitHub mutation on an advisory red unless asked. At n > 0 the failed run(s) are re-run within that allowance and the merge wait re-polls inside its existing budget, landing or blocking on the re-run verdict. Overridden per invocation by --rerun-advisory <n>. |
250
250
  | `routing` | No | `object` | — | v2 delivery-spawn routing: role-scoped boot contexts and the ceremony profile. The v1 singleDelivery epic-route kill-switch was removed in Stage 6; the freshCriticSampleRate sampling floor was retired in Story #5313 and the derived-level ceremony routing in Story #5343. |
251
- | `routing.roleScopedAgents` | No | `boolean` | `true` | Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full CLAUDE.md @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate. |
251
+ | `routing.roleScopedAgents` | No | `boolean` | `true` | Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full entry-doc (AGENTS.md or CLAUDE.md) @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate. |
252
252
  | `routing.ceremonyProfile` | No | `"minimal"` \| `"standard"` \| `"strict"` | `"standard"` | Acceptance-ceremony depth — who authors the Story acceptance verdict. minimal and standard (default) = the inline self-eval, whatever the diff touches; strict = a fresh-context maker-blind critic. Review depth is a separate decision and still derives `deep` for any sensitive path (review-depth.js). |
253
253
  | `routing.closeAndLand` | No | `boolean` | `true` | When true (default), single-story-close lands through merge in one close. Opt out per-run with --no-wait-merge. |
254
254
 
@@ -386,15 +386,16 @@ then skips `npm ci` and junctions/symlinks the donor's `node_modules`.
386
386
 
387
387
  #### `delivery.codeReview` — provider chain
388
388
 
389
- `providers[]` is iterated in declaration order (unset/empty →
390
- `[{ name: "native" }]`). Beyond the field list in the generated table: `scopes`
389
+ `providers[]` is iterated in declaration order (unset/empty → `native` plus
390
+ an optional story-scoped `code-review`, the low-effort `claude --print` bug
391
+ review whose every finding is critical and halts close). Beyond the field list in the generated table: `scopes`
391
392
  defaults to both `story` and `epic`; `when` is a label predicate (`when.label`
392
393
  / `when.labelAny`) that silently skips the entry when false; `manualPrompt`
393
394
  entries contribute a one-line suggestion and do **not** affect severity counts
394
395
  or the `halted` gate. **Cross-runtime contract:** manual-prompt providers emit
395
396
  Markdown only and MUST NEVER throw under any host; inline providers that shell
396
- out to a host-specific binary (e.g. `security-review` →
397
- `claude --print /security-review`) SHOULD be declared `optional: true` so
397
+ out to a host-specific binary (e.g. `code-review` / `security-review` →
398
+ `claude --print`) SHOULD be declared `optional: true` so
398
399
  non-Claude consumers can pin the same `.agents/` version unmodified. The fix
399
400
  budget (`maxFixAttempts` / `maxFixScopeFiles`) uses the same values for every
400
401
  Story in a run — there is no per-Story override.
@@ -1180,7 +1180,7 @@
1180
1180
  },
1181
1181
  "mergeWatch": {
1182
1182
  "type": "object",
1183
- "description": "Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The 30s poll cadence and the cap of 3 behind-the-base updates are fixed.",
1183
+ "description": "Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The poll cadence is internal (at most 30s while checks run, at most 10s once they are green and the PR is unmerged); it and the cap of 3 behind-the-base updates are not configurable.",
1184
1184
  "properties": {
1185
1185
  "mode": {
1186
1186
  "type": "string",
@@ -1215,11 +1215,12 @@
1215
1215
  "type": "string",
1216
1216
  "enum": [
1217
1217
  "native",
1218
+ "code-review",
1218
1219
  "codex",
1219
1220
  "security-review",
1220
1221
  "ultrareview"
1221
1222
  ],
1222
- "description": "Registered provider key. Inline: native, codex, security-review. Manual-prompt: ultrareview."
1223
+ "description": "Registered provider key. Inline: native, code-review, codex, security-review. Manual-prompt: ultrareview."
1223
1224
  },
1224
1225
  "scopes": {
1225
1226
  "type": "array",
@@ -1263,23 +1264,15 @@
1263
1264
  },
1264
1265
  "additionalProperties": false
1265
1266
  },
1266
- "description": "Review-provider chain (Story #2871). When unset or empty, defaults to [{ name: \"native\" }]. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain.",
1267
+ "description": "Review-provider chain (Story #2871). When unset or empty, falls back to this default: `native` (scoped lint + MI) then an optional story-scoped `code-review` — a low-effort `claude --print` bug review whose every finding is critical and halts close before auto-merge; hosts without the `claude` CLI skip it. `security-review` and `ultrareview` are opt-in. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain.",
1267
1268
  "default": [
1268
1269
  {
1269
1270
  "name": "native"
1270
1271
  },
1271
1272
  {
1272
- "name": "security-review",
1273
+ "name": "code-review",
1273
1274
  "scopes": ["story"],
1274
1275
  "optional": true
1275
- },
1276
- {
1277
- "name": "ultrareview",
1278
- "scopes": ["story"],
1279
- "manualPrompt": true,
1280
- "when": {
1281
- "label": "risk::high"
1282
- }
1283
1276
  }
1284
1277
  ]
1285
1278
  },
@@ -1367,7 +1360,7 @@
1367
1360
  "properties": {
1368
1361
  "roleScopedAgents": {
1369
1362
  "type": "boolean",
1370
- "description": "Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full CLAUDE.md @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate.",
1363
+ "description": "Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full entry-doc (AGENTS.md or CLAUDE.md) @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate.",
1371
1364
  "default": true
1372
1365
  },
1373
1366
  "ceremonyProfile": {
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://mandrel.dev/schemas/audit-rules.schema.json",
4
4
  "title": "AuditRulesManifest",
5
- "description": "Schema describing the audit-rules.json manifest read by lib/audit-suite/selector.js and lib/audit-suite/runner.js. The manifest enumerates registered audit workflows and the triggers / substitution keys each one accepts, plus the sensitive-path classes that derive review depth from a change set.",
5
+ "description": "Schema describing the audit-rules.json manifest read by lib/audit-suite/selector.js and lib/audit-suite/checklist-threading.js. The manifest enumerates registered audit workflows and the triggers / substitution keys each one accepts, plus the sensitive-path classes that derive review depth from a change set.",
6
6
  "type": "object",
7
7
  "required": ["version", "audits"],
8
8
  "additionalProperties": false,
@@ -204,6 +204,11 @@
204
204
  },
205
205
  "additionalProperties": false
206
206
  },
207
+ "phaseDurations": {
208
+ "type": ["object", "null"],
209
+ "description": "Story #5417 — wall-clock seconds per close phase that ran (wrong-tree-guard, base-sync, close-validation, push, pull-request, code-review, auto-merge, confirm-merge, post-land). Optional and additive; absent or null when no phase was timed.",
210
+ "additionalProperties": { "type": "number", "minimum": 0 }
211
+ },
207
212
  "timestamp": { "type": "string", "format": "date-time" }
208
213
  },
209
214
  "allOf": [
@@ -10,7 +10,7 @@ import fs from 'node:fs';
10
10
  import path from 'node:path';
11
11
  import readline from 'node:readline/promises';
12
12
  import { fileURLToPath } from 'node:url';
13
-
13
+ import { stageLegacyEntryDocRemoval } from './lib/bootstrap/agents-md-fold.js';
14
14
  import {
15
15
  buildManualInstructions,
16
16
  COMMIT_SUBJECT,
@@ -1068,7 +1068,13 @@ export async function offerCommitPush(state, deps = {}) {
1068
1068
  return { ok: true, payload: { commitPush: { action: 'declined' } } };
1069
1069
  }
1070
1070
 
1071
- const staged = stageBootstrapFiles({ projectRoot: cwd, runGit: runGitImpl });
1071
+ const added = stageBootstrapFiles({ projectRoot: cwd, runGit: runGitImpl });
1072
+ // The folded-away CLAUDE.md deletion rides the same commit.
1073
+ const staged = stageLegacyEntryDocRemoval({
1074
+ projectRoot: cwd,
1075
+ runGit: runGitImpl,
1076
+ after: added,
1077
+ });
1072
1078
  if (!staged.ok) {
1073
1079
  Logger.warn(`[Bootstrap] Could not stage the wiring: ${staged.error}`);
1074
1080
  Logger.info(`\n[Bootstrap] ${instructions}`);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * CLI: ratchet gate for documentation context bytes against
3
- * `baselines/context-budget.json`. Measures `alwaysLoaded` (the `CLAUDE.md`
3
+ * `baselines/context-budget.json`. Measures `alwaysLoaded` (the entry-doc
4
4
  * `@`-import closure), `mandatoryRead` (`project.docsContextFiles`) and
5
5
  * `workflow` (entry points + `mandatoryReads:` closure).
6
6
  *
@@ -105,9 +105,7 @@ export class ITicketingProvider {
105
105
  }
106
106
 
107
107
  /**
108
- * Resolve a container parent in one call. Returns `null` (never throws) when
109
- * there is no parent or the backend cannot answer, so a degraded lookup
110
- * never fails a lifecycle edge.
108
+ * `null` means "no parent"; throws when the lookup cannot answer.
111
109
  *
112
110
  * @param {number} _number
113
111
  * @returns {Promise<object|null>}
@@ -1,27 +1,11 @@
1
1
  /**
2
- * Findings severity histogram and per-component baseline rollup deltas.
2
+ * The surviving-Critical halting rule and per-component baseline rollup deltas.
3
3
  * Deltas are per rollup, not per row: row churn that moves no rollup is noise.
4
4
  * Pure, no I/O.
5
5
  */
6
6
 
7
7
  import { groupRows, resolveComponents } from '../baselines/components.js';
8
8
 
9
- /**
10
- * Non-standard severities are ignored.
11
- *
12
- * @param {Array<{ severity?: string }>|null|undefined} findings
13
- * @returns {{ critical: number, high: number, medium: number, low: number }}
14
- */
15
- export function aggregateSummary(findings) {
16
- const summary = { critical: 0, high: 0, medium: 0, low: 0 };
17
- for (const finding of findings ?? []) {
18
- if (Object.hasOwn(summary, finding.severity)) {
19
- summary[finding.severity] += 1;
20
- }
21
- }
22
- return summary;
23
- }
24
-
25
9
  /**
26
10
  * The single halting rule: a surviving Critical finding halts delivery. Every
27
11
  * consumer routes through this rather than re-deriving `critical > 0`.
@@ -8,8 +8,6 @@
8
8
  // biome-ignore-start lint/complexity/useRegexLiterals: typhonjs-escomplex MI workaround
9
9
  const FRONTMATTER_RE = new RegExp(String.raw`^---\r?\n([\s\S]*?)\r?\n---\r?\n`);
10
10
  const NEWLINE_SPLIT_RE = new RegExp(String.raw`\r?\n`);
11
- const PARAGRAPH_SPLIT_RE = new RegExp(String.raw`\r?\n\s*\r?\n`);
12
- const COLLAPSE_WS_RE = new RegExp(String.raw`\s+`, 'g');
13
11
  const SENTENCE_RE = new RegExp(String.raw`[^.!?\n]+[.!?]+`, 'g');
14
12
  // biome-ignore-end lint/complexity/useRegexLiterals: typhonjs-escomplex MI workaround
15
13
 
@@ -42,22 +40,6 @@ export function extractFrontmatter(content) {
42
40
  return fm;
43
41
  }
44
42
 
45
- /**
46
- * @param {string} content
47
- * @returns {string}
48
- */
49
- export function firstProseParagraph(content) {
50
- const stripped = content.replace(FRONTMATTER_RE, '');
51
- for (const block of stripped.split(PARAGRAPH_SPLIT_RE)) {
52
- const trimmed = block.trim();
53
- if (!trimmed) continue;
54
- if (trimmed.startsWith('#')) continue;
55
- if (trimmed.startsWith('---')) continue;
56
- return trimmed.replace(COLLAPSE_WS_RE, ' ');
57
- }
58
- return '';
59
- }
60
-
61
43
  /**
62
44
  * @param {string} text
63
45
  * @returns {string}
@@ -74,13 +56,3 @@ export function clampSummary(text) {
74
56
  }
75
57
  return result;
76
58
  }
77
-
78
- /**
79
- * @param {string} content
80
- * @returns {string}
81
- */
82
- export function summarizeWorkflow(content) {
83
- const fm = extractFrontmatter(content);
84
- const candidate = fm.description?.trim() || firstProseParagraph(content);
85
- return clampSummary(candidate);
86
- }
@@ -7,16 +7,10 @@ export {
7
7
  readAuditRules,
8
8
  } from './checklist-threading.js';
9
9
  export { buildDispatchChecklist } from './dispatch-checklist.js';
10
- export {
11
- countChangedLines,
12
- evaluateLensDiffFloor,
13
- } from './lens-diff-floor.js';
14
- export { runAuditSuite } from './runner.js';
15
10
  export {
16
11
  LENS_TIERS,
17
12
  matchesAnyFilePattern,
18
13
  matchesFilePattern,
19
14
  resolveLensTier,
20
15
  selectAudits,
21
- selectLocalLenses,
22
16
  } from './selector.js';
@@ -45,37 +45,6 @@ export function resolveLensTier(lens) {
45
45
  return scope;
46
46
  }
47
47
 
48
- /**
49
- * Local-tier lenses whose `filePatterns` hit the change set (not
50
- * {@link selectAudits}, which would widen it with keyword matches).
51
- *
52
- * @param {{
53
- * changedFiles?: string[],
54
- * injectedRules?: { audits?: Record<string, object> },
55
- * resolveLensTierFn?: typeof resolveLensTier,
56
- * }} [params]
57
- * @returns {string[]} The matched local-lens identifiers, in manifest order.
58
- */
59
- export function selectLocalLenses({
60
- changedFiles,
61
- injectedRules,
62
- resolveLensTierFn = resolveLensTier,
63
- } = {}) {
64
- const files = Array.isArray(changedFiles) ? changedFiles : [];
65
- if (files.length === 0) return [];
66
-
67
- const rules = injectedRules ?? readAuditRulesSync();
68
- const selected = [];
69
- for (const [lens, entry] of Object.entries(rules.audits ?? {})) {
70
- if (resolveLensTierFn(lens) !== 'local') continue;
71
- const patterns = entry?.triggers?.filePatterns ?? [];
72
- if (matchesAnyFilePattern(patterns, files)) {
73
- selected.push(lens);
74
- }
75
- }
76
- return selected;
77
- }
78
-
79
48
  /**
80
49
  * Manifest `sensitivePaths` classes the change set touches (review depth).
81
50
  *
@@ -0,0 +1,156 @@
1
+ /**
2
+ * CLAUDE.md → AGENTS.md fold for bootstrap and the update migration: a
3
+ * surviving CLAUDE.md would shadow AGENTS.md, since the host prefers it.
4
+ *
5
+ * @module bootstrap/agents-md-fold
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ /** Entry-doc wiring keys idempotence off this exact import path. */
12
+ export const SYSTEM_PROMPT_IMPORT = '@.agents/instructions.md';
13
+
14
+ export const SYSTEM_PROMPT_BLOCK = `## System Prompt
15
+
16
+ ${SYSTEM_PROMPT_IMPORT}
17
+ `;
18
+
19
+ /** Install template for a fresh entry doc. */
20
+ export const SYSTEM_PROMPT_ENTRY_DOC = `# Agent Protocols
21
+
22
+ ${SYSTEM_PROMPT_BLOCK}`;
23
+
24
+ const ENTRY_DOC = 'AGENTS.md';
25
+ export const LEGACY_ENTRY_DOC = 'CLAUDE.md';
26
+
27
+ const SELF_IMPORT = '@AGENTS.md';
28
+
29
+ /** @type {readonly string[]} */
30
+ const BOOTSTRAP_REMOVED_PATHS = Object.freeze([LEGACY_ENTRY_DOC]);
31
+
32
+ /**
33
+ * @param {string} text
34
+ * @returns {string}
35
+ */
36
+ function withTrailingNewline(text) {
37
+ return text.length === 0 || text.endsWith('\n') ? text : `${text}\n`;
38
+ }
39
+
40
+ /**
41
+ * Keep the first system-prompt import line, drop every later one.
42
+ *
43
+ * @param {string[]} lines
44
+ * @returns {string[]}
45
+ */
46
+ function dedupeImport(lines) {
47
+ let seen = false;
48
+ return lines.filter((line) => {
49
+ if (line.trim() !== SYSTEM_PROMPT_IMPORT) return true;
50
+ if (seen) return false;
51
+ seen = true;
52
+ return true;
53
+ });
54
+ }
55
+
56
+ /**
57
+ * `agents` null means AGENTS.md is absent.
58
+ *
59
+ * @param {{ claude: string, agents?: string|null }} input
60
+ * @returns {string} the resulting AGENTS.md content
61
+ */
62
+ function foldEntryDocs({ claude, agents = null }) {
63
+ const claudeBody = claude
64
+ .split('\n')
65
+ .filter((line) => line.trim() !== SELF_IMPORT)
66
+ .join('\n');
67
+ const combined =
68
+ typeof agents === 'string'
69
+ ? `${withTrailingNewline(agents)}\n${claudeBody}`
70
+ : claudeBody;
71
+ const lines = dedupeImport(combined.split('\n'));
72
+ let result = withTrailingNewline(lines.join('\n'));
73
+ if (!lines.some((line) => line.trim() === SYSTEM_PROMPT_IMPORT)) {
74
+ result =
75
+ result.trim().length === 0
76
+ ? SYSTEM_PROMPT_ENTRY_DOC
77
+ : `${result}\n${SYSTEM_PROMPT_BLOCK}`;
78
+ }
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * Fold a root CLAUDE.md into AGENTS.md on disk and delete CLAUDE.md. No-op
84
+ * when CLAUDE.md is absent, so a second pass mutates nothing.
85
+ *
86
+ * @param {string} projectRoot
87
+ * @param {typeof fs} [fsImpl]
88
+ * @returns {{ action: 'folded'|'none', path: string }}
89
+ */
90
+ export function foldClaudeMdIntoAgentsMd(projectRoot, fsImpl = fs) {
91
+ const claudePath = path.join(projectRoot, LEGACY_ENTRY_DOC);
92
+ const agentsPath = path.join(projectRoot, ENTRY_DOC);
93
+ if (!fsImpl.existsSync(claudePath)) {
94
+ return { action: 'none', path: agentsPath };
95
+ }
96
+ const claude = fsImpl.readFileSync(claudePath, 'utf8');
97
+ const agents = fsImpl.existsSync(agentsPath)
98
+ ? fsImpl.readFileSync(agentsPath, 'utf8')
99
+ : null;
100
+ fsImpl.writeFileSync(agentsPath, foldEntryDocs({ claude, agents }), 'utf8');
101
+ fsImpl.rmSync(claudePath, { force: true });
102
+ return { action: 'folded', path: agentsPath };
103
+ }
104
+
105
+ /**
106
+ * Wire the system-prompt import into AGENTS.md, folding a CLAUDE.md in first.
107
+ * Creates, folds, appends, or no-ops.
108
+ *
109
+ * @param {string} projectRoot
110
+ * @param {typeof fs} [fsImpl]
111
+ * @returns {{ action: 'created'|'folded'|'appended'|'already-present', path: string }}
112
+ */
113
+ export function wireEntryDoc(projectRoot, fsImpl = fs) {
114
+ const folded = foldClaudeMdIntoAgentsMd(projectRoot, fsImpl);
115
+ const target = folded.path;
116
+ if (folded.action === 'folded') return folded;
117
+ if (!fsImpl.existsSync(target)) {
118
+ fsImpl.writeFileSync(target, SYSTEM_PROMPT_ENTRY_DOC, 'utf8');
119
+ return { action: 'created', path: target };
120
+ }
121
+ const existing = fsImpl.readFileSync(target, 'utf8');
122
+ if (existing.includes(SYSTEM_PROMPT_IMPORT)) {
123
+ return { action: 'already-present', path: target };
124
+ }
125
+ const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
126
+ fsImpl.writeFileSync(
127
+ target,
128
+ `${existing}${separator}\n${SYSTEM_PROMPT_BLOCK}`,
129
+ 'utf8',
130
+ );
131
+ return { action: 'appended', path: target };
132
+ }
133
+
134
+ /**
135
+ * Stage removal of folded-away entry docs; a failed `after` passes through.
136
+ *
137
+ * @param {{ after?: { ok: boolean, error?: string }, projectRoot: string, runGit: (args: string[], cwd: string) => { ok: boolean, stderr?: string }, fsImpl?: typeof fs }} args
138
+ * @returns {{ ok: boolean, error?: string, removed: string[] }}
139
+ */
140
+ export function stageLegacyEntryDocRemoval({
141
+ projectRoot,
142
+ runGit,
143
+ fsImpl = fs,
144
+ after = { ok: true },
145
+ }) {
146
+ if (!after.ok) return after;
147
+ const removed = BOOTSTRAP_REMOVED_PATHS.filter(
148
+ (rel) => !fsImpl.existsSync(path.join(projectRoot, rel)),
149
+ );
150
+ if (removed.length === 0) return { ok: true, removed };
151
+ const result = runGit(
152
+ ['rm', '--cached', '--ignore-unmatch', '--quiet', '--', ...removed],
153
+ projectRoot,
154
+ );
155
+ return { ok: result.ok, error: result.stderr || 'git rm failed', removed };
156
+ }
@@ -19,7 +19,7 @@ import path from 'node:path';
19
19
  export const BOOTSTRAP_COMMIT_PATHS = Object.freeze([
20
20
  '.agents',
21
21
  '.agentrc.json',
22
- 'CLAUDE.md',
22
+ 'AGENTS.md',
23
23
  '.claude/settings.json',
24
24
  '.gitignore',
25
25
  'package.json',
@@ -62,10 +62,10 @@ export function buildMutationManifest(ctx = {}) {
62
62
  entries.push(
63
63
  {
64
64
  phaseGroup: PHASE_GROUPS.IDE_WIRING,
65
- target: rel('CLAUDE.md'),
65
+ target: rel('AGENTS.md'),
66
66
  action: 'merge',
67
67
  detail:
68
- 'Wire the @.agents/instructions.md system-prompt import so Claude Code hydrates the framework on cold start.',
68
+ 'Wire the @.agents/instructions.md system-prompt import into AGENTS.md (folding any CLAUDE.md into it) so Claude Code hydrates the framework on cold start.',
69
69
  reversible: true,
70
70
  },
71
71
  {