mandrel 2.7.0 → 2.8.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 (73) hide show
  1. package/.agents/README.md +104 -330
  2. package/.agents/agents/auditor.md +135 -0
  3. package/.agents/agents/plan-critic.md +80 -0
  4. package/.agents/audit-checklists/dependencies.md +7 -0
  5. package/.agents/audit-checklists/documentation.md +1 -0
  6. package/.agents/docs/SDLC.md +69 -162
  7. package/.agents/docs/configuration.md +133 -499
  8. package/.agents/docs/quality-gates.md +59 -180
  9. package/.agents/instructions.md +170 -295
  10. package/.agents/rules/changelog-style.md +8 -66
  11. package/.agents/rules/ci-remediation.md +65 -124
  12. package/.agents/rules/gherkin-standards.md +10 -31
  13. package/.agents/rules/git-conventions-reference.md +28 -61
  14. package/.agents/rules/git-conventions.md +1 -1
  15. package/.agents/rules/orchestration-error-handling.md +5 -15
  16. package/.agents/rules/security-baseline.md +7 -13
  17. package/.agents/rules/shell-conventions.md +4 -13
  18. package/.agents/rules/test-seams.md +2 -2
  19. package/.agents/rules/testing-standards.md +7 -17
  20. package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
  21. package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
  22. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
  23. package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
  24. package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
  25. package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
  26. package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
  27. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
  28. package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
  29. package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
  30. package/.agents/skills/core/security-and-hardening/reference.md +15 -273
  31. package/.agents/skills/skills.index.json +5 -5
  32. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
  33. package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
  34. package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
  35. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
  36. package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
  37. package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
  38. package/.agents/workflows/audit-accessibility.md +34 -108
  39. package/.agents/workflows/audit-architecture.md +35 -123
  40. package/.agents/workflows/audit-clean-code.md +19 -78
  41. package/.agents/workflows/audit-data-model.md +32 -100
  42. package/.agents/workflows/audit-dependencies.md +47 -111
  43. package/.agents/workflows/audit-devops.md +16 -83
  44. package/.agents/workflows/audit-documentation.md +46 -93
  45. package/.agents/workflows/audit-navigability.md +26 -80
  46. package/.agents/workflows/audit-performance.md +40 -106
  47. package/.agents/workflows/audit-privacy.md +17 -80
  48. package/.agents/workflows/audit-quality.md +35 -99
  49. package/.agents/workflows/audit-security.md +20 -78
  50. package/.agents/workflows/audit-seo.md +20 -98
  51. package/.agents/workflows/audit-sre.md +20 -88
  52. package/.agents/workflows/audit-to-stories.md +1 -8
  53. package/.agents/workflows/audit-ux-ui.md +17 -80
  54. package/.agents/workflows/deliver.md +54 -9
  55. package/.agents/workflows/git-cleanup.md +50 -275
  56. package/.agents/workflows/helpers/audit-lens-core.md +230 -0
  57. package/.agents/workflows/helpers/code-review.md +11 -23
  58. package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
  59. package/.agents/workflows/helpers/deliver-story.md +26 -186
  60. package/.agents/workflows/helpers/qa-core.md +174 -0
  61. package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
  62. package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
  63. package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
  64. package/.agents/workflows/mandrel-update.md +7 -13
  65. package/.agents/workflows/plan.md +17 -15
  66. package/.agents/workflows/qa-assist.md +140 -269
  67. package/.agents/workflows/qa-explore.md +125 -316
  68. package/.agents/workflows/qa-run.md +180 -380
  69. package/docs/CHANGELOG.md +10 -0
  70. package/package.json +1 -1
  71. package/.agents/workflows/helpers/audit-dual-path.md +0 -59
  72. package/.agents/workflows/helpers/audit-self-check.md +0 -70
  73. package/.agents/workflows/helpers/audit-severity-scale.md +0 -19
@@ -194,6 +194,61 @@ behaviour and warrants pre-merge review.
194
194
 
195
195
  ## Step 4 — CI watch + fix recovery
196
196
 
197
+ Enter this step **only** when Step 3 returned `blocked` with
198
+ `blockClass: "checks-failed"` (a required check went red), or when a
199
+ `--no-wait-merge` run left the PR for you to shepherd. When a required check is
200
+ red, the agent owns the green-CI outcome, not just the push: local
201
+ close-validation gates pass on the dev host's environment; CI runs on a
202
+ different OS and concurrency, and coverage rounding, platform-conditional
203
+ branches, and timing-sensitive tests routinely drift between the two.
204
+
205
+ Fix the failure and push a new commit on `story-<storyId>` — auto-merge stays
206
+ armed across retries, so you do not re-arm — then resume the land with the
207
+ envelope's `nextCommand`.
208
+
209
+ To watch the checks on the red path, drive `pr-watch-with-update.js` — the
210
+ **single CI-watch mechanism** (Story #4358). It polls the required checks to a
211
+ terminal state and auto-recovers from `mergeStateStatus: BEHIND`; do **not**
212
+ fall back to a bare `gh pr checks` watch invocation:
213
+
214
+ ```bash
215
+ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber> --story <storyId>
216
+ ```
217
+
218
+ `--story` is what keys the red-path CI digest
219
+ (`temp/story-<id>-ci-digest.{json,md}` — failing check name, run id, and a
220
+ `gh run view --log-failed` tail). Omit it and a red check writes no digest.
221
+ Poll cadence and caps come from `delivery.ci.watch.*` (`pollIntervalMs`,
222
+ `maxPolls`, `maxResumes`); pass `--poll-interval-ms`, `--max-polls`, or
223
+ `--max-resumes` to override for one run.
224
+
225
+ When the watch exits, branch on the exit code:
226
+
227
+ - **Exit 0 (all checks ✓)** — auto-merge will fire (or has already). The Story
228
+ is still at `agent::closing` with its issue OPEN. **Proceed to merge
229
+ confirmation (§ Step 5) within the same turn** — green CI is the *start* of
230
+ the merge-confirm sequence, not a terminal state.
231
+ - **Exit 1 (a check genuinely failed)** — diagnose, fix, and push a new commit
232
+ on `story-<storyId>`, then re-watch. Auto-merge stays enabled across retries;
233
+ no need to re-arm it. The Story stays at `agent::closing` throughout, so a
234
+ failed/abandoned PR never strands a CLOSED issue. If the same failure class
235
+ recurs, hand convergence off to a self-paced host loop (`/loop`) that re-runs
236
+ the failing check and applies the smallest fix until it exits green.
237
+ - **Exit 2 (still-running — slow CI, not red)** — the poll cap fired with checks
238
+ still pending and the watcher exhausted its resume budget with nothing red.
239
+ This is **never** a failure. Hand the wait off to the host's interval loop
240
+ rather than ending your turn: `/loop 5m` polling `gh pr checks` until the
241
+ checks settle.
242
+
243
+ **Triage authority.** How to classify and remediate a red (or repeatedly slow)
244
+ check — the root-cause-only decision tree for infra/transient and flaky failures
245
+ (reproduce → check `main` → bisect env vs code → fix in-scope or file a
246
+ `meta::framework-gap` issue), the never-rerun / never-quarantine prohibitions,
247
+ and the escalation criteria (three-strikes, the 30-minute wall-clock timebox,
248
+ and the clearly-environmental fast path) — is defined once in
249
+ [`.agents/rules/ci-remediation.md`](../../rules/ci-remediation.md). Read it
250
+ before remediating a red check.
251
+
197
252
  ### The auto-merge wait is an internally-blocking step
198
253
 
199
254
  This is the single most important contract of this workflow, and the seam
@@ -281,6 +336,20 @@ the watch exits clean.
281
336
 
282
337
  ## Step 5 — Merge confirmation detail
283
338
 
339
+ > On the default path Step 3 already did this. Run it only to resume a
340
+ > `pending` envelope, to finish a `--no-wait-merge` run, or to rescue a
341
+ > merged-but-mislabelled Story.
342
+
343
+ ```bash
344
+ node .agents/scripts/single-story-confirm-merge.js --story <storyId> --cwd <main-repo>
345
+ ```
346
+
347
+ This is the **same** shared land path Step 3 reaches: it flips
348
+ `agent::closing → agent::done` on a confirmed merge (closing the issue) and runs
349
+ the **same** post-land tail — so the two surfaces cannot diverge. It is
350
+ idempotent, emits the same terminal envelope, and is safe to re-run while the PR
351
+ is still open (returns `pending`).
352
+
284
353
  `single-story-confirm-merge.js` re-reads the live PR state (`gh pr view
285
354
  --json state,mergedAt`, probing `gh pr list --head story-<id> --state all`
286
355
  when `--pr` is omitted) and:
@@ -302,6 +371,18 @@ The issue closes exactly when the work has merged, never at PR-open
302
371
 
303
372
  ## Step 5.5 — Re-assert Status column detail
304
373
 
374
+ > **The land tail already ran this** (Story #4543) — it is `tail.statusResync`
375
+ > in the terminal envelope. Run it by hand only when that step reported
376
+ > `false`, or after a manual merge on a `--no-wait-merge` run.
377
+
378
+ ```bash
379
+ node .agents/scripts/resync-status-column.js --story <storyId>
380
+ ```
381
+
382
+ The helper re-fires the `ColumnSync` mutation and **polls for ~15 s** to win the
383
+ race against the bot's late write (Story #2876). It is idempotent and
384
+ no-op-safe (`no-project` / `not-on-project` exit 0).
385
+
305
386
  The GitHub Projects v2 built-in workflows `Pull request merged` and
306
387
  `Pull request linked to issue` are enabled by default on most boards
307
388
  and fire ~minutes *after* auto-merge lands. They overwrite the Status
@@ -348,6 +429,30 @@ defense-in-depth against re-enabled or future workflows.
348
429
 
349
430
  ## Step 6 — Local branch cleanup detail
350
431
 
432
+ > **The land tail already ran this** (Story #4543) — it is `tail.refCleanup` and
433
+ > `tail.baseFastForward` in the terminal envelope, done in-process against the
434
+ > same planners this command drives. Run it by hand only when either step
435
+ > reported `false` (a dirty shared checkout is the common, benign cause), or
436
+ > after a manual merge on a `--no-wait-merge` run. To prune the story ref **and**
437
+ > fast-forward local `main` (or `project.baseBranch`):
438
+
439
+ ```bash
440
+ node .agents/scripts/git-cleanup.js \
441
+ --execute \
442
+ --remote \
443
+ --yes \
444
+ --fast-forward-main \
445
+ --branches \
446
+ --include "story-<storyId>"
447
+ ```
448
+
449
+ `--fast-forward-main` brings local `main` current (the next init seeds from it),
450
+ `--branches` + `--include` reap only this Story's ref, and
451
+ `--execute --remote --yes` run the deletes non-interactively. The sweep is
452
+ idempotent and safe to run before `MERGED` confirms. Skip it only when the
453
+ operator opted out via `--no-auto-merge` AND has not yet merged the PR — run the
454
+ cleanup after the manual merge lands.
455
+
351
456
  GitHub deletes the **remote** branch on auto-merge (via the
352
457
  `--delete-branch` flag `single-story-close.js` passes to `gh pr merge`).
353
458
  The **local** `story-<storyId>` ref, however, lingers in the main
@@ -400,16 +505,10 @@ follows is the *judgement* around it, which a schema cannot express.
400
505
 
401
506
  ### `pending` is a real status — and it is not a park
402
507
 
403
- Earlier revisions asserted "the auto-merge wait does not produce a fourth
404
- status", on the reasoning that the wait is internally blocking so a run either
405
- merges or blocks. That was true only while the wait was unbounded — and it was
406
- never actually unbounded, because the host kills a tool invocation at ~10
407
- minutes. So a close-and-land whose CI outlived that ceiling took **no**
408
- terminal path at all: no event, no label, the Story parked at
409
- `agent::closing`. The status the model refused to name was the one that kept
410
- happening.
411
-
412
- `pending` names it, with its own exit code (3):
508
+ `pending` is a real terminal status with its own exit code (3) — the honest
509
+ name for a close-and-land whose CI outlived the host's ~10-minute
510
+ tool-invocation ceiling, which would otherwise park the Story at
511
+ `agent::closing` with no event and no label:
413
512
 
414
513
  - It is **resumable**: no label was mutated, no `merge.unlanded` was emitted,
415
514
  and `nextCommand` names the one command that continues it. The cumulative
@@ -419,13 +518,11 @@ happening.
419
518
  is the Story #1553 / PR #1554 failure mode wearing a schema. Return it only
420
519
  when the bound genuinely expired, or a human owns the merge.
421
520
 
422
- The no-park rule is therefore unchanged in substance: a turn that ends with
423
- prose ("I'll wait for the watch task…", "the next event will be its
424
- completion notification…") and an unconfirmed merge is a **contract
425
- violation** — the parent cannot distinguish "still working" from "done but
426
- silent". What changed is that there is now an honest, machine-readable way to
427
- say "not finished, here is exactly how to continue" instead of a choice
428
- between lying and blocking forever.
521
+ The no-park rule holds: a turn that ends with prose ("I'll wait for the watch
522
+ task…", "the next event will be its completion notification…") and an
523
+ unconfirmed merge is a **contract violation** — the parent cannot distinguish
524
+ "still working" from "done but silent". `pending` is the honest,
525
+ machine-readable alternative: "not finished, here is exactly how to continue."
429
526
 
430
527
  ### Exit-code compatibility note (`--no-wait-merge`)
431
528
 
@@ -131,32 +131,9 @@ Operator/agent responsibilities while in the worktree:
131
131
  `checklistPath` (footprint-matched **local**-lens authoring checklists),
132
132
  read it before you write and self-check as you author. When absent,
133
133
  lens-aware coverage still runs maker-blind at Story-scope review inside
134
- the close subprocess.
135
-
136
- **Producing `checklistPath` at dispatch (Story #4627).** The dispatch that
137
- spawns this worker threads `checklistPath` the same way it threads
138
- `docsDigestPath`. Before the spawn, compute the payload from the Story's
139
- predicted footprint (its `changes[]` / `references[]` path entries) with
140
- `buildDispatchChecklist` and write it to the run temp dir:
141
-
142
- ```bash
143
- node --input-type=module -e '
144
- import { buildDispatchChecklist } from "<main-repo>/.agents/scripts/lib/audit-suite/index.js";
145
- import { parse } from "<main-repo>/.agents/scripts/lib/story-body/story-body.js";
146
- // storyBody is the fetched Story issue body.
147
- const { changes, references } = parse(process.env.STORY_BODY);
148
- const { checklistPath } = buildDispatchChecklist({
149
- storyId: <storyId>, changes, references, runTempDir: "temp/run-<id>",
150
- });
151
- console.log(checklistPath ?? "");
152
- '
153
- ```
154
-
155
- A non-empty `checklistPath` is threaded into this worker's prompt; an empty
156
- footprint match prints nothing and the worker runs with no write-time
157
- checklist (the maker-blind close-scope pass still covers it). The builder is
158
- a pure function of the footprint and the on-disk checklists —
159
- `buildDispatchChecklist` (`lib/audit-suite/dispatch-checklist.js`).
134
+ the close subprocess. The dispatch step produces `checklistPath` from the
135
+ Story's predicted footprint before it spawns this worker (Story #4627) — see
136
+ [`/deliver`](../deliver.md).
160
137
  2. Implement the changes. When the body has a `## Slicing` / Delivery
161
138
  Slicing table, walk rows as **intra-session checkpoints** (commit +
162
139
  flip each row when done) — never as sibling tickets.
@@ -364,173 +341,36 @@ Flags:
364
341
 
365
342
  ---
366
343
 
367
- ## Step 4 — CI fix loop (**recovery-only**)
344
+ ## Steps 4–6Recovery router (**recovery-only**)
368
345
 
369
346
  > **Steps 4, 5, 5.5, and 6 are recovery paths, not routine choreography
370
347
  > (Story #4543).** On the default path Step 3 already polled the PR to a
371
348
  > confirmed merge, flipped `agent::done`, and ran the whole post-land tail —
372
349
  > follow-up capture, status resync, ref cleanup, base fast-forward — in one
373
350
  > process. A `landed` envelope means all of it ran; go straight to Step 7.
374
- >
375
- > Enter this step **only** when Step 3 returned `blocked` with
376
- > `blockClass: "checks-failed"` (a required check went red), or when a
377
- > `--no-wait-merge` run left the PR for you to shepherd.
378
-
379
- When a required check is red, the agent owns the green-CI outcome, not just
380
- the push. Local close-validation gates pass on the dev host's environment;
381
- CI runs on a different OS and concurrency, and coverage rounding,
382
- platform-conditional branches, and timing-sensitive tests routinely drift
383
- between the two.
384
-
385
- Fix the failure and push a new commit on `story-<storyId>` — auto-merge stays
386
- armed across retries, so you do not re-arm — then resume the land with the
387
- envelope's `nextCommand`.
388
-
389
- > **A watch is an internally-blocking step, not a reason to end your turn.**
390
- > `pr-watch-with-update.js` blocks the current turn until CI resolves — that
391
- > IS how you wait. Ending the turn with prose and an unconfirmed merge is a
392
- > contract violation (the Story #1553 / PR #1554 failure mode). See
393
- > [`deliver-story-reference.md` § The auto-merge wait is an internally-blocking step](deliver-story-reference.md#the-auto-merge-wait-is-an-internally-blocking-step).
394
-
395
- To watch the checks on the red path, drive
396
- `pr-watch-with-update.js` — the **single CI-watch mechanism**
397
- (Story #4358). It polls the required checks to a
398
- terminal state and auto-recovers from `mergeStateStatus: BEHIND`; do
399
- **not** fall back to a bare `gh pr checks` watch invocation:
400
351
 
401
- ```bash
402
- node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber> --story <storyId>
403
- ```
404
-
405
- `--story` is what keys the red-path CI digest
406
- (`temp/story-<id>-ci-digest.{json,md}` — failing check name, run id, and a
407
- `gh run view --log-failed` tail). Omit it and a red check writes no digest.
408
-
409
- Poll cadence and caps come from `delivery.ci.watch.*`
410
- (`pollIntervalMs`, `maxPolls`, `maxResumes`); pass `--poll-interval-ms`,
411
- `--max-polls`, or `--max-resumes` to override for one run.
412
-
413
- When the watch exits, branch on the exit code:
414
-
415
- - **Exit 0 (all checks ✓)** auto-merge will fire (or has already). The
416
- Story is still at `agent::closing` with its issue OPEN. **Proceed to
417
- Step 5 within the same turn** green CI is the *start* of the
418
- merge-confirm sequence, not a terminal state.
419
- - **Exit 1 (a check genuinely failed)** — diagnose, fix, and push a new
420
- commit on `story-<storyId>`, then re-watch. Auto-merge stays enabled
421
- across retries; no need to re-arm it. The Story stays at
422
- `agent::closing` throughout, so a failed/abandoned PR never strands a
423
- CLOSED issue. If the same failure class recurs, hand convergence off to a
424
- self-paced host loop (`/loop`) that re-runs the failing check and applies
425
- the smallest fix until it exits green.
426
- - **Exit 2 (still-running — slow CI, not red)** — the poll cap fired with
427
- checks still pending and the watcher exhausted its resume budget with
428
- nothing red. This is **never** a failure. Hand the wait off to the
429
- host's interval loop rather than ending your turn: `/loop 5m` polling
430
- `gh pr checks` until the checks settle.
431
-
432
- > **Triage authority.** How to classify and remediate a red (or repeatedly
433
- > slow) check — the root-cause-only decision tree for infra/transient and
434
- > flaky failures (reproduce → check `main` → bisect env vs code → fix in-scope
435
- > or file a `meta::framework-gap` issue), the never-rerun / never-quarantine
436
- > prohibitions, and the escalation criteria (three-strikes, the 30-minute
437
- > wall-clock timebox, and the clearly-environmental fast path) — is defined
438
- > once in [`.agents/rules/ci-remediation.md`](../../rules/ci-remediation.md).
439
- > Read it before remediating a red check above.
440
- >
441
- > **CI recovery procedures.** For resurrecting the worktree after
442
- > `reapOnSuccess`, pulling the failing job log, fixing coverage/CRAP
443
- > baselines without re-running close-validation, and the when-to-stop
444
- > Anti-Thrashing rules, see
445
- > [`deliver-story-reference.md` § Step 4 — CI watch + fix recovery](deliver-story-reference.md#step-4--ci-watch--fix-recovery).
446
-
447
- ---
448
-
449
- ## Step 5 — Merge confirmation + land tail (**recovery-only**)
450
-
451
- > On the default path Step 3 already did this. Run it only to resume a
452
- > `pending` envelope, to finish a `--no-wait-merge` run, or to rescue a
453
- > merged-but-mislabelled Story.
454
-
455
- ```bash
456
- node .agents/scripts/single-story-confirm-merge.js --story <storyId> --cwd <main-repo>
457
- ```
458
-
459
- This is the **same** shared land path Step 3 reaches: it flips
460
- `agent::closing → agent::done` on a confirmed merge (closing the issue) and
461
- runs the **same** post-land tail — so the two surfaces cannot diverge. It is
462
- idempotent, emits the same terminal envelope, and is safe to re-run while
463
- the PR is still open (returns `pending`).
464
-
465
- > **Confirmation outcomes.** `single-story-confirm-merge.js` re-reads the
466
- > live PR state and flips to `agent::done` only on a confirmed `MERGED` PR;
467
- > it is idempotent and safe to re-run while the PR is still open (returns
468
- > `pending`). See
469
- > [`deliver-story-reference.md` § Step 5 — Merge confirmation detail](deliver-story-reference.md#step-5--merge-confirmation-detail).
470
-
471
- ---
472
-
473
- ## Step 5.5 — Re-assert Status column (**recovery-only**)
474
-
475
- > **The land tail already ran this** (Story #4543) — it is `tail.statusResync`
476
- > in the terminal envelope. Run it by hand only when that step reported
477
- > `false`, or after a manual merge on a `--no-wait-merge` run.
478
-
479
- GitHub Projects v2 built-in workflows fire minutes *after* auto-merge lands
480
- and clobber the `Done` Status the confirm step set, stranding closed
481
- Stories at `In Progress` on the board (reproduced on Story #2813).
482
- Re-assert authority:
483
-
484
- ```bash
485
- node .agents/scripts/resync-status-column.js --story <storyId>
486
- ```
487
-
488
- The helper re-fires the `ColumnSync` mutation and **polls for ~15 s** to win
489
- the race against the bot's late write (Story #2876). It is idempotent and
490
- no-op-safe (`no-project` / `not-on-project` exit 0).
491
-
492
- > **Status-column detail + tuning flags + operator fix.** For the poll-loop
493
- > flags (`--poll-attempts`, `--poll-delay-ms`), the `attempts` / `drifted`
494
- > envelope semantics, and the canonical
495
- > `--reap-conflicting-workflows` operator fix, see
496
- > [`deliver-story-reference.md` § Step 5.5 — Re-assert Status column detail](deliver-story-reference.md#step-55--re-assert-status-column-detail).
497
-
498
- ---
499
-
500
- ## Step 6 — Local branch cleanup (**recovery-only**)
501
-
502
- > **The land tail already ran this** (Story #4543) — it is `tail.refCleanup`
503
- > and `tail.baseFastForward` in the terminal envelope, done in-process
504
- > against the same planners this command drives. Run it by hand only when
505
- > either step reported `false` (a dirty shared checkout is the common,
506
- > benign cause), or after a manual merge on a `--no-wait-merge` run.
507
-
508
- GitHub deletes the **remote** branch on auto-merge, but the **local**
509
- `story-<storyId>` ref lingers in the main checkout until something prunes
510
- it. To prune the story ref **and** fast-forward local `main` (or
511
- `project.baseBranch`):
512
-
513
- ```bash
514
- node .agents/scripts/git-cleanup.js \
515
- --execute \
516
- --remote \
517
- --yes \
518
- --fast-forward-main \
519
- --branches \
520
- --include "story-<storyId>"
521
- ```
522
-
523
- `--fast-forward-main` brings local `main` current (the next init seeds from
524
- it), `--branches` + `--include` reap only this Story's ref, and
525
- `--execute --remote --yes` run the deletes non-interactively. The sweep is
526
- idempotent and safe to run before `MERGED` confirms. Skip Step 6 only when
527
- the operator opted out via `--no-auto-merge` AND has not yet merged the PR —
528
- run the cleanup after the manual merge lands.
529
-
530
- > **Why local `main` goes stale + per-flag behaviour.** For the stale-`main`
531
- > mechanism and the full `--fast-forward-main` / `--branches` / `--include`
532
- > flag semantics, see
533
- > [`deliver-story-reference.md` § Step 6 — Local branch cleanup detail](deliver-story-reference.md#step-6--local-branch-cleanup-detail).
352
+ Enter a recovery path **only** when Step 3's terminal envelope tells you to.
353
+ The full procedures — commands, exit-code branches, and the
354
+ internally-blocking-watch contract — live in
355
+ [`deliver-story-reference.md`](deliver-story-reference.md); route by the
356
+ envelope:
357
+
358
+ - **`blocked` / `blockClass: "checks-failed"`** (a required check went red)
359
+ fix and push a new commit on `story-<storyId>` (auto-merge stays armed), then
360
+ resume with the envelope's `nextCommand`. The watch is an internally-blocking
361
+ step never end your turn with prose and an unconfirmed merge (Story #1553).
362
+ Procedure:
363
+ [reference § Step 4 — CI watch + fix recovery](deliver-story-reference.md#step-4--ci-watch--fix-recovery)
364
+ (triage per [`rules/ci-remediation.md`](../../rules/ci-remediation.md)).
365
+ - **`pending`** (bounded merge wait expired, PR healthy; or a `--no-wait-merge`
366
+ run to shepherd) run the envelope's `nextCommand`
367
+ (`single-story-confirm-merge.js`) until it resolves
368
+ [reference § Step 5 — Merge confirmation detail](deliver-story-reference.md#step-5--merge-confirmation-detail).
369
+ - **`tail.statusResync: false`** re-assert the Status column by hand —
370
+ [reference § Step 5.5](deliver-story-reference.md#step-55--re-assert-status-column-detail).
371
+ - **`tail.refCleanup: false` / `tail.baseFastForward: false`** prune the local
372
+ ref and fast-forward `main` by hand
373
+ [reference § Step 6](deliver-story-reference.md#step-6--local-branch-cleanup-detail).
534
374
 
535
375
  ---
536
376
 
@@ -0,0 +1,174 @@
1
+ ---
2
+ description: >-
3
+ Helper — not a slash command. The shared core the three QA workflows
4
+ (/qa-run, /qa-explore, /qa-assist) consume: contract resolution + loud
5
+ failure, the session & ledger contract, redact-first, the QaLedgerItem shape,
6
+ the triage procedure (classify → route → disposition → promote), and the HITL
7
+ write gate. Each workflow states only its mode-specific phases plus a short
8
+ Constraints delta and points here for everything else.
9
+ caller: qa-run.md, qa-explore.md, qa-assist.md
10
+ ---
11
+
12
+ # helpers/qa-core — shared QA harness core
13
+
14
+ > **Not a slash command.** This file lives in `helpers/` and is not projected
15
+ > into the plugin command tree. It is consumed by reference from
16
+ > [`/qa-run`](../qa-run.md), [`/qa-explore`](../qa-explore.md), and
17
+ > [`/qa-assist`](../qa-assist.md) — it states each shared block **once** so the
18
+ > three workflows keep only their mode-specific phases and a Constraints delta.
19
+
20
+ All three QA workflows are **prose workflows**, not Node orchestrators: the
21
+ host LLM executes the procedure; deterministic Node helpers under
22
+ `.agents/scripts/lib/qa/` (contract, session, redaction, coverage, missing-test)
23
+ and `.agents/scripts/lib/findings/` (classification, dedup/route, cluster/size/
24
+ promote) own every decision. The agent never invents those decisions in prose.
25
+
26
+ ## Contract resolution (fail loudly when absent)
27
+
28
+ Resolve the consumer's `qa` contract block **before any QA work**, through the
29
+ single seam [`resolve-qa-contract.js`](../../scripts/lib/qa/resolve-qa-contract.js):
30
+
31
+ ```js
32
+ import { resolveQaContract } from '../scripts/lib/qa/resolve-qa-contract.js';
33
+ const contract = resolveQaContract(config); // throws loudly if unbound
34
+ ```
35
+
36
+ `resolveQaContract` **throws** — there is no silent fallback to auto-detection
37
+ — when the `qa` block is absent (no `qa` key, or an empty `qa: {}`), malformed
38
+ (wrong-typed or unknown field, e.g. `qa.featureRoot must be a string`), or
39
+ missing a required field (it names the first one). The absent-block message
40
+ reads: _"qa: this project has not bound the QA harness — add a `qa` block to
41
+ .agentrc.json (featureRoot, fixturesManifest, environments, personas) before
42
+ invoking the QA harness."_
43
+
44
+ When the resolver throws, **STOP immediately**: relay its verbatim message to
45
+ the operator as terminal output and do not proceed. Do not invent a
46
+ `featureRoot`, guess a sign-in seam, or fall back to any retired headless BDD
47
+ runner. The loud failure is the contract — a consumer that has not bound the
48
+ harness has not opted into it.
49
+
50
+ The normalized contract exposes `featureRoot`, `fixturesManifest`,
51
+ `environments` (each keyed to `{ baseUrl, signInSeam, allowWrites? }`, resolved
52
+ to one target via [`resolveQaEnvironment`](../../scripts/lib/qa/resolve-qa-contract.js)),
53
+ `defaultEnvironment`, `personas` (canonical name-keyed map; a name-only persona
54
+ resolves to an empty record), `consoleAllowlist` (default `[]`), and
55
+ `designTokens` (default `null`).
56
+
57
+ ## Session & ledger (temp/qa/)
58
+
59
+ Resolve the session and its ledger path **once**, up front, via
60
+ [`qa-session.js`](../../scripts/lib/qa/qa-session.js):
61
+
62
+ ```js
63
+ import { resolveQaSession } from '../scripts/lib/qa/qa-session.js';
64
+ const { sessionId, ledgerPath, reused, untriaged } = resolveQaSession({ config });
65
+ ```
66
+
67
+ - The ledger is always written under **`temp/qa/<sessionId>.ndjson`**
68
+ (`<tempRoot>/qa/`, from `project.paths.tempRoot`), one `QaLedgerItem` per line
69
+ validated against [`qa-ledger.schema.json`](../../schemas/qa-ledger.schema.json).
70
+ **Never** write it anywhere else, and never commit it — `temp/` is gitignored
71
+ per [`.agents/instructions.md` § 6](../../instructions.md).
72
+ - When `reused` is `true`, a prior session of the same id exists: **append**,
73
+ never overwrite, and carry the `untriaged` items forward as the rolling
74
+ backlog. Pass `--session-id <id>` (or `QA_SESSION_ID`) to resume a named
75
+ session.
76
+
77
+ ## Redact first
78
+
79
+ Before any evidence string touches disk or GitHub, scrub it through
80
+ [`redact-evidence.js`](../../scripts/lib/qa/redact-evidence.js):
81
+
82
+ ```js
83
+ import { redactEvidence } from '../scripts/lib/qa/redact-evidence.js';
84
+ const evidence = redactEvidence(rawObservation);
85
+ ```
86
+
87
+ This is mandatory per [`security-baseline.md`](../../rules/security-baseline.md)
88
+ (§ Data Leakage & Logging, § Secrets Management) — bearer tokens, session
89
+ cookies, `Authorization` headers, and emails are masked. The pass is
90
+ idempotent, so redact eagerly; captured console and network evidence is
91
+ untrusted until scrubbed. Secrets are never echoed into chat, findings, or the
92
+ ledger.
93
+
94
+ ## The QaLedgerItem shape
95
+
96
+ Each observation/finding is recorded as one `QaLedgerItem` on the session
97
+ ledger, conforming to [`qa-ledger.schema.json`](../../schemas/qa-ledger.schema.json):
98
+
99
+ - **`id`** — stable `L1`, `L2`, … in append order (after any carried backlog).
100
+ - **`evidence`** — the **redacted** symptom / observation string.
101
+ - **`coverage`** — the surface label the item points at (or `unknown`).
102
+ - **`class`** — the ledger class (`product-bug`, `environment-setup`,
103
+ `tooling-dx`, `test-gap`, `enhancement`, …); resolves to the focus/meta label
104
+ set Triage applies.
105
+ - **`severity`** — the tentative severity.
106
+ - **`missingTest`** — the lowest absent test tier's description, or `null`.
107
+ - **`disposition`** — left **untriaged** at capture; set only in Triage.
108
+
109
+ **Append** to the ledger, never overwrite; a re-run appends to the same
110
+ session. This is the single findings channel across all three workflows — there
111
+ is no per-workflow finding schema.
112
+
113
+ ## Triage — classify → route → disposition → promote
114
+
115
+ Route the ledger through the shared classify/route/dedup/promote core. The
116
+ outcome is that **every ledger item carries a class, a route decision, and an
117
+ operator-confirmed disposition**, with each `file` item promoted via
118
+ `promote-finding.js` into `/plan` — verified by the cluster's fingerprint
119
+ footer landing in each seed body. For each untriaged item:
120
+
121
+ 1. **Classify** via
122
+ [`classify-finding.js`](../../scripts/lib/findings/classify-finding.js). The
123
+ item's `class` resolves to the focus/meta label set (`tooling-dx` carries
124
+ `meta::framework-gap`; `enhancement` carries `meta::consumer-improvement`).
125
+ The helper **throws** on an absent/unknown class — fix the item's class
126
+ rather than defaulting.
127
+ 2. **Dedup / route** against existing GitHub Issues (open **and** closed) via
128
+ [`route-finding.js`](../../scripts/lib/findings/route-finding.js) — the
129
+ **single** dedup implementation shared with `audit-to-stories`:
130
+
131
+ ```js
132
+ import { routeFinding, fingerprintFooter } from '../scripts/lib/findings/route-finding.js';
133
+ const { decision, matchedIssue, fingerprint } =
134
+ await routeFinding(finding, { searchIssues });
135
+ ```
136
+
137
+ `decision` is one of `new` / `update-existing` / `duplicate` /
138
+ `regression-of-closed`. Wire `searchIssues` to the GitHub provider and stamp
139
+ the `fingerprintFooter(sha)` marker into any Issue body so future runs dedup
140
+ against it.
141
+ 3. **Decide the disposition** with the operator (`file` / `defer` / `dismiss`)
142
+ and record it back onto the ledger item.
143
+ 4. **Promote the `file`-dispositioned findings through `/plan`** via
144
+ [`promote-finding.js`](../../scripts/lib/findings/promote-finding.js) — the
145
+ same cluster/size/route/file path `audit-to-stories` consumes. Never
146
+ hand-roll the clustering, sizing, or promotion in prose:
147
+
148
+ ```js
149
+ import { promoteFindings } from '../scripts/lib/findings/promote-finding.js';
150
+ const { promotions } = await promoteFindings(ledgerItems, {
151
+ searchIssues, // GitHub provider, open + closed
152
+ createStory, // tight cluster (≤2 surfaces): seed → /plan --seed-file
153
+ createPlanSeed, // broad cluster (>2 surfaces): same /plan --seed-file path (may N>1)
154
+ });
155
+ ```
156
+
157
+ `promoteFindings` runs `clusterLedgerItems` + `targetForCluster`: a cluster
158
+ spanning **≤2** distinct coverage surfaces routes to `createStory`, **>2** to
159
+ `createPlanSeed` — neither opens an Epic; both render a **redacted** plan
160
+ seed (redaction already ran at capture), **stamp the cluster's
161
+ `fingerprintFooter(sha)` verbatim into the seed body**, and chain
162
+ `/plan --seed-file <seed>`. Prefer one Story; split only under the
163
+ default-single policy. A `file` disposition **never** opens a raw GitHub
164
+ Issue; only `defer` and `dismiss` skip the `/plan` handoff.
165
+
166
+ ## The HITL write gate
167
+
168
+ Capture stays read-only precisely so every state change lands in Triage,
169
+ deliberately and confirmed. Any ticket-filing, seed write, `/plan` invocation,
170
+ or label mutation is a **write** — present the artifact, confirm each one with
171
+ the operator, and wait before it happens. The agent never files tickets,
172
+ promotes findings, or mutates a label autonomously. The plan→deliver hard stop
173
+ is preserved: each `/plan` chain pauses at its own HITL gates and never
174
+ auto-delivers.
@@ -0,0 +1,35 @@
1
+ ---
2
+ description: >-
3
+ Reference sibling for helpers/qa-run-scenario.md — the spec-only, not-yet-
4
+ enabled batched sub-agent dispatch mode. Read only when turning that mode on.
5
+ caller: qa-run-scenario.md
6
+ ---
7
+
8
+ # helpers/qa-run-scenario — reference: batched sub-agent dispatch (spec-only)
9
+
10
+ > **Not yet enabled.** This section specifies a future execution mode; the
11
+ > current `/qa-run` sweep calls [`qa-run-scenario`](qa-run-scenario.md)
12
+ > **inline**, one scenario at a time, in the orchestrator's own turn. The
13
+ > batched mode below is documented so the contract is stable when it is turned
14
+ > on — do not implement it as live behavior from this spec alone.
15
+
16
+ In the deferred mode, the orchestrator MAY dispatch scenarios to fresh-context
17
+ sub-agents to keep its own context window focused, under these hard rules:
18
+
19
+ - **Sequential, never parallel.** Sub-agents run **one at a time**, never
20
+ concurrently. A live browser surface is a single shared resource; parallel
21
+ drivers would race on navigation and cross-contaminate evidence. (This
22
+ sequential-only rule is live today and stated in
23
+ [`qa-run-scenario.md`](qa-run-scenario.md) — it is not deferred.)
24
+ - **One sub-agent per persona group.** Scenarios are grouped by persona and a
25
+ single sub-agent drives all of one persona's scenarios, so the persona is
26
+ signed in once per group rather than per scenario.
27
+ - **Re-verify auth on entry.** Each sub-agent MUST re-verify the
28
+ authenticated-session precondition (a `take_snapshot` confirming the persona
29
+ badge) when it starts, because it does not share the orchestrator's live
30
+ session state.
31
+ - **Same input/output contract.** Each sub-agent consumes the input contract
32
+ and returns the per-scenario result shape from
33
+ [`qa-run-scenario.md`](qa-run-scenario.md) for every scenario it drove — the
34
+ orchestrator aggregates identically whether the helper ran inline or via a
35
+ batched sub-agent.