mandrel 2.40.0 → 2.41.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 (59) hide show
  1. package/.agents/README.md +2 -2
  2. package/.agents/instructions.md +5 -6
  3. package/.agents/rules/api-conventions.md +43 -7
  4. package/.agents/rules/ci-remediation.md +3 -14
  5. package/.agents/rules/gherkin-standards.md +21 -6
  6. package/.agents/rules/git-conventions.md +6 -5
  7. package/.agents/rules/security-baseline.md +6 -7
  8. package/.agents/rules/testing-standards.md +75 -198
  9. package/.agents/scripts/install-matrix-assert.js +2 -2
  10. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +6 -0
  11. package/.agents/scripts/lib/orchestration/dependency-candidates.js +139 -0
  12. package/.agents/scripts/lib/orchestration/epic-candidates.js +159 -0
  13. package/.agents/scripts/lib/orchestration/epic-checklist.js +103 -0
  14. package/.agents/scripts/lib/orchestration/epic-container.js +18 -2
  15. package/.agents/scripts/lib/orchestration/plan-context.js +97 -36
  16. package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +80 -0
  17. package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +192 -0
  18. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +5 -1
  19. package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +164 -0
  20. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +14 -2
  21. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +26 -5
  22. package/.agents/scripts/lib/orchestration/ticket-validator.js +11 -1
  23. package/.agents/scripts/plan-persist.js +60 -1
  24. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +5 -10
  25. package/.agents/skills/core/browser-testing-with-devtools/reference.md +7 -46
  26. package/.agents/skills/core/code-review-and-quality/SKILL.md +0 -5
  27. package/.agents/skills/core/documentation-and-adrs/SKILL.md +0 -3
  28. package/.agents/skills/core/gates-and-baselines/SKILL.md +10 -137
  29. package/.agents/skills/core/gates-and-baselines/reference.md +103 -0
  30. package/.agents/skills/core/idea-refinement/SKILL.md +2 -18
  31. package/.agents/skills/core/security-and-hardening/SKILL.md +2 -4
  32. package/.agents/skills/core/security-and-hardening/reference.md +0 -70
  33. package/.agents/skills/skills.index.json +10 -70
  34. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +4 -10
  35. package/.agents/skills/stack/qa/gherkin-authoring/reference.md +9 -113
  36. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +29 -154
  37. package/.agents/skills/stack/qa/qa-harness/SKILL.md +157 -98
  38. package/.agents/workflows/git-cleanup.md +3 -2
  39. package/.agents/workflows/git-deliver.md +3 -2
  40. package/.agents/workflows/helpers/plan-reference.md +82 -2
  41. package/.agents/workflows/mandrel-plan.md +45 -45
  42. package/.agents/workflows/qa-assist.md +20 -17
  43. package/.agents/workflows/qa-explore.md +30 -29
  44. package/.agents/workflows/qa-run.md +2 -1
  45. package/docs/CHANGELOG.md +14 -0
  46. package/package.json +1 -1
  47. package/.agents/rules/changelog-style.md +0 -180
  48. package/.agents/rules/shell-conventions.md +0 -61
  49. package/.agents/scripts/lib/qa/coverage-verdict.js +0 -214
  50. package/.agents/skills/core/api-and-interface-design/SKILL.md +0 -55
  51. package/.agents/skills/core/api-and-interface-design/reference.md +0 -76
  52. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +0 -45
  53. package/.agents/skills/core/debugging-and-error-recovery/reference.md +0 -56
  54. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +0 -54
  55. package/.agents/skills/core/idea-refinement/refinement-criteria.md +0 -155
  56. package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +0 -15
  57. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +0 -105
  58. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +0 -152
  59. package/.agents/skills/stack/qa/vitest/SKILL.md +0 -22
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  name: qa-harness
3
3
  description:
4
- Conventions for the agent-driven QA harness that drives Gherkin scenarios
5
- through a real browser. Use when executing `/qa-run` or instrumenting
6
- a live surface — covers navigation-first execution, per-surface console and
7
- network capture, design-token visual checks, and the framework-generic
8
- heuristic cards for turning signal into findings. The harness procedure lives
9
- in `.agents/workflows/qa-run.md`; this skill is the conventions
10
- reference it leans on.
4
+ Conventions for agent-driven QA driving the one driving-conventions
5
+ reference behind both `/qa-run` (the known-scenario sweep through a real
6
+ browser) and `/qa-explore` (agent-led exploratory driving). Use when
7
+ instrumenting a live surface covers navigation-first execution,
8
+ per-surface console and network capture, design-token visual checks, the
9
+ `F#` finding shape, per-environment resolution, and static driving as the
10
+ documented interim. The run procedures live in `.agents/workflows/qa-run.md`
11
+ and `.agents/workflows/qa-explore.md`; this skill is the conventions
12
+ reference they lean on.
11
13
  ---
12
14
 
13
15
  # Skill: qa-harness
@@ -15,47 +17,110 @@ description:
15
17
  ## Policy Capsule
16
18
 
17
19
  - Driving rules (navigation-first, semantic `Then`, mandatory redaction, sequential-only) live in one prose home — [`qa-run-scenario.md`](../../../../workflows/helpers/qa-run-scenario.md); apply them, do not restate them.
20
+ - Resolve the `qa` contract first and fail loudly when it is absent or malformed; there is no auto-detection fallback and no headless degrade.
21
+ - Resolve the target **environment** before driving (via `resolveQaEnvironment`); each environment keys its own `baseUrl`, `signInSeam`, and `allowWrites`, and its name is recorded on the ledger.
22
+ - Sign in only through the resolved `signInSeam` (`kind: 'url'` dev impersonation or `kind: 'skill'` with a stored `credentialRef`). **Never** type real credentials inline and never fabricate a session.
23
+ - Hold the read-only capture invariant absolutely: no source edits, no product mutations — the only write is appending to the `temp/qa/<sessionId>` ledger.
24
+ - Scrub captured console, network, and headers of tokens, session cookies, and PII before any finding reaches disk or GitHub; findings are posted to GitHub at approval time.
18
25
  - Capture console and network per surface; turn each non-allowlisted console error and each failed/error-status request into one structured `F#` finding.
19
26
  - Filter console through `qa.consoleAllowlist` via `filterConsoleMessages`; treat the allowlist as a benign-noise filter, never as a security control to silence genuine errors.
20
27
  - Spot-check surfaces against `qa.designTokens` when set; flag gross token violations (off-palette colors, off-scale spacing/typography) as findings.
21
- - Scrub captured console and network of tokens, session cookies, and PII before rendering any finding findings are posted to GitHub at approval time.
22
- - Record findings as `QaLedgerItem`s and route them through the shared classify/route/promote core ([`qa-core.md`](../../../../workflows/helpers/qa-core.md)); the harness never files tickets autonomously.
23
- - Resolve the `qa` contract first and fail loudly when it is absent or malformed; there is no auto-detection fallback and no headless degrade.
24
-
25
- Guidance for executing the agent-driven QA harness through a real browser (the
26
- chrome-devtools MCP surface). The harness **procedure** — argument parsing,
27
- step ordering, contract resolution sequence — is the SSOT in
28
- [`.agents/workflows/qa-run.md`](../../../../workflows/qa-run.md);
29
- this skill shows **how** to apply the instrumentation and inspection
30
- conventions that procedure depends on. The assertion-tier rules it enforces
31
- live in [`testing-standards.md`](../../../../rules/testing-standards.md)
28
+ - Choose **static driving** only at Plan time and only where no seam resolves the documented interim, never a silent fallback and record the partial coverage.
29
+ - Record findings as `QaLedgerItem`s and route them through the shared classify/route/promote core ([`qa-core.md`](../../../../workflows/helpers/qa-core.md)); the harness never files tickets autonomously, and every phase transition is HITL-gated.
30
+
31
+ Guidance for driving a live surface through a real browser (the chrome-devtools
32
+ MCP surface). Two workflows lean on this one skill: the **known-scenario
33
+ sweep** ([`qa-run.md`](../../../../workflows/qa-run.md)) walks a resolved
34
+ Gherkin scenario set, and **exploratory driving**
35
+ ([`qa-explore.md`](../../../../workflows/qa-explore.md)) walks a named surface
36
+ the agent has no script for. Each **procedure** argument parsing, phase
37
+ gates, contract resolution sequence is the SSOT in its own workflow; this
38
+ skill shows **how** to apply the driving and instrumentation conventions both
39
+ depend on. Assertion-tier rules live in
40
+ [`testing-standards.md`](../../../../rules/testing-standards.md)
32
41
  (§ Assertion Placement); scenario prose conventions live in
33
42
  [`gherkin-authoring`](../gherkin-authoring/SKILL.md); browser-locator
34
- discipline is shared with [`playwright`](../playwright/SKILL.md). Read this
35
- skill before instrumenting a live surface; read the workflow for the run order.
43
+ discipline is shared with [`playwright`](../playwright/SKILL.md); browser
44
+ instrumentation lives in
45
+ [`browser-testing-with-devtools`](../../../core/browser-testing-with-devtools/SKILL.md).
46
+ Read this skill before instrumenting a live surface; read the workflow for the
47
+ run order.
36
48
 
37
49
  ## 1. Driving Rules Live in One Prose Home
38
50
 
39
- The driving rules the harness depends on — **navigation-first / never URL-jump**,
40
- **semantic `Then` assertion** against the accessibility snapshot, the Gherkin →
41
- browser-action mapping, the per-`When` write guard, mandatory evidence
42
- redaction, and the **sequential-only** browser rule — are stated once in
43
- [`qa-run-scenario.md`](../../../../workflows/helpers/qa-run-scenario.md), the
44
- single-scenario driver `/qa-run` delegates to. Apply them from there; this skill
45
- does not restate them. In short: reach every surface the way a real user would
46
- (start at a root, click affordances, never deep-link a `Given`), assert `Then`
47
- semantically (roles, accessible names, visible text — never DOM/CSS/XPath
48
- selectors, HTTP status, response bodies, or DB rows), and record each scenario's
49
- result (pass / fail / blocked), the surface it ended on, and a one-line symptom
50
- for any failure. Assertion-tier rules are in
51
+ The driving rules both modes depend on — **navigation-first / never
52
+ URL-jump**, **semantic `Then`** assertion against the accessibility snapshot,
53
+ the Gherkin → browser-action mapping, the per-`When` write guard, mandatory
54
+ evidence redaction, and the **sequential-only** browser rule — are stated once
55
+ in [`qa-run-scenario.md`](../../../../workflows/helpers/qa-run-scenario.md).
56
+ Apply them from there; this skill does not restate them. In short: reach every
57
+ surface the way a real user would (start at a root, click affordances, never
58
+ deep-link a starting state), assert semantically (roles, accessible names,
59
+ visible text — never DOM/CSS/XPath selectors, HTTP status, response bodies, or
60
+ DB rows), and treat broken navigation a missing affordance, a nav 404, a
61
+ guard redirect loop as a **finding, not a workaround**. Assertion-tier rules
62
+ are in
51
63
  [`testing-standards.md` § Assertion Placement](../../../../rules/testing-standards.md#assertion-placement).
52
64
 
53
- ## 2. Per-Surface Console & Network Capture
65
+ ## 2. The Read-Only Capture Invariant
66
+
67
+ Capture is **strictly read-only** in both modes. This invariant is inviolable
68
+ per [`security-baseline.md`](../../../../rules/security-baseline.md) — it is
69
+ not a soft preference.
70
+
71
+ - **No source edits.** The agent does not modify application code, config, or
72
+ tests while driving. Driving observes; it never repairs.
73
+ - **No product mutations.** No creating, updating, or deleting product data, no
74
+ destructive form submissions, no irreversible actions "to see what happens".
75
+ When a surface's only path forward is a mutating action, record the boundary
76
+ as the finding and stop — do not cross it.
77
+ - **The only write is the ledger.** The single permitted side effect is
78
+ appending finding lines under `temp/qa/<sessionId>`.
79
+ - **Scrub before persisting.** Strip tokens, session cookies, `Authorization`
80
+ headers, and PII from captured console and network evidence via the shared
81
+ redaction path **before** any finding reaches disk or GitHub. Captured
82
+ evidence is untrusted until scrubbed.
83
+ - **HITL gates every write outward.** Phase transitions and GitHub writes
84
+ (ticket creation, promotion) happen only behind an operator confirmation
85
+ gate; the agent never files or promotes findings autonomously.
86
+
87
+ ## 3. Contract, Environment & Sign-In
54
88
 
55
- Instrument each surface the moment you land on it, before moving on. Capture is
56
- **per surface** so evidence is attributable to a concrete user-reachable state.
89
+ - **Resolve the `qa` contract first.** Before any browser work, resolve the
90
+ contract via `resolveQaContract(config)`. When the block is absent,
91
+ malformed, or missing a required field, the resolver **throws** — relay its
92
+ verbatim message and STOP. There is no auto-detection fallback.
93
+ - **Resolve the environment.** `resolveQaEnvironment` keys each deployment
94
+ target to `{ name, baseUrl, signInSeam, allowWrites }`. Where the operator's
95
+ input does not pin an unambiguous target and the contract declares more than
96
+ one environment, prompt (or accept `defaultEnvironment`) — never silently
97
+ pick one. Record the resolved name on the ledger.
98
+ - **Sign in through the seam, never inline.** The two seam kinds:
99
+ - **`kind: 'url'` (dev impersonation).** Substitute the persona **name** into
100
+ the seam's URL template and navigate there. The persona name is the sole
101
+ input; no per-persona auth material is read.
102
+ - **`kind: 'skill'` (procedural / credential).** Invoke the named consumer
103
+ sign-in skill, which reads a per-persona **`credentialRef`** — an indirect
104
+ handle to a stored credential, never an inline secret. Read that skill's
105
+ `SKILL.md` and follow it.
106
+
107
+ The agent MUST NOT type real usernames, passwords, or tokens, and MUST NOT
108
+ fabricate or forge a session. This is a hard security boundary, not a
109
+ convenience to work around. Confirm authenticated state with a
110
+ `take_snapshot` before driving.
111
+ - **No headless fallback.** The chrome-devtools MCP surface is a host-provided
112
+ runtime dependency. If it is unavailable, degrade with a clear error and stop
113
+ — never fall back to the retired headless BDD runner.
57
114
 
58
- ### 2.1 Console
115
+ ## 4. Mode — Known-Scenario Sweep (`/qa-run`)
116
+
117
+ The sweep drives a resolved scenario set and instruments each surface the
118
+ moment it lands on it, before moving on. Capture is **per surface** so evidence
119
+ is attributable to a concrete user-reachable state. Record each scenario's
120
+ result (pass / fail / blocked), the surface it ended on, and a one-line symptom
121
+ for any failure.
122
+
123
+ ### 4.1 Console
59
124
 
60
125
  1. Capture with `list_console_messages` on the current surface.
61
126
  2. Filter through the contract's `consoleAllowlist` using
@@ -75,13 +140,13 @@ Allowlist matching is case-sensitive substring matching, so patterns stay
75
140
  readable in `.agentrc.json` without regex escaping; a blank pattern is ignored
76
141
  rather than matching everything.
77
142
 
78
- ### 2.2 Network
143
+ ### 4.2 Network
79
144
 
80
145
  Capture with `list_network_requests` on the surface. Failed requests and
81
146
  error-status responses (4xx / 5xx) become findings alongside the
82
147
  console-derived set, sharing the same `F#` numbering across the surface.
83
148
 
84
- ### 2.3 Design-token visual check
149
+ ### 4.3 Design-token visual check
85
150
 
86
151
  When the contract's `designTokens` pointer is set (it defaults to `null`),
87
152
  spot-check the rendered surface against the token source. Flag **gross** token
@@ -98,10 +163,10 @@ A gross violation is one a designer would call a regression on sight; subtle
98
163
  sub-pixel differences are not harness findings. When `designTokens` is `null`,
99
164
  skip this check entirely — do not invent a token source.
100
165
 
101
- ## 3. Findings — the `F#` Shape
166
+ ### 4.4 The `F#` finding shape
102
167
 
103
- Every captured problem is normalized into the structured `F#` finding shape so
104
- the sweep can record it onto the shared ledger (each `F#` finding becomes one
168
+ Every captured problem is normalized into the structured `F#` shape so the
169
+ sweep can record it onto the shared ledger (each `F#` finding becomes one
105
170
  `QaLedgerItem` — see [`qa-core.md`](../../../../workflows/helpers/qa-core.md))
106
171
  and the schema validates:
107
172
 
@@ -111,7 +176,7 @@ and the schema validates:
111
176
  "classification": "console-error", // console-error | network-error | visual-token | ...
112
177
  "surface": "/invoices", // the user-reachable surface, not a deep link
113
178
  "symptom": "...", // one-line user-visible / captured symptom
114
- "likelyRootCause": null, // heuristic card output (§4); null until enriched
179
+ "likelyRootCause": null, // null unless the cause is evidenced, not guessed
115
180
  "disposition": "follow-up", // blocker | follow-up
116
181
  "acceptance": null, // AC this folds into, when known
117
182
  "evidence": {
@@ -124,77 +189,71 @@ and the schema validates:
124
189
  - **Determinism is load-bearing.** Re-running the same selector over the same
125
190
  captured console with the same allowlist yields the same findings in the same
126
191
  order. Do not reorder or renumber findings between sweeps.
127
- - **Scrub before rendering.** Before any finding's `evidence` is rendered or
128
- drafted, strip tokens, session cookies, Authorization headers, and PII from
129
- the captured console and network per
130
- [`security-baseline.md`](../../../../rules/security-baseline.md). Findings are
131
- posted to GitHub at approval time — captured evidence is untrusted until
132
- scrubbed.
133
-
134
- ## 4. Framework-Generic Heuristic Cards
135
-
136
- The harness ships **framework-generic** root-cause heuristics — they reason
137
- about symptoms, not about any one frontend framework. Use a card to populate
138
- `likelyRootCause` and to set `disposition`. The cards are guidance, not a
139
- classifier: when a symptom matches none cleanly, leave `likelyRootCause: null`
140
- and let the operator triage from the symptom.
141
-
142
- | Symptom pattern | Likely root cause | Default disposition |
143
- | --- | --- | --- |
144
- | `404` / `Not Found` on a navigation or asset request | Dead route, broken link, or missing build artifact | follow-up (blocker if it breaks the scenario path) |
145
- | `401` / `403` reaching a surface the persona should see | Missing or over-tight authorization check; guard misconfig | blocker |
146
- | `500` / `502` / `503` on a user action | Server-side fault behind the action | blocker |
147
- | Uncaught `TypeError` / `ReferenceError` in console | Null/undefined dereference or missing binding in client code | blocker when it breaks the surface, else follow-up |
148
- | `Failed to fetch` / `NetworkError` / CORS-rejected request | Misconfigured CORS allowlist, wrong origin, or a downed dependency | follow-up |
149
- | Hydration / mismatch warning escalated to error | Server/client render divergence | follow-up |
150
- | Off-palette color, off-scale spacing/typography | Design-token drift — hard-coded value bypassing the token | follow-up |
151
- | Repeated identical console error across many surfaces | A shared component or global bootstrap fault | record once; the shared route/dedup core collapses duplicates at triage |
152
-
153
- Heuristics for working the cards:
154
-
155
- - **Record once, let dedup collapse.** When the same error fires on many
156
- surfaces, record it once rather than filing N copies; the shared
157
- classify/route/dedup core ([`qa-core.md`](../../../../workflows/helpers/qa-core.md))
158
- collapses duplicates at triage against the fingerprint footer.
159
192
  - **Blocker vs. follow-up.** A finding is a **blocker** when it breaks the
160
193
  scenario's user-visible outcome or exposes an authorization gap. Everything
161
- else (noise that does not break the journey, cosmetic token drift) is a
194
+ else noise that does not break the journey, cosmetic token drift is a
162
195
  **follow-up**.
163
196
  - **Symptom over diagnosis.** When unsure of the root cause, record the precise
164
197
  symptom and leave `likelyRootCause: null`. A wrong guess is worse than an
165
198
  honest "unknown" the operator can triage.
166
-
167
- ## 5. Record onto the Ledger & Triage (Never File Autonomously)
168
-
169
- Record each `F#` finding as a `QaLedgerItem` on the shared session ledger under
199
+ - **Record once, let dedup collapse.** When the same error fires on many
200
+ surfaces, record it once rather than filing N copies; the shared
201
+ classify/route/dedup core collapses duplicates at triage against the
202
+ fingerprint footer.
203
+
204
+ ## 5. Mode — Exploratory Driving (`/qa-explore`)
205
+
206
+ Exploratory driving walks a named surface with no scenario script, capturing
207
+ what it observes. It is the agent-led half of exploratory QA; its human-led
208
+ sibling is `/qa-assist` (the human drives, the agent scribes). Everything in
209
+ §§ 1–3 applies unchanged; the deltas are the method choice and the per-surface
210
+ boundary.
211
+
212
+ - **Pick the driving method explicitly at Plan time** — drive (default) vs.
213
+ static — and record it in the ledger. Do not switch methods mid-surface
214
+ without a new Plan note.
215
+ - **Drive is the default.** When the resolved environment carries a
216
+ `signInSeam`, authenticated surfaces — including deployed hosts — are
217
+ **driven** through that seam, not statically deferred.
218
+ - **Static driving is the documented interim**, chosen **only where no seam
219
+ resolves** for the target environment. It walks the surface from source,
220
+ route definitions, and rendered markup rather than a running browser:
221
+ - **Never a silent fallback.** Static is a deliberate Plan-phase decision
222
+ recorded with its reason ("environment: preview, method: static, reason: no
223
+ seam resolves"), not something the agent slips into when the browser MCP
224
+ hiccups.
225
+ - **Interim, not equivalent.** Static driving cannot exercise real
226
+ authorization, routing guards, or runtime console/network signal. Treat its
227
+ coverage as partial and say so in the ledger; a static pass does not close
228
+ the same coverage a driven pass would.
229
+ - **Same read-only invariant** (§ 2) applies identically.
230
+ - **Promote to driving when a seam lands.** Re-run the surface driven rather
231
+ than leaving it permanently static.
232
+ - **A surface that could not be driven is itself a signal.** Where the
233
+ environment resolves no seam, drive the unauthenticated surface or fall back
234
+ to static and record the gap — never a silent skip.
235
+ - **Observe, do not fabricate.** Never script the runtime to manufacture an
236
+ outcome the exploration is meant to discover.
237
+
238
+ ## 6. Record onto the Ledger & Triage (Never File Autonomously)
239
+
240
+ Record each finding as a `QaLedgerItem` on the shared session ledger under
170
241
  `temp/qa/`, then route the ledger through the shared classify → route →
171
242
  disposition → promote core — both stated once in
172
243
  [`qa-core.md`](../../../../workflows/helpers/qa-core.md). The harness **MUST
173
- NOT** create tickets autonomously: findings are promoted through `/mandrel-plan` only
174
- after the operator confirms each disposition at the HITL write gate. That gate
175
- is the safety boundary against spurious filing.
176
-
177
- ## 6. Sign-In & Contract Discipline
178
-
179
- - **Resolve the `qa` contract first.** Before any browser work, resolve the
180
- contract via `resolveQaContract(config)`. When the block is absent,
181
- malformed, or missing a required field, the resolver **throws** — relay its
182
- verbatim message and STOP. There is no auto-detection fallback.
183
- - **Dev seam only.** Sign in once per persona via the contract's `signInSeam`
184
- (`kind: 'url'` dev seam or `kind: 'skill'`). **Never** enter real
185
- credentials. Confirm authenticated state with a `take_snapshot` before
186
- driving any scenario.
187
- - **No headless fallback.** The chrome-devtools MCP surface is a host-provided
188
- runtime dependency. If it is unavailable, degrade with a clear error and stop
189
- — never fall back to the retired headless BDD runner.
244
+ NOT** create tickets autonomously: findings are promoted through
245
+ `/mandrel-plan` only after the operator confirms each disposition at the HITL
246
+ write gate. That gate is the safety boundary against spurious filing.
190
247
 
191
248
  ## 7. Cross-References
192
249
 
193
- - Run procedure (SSOT): [`qa-run.md`](../../../../workflows/qa-run.md).
250
+ - Sweep procedure (SSOT): [`qa-run.md`](../../../../workflows/qa-run.md).
251
+ - Exploration procedure (SSOT): [`qa-explore.md`](../../../../workflows/qa-explore.md).
194
252
  - Driving rules (one prose home): [`qa-run-scenario.md`](../../../../workflows/helpers/qa-run-scenario.md).
195
253
  - Shared QA core (contract/session/redaction/ledger/triage/HITL): [`qa-core.md`](../../../../workflows/helpers/qa-core.md).
196
254
  - Console filter module: [`console-allowlist.js`](../../../../scripts/lib/qa/console-allowlist.js).
197
255
  - Assertion-tier rules: [`testing-standards.md`](../../../../rules/testing-standards.md).
198
256
  - Scenario prose: [`gherkin-authoring`](../gherkin-authoring/SKILL.md).
199
257
  - Browser-locator discipline: [`playwright`](../playwright/SKILL.md).
200
- - Evidence scrubbing: [`security-baseline.md`](../../../../rules/security-baseline.md).
258
+ - Browser instrumentation: [`browser-testing-with-devtools`](../../../core/browser-testing-with-devtools/SKILL.md).
259
+ - Evidence scrubbing / read-only boundary: [`security-baseline.md`](../../../../rules/security-baseline.md).
@@ -17,8 +17,9 @@ Reach for it when the automated hygiene left an unusual state behind.
17
17
  > **When to run**: after a session that landed several PRs, or before starting a
18
18
  > new Story, to put the local checkout into a known tidy state.
19
19
  >
20
- > **Persona**: `devops-engineer` · **Skills**:
21
- > `core/git-workflow-and-versioning`
20
+ > **Persona**: `devops-engineer` git guidance comes from the always-on
21
+ > [`rules/git-conventions.md`](../rules/git-conventions.md), which needs no
22
+ > skill citation.
22
23
 
23
24
  The enumeration + reap logic lives in
24
25
  [`git-cleanup.js`](../scripts/git-cleanup.js) — it computes the candidate list,
@@ -19,8 +19,9 @@ explicitly; the interactive choice prompt fires **only** when the detected
19
19
  state is genuinely ambiguous, so the common path stays non-interactive and
20
20
  scriptable.
21
21
 
22
- > **Persona**: `devops-engineer` · **Skills**:
23
- > `core/git-workflow-and-versioning`
22
+ > **Persona**: `devops-engineer` git guidance comes from the always-on
23
+ > [`rules/git-conventions.md`](../rules/git-conventions.md), which needs no
24
+ > skill citation.
24
25
 
25
26
  ---
26
27
 
@@ -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.