mandrel 2.7.0 → 2.9.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 +104 -330
- package/.agents/agents/auditor.md +135 -0
- package/.agents/agents/plan-critic.md +80 -0
- package/.agents/audit-checklists/dependencies.md +7 -0
- package/.agents/audit-checklists/documentation.md +1 -0
- package/.agents/docs/SDLC.md +69 -162
- package/.agents/docs/configuration.md +159 -499
- package/.agents/docs/quality-gates.md +59 -180
- package/.agents/instructions.md +170 -295
- package/.agents/rules/changelog-style.md +8 -66
- package/.agents/rules/ci-remediation.md +65 -124
- package/.agents/rules/gherkin-standards.md +10 -31
- package/.agents/rules/git-conventions-reference.md +28 -61
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/rules/orchestration-error-handling.md +5 -15
- package/.agents/rules/security-baseline.md +7 -13
- package/.agents/rules/shell-conventions.md +4 -13
- package/.agents/rules/test-seams.md +2 -2
- package/.agents/rules/testing-standards.md +7 -17
- package/.agents/schemas/agentrc.schema.json +21 -0
- package/.agents/scripts/audit-to-stories.js +51 -0
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +120 -55
- package/.agents/scripts/lib/config-settings-schema.js +32 -0
- package/.agents/scripts/lib/findings/semantic-issue-search.js +43 -5
- package/.agents/scripts/lib/observability/terse-result.js +114 -0
- package/.agents/scripts/lib/orchestration/complexity-gate.js +207 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +3 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +221 -8
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +55 -14
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +9 -3
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +13 -40
- package/.agents/scripts/lib/story-body/body-format-lints.js +215 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -0
- package/.agents/scripts/providers/github/issues.js +54 -7
- package/.agents/scripts/providers/github/search-budget.js +124 -0
- package/.agents/scripts/providers/github/search-query.js +71 -0
- package/.agents/scripts/single-story-confirm-merge.js +14 -5
- package/.agents/scripts/single-story-init.js +19 -3
- package/.agents/scripts/sync-branch-from-base.js +9 -3
- package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
- package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
- package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
- package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
- package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
- package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
- package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
- package/.agents/skills/core/security-and-hardening/reference.md +15 -273
- package/.agents/skills/skills.index.json +5 -5
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
- package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
- package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
- package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
- package/.agents/workflows/audit-accessibility.md +34 -108
- package/.agents/workflows/audit-architecture.md +35 -123
- package/.agents/workflows/audit-clean-code.md +19 -78
- package/.agents/workflows/audit-data-model.md +32 -100
- package/.agents/workflows/audit-dependencies.md +47 -111
- package/.agents/workflows/audit-devops.md +16 -83
- package/.agents/workflows/audit-documentation.md +46 -93
- package/.agents/workflows/audit-navigability.md +26 -80
- package/.agents/workflows/audit-performance.md +40 -106
- package/.agents/workflows/audit-privacy.md +17 -80
- package/.agents/workflows/audit-quality.md +35 -99
- package/.agents/workflows/audit-security.md +20 -78
- package/.agents/workflows/audit-seo.md +20 -98
- package/.agents/workflows/audit-sre.md +20 -88
- package/.agents/workflows/audit-to-stories.md +1 -8
- package/.agents/workflows/audit-ux-ui.md +17 -80
- package/.agents/workflows/deliver.md +54 -9
- package/.agents/workflows/git-cleanup.md +50 -275
- package/.agents/workflows/helpers/audit-lens-core.md +230 -0
- package/.agents/workflows/helpers/code-review.md +11 -23
- package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
- package/.agents/workflows/helpers/deliver-story.md +36 -186
- package/.agents/workflows/helpers/qa-core.md +174 -0
- package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
- package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
- package/.agents/workflows/mandrel-update.md +7 -13
- package/.agents/workflows/plan.md +44 -17
- package/.agents/workflows/qa-assist.md +140 -269
- package/.agents/workflows/qa-explore.md +125 -316
- package/.agents/workflows/qa-run.md +180 -380
- package/docs/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/.agents/workflows/helpers/audit-dual-path.md +0 -59
- package/.agents/workflows/helpers/audit-self-check.md +0 -70
- 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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
|
|
@@ -39,6 +39,16 @@ large — uses the same machinery:
|
|
|
39
39
|
If the Story still carries an `Epic: #N` reference, **stop** — that is a v1
|
|
40
40
|
Epic-attached ticket; re-plan as a v2 Story or finish it on a pre-v2 checkout.
|
|
41
41
|
|
|
42
|
+
> **Ceremony-lite Stories still land through this engine unchanged (Story
|
|
43
|
+
> #4683).** A Story that `/plan` routed onto the ceremony-lite path (its
|
|
44
|
+
> `complexityRoute.route === "lite"`) collapses only the *advisory* plan/deliver
|
|
45
|
+
> ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
|
|
46
|
+
> not earn. It does **not** get a cheaper landing: the close-validation gates
|
|
47
|
+
> (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
|
|
48
|
+
> and the `rules/security-baseline.md` MUSTs all run here exactly as for a
|
|
49
|
+
> full-ceremony Story. The lite route's `preserves` field is the machine-readable
|
|
50
|
+
> record of those non-negotiables; there is no lite-specific gate bypass.
|
|
51
|
+
|
|
42
52
|
## Prerequisites
|
|
43
53
|
|
|
44
54
|
1. A GitHub Issue with the `type::story` label and **no** `Epic: #N`
|
|
@@ -131,32 +141,9 @@ Operator/agent responsibilities while in the worktree:
|
|
|
131
141
|
`checklistPath` (footprint-matched **local**-lens authoring checklists),
|
|
132
142
|
read it before you write and self-check as you author. When absent,
|
|
133
143
|
lens-aware coverage still runs maker-blind at Story-scope review inside
|
|
134
|
-
the close subprocess.
|
|
135
|
-
|
|
136
|
-
|
|
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`).
|
|
144
|
+
the close subprocess. The dispatch step produces `checklistPath` from the
|
|
145
|
+
Story's predicted footprint before it spawns this worker (Story #4627) — see
|
|
146
|
+
[`/deliver`](../deliver.md).
|
|
160
147
|
2. Implement the changes. When the body has a `## Slicing` / Delivery
|
|
161
148
|
Slicing table, walk rows as **intra-session checkpoints** (commit +
|
|
162
149
|
flip each row when done) — never as sibling tickets.
|
|
@@ -364,173 +351,36 @@ Flags:
|
|
|
364
351
|
|
|
365
352
|
---
|
|
366
353
|
|
|
367
|
-
##
|
|
354
|
+
## Steps 4–6 — Recovery router (**recovery-only**)
|
|
368
355
|
|
|
369
356
|
> **Steps 4, 5, 5.5, and 6 are recovery paths, not routine choreography
|
|
370
357
|
> (Story #4543).** On the default path Step 3 already polled the PR to a
|
|
371
358
|
> confirmed merge, flipped `agent::done`, and ran the whole post-land tail —
|
|
372
359
|
> follow-up capture, status resync, ref cleanup, base fast-forward — in one
|
|
373
360
|
> 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
|
-
|
|
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
361
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
362
|
+
Enter a recovery path **only** when Step 3's terminal envelope tells you to.
|
|
363
|
+
The full procedures — commands, exit-code branches, and the
|
|
364
|
+
internally-blocking-watch contract — live in
|
|
365
|
+
[`deliver-story-reference.md`](deliver-story-reference.md); route by the
|
|
366
|
+
envelope:
|
|
367
|
+
|
|
368
|
+
- **`blocked` / `blockClass: "checks-failed"`** (a required check went red) →
|
|
369
|
+
fix and push a new commit on `story-<storyId>` (auto-merge stays armed), then
|
|
370
|
+
resume with the envelope's `nextCommand`. The watch is an internally-blocking
|
|
371
|
+
step — never end your turn with prose and an unconfirmed merge (Story #1553).
|
|
372
|
+
Procedure:
|
|
373
|
+
[reference § Step 4 — CI watch + fix recovery](deliver-story-reference.md#step-4--ci-watch--fix-recovery)
|
|
374
|
+
(triage per [`rules/ci-remediation.md`](../../rules/ci-remediation.md)).
|
|
375
|
+
- **`pending`** (bounded merge wait expired, PR healthy; or a `--no-wait-merge`
|
|
376
|
+
run to shepherd) → run the envelope's `nextCommand`
|
|
377
|
+
(`single-story-confirm-merge.js`) until it resolves —
|
|
378
|
+
[reference § Step 5 — Merge confirmation detail](deliver-story-reference.md#step-5--merge-confirmation-detail).
|
|
379
|
+
- **`tail.statusResync: false`** → re-assert the Status column by hand —
|
|
380
|
+
[reference § Step 5.5](deliver-story-reference.md#step-55--re-assert-status-column-detail).
|
|
381
|
+
- **`tail.refCleanup: false` / `tail.baseFastForward: false`** → prune the local
|
|
382
|
+
ref and fast-forward `main` by hand —
|
|
383
|
+
[reference § Step 6](deliver-story-reference.md#step-6--local-branch-cleanup-detail).
|
|
534
384
|
|
|
535
385
|
---
|
|
536
386
|
|
|
@@ -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.
|