mandrel 2.40.0 → 2.42.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 (90) hide show
  1. package/.agents/README.md +2 -2
  2. package/.agents/agents/story-worker.md +24 -14
  3. package/.agents/docs/agentrc-reference.json +7 -2
  4. package/.agents/docs/configuration.md +5 -2
  5. package/.agents/instructions.md +5 -6
  6. package/.agents/rules/api-conventions.md +43 -7
  7. package/.agents/rules/ci-remediation.md +3 -14
  8. package/.agents/rules/gherkin-standards.md +21 -6
  9. package/.agents/rules/git-conventions.md +6 -5
  10. package/.agents/rules/security-baseline.md +6 -7
  11. package/.agents/rules/testing-standards.md +75 -198
  12. package/.agents/schemas/agentrc.schema.json +17 -2
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +68 -3
  15. package/.agents/scripts/coverage-capture.js +25 -8
  16. package/.agents/scripts/install-matrix-assert.js +2 -2
  17. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +6 -0
  18. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +7 -2
  19. package/.agents/scripts/lib/baselines/git-base.js +74 -38
  20. package/.agents/scripts/lib/close-validation/gates.js +153 -25
  21. package/.agents/scripts/lib/close-validation/process.js +30 -1
  22. package/.agents/scripts/lib/close-validation/runner.js +5 -0
  23. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +33 -12
  24. package/.agents/scripts/lib/config/quality.js +36 -21
  25. package/.agents/scripts/lib/config-settings-schema-delivery.js +6 -0
  26. package/.agents/scripts/lib/coverage-capture-incremental.js +12 -6
  27. package/.agents/scripts/lib/crap-baseline-join.js +11 -7
  28. package/.agents/scripts/lib/full-suite-lock.js +311 -0
  29. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  30. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +11 -104
  31. package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +320 -0
  32. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
  33. package/.agents/scripts/lib/orchestration/dependency-candidates.js +139 -0
  34. package/.agents/scripts/lib/orchestration/epic-candidates.js +159 -0
  35. package/.agents/scripts/lib/orchestration/epic-checklist.js +103 -0
  36. package/.agents/scripts/lib/orchestration/epic-container.js +18 -2
  37. package/.agents/scripts/lib/orchestration/plan-context.js +97 -36
  38. package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +80 -0
  39. package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +192 -0
  40. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +5 -1
  41. package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +164 -0
  42. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +14 -2
  43. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +26 -5
  44. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +83 -4
  45. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +39 -7
  46. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +70 -18
  47. package/.agents/scripts/lib/orchestration/ticket-validator.js +11 -1
  48. package/.agents/scripts/lib/orchestration/verify-credit.js +207 -0
  49. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +24 -0
  50. package/.agents/scripts/plan-persist.js +60 -1
  51. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +5 -10
  52. package/.agents/skills/core/browser-testing-with-devtools/reference.md +7 -46
  53. package/.agents/skills/core/code-review-and-quality/SKILL.md +0 -5
  54. package/.agents/skills/core/documentation-and-adrs/SKILL.md +0 -3
  55. package/.agents/skills/core/gates-and-baselines/SKILL.md +10 -137
  56. package/.agents/skills/core/gates-and-baselines/reference.md +103 -0
  57. package/.agents/skills/core/idea-refinement/SKILL.md +2 -18
  58. package/.agents/skills/core/security-and-hardening/SKILL.md +2 -4
  59. package/.agents/skills/core/security-and-hardening/reference.md +0 -70
  60. package/.agents/skills/skills.index.json +10 -70
  61. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +4 -10
  62. package/.agents/skills/stack/qa/gherkin-authoring/reference.md +9 -113
  63. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +29 -154
  64. package/.agents/skills/stack/qa/qa-harness/SKILL.md +157 -98
  65. package/.agents/workflows/git-cleanup.md +3 -2
  66. package/.agents/workflows/git-deliver.md +3 -2
  67. package/.agents/workflows/helpers/acceptance-self-eval.md +12 -0
  68. package/.agents/workflows/helpers/deliver-digest.md +31 -10
  69. package/.agents/workflows/helpers/deliver-story-reference.md +50 -30
  70. package/.agents/workflows/helpers/deliver-story.md +23 -21
  71. package/.agents/workflows/helpers/plan-reference.md +82 -2
  72. package/.agents/workflows/mandrel-plan.md +45 -45
  73. package/.agents/workflows/qa-assist.md +20 -17
  74. package/.agents/workflows/qa-explore.md +30 -29
  75. package/.agents/workflows/qa-run.md +2 -1
  76. package/docs/CHANGELOG.md +32 -0
  77. package/package.json +1 -1
  78. package/.agents/rules/changelog-style.md +0 -180
  79. package/.agents/rules/shell-conventions.md +0 -61
  80. package/.agents/scripts/lib/qa/coverage-verdict.js +0 -214
  81. package/.agents/skills/core/api-and-interface-design/SKILL.md +0 -55
  82. package/.agents/skills/core/api-and-interface-design/reference.md +0 -76
  83. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +0 -45
  84. package/.agents/skills/core/debugging-and-error-recovery/reference.md +0 -56
  85. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +0 -54
  86. package/.agents/skills/core/idea-refinement/refinement-criteria.md +0 -155
  87. package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +0 -15
  88. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +0 -105
  89. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +0 -152
  90. package/.agents/skills/stack/qa/vitest/SKILL.md +0 -22
@@ -40,7 +40,7 @@ that dispatched the work**, never to a spawned worker.
40
40
 
41
41
  **A worker returning no terminal envelope is expected, not a failure** — only
42
42
  Step 3 mints one. Never re-dispatch the Story on it; resume per § Recovery
43
- (reference § Idempotence and the standing constraints).
43
+ (reference § Idempotence).
44
44
 
45
45
  ## Step 0 — Initialize (`single-story-init.js`)
46
46
 
@@ -73,12 +73,8 @@ One branch, one PR to `main`, commits against the inline `acceptance[]` /
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
75
  2. Implement and commit on the Story branch, iterating with quick advisory
76
- gates (`typecheck`, `lint`, scoped tests) — the full chain runs in Step 3.
77
- 3. Run the full suite once in the worktree **before Step 1a**: repo-invariant
78
- guards outside the Story's scoped greps are the failure class that bounces
79
- deliveries. Fix and commit first, then run the self-eval loop. Run it **so
80
- Step 3 credits it** — a bare `npm test` records nothing, so close re-runs
81
- the identical suite (reference § Step 1, "Pre-eval full-suite discipline").
76
+ gates (`typecheck`, `lint`, scoped tests) — the full chain runs in Step 3,
77
+ and the **one** creditable full-suite run at Step 2.5.
82
78
 
83
79
  ### Step 1a — Bounded acceptance self-eval loop (**required**)
84
80
 
@@ -94,14 +90,21 @@ Ceremony is `delivery.routing.ceremonyProfile` × the **derived change level**,
94
90
  never a planner-authored verdict. **Digest § 3** is the incantation (change set
95
91
  once, derive the level, resolve critics with `ceremony-routing.js`); edge cases
96
92
  are reference § Step 2. Hard gates always run in Step 3 — the derived level
97
- never disables them; do **not** pre-run the chain here.
93
+ never disables them; do **not** pre-run the chain here — Step 2.5's credited
94
+ suite run is the sole exception.
98
95
 
99
- ### Step 2.5 — Push and hand off (sub-agent dispatch only)
96
+ ### Step 2.5 — The creditable full-suite run, then push and hand off
100
97
 
101
- Push `story-<storyId>` to `origin` and confirm the remote ref moved. Return the
102
- hand-off Story id, `workCwd`, branch, pushed head SHA, self-eval verdict,
103
- `verify[]` evidence then stop. Do not open the PR; do not compose a terminal
104
- envelope. An inline run skips this.
98
+ Run the full suite **once**, after the self-eval loop's last fix commit and
99
+ immediately **before** the push, in the shape close credits (**digest § 5**):
100
+ the credit is keyed on the tree, so any later commit invalidates it, and a bare
101
+ `npm test` deposits none. Red → fix, commit, re-run. An inline run makes the
102
+ same run before Step 3.
103
+
104
+ Then (sub-agent dispatch only) push `story-<storyId>` to `origin`, confirm the
105
+ remote ref moved, and return the hand-off — Story id, `workCwd`, branch, pushed
106
+ head SHA, self-eval verdict, `verify[]` evidence — then stop. Do not open the
107
+ PR; do not compose a terminal envelope.
105
108
 
106
109
  ## Step 3 — Close and land (`single-story-close.js`)
107
110
 
@@ -115,7 +118,7 @@ node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <
115
118
  **The whole delivery tail** — gates, PR, merge wait, `agent::done` flip,
116
119
  post-land tail in one process. Never background it, never delegate it to a
117
120
  child, and never end your turn while it is still running: "close is running"
118
- is not a return value. Branch on the envelope's `status` per **digest § 5**
121
+ is not a return value. Branch on the envelope's `status` per **digest § 6**
119
122
  (`landed` → Step 7; `pending` → run `nextCommand`; `blocked`/`checks-failed`
120
123
  → Step 4; `failed` → diagnose, re-run). Gate output is captured.
121
124
 
@@ -126,7 +129,7 @@ Internals, merge-wait budgets, the slow-CI **async** confirm mode, the
126
129
 
127
130
  Relay the validated envelope close emits between its
128
131
  `--- STORY DELIVER TERMINAL ---` markers — never free-form prose, never a
129
- hand-composed object. Statuses, exits and fields: **digest § 5** (SSOT: the
132
+ hand-composed object. Statuses, exits and fields: **digest § 6** (SSOT: the
130
133
  shipped [schema](../../schemas/story-deliver-terminal.schema.json)).
131
134
  `pending` is the only sanctioned no-merge ending.
132
135
 
@@ -139,15 +142,14 @@ so only a green on a NEW head SHA re-arms it — a re-run is refused; fix at sou
139
142
  and push ([`rules/ci-remediation.md`](../../rules/ci-remediation.md)). And a
140
143
  `tail.*: false` degrades the report, never the land.
141
144
 
142
- **Watch exit codes** — `pr-watch-with-update.js` exits 0 green, 1 only when a
143
- required check genuinely failed (or the PR is unreadable), 2 slow-but-not-red:
144
- still-running, unresolved, **or** `notYetStarted` no required context attached
145
- inside `attachWindowMs`. Never route a 2 onto the red path; nothing is broken
146
- and no digest exists to read.
145
+ **Watch exit codes** — `pr-watch-with-update.js` exits 0 green, 1 on a genuine
146
+ red, 2 slow-but-not-red (still-running, unresolved, or `notYetStarted`). Never
147
+ route a 2 onto the red path: nothing is broken and no digest exists to read.
148
+ Which slow condition, and what to do: reference § Step 4.
147
149
 
148
150
  **Lost envelope first: read it off disk.** Close persists each to
149
151
  `temp/orchestration/story-deliver-terminal-<storyId>.json`; branch on it per
150
- digest § 5. Otherwise do not guess — probe **read-only** with
152
+ digest § 6. Otherwise do not guess — probe **read-only** with
151
153
  `node .agents/scripts/deliver-recover.js --story <storyId>`; it prints the
152
154
  **one** next command with its evidence, never a menu. A live close answers
153
155
  `close-in-flight`: wait, never re-init underneath it.
@@ -234,7 +234,7 @@ Each `stories.json` entry: `slug` (`^[a-z0-9][a-z0-9-]*$`), `type: "story"`,
234
234
  `title`, `body` (`goal`, optional `spec`, `changes[{path, assumption}]` —
235
235
  `creates|refactors-existing|deletes`, `non_goals`, `reason_to_exist`),
236
236
  top-level `acceptance[]`, `verify[]` (`… (unit|contract|e2e|validate)`), and
237
- `depends_on[]` (N>1 only).
237
+ `depends_on[]` (a sibling slug, or `#<id>` for an existing open Story).
238
238
 
239
239
  Nothing in that shape inventories the repo for the author. `changes[]` arrives
240
240
  pre-resolved against the working tree, and Phase 8's
@@ -414,7 +414,53 @@ That is the whole point of running it first: a dry run that comes back clean
414
414
  has already paid for every deterministic refusal, so the real persist has
415
415
  nothing left to discover except network failure.
416
416
 
417
- ## The container Epic (Gate #3, N>2)
417
+ ## The container Epic (Gate #3)
418
+
419
+ Gate #3 has two branches, in this order: **adopt** an Epic that already exists,
420
+ else **create** a new one.
421
+
422
+ ### Adopting an open Epic (any N)
423
+
424
+ `epicCandidates[]` in the plan-context envelope lists **every open `type::epic`
425
+ issue**, each `{ id, title, url, score, childIds }`, ranked by token overlap
426
+ between the seed and the Epic's title, `## Goal` and its children's titles. The
427
+ list is deliberately **complete rather than thresholded**: a low score is
428
+ evidence for the operator to weigh, and hiding a candidate is exactly how a
429
+ plan opens its second container for one body of work.
430
+
431
+ This branch fires at **any N, N=1 included** — "add this to the Epic we started
432
+ last week" is the single-Story case, and refusing it below three Stories would
433
+ leave the common follow-up plan with nowhere to file itself. The three-Story
434
+ threshold governs **creation** only, where it still holds: at two Stories a
435
+ pair of ids is as easy to carry as one container id.
436
+
437
+ On a yes, pass `--epic <id>`. Persist then:
438
+
439
+ 1. resolves the id **before the first create** (dry run included) — it must be
440
+ **open** and carry `type::epic`, or the run hard-errors having written
441
+ nothing;
442
+ 2. after the Stories exist, appends one `- [ ] #N` row per new Story to the
443
+ Epic's checklist via `appendEpicChildIds`, which is idempotent and preserves
444
+ existing rows' **checked state**, their order, and the fingerprint marker;
445
+ 3. mirrors a native sub-issue edge per child.
446
+
447
+ The refusal posture is the **opposite** of creation's, deliberately. Creation
448
+ degrades (an unensurable label just skips the container) because the operator
449
+ never named one. Adoption cannot: the operator named a specific id, so silently
450
+ not adopting it would leave them believing their Stories were filed somewhere
451
+ they were not. Hence a hard error, raised while nothing has been written and
452
+ the fix is free. Once the Stories are live the posture flips back — a failed
453
+ checklist write or sub-issue edge only warns.
454
+
455
+ **Only open Epics are adoptable.** A closed Epic is a finished body of work;
456
+ joining one would reopen a container the epilogue deliberately closed and
457
+ re-scope a completed plan. Open a new container, or reopen the old one by hand.
458
+
459
+ `--epic` and `--epic-title`/`--epic-goal` are **mutually exclusive** — a run
460
+ either joins a container or opens one — and supplying both is a usage error
461
+ raised before any I/O.
462
+
463
+ ### Creating a new container (N>2)
418
464
 
419
465
  Above two Stories, `/mandrel-plan` offers to group them under one `type::epic`
420
466
  container. Confirmed, persist opens it **after** the Stories — its body embeds
@@ -442,6 +488,40 @@ existing Epic carrying the same fingerprint, which is keyed on the title **and
442
488
  the exact child set**, so a run grouping different Stories never adopts the
443
489
  wrong container.
444
490
 
491
+ ## Cross-plan `depends_on` (`#<id>`)
492
+
493
+ A `depends_on[]` entry is read **lexically**: `some-slug` is a sibling inside
494
+ this plan, `#<id>` an **external** blocker already live on the tracker. The
495
+ second form is what lets a plan authored today wait on a Story an earlier plan
496
+ opened and never had to name.
497
+
498
+ `dependencyCandidates[]` in the envelope is the advisory prompt for it: open
499
+ `type::story` issues whose declared `changes[]` footprint intersects the seed's
500
+ `complexitySignals.predictedPaths`, each carrying the `overlappingPaths[]` that
501
+ matched. Overlap is computed on **declared footprints** and not on prose, using
502
+ the same `storyFootprint` the wave runner uses to withhold colliding Stories at
503
+ dispatch — so the planner sees the collision the runtime would later enforce,
504
+ one layer earlier and while it is still cheap to order around. A seed naming no
505
+ paths short-circuits to `[]` with no provider call at all.
506
+
507
+ It stays advisory: two Stories can touch a shared barrel file with no real
508
+ ordering between them, and only the operator knows.
509
+
510
+ External refs are **excluded from sibling ordering and cycle detection** —
511
+ a Story already open is not scheduled by this run, so it has no position in the
512
+ topological sort and cannot close a cycle back into a Story that does not exist
513
+ yet. They are validated **before any create** (dry run included): each must
514
+ resolve to an **open `type::story`**, and a closed issue, a container Epic, a
515
+ missing id or a non-Story hard-errors with every bad ref named in one pass. The
516
+ strictness is the point — a blocker that can never be satisfied reads to the
517
+ delivery engine as a permanent wedge rather than as an error worth reporting.
518
+
519
+ Persist renders the entry unchanged as a `blocked by #<id>` footer line and
520
+ mirrors the native `blocked_by` edge, which is the same pair of surfaces a
521
+ sibling edge produces. `/mandrel-deliver` therefore gates on it with no engine
522
+ change: `resolve-stories.js` has always resolved foreign blockers from live
523
+ state.
524
+
445
525
  ## Ready means fully persisted
446
526
 
447
527
  `agent::ready` is the **terminal** step, not part of the creating POST.
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  description:
3
3
  Unified planning entry point. Interrogate → author → persist. Emits one
4
- Story by default; splits into N>1 only under the default-single split
5
- policy.
4
+ Story by default; splits into N>1 only under the default-single split policy.
6
5
  ---
7
6
 
8
7
  # /mandrel-plan
@@ -19,20 +18,20 @@ mode from what the operator typed, announce it, act**:
19
18
  | Invocation | Mode | Behavior |
20
19
  | --- | --- | --- |
21
20
  | `/mandrel-plan` | ask | Ask what to plan; nothing runs first. |
22
- | `/mandrel-plan add a --json flag to doctor` | seed | Ideation from prose: interrogate author **one Story by default** → persist. |
21
+ | `/mandrel-plan add a --json flag to doctor` | seed | Ideation from prose → **one Story by default** → persist. |
23
22
  | `/mandrel-plan temp/notes/idea.md` | seed-file | Same, from notes. An existing file is a path, not prose. |
24
23
  | `/mandrel-plan 4712[,4713…]` | tickets | Fetch issue(s), analyze into proper Stories (prefer N=1 rewrite). |
25
- | `/mandrel-plan 4712`, already delivered | amends | Amend a shipped Story from a **delta envelope**, not a re-interrogation. |
24
+ | `/mandrel-plan 4712`, already delivered | amends | Amend a shipped Story from a **delta envelope**. |
26
25
 
27
26
  **Resolving a bare id.** Read live state rather than asking: `agent::done` can
28
- only be amended, an open unplanned issue only planned. **Announce the derivation** —
29
- "4712 is `agent::done` → amending". Ask **only** for an open Story already at
30
- `agent::ready`.
27
+ only be amended, an open unplanned issue only planned. **Announce the
28
+ derivation** — "4712 is `agent::done` → amending". Ask **only** for an open
29
+ Story already at `agent::ready`.
31
30
 
32
31
  ## Saying what you want
33
32
 
34
33
  No flags to remember — state intent; the workflow fills in the CLI
35
- ([reference](helpers/plan-reference.md)). Run scripts with `--help`.
34
+ ([ref](helpers/plan-reference.md)). Run scripts with `--help`.
36
35
 
37
36
  `--yes` is **runner-set, never operator-typed** — cron, `/loop`, and headless
38
37
  dispatch set it to mean *nobody is at the keyboard*, which auto-proceeds the
@@ -42,8 +41,7 @@ gates below (#1 and #2) and skips #3. Never offer it to an operator.
42
41
 
43
42
  Author **one Story** unless the pieces have **near-zero overlap** or sit across
44
43
  an **architectural seam**. Coupled work stays one Story — `## Slicing`
45
- checkpoints, not sibling tickets
46
- ([detail](helpers/plan-reference.md)). **N=1 is lean.**
44
+ checkpoints, not sibling tickets ([ref](helpers/plan-reference.md)).
47
45
 
48
46
  ## Procedure
49
47
 
@@ -52,19 +50,21 @@ checkpoints, not sibling tickets
52
50
  ```bash
53
51
  node .agents/scripts/plan-context.js --seed "<seed>" \
54
52
  --out temp/plan-<slug>/plan-context.json
55
- # or: --seed-file <path> | --tickets 123,456 | --amends #<id>
53
+ # or: --seed-file <path> | --tickets 123,456 | --amends #<id>
56
54
  ```
57
55
 
58
56
  **Always pass `--out`.** Persist auto-discovers the envelope from `--plan-dir`
59
- and derives source ids from its `sourceTickets[]`; the CLI also writes
57
+ and derives source ids from its `sourceTickets[]`; it also writes
60
58
  **`stories.template.json`**, step 2's skeleton.
61
59
 
62
60
  The envelope carries docs context, the story-author prompt, `sourceTickets[]`,
63
- `duplicates[]` (open **Stories**, never Epics) and advisory `complexitySignals`
64
- (**no routing authority**). A trivial scope can claim the lite route at
65
- persist shape-validated, failing closed to `full` ([detail](helpers/plan-reference.md)).
61
+ `duplicates[]` (open **Stories**, never Epics), `epicCandidates[]` +
62
+ `dependencyCandidates[]` (Gate #3; path collisions) and advisory
63
+ `complexitySignals` (**no routing authority**). A trivial scope can claim the
64
+ lite route at persist — shape-validated, failing closed to `full`
65
+ ([ref](helpers/plan-reference.md)).
66
66
 
67
- **Triage each unknown by resolver** ([detail](helpers/plan-reference.md)): an
67
+ **Triage each unknown by resolver** ([ref](helpers/plan-reference.md)): an
68
68
  **AFK** unknown (research settles it) is resolved before authoring, never
69
69
  assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
70
70
  operator questions — AFK unknowns are still researched; only HITL unknowns land
@@ -74,26 +74,26 @@ in Key Assumptions, each a decision-made-by-default.
74
74
  duplicate-candidate review. Under `--yes`, auto-proceed.
75
75
 
76
76
  On a truthy `memoryPoolAdvisory.recommend`, name
77
- [`/memory-consolidate`](memory-consolidate.md) quoting its `reasons[]` —
78
- advisory, never invoked here.
77
+ [`/memory-consolidate`](memory-consolidate.md) quoting its `reasons[]`;
78
+ advisory.
79
79
 
80
80
  On a truthy `deliverLightSuggestion.suggested`, offer — advisory, never an
81
81
  automatic reroute — to deliver the seed instead; on confirm route **in this
82
82
  session** into [`helpers/deliver-light.md`](helpers/deliver-light.md), its gate
83
83
  filled from this envelope. A truthy `complexitySignals.uiSurface` names
84
- [`/prototype`](prototype.md) as an option; never invoke it here.
85
- [Both offers](helpers/plan-reference.md).
84
+ [`/prototype`](prototype.md); never invoke it here.
85
+ [Both](helpers/plan-reference.md).
86
86
 
87
87
  ### 2. Author
88
88
 
89
- **One-shot authoring.** Start from `stories.template.json`; author
90
- `stories.json` in one pass. `body` is markdown **or** a structured object;
89
+ **One-shot authoring.** From `stories.template.json`, author `stories.json`
90
+ in one pass. `body` is markdown **or** a structured object;
91
91
  persist parses either, serializes canonical markdown and syncs top-level
92
- `acceptance[]` / `verify[]` in — never dual-author those lists.
92
+ `acceptance[]` / `verify[]` in — never dual-author them.
93
93
 
94
94
  **Grounding = your reads + Phase 8.** Nothing inventories the repo: read each
95
- file you cite; persist hard-errors on any `{path, assumption}` absent from
96
- the tree. Fields: [reference](helpers/plan-reference.md).
95
+ file you cite; persist hard-errors on any `{path, assumption}` absent from the
96
+ tree. Fields: [ref](helpers/plan-reference.md).
97
97
 
98
98
  Artifacts under `temp/plan-<slug>/`: `stories.json` (**length 1 by default**;
99
99
  over-budget Specs fail closed — split or tighten, never under `docs/`); optional
@@ -112,10 +112,10 @@ node .agents/scripts/plan-critics.js \
112
112
  [--tech-spec temp/plan-<slug>/techspec.md]
113
113
  ```
114
114
 
115
- Run **before** persist — the last point a finding folds into a re-author.
116
- It exits 0 on **any** verdict (verdicts route work, they do not gate) and
117
- exits **1** only on a usage/IO error — no critic ran: **do not proceed to
118
- Persist**, fix and re-run.
115
+ Run **before** persist — the last point a finding folds into a re-author. It
116
+ exits 0 on **any** verdict (verdicts route work, they do not gate) and exits
117
+ **1** only on a usage/IO error — no critic ran: **do not proceed to Persist**,
118
+ fix and re-run.
119
119
 
120
120
  - **Both `dispatch: false`** — proceed to Persist (each skip is ledgered).
121
121
  - **Either `dispatch: true`** — dispatch **one fresh-context, maker-blind
@@ -130,12 +130,12 @@ Persist**, fix and re-run.
130
130
  **Gate #2** — STOP for approval before persist **only** when the operator asked
131
131
  to review (`--force-review`). Under `--yes`, auto-proceed.
132
132
 
133
- **Gate #3 (N>2 only)** offer a **container Epic**; on a yes add
134
- `--epic-title` / `--epic-goal` below. It carries nothing a child does not
135
- ([shape](helpers/plan-reference.md)). Never below 3 Stories, never unasked.
133
+ **Gate #3 adopt, else create.** Offer the top `epicCandidates[]` Epic at
134
+ **any N** (`--epic <id>`); else, at **N>2**, a new container (`--epic-title` /
135
+ `--epic-goal`). Never unasked ([ref](helpers/plan-reference.md)).
136
136
 
137
- Run persist with `--dry-run` **first** — same command, writes suppressed; every
138
- gate runs before the first `createIssue` ([list](helpers/plan-reference.md)):
137
+ Run persist `--dry-run` **first** — same command, writes suppressed; every gate
138
+ runs before the first `createIssue` ([list](helpers/plan-reference.md)):
139
139
 
140
140
  ```bash
141
141
  node .agents/scripts/plan-persist.js \
@@ -144,28 +144,28 @@ node .agents/scripts/plan-persist.js \
144
144
  [--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
145
145
  [--tech-spec temp/plan-<slug>/techspec.md] \
146
146
  [--source-tickets 123,456] \
147
- [--epic-title "<name>" --epic-goal "<one paragraph>"]
147
+ [--epic <id> | --epic-title "<name>" --epic-goal "<one paragraph>"]
148
148
  ```
149
149
 
150
- At lite shape `--chain-on-clean` folds a clean dry-run into the real persist;
151
- a full plan keeps its review trip.
150
+ At lite shape `--chain-on-clean` folds a clean dry-run into the persist; a full
151
+ plan keeps its review trip.
152
152
 
153
153
  Persist creates `type::story` issue(s), a **metadata-only** `plan-run::<id>`
154
- label, `blocked by #<id>` footers for N>1 `depends_on` edges, and on a confirmed
155
- Gate #3 a `type::epic` container linking every created Story.
156
- `agent::ready` is the **terminal** flip after receipts land; stdout is pure
157
- JSON. Tickets mode also comments on and closes each source id
158
- ([detail](helpers/plan-reference.md)).
154
+ label, `blocked by #<id>` footers for every `depends_on` edge, and on a Gate #3
155
+ yes links them under a `type::epic` container (adopted or new). `agent::ready`
156
+ is the **terminal** flip after receipts land; stdout is pure JSON. Tickets mode
157
+ also comments on and closes each source id ([ref](helpers/plan-reference.md)).
159
158
 
160
159
  ## Constraints
161
160
 
162
161
  - `/mandrel-plan` starts delivery **only** through a confirmed Gate #1 light
163
- route — never off its own authored Stories, which land via
162
+ route — never off its Stories, which land via
164
163
  [`/mandrel-deliver`](mandrel-deliver.md).
165
164
  - Duplicate search targets open Stories (`type::story`), not Epics; and
166
165
  deterministic gates still fail closed under `--yes`.
167
- - A container Epic is never a work item, and no Story body gains an `Epic: #N`
168
- footer: linkage is parent→child only.
166
+ - A container Epic is never a work item and only an **open** one is adoptable;
167
+ no Story body gains an `Epic: #N` footer (linkage is parent→child only).
168
+ - `depends_on` takes a sibling slug or `#<id>` (open Story).
169
169
 
170
170
  ## See also
171
171
 
@@ -42,7 +42,8 @@ Constraints delta.
42
42
  > triage-ready finding without breaking stride — then, when the testing pass is
43
43
  > done, turned into a plan in one batch.
44
44
  >
45
- > **Skills**: `core/qa-coverage-mapping`
45
+ > **Rules**: [`testing-standards.md`](../rules/testing-standards.md)
46
+ > (§ The Three Tiers — the tier-classification rules enrichment applies)
46
47
 
47
48
  ## Role framing
48
49
 
@@ -106,7 +107,8 @@ ledger is hard-gated.**
106
107
 
107
108
  Goal: become the operator's QA assistant before any observation arrives.
108
109
 
109
- 1. Re-read the QA role framing and `core/qa-coverage-mapping` skill.
110
+ 1. Re-read the QA role framing and the tier-classification rules in
111
+ [`testing-standards.md`](../rules/testing-standards.md) (§ The Three Tiers).
110
112
  2. **Load codebase context.** Read the files in `project.docsContextFiles`
111
113
  (architecture, decisions, patterns) and, when the testing touches UI/routing,
112
114
  `docs/style-guide.md` / `docs/web-routes.md`. This is the context you will
@@ -176,17 +178,16 @@ every decision to the shared helpers; never re-derive them in prose.
176
178
  `gh issue view <ticketNumber> --json title,body,labels`, and verify each
177
179
  surface-map path resolves with `git cat-file -e HEAD:<path>` — flag every
178
180
  miss rather than citing a path that does not exist.
179
- 4. **Compute the coverage verdict** for the surface the observation points at,
180
- via [`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js) the
181
- deterministic seam behind the
182
- [`core/qa-coverage-mapping`](../skills/core/qa-coverage-mapping/SKILL.md)
183
- skill. Read that skill for how to assemble the `surface` input and read the
184
- per-tier `{present|absent}` verdict.
185
- 5. **Name the missing test** (if any) from that verdict: take the lowest tier
186
- the verdict marks `absent` (unit → contract → acceptance) and write one
187
- concrete sentence describing the test that would close it. Every tier
188
- `present` means no missing test. Record that sentence as the ledger item's
189
- `missingTest`.
181
+ 4. **Read the coverage tiers** for the surface the observation points at:
182
+ gather the tests that exercise it and classify each by path per
183
+ [`testing-standards.md` § The Three Tiers](../rules/testing-standards.md#the-three-tiers)
184
+ — a `.feature` file is **acceptance**, a path containing `/contract/` or
185
+ `.contract.test.` is **contract**, and a path containing `.test.` or
186
+ `__tests__/` is **unit**. A skipped test leaves its tier uncovered.
187
+ 5. **Name the missing test** (if any): take the lowest tier with no live test
188
+ (unit → contract → acceptance) and write one concrete sentence describing
189
+ the test that would close it. Every tier covered means no missing test.
190
+ Record that sentence as the ledger item's `missingTest`.
190
191
  6. **Classify** the finding via
191
192
  [`classify-finding.js`](../scripts/lib/findings/classify-finding.js) so the
192
193
  tentative `class` resolves to the correct focus/meta label set. The helper
@@ -267,10 +268,12 @@ the `/qa-assist`-specific deltas are:
267
268
  - **Persistent, resumable rolling session** — `/qa-assist` defaults to resuming
268
269
  the same session and appending; a reused session carries the untriaged backlog
269
270
  forward and never overwrites a prior ledger.
270
- - **Enrichment delegates where a helper exists** — the coverage verdict and
271
- the finding classification come from their deterministic helpers, never from
272
- prose. Context lookup and the missing-test sentence are the model's own work:
273
- they are judgments, not computations, and routing them through a module only
271
+ - **Enrichment delegates where a helper exists** — the finding classification
272
+ comes from its deterministic helper, never from prose; tier placement comes
273
+ from the three path rules in
274
+ [`testing-standards.md`](../rules/testing-standards.md#the-three-tiers).
275
+ Context lookup and the missing-test sentence are the model's own work: they
276
+ are judgments, not computations, and routing them through a module only
274
277
  bought a round-trip.
275
278
 
276
279
  ## See also
@@ -34,7 +34,7 @@ Constraints delta.
34
34
  > or Feature, a regression sweep over a risky surface before `/mandrel-deliver`, or a
35
35
  > structured agent-driven bug-hunt captured into a triageable ledger.
36
36
  >
37
- > **Skills**: `core/qa-coverage-mapping`, `stack/qa/qa-explore-driving`
37
+ > **Skills**: `stack/qa/qa-harness`
38
38
 
39
39
  ## Role framing
40
40
 
@@ -45,15 +45,16 @@ the surface shows. Apply the QA skills below; there is no separate persona pack.
45
45
  ## Driving conventions
46
46
 
47
47
  Before you drive a surface, read the
48
- [`stack/qa/qa-explore-driving`](../skills/stack/qa/qa-explore-driving/SKILL.md)
49
- skill — the **one** conventions reference for the *how* of agent-driven
50
- exploration (navigation-first driving as the default; static driving as the
51
- documented interim chosen at Plan time only where no seam resolves;
52
- authenticated driving through the resolved environment's `signInSeam`; broken
53
- navigation is a finding, not a workaround). The driving method (drive vs.
54
- static) is a **Plan-phase decision recorded in the ledger**; do not switch
55
- methods mid-surface without a new Plan note. Do not restate these conventions
56
- inline the skill owns them.
48
+ [`stack/qa/qa-harness`](../skills/stack/qa/qa-harness/SKILL.md)
49
+ skill — the **one** conventions reference for the *how* of agent-driven driving,
50
+ shared with the known-scenario sweep (navigation-first driving as the default;
51
+ static driving as the documented interim chosen at Plan time only where no seam
52
+ resolves; authenticated driving through the resolved environment's
53
+ `signInSeam`; broken navigation is a finding, not a workaround). Its § 5 carries
54
+ the exploratory-mode deltas. The driving method (drive vs. static) is a
55
+ **Plan-phase decision recorded in the ledger**; do not switch methods
56
+ mid-surface without a new Plan note. Do not restate these conventions inline —
57
+ the skill owns them.
57
58
 
58
59
  ## Slash Command
59
60
 
@@ -110,7 +111,7 @@ then the captured ledger), and wait. If the operator does not confirm, hold.
110
111
  Goal: agree on **what** will be explored and **how the agent will drive it**
111
112
  before touching the surface.
112
113
 
113
- 1. Re-read the `stack/qa/qa-explore-driving` skill and resolve the contract and
114
+ 1. Re-read the `stack/qa/qa-harness` skill and resolve the contract and
114
115
  session (above).
115
116
  2. **Resolve the target environment** via
116
117
  [`resolveQaEnvironment`](../scripts/lib/qa/resolve-qa-contract.js) — it keys
@@ -149,8 +150,8 @@ Goal: **the agent drives the confirmed surface itself** and records its
149
150
  observations. **This phase is strictly read-only.**
150
151
 
151
152
  > **Read-only invariant.** The agent observes; it never mutates. Per
152
- > [`stack/qa/qa-explore-driving`](../skills/stack/qa/qa-explore-driving/SKILL.md)
153
- > § 3 (inviolable per [`security-baseline.md`](../rules/security-baseline.md)),
153
+ > [`stack/qa/qa-harness`](../skills/stack/qa/qa-harness/SKILL.md)
154
+ > § 2 (inviolable per [`security-baseline.md`](../rules/security-baseline.md)),
154
155
  > do **not** edit source, run write commands, file or label GitHub issues,
155
156
  > change tickets, submit destructive forms, or alter the product under test. The
156
157
  > only write Capture performs is **appending ledger lines to
@@ -170,17 +171,16 @@ For each observation the agent makes while driving:
170
171
 
171
172
  1. **Redact first** (per [`helpers/qa-core.md`](helpers/qa-core.md)) — scrub the
172
173
  evidence string through `redactEvidence` before it touches disk.
173
- 2. **Compute the coverage verdict** for the surface the observation points at,
174
- via [`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js) the
175
- deterministic seam behind the
176
- [`core/qa-coverage-mapping`](../skills/core/qa-coverage-mapping/SKILL.md)
177
- skill. Read that skill for how to assemble the `surface` input and read the
178
- per-tier `{present|absent}` verdict.
179
- 3. **Name the missing test** (if any) from that verdict: take the lowest tier
180
- the verdict marks `absent` (unit → contract → acceptance) and write one
181
- concrete sentence describing the test that would close it. Every tier
182
- `present` means no missing test. Record that sentence as the ledger item's
183
- `missingTest` (or `null`).
174
+ 2. **Read the coverage tiers** for the surface the observation points at:
175
+ gather the tests that exercise it and classify each by path per
176
+ [`testing-standards.md` § The Three Tiers](../rules/testing-standards.md#the-three-tiers)
177
+ — a `.feature` file is **acceptance**, a path containing `/contract/` or
178
+ `.contract.test.` is **contract**, and a path containing `.test.` or
179
+ `__tests__/` is **unit**. A skipped test leaves its tier uncovered.
180
+ 3. **Name the missing test** (if any): take the lowest tier with no live test
181
+ (unit → contract → acceptance) and write one concrete sentence describing
182
+ the test that would close it. Every tier covered means no missing test.
183
+ Record that sentence as the ledger item's `missingTest` (or `null`).
184
184
  4. **Append a `QaLedgerItem`** to the ledger (shape per
185
185
  [`helpers/qa-core.md`](helpers/qa-core.md)): a stable `id`, the redacted
186
186
  `evidence`, the `coverage` label (the `surface`, or `unknown`), a tentative
@@ -215,7 +215,7 @@ resumed session will pick up.
215
215
  Beyond the shared core ([`helpers/qa-core.md`](helpers/qa-core.md): contract +
216
216
  loud failure, session/ledger, redact-first, QaLedgerItem, triage, HITL gate)
217
217
  and the driving conventions
218
- ([`stack/qa/qa-explore-driving`](../skills/stack/qa/qa-explore-driving/SKILL.md)),
218
+ ([`stack/qa/qa-harness`](../skills/stack/qa/qa-harness/SKILL.md)),
219
219
  the `/qa-explore`-specific deltas are:
220
220
 
221
221
  - **Agent-led, bounded per surface.** The agent drives one named surface per
@@ -231,10 +231,11 @@ the `/qa-explore`-specific deltas are:
231
231
  and fall back to static.
232
232
  - **Broken navigation is a finding, not a workaround** — never URL-jump around a
233
233
  missing affordance, a nav 404, or a guard redirect loop.
234
- - **Delegate the coverage verdict to the helper.** Tier placement comes from
235
- [`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js) deterministic,
236
- never re-derived in prose. The missing-test sentence is yours to write from
237
- that verdict's lowest `absent` tier.
234
+ - **Read tier placement from the rule, not from prose you invent.** The three
235
+ path rules in
236
+ [`testing-standards.md` § The Three Tiers](../rules/testing-standards.md#the-three-tiers)
237
+ decide which tier a test occupies; the missing-test sentence is yours to write
238
+ from the lowest uncovered tier.
238
239
 
239
240
  ## See also
240
241
 
@@ -29,7 +29,8 @@ the agent never invents those decisions in prose.
29
29
  > passes before `/mandrel-deliver`, or on demand while debugging a Story's
30
30
  > user-visible behavior in a live browser.
31
31
  >
32
- > **Skills**: `stack/qa/gherkin-authoring`, `stack/qa/playwright-bdd`
32
+ > **Skills**: `stack/qa/qa-harness` (driving and instrumentation
33
+ > conventions), `stack/qa/gherkin-authoring` and `stack/qa/playwright-bdd`
33
34
  > (authoring reference; this harness owns execution)
34
35
 
35
36
  ## Slash Command
package/docs/CHANGELOG.md CHANGED
@@ -15,6 +15,38 @@ 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.42.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.41.0...mandrel-v2.42.0) (2026-09-06)
19
+
20
+
21
+ ### Added
22
+
23
+ * full-suite capture economy: default the coverage-independent skip on, and serialize concurrent captures behind a host lock ([#5173](https://github.com/dsj1984/mandrel/issues/5173)) ([#5178](https://github.com/dsj1984/mandrel/issues/5178)) ([ed79751](https://github.com/dsj1984/mandrel/commit/ed7975199b6e2a3279a5d0d02b50e3068509987f))
24
+ * story-worker digest carries the creditable full-suite invocation, runs it after self-eval, and the critic reuses its stamp ([#5174](https://github.com/dsj1984/mandrel/issues/5174)) ([#5175](https://github.com/dsj1984/mandrel/issues/5175)) ([1e46457](https://github.com/dsj1984/mandrel/commit/1e464574aa6305dcc8f1ff8522015ae0f9a5301a))
25
+
26
+
27
+ ### Fixed
28
+
29
+ * scope the baseline refresh acknowledgment to the rows the refresh commit actually refreshed ([#5179](https://github.com/dsj1984/mandrel/issues/5179)) ([#5180](https://github.com/dsj1984/mandrel/issues/5180)) ([e8758b9](https://github.com/dsj1984/mandrel/commit/e8758b909ec19ed55c0678c05f481d515937f978))
30
+
31
+
32
+ ### Changed
33
+
34
+ * close fails fast: base-sync before validation, and coverage-independent baseline kinds in the parallel gate phase ([#5172](https://github.com/dsj1984/mandrel/issues/5172)) ([#5177](https://github.com/dsj1984/mandrel/issues/5177)) ([dff33d5](https://github.com/dsj1984/mandrel/commit/dff33d530965ef7da8ee3dd4f1f218d11805273f))
35
+
36
+ ## [2.41.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.40.0...mandrel-v2.41.0) (2026-09-06)
37
+
38
+
39
+ ### Added
40
+
41
+ * /mandrel-plan adopts an existing open Epic and declares depends_on edges on existing open Stories ([#5155](https://github.com/dsj1984/mandrel/issues/5155)) ([#5156](https://github.com/dsj1984/mandrel/issues/5156)) ([9119f31](https://github.com/dsj1984/mandrel/commit/9119f313146ab08ed676909f9b0a61671c4d6069))
42
+
43
+
44
+ ### Changed
45
+
46
+ * **agents:** retire four frontier-known rules/skills, fold two more into rules (refs [#5158](https://github.com/dsj1984/mandrel/issues/5158)) ([#5162](https://github.com/dsj1984/mandrel/issues/5162)) ([065f883](https://github.com/dsj1984/mandrel/commit/065f8839fafcc7064e4ad6d2ea30f2bfebbd5c0c))
47
+ * **qa:** fold qa-explore-driving into qa-harness (refs [#5159](https://github.com/dsj1984/mandrel/issues/5159)) ([#5163](https://github.com/dsj1984/mandrel/issues/5163)) ([2fbc8db](https://github.com/dsj1984/mandrel/commit/2fbc8db5a420450414cfd046bb6418e0c210bf7f))
48
+ * **rules:** trim surviving rules and core skills to their contracts (refs [#5160](https://github.com/dsj1984/mandrel/issues/5160)) ([#5164](https://github.com/dsj1984/mandrel/issues/5164)) ([f0e6a62](https://github.com/dsj1984/mandrel/commit/f0e6a62feaba275377c9cc435f03ea6089008148))
49
+
18
50
  ## [2.40.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.39.0...mandrel-v2.40.0) (2026-09-06)
19
51
 
20
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.40.0",
3
+ "version": "2.42.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",