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.
- package/.agents/README.md +2 -2
- package/.agents/instructions.md +5 -6
- package/.agents/rules/api-conventions.md +43 -7
- package/.agents/rules/ci-remediation.md +3 -14
- package/.agents/rules/gherkin-standards.md +21 -6
- package/.agents/rules/git-conventions.md +6 -5
- package/.agents/rules/security-baseline.md +6 -7
- package/.agents/rules/testing-standards.md +75 -198
- package/.agents/scripts/install-matrix-assert.js +2 -2
- package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +6 -0
- package/.agents/scripts/lib/orchestration/dependency-candidates.js +139 -0
- package/.agents/scripts/lib/orchestration/epic-candidates.js +159 -0
- package/.agents/scripts/lib/orchestration/epic-checklist.js +103 -0
- package/.agents/scripts/lib/orchestration/epic-container.js +18 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +97 -36
- package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +80 -0
- package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +192 -0
- package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +5 -1
- package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +164 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +14 -2
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +26 -5
- package/.agents/scripts/lib/orchestration/ticket-validator.js +11 -1
- package/.agents/scripts/plan-persist.js +60 -1
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +5 -10
- package/.agents/skills/core/browser-testing-with-devtools/reference.md +7 -46
- package/.agents/skills/core/code-review-and-quality/SKILL.md +0 -5
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +0 -3
- package/.agents/skills/core/gates-and-baselines/SKILL.md +10 -137
- package/.agents/skills/core/gates-and-baselines/reference.md +103 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +2 -18
- package/.agents/skills/core/security-and-hardening/SKILL.md +2 -4
- package/.agents/skills/core/security-and-hardening/reference.md +0 -70
- package/.agents/skills/skills.index.json +10 -70
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +4 -10
- package/.agents/skills/stack/qa/gherkin-authoring/reference.md +9 -113
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +29 -154
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +157 -98
- package/.agents/workflows/git-cleanup.md +3 -2
- package/.agents/workflows/git-deliver.md +3 -2
- package/.agents/workflows/helpers/plan-reference.md +82 -2
- package/.agents/workflows/mandrel-plan.md +45 -45
- package/.agents/workflows/qa-assist.md +20 -17
- package/.agents/workflows/qa-explore.md +30 -29
- package/.agents/workflows/qa-run.md +2 -1
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/.agents/rules/changelog-style.md +0 -180
- package/.agents/rules/shell-conventions.md +0 -61
- package/.agents/scripts/lib/qa/coverage-verdict.js +0 -214
- package/.agents/skills/core/api-and-interface-design/SKILL.md +0 -55
- package/.agents/skills/core/api-and-interface-design/reference.md +0 -76
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +0 -45
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +0 -56
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +0 -54
- package/.agents/skills/core/idea-refinement/refinement-criteria.md +0 -155
- package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +0 -15
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +0 -105
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +0 -152
- 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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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)
|
|
35
|
-
|
|
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
|
|
40
|
-
**semantic `Then
|
|
41
|
-
browser-action mapping, the per-`When` write guard, mandatory
|
|
42
|
-
redaction, and the **sequential-only** browser rule — are stated once
|
|
43
|
-
[`qa-run-scenario.md`](../../../../workflows/helpers/qa-run-scenario.md)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
|
|
166
|
+
### 4.4 The `F#` finding shape
|
|
102
167
|
|
|
103
|
-
Every captured problem is normalized into the structured `F#`
|
|
104
|
-
|
|
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, //
|
|
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
174
|
-
after the operator confirms each disposition at the HITL
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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`
|
|
21
|
-
> `
|
|
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`
|
|
23
|
-
> `
|
|
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[]` (
|
|
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
|
|
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.
|