mandrel 1.86.0 → 1.88.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 (40) hide show
  1. package/.agents/docs/SDLC.md +15 -3
  2. package/.agents/docs/configuration.md +2 -0
  3. package/.agents/instructions.md +7 -0
  4. package/.agents/rules/git-conventions.md +13 -1
  5. package/.agents/schemas/agentrc.schema.json +12 -0
  6. package/.agents/scripts/boot-sweep.js +36 -4
  7. package/.agents/scripts/git-cleanup.js +8 -0
  8. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +107 -30
  9. package/.agents/scripts/lib/config/explain.js +4 -0
  10. package/.agents/scripts/lib/config/runners.js +13 -2
  11. package/.agents/scripts/lib/config-settings-schema-delivery.js +7 -0
  12. package/.agents/scripts/lib/config-settings-schema-quality.js +7 -0
  13. package/.agents/scripts/lib/epic-plan-ideation.js +24 -3
  14. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +19 -5
  15. package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +17 -0
  16. package/.agents/scripts/lib/framework-version.js +210 -0
  17. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +7 -22
  18. package/.agents/scripts/lib/orchestration/epic-cleanup.js +41 -5
  19. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +34 -3
  20. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +102 -7
  21. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +85 -1
  22. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +34 -3
  23. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +71 -4
  24. package/.agents/scripts/lib/single-story-sweep.js +60 -5
  25. package/.agents/scripts/lib/story-body/story-body.js +81 -4
  26. package/.agents/scripts/providers/github/tickets.js +18 -1
  27. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +7 -2
  28. package/.agents/skills/core/epic-plan-premortem/SKILL.md +8 -2
  29. package/.agents/skills/skills.index.json +3 -3
  30. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +36 -8
  31. package/.agents/workflows/git-cleanup.md +72 -18
  32. package/.agents/workflows/helpers/acceptance-self-eval.md +23 -1
  33. package/.agents/workflows/helpers/code-review.md +83 -7
  34. package/.agents/workflows/helpers/deliver-epic.md +46 -7
  35. package/.agents/workflows/helpers/epic-audit.md +153 -12
  36. package/.agents/workflows/helpers/parallel-tooling.md +9 -2
  37. package/.agents/workflows/helpers/plan-epic.md +32 -14
  38. package/.agents/workflows/loops/nightly-audit.md +9 -1
  39. package/docs/CHANGELOG.md +22 -0
  40. package/package.json +1 -1
@@ -31,7 +31,12 @@ allowed_tools:
31
31
  Senior Engineer + Architect, acting as a **fresh-context pre-mortem critic** —
32
32
  deliberately *separate* from `epic-plan-decompose-author` (the generator) and
33
33
  `epic-plan-consolidate` (the scope-preserving merge critic) so it is a
34
- fresh-context, code-reading review, not a same-pass self-critique.
34
+ fresh-context, code-reading review, not a same-pass self-critique. The `/plan`
35
+ workflow delivers that fresh context by **dispatching this skill inside a
36
+ genuine sub-agent** (`Agent` tool, `subagent_type: general-purpose`) at Phase
37
+ 8.5, rather than activating it inline in the authoring turn — the sub-agent does
38
+ not inherit the authoring conversation, so its code-reading review is
39
+ independent of the draft it grades.
35
40
 
36
41
  > **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
37
42
  > why this critic opens the actual cited code, why it is additive-recommendation
@@ -52,7 +57,8 @@ surfaces reaches GitHub unreviewed.
52
57
 
53
58
  ## Inputs
54
59
 
55
- The workflow passes the Epic ID as the Skill argument. The Skill itself reads:
60
+ The `/plan` workflow dispatches this skill inside a fresh-context sub-agent,
61
+ passing the Epic ID as the Skill argument. The Skill itself reads:
56
62
 
57
63
  - `temp/epic-<Epic_ID>/tickets.json` — the **draft** (or consolidated) Story
58
64
  array. This is the pre-mortem subject.
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-07-04T23:12:38.376Z",
2
+ "generatedAt": "2026-07-08T12:24:41.101Z",
3
3
  "generator": "generate-skills-index.js@1",
4
4
  "skills": [
5
5
  {
@@ -377,8 +377,8 @@
377
377
  "tier": "stack",
378
378
  "category": "architecture",
379
379
  "path": ".agents/skills/stack/architecture/subagent-orchestration/SKILL.md",
380
- "description": "Coordinates complex tasks via task-isolated subagents. Use when one objective is too large for a single agent or when independent work streams should run concurrently with minimal context bleed. One objective per subagent; summarize before returning to keep the main context window clean.",
381
- "policyCapsuleBullets": 7,
380
+ "description": "Coordinates complex tasks via task-isolated subagents. Use when one objective is too large for a single agent or when independent work streams should run concurrently with minimal context bleed. One objective per subagent; summarize before returning to keep the orchestrator's context window clean. Applies recursively — an orchestrator at any supported nesting depth applies the same policy to its own children.",
381
+ "policyCapsuleBullets": 8,
382
382
  "allowedTools": null,
383
383
  "vendor": null
384
384
  },
@@ -4,11 +4,33 @@ description:
4
4
  Coordinates complex tasks via task-isolated subagents. Use when one objective
5
5
  is too large for a single agent or when independent work streams should run
6
6
  concurrently with minimal context bleed. One objective per subagent;
7
- summarize before returning to keep the main context window clean.
7
+ summarize before returning to keep the orchestrator's context window clean.
8
+ Applies recursively — an orchestrator at any supported nesting depth applies
9
+ the same policy to its own children.
8
10
  ---
9
11
 
10
12
  # Skill: Subagent Orchestration
11
13
 
14
+ ## Recursive orchestration model
15
+
16
+ This skill describes **recursive orchestration**, not a fixed two-tier
17
+ "main agent vs. subagents" split. An **orchestrator** is any agent that
18
+ dispatches sub-agents; a sub-agent is itself an orchestrator over its own
19
+ children. The Claude Code harness carries the `Agent` tool into sub-agents
20
+ (verified nesting depth 2, announced max depth 5; see
21
+ [#2870](https://github.com/dsj1984/mandrel/issues/2870)), so the same
22
+ one-objective / verify / parallelize policy applies **at every level** —
23
+ substitute "orchestrator" for "main agent" and "child" for "subagent"
24
+ throughout and the rules hold unchanged. Keeping a given dispatch level
25
+ flat remains a legitimate **design choice** (e.g. the `/deliver` wave
26
+ loop), but it is no longer forced by a harness limitation.
27
+
28
+ The cost caution compounds with depth: every nesting level re-pays the
29
+ full always-loaded context, so an orchestrator MUST weigh the depth it
30
+ opens against its budget (see
31
+ [`instructions.md` § 4](../../../../instructions.md)) and stay within the
32
+ supported depth envelope.
33
+
12
34
  ## Policy Capsule
13
35
 
14
36
  - Dispatch one objective per subagent; never bundle unrelated goals into a single delegation.
@@ -16,11 +38,13 @@ description:
16
38
  - Specify the expected return format explicitly (JSON summary, diff, bullet list) in every handoff.
17
39
  - Verify the subagent's output before incorporating it; treat returned artifacts as untrusted until checked.
18
40
  - Run non-dependent subagents in parallel; serialize only when one subagent's output is required input for another.
19
- - Require a concise summary back from each subagent to keep the main context window clean.
41
+ - Require a concise summary back from each subagent to keep the orchestrator's context window clean.
20
42
  - Investigate subagent failures rather than retrying blindly with the same prompt.
43
+ - Respect the nesting depth budget; each level opened re-pays the always-loaded context, so orchestrate deeper only when the isolation or parallelism gain justifies the cost.
21
44
 
22
45
  Internal protocol for managing complex tasks through the creation and
23
- coordination of subagents.
46
+ coordination of subagents, applied recursively by the orchestrator at any
47
+ supported depth.
24
48
 
25
49
  ## 1. Core Principles
26
50
 
@@ -28,8 +52,11 @@ coordination of subagents.
28
52
  with multiple unrelated tasks.
29
53
  - **Minimal Context:** Provide only the necessary context (files, docs, specific
30
54
  goal) to keep the subagent focused and token-efficient.
31
- - **Verification:** The main agent must always verify the subagent's output
32
- before incorporating it into the final solution.
55
+ - **Verification:** The orchestrator must always verify each child's output
56
+ before incorporating it into its own result — at every level of the tree.
57
+ - **Depth Awareness:** Orchestration is recursive; before opening a deeper
58
+ level, confirm the work justifies re-paying the always-loaded context and
59
+ that the nesting stays within the supported depth envelope.
33
60
 
34
61
  ## 2. Operation Standards
35
62
 
@@ -38,11 +65,12 @@ coordination of subagents.
38
65
  - **Error Handling:** If a subagent fails or returns an ambiguous result,
39
66
  investigate the failure rather than retrying blindly.
40
67
  - **Parallelism:** Use subagents to perform non-dependent tasks concurrently
41
- (e.g., auditing three different modules simultaneously).
68
+ (e.g., auditing three different modules simultaneously). A child that is
69
+ itself an orchestrator may parallelize its own sub-units the same way.
42
70
 
43
71
  ## 3. Best Practices
44
72
 
45
- - **State Sync:** Ensure the main agent's mental model remains the source of
73
+ - **State Sync:** Ensure the orchestrator's mental model remains the source of
46
74
  truth if multiple subagents modify the codebase.
47
75
  - **Summarization:** Require subagents to provide a concise summary of their
48
- findings to prevent the main context window from being flooded.
76
+ findings to prevent the orchestrator's context window from being flooded.
@@ -18,11 +18,17 @@ confirmation:
18
18
  3. **reap merged local branches** — the existing squash-aware
19
19
  `gh pr list --state merged` + `git branch --merged <base>` sweep,
20
20
  with attached worktrees removed first. Optionally also deletes the
21
- `origin/<branch>` ref when `--remote` is passed. With `--remote`,
22
- the planner additionally enumerates `refs/remotes/origin/*` and
23
- reaps any **remote-only** merged branches — branches whose local
24
- ref is already gone (or never existed) but whose `origin/<branch>`
25
- still points at a merged PR.
21
+ `origin/<branch>` ref when `--remote` is passed. Every default run
22
+ also **enumerates** `refs/remotes/origin/*` and reports any
23
+ **remote-only** merged branches — branches whose local ref is
24
+ already gone (or never existed) but whose `origin/<branch>` still
25
+ points at a merged PR — even without `--remote`; `--remote` is still
26
+ required to *delete* them. A third branch, whose content already
27
+ landed in `<base>` by another route (a squash-merged Epic PR, a
28
+ cherry-pick, a manual `merge --squash`), is caught by a
29
+ **content-equivalence probe** (`git merge-tree --write-tree`,
30
+ git ≥ 2.38) even when it has no merged PR of its own and is not a
31
+ git ancestor of `<base>`.
26
32
  4. **triage `git stash` entries** — list every stash and prompt for
27
33
  `drop / keep / quit` per entry (or pass `--drop-stashes <ref>` for
28
34
  non-interactive use).
@@ -150,6 +156,10 @@ programmatic consumption:
150
156
  "detectedBy": "gh"
151
157
  }
152
158
  ],
159
+ "skipped": [
160
+ { "branch": "story-4200", "reason": "not-merged", "lastCommitAt": "2026-05-01T00:00:00Z" }
161
+ ],
162
+ "ghDegraded": false,
153
163
  "worktrees": [{ "path": "C:/repo/.worktrees/fix-foo", "ok": true, "dirty": false }],
154
164
  "local": [{ "branch": "fix/foo", "ok": true, "alreadyGone": false }],
155
165
  "remote": [{ "branch": "fix/foo", "ok": true, "alreadyGone": true }],
@@ -200,18 +210,51 @@ follow-up prune when `--remote` is set, so passing both is idempotent
200
210
 
201
211
  ### branches
202
212
 
203
- The merged-branch sweep semantics:
204
-
205
- - A branch is a candidate iff it is not `<base>`, not the current
206
- HEAD, not in `git config branch.protectedBranches`, and either has a
207
- merged PR (`gh pr list --head <branch> --state merged`) or appears in
208
- `git branch --merged <base>`.
213
+ The merged-branch sweep recognizes three detection signals, in order:
214
+
215
+ 1. **`detectedBy: 'gh'`** the branch has a merged PR
216
+ (`gh pr list --head <branch> --state all`, classified by the
217
+ **latest** PR's state).
218
+ 2. **`detectedBy: 'git-merged'`** — the branch is a git ancestor of
219
+ `<base>` (`git branch --merged <base>`), or of `origin/<base>` when
220
+ that remote-tracking ref exists (unioned so a stale local `<base>` —
221
+ fast-forward phase skipped, or `--branches` run alone — no longer
222
+ hides a branch already merged on the remote).
223
+ 3. **`detectedBy: 'content-merged'`** (Story #4395) — the branch has no
224
+ reapable PR verdict and is not an ancestor of `<base>` under either
225
+ anchor, but simulating the merge via
226
+ `git merge-tree --write-tree <base> <branch>` (git ≥ 2.38) produces a
227
+ tree identical to `<base>`'s own tree — i.e. applying the branch's
228
+ changes on top of `<base>` is a content no-op. This catches
229
+ `story-<id>` branches merged into `epic/<id>` whose Epic PR
230
+ **squash-merged** to `main` (the story commits are not ancestors of
231
+ `main` and the story branch usually has no PR of its own), and any
232
+ other branch whose content landed via a different route (a renamed
233
+ head, a cherry-pick, a manual `merge --squash`). When git rejects
234
+ `--write-tree` (git < 2.38) or the simulated merge conflicts, the
235
+ probe is inconclusive and the branch keeps its existing `not-merged`
236
+ skip — the signal never guesses. `content-merged` candidates render
237
+ with a "weaker signal — verify before deleting" annotation in the
238
+ dry-run list and are called out separately in the confirmation
239
+ prompt, since — unlike a merged PR or git ancestry — no CI or GitHub
240
+ merge check ever validated this branch's exact diff.
241
+
242
+ Other candidate semantics:
243
+
244
+ - A branch is a candidate iff it is not `<base>`, not the current HEAD,
245
+ not in `git config branch.protectedBranches`, and matches one of the
246
+ three signals above.
209
247
  - When a candidate has an attached worktree, the worktree is removed
210
248
  (force if dirty) **before** `git branch -D`, mirroring the pattern in
211
249
  [`worktree-lifecycle.md`](helpers/worktree-lifecycle.md).
212
250
  - `--remote` is required on top of `--execute` to touch `origin/`.
251
+ - A throwing `gh` runner (auth failure, rate limit, missing binary) no
252
+ longer aborts the run: the branches phase logs one warning and
253
+ continues with the git-only signals (ancestry + content-equivalence).
254
+ The JSON envelope's `ghDegraded: true` records that this happened for
255
+ the run, and the dry-run text carries a matching warning line.
213
256
 
214
- The skip taxonomy distinguishes two unreapable cases:
257
+ The skip taxonomy:
215
258
 
216
259
  - `reason: 'protected'` — the base branch or a name in
217
260
  `git config branch.protectedBranches`. Not reapable; ignore.
@@ -219,15 +262,26 @@ The skip taxonomy distinguishes two unreapable cases:
219
262
  `git checkout <base>`. The dry-run output surfaces a remediation
220
263
  hint so the operator sees the recovery path without having to look
221
264
  in the JSON envelope.
222
-
223
- The `--remote` flag also opts the planner into a **remote-only
265
+ - `reason: 'tip-diverged-from-merge'` — the latest PR merged, but the
266
+ branch's tip has since moved past the merged commit (a post-merge
267
+ force-push). The dry-run line names both SHAs and a remediation hint
268
+ (delete manually via `git branch -D <branch>`, or push the follow-up
269
+ commit).
270
+ - `reason: 'not-merged'` — none of the three detection signals matched.
271
+ Previously silent; the dry-run output now lists every surviving
272
+ `not-merged` branch as a one-line-per-branch summary with its
273
+ last-commit age, so the operator can see why a leftover branch isn't
274
+ reaped instead of hunting for it by hand.
275
+
276
+ Every default run also opts the planner into a **remote-only
224
277
  enumeration pass**: in addition to walking `refs/heads/*`, the planner
225
278
  also walks `refs/remotes/origin/*` and emits candidates for any branch
226
279
  that exists on `origin` with a merged PR but has no local ref. These
227
- candidates carry `detectedBy: 'remote-only'` and `localExists: false`,
228
- and the executor runs only the `git push --delete origin/<branch>`
229
- path for them (no local `git branch -D` is attempted — there is no
230
- local branch to delete).
280
+ candidates carry `detectedBy: 'remote-only'` and `localExists: false`
281
+ and are always shown in the dry-run list; **deleting** them (via the
282
+ `git push --delete origin/<branch>` path no local `git branch -D` is
283
+ attempted, since there is no local branch) still requires `--remote` on
284
+ top of `--execute`, unchanged.
231
285
 
232
286
  ### stashes
233
287
 
@@ -35,7 +35,29 @@ per-criterion, mid-delivery, and evaluates the actual work product.
35
35
  1. **Eval pass (fresh context, independent of the author).** Run a **separate
36
36
  critic pass** — a fresh-context sub-agent (`Agent` tool,
37
37
  `subagent_type: general-purpose`), *not* a continuation of your implementing
38
- turn — so the evaluator does not grade its own homework. The critic:
38
+ turn — so the evaluator does not grade its own homework.
39
+
40
+ > **Inline-critic fallback (nesting-absent harness).** Dispatching the
41
+ > critic as a nested `Agent` is the preferred shape — it gives genuine
42
+ > fresh-context isolation — and works on any harness that carries `Agent`
43
+ > into sub-agents (Claude Code ≥ 2.1.202; see
44
+ > [#2870](https://github.com/dsj1984/mandrel/issues/2870)). This eval loop
45
+ > itself runs inside a Story delivery sub-agent, so the nested critic sits
46
+ > at nesting depth 2. If the host does **not** support nested `Agent`
47
+ > dispatch at that depth — the tool is absent, or a spawn attempt returns
48
+ > an unsupported-capability error — do **not** stall the Story. Fall back
49
+ > to authoring the verdict **inline**: in a deliberately scoped,
50
+ > self-critical pass (re-read only the diff, the `acceptance[]` /
51
+ > `verify[]` arrays, and the `verify[]` command output — treat the
52
+ > implementation reasoning as untrusted and score against the criteria
53
+ > afresh), write the same verdict file described below and hand it to the
54
+ > same `acceptance-eval.js` gate. The fresh-context isolation is weaker in
55
+ > the inline path, but the gate, the schema, the round cap, and the
56
+ > proceed / redraft / block decision are identical — a Story is **never**
57
+ > stranded on a nesting-absent harness. Note in the blocked/friction
58
+ > comment (if you block) that the inline fallback was used.
59
+
60
+ The critic:
39
61
  - Inspects the working diff (`git diff origin/<baseBranch>...HEAD`) and the
40
62
  Story's inline `acceptance[]` / `verify[]` arrays.
41
63
  - **Runs the `verify[]` commands** and consumes their output as **required
@@ -293,12 +293,63 @@ For every finding, provide:
293
293
  fix worked. Keep it tight (≤ 5 sentences); the sub-agent will read the
294
294
  surrounding code itself.
295
295
 
296
+ ### The `## Fixed on-branch` section (Story #4399)
297
+
298
+ Findings that Step 4.5 remediated on `[HEAD_REF]` MUST be rendered under a
299
+ dedicated **`## Fixed on-branch`** heading, **not** in the severity groups
300
+ above. This is the contract seam that keeps remediated findings from
301
+ spawning ghost follow-up issues: the
302
+ [`code-review` graduator](../../scripts/lib/feedback-loop/code-review-graduator.js)
303
+ skips every entry inside this section (both because a fixed entry is
304
+ rendered with a **✅ prefix** — so it carries no leading severity emoji the
305
+ parser would match — and because the parser has an explicit
306
+ Fixed-on-branch section guard).
307
+
308
+ Render each fixed finding as a `✅`-prefixed line naming its original
309
+ severity, the file path in backticks, and the remediating commit SHA, e.g.:
310
+
311
+ ```markdown
312
+ ## Fixed on-branch
313
+
314
+ - ✅ 🟡 Medium: `src/lib/foo.js` — missing edge-case guard added (a1b2c3d)
315
+ - ✅ 🟠 High: `src/api/users.js` — ownership check added (d4e5f6a)
316
+ ```
317
+
318
+ Open (escalated / unfixed) findings stay in their severity group with
319
+ their leading severity emoji so the graduator still files them.
320
+
296
321
  ## Step 4.5 — Focused-fix Routing (host LLM, no automated loop)
297
322
 
298
323
  There is **no runtime auto-fix function** at this phase. The host LLM is
299
- the executor: for each 🔴 / 🟠 finding from Step 4, decide between two
300
- paths and keep the `code-review` structured comment authoritative for
301
- anything not fixed in-place.
324
+ the executor: it decides, per finding, between a focused fix on
325
+ `[HEAD_REF]` and leaving the finding on the `code-review` structured
326
+ comment for the operator.
327
+
328
+ ### Resolve the remediation threshold (Story #4399)
329
+
330
+ Read `delivery.codeReview.autoFixSeverity` from the resolved `.agentrc.json`
331
+ (default **`medium`**; the resolver in
332
+ [`config/runners.js`](../../scripts/lib/config/runners.js) supplies the
333
+ default when the key is absent). The threshold governs **which severities
334
+ route into on-branch remediation** — it never changes the halting rule (a
335
+ surviving 🔴 still stops) or the escalation classes:
336
+
337
+ - **`medium`** (default) — route 🔴 Critical, 🟠 High, **and 🟡 Medium**
338
+ findings into remediation. 🟢 Suggestions stay on the comment (never
339
+ auto-fixed).
340
+ - **`high`** — route only 🔴 Critical and 🟠 High findings, reproducing
341
+ the pre-4399 behavior exactly. 🟡 Medium and 🟢 Suggestion findings stay
342
+ on the comment.
343
+
344
+ Hard cutover per
345
+ [`rules/git-conventions.md`](../../rules/git-conventions.md) § Contract
346
+ Cutovers — no back-compat flag; `high` is opt-in to the old routing.
347
+
348
+ ### 🔴 / 🟠 findings — per-finding ceremony (unchanged)
349
+
350
+ For each 🔴 / 🟠 finding from Step 4, decide between two paths and keep the
351
+ `code-review` structured comment authoritative for anything not fixed
352
+ in-place.
302
353
 
303
354
  1. **Apply a focused fix on `[HEAD_REF]`.** Permitted only when the
304
355
  finding is unambiguously *fixable* (clean remediation, no scope
@@ -328,11 +379,36 @@ anything not fixed in-place.
328
379
  attempt (the equivalent of the prior loop's
329
380
  `validation-regression` / `thrash-detected` exits).
330
381
 
382
+ ### 🟡 Medium findings — batched per-lens ceremony (only when `autoFixSeverity: medium`)
383
+
384
+ When the threshold is `medium`, remediate the fixable 🟡 Medium findings in
385
+ a **batch keyed by owning review lens/pillar** rather than the per-finding
386
+ ceremony above:
387
+
388
+ 1. Group the fixable Mediums by owning lens (the pillar or audit family
389
+ that produced them). A Medium is fixable on the same terms as a 🟠; a
390
+ Medium in any escalation class stays on the comment exactly like a 🟠.
391
+ 2. For each lens, call `assert-branch.js --expected [HEAD_REF]`, stage
392
+ explicit paths only, and make **one focused conventional commit per
393
+ lens** (`fix(<scope>): <description> (review findings batch)`).
394
+ 3. Bounded-attempt semantics extend to the batch: each finding gets **at
395
+ most one** attempt, and a lens's batch commit that would exceed
396
+ `delivery.codeReview.maxFixScopeFiles` routes that lens's findings to
397
+ escalation (`scope-exceeded`) instead of committing.
398
+ 4. After **all** lens batches are committed, run a **single** validation
399
+ pass (`npm run lint` plus the relevant `npm test` slice) and a
400
+ **single** targeted rescan over the touched files. Surviving batched
401
+ findings stay on the comment for Step 5.
402
+
403
+ Record every remediated finding (🟠 or 🟡) in the **"Fixed on-branch"**
404
+ section of the `code-review` comment (Step 4) so it does not graduate to a
405
+ follow-up issue.
406
+
331
407
  Do not invent a programmatic retry budget. The host LLM applies *at most
332
- one* focused-fix attempt per finding before escalating to the operator.
333
- Escalated findings remain on the `code-review` structured comment with
334
- their reason recorded, so Step 5 (and downstream consumers) see exactly
335
- why each one was not auto-remediated.
408
+ one* focused-fix attempt per finding (or per batched finding) before
409
+ escalating to the operator. Escalated findings remain on the `code-review`
410
+ structured comment with their reason recorded, so Step 5 (and downstream
411
+ consumers) see exactly why each one was not auto-remediated.
336
412
 
337
413
  ## Step 4.6 — Cross-phase re-check trigger
338
414
 
@@ -89,9 +89,17 @@ Every other runtime modifier is sourced from the Epic's labels or from
89
89
  - **Single pause point.** Only `agent::blocked` halts execution. No
90
90
  clarifying questions — if stuck, flip to `agent::blocked`, post a
91
91
  friction comment, park.
92
- - **Two-level dispatch.** Host LLM fans out per-Story Agent calls
93
- directly with `subagent_type: general-purpose`. Sub-agents do not
94
- carry the `Agent` tool, so this stays flat.
92
+ - **Flat Story dispatch by design.** Host LLM fans out per-Story Agent
93
+ calls directly with `subagent_type: general-purpose`. Keeping Story
94
+ dispatch flat — the host owns the single fan-out level — is a
95
+ **design choice**, not a harness constraint: the wave aggregator, idle
96
+ watchdog, and merge-lock all assume one host-owned dispatch level. As of
97
+ Claude Code 2.1.202 a level-1 sub-agent **does** carry the `Agent` tool
98
+ and can nest further (verified depth 2, announced max depth 5; see
99
+ [#2870](https://github.com/dsj1984/mandrel/issues/2870)), so a Story
100
+ worker may itself fan out for its own sub-work within that depth budget —
101
+ the Epic wave loop nonetheless stays flat by choice, not because nesting
102
+ is unavailable.
95
103
  - **Operator-merges-PR exit.** Phase 7 opens the PR; the workflow
96
104
  never merges to `main` itself. Phase 8.5 may fire auto-merge when
97
105
  every signal is clean.
@@ -481,8 +489,29 @@ therefore auto-runs its mapped lenses (e.g. a `security`-axis Epic runs
481
489
  low-risk Epic adds nothing. Findings are persisted as an `audit-results`
482
490
  structured comment on the Epic.
483
491
 
484
- - **Any 🔴 Critical Blocker** STOP. Relay to the operator.
485
- - **Only 🟠/🟡/🟢** log as non-blocking and continue.
492
+ The helper's Step 3 remediation is **threshold-aware** (Story #4399): it
493
+ reads `delivery.epicAudit.autoFixSeverity` (default **`medium`**) and, at
494
+ `medium`, routes 🔴/🟠/**🟡** findings into on-branch remediation (Mediums
495
+ batched per lens — one commit per lens, a single validation + overlapping-
496
+ lens rescan at the end) while 🟢 Suggestions still graduate; `high`
497
+ reproduces the pre-4399 Critical/High-only routing. Remediated findings are
498
+ rendered under the comment's `## Fixed on-branch` section so they never
499
+ graduate to follow-up issues. The severity gate below is **unchanged** —
500
+ it keys off the surviving (unfixed) findings.
501
+
502
+ The helper walks the selected roster **serially in-context by default**; when
503
+ the roster carries more than one lens it **may delegate the walk to a single
504
+ audit-orchestrator sub-agent** that fans the already-selected lenses out as
505
+ parallel level-2 agents and returns only the aggregated `audit-results` (see
506
+ [`epic-audit.md` § "Optional: delegate the roster walk to an audit-orchestrator
507
+ sub-agent"](epic-audit.md), within the sub-agent depth budget noted under
508
+ "Flat Story dispatch by design" above). The roster stays fixed upstream, every
509
+ per-lens cost gate is preserved, and the seven sequential-only lenses are **not**
510
+ batch-converted — the fan-out parallelizes across lenses only and never changes
511
+ how any single lens runs internally.
512
+
513
+ - **Any surviving 🔴 Critical Blocker** — STOP. Relay to the operator.
514
+ - **Only 🟠/🟡/🟢 surviving** — log as non-blocking and continue.
486
515
  - **Selector reports `degraded: true`** — STOP. Propagate the
487
516
  `reason`/`detail`, post a friction comment, do not fall back to a
488
517
  full-roster audit.
@@ -514,8 +543,18 @@ emit so a high-risk Epic gets a deeper adversarial pass and a low-risk one a
514
543
  lighter one. Depth is **input-only** — it never changes the findings envelope
515
544
  or the posted comment shape.
516
545
 
517
- - **Any 🔴 Critical Blocker** — STOP. Relay to the operator.
518
- - **Only 🟠/🟡/🟢** log as non-blocking and continue.
546
+ The helper's Step 4.5 focused-fix routing is **threshold-aware**
547
+ (Story #4399): it reads `delivery.codeReview.autoFixSeverity` (default
548
+ **`medium`**) and, at `medium`, routes 🔴/🟠/**🟡** findings into on-branch
549
+ remediation (Mediums batched per lens — one commit per lens, a single
550
+ validation + rescan at the end) while 🟢 Suggestions stay on the comment;
551
+ `high` reproduces the pre-4399 Critical/High-only routing. Remediated
552
+ findings are rendered under the comment's `## Fixed on-branch` section so
553
+ they never graduate to follow-up issues. The severity gate below is
554
+ **unchanged** — it keys off the surviving (unfixed) findings.
555
+
556
+ - **Any surviving 🔴 Critical Blocker** — STOP. Relay to the operator.
557
+ - **Only 🟠/🟡/🟢 surviving** — log as non-blocking and continue.
519
558
 
520
559
  ---
521
560