@dombaras/agent-harness 0.1.11 → 0.1.14

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/README.md CHANGED
@@ -11,8 +11,9 @@ and QA gates — then deploys them into any project.
11
11
 
12
12
  ## Why
13
13
 
14
- - The agent harness is tooling, not application code. It should not be committed
15
- into every app repo.
14
+ - The agent harness is tooling, not application code. Its source lives in the
15
+ npm package you deploy it with `init`/`update` rather than hand-copying it
16
+ into every repo.
16
17
  - One canonical, versioned source of truth for personas/rules/QA tiers, deployed
17
18
  on demand and updatable via `npx @dombaras/agent-harness update`.
18
19
 
@@ -42,8 +43,10 @@ npx @dombaras/agent-harness init --target . --dry-run
42
43
  | `.agents/features/INDEX.md` | pointer to `BACKLOG.md` (per-feature doc tree root) | **project** (create-if-missing) |
43
44
  | `opencode.json` | main/small model routing + `instructions` | harness (**merged**, see below) |
44
45
  | `scripts/qa/*` | `test:dispatch` / `test:governance` / `test:qa-plan` gates + QA-script wiring check | harness (overwrite) |
45
- | `.agents/memory/*` | project data (domain-map, stack-versions, handoff, locations, model-routing, history, flow-map, qa-plan) | **project** (create-if-missing) |
46
+ | `.agents/memory/*` | project data (domain-map, stack-versions, handoff, locations, model-routing, history, flow-map, product-principles) | **project** (create-if-missing) |
47
+ | `.agents/qa-plans/*` | project data (one QA plan file per change — sharded so concurrent sessions never collide) | **project** (create-if-missing) |
46
48
  | `.harness.json` | deployed version + project profile + per-file checksums | harness |
49
+ | `.gitignore` | harness-managed section (`.harness-backup/`) merged in, project entries preserved | harness (merged) |
47
50
 
48
51
  ### `opencode.json` is merged, not clobbered
49
52
 
@@ -67,12 +70,24 @@ single write-path for all open work: what's known, priority, evidence). When a f
67
70
  is picked up, the `features` persona authors the per-feature doc chain
68
71
  (`.agents/features/<slug>/`), each layer routed to its author persona — `intent.md`
69
72
  (product-manager) → `scope.md` (system-architect, HIGH-risk only) → `plan.md` (planner)
70
- → `tests.md` (qa-architect, points to `qa-plan.md`). Ceremony is right-sized to blast
73
+ → `tests.md` (qa-architect, points to the feature's QA plan). Ceremony is right-sized to blast
71
74
  radius: trivial → none, low → single `intent.md`, high (auth/DB/public API/flow-siblings)
72
75
  → full chain. Docs live beside the code, are written-back before continuing when
73
76
  implementation invalidates them, and are archived on ship. `.agents/features/INDEX.md`
74
77
  is a pointer to `BACKLOG.md`. See `.agents/skills/features/SKILL.md`.
75
78
 
79
+ **Product-first planning (`.agents/memory/product-principles.md`)** — the project-owned
80
+ product concepts (actors, product concepts, UX heuristics, design language, key journeys,
81
+ value-maximization rules) that LEAD planning. The `planner` reads it (plus a feature's
82
+ `intent.md`) before emitting a dispatch plan and sequences work value-first — smallest
83
+ slice that delivers the core outcome first — rather than by tech dependency. `intent.md`
84
+ must define Actor(s) → Need → Outcome → Acceptance criteria, and `plan.md` a Value-first
85
+ sequence + Product concepts applied; `npm run test:product` fails a feature that reaches
86
+ planning/implementation without them (a plan with no intent, or an intent that never
87
+ names a user/need/outcome). Fill `product-principles.md` in at `init`; evolve it as the
88
+ product does — see `.agents/skills/planner/SKILL.md` and
89
+ `.agents/skills/features/SKILL.md`.
90
+
76
91
  **Flow map (`.agents/memory/flow-map.md`)** — the project-owned registry powering the
77
92
  flow-closure half of `test:qa-plan`. Each user-facing flow lists the code surfaces that
78
93
  implement the same behavior across codebases/layers (e.g. web component, `mobile/` sheet,
@@ -109,10 +124,9 @@ warns but does not block; the guarantee only applies to registered flows.
109
124
  `opencode/deepseek-v4-pro` (gateway), `pickle` → `opencode/big-pickle`, or any
110
125
  explicit `provider/model`. Restart opencode after switching (config reads once).
111
126
  - **`/board` command** — every deploy ships an opencode command
112
- (`.opencode/command/board.md`) that refreshes the canonical `BACKLOG.md` board:
113
- `git pull --ff-only` (safe, never forces), then renders Open (by priority),
114
- Frozen, the last few shipped rows, and the newest `.agents/memory/handoff.md`
115
- delta. Read-only — it never edits the board.
127
+ (`.opencode/command/board.md`) that reads the local `BACKLOG.md` and renders a
128
+ clean task list (Priority · Title · Short description). Read-only, no git the
129
+ local file is the source of truth.
116
130
 
117
131
  ## QA gates
118
132
 
@@ -122,9 +136,15 @@ warns but does not block; the guarantee only applies to registered flows.
122
136
  source of truth; status/priority enums, frozen-reopen conditions, non-empty
123
137
  priority/evidence, no duplicate IDs, and `INDEX.md`/`todo.md` must be pointers (not
124
138
  live lists).
139
+ - `npm run test:product` — **product/UX-first gate**: every feature doc in
140
+ `.agents/features/<slug>/` must define its user before planning/implementation.
141
+ `intent.md` must carry Actor(s) → Need → Outcome → Acceptance criteria, and `plan.md`
142
+ a Value-first sequence + Product concepts applied; a `plan.md` with no `intent.md`
143
+ fails (never plan around an undefined user need). Backward-compatible — no feature
144
+ docs, gate passes.
125
145
  - `npm run test:qa-plan` — **diff-coverage + flow-closure gate**:
126
146
  every changed CODE path must have a covering assertion (or a waivered reason) in
127
- `.agents/memory/qa-plan.md` before a change is verified — and every touched flow
147
+ a QA plan (one per-change file under `.agents/qa-plans/`) before a change is verified — and every touched flow
128
148
  (`.agents/memory/flow-map.md`) must have every sibling surface and declared optional
129
149
  variant addressed in the plan's `## Parallel-surface & variant audit`. Kills the
130
150
  "verified by a suite that never touched the change" failure mode: a tier label never
@@ -148,12 +168,14 @@ npx @dombaras/agent-harness update --target /path/to/project
148
168
 
149
169
  - Overwrites harness-owned files, preserves `.agents/memory/*` and `BACKLOG.md`.
150
170
  - Auto-wires the harness gate scripts (`test:dispatch`, `test:governance`,
151
- `test:qa-plan`, `test:backlog`) into the target's `package.json` (merged, add-only).
152
- - **Auto-commits** only the harness files it changed (`chore(harness): @dombaras/agent-harness
171
+ `test:qa-plan`, `test:backlog`, `test:product`) into the target's `package.json` (merged, add-only).
172
+ - **Auto-commits** the harness files it changed (`chore(harness): @dombaras/agent-harness
153
173
  <old> -> <new>`) and **pushes** to origin, so the next session never sees unexplained
154
174
  modified harness files. Your unrelated uncommitted work is never staged.
155
175
  - `--no-commit` to skip commit+push, `--no-push` to commit but not push.
156
- - `init` never commits.
176
+ - `init` auto-commits + pushes by default too: it commits everything it scaffolds
177
+ (including `.agents/memory/*`, `.agents/features/*`, and `BACKLOG.md`) so a fresh
178
+ project starts fully tracked and no harness file is left untracked.
157
179
  - Harness-owned paths are **force-added**, so a project that gitignores deployment
158
180
  artifacts (an inherited `.agents/` `.opencode/` `AGENTS.md` pattern) still gets the
159
181
  harness commit instead of a raw `git add` failure that silently skips it — force-added
@@ -171,6 +193,10 @@ npx @dombaras/agent-harness update --target /path/to/project
171
193
  last deploy (tracked by checksum in `.harness.json`), it is backed up to
172
194
  `.harness-backup/<timestamp>/` before overwrite.
173
195
  - `opencode.json` is merged (project keys preserved).
196
+ - **Managed `.gitignore`**: the harness merges a marked section (`.harness-backup/`, its
197
+ local rollback artifacts) into the project's `.gitignore`, preserving every project entry.
198
+ New harness files are tracked (committed), not ignored — so they don't need a gitignore
199
+ change. If the harness ever adds a new local-only artifact, `update` re-merges the entry.
174
200
  - `--dry-run` previews the plan without writing.
175
201
 
176
202
  ### Backlog migration (`migrate-backlog`)
@@ -92,14 +92,16 @@ function relKey(rel) {
92
92
  return rel.split(path.sep).join("/");
93
93
  }
94
94
 
95
- /* `.agents/memory/*` (session memory) and `.agents/features/*` (feature doc
96
- * tree) are project data — scaffolded create-if-missing, never overwritten,
97
- * and never tracked in the manifest, exactly like the memory files. */
95
+ /* `.agents/memory/*` (session memory), `.agents/features/*` (feature doc tree)
96
+ * and `.agents/qa-plans/*` (one QA plan file per change) are project data —
97
+ * scaffolded create-if-missing, never overwritten, and never tracked in the
98
+ * manifest. qa-plans is sharded per change so concurrent sessions never collide
99
+ * on a single shared file. */
98
100
  function isProjectScaffold(rel) {
99
101
  const parts = rel.split(path.sep);
100
102
  return (
101
103
  parts.includes(".agents") &&
102
- (parts.includes("memory") || parts.includes("features"))
104
+ (parts.includes("memory") || parts.includes("features") || parts.includes("qa-plans"))
103
105
  );
104
106
  }
105
107
  /* The canonical `BACKLOG.md` at the repo root is also project data (a live task
@@ -199,6 +201,7 @@ const HARNESS_SCRIPTS = {
199
201
  "test:governance": "node scripts/qa/governance.js",
200
202
  "test:qa-plan": "node scripts/qa/check-qa-plan.js",
201
203
  "test:backlog": "node scripts/qa/check-backlog.js",
204
+ "test:product": "node scripts/qa/check-product.js",
202
205
  };
203
206
 
204
207
  // Merge harness gate scripts into the project's existing package.json, preserving
@@ -217,6 +220,25 @@ function mergePackageJson(target) {
217
220
  return { text: JSON.stringify(merged, null, 2) + "\n", hadPkg };
218
221
  }
219
222
 
223
+ // Harness-managed .gitignore entries. These are LOCAL-ONLY artifacts (never
224
+ // committed): everything else the harness deploys is tracked by git. Entries
225
+ // live in a marked section so `update` can re-merge them (and pick up new
226
+ // entries) without touching the project's own ignores.
227
+ const GITIGNORE_MARKER = "# agent-harness (managed below)";
228
+ const HARNESS_IGNORE_ENTRIES = [".harness-backup/"];
229
+
230
+ function mergeGitignore(target) {
231
+ const p = path.join(target, ".gitignore");
232
+ const existing = fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
233
+ const lines = existing.split(/\r?\n/);
234
+ const idx = lines.findIndex((l) => l === GITIGNORE_MARKER);
235
+ const kept = idx === -1 ? lines : lines.slice(0, idx);
236
+ while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
237
+ const managed = [GITIGNORE_MARKER, ...HARNESS_IGNORE_ENTRIES, ""];
238
+ const out = kept.length ? [...kept, "", ...managed] : [...managed];
239
+ return out.join("\n");
240
+ }
241
+
220
242
  // ---------------------------------------------------------------- git helpers
221
243
 
222
244
  function runGit(target, args) {
@@ -348,6 +370,23 @@ async function deploy(target, opts) {
348
370
  }
349
371
  }
350
372
 
373
+ // Merge harness-managed gitignore entries (add-only, marked section).
374
+ {
375
+ const text = mergeGitignore(target);
376
+ const key = ".gitignore";
377
+ const targetAbs = path.join(target, ".gitignore");
378
+ const had = fs.existsSync(targetAbs);
379
+ const current = had ? fs.readFileSync(targetAbs, "utf8") : null;
380
+ if (!had) actions.push({ rel: key, kind: "create" });
381
+ else if (current !== text) actions.push({ rel: key, kind: "merge" });
382
+ else actions.push({ rel: key, kind: "unchanged" });
383
+ manifest[key] = sha256(text);
384
+ if (!dryRun) {
385
+ fs.mkdirSync(path.dirname(targetAbs), { recursive: true });
386
+ fs.writeFileSync(targetAbs, text, "utf8");
387
+ }
388
+ }
389
+
351
390
  for (const { abs, rel } of listFiles(TEMPLATES_DIR)) {
352
391
  const key = relKey(rel);
353
392
  const targetAbs = path.join(target, rel);
@@ -425,15 +464,15 @@ async function deploy(target, opts) {
425
464
  fs.writeFileSync(path.join(target, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n", "utf8");
426
465
  }
427
466
 
428
- // Auto-commit only this run's changed harness-owned files (update only).
467
+ // Auto-commit the files this run created/changed so nothing the harness
468
+ // deploys is left untracked. Runs for both `init` and `update`. Excludes only
469
+ // rollback artifacts; freshly-scaffolded project-data files (memory/features/
470
+ // BACKLOG) are committed so they start tracked, while pre-existing project
471
+ // data is never touched (a "preserve" never enters `changedRelsAll`).
429
472
  let commitResult = null;
430
- if (isUpdate && !dryRun && !nothingToUpdate) {
431
- const changedRels = changedRelsAll.filter(
432
- (r) =>
433
- !r.startsWith(".agents/memory/") &&
434
- !r.startsWith(".agents/features/") &&
435
- !r.startsWith(".harness-backup/")
436
- );
473
+ const shouldCommit = !dryRun && (!isUpdate || !nothingToUpdate);
474
+ if (shouldCommit) {
475
+ const changedRels = changedRelsAll.filter((r) => !r.startsWith(".harness-backup/"));
437
476
  changedRels.push(CONFIG_FILE); // .harness.json reflects the new deployed version
438
477
  commitResult = commitHarnessChanges(target, changedRels, {
439
478
  commit,
@@ -514,26 +553,29 @@ function printUnpublishedWarning() {
514
553
  function printNextSteps() {
515
554
  console.log(" Next steps:");
516
555
  console.log(" 1. Fill in `.agents/memory/domain-map.md`, `.agents/memory/stack-versions.md`,");
556
+ console.log(" `.agents/memory/product-principles.md` (actors, product concepts, UX");
557
+ console.log(" heuristics — the planner reads this to lead planning product-first),");
517
558
  console.log(" and `.agents/memory/flow-map.md` (register cross-surface flows so");
518
559
  console.log(" test:qa-plan enforces their sibling-surface/variant closure).");
519
- console.log(" 2. Harness gate scripts (`test:dispatch`, `test:governance`, `test:qa-plan`,");
520
- console.log(" `test:backlog`) were auto-wired into package.json \"scripts\".");
521
- console.log(" 3. Capture found items as one light row in the canonical `BACKLOG.md` (repo root);");
522
- console.log(" dispatch the `features` persona to author the per-feature doc chain when one is picked up.");
523
- console.log(" 4. Restart your agent CLI (config is read once at startup).\n");
560
+ console.log(" 2. Harness gate scripts (`test:dispatch`, `test:governance`, `test:qa-plan`,");
561
+ console.log(" `test:backlog`, `test:product`) were auto-wired into package.json \"scripts\".");
562
+ console.log(" 3. Capture found items as one light row in the canonical `BACKLOG.md` (repo root);");
563
+ console.log(" dispatch the `features` persona to author the per-feature doc chain when one is picked up.");
564
+ console.log(" 4. Restart your agent CLI (config is read once at startup).\n");
524
565
  }
525
566
 
526
567
  function printUsage() {
527
568
  console.log(
528
569
  `agent-harness v${PKG.version}\n\n` +
529
570
  `Usage:\n` +
530
- ` agent-harness init [--target <dir>] [--name <project>] [--domain <desc>] [--yes] [--dry-run]\n` +
571
+ ` agent-harness init [--target <dir>] [--name <project>] [--domain <desc>] [--yes] [--dry-run] [--no-commit] [--no-push]\n` +
531
572
  ` agent-harness update [--target <dir>] [--dry-run] [--no-commit] [--no-push]\n` +
532
573
  ` agent-harness migrate-backlog [--target <dir>]\n` +
533
574
  `\n` +
534
- ` update auto-commits only the harness files it changes (chore(harness): ...) and\n` +
535
- ` pushes to origin by default. Use --no-commit to skip commit+push, or --no-push\n` +
536
- ` to commit but not push. Your unrelated uncommitted work is never staged.\n` +
575
+ ` init and update auto-commit only the harness files they create/change\n` +
576
+ ` (chore(harness): ...) and push to origin by default. Use --no-commit to\n` +
577
+ ` skip commit+push, or --no-push to commit but not push. Your unrelated\n` +
578
+ ` uncommitted work is never staged.\n` +
537
579
  `\n` +
538
580
  ` migrate-backlog consolidates any live \`.agents/features/INDEX.md\` F-rows and\n` +
539
581
  ` \`.agents/memory/todo.md\` into the canonical BACKLOG.md, then rewrites those\n` +
@@ -785,10 +827,11 @@ async function init(target, flags) {
785
827
  yes: !!flags["--yes"],
786
828
  dryRun: !!flags["--dry-run"],
787
829
  isUpdate: false,
788
- commit: false,
789
- push: false,
830
+ commit: !flags["--no-commit"],
831
+ push: !flags["--no-commit"] && !flags["--no-push"],
790
832
  });
791
833
  printSummary(result, false);
834
+ printCommitResult(result);
792
835
  if (!result.dryRun) printNextSteps();
793
836
  }
794
837
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dombaras/agent-harness",
3
- "version": "0.1.11",
3
+ "version": "0.1.14",
4
4
  "description": "Reusable multi-agent harness for AI-assisted development: personas, skills, operating rules, model routing, and QA gates. Deploy into any project with `npx @dombaras/agent-harness init`.",
5
5
  "bin": {
6
6
  "agent-harness": "bin/agent-harness.js"
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "test": "node --test",
19
- "lint": "node --check bin/agent-harness.js && node --check templates/scripts/qa/check-dispatch-config.js && node --check templates/scripts/qa/governance.js && node --check templates/scripts/qa/check-qa-plan.js && node --check templates/scripts/qa/check-qa-scripts.js && node --check templates/scripts/qa/check-backlog.js"
19
+ "lint": "node --check bin/agent-harness.js && node --check templates/scripts/qa/check-dispatch-config.js && node --check templates/scripts/qa/governance.js && node --check templates/scripts/qa/check-qa-plan.js && node --check templates/scripts/qa/check-qa-scripts.js && node --check templates/scripts/qa/check-backlog.js && node --check templates/scripts/qa/check-product.js"
20
20
  },
21
21
  "keywords": [
22
22
  "opencode",
@@ -42,7 +42,7 @@ The canonical rulebook. The always-loaded summary is `.agents/rules/00-operating
42
42
  - **Persona map**:
43
43
  | Work area | Persona |
44
44
  |---|---|
45
- | Task decomposition → dispatch plan | `planner` |
45
+ | Task decomposition → value-led dispatch plan (product-first) | `planner` |
46
46
  | Web app (React/Next.js/Tailwind/shadcn) | `frontend-engineer` |
47
47
  | Mobile native (Expo/React Native) | `mobile-engineer` |
48
48
  | Design system / tokens / shared components | `ui-designer` |
@@ -64,16 +64,25 @@ The canonical rulebook. The always-loaded summary is `.agents/rules/00-operating
64
64
  low → single `intent.md`, high (auth/DB/API/flow-siblings) → full chain. Write
65
65
  back first (update the doc before continuing when implementation invalidates it),
66
66
  archive on ship. Feature docs are durable and complementary to the per-session
67
- `qa-plan.md`/`handoff.md`; the `tests` doc points to `qa-plan.md`, never duplicates it.
67
+ `.agents/qa-plans/<feature-id>.md`/`handoff.md`; the `tests` doc points to the
68
+ feature's QA plan, never duplicates it.
69
+ - **Product intent is never optional (no silent skip)**: planning is
70
+ product-first. `intent.md` must define Actor(s) → Need → Outcome → Acceptance
71
+ criteria, and `plan.md` a Value-first sequence + Product concepts applied, read
72
+ against `.agents/memory/product-principles.md`. `npm run test:product` fails any
73
+ feature doc missing these, or a `plan.md` with no `intent.md` — mirroring the
74
+ "QA planning is never optional" ladder: `product-manager`/`planner` →
75
+ orchestrator fallback → (never a silent skip).
68
76
  - **QA planning is never optional (no silent skip)**: every change that touches
69
77
  code paths MUST get a QA plan before it is verified. The plan is authored by
70
78
  `qa-architect` (thinker). If `qa-architect` is not dispatched — for ANY reason,
71
79
  including dispatch failure or simply not invoking it — the **main orchestrator
72
80
  performs the qa-architect role itself** and writes the same coverage plan into
73
- `.agents/memory/qa-plan.md`. The QA plan/copy escalation ladder NEVER leaves a
74
- code change unplanned: `qa-architect` -> orchestrator fallback -> (never a silent
75
- skip). `npm run test:qa-plan` enforces that every changed code path has a covering
76
- assertion or a waivered reason.
81
+ a per-change file under `.agents/qa-plans/` (one file per change, named by
82
+ `BACKLOG.md` ID or slug never edit another change's plan file). The QA
83
+ plan/copy escalation ladder NEVER leaves a code change unplanned: `qa-architect` ->
84
+ orchestrator fallback -> (never a silent skip). `npm run test:qa-plan` enforces
85
+ that every changed code path has a covering assertion or a waivered reason.
77
86
  - **Waivers** (the only way to skip a persona): a persona may be skipped only when (a) the change is fully covered by an automated gate on push or in the QA tiers, AND (b) the skip is pre-audited in `.agents/memory/` with a cited pointer. Log every waiver as `waived: <persona>` with `reason: <gate|pointer>`.
78
87
  - **Dispatch failure ladder** (never silent): retry once (resume the same `task_id`); if it still fails, do the work inline and log `degraded: <persona> model: <reason>`; never silently skip.
79
88
  - **Bound every dispatch (task spec)** — never hand a persona a bare metric ("get under N lines", "type everything"). Each dispatch prompt carries: `Objective` (one deliverable) → `Owned files` (exact paths) → `Read-only files` → `Shared contracts (owns|consumes)` → `Done = <gate command + observable metric>` → `Out-of-scope`.
@@ -90,7 +99,7 @@ The canonical rulebook. The always-loaded summary is `.agents/rules/00-operating
90
99
  - Split QA personas: `qa-architect` (thinks — inspects the diff, audits gaps, selects the minimal tier, authors progression tests) vs `qa-runner` (does — executes the chosen tier, reports pass/fail verbatim). The thinker never runs; the doer never designs.
91
100
  - **Static ≠ Runtime rule**: never declare UI/mobile/API changes verified from static typing alone — execute a real runtime path.
92
101
  - **Coverage ≠ label rule**: a tier command verifies a change ONLY when its backing suite actually exercises the changed path. A suite that touches unrelated code proves nothing about this change. `npm run test:qa-plan` mechanically asserts every changed code path is covered by the QA plan (or waivered) before verification — and, via `flow-map.md`, that every touched flow's sibling surfaces and declared optional variants are addressed in the plan's audit (the "fix one, fix all" gate: a web-only diff must still say what the mobile twin does).
93
- - **Tiers** (commands are **project-provided** — the target project must define them; the harness ships only `test:dispatch`, `test:governance`, and `test:qa-plan`):
102
+ - **Tiers** (commands are **project-provided** — the target project must define them; the harness ships only its own gates: `test:dispatch`, `test:governance`, `test:qa-plan`, `test:backlog`, `test:product`):
94
103
  | Tier | Scope | Command |
95
104
  |---|---|---|
96
105
  | 1 | UI/CSS/mobile/copy | `npm run test:quick` |
@@ -108,6 +117,7 @@ run when the session touched code paths — a backlog-only (or docs-only) change
108
117
  them. Steps 3-5 are the unconditional session close; they are NOT gated by QA.
109
118
 
110
119
  0. **Feature Completeness Gate** (code changes only):
120
+ - [ ] Product-first: `intent.md` (actor/need/outcome/acceptance) + value-led `plan.md` present — `npm run test:product` green (feature work only).
111
121
  - [ ] 5 UI states handled on every affected screen (list them).
112
122
  - [ ] No `catch` blocks that only `console.warn` without user-facing feedback.
113
123
  - [ ] Haptic/feedback consistency for every user-initiated action.
@@ -115,7 +125,7 @@ them. Steps 3-5 are the unconditional session close; they are NOT gated by QA.
115
125
  - [ ] Cross-screen audit if a new UX pattern was introduced.
116
126
  - [ ] No modified file over ~500 lines without extracting components.
117
127
  - [ ] No blanket file-level `eslint-disable`/`@ts-nocheck`/`@ts-ignore` suppressions (line-level only, each with a reason).
118
- 1. **QA tier** (code changes only) — `qa-architect` inspects the diff, picks the tier BY COVERAGE (not path-label), records the coverage map in `.agents/memory/qa-plan.md`, and authors progression tests for any GAP; if `qa-architect` is not dispatched the orchestrator plans in its place. `qa-runner` executes and makes it pass (§6). A change is not verified until `npm run test:qa-plan` passes: every changed code path covered or waivered, **and** every touched flow (`.agents/memory/flow-map.md`) has every sibling surface and declared variant addressed in the plan's `## Parallel-surface & variant audit`, **AND** the executed tier exercised the modified path.
128
+ 1. **QA tier** (code changes only) — `qa-architect` inspects the diff, picks the tier BY COVERAGE (not path-label), records the coverage map in a per-change plan under `.agents/qa-plans/` (one file per change — never edit another change's plan), and authors progression tests for any GAP; if `qa-architect` is not dispatched the orchestrator plans in its place. `qa-runner` executes and makes it pass (§6). A change is not verified until `npm run test:qa-plan` passes: every changed code path covered or waivered, **and** every touched flow (`.agents/memory/flow-map.md`) has every sibling surface and declared variant addressed in the plan's `## Parallel-surface & variant audit`, **AND** the executed tier exercised the modified path.
119
129
  2. **Security check** (code changes only) — apply `security-engineer` when the change touches data/auth/input/secrets/deps.
120
130
  3. **Backlog write-back** (always) — move every task this session shipped from `Open` to `Archive (shipped · done)` in `BACKLOG.md` (with date/commit), then stage it explicitly: `git add BACKLOG.md` (it starts untracked — `git commit -am` / `git commit` without `add` will NOT pick it up). The board syncs across sessions only through git, so this delta is committed with the code, never left uncommitted.
121
131
  4. **Commit** (always) — concise `feat:` / `fix:` / `refactor:` message, including the `BACKLOG.md` delta.
@@ -0,0 +1,37 @@
1
+ # Product principles — {{PROJECT_NAME}}
2
+
3
+ Project-owned product concepts that **lead** planning. The planner reads this
4
+ before emitting a dispatch plan; `product-manager` and `ui-designer` keep it
5
+ current. Fill it in during `npx @dombaras/agent-harness init`; evolve it as the
6
+ product does. Skills reference this instead of hardcoding product/UX assumptions.
7
+
8
+ ## Actors
9
+ <!-- who uses the product, their goals, and emotional states -->
10
+ _TODO: list each actor (role), their primary goal, and what frustrates/delights them._
11
+
12
+ ## Product concepts
13
+ <!-- the durable product ideas every feature must honor (e.g. community trust, one-tap primary actions) -->
14
+ _TODO: name the 2-5 concepts that define this product._
15
+
16
+ ## UX heuristics
17
+ <!-- the standing UX rules every screen/flow follows -->
18
+ - **The 5 UI states** — ideal / empty (with CTA) / loading (skeleton) / error (retry) / partial.
19
+ - **Progressive disclosure** — high-frequency actions primary (one tap); fold secondary tiers behind menus/sheets.
20
+ - **Empty = default** — a new user's empty state is a primary design target, never an edge case.
21
+ - **Mobile ergonomics** — thumb-reachability, bottom sheets over modals, swipe actions.
22
+ - **RTL** — logical properties (`ps-*`/`pe-*`), direction-aware layout.
23
+ - **No dead-ends** — every state has a way forward (CTA, retry, dismissal).
24
+
25
+ ## Design language
26
+ <!-- tokens & patterns (from ui-designer): color/typography/spacing, shared components -->
27
+ _TODO: reference the shared component / token system._
28
+
29
+ ## Key journeys
30
+ <!-- the user-facing flows that matter most, with their happy + unhappy paths -->
31
+ _TODO: list the core journeys and their critical states._
32
+
33
+ ## Value maximization rules
34
+ <!-- how planning sequences work to maximize user value first -->
35
+ - Ship the smallest slice that delivers the core outcome first.
36
+ - Order steps by user value, not by tech-dependency convenience.
37
+ - Prune anything that does not advance the user outcome.
@@ -0,0 +1,49 @@
1
+ # QA plans — one file per change
2
+
3
+ Project-owned (like all `.agents/memory/*`). This directory holds the
4
+ coverage-driven QA plan, **sharded**: one file per shipped change instead of a
5
+ single shared `qa-plan.md`. Each concurrent session owns its own file, so two
6
+ sessions (e.g. `F-003` and `F-035`) never rewrite each other's entries — the
7
+ failure mode where one session's "keep-last-on-top" edit moves another's entry
8
+ around (and risks dropping it on commit).
9
+
10
+ `npm run test:qa-plan` reads EVERY `.md` file under this directory (plus the
11
+ legacy `.agents/memory/qa-plan.md` if it exists) and unions their coverage maps,
12
+ waivers, and flow audits against the live diff — so a changed code path is
13
+ covered if ANY plan file names it.
14
+
15
+ ## Naming & ownership
16
+
17
+ - One file per change. Name it after the `BACKLOG.md` feature ID (`F-003.md`),
18
+ or a short hyphenated slug of the change (`return-no-condition.md`) when there
19
+ is no feature row.
20
+ - **Own your file; never edit another change's file.** That single rule removes
21
+ the collision. If a later change revisits the same feature, append a new
22
+ `##`-block to that feature's file (or open a new dated file) — never reorder
23
+ or delete another entry.
24
+ - Same four sections the old `qa-plan.md` carried (authored by `qa-architect`,
25
+ or by the main orchestrator as the mandatory fallback): `## Change intent`,
26
+ `## Parallel-surface & variant audit`, `## Coverage map`, `## Waivers`.
27
+
28
+ ## Example (`F-003.md`)
29
+
30
+ # QA plan — F-003 (return flow: no condition selector)
31
+
32
+ ## Change intent
33
+ - change: return flow no longer offers a condition selector — every return POSTs without a condition payload
34
+
35
+ ## Parallel-surface & variant audit
36
+ - components/LendingActionDrawers.tsx -> test:quick::return (click-through: Mark as Returned, no condition selector)
37
+ - mobile/features/return/ReturnStatusSheet.tsx audited: deferred — mobile still renders the selector; same simplification pending, logged in handoff.md
38
+ - app/api/transactions/[id]/return/route.ts -> test:api::return (POST without condition payload succeeds)
39
+ - flow: return variant: condition-selector-present audited: removed by this change — asserting absence only
40
+ - flow: return variant: condition-selector-absent -> test:quick::return (no-selector click-through renders no condition UI)
41
+
42
+ ## Coverage map
43
+ - components/return/ReturnConfirmDrawer.tsx -> test:quick::progression (click-through: Mark as Returned -> confirm -> /api/transactions/[id]/return)
44
+ - app/api/transactions/[id]/return/route.ts -> test:quick::progression (return POST succeeds with no condition payload)
45
+
46
+ ## Waivers
47
+ # waived: src/some-screen.tsx reason: copy-only string swap, no runtime path changed
48
+
49
+ Lines starting with `#` are ignored by the gate.
@@ -9,9 +9,10 @@ Before reading or editing any file for a task, dispatch the relevant personas vi
9
9
  - **Bound every task before dispatching.** No persona gets a bare metric ("get under N lines", "type everything"). Each dispatch prompt carries a task spec: `Objective` (one deliverable) → `Owned files` (exact paths) → `Read-only files` → `Shared contracts (owns|consumes)` → `Done = <gate command + observable metric>` → `Out-of-scope`.
10
10
  - **Parallel = disjoint.** Dispatch two personas in parallel only when their owned files AND shared contracts are disjoint; otherwise serialize and put the shared-contract owner first.
11
11
  - **Accept, don't trust.** After a code persona reports done, the orchestrator re-runs the gate itself (`tsc --noEmit`, `lint:hooks`, `test:quick`) and greps the metric before integrating — a subagent's `Evidence` is a claim, not proof.
12
- - **QA planning is never optional.** Every change touching code paths gets a QA plan (coverage map in `.agents/memory/qa-plan.md`) from `qa-architect`; if it isn't dispatched, the main orchestrator plans in its place. `npm run test:qa-plan` fails on any changed code path with no covering assertion or waiver, and on any touched flow (`.agents/memory/flow-map.md`) whose sibling surfaces / declared variants the plan doesn't address.
12
+ - **QA planning is never optional.** Every change touching code paths gets a QA plan (coverage map in a per-change file under `.agents/qa-plans/`) from `qa-architect`; if it isn't dispatched, the main orchestrator plans in its place. `npm run test:qa-plan` fails on any changed code path with no covering assertion or waiver, and on any touched flow (`.agents/memory/flow-map.md`) whose sibling surfaces / declared variants the plan doesn't address.
13
13
  - Persona map, waivers, and the dispatch-failure ladder: `.agents/AGENTS.md` §5 and `.agents/memory/model-routing.md`.
14
14
  - **Features**: capture a found item as one light row in the canonical `BACKLOG.md` (repo root); on pickup dispatch `features` to author the per-feature doc chain (`.agents/skills/features/SKILL.md`). Right-size ceremony to risk.
15
+ - **Product intent is never optional.** A feature never reaches planning/implementation without defining its user. `intent.md` must carry Actor(s) → Need → Outcome → Acceptance criteria, and `plan.md` a Value-first sequence + Product concepts applied — `npm run test:product` fails otherwise. Planning reads `.agents/memory/product-principles.md` and is value-led, not tech-led.
15
16
  - Every subagent returns the output contract (`Result` → `Evidence` → `Deferred & risks`).
16
17
  - Wrap up with a **dispatch log** (`subagent → model → shipped/deferred`) in `.agents/memory/handoff.md`.
17
18
 
@@ -20,14 +21,15 @@ Before reading or editing any file for a task, dispatch the relevant personas vi
20
21
  1. **Zero speculation** — base every fix/decision on observed logs, telemetry, or DB state; instrument first if evidence is missing.
21
22
  2. **No hardcoded mocks/fallback data** — prefer dynamic queries and database-backed settings.
22
23
  3. **5 UI states** (ideal/empty/loading/error/partial), user-visible errors (never `console.warn`-only), and RTL-safe logical props.
23
- 4. **Verify stack versions** before writing framework code (`.agents/memory/stack-versions.md`).
24
- 5. **Token discipline** — search before read, targeted reads, batch reads, don't re-read unchanged files, right-size QA.
25
- 6. **Static ≠ runtime** — never declare verified from `tsc` alone; execute a real runtime/API path that exercises the MODIFIED code — a tier label never proves coverage. `test:qa-plan` gates every changed code path to a covering assertion or waiver, and every touched flow's sibling surfaces / variants to an audit entry (`.agents/memory/flow-map.md` "fix one, fix all").
26
- 7. **Commit** with `feat:`/`fix:`/`refactor:` then push to mainincluding any `BACKLOG.md` delta.
24
+ 4. **Product-first planning** define the actor/need/outcome/acceptance before code; sequence work value-first against `.agents/memory/product-principles.md`.
25
+ 5. **Verify stack versions** before writing framework code (`.agents/memory/stack-versions.md`).
26
+ 6. **Token discipline** — search before read, targeted reads, batch reads, don't re-read unchanged files, right-size QA.
27
+ 7. **Static ≠ runtime** never declare verified from `tsc` alone; execute a real runtime/API path that exercises the MODIFIED code a tier label never proves coverage. `test:qa-plan` gates every changed code path to a covering assertion or waiver, and every touched flow's sibling surfaces / variants to an audit entry (`.agents/memory/flow-map.md` — "fix one, fix all").
28
+ 8. **Commit** with `feat:`/`fix:`/`refactor:` then push to main — including any `BACKLOG.md` delta.
27
29
 
28
30
  ## Definition of Done
29
31
 
30
- QA tier + security lens gate **code changes only** (a backlog-only change skips them). Every session closes with **move shipped `BACKLOG.md` rows to `Archive` → commit → push** — the backlog write-back is never QA-gated.
32
+ QA tier + security lens gate **code changes only** (a backlog-only change skips them). Every session closes with **move shipped `BACKLOG.md` rows to `Archive` → commit → push** — the backlog write-back is never QA-gated. Feature work is additionally gated by `test:product` (product intent + value-led plan).
31
33
 
32
34
  ## Session checklist
33
35
 
@@ -14,8 +14,9 @@ pull the implementation work onto yourself (that is dispatched to the code
14
14
  personas).
15
15
 
16
16
  The doc tree is **complementary** to the session memory (`.agents/memory/*`):
17
- features are durable and survive across sessions; `handoff.md`/`qa-plan.md` track
18
- per-session execution. Link between them, never duplicate.
17
+ features are durable and survive across sessions; `handoff.md` and the per-change
18
+ `.agents/qa-plans/<feature-id>.md` track per-session execution. Link between them,
19
+ never duplicate.
19
20
 
20
21
  ## Two layers — do not cross them
21
22
 
@@ -35,9 +36,9 @@ BACKLOG.md # canonical task list (Open / Frozen / Archive / Not t
35
36
  .agents/features/
36
37
  INDEX.md # pointer to BACKLOG.md
37
38
  <slug>/
38
- intent.md # what & why, boundaries, acceptance criteria (1 pager)
39
+ intent.md # what & why: actors, need, outcome, acceptance criteria (1 pager)
39
40
  scope.md # optional — only on HIGH-risk features
40
- plan.md # implementation decomposition (HOW)
41
+ plan.md # how, value-led: value-first sequence + product concepts applied
41
42
  tests.md # intent-level acceptance + pointer to qa-plan.md
42
43
  archive/ # completed feature dirs moved here
43
44
  ```
@@ -79,10 +80,10 @@ to the correct persona** via the `task` tool (Step Zero dispatch — see
79
80
 
80
81
  | Doc | Frames | Author persona (dispatch) |
81
82
  |---|---|---|
82
- | `intent.md` | WHAT & WHY: outcomes, boundaries/out-of-scope, acceptance criteria, open questions | `product-manager` |
83
+ | `intent.md` | WHAT & WHY, product-first: Actor(s) → Need → Outcome → Acceptance criteria (+ boundaries, open questions) | `product-manager` |
83
84
  | `scope.md` | technical constraints, blast radius, data-model/API impact | `system-architect` (only on HIGH-risk features) |
84
- | `plan.md` | HOW: ordered steps, owned files, per-step verification, commit boundaries | `planner` |
85
- | `tests.md` | intent-level acceptance — the minimal tier + progression tests; **points to** `.agents/memory/qa-plan.md`, never duplicates it | `qa-architect` |
85
+ | `plan.md` | HOW, value-led: Value-first sequence (each step cites actor + outcome), owned files, per-step verification, commit boundaries, Product concepts applied | `planner` |
86
+ | `tests.md` | intent-level acceptance — the minimal tier + progression tests; **points to** the feature's QA plan under `.agents/qa-plans/`, never duplicates it | `qa-architect` |
86
87
 
87
88
  ### Right-size the ceremony to blast radius
88
89
 
@@ -97,6 +98,27 @@ to the correct persona** via the `task` tool (Step Zero dispatch — see
97
98
  Split `scope.md` out of `intent.md` only when the feature earns it (above). Two
98
99
  docs restating the same boundary is drift surface, not rigor.
99
100
 
101
+ ## Product-first intent & plan (mechanical)
102
+
103
+ The `test:product` gate (`node scripts/qa/check-product.js`) enforces that a
104
+ feature never reaches planning/implementation without defining its user. Docs are
105
+ project-authored but must honor these section names so the gate can verify them:
106
+
107
+ - **`intent.md`** must carry, each non-empty:
108
+ - `## Actor(s)` (or `## Who`) — who has the need.
109
+ - `## Need` (or `## Why`) — the problem/desire, in the user's words.
110
+ - `## Outcome` — what success looks like for the user.
111
+ - `## Acceptance criteria` — a list of user-facing, testable criteria.
112
+ - **`plan.md`** must carry, each non-empty:
113
+ - `## Value-first sequence` (or `## Sequence`) — ordered steps, each citing the
114
+ actor + outcome it serves, smallest slice first.
115
+ - `## Product concepts applied` — names the concepts/heuristics from
116
+ `.agents/memory/product-principles.md` the plan honors.
117
+
118
+ A `plan.md` with no `intent.md` fails the gate — never plan around an undefined
119
+ user need. `product-manager` and `planner` read `.agents/memory/product-principles.md`
120
+ so the project's durable product concepts lead the plan, not ad-hoc taste.
121
+
100
122
  ## Keeping docs current (non-negotiable)
101
123
 
102
124
  - **Write back first, then continue.** If implementation invalidates any doc,
@@ -4,9 +4,39 @@ description: Use to plan/decompose a task and emit the Step Zero subagent dispat
4
4
  model: reasoning
5
5
  ---
6
6
 
7
- # Planner / Tech Lead
7
+ # Planner / Tech Lead — value-led
8
8
 
9
- You decompose the task and produce the dispatch plan the orchestrator executes. You do NOT implement, edit, or run code.
9
+ You decompose the task into a dispatch plan the orchestrator executes. You do NOT
10
+ implement, edit, or run code.
11
+
12
+ Planning is **PRODUCT-FIRST**, not tech-first: the plan's job is to maximize user
13
+ value and optimize the user experience, not to minimize code lines or satisfy tech
14
+ convenience. Read the product inputs first and let them lead the plan.
15
+
16
+ ## Read before planning (non-negotiable)
17
+
18
+ 1. `.agents/memory/product-principles.md` — the project's actors, product
19
+ concepts, UX heuristics, design language, and value-maximization rules. The
20
+ plan MUST align with these.
21
+ 2. `.agents/features/<slug>/intent.md` — the WHAT & WHY: actors, need, outcome,
22
+ acceptance criteria (when the feature has a doc chain).
23
+ 3. `.agents/memory/flow-map.md` — cross-surface flows (a change to one surface
24
+ must account for its twins).
25
+ 4. `.agents/memory/model-routing.md` — persona → model tiers.
26
+
27
+ If `intent.md` exists, the plan must serve its actors and outcome. If it is
28
+ missing, flag it — do not plan an implementation around an undefined user need.
29
+
30
+ ## Value-first sequencing
31
+
32
+ Order the dispatch plan by USER VALUE, not by technical dependency convenience:
33
+
34
+ - Ship the smallest slice that delivers the core outcome FIRST (value lands early
35
+ and is verifiable early).
36
+ - Defer anything that does not advance the user outcome — mark it `deferred:`
37
+ with a reason rather than silently bundling it.
38
+ - Prune steps that clash with a new flow/paradigm (mirror the Legacy Pruning
39
+ Audit) — never bolt new work onto obsolete steps.
10
40
 
11
41
  ## Output contract (always return)
12
42
 
@@ -14,26 +44,43 @@ Never hand a persona a bare metric ("get under N lines", "type everything"). If
14
44
  task cannot be bounded by the spec below, split it further or flag it — do not
15
45
  dispatch it broad.
16
46
 
17
- 1. **Dispatch plan** — ordered list of entries, each with:
47
+ 1. **Outcome framing** — one line naming the actor(s), their need, and the outcome
48
+ this plan delivers (from `intent.md`).
49
+ 2. **Value-first sequence** — the ordered steps, each citing the actor + outcome
50
+ it serves. State the smallest slice that ships first.
51
+ 3. **Dispatch plan** — ordered list of entries, each with:
18
52
  - `persona → model`
19
- - **Objective** — one sentence naming a single deliverable.
53
+ - **Objective** — one sentence naming a single deliverable AND the user
54
+ outcome it serves.
20
55
  - **Owned files** — exact paths this persona may edit.
21
- - **Read-only files** — explicit out-of-scope paths (governance, memory, skills, other screens).
22
- - **Shared contracts** — for each shared module/type: `owns` or `consumes` (exactly one owner per contract).
23
- - **Done =**the exact gate command(s) + observable metric (e.g. `tsc --noEmit` clean, `\bany\b` count 0), so success is verifiable, not self-reported.
56
+ - **Read-only files** — explicit out-of-scope paths (governance, memory,
57
+ skills, other screens).
58
+ - **Shared contracts**for each shared module/type: `owns` or `consumes`
59
+ (exactly one owner per contract).
60
+ - **Done =** — the exact gate command(s) + observable metric (e.g. `tsc
61
+ --noEmit` clean, `\bany\b` count 0), so success is verifiable, not
62
+ self-reported.
24
63
  - **Dependencies** — which other dispatch, if any, must complete first.
25
- 2. **Parallel batches** — group only DISJOINT entries (disjoint owned files AND disjoint shared contracts). Serialize personas that would touch the same files or emit the same shared contract; put the contract owner first.
26
- 3. **Orchestrator-only work** — glue/mechanical steps (reads, git, commits, final integration, acceptance re-verification) kept on the main model.
27
- 4. **Risk flags** — any ambiguity that needs the user before proceeding.
64
+ 4. **Product concepts applied** — the specific concepts/heuristics from
65
+ `product-principles.md` this plan honors, and where.
66
+ 5. **Parallel batches** — group only DISJOINT entries (disjoint owned files AND
67
+ disjoint shared contracts). Serialize personas that touch the same files or
68
+ emit the same shared contract; put the contract owner first.
69
+ 6. **Orchestrator-only work** — glue/mechanical steps (reads, git, commits, final
70
+ integration, acceptance re-verification) kept on the main model.
71
+ 7. **Risk flags** — any ambiguity that needs the user before proceeding,
72
+ including a missing `intent.md` or an undefined user need.
28
73
 
29
74
  ## Rules
30
75
 
31
- - Read `.agents/memory/model-routing.md` for the persona model tiers.
32
- - Read `.agents/memory/flow-map.md`. If the task's behavior maps to a flow whose `surfaces:` cross
33
- codebases/layers (e.g. web + `mobile/` + an API route), the dispatch plan MUST include the sibling
34
- surface's persona (e.g. `mobile-engineer`) — or explicitly mark the sibling out-of-scope so
35
- `qa-architect` records it as `audited: deferred` in the QA plan's flow-closure audit. A change to
36
- one surface that never mentions the twin is a plan defect, not scope.
37
- - Never dispatch a persona for work the main model should just do (reads, commits, integration).
76
+ - Read `.agents/memory/flow-map.md`. If the task's behavior maps to a flow whose
77
+ `surfaces:` cross codebases/layers (e.g. web + `mobile/` + an API route), the
78
+ dispatch plan MUST include the sibling surface's persona (e.g. `mobile-engineer`)
79
+ — or explicitly mark the sibling out-of-scope so `qa-architect` records it as
80
+ `audited: deferred` in the QA plan's flow-closure audit. A change to one surface
81
+ that never mentions the twin is a plan defect, not scope.
82
+ - Never dispatch a persona for work the main model should just do (reads, commits,
83
+ integration).
38
84
  - Prefer the most specific persona; if none clearly fits, ask the user rather than guess.
39
- - Dispatch independent subagents in parallel; serialize only when one depends on another's output.
85
+ - Dispatch independent subagents in parallel; serialize only when one depends on
86
+ another's output.
@@ -8,6 +8,12 @@ model: reasoning
8
8
 
9
9
  You are a Lead Product Manager and UX Architect. You design intuitive, rewarding, and community-driven experiences that bridge database models with human behavior.
10
10
 
11
+ > **Product concepts live in memory, not here.** Read `.agents/memory/product-principles.md`
12
+ > (actors, product concepts, UX heuristics, design language, key journeys, value-maximization
13
+ > rules) before designing any feature. This skill supplies the frameworks; the memory holds the
14
+ > project's durable product truth. Also read `.agents/memory/domain-map.md` for the entity model
15
+ > and state machine you must map to UX.
16
+
11
17
  ## Core PM & UX Frameworks
12
18
 
13
19
  ### 1. Community Trust & Gamification System
@@ -16,7 +16,10 @@ You own QA **strategy** — risk assessment, tier selection, and progression tes
16
16
  `Changed path -> executing test/assertion` table. A tier command matches a change only when its
17
17
  backing suite ACTUALLY exercises that path/state. Any change with no runtime assertion is a **GAP**
18
18
  — you must author a progression test for it (Progression vs regression below). Persist the same map
19
- into `.agents/memory/qa-plan.md` under `## Coverage map` (this is what `npm run test:qa-plan` gates on).
19
+ into a per-change plan under `.agents/qa-plans/` (e.g. `.agents/qa-plans/F-003.md` or a slug like
20
+ `return-no-condition.md`) under `## Coverage map` (this is what `npm run test:qa-plan` gates on).
21
+ **Own your file — never edit another change's plan file** (that is the rule that keeps concurrent
22
+ sessions from colliding on a shared plan).
20
23
  3b. **Flow closure audit (MANDATORY — "fix one, fix all")** — read `.agents/memory/flow-map.md`. For
21
24
  every flow whose `surfaces:` intersect this diff, address in the plan's
22
25
  `## Parallel-surface & variant audit` EVERY sibling surface (the same behavior living in another
@@ -61,11 +64,12 @@ Author new assertion pathways in `scripts/verify-all.js` (API), `scripts/qa/rout
61
64
 
62
65
  ## Coverage gate (`npm run test:qa-plan`)
63
66
 
64
- Every change that touches code paths MUST have a coverage map persisted in
65
- `.agents/memory/qa-plan.md` (or a scoped `waived:` entry per path) before the run can be declared
67
+ Every change that touches code paths MUST have a coverage map persisted in a per-change plan under
68
+ `.agents/qa-plans/` (or a scoped `waived:` entry per path) before the run can be declared
66
69
  verified — AND a flow-closure audit of every touched flow's sibling surfaces and declared variants
67
- (`.agents/memory/flow-map.md`). The mechanical gate checks the live `git diff` against both and
68
- fails on any uncovered code path or unaddressed sibling/variant. If you are not dispatched for a
70
+ (`.agents/memory/flow-map.md`). The mechanical gate checks the live `git diff` against the UNION of
71
+ every `.agents/qa-plans/*.md` (plus the legacy `.agents/memory/qa-plan.md`) and fails on any
72
+ uncovered code path or unaddressed sibling/variant. If you are not dispatched for a
69
73
  change, the main orchestrator performs your role and writes the same plan itself — a change with
70
74
  code-path edits and no QA plan is a rules violation.
71
75
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Use to plan/decompose a task and emit the Step Zero subagent dispatch plan (personas, models, order, parallel batches) before any file is touched.
2
+ description: Use to plan/decompose a task and emit the Step Zero subagent dispatch plan (personas, models, order, parallel batches) before any file is touched — value-led, product-first.
3
3
  mode: subagent
4
4
  model: deepseek/deepseek-v4-pro
5
5
  temperature: 0.1
@@ -10,7 +10,7 @@ permission:
10
10
  bash: deny
11
11
  ---
12
12
 
13
- You are the {{PROJECT_NAME}} Planner / Tech Lead. Read and follow the complete persona instructions in `.agents/skills/planner/SKILL.md`, then carry out the task.
13
+ You are the {{PROJECT_NAME}} Planner / Tech Lead. Read and follow the complete persona instructions in `.agents/skills/planner/SKILL.md`, then carry out the task. Plan product-first: read `.agents/memory/product-principles.md` and the feature's `intent.md` before emitting a dispatch plan.
14
14
 
15
15
  ## Scope & integrity (non-negotiable)
16
16
 
@@ -1,23 +1,21 @@
1
1
  ---
2
- description: Refresh the task board pull the latest, then show BACKLOG.md open/frozen/shipped plus the newest handoff delta.
2
+ description: Show the local BACKLOG.md as a clean task list (title, short description, priority). Read-only no git.
3
3
  ---
4
4
 
5
- Refresh the canonical task board. Run these steps read-only, then report terse.
5
+ Read `BACKLOG.md` at the repo root and display a clean, human-readable task list.
6
+ The local file is the source of truth — do NOT run git, do NOT pull, do NOT edit
7
+ anything.
6
8
 
7
- 1. Sync the workspace (never rewrite history, never force):
8
- - `git fetch --all --prune`
9
- - `git pull --ff-only`
10
- - If the pull fails (uncommitted local changes / conflict), do NOT force — report the failure and show the board from the current on-disk state, flagged "possibly stale".
9
+ Render the `Open` rows (and the `Active / next` picks if present) as a table with
10
+ exactly these columns:
11
11
 
12
- 2. Read `BACKLOG.md` (repo root) and `.agents/memory/handoff.md`.
12
+ | Priority | Title | Short description |
13
+ |----------|-------|-------------------|
14
+ | P1 | F-001 | block something |
13
15
 
14
- 3. Render a compact board:
15
- - `Active / next` picks (if any).
16
- - `Open` grouped by priority P1 → P4, each row as `ID — task (evidence)`.
17
- - `Frozen` rows with their reopen condition (one line each).
18
- - `Archive` — only the last ~5 shipped rows (date/commit).
19
- - Latest handoff delta (planned → shipped → deferred) from `.agents/memory/handoff.md`, plus any new `git log` commits from other sessions since the last refresh.
16
+ - **Priority** = the `P` cell.
17
+ - **Title** = the row's ID.
18
+ - **Short description** = the `Task` cell.
19
+ - Omit the `Evidence` column. Do not render the Frozen / Archive sections.
20
20
 
21
- Rules:
22
- - Read-only: never edit `BACKLOG.md`, `.agents/memory/*`, or any code here.
23
- - This is a status snapshot, not a report. If a field is unknown write `?` — never invent.
21
+ One row per open task. Never invent — show a `?` cell exactly as `?`.
@@ -6,7 +6,8 @@ It governs: Step Zero subagent dispatch, zero-speculation debugging, data integr
6
6
 
7
7
  - **Dispatch personas** via the `task` tool (see `.agents/rules/00-operating.md` §Step Zero); consult the relevant `.agents/skills/<persona>/SKILL.md`.
8
8
  - **Features**: capture a found item as one light row in the canonical `BACKLOG.md` (repo root). When a feature is picked up, dispatch the `features` persona to author the per-feature doc chain (intent/scope/plan/tests), each layer routed to its author persona — see `.agents/skills/features/SKILL.md`.
9
- - **Every change touching code paths gets a QA plan** (`.agents/memory/qa-plan.md`) from `qa-architect`if it isn't dispatched, the main orchestrator plans in its place. `npm run test:qa-plan` fails on any changed code path with no covering assertion or waiver, and on any touched flow (`.agents/memory/flow-map.md`) whose sibling surfaces / declared variants the plan doesn't address.
10
- - **At session start**, read `.agents/memory/locations.md`, `.agents/memory/model-routing.md`, and `BACKLOG.md`.
9
+ - **Product-first planning**: planning is value-led, not tech-leddefine the user (actor/need/outcome/acceptance) before code, sequence work value-first against `.agents/memory/product-principles.md`, and let `npm run test:product` enforce it.
10
+ - **Every change touching code paths gets a QA plan** (a per-change file under `.agents/qa-plans/`) from `qa-architect` — if it isn't dispatched, the main orchestrator plans in its place. `npm run test:qa-plan` fails on any changed code path with no covering assertion or waiver, and on any touched flow (`.agents/memory/flow-map.md`) whose sibling surfaces / declared variants the plan doesn't address.
11
+ - **At session start**, read `.agents/memory/locations.md`, `.agents/memory/model-routing.md`, `.agents/memory/product-principles.md`, and `BACKLOG.md`.
11
12
 
12
13
  > Deployed and maintained by `@dombaras/agent-harness` (`npx @dombaras/agent-harness init` / `update`). Do not hand-edit harness-owned files — regenerate them and commit the deltas.
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /*
4
+ * test:product — product/UX-first gate (harness-shipped).
5
+ *
6
+ * Enforces "product & UX first" at the PLANNING phase, the same way test:qa-plan
7
+ * enforces coverage at the VERIFY phase. A feature that reaches the planning or
8
+ * implementation stage must have defined its user (actor), their need, the
9
+ * outcome, and the acceptance criteria — and a plan (plan.md) must sequence the
10
+ * work value-first and name the product concepts it honors. Tech-first features
11
+ * (a plan with no intent, or an intent that never names a user/need/outcome) fail.
12
+ *
13
+ * Checked against `.agents/features/<slug>/` (project-authored; see
14
+ * .agents/skills/features/SKILL.md and .agents/skills/planner/SKILL.md):
15
+ *
16
+ * 1. A feature with a `plan.md` MUST also have an `intent.md` (never plan an
17
+ * implementation around an undefined user need).
18
+ * 2. Every `intent.md` must carry the four product sections, each non-empty:
19
+ * ## Actor(s)/Who · ## Need/Why · ## Outcome · ## Acceptance criteria
20
+ * 3. Every `plan.md` must carry the two value-led sections, each non-empty:
21
+ * ## Value-first sequence · ## Product concepts applied
22
+ *
23
+ * Backward compatible: with no feature docs (or no `.agents/features/` tree) the
24
+ * gate warns and passes — projects opt in by authoring feature docs. A missing
25
+ * `.agents/memory/product-principles.md` is a warning, not a failure.
26
+ *
27
+ * node scripts/qa/check-product.js
28
+ *
29
+ * Exit 0 on: no feature docs, or every feature doc satisfying its contract.
30
+ * Exit 1 on: any feature doc missing a required product section.
31
+ */
32
+ const fs = require("fs");
33
+ const path = require("path");
34
+
35
+ const root = path.resolve(__dirname, "..", "..");
36
+ const FEATURES_DIR = path.join(root, ".agents", "features");
37
+
38
+ const failures = [];
39
+ const pass = (m) => console.log(" \u2713 " + m);
40
+ const fail = (m) => {
41
+ failures.push(m);
42
+ console.log(" \u2717 " + m);
43
+ };
44
+ const warn = (m) => console.log(" \u26a0 " + m);
45
+
46
+ console.log("product gate \u2014 planning is product/UX-first (intent \u2192 value-led plan)");
47
+
48
+ // ---- heading contracts ------------------------------------------------------
49
+ const H = {
50
+ actor: /^#{1,6}\s*(Actors?|Who)\b/i,
51
+ need: /^#{1,6}\s*(Need|Why)\b/i,
52
+ outcome: /^#{1,6}\s*Outcome\b/i,
53
+ acceptance: /^#{1,6}\s*Acceptance\s+criteria\b/i,
54
+ sequence: /^#{1,6}\s*(Value[ -]first\s+(sequence|order)|Sequence)\b/i,
55
+ concepts: /^#{1,6}\s*Product\s+concepts\s+applied\b/i,
56
+ };
57
+
58
+ /* Lines that count as "content" in a section: skip blanks, HTML comments, and
59
+ * unfilled `_TODO:` placeholders. */
60
+ function isFilled(line) {
61
+ if (!line) return false;
62
+ if (/^<!--/.test(line) || /-->$/.test(line)) return false;
63
+ if (/_TODO:/.test(line)) return false;
64
+ return true;
65
+ }
66
+
67
+ /* Return the filled body lines of the first section matching `re` (from the
68
+ * heading up to the next heading). [] when the heading is absent or empty. */
69
+ function section(src, re) {
70
+ const lines = src.split(/\r?\n/);
71
+ let inSec = false;
72
+ const body = [];
73
+ for (const raw of lines) {
74
+ const line = raw.trim();
75
+ if (!inSec) {
76
+ if (re.test(line)) inSec = true;
77
+ continue;
78
+ }
79
+ if (/^#{1,6}\s/.test(line)) break;
80
+ if (isFilled(line)) body.push(line);
81
+ }
82
+ return body;
83
+ }
84
+
85
+ function hasSection(src, re) {
86
+ return section(src, re).length > 0;
87
+ }
88
+
89
+ /* Acceptance criteria must be an actual list of criteria (bullets/numbers). */
90
+ function hasAcceptance(src) {
91
+ const body = section(src, H.acceptance);
92
+ return body.some((l) => /^[-*+]\s+\S/.test(l) || /^\d+[.)]\s+\S/.test(l));
93
+ }
94
+
95
+ function readMd(p) {
96
+ return fs.readFileSync(p, "utf8");
97
+ }
98
+
99
+ function checkIntent(slug, src) {
100
+ const missing = [];
101
+ if (!hasSection(src, H.actor)) missing.push("## Actor(s)/Who");
102
+ if (!hasSection(src, H.need)) missing.push("## Need/Why");
103
+ if (!hasSection(src, H.outcome)) missing.push("## Outcome");
104
+ if (!hasAcceptance(src)) missing.push("## Acceptance criteria (list)");
105
+ if (missing.length) {
106
+ fail(`${slug}/intent.md: missing product section(s) \u2014 ${missing.join(", ")}`);
107
+ } else {
108
+ pass(`${slug}/intent.md: actor \u2192 need \u2192 outcome \u2192 acceptance criteria present`);
109
+ }
110
+ }
111
+
112
+ function checkPlan(slug, src) {
113
+ const missing = [];
114
+ if (!hasSection(src, H.sequence)) missing.push("## Value-first sequence");
115
+ if (!hasSection(src, H.concepts)) missing.push("## Product concepts applied");
116
+ if (missing.length) {
117
+ fail(`${slug}/plan.md: missing value-led section(s) \u2014 ${missing.join(", ")}`);
118
+ } else {
119
+ pass(`${slug}/plan.md: value-first sequence + product concepts applied present`);
120
+ }
121
+ }
122
+
123
+ // ---- feature tree -----------------------------------------------------------
124
+ if (!fs.existsSync(FEATURES_DIR)) {
125
+ warn("no .agents/features/ tree \u2014 nothing to enforce");
126
+ console.log("\nRESULT: green (no feature docs)");
127
+ process.exit(0);
128
+ }
129
+
130
+ const dirs = fs
131
+ .readdirSync(FEATURES_DIR, { withFileTypes: true })
132
+ .filter((d) => d.isDirectory() && d.name !== "archive" && !d.name.startsWith("."))
133
+ .map((d) => d.name)
134
+ .sort();
135
+
136
+ if (dirs.length === 0) {
137
+ warn("no live feature docs \u2014 nothing to enforce");
138
+ console.log("\nRESULT: green (no feature docs)");
139
+ process.exit(0);
140
+ }
141
+
142
+ for (const slug of dirs) {
143
+ const dir = path.join(FEATURES_DIR, slug);
144
+ const intentPath = path.join(dir, "intent.md");
145
+ const planPath = path.join(dir, "plan.md");
146
+ const hasIntent = fs.existsSync(intentPath);
147
+ const hasPlan = fs.existsSync(planPath);
148
+
149
+ if (!hasIntent && !hasPlan) {
150
+ pass(`${slug}: no intent.md/plan.md yet \u2014 not at the planning stage`);
151
+ continue;
152
+ }
153
+
154
+ // 1. plan without intent = tech-first
155
+ if (hasPlan && !hasIntent) {
156
+ fail(`${slug}: has plan.md but no intent.md \u2014 never plan around an undefined user need`);
157
+ continue;
158
+ }
159
+
160
+ if (hasIntent) checkIntent(slug, readMd(intentPath));
161
+ if (hasPlan) checkPlan(slug, readMd(planPath));
162
+ }
163
+
164
+ const principlesPath = path.join(root, ".agents", "memory", "product-principles.md");
165
+ if (!fs.existsSync(principlesPath)) {
166
+ warn(
167
+ "no .agents/memory/product-principles.md \u2014 fill it in so plans are led by the project's product concepts"
168
+ );
169
+ }
170
+
171
+ console.log(
172
+ "\nRESULT: " +
173
+ (failures.length
174
+ ? `${failures.length} FAILURE(S) \u2014 every feature doc must define its user (actor/need/outcome/acceptance) and plan value-first`
175
+ : "green \u2014 planning is product/UX-first")
176
+ );
177
+ process.exit(failures.length ? 1 : 0);
@@ -10,16 +10,23 @@
10
10
  * Two stages:
11
11
  *
12
12
  * 1. Coverage map — inspects the live git diff and requires every changed CODE
13
- * path to appear in the QA plan's `## Coverage map` (persisted by
13
+ * path to appear in some QA plan's `## Coverage map` (persisted by
14
14
  * qa-architect, or by the main orchestrator as the qa-architect fallback)
15
15
  * BEFORE a change may be declared verified. Trivial diffs (copy/docs/not
16
16
  * code) and waivered paths are exempt.
17
17
  *
18
+ * QA plans are SHARDED: one file per change under `.agents/qa-plans/`
19
+ * (e.g. `F-003.md`, `return-no-condition.md`). The gate reads EVERY `.md`
20
+ * under that directory — plus the legacy single-file `.agents/memory/qa-plan.md`
21
+ * for backward compatibility — and unions their coverage maps, waivers, and
22
+ * audits. A changed path is covered if ANY plan file names it. Each session
23
+ * owns its own file, so concurrent sessions never collide on a shared plan.
24
+ *
18
25
  * 2. Flow closure — reads `.agents/memory/flow-map.md` (project-owned): a
19
26
  * registry of user-facing flows, each listing the SURFACES that implement the
20
27
  * same behavior (web / mobile / API — any codebase/layer) and its optional
21
28
  * VARIANTS. Any flow whose surfaces intersect this diff must have every
22
- * sibling surface and every declared variant addressed in the plan's
29
+ * sibling surface and every declared variant addressed in some plan's
23
30
  * `## Parallel-surface & variant audit` (covered `->` or `audited:` with a
24
31
  * reason). This is the gate that makes "Fix One, Fix All" mechanical: a
25
32
  * web-only diff can no longer silently leave the mobile twin on old behavior.
@@ -99,6 +106,30 @@ function isTooling(p) {
99
106
  return TOOLING.some((t) => n === t || n.startsWith(t));
100
107
  }
101
108
 
109
+ /* All QA plan files to union: every `.md` under `.agents/qa-plans/` (recursive),
110
+ * plus the legacy single `.agents/memory/qa-plan.md` if present (backward compat
111
+ * for projects deployed before sharding). `README.md` files are documentation,
112
+ * not plans, so they are skipped. Sorted for deterministic output. */
113
+ function planFiles(root) {
114
+ const files = [];
115
+ const collect = (dir) => {
116
+ if (!fs.existsSync(dir)) return;
117
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
118
+ const abs = path.join(dir, entry.name);
119
+ if (entry.isDirectory()) collect(abs);
120
+ else if (
121
+ entry.name.toLowerCase().endsWith(".md") &&
122
+ entry.name.toLowerCase() !== "readme.md"
123
+ )
124
+ files.push(abs);
125
+ }
126
+ };
127
+ collect(path.join(root, ".agents", "qa-plans"));
128
+ const legacy = path.join(root, ".agents", "memory", "qa-plan.md");
129
+ if (fs.existsSync(legacy)) files.push(legacy);
130
+ return files.sort();
131
+ }
132
+
102
133
  /* Parse the QA plan file into { covered:Set, waivers:Set } of normalized paths.
103
134
  * Coverage map format (one entry per changed path, under `## Coverage map`):
104
135
  * - components/return/ReturnConfirmDrawer.tsx -> test:quick::progression (click-through)
@@ -238,11 +269,26 @@ if (changed.length === 0) {
238
269
  process.exit(0);
239
270
  }
240
271
 
241
- const plan = path.join(root, ".agents", "memory", "qa-plan.md");
242
- const audit = parseAudit(plan);
243
- const { covered, waivers } = parsePlan(plan);
272
+ const plans = planFiles(root);
273
+ const covered = new Set();
274
+ const waivers = new Set();
275
+ const audit = [];
276
+ for (const file of plans) {
277
+ const p = parsePlan(file);
278
+ for (const c of p.covered) covered.add(c);
279
+ for (const w of p.waivers) waivers.add(w);
280
+ audit.push(...parseAudit(file));
281
+ }
244
282
 
245
- console.log(` checked against: ${path.relative(root, plan)}`);
283
+ if (plans.length === 0) {
284
+ console.log(
285
+ " \u26a0 no QA plan files found \u2014 add one under .agents/qa-plans/<feature-id>.md (or .agents/memory/qa-plan.md)"
286
+ );
287
+ } else {
288
+ console.log(
289
+ " checked against: " + plans.map((f) => path.relative(root, f)).join(", ")
290
+ );
291
+ }
246
292
 
247
293
  const changedCode = [];
248
294
  for (const f of changed) {
@@ -262,7 +308,7 @@ for (const f of changed) {
262
308
  if (isCovered(f, covered, waivers)) pass(`${f} covered by QA plan`);
263
309
  else
264
310
  fail(
265
- `${f} NOT covered \u2014 add it to the \`## Coverage map\` in qa-plan.md (or a waivered entry with a reason)`
311
+ `${f} NOT covered \u2014 add it to the \`## Coverage map\` in a .agents/qa-plans/<feature-id>.md plan (or a waivered entry with a reason)`
266
312
  );
267
313
  }
268
314
 
@@ -1,49 +0,0 @@
1
- # QA plan — coverage-driven
2
-
3
- One entry per SHIPPED change. Authored by `qa-architect` (thinker), or by the
4
- main orchestrator as the mandatory fallback when qa-architect is not dispatched.
5
- Gated mechanically by `npm run test:qa-plan` — a changed code path that is neither
6
- covered nor waivered here fails, so the change cannot be declared verified.
7
-
8
- Keep the LAST change on top. Remove the illustrative examples below; never leave
9
- a real-looking path in Coverage map without the actual runtime assertion backing it.
10
-
11
- ## Change intent
12
-
13
- One sentence naming the SEMANTIC behavior change (what the user experiences), not
14
- the file list. The flow-closure half of the gate judges sibling surfaces and
15
- optional variant states against this intent.
16
-
17
- - change: return flow no longer offers a condition selector — every return POSTs without a condition payload
18
-
19
- ## Parallel-surface & variant audit
20
-
21
- For every flow in `.agents/memory/flow-map.md` whose `surfaces:` intersect this
22
- diff, address EVERY sibling surface and EVERY declared `variant` here — either
23
- `->` a covering assertion or `audited:` with a reason. `test:qa-plan` fails on any
24
- sibling surface or variant the plan does not NAME. A sibling that intentionally
25
- keeps old behavior must say so (`audited: deferred — logged in handoff`), never be
26
- silently omitted.
27
-
28
- - components/LendingActionDrawers.tsx -> test:quick::return (click-through: Mark as Returned, no condition selector)
29
- - mobile/features/return/ReturnStatusSheet.tsx audited: deferred — mobile still renders the selector; same simplification pending, logged in handoff.md
30
- - app/api/transactions/[id]/return/route.ts -> test:api::return (POST without condition payload succeeds)
31
- - flow: return variant: condition-selector-present audited: removed by this change — asserting absence only
32
- - flow: return variant: condition-selector-absent -> test:quick::return (no-selector click-through renders no condition UI)
33
-
34
- ## Coverage map
35
-
36
- A `Changed path -> executing test/assertion` list. Every changed CODE path needs a
37
- runtime assertion that ACTUALLY exercises it (never trust a tier label — verify the
38
- backing suite reaches the path). Paths may be files or directories. Lines starting
39
- with `#` are ignored.
40
-
41
- # components/return/ReturnConfirmDrawer.tsx -> test:quick::progression (click-through: Mark as Returned -> confirm -> /api/transactions/[id]/return)
42
- # app/api/transactions/[id]/return/route.ts -> test:quick::progression (return POST succeeds with no condition payload)
43
-
44
- ## Waivers
45
-
46
- Trivial diffs only (pure copy/docs, or a path whose behavior is already covered
47
- elsewhere). Every waiver MUST carry a `reason:` on the same line.
48
-
49
- # waived: src/some-screen.tsx reason: copy-only string swap, no runtime path changed