qualia-framework 5.1.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +50 -26
  2. package/agents/builder.md +8 -0
  3. package/agents/plan-checker.md +10 -1
  4. package/agents/planner.md +1 -1
  5. package/agents/qa-browser.md +10 -0
  6. package/agents/research-synthesizer.md +10 -0
  7. package/agents/researcher.md +38 -2
  8. package/agents/roadmapper.md +10 -0
  9. package/agents/verifier.md +15 -3
  10. package/agents/visual-evaluator.md +1 -1
  11. package/bin/install.js +42 -0
  12. package/bin/state.js +155 -133
  13. package/docs/archive/session-report-2026-04-18.md +199 -0
  14. package/docs/archive/v4.0.0-review.md +288 -0
  15. package/docs/instruction-budget-audit.md +113 -0
  16. package/docs/polish-loop-supervised-run.md +111 -0
  17. package/guide.md +11 -4
  18. package/hooks/session-start.js +1 -1
  19. package/package.json +5 -2
  20. package/rules/architecture.md +125 -0
  21. package/rules/infrastructure.md +1 -2
  22. package/rules/speed.md +55 -0
  23. package/skills/qualia-help/SKILL.md +1 -1
  24. package/skills/qualia-hook-gen/SKILL.md +206 -0
  25. package/skills/qualia-map/SKILL.md +1 -1
  26. package/skills/qualia-milestone/SKILL.md +1 -1
  27. package/skills/qualia-new/SKILL.md +2 -2
  28. package/skills/qualia-optimize/REFERENCE.md +65 -2
  29. package/skills/qualia-optimize/SKILL.md +26 -1
  30. package/skills/qualia-polish/SKILL.md +3 -3
  31. package/skills/qualia-polish-loop/REFERENCE.md +1 -1
  32. package/skills/qualia-polish-loop/SKILL.md +3 -3
  33. package/skills/qualia-polish-loop/fixtures/broken.html +2 -2
  34. package/skills/qualia-polish-loop/scripts/loop.mjs +26 -5
  35. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +14 -5
  36. package/skills/qualia-polish-loop/scripts/score.mjs +1 -1
  37. package/skills/qualia-postmortem/SKILL.md +1 -1
  38. package/skills/qualia-prd/SKILL.md +199 -0
  39. package/skills/qualia-quick/SKILL.md +1 -1
  40. package/skills/qualia-research/SKILL.md +5 -3
  41. package/skills/qualia-road/SKILL.md +15 -5
  42. package/skills/qualia-task/SKILL.md +1 -1
  43. package/templates/PRODUCT.md +1 -1
  44. package/tests/bin.test.sh +155 -8
  45. package/tests/skills.test.sh +143 -0
  46. package/tests/slop-detect.test.sh +160 -0
  47. package/docs/playwright-loop-review-2026-05-03.md +0 -65
  48. /package/{rules → qualia-design}/design-brand.md +0 -0
  49. /package/{rules → qualia-design}/design-laws.md +0 -0
  50. /package/{rules → qualia-design}/design-product.md +0 -0
  51. /package/{rules → qualia-design}/design-reference.md +0 -0
  52. /package/{rules → qualia-design}/design-rubric.md +0 -0
  53. /package/{rules → qualia-design}/frontend.md +0 -0
@@ -63,14 +63,20 @@ function fingerprintIssue(issue) {
63
63
  function cmdInit() {
64
64
  const statePath = flag("--state");
65
65
  if (!statePath) { console.error("--state required"); exit(2); }
66
- const url = flag("--url");
67
- if (!url) { console.error("--url required"); exit(2); }
66
+ const routesFlag = flag("--routes");
67
+ const urlFlag = flag("--url");
68
+ if (!routesFlag && !urlFlag) { console.error("--url or --routes required"); exit(2); }
69
+ const urls = routesFlag
70
+ ? routesFlag.split(",").map((s) => s.trim()).filter(Boolean)
71
+ : [urlFlag];
68
72
  const max = flagInt("--max", 8);
69
73
  const budget = flagInt("--budget", 100000);
70
74
  const state = {
71
- url,
75
+ url: urls[0], // primary URL (backward compat with single-route SKILL.md)
76
+ urls, // full list — multi-route mode when length > 1
72
77
  brief_path: flag("--brief", null),
73
78
  reference_path: flag("--ref", null),
79
+ reduced_motion: argv.includes("--reduced-motion"),
74
80
  max_iterations: max,
75
81
  token_budget: budget,
76
82
  tokens_used: 0,
@@ -234,8 +240,13 @@ function cmdReport() {
234
240
  const lines = [];
235
241
  lines.push(`# Visual-Polish Loop Report`);
236
242
  lines.push("");
237
- lines.push(`- **URL:** ${state.url}`);
243
+ if (Array.isArray(state.urls) && state.urls.length > 1) {
244
+ lines.push(`- **URLs (${state.urls.length}):** ${state.urls.join(", ")}`);
245
+ } else {
246
+ lines.push(`- **URL:** ${state.url}`);
247
+ }
238
248
  lines.push(`- **Brief:** ${state.brief_path || "_(none)_"}`);
249
+ if (state.reduced_motion) lines.push(`- **Reduced motion:** forced`);
239
250
  lines.push(`- **Started:** ${state.started_at}`);
240
251
  lines.push(`- **Final verdict:** ${state.verdict.toUpperCase()}${state.kill_reason ? ` — ${state.kill_reason}` : ""}`);
241
252
  lines.push(`- **Iterations:** ${state.iteration} / ${state.max_iterations}`);
@@ -286,12 +297,22 @@ switch (cmd) {
286
297
  console.log(`loop.mjs — orchestrator for /qualia-polish-loop
287
298
 
288
299
  Commands:
289
- init --state PATH --url URL [--brief PATH] [--ref PATH] [--max 8] [--budget 100000]
300
+ init --state PATH (--url URL | --routes URL1,URL2,...) [--brief PATH] [--ref PATH] [--max 8] [--budget 100000] [--reduced-motion]
290
301
  record --state PATH --eval PATH
291
302
  status --state PATH
292
303
  commit-fix --state PATH --file PATH --slug TEXT
293
304
  report --state PATH > report.md
294
305
 
306
+ Multi-route mode (v5.2):
307
+ --routes wins over --url. State stores both state.url (first, backward
308
+ compat) and state.urls (full list). Orchestrator drives capture+eval
309
+ per URL; aggregate scores are min across URLs and viewports.
310
+
311
+ Reduced-motion mode (v5.2):
312
+ --reduced-motion is recorded in state.reduced_motion. Capture script
313
+ is invoked with --reduced-motion which forces prefers-reduced-motion.
314
+ Vision evaluator scores motion on CSS-declaration quality only.
315
+
295
316
  Exit codes (record):
296
317
  0 = success (all dims >= 3) 1 = continue (more iterations needed)
297
318
  2 = invocation error 3 = killed (regression / budget / max)`);
@@ -26,7 +26,7 @@ import { homedir } from "node:os";
26
26
 
27
27
  // ── Arg parsing ──────────────────────────────────────────────────────────
28
28
  function parseArgs() {
29
- const args = { url: null, out: null, viewports: [375, 768, 1440], wait: 1500 };
29
+ const args = { url: null, out: null, viewports: [375, 768, 1440], wait: 1500, reducedMotion: false };
30
30
  for (let i = 2; i < argv.length; i++) {
31
31
  const a = argv[i];
32
32
  if (a === "--url" && argv[i + 1]) args.url = argv[++i];
@@ -34,11 +34,16 @@ function parseArgs() {
34
34
  else if (a === "--viewports" && argv[i + 1]) {
35
35
  args.viewports = argv[++i].split(",").map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n) && n > 0);
36
36
  } else if (a === "--wait" && argv[i + 1]) args.wait = parseInt(argv[++i], 10);
37
+ else if (a === "--reduced-motion") args.reducedMotion = true;
37
38
  else if (a === "--help" || a === "-h") {
38
39
  console.log(`playwright-capture.mjs — Screenshot capture for /qualia-polish-loop
39
40
 
40
41
  Usage:
41
- node playwright-capture.mjs --url <url> --out <dir> [--viewports 375,768,1440] [--wait 1500]
42
+ node playwright-capture.mjs --url <url> --out <dir> [--viewports 375,768,1440] [--wait 1500] [--reduced-motion]
43
+
44
+ Flags:
45
+ --reduced-motion Force prefers-reduced-motion: reduce in the captured page.
46
+ Use when the brief explicitly opts out of motion (a11y mode).
42
47
 
43
48
  Backend selection (auto):
44
49
  1. Playwright — import('playwright') if installed
@@ -82,13 +87,15 @@ async function captureViaPlaywright(args) {
82
87
  const height = viewportHeight(width);
83
88
  const file = join(args.out, `${name}-${width}.png`);
84
89
  try {
85
- const ctx = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1 });
90
+ const ctxOpts = { viewport: { width, height }, deviceScaleFactor: 1 };
91
+ if (args.reducedMotion) ctxOpts.reducedMotion = "reduce";
92
+ const ctx = await browser.newContext(ctxOpts);
86
93
  const page = await ctx.newPage();
87
94
  await page.goto(args.url, { waitUntil: "networkidle", timeout: 30000 });
88
95
  if (args.wait > 0) await page.waitForTimeout(args.wait);
89
96
  await page.screenshot({ path: file, fullPage: false });
90
97
  await ctx.close();
91
- results.push({ viewport: name, width, height, file, ok: true, backend: "playwright" });
98
+ results.push({ viewport: name, width, height, file, ok: true, backend: "playwright", reducedMotion: !!args.reducedMotion });
92
99
  } catch (err) {
93
100
  results.push({ viewport: name, width, height, file, ok: false, backend: "playwright", error: err.message });
94
101
  }
@@ -140,8 +147,9 @@ function captureViaChromeBinary(args, binary) {
140
147
  `--window-size=${width},${height}`,
141
148
  `--screenshot=${file}`,
142
149
  `--virtual-time-budget=${Math.max(args.wait + 1000, 3000)}`,
143
- args.url,
144
150
  ];
151
+ if (args.reducedMotion) flags.push("--force-prefers-reduced-motion");
152
+ flags.push(args.url);
145
153
  const r = spawnSync(binary, flags, { encoding: "utf8", timeout: 30000 });
146
154
  let ok = r.status === 0 && existsSync(file);
147
155
  let size = 0;
@@ -152,6 +160,7 @@ function captureViaChromeBinary(args, binary) {
152
160
  results.push({
153
161
  viewport: name, width, height, file, ok,
154
162
  backend: "chrome-binary", binary,
163
+ reducedMotion: !!args.reducedMotion,
155
164
  ...(ok ? {} : { error: r.stderr ? r.stderr.split("\n").slice(0, 3).join(" / ") : `exit ${r.status}` }),
156
165
  });
157
166
  }
@@ -3,7 +3,7 @@
3
3
  * score.mjs -- Qualia visual-polish loop scoring utility.
4
4
  *
5
5
  * Takes a JSON object with 8 dimension scores and computes pass/fail
6
- * per the design rubric formula from rules/design-rubric.md.
6
+ * per the design rubric formula from qualia-design/design-rubric.md.
7
7
  *
8
8
  * Usage:
9
9
  * echo '{"typography":3,"color":2,"spatial":3,"layout":3,"shadow":3,"motion":3,"microcopy":3,"container":3}' | node score.mjs
@@ -95,7 +95,7 @@ matches the failure. Use this lookup:
95
95
  | Wave 2 task ran before wave 1 committed | `agents/planner.md` (dependency graph) |
96
96
  | Build passed locally, broke in CI | `rules/deployment.md` or a missing pre-deploy-gate scan |
97
97
  | RLS missing on new table | `rules/security.md` + `agents/builder.md` (security persona handling) |
98
- | Design regression — fonts off, contrast fail | `rules/frontend.md` + `skills/qualia-design/SKILL.md` |
98
+ | Design regression — fonts off, contrast fail | `qualia-design/frontend.md` + `skills/qualia-design/SKILL.md` |
99
99
  | Migration unsafe (DROP without IF EXISTS, etc.) | `hooks/migration-guard.js` |
100
100
  | Verifier missed it | `agents/verifier.md` — most embarrassing case, address with extra care |
101
101
 
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: qualia-prd
3
+ description: "Synthesize the current conversation into a durable Product Requirements Document (PRD) at .planning/PRD-{slug}.md. Optionally opens a parent GitHub issue. No interview — synthesizes what's already been discussed. Trigger on 'qualia-prd', 'turn this into a PRD', 'write this up as a feature spec', 'make a spec from this discussion', 'PRD this', 'externalize this idea', 'capture this as a spec'. Pairs with /qualia-issues to break the PRD into vertical-slice issues. Distinct from /qualia-plan (phase-operational) — /qualia-prd is feature-durable. v5.3 flagship from Matt Pocock's /to-prd pattern."
4
+ allowed-tools:
5
+ - Bash
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Grep
10
+ - Glob
11
+ - Agent
12
+ argument-hint: "[slug] [--issue] [--no-issue] [--update PATH]"
13
+ ---
14
+
15
+ # /qualia-prd — Conversation → durable feature spec
16
+
17
+ You've been discussing a feature in chat. `/qualia-prd` synthesizes that conversation into a durable PRD on disk so the spec lives outside the chat context. From there, `/qualia-issues` can split it into vertical-slice GH issues, and `/qualia-plan` can plan a phase against it.
18
+
19
+ **Distinct from `/qualia-new`:** `/qualia-new` is project setup (one-shot — JOURNEY.md, PRODUCT.md, CONTEXT.md). `/qualia-prd` is mid-project feature spec capture. You'll run it dozens of times across a project's life; you run `/qualia-new` once.
20
+
21
+ ## When to use
22
+
23
+ - Mid-project, when a feature has been discussed enough that you want it durable
24
+ - Before `/qualia-issues` so the issues link back to a real spec
25
+ - Before `/qualia-plan` if the phase needs a feature spec upstream of it
26
+ - When the conversation is about to compact and the PRD context would be lost
27
+
28
+ ## Pre-flight
29
+
30
+ ```bash
31
+ node ~/.claude/bin/qualia-ui.js banner plan
32
+ ```
33
+
34
+ | Gate | Check | If fail |
35
+ |---|---|---|
36
+ | In a project | `.planning/` exists | HALT — "Run `/qualia-new` first or use `/qualia-quick` for one-off work" |
37
+ | PRODUCT.md present | `.planning/PRODUCT.md` readable | NUDGE — proceed but recommend running `/qualia-new` to seed PRODUCT.md |
38
+ | CONTEXT.md present | `.planning/CONTEXT.md` readable | NUDGE — PRD will use ad-hoc terminology instead of the glossary |
39
+ | Working tree | `git status --porcelain` empty | OK to be dirty — PRD is additive, no logic changes |
40
+ | Slug | First arg or auto-derive from first heading | Auto-derive: kebab-case the conversation's main feature topic |
41
+
42
+ ## Process
43
+
44
+ ### 1. Slug + path
45
+
46
+ ```bash
47
+ SLUG="${1:-$(date +%Y%m%d)-feature}" # or auto-derive from conversation
48
+ PRD_PATH=".planning/PRD-${SLUG}.md"
49
+ ```
50
+
51
+ If `--update PATH` is provided, update an existing PRD instead of writing a new one. The synthesis preserves existing sections that haven't been touched in conversation; only changed sections get rewritten.
52
+
53
+ ### 2. Spawn synthesizer (forked subagent — preserves taste, cheap on context)
54
+
55
+ The synthesis runs in a **forked subagent**. Forks inherit the full conversation history + share the prompt cache, so the agent can pull the design discussion, decisions, ADR-worthy moments, and user voice without re-loading anything. The fork writes the PRD file and returns just the path + 1-line summary — keeps the parent session lean.
56
+
57
+ ```
58
+ Agent(
59
+ subagent_type="general-purpose",
60
+ description="Synthesize conversation → PRD",
61
+ prompt=`
62
+ You are synthesizing this conversation's feature discussion into a durable PRD.
63
+
64
+ # Output location
65
+ Write to: ${PRD_PATH}
66
+
67
+ # Inputs you have (from forked context)
68
+ - The full conversation up to this point
69
+ - @.planning/PRODUCT.md (project register, voice, anti-references)
70
+ - @.planning/CONTEXT.md (domain glossary — USE these terms, do not invent synonyms)
71
+ - @.planning/decisions/*.md (ADRs constraining the design space)
72
+
73
+ # PRD structure (copy this skeleton; fill from conversation)
74
+
75
+ \`\`\`markdown
76
+ ---
77
+ name: {feature name}
78
+ slug: ${SLUG}
79
+ created: ${date}
80
+ status: draft | accepted | shipped | abandoned
81
+ ---
82
+
83
+ # {Feature name}
84
+
85
+ ## Why this exists
86
+ {1-3 sentences: what problem does this solve, for whom, why now}
87
+
88
+ ## User stories
89
+ - As a {persona}, I want to {do X} so that {outcome}.
90
+ - ...
91
+
92
+ ## Acceptance criteria
93
+ Observable, testable behaviors. Each must be verifiable post-build.
94
+ - [ ] {criterion 1}
95
+ - [ ] {criterion 2}
96
+
97
+ ## Out of scope (mandatory)
98
+ What this feature is NOT. Pin this down so scope creep is detectable.
99
+ - {explicit non-goal 1}
100
+ - {explicit non-goal 2}
101
+
102
+ ## Modules touched
103
+ List the deep modules / files / packages this PRD will modify or create.
104
+ Use CONTEXT.md domain language. Surface interface changes explicitly.
105
+
106
+ | Module | Interface change | Rationale |
107
+ |---|---|---|
108
+ | {module} | {new method / removed export / signature change} | {why} |
109
+
110
+ ## Testing decisions
111
+ Which behaviors get tests, what kind (unit / integration / e2e), where the
112
+ seams are. /qualia-test --tdd will read this.
113
+
114
+ ## Open questions
115
+ Things the conversation flagged but didn't resolve. /qualia-discuss can
116
+ walk these down before /qualia-plan.
117
+
118
+ ## References
119
+ - Conversation timestamp: {ISO}
120
+ - Related ADRs: docs/adr/...
121
+ - Related PRDs: .planning/PRD-...
122
+ \`\`\`
123
+
124
+ # Discipline
125
+ - NO interview. Synthesize what was DISCUSSED. If a section has nothing in
126
+ the conversation, leave a TODO marker — do NOT invent.
127
+ - Use CONTEXT.md domain terms. If the user said "lesson" but CONTEXT.md says
128
+ "module", normalize to "module" and note the alias.
129
+ - Voice = PRODUCT.md voice. No "Welcome to" / "Get Started" / em-dashes in
130
+ user-facing copy snippets.
131
+ - Output: write the file, then return ONLY \`{ "path": "...", "summary": "1 sentence" }\`.
132
+ `
133
+ )
134
+ ```
135
+
136
+ ### 3. Optional GH issue
137
+
138
+ If `--issue` (or default when `gh` is configured AND `.planning/agents/tracker.md` exists), open a parent issue linking to the PRD path:
139
+
140
+ ```bash
141
+ PRD_BODY=$(cat "${PRD_PATH}")
142
+ gh issue create \
143
+ --title "PRD: {feature name}" \
144
+ --body-file "${PRD_PATH}" \
145
+ --label "prd,needs-triage"
146
+ ```
147
+
148
+ Pass `--no-issue` to skip. The PRD itself is the source of truth — the GH issue is a notification surface.
149
+
150
+ ### 4. Commit
151
+
152
+ ```bash
153
+ git add "${PRD_PATH}"
154
+ git -c user.name="Qualia Solutions" -c user.email="info@qualiasolutions.net" \
155
+ commit -m "prd(${SLUG}): {feature name}"
156
+ ```
157
+
158
+ ### 5. End-card + next-command hint
159
+
160
+ ```bash
161
+ node ~/.claude/bin/qualia-ui.js divider
162
+ node ~/.claude/bin/qualia-ui.js ok "PRD: ${PRD_PATH}"
163
+ node ~/.claude/bin/qualia-ui.js ok "Issue: {url or 'skipped'}"
164
+ node ~/.claude/bin/qualia-ui.js end "PRD CAPTURED" "/qualia-issues # break into vertical slices"
165
+ ```
166
+
167
+ ## Token discipline (mandatory)
168
+
169
+ This skill is the v5.3 reply to Matt's instruction-budget thesis. Three rules:
170
+
171
+ 1. **Forked subagent, file output.** The synthesis runs in a fork; main session never sees the full PRD body. Only `{path, summary}` flows back.
172
+ 2. **No re-summarization.** When the parent session needs the PRD later, it Reads the file (or a later skill does). Never paste the PRD body into chat to "confirm."
173
+ 3. **Fork prefix is stable.** Role + PRD skeleton are stable across spawns — Anthropic prompt caching applies. Per-invocation cost is ~5K tokens for the fork + ~2K for the synthesis output.
174
+
175
+ ## Failure modes
176
+
177
+ | Symptom | Cause | Action |
178
+ |---|---|---|
179
+ | `.planning/ not found` | Not in a Qualia project | HALT; recommend `/qualia-new` or `/qualia-quick` |
180
+ | Conversation has no clear feature topic | Skill invoked too early | NUDGE — ask the user "what feature are we PRD-ing? Pick a slug or paste a 1-line summary" |
181
+ | `gh` not configured | No GitHub auth | Skip issue creation silently; print PRD path |
182
+ | PRD path collision | Slug already exists | Append `-2`, `-3` to slug; surface to user |
183
+ | Empty conversation context | Fresh session | HALT — "/qualia-prd needs a discussion to synthesize. Discuss first, then run." |
184
+
185
+ ## Rules
186
+
187
+ 1. **Don't interview.** This is synthesis, not a deep-dive. If gaps exist, mark TODO and recommend `/qualia-discuss`.
188
+ 2. **CONTEXT.md is law.** Use the project's terms. Don't invent.
189
+ 3. **One PRD per feature.** If two features got conflated in conversation, write two PRDs and link them.
190
+ 4. **Voice match.** PRODUCT.md voice. No generic SaaS copy.
191
+ 5. **Forked context, file output.** Token discipline above.
192
+ 6. **Commit immediately.** PRDs are durable artifacts — they ship in git.
193
+
194
+ ## Pairs with
195
+
196
+ - `/qualia-discuss` — run BEFORE if the conversation has open questions
197
+ - `/qualia-issues` — run AFTER to break PRD into vertical-slice GH issues
198
+ - `/qualia-plan` — run AFTER `/qualia-issues` if you want a phase plan
199
+ - `/qualia-new` — runs ONCE at project setup; `/qualia-prd` is the per-feature equivalent
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qualia-quick
3
- description: "Fast path for small tasksbug fixes, tweaks, hot fixes. Skips full phase planning. Trigger on 'quick fix', 'small change', 'tweak', 'hot fix', 'one-line fix', 'quick edit', 'small bug'."
3
+ description: "Fast inline path for trivial fixes≤1 hour, typically 1 file, no plan, NO subagent spawn. The current Claude does the work directly. For a 1-5 file change that justifies a fresh builder context, use /qualia-task instead. Trigger on 'quick fix', 'small change', 'tweak', 'hot fix', 'one-line fix', 'typo', 'config tweak'."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qualia-research
3
- description: "Deep-research a niche domain or library BEFORE planning a specific phase. Spawns the researcher agent with Context7/WebFetch access. Writes to .planning/phase-{N}-research.md."
3
+ description: "Deep-research a niche domain or library BEFORE planning a specific phase. Spawns the researcher agent with Context7/WebFetch access. Writes to .planning/phase-{N}-research.md. Triggers: 'research X library', 'research the domain before planning', 'study Stripe webhooks', 'how do others do RAG', 'best practices for X', 'compare libraries', 'I need depth before planning phase N'. Distinct from /qualia-recall (which queries the local Obsidian vault) and /qualia-discuss (which interviews the user)."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
@@ -75,7 +75,8 @@ Reqs: {REQ-IDs for this phase}
75
75
 
76
76
  <output_path>.planning/phase-{N}-research.md</output_path>
77
77
 
78
- Priority: Context7 → WebFetch → WebSearch.
78
+ Priority: NotebookLM (cross-notebook query) → local knowledge layer (knowledge.js search + qualia-recall) → Context7 → WebFetch → WebSearch.
79
+ Skip web round when local sources cover the question with confidence ≥ MEDIUM (per agents/researcher.md §1b).
79
80
  Include: recommendation, rationale, versions, code examples, alternatives, pitfalls, sources.
80
81
  ", subagent_type="qualia-researcher", description="Phase {N} research")
81
82
  ```
@@ -123,4 +124,5 @@ node ~/.claude/bin/qualia-ui.js end "PHASE {N} RESEARCH DONE" "/qualia-plan {N}"
123
124
  1. **One session per run.** Don't research phases 1-5 in one call.
124
125
  2. **Must produce a file.** Research in conversation only is worthless.
125
126
  3. **Honor locked decisions.** Don't research alternatives to locked choices.
126
- 4. **Context7 first.** Try Context7 MCP before WebFetch.
127
+ 4. **Local-first.** Drain NotebookLM and `~/qualia-memory` before any external call. The team has already researched most domains we touch — querying existing notebooks is near-zero token cost AND higher-quality than fresh WebSearch.
128
+ 5. **Context7 before WebFetch.** When you do go external, Context7 first for libraries; only WebFetch for non-library content (blog posts, case studies, post-mortems).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qualia-road
3
- description: "Show the Qualia workflow map in the terminal — Project → Journey → Milestones → Phases → Tasks. Lists every command, when to use it, and how phases chain. Use when user asks 'how does Qualia work', 'what's the workflow', 'show me the road', 'what command does X', 'how do projects flow', or is new to the framework. (For an interactive HTML reference instead, use /qualia-help.)"
3
+ description: "TERMINAL workflow map — Project → Journey → Milestones → Phases → Tasks. Use this in headless/SSH/no-browser sessions or when the user asks for the road in chat. For the HTML reference (default when a browser is available), use /qualia-help. Triggers: 'how does Qualia work', 'what's the workflow', 'show me the road', 'what command does X', 'how do projects flow', 'in terminal please', SSH context."
4
4
  disable-model-invocation: true
5
5
  allowed-tools:
6
6
  - Read
@@ -45,14 +45,24 @@ Every road agent loads `PRODUCT.md + DESIGN.md + design-laws.md` substrate. Buil
45
45
  /qualia-polish --quick ~1m gates only
46
46
  ```
47
47
 
48
- ## /qualia-polish-loop -- autonomous visual QA (v5.1+)
48
+ ## /qualia-polish-loop -- autonomous visual QA (v5.1+, hardened in v5.2)
49
49
  ```
50
- /qualia-polish-loop http://localhost:3000 screenshot + eval + fix loop
51
- /qualia-polish-loop {url} --max 4 cap iterations
52
- /qualia-polish-loop {url} --ref design.png anchor to reference image
50
+ /qualia-polish-loop http://localhost:3000 screenshot + eval + fix loop
51
+ /qualia-polish-loop {url} --max 4 cap iterations
52
+ /qualia-polish-loop {url} --ref design.png anchor to reference image
53
+ /qualia-polish-loop {url} --reduced-motion force prefers-reduced-motion (v5.2+)
54
+ /qualia-polish-loop --routes /a,/b,/c multi-route sweep (v5.2+)
53
55
  ```
54
56
  Screenshots at 3 viewports (375/768/1440), scores 8 design dimensions using vision, fixes issues, re-screenshots, loops until all dims >= 3 or kill-switch triggers. Per-iteration git commits for clean revert.
55
57
 
58
+ ## v5.3+ skills (Matt Pocock gaps closed)
59
+ ```
60
+ /qualia-prd synthesize current conversation → .planning/PRD-{slug}.md (durable feature spec)
61
+ /qualia-hook-gen convert a CLAUDE.md/rules instruction into a deterministic pre-tool-use hook
62
+ /qualia-optimize --deepen now spawns 3 parallel interface-design variants per candidate (Step 5b)
63
+ ```
64
+ `/qualia-prd` pairs with `/qualia-issues` to form the PRD → vertical-slice → execute loop. `/qualia-hook-gen` reduces lifetime token cost (each migrated rule frees ~50-200 tokens per request). `/qualia-optimize --deepen` produces dramatically better refactor RFCs because 3 radically-different interfaces are surfaced and the human picks/hybridizes.
65
+
56
66
  ## Alignment substrate (v5.0+)
57
67
  Before high-stakes phases, run alignment skills against `.planning/CONTEXT.md` (domain glossary) and `.planning/decisions/` (ADRs):
58
68
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qualia-task
3
- description: "Builds a single focused task in a fresh builder context with atomic commit and validation. More structured than /qualia-quick, lighter than /qualia-build (no phase plan needed). Use when the user says 'build this one thing', 'add a component', 'implement this feature', 'qualia-task', or for any 1-5 file change outside a full phase."
3
+ description: "Single focused task with a FRESH builder subagent spawn — 1-3 hours, 1-5 files, atomic commit, validation contract. Heavier than /qualia-quick (which runs inline with no spawn) but lighter than /qualia-build (which needs a phase plan). Use when the user says 'build this one thing', 'add a component', 'implement this feature', 'qualia-task', or for any 1-5 file feature outside a full phase."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
@@ -49,7 +49,7 @@ Sites the project should NOT look like. Anti-references pin down what the design
49
49
  - {URL or descriptor} — ...
50
50
  ```
51
51
 
52
- For Brand register, these are usually saturated aesthetic lanes (see `rules/design-brand.md`).
52
+ For Brand register, these are usually saturated aesthetic lanes (see `qualia-design/design-brand.md`).
53
53
  For Product register, these are usually patterns we don't want to inherit (e.g., "Salesforce Lightning — too dense, too many panels").
54
54
 
55
55
  ## Positive references (optional, ≤3)
package/tests/bin.test.sh CHANGED
@@ -1200,11 +1200,11 @@ else
1200
1200
  fail_case "qualia-road missing qualia-polish-loop reference"
1201
1201
  fi
1202
1202
 
1203
- # 108. package.json version is 5.1.x (multi-target install + polish-loop in v5.x line)
1204
- if grep -qE '"5\.1\.' "$FRAMEWORK_DIR/package.json"; then
1205
- pass "package.json version is 5.1.x"
1203
+ # 108. package.json version is 5.x (5.1+ accepted; v5.1 / v5.2 share the v5 line)
1204
+ if grep -qE '"5\.[1234]\.' "$FRAMEWORK_DIR/package.json"; then
1205
+ pass "package.json version is 5.x"
1206
1206
  else
1207
- fail_case "package.json version not 5.1.x"
1207
+ fail_case "package.json version not 5.x"
1208
1208
  fi
1209
1209
 
1210
1210
  # 109. loop.mjs installs (orchestrator)
@@ -1428,12 +1428,159 @@ else
1428
1428
  fail_case "qualia-ui CLI broke"
1429
1429
  fi
1430
1430
 
1431
- # 128. package.json bumped to 5.1.x
1431
+ # 128. package.json bumped to 5.x (5.1+ accepted; 5.2 is the v5.2 release)
1432
1432
  PKG_V=$($NODE -e 'console.log(require("'"$FRAMEWORK_DIR"'/package.json").version)')
1433
- if echo "$PKG_V" | grep -qE "^5\.1\."; then
1434
- pass "package.json version bumped to 5.1.x ($PKG_V)"
1433
+ if echo "$PKG_V" | grep -qE "^5\.[1234]\."; then
1434
+ pass "package.json version bumped to 5.x ($PKG_V)"
1435
1435
  else
1436
- fail_case "package.json version not bumped to 5.1.x" "got=$PKG_V"
1436
+ fail_case "package.json version not 5.x" "got=$PKG_V"
1437
+ fi
1438
+
1439
+ echo ""
1440
+ echo "--- v5.2.0 (polish-loop reliability) ---"
1441
+
1442
+ # 129. loop.mjs init accepts --routes and stores the URL list
1443
+ TMP_S=$(mktmp)/qpl-routes.json
1444
+ mkdir -p "$(dirname "$TMP_S")"
1445
+ EXIT=0; $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
1446
+ --state "$TMP_S" \
1447
+ --routes "http://x.test/a,http://x.test/b,http://x.test/c" \
1448
+ --max 4 >/dev/null 2>&1 || EXIT=$?
1449
+ if [ "$EXIT" -eq 0 ] \
1450
+ && grep -q '"urls"' "$TMP_S" \
1451
+ && grep -q '"http://x.test/a"' "$TMP_S" \
1452
+ && grep -q '"http://x.test/b"' "$TMP_S" \
1453
+ && grep -q '"http://x.test/c"' "$TMP_S"; then
1454
+ pass "loop.mjs init --routes stores URL list (multi-route)"
1455
+ else
1456
+ fail_case "loop.mjs --routes failed (exit=$EXIT)"
1457
+ fi
1458
+
1459
+ # 130. state.url is the first --routes entry (backward compat with single-route SKILL.md)
1460
+ if grep -q '"url": "http://x.test/a"' "$TMP_S"; then
1461
+ pass "loop.mjs init --routes sets state.url = first URL (backward compat)"
1462
+ else
1463
+ fail_case "loop.mjs --routes did not set state.url to first entry"
1464
+ fi
1465
+
1466
+ # 131. loop.mjs init accepts --reduced-motion and records it in state
1467
+ TMP_S2=$(mktmp)/qpl-rm.json
1468
+ mkdir -p "$(dirname "$TMP_S2")"
1469
+ EXIT=0; $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
1470
+ --state "$TMP_S2" --url "http://x.test/" --reduced-motion >/dev/null 2>&1 || EXIT=$?
1471
+ if [ "$EXIT" -eq 0 ] && grep -q '"reduced_motion": true' "$TMP_S2"; then
1472
+ pass "loop.mjs init --reduced-motion records state.reduced_motion=true"
1473
+ else
1474
+ fail_case "loop.mjs --reduced-motion not recorded (exit=$EXIT)"
1475
+ fi
1476
+
1477
+ # 132. playwright-capture.mjs accepts --reduced-motion (parses without error)
1478
+ EXIT=0; OUT=$($NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/playwright-capture.mjs" --help 2>&1) || EXIT=$?
1479
+ if [ "$EXIT" -eq 0 ] && echo "$OUT" | grep -q -- "--reduced-motion"; then
1480
+ pass "playwright-capture.mjs --help documents --reduced-motion"
1481
+ else
1482
+ fail_case "playwright-capture --reduced-motion not in --help"
1483
+ fi
1484
+
1485
+ # 133. loop.mjs init rejects when neither --url nor --routes given
1486
+ TMP_S3=$(mktmp)/qpl-nourl.json
1487
+ mkdir -p "$(dirname "$TMP_S3")"
1488
+ EXIT=0; $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
1489
+ --state "$TMP_S3" --max 4 >/dev/null 2>&1 || EXIT=$?
1490
+ if [ "$EXIT" -eq 2 ]; then
1491
+ pass "loop.mjs init rejects missing --url/--routes (exit 2)"
1492
+ else
1493
+ fail_case "loop.mjs init did not reject missing URL (exit=$EXIT)"
1494
+ fi
1495
+
1496
+ # 134. loop.mjs report mentions multi-route when state.urls > 1
1497
+ TMP_S4=$(mktmp)/qpl-rep.json
1498
+ mkdir -p "$(dirname "$TMP_S4")"
1499
+ $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
1500
+ --state "$TMP_S4" --routes "http://a/,http://b/" >/dev/null 2>&1
1501
+ REP=$($NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" report --state "$TMP_S4" 2>&1)
1502
+ if echo "$REP" | grep -q "URLs (2)"; then
1503
+ pass "loop.mjs report renders multi-route header"
1504
+ else
1505
+ fail_case "loop.mjs report missing multi-route header"
1506
+ fi
1507
+
1508
+ echo ""
1509
+ echo "--- v5.3.0 (Matt Pocock gaps: prd, hook-gen, parallel-interface) ---"
1510
+
1511
+ # Re-install for v5.3 assertions (TMP from #99 may have v5.1 state)
1512
+ TMP=$(mktmp)
1513
+ echo "QS-FAWZI-01" | HOME="$TMP" $NODE "$INSTALL_JS" >/dev/null 2>&1
1514
+
1515
+ # 135. qualia-prd skill installs
1516
+ if [ -f "$TMP/.claude/skills/qualia-prd/SKILL.md" ]; then
1517
+ pass "qualia-prd skill installs"
1518
+ else
1519
+ fail_case "qualia-prd SKILL.md missing after install"
1520
+ fi
1521
+
1522
+ # 136. qualia-prd description mentions PRD synthesis from conversation
1523
+ if grep -q "synthesize\|Synthesize" "$TMP/.claude/skills/qualia-prd/SKILL.md" \
1524
+ && grep -q "/qualia-issues" "$TMP/.claude/skills/qualia-prd/SKILL.md"; then
1525
+ pass "qualia-prd describes synthesis flow + pairs with /qualia-issues"
1526
+ else
1527
+ fail_case "qualia-prd missing synthesis or /qualia-issues link"
1528
+ fi
1529
+
1530
+ # 137. qualia-prd documents fork-based token discipline
1531
+ if grep -qE "[Ff]orked subagent|fork.*subagent" "$TMP/.claude/skills/qualia-prd/SKILL.md" \
1532
+ && grep -q "Token discipline" "$TMP/.claude/skills/qualia-prd/SKILL.md"; then
1533
+ pass "qualia-prd documents fork-based synthesis (token discipline)"
1534
+ else
1535
+ fail_case "qualia-prd missing fork/token-discipline section"
1536
+ fi
1537
+
1538
+ # 138. qualia-hook-gen skill installs
1539
+ if [ -f "$TMP/.claude/skills/qualia-hook-gen/SKILL.md" ]; then
1540
+ pass "qualia-hook-gen skill installs"
1541
+ else
1542
+ fail_case "qualia-hook-gen SKILL.md missing after install"
1543
+ fi
1544
+
1545
+ # 139. qualia-hook-gen documents the three enforcement patterns (block/rewrite/warn)
1546
+ if grep -q "Block" "$TMP/.claude/skills/qualia-hook-gen/SKILL.md" \
1547
+ && grep -q "Rewrite" "$TMP/.claude/skills/qualia-hook-gen/SKILL.md" \
1548
+ && grep -q "Warn" "$TMP/.claude/skills/qualia-hook-gen/SKILL.md"; then
1549
+ pass "qualia-hook-gen documents block/rewrite/warn patterns"
1550
+ else
1551
+ fail_case "qualia-hook-gen missing one of block/rewrite/warn patterns"
1552
+ fi
1553
+
1554
+ # 140. qualia-hook-gen mandates Node hooks (cross-platform), not .sh scripts
1555
+ if grep -q "pure Node\|pure-node\|cross-platform" "$TMP/.claude/skills/qualia-hook-gen/SKILL.md" \
1556
+ && grep -q "No \`.sh\`\|No \\.sh\|exit 0/2\|exit 2 to" "$TMP/.claude/skills/qualia-hook-gen/SKILL.md"; then
1557
+ pass "qualia-hook-gen mandates pure-Node hooks (cross-platform discipline)"
1558
+ else
1559
+ fail_case "qualia-hook-gen missing pure-Node mandate"
1560
+ fi
1561
+
1562
+ # 141. qualia-optimize SKILL.md adds Step 5b (parallel-interface design)
1563
+ if grep -q "Step 5b\|Parallel Interface Design\|Wave 3" "$TMP/.claude/skills/qualia-optimize/SKILL.md" \
1564
+ && grep -q "radically different" "$TMP/.claude/skills/qualia-optimize/SKILL.md"; then
1565
+ pass "qualia-optimize Step 5b parallel-interface fan-out documented"
1566
+ else
1567
+ fail_case "qualia-optimize missing Step 5b parallel-interface stage"
1568
+ fi
1569
+
1570
+ # 142. qualia-optimize REFERENCE.md has the parallel-interface spawn template
1571
+ if grep -q "Parallel interface design prompt" "$TMP/.claude/skills/qualia-optimize/REFERENCE.md" \
1572
+ && grep -q "Variant 1\|variant 1" "$TMP/.claude/skills/qualia-optimize/REFERENCE.md"; then
1573
+ pass "qualia-optimize REFERENCE.md has parallel-interface spawn template"
1574
+ else
1575
+ fail_case "qualia-optimize REFERENCE.md missing parallel-interface template"
1576
+ fi
1577
+
1578
+ # 143. package.json version is 5.x (5.1+ accepted; v5.3 is the v5.3 release)
1579
+ PKG_V=$($NODE -e 'console.log(require("'"$FRAMEWORK_DIR"'/package.json").version)')
1580
+ if echo "$PKG_V" | grep -qE "^5\.[1234]\."; then
1581
+ pass "package.json version is 5.x ($PKG_V) — v5.3 accepted"
1582
+ else
1583
+ fail_case "package.json version not 5.x" "got=$PKG_V"
1437
1584
  fi
1438
1585
 
1439
1586
  echo ""