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.
- package/README.md +50 -26
- package/agents/builder.md +8 -0
- package/agents/plan-checker.md +10 -1
- package/agents/planner.md +1 -1
- package/agents/qa-browser.md +10 -0
- package/agents/research-synthesizer.md +10 -0
- package/agents/researcher.md +38 -2
- package/agents/roadmapper.md +10 -0
- package/agents/verifier.md +15 -3
- package/agents/visual-evaluator.md +1 -1
- package/bin/install.js +42 -0
- package/bin/state.js +155 -133
- package/docs/archive/session-report-2026-04-18.md +199 -0
- package/docs/archive/v4.0.0-review.md +288 -0
- package/docs/instruction-budget-audit.md +113 -0
- package/docs/polish-loop-supervised-run.md +111 -0
- package/guide.md +11 -4
- package/hooks/session-start.js +1 -1
- package/package.json +5 -2
- package/rules/architecture.md +125 -0
- package/rules/infrastructure.md +1 -2
- package/rules/speed.md +55 -0
- package/skills/qualia-help/SKILL.md +1 -1
- package/skills/qualia-hook-gen/SKILL.md +206 -0
- package/skills/qualia-map/SKILL.md +1 -1
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-new/SKILL.md +2 -2
- package/skills/qualia-optimize/REFERENCE.md +65 -2
- package/skills/qualia-optimize/SKILL.md +26 -1
- package/skills/qualia-polish/SKILL.md +3 -3
- package/skills/qualia-polish-loop/REFERENCE.md +1 -1
- package/skills/qualia-polish-loop/SKILL.md +3 -3
- package/skills/qualia-polish-loop/fixtures/broken.html +2 -2
- package/skills/qualia-polish-loop/scripts/loop.mjs +26 -5
- package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +14 -5
- package/skills/qualia-polish-loop/scripts/score.mjs +1 -1
- package/skills/qualia-postmortem/SKILL.md +1 -1
- package/skills/qualia-prd/SKILL.md +199 -0
- package/skills/qualia-quick/SKILL.md +1 -1
- package/skills/qualia-research/SKILL.md +5 -3
- package/skills/qualia-road/SKILL.md +15 -5
- package/skills/qualia-task/SKILL.md +1 -1
- package/templates/PRODUCT.md +1 -1
- package/tests/bin.test.sh +155 -8
- package/tests/skills.test.sh +143 -0
- package/tests/slop-detect.test.sh +160 -0
- package/docs/playwright-loop-review-2026-05-03.md +0 -65
- /package/{rules → qualia-design}/design-brand.md +0 -0
- /package/{rules → qualia-design}/design-laws.md +0 -0
- /package/{rules → qualia-design}/design-product.md +0 -0
- /package/{rules → qualia-design}/design-reference.md +0 -0
- /package/{rules → qualia-design}/design-rubric.md +0 -0
- /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
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 | `
|
|
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
|
|
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. **
|
|
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: "
|
|
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
|
|
51
|
-
/qualia-polish-loop {url} --max 4
|
|
52
|
-
/qualia-polish-loop {url} --ref design.png
|
|
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: "
|
|
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
|
package/templates/PRODUCT.md
CHANGED
|
@@ -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 `
|
|
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.
|
|
1204
|
-
if grep -qE '"5\.
|
|
1205
|
-
pass "package.json version is 5.
|
|
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.
|
|
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.
|
|
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\.
|
|
1434
|
-
pass "package.json version bumped to 5.
|
|
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
|
|
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 ""
|