qualia-framework 4.5.0 → 5.1.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 (64) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +12 -75
  3. package/README.md +23 -16
  4. package/agents/builder.md +9 -21
  5. package/agents/planner.md +8 -0
  6. package/agents/verifier.md +8 -0
  7. package/agents/visual-evaluator.md +132 -0
  8. package/bin/cli.js +54 -18
  9. package/bin/install.js +369 -29
  10. package/bin/qualia-ui.js +208 -1
  11. package/bin/slop-detect.mjs +5 -0
  12. package/bin/state.js +34 -1
  13. package/docs/install-redesign-builder-prompt.md +290 -0
  14. package/docs/install-redesign-pilot.md +234 -0
  15. package/docs/playwright-loop-builder-prompt.md +185 -0
  16. package/docs/playwright-loop-design-notes.md +108 -0
  17. package/docs/playwright-loop-pilot-results.md +170 -0
  18. package/docs/playwright-loop-review-2026-05-03.md +65 -0
  19. package/docs/playwright-loop-tester-prompt.md +213 -0
  20. package/docs/reviews/matt-pocock-skills-analysis.md +300 -0
  21. package/guide.md +9 -5
  22. package/hooks/env-empty-guard.js +74 -0
  23. package/hooks/pre-compact.js +19 -9
  24. package/hooks/pre-deploy-gate.js +8 -2
  25. package/hooks/pre-push.js +26 -12
  26. package/hooks/supabase-destructive-guard.js +62 -0
  27. package/hooks/vercel-account-guard.js +91 -0
  28. package/package.json +2 -1
  29. package/rules/design-brand.md +4 -0
  30. package/rules/design-laws.md +4 -0
  31. package/rules/design-product.md +4 -0
  32. package/rules/design-rubric.md +4 -0
  33. package/rules/grounding.md +4 -0
  34. package/skills/qualia-build/SKILL.md +40 -46
  35. package/skills/qualia-discuss/SKILL.md +51 -68
  36. package/skills/qualia-handoff/SKILL.md +1 -0
  37. package/skills/qualia-issues/SKILL.md +151 -0
  38. package/skills/qualia-map/SKILL.md +78 -35
  39. package/skills/qualia-new/REFERENCE.md +139 -0
  40. package/skills/qualia-new/SKILL.md +45 -121
  41. package/skills/qualia-optimize/REFERENCE.md +202 -0
  42. package/skills/qualia-optimize/SKILL.md +72 -237
  43. package/skills/qualia-plan/SKILL.md +58 -65
  44. package/skills/qualia-polish-loop/REFERENCE.md +265 -0
  45. package/skills/qualia-polish-loop/SKILL.md +201 -0
  46. package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
  47. package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
  48. package/skills/qualia-polish-loop/scripts/loop.mjs +302 -0
  49. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +197 -0
  50. package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
  51. package/skills/qualia-report/SKILL.md +141 -200
  52. package/skills/qualia-research/SKILL.md +28 -33
  53. package/skills/qualia-road/SKILL.md +103 -0
  54. package/skills/qualia-ship/SKILL.md +1 -0
  55. package/skills/qualia-task/SKILL.md +1 -1
  56. package/skills/qualia-test/SKILL.md +50 -2
  57. package/skills/qualia-triage/SKILL.md +152 -0
  58. package/skills/qualia-verify/SKILL.md +63 -104
  59. package/skills/qualia-zoom/SKILL.md +51 -0
  60. package/skills/zoho-workflow/SKILL.md +1 -1
  61. package/templates/CONTEXT.md +36 -0
  62. package/templates/decisions/ADR-template.md +30 -0
  63. package/tests/bin.test.sh +451 -7
  64. package/tests/state.test.sh +58 -0
@@ -0,0 +1,265 @@
1
+ # REFERENCE — /qualia-polish-loop
2
+
3
+ Verbatim agent prompts and operational details. Loaded on demand by SKILL.md, not carried in the system prompt. Per progressive-disclosure discipline (Matt Pocock): the agent reads SKILL.md first, then this file when it needs the spawn templates.
4
+
5
+ ## Architecture summary
6
+
7
+ ```
8
+ SKILL.md driver (Claude session)
9
+
10
+ ├─ scripts/playwright-capture.mjs (deterministic Node — produces PNGs)
11
+ │ ↓ writes /tmp/qpl-{ts}/iter-{N}/{mobile,tablet,desktop}-*.png
12
+
13
+ ├─ Agent({subagent_type: "qualia-visual-evaluator", ...})
14
+ │ ↓ reads PNGs, returns single JSON envelope (eval.json)
15
+
16
+ ├─ scripts/loop.mjs record (deterministic — verdict + fingerprints)
17
+ │ ↓ exit 0=SUCCESS, 1=CONTINUE, 3=KILLED
18
+
19
+ ├─ Agent({subagent_type: "qualia-builder", ...}) × up to 3 in parallel
20
+ │ ↓ each fixes ONE issue, calls scripts/loop.mjs commit-fix
21
+
22
+ └─ scripts/loop.mjs report (final markdown report)
23
+ ↓ writes .planning/visual-polish-loop.md
24
+ ```
25
+
26
+ ## Capture: backend selection
27
+
28
+ The capture script (`scripts/playwright-capture.mjs`) auto-selects in this order:
29
+
30
+ 1. `import('playwright')` — preferred when available; gives deterministic `waitUntil: 'networkidle'`
31
+ 2. `import('playwright-core')` — same API, lighter package
32
+ 3. `~/.cache/ms-playwright/chromium-{version}/chrome-{linux64,linux,mac,win}/chrome` — if Playwright was ever installed for browsers but the package isn't import-resolvable
33
+ 4. `which google-chrome` / `chromium` / `chromium-browser` / `chrome` — system browser fallback
34
+
35
+ For backends 3 and 4 (binary-direct), the script uses `--headless=new --screenshot --virtual-time-budget`. Less precise than Playwright's `networkidle` waiting but works without any npm dependency.
36
+
37
+ Setup hints if all four fail:
38
+
39
+ ```bash
40
+ # Option A — Playwright (best stability)
41
+ npm i -D playwright && npx playwright install chromium
42
+
43
+ # Option B — system Chrome (fastest setup if you already have Chrome installed)
44
+ # (no action needed if google-chrome is on PATH)
45
+ ```
46
+
47
+ ## Vision-evaluator spawn template (VERBATIM)
48
+
49
+ The vision evaluator's anchored discipline: **DEFAULT TO 3.** Only score above 3 with a cited design principle. Only score below 3 with a quoted violation. Without anchoring, vision models return "looks great!" to everything — that failure mode is the entire reason this loop exists. The full rubric criteria live in `agents/visual-evaluator.md`; this section is the spawn template.
50
+
51
+ When the loop reaches step 2 (Evaluate), spawn ONE agent with the screenshots, brief, rubric, and previous-iteration context. Inline this prompt verbatim — do not paraphrase.
52
+
53
+ ```
54
+ Agent({
55
+ subagent_type: "qualia-visual-evaluator",
56
+ description: "Score iteration {N} screenshots against rubric",
57
+ prompt: `
58
+ Role: @~/.claude/agents/visual-evaluator.md
59
+
60
+ <rubric>
61
+ {INLINE rules/design-rubric.md §"The 8 dimensions" through §"Aggregate score"}
62
+ </rubric>
63
+
64
+ <brief>
65
+ {INLINE the relevant excerpt from .planning/DESIGN.md — sections "Direction", "Color", "Typography"}
66
+ </brief>
67
+
68
+ <product>
69
+ {INLINE the relevant excerpt from .planning/PRODUCT.md — register, voice, anti-references}
70
+ </product>
71
+
72
+ <screenshots>
73
+ - mobile (375px): /tmp/qpl-{ts}/iter-{N}/mobile-375.png
74
+ - tablet (768px): /tmp/qpl-{ts}/iter-{N}/tablet-768.png
75
+ - desktop (1440px): /tmp/qpl-{ts}/iter-{N}/desktop-1440.png
76
+ </screenshots>
77
+
78
+ <viewport_meta>
79
+ { "reduced_motion": {true|false}, "viewport_widths": [375, 768, 1440] }
80
+ </viewport_meta>
81
+
82
+ <previous_iteration>
83
+ {If N > 1, INLINE eval.json.top_issues from iter-{N-1} so the evaluator can verify regression vs improvement. Otherwise: "(first iteration — no prior data)"}
84
+ </previous_iteration>
85
+
86
+ <task>
87
+ This is iteration {N} of {max}. Read each screenshot. Score every dimension 1-5 with one-line evidence per dimension per viewport. Return a single fenced JSON block per the contract in your role file. No prose outside the JSON.
88
+ </task>
89
+ `
90
+ })
91
+ ```
92
+
93
+ The evaluator's role file (`agents/visual-evaluator.md`) carries the trust-boundary block, the calibration examples, and the JSON output contract. Together with this spawn template, the prompt prefix is stable across iterations — Anthropic prompt caching reuses the role + rubric + brief prefix, so the per-iteration cost is roughly: 3 image reads + the previous-iteration delta.
94
+
95
+ ## Fix-builder spawn template (VERBATIM)
96
+
97
+ When the loop has 1-3 issues to fix, spawn one builder per issue IN THE SAME RESPONSE TURN (parallel). Each fixes one dimension, narrowly.
98
+
99
+ ```
100
+ Agent({
101
+ subagent_type: "qualia-builder",
102
+ description: "Fix {dim} issue: {short description}",
103
+ prompt: `
104
+ Role: @~/.claude/agents/builder.md
105
+
106
+ <phase_context>
107
+ You are inside /qualia-polish-loop iteration {N}. The vision evaluator scored
108
+ the {dim} dimension at {score}. Your single task: fix that one dimension.
109
+
110
+ <design>
111
+ {INLINE .planning/DESIGN.md tokens relevant to {dim}}
112
+ </design>
113
+
114
+ <product>
115
+ {INLINE .planning/PRODUCT.md voice + register}
116
+ </product>
117
+ </phase_context>
118
+
119
+ <task_context>
120
+ # Issue
121
+ - Dimension: {dim}
122
+ - Severity: {severity}
123
+ - Description: {description}
124
+ - Likely file: {likely_file or "(infer from grep — start at the path the screenshot suggests)"}
125
+ - Recommended fix: {fix}
126
+
127
+ # Files probably affected
128
+ {1-3 candidate paths the loop has inferred from the URL routing}
129
+ </task_context>
130
+
131
+ <task>
132
+ 1. Read the likely file. If the issue is in a different file, follow the import graph until you find the source.
133
+ 2. Make the MINIMUM edit to fix this one dimension. Do not refactor. Do not change logic. Do not touch state management. Do not change copy unless this is a microcopy issue.
134
+ 3. Use design tokens from DESIGN.md. Do not invent new color values, font names, or spacing.
135
+ 4. After the edit, commit via the orchestrator (slop-detect-gated):
136
+ node ~/.claude/skills/qualia-polish-loop/scripts/loop.mjs commit-fix --state {STATE} --file {file} --slug {dim}-{short-keyword}
137
+ If slop-detect blocks (exit 2), READ the slop output and re-edit. If you cannot fix without violating slop-detect, return BLOCKED with the conflict.
138
+ 5. Return DONE with: file modified, lines changed, slop-detect: pass, commit: {sha}.
139
+ </task>
140
+
141
+ <rules>
142
+ - Vision says: {evidence from eval.json.viewport_results[].evidence[{dim}]}
143
+ - Do not add features.
144
+ - Do not write tests for this fix (the loop's next iteration is the test).
145
+ - Single commit. The orchestrator handles the slug + iteration prefix.
146
+ </rules>
147
+ `
148
+ })
149
+ ```
150
+
151
+ ## Iteration log entry (what `loop.mjs record` writes to state.json.iterations[])
152
+
153
+ ```json
154
+ {
155
+ "iteration": 1,
156
+ "scores": { "typography": 1, "color": 1, "spatial": 3, "layout": 1, "shadow": 3, "motion": 3, "microcopy": 1, "container": 1 },
157
+ "aggregate": 14,
158
+ "pass": false,
159
+ "failing_dims": ["typography", "color", "layout", "microcopy", "container"],
160
+ "top_issues": [
161
+ { "dim": "color", "severity": "critical", "description": "blue→purple gradient on hero", "likely_file": "src/styles/globals.css", "fix": "replace linear-gradient with single accent var(--accent)" },
162
+ { "dim": "typography", "severity": "critical", "description": "Inter as primary font-family", "likely_file": "src/styles/globals.css", "fix": "swap to Fraunces + JetBrains Mono per DESIGN.md §3" },
163
+ { "dim": "layout", "severity": "high", "description": "three identical feature cards in section 2", "likely_file": "src/pages/index.tsx", "fix": "vary card sizes per design-brand.md §Layout" }
164
+ ],
165
+ "tokens_used": 14500,
166
+ "timestamp": "2026-05-03T12:34:56.000Z"
167
+ }
168
+ ```
169
+
170
+ ## Issue fingerprint (regression detection)
171
+
172
+ The orchestrator computes a fingerprint per top_issue for each iteration:
173
+
174
+ ```
175
+ fingerprint = `${dim}__${path.basename(likely_file)}__${first_32_chars_of_description}`
176
+ .toLowerCase().replace(/\W+/g, "_")
177
+ ```
178
+
179
+ State stores `state.fingerprints[fingerprint] = { iterations: [1,2,3], description, dim }`. The KILL trigger is **3 consecutive integer iterations in `iterations[]`** — non-consecutive recurrences don't kill (the issue may have been fixed, broken by a different change, then refixed; that's a different signal than "fix-builder cannot fix this").
180
+
181
+ When the kill trigger fires, the verdict becomes `killed_regression` and `state.kill_fingerprint` records which one. The user can `cat state.json | jq '.fingerprints | to_entries | map(select(.key == "{fingerprint}"))'` to see the recurrence pattern.
182
+
183
+ ## Token-budget table
184
+
185
+ | Iterations | Tokens (est.) | Sized for |
186
+ |---|---|---|
187
+ | 2 | ~30K | known-clean page sanity-check |
188
+ | 4 | ~60K | mid-confidence |
189
+ | 6 | ~90K | default |
190
+ | 8 | ~120K | hard cap; pass `--budget 150000` to allow |
191
+
192
+ Per-iteration cost (rough):
193
+ - 3 screenshot reads ≈ 9K
194
+ - rubric + brief inlined ≈ 2K (cached after iter 1)
195
+ - previous-iteration delta ≈ 0.5K
196
+ - 3 fix-builder spawns × (file read + edit + commit-fix call) ≈ 3K
197
+ - **per-iteration ≈ 14.5K**
198
+
199
+ ## Self-test scenarios (mapping to spec)
200
+
201
+ | # | Fixture | Expected | Verifier |
202
+ |---|---|---|---|
203
+ | 1 | `fixtures/clean.html` | SUCCESS in 1-2 iterations, all dims ≥ 4 | run capture, run evaluator inline, assert pass |
204
+ | 2 | `fixtures/broken.html` | SUCCESS in 4-6 iters; identifies banned font + gradient + 3-card grid + side-stripe + generic CTA | each fix-builder commits a `qpl-N:` change; final eval all dims ≥ 3 |
205
+ | 3 | Kill-switch | KILL at iter ≤ 4 with `LOOP_REGRESSION_DETECTED` | call `loop.mjs record` 3× with the same fingerprint; assert exit 3 + correct verdict |
206
+
207
+ The pilot-results doc at `docs/playwright-loop-pilot-results.md` records the actual outcome from `bash scripts/_self-tests.sh` (Scenario 3 is exercised by a deterministic unit-style invocation; Scenarios 1+2 require a real vision pass and are run by Claude when the loop ships).
208
+
209
+ ## Final report template (what `loop.mjs report` emits to stdout)
210
+
211
+ ```markdown
212
+ # Visual-Polish Loop Report
213
+
214
+ - **URL:** http://localhost:3000
215
+ - **Brief:** .planning/DESIGN.md
216
+ - **Started:** 2026-05-03T12:00:00Z
217
+ - **Final verdict:** SUCCESS
218
+ - **Iterations:** 4 / 8
219
+ - **Tokens used:** 58000 / 100000
220
+ - **Fixes committed:** 7
221
+
222
+ ## Iteration log
223
+
224
+ ### Iteration 1
225
+ - Scores: typo=1 colo=1 spat=3 layo=1 shad=3 moti=3 micr=1 cont=1
226
+ - Aggregate: 14/40 (avg 1.75)
227
+ - Pass: NO (failing: typography, color, layout, microcopy, container)
228
+ - Top issues:
229
+ - **color** [critical] blue→purple gradient on hero → src/styles/globals.css
230
+ - **typography** [critical] Inter as primary → src/styles/globals.css
231
+ - **layout** [high] three identical cards → src/pages/index.tsx
232
+
233
+ ### Iteration 2
234
+ - Scores: typo=3 colo=3 spat=3 layo=2 shad=3 moti=3 micr=2 cont=2
235
+ - Aggregate: 21/40 (avg 2.62)
236
+ - Pass: NO (failing: layout, microcopy, container)
237
+ - ...
238
+
239
+ ### Iteration 3
240
+ - Scores: typo=4 colo=3 spat=3 layo=3 shad=3 moti=3 micr=3 cont=3
241
+ - Aggregate: 25/40 (avg 3.13)
242
+ - Pass: YES
243
+
244
+ ## Fix commits (revertable)
245
+ - abc1234 qpl-1: color-gradient-removal — src/styles/globals.css
246
+ - def5678 qpl-1: typography-fraunces — src/styles/globals.css
247
+ - ...
248
+
249
+ ## Issue fingerprints (regression tracker)
250
+ - color__globals_css__blue_purple_gradient — iterations [1] — fixed at iter 2
251
+ ```
252
+
253
+ ## Why three viewports
254
+
255
+ Per the spec's hard constraint (§5g `prefers-reduced-motion` and §5c mobile-only failures), the loop MUST evaluate at mobile (375), tablet (768), and desktop (1440). The aggregate score is the **minimum** across viewports for each dimension — a layout that's elegant on desktop but breaks at 375 is a fail, full stop.
256
+
257
+ This is intentional. Most visual regressions Fawzi has documented in `/insights` (hero videos cropped wrong on mobile, touch targets < 44px on mobile, navigation collapse misbehaving) only show up below 768. Scoring on desktop alone is how we got "looks great in dev" → "looks broken on the user's phone."
258
+
259
+ ## What the loop does NOT do (deferred to v5.2)
260
+
261
+ - Cross-browser rendering checks (Firefox / WebKit) — Chromium-only, per `qualia-polish` Stage 4 precedent
262
+ - Accessibility audits beyond what the rubric scores — use `/qualia-polish` Stage 3 (Lighthouse + axe) for that
263
+ - Performance regressions — use `/qualia-polish-loop` only after Lighthouse score passes
264
+ - Reference-image-only mode (compare to a target screenshot without a brief) — currently the brief is required; reference is supplemental
265
+ - Multi-page sweeps — one URL per invocation; chain `/qualia-polish-loop` per route for site-wide passes
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: qualia-polish-loop
3
+ description: "Autonomous visual-polish loop — screenshots a live URL at three viewports, scores 8 design dimensions against the rubric using vision, fixes issues in the codebase, re-screenshots, loops until every dimension scores ≥ 3 or the kill-switch fires. Trigger on 'polish loop', 'visual loop', 'screenshot polish', 'visual QA loop', 'fix what I see', 'make it look right', 'iterate on the design until it's correct'. v5.1 flagship — closes the design-iteration churn friction."
4
+ allowed-tools:
5
+ - Bash
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Grep
10
+ - Glob
11
+ - Agent
12
+ argument-hint: "{url} [--brief PATH] [--max 8] [--viewports 375,768,1440] [--ref PATH] [--budget 100000]"
13
+ ---
14
+
15
+ # /qualia-polish-loop — Autonomous Visual-Polish Loop
16
+
17
+ See its own work. Fix its own work. Stop only when correct.
18
+
19
+ ## What it does
20
+
21
+ Takes a URL + design brief. Screenshots at 3 viewports (mobile / tablet / desktop). Spawns a vision evaluator that scores 8 dimensions of `rules/design-rubric.md` against the brief with cited evidence. Spawns up to 3 fix-builders in parallel for the top issues. Re-screenshots. Loops until all dimensions ≥ 3 or the kill-switch trips (regression, budget, or max iterations).
22
+
23
+ Different from `/qualia-polish`: that one is read+edit+slop-detect, single pass. This one is **see+edit+verify+repeat** with a real loop and real screenshots.
24
+
25
+ ## When to use
26
+
27
+ - After `/qualia-build` or `/qualia-verify` when visual issues remain that text-based slop-detect can't catch (mobile-only breakage, motion missing, hero-video framing)
28
+ - When the user says "it doesn't look right" / "fix what I see" / "the mobile version is broken"
29
+ - As an opt-in upgrade to `/qualia-polish --redesign` Stage 4 (vision loop)
30
+ - Before `/qualia-ship` for a final visual QA pass
31
+
32
+ ## Pre-flight (sequential, every invocation)
33
+
34
+ ```bash
35
+ node ~/.claude/bin/qualia-ui.js banner polish
36
+ ```
37
+
38
+ Run these in order. Halt on the first failure.
39
+
40
+ | Gate | Check | If fail |
41
+ |---|---|---|
42
+ | Substrate | `rules/design-rubric.md`, `rules/design-laws.md` exist | Run `npx qualia install` |
43
+ | Brief | `--brief` PATH if provided, else `.planning/DESIGN.md`, else PRODUCT.md | If none, HALT: "No design brief found. Pass --brief or run /qualia-new." |
44
+ | Browser | `node ~/.claude/skills/qualia-polish-loop/scripts/playwright-capture.mjs --url about:blank --out /tmp/qpl-preflight` exits 0 | HALT with the script's setup hint |
45
+ | URL reachable | `curl -fsS -o /dev/null -w '%{http_code}' "$URL"` returns 2xx/3xx | HALT — start the dev server first |
46
+ | Working tree | `git status --porcelain` is empty | HALT — "Loop commits per iteration. Stash or commit pending changes first." |
47
+ | Budget | Estimate iters × ~14.5K tokens; warn if > 100K | If `--budget` not set, default 100K. Surface estimate to user. |
48
+
49
+ State the preflight explicitly:
50
+
51
+ ```
52
+ QPL_PREFLIGHT: substrate=pass brief={path} browser=pass url=200 git=clean budget=100K
53
+ ```
54
+
55
+ ## Loop (max 8 iterations, default 6)
56
+
57
+ Single state file at `/tmp/qpl-{timestamp}/state.json` keeps the deterministic counters (iteration, fingerprints, fixes, verdict) out of the LLM context.
58
+
59
+ ```bash
60
+ RUN_ID="qpl-$(date +%s)"
61
+ STATE="/tmp/${RUN_ID}/state.json"
62
+ node ~/.claude/skills/qualia-polish-loop/scripts/loop.mjs init \
63
+ --state "$STATE" --url "$URL" --brief "$BRIEF" \
64
+ --max "${MAX:-8}" --budget "${BUDGET:-100000}"
65
+ ```
66
+
67
+ Each iteration:
68
+
69
+ ### 1. Capture (deterministic, ~3-5s)
70
+
71
+ ```bash
72
+ ITER_DIR="/tmp/${RUN_ID}/iter-${N}"
73
+ node ~/.claude/skills/qualia-polish-loop/scripts/playwright-capture.mjs \
74
+ --url "$URL" --out "$ITER_DIR" --viewports 375,768,1440 --wait 1500
75
+ ```
76
+
77
+ Three PNGs land in `$ITER_DIR/{mobile,tablet,desktop}-*.png`. The capture script auto-selects a backend: Playwright if `import('playwright')` resolves, else the cached `~/.cache/ms-playwright/chromium-*` binary, else `google-chrome` / `chromium` on PATH.
78
+
79
+ ### 2. Evaluate (vision — single Agent spawn per iteration)
80
+
81
+ Spawn `Agent` with `subagent_type="qualia-visual-evaluator"`. Inline the rubric, the brief, the screenshots paths, the viewport meta, and the previous iteration's issues (if any). The agent reads the screenshots itself and returns a single JSON envelope per the contract in `agents/visual-evaluator.md`.
82
+
83
+ Save the agent's JSON to `$ITER_DIR/eval.json`. The exact spawn template lives in REFERENCE.md (so the SKILL.md stays under 250 lines per progressive-disclosure discipline).
84
+
85
+ ### 3. Decide (deterministic)
86
+
87
+ ```bash
88
+ node ~/.claude/skills/qualia-polish-loop/scripts/loop.mjs record \
89
+ --state "$STATE" --eval "$ITER_DIR/eval.json"
90
+ ```
91
+
92
+ Exit codes: `0` = SUCCESS (all dims ≥ 3), `1` = CONTINUE (more iterations), `3` = KILLED (regression / budget / max).
93
+
94
+ The orchestrator computes the verdict per `rules/design-rubric.md`:
95
+
96
+ - **all aggregate scores ≥ 3 AND no critical issues remain** → SUCCESS, exit loop
97
+ - **same issue fingerprint recurred 3 consecutive iterations** → KILL, `LOOP_REGRESSION_DETECTED`
98
+ - **tokens used exceeds budget** → KILL, `OUT_OF_BUDGET`
99
+ - **iteration count = max** → KILL, `MAX_ITERATIONS_REACHED`
100
+ - **else** → CONTINUE
101
+
102
+ ### 4. Fix (parallel, up to 3 builders)
103
+
104
+ For the top 3 issues from `eval.json.top_issues`, spawn `qualia-builder` agents IN THE SAME RESPONSE TURN (parallel). Each builder receives the issue, the design tokens from DESIGN.md, and explicit "fix only this dimension, do not refactor" rules. Template in REFERENCE.md.
105
+
106
+ Each builder commits its fix via the orchestrator's slop-detect-gated commit:
107
+
108
+ ```bash
109
+ node ~/.claude/skills/qualia-polish-loop/scripts/loop.mjs commit-fix \
110
+ --state "$STATE" --file "$AFFECTED_FILE" --slug "{issue-slug}"
111
+ ```
112
+
113
+ This stages the file, runs `slop-detect` first (critical findings BLOCK and the builder must retry), then commits with `qpl-{N}: {slug}`. Every fix is a real revertable git commit. Failed fixes leave the working tree clean — no half-edits.
114
+
115
+ ### 5. Redeploy (default: dev-localhost)
116
+
117
+ Default mode does NOT redeploy. The loop assumes the dev server is running with HMR; the next capture re-screenshots the changed page. Verify the dev server is still up:
118
+
119
+ ```bash
120
+ curl -fsS -o /dev/null "$URL" || HALT "dev server died at iteration $N"
121
+ ```
122
+
123
+ For Vercel-preview mode (opt-in via `--deploy preview`), run `vercel deploy` (NOT `--prod`) and update `URL` to the deploy URL before the next capture. Slower (~30-60s/iter), but works against a fully-built environment when HMR isn't reliable.
124
+
125
+ ### 6. Loop back to Capture, increment N
126
+
127
+ ## Post-loop
128
+
129
+ ```bash
130
+ node ~/.claude/skills/qualia-polish-loop/scripts/loop.mjs report --state "$STATE" \
131
+ > .planning/visual-polish-loop.md
132
+ git add .planning/visual-polish-loop.md
133
+ git -c user.name="Qualia Solutions" -c user.email="info@qualiasolutions.net" \
134
+ commit -m "qpl-final: visual-polish-loop report"
135
+ ```
136
+
137
+ Then a closing card via `qualia-ui`:
138
+
139
+ ```bash
140
+ node ~/.claude/bin/qualia-ui.js divider
141
+ node ~/.claude/bin/qualia-ui.js ok "Iterations: {N}/{max}"
142
+ node ~/.claude/bin/qualia-ui.js ok "Final aggregate: {sum}/40 (avg {avg})"
143
+ node ~/.claude/bin/qualia-ui.js ok "Fixes committed: {count}"
144
+ node ~/.claude/bin/qualia-ui.js ok "Tokens used: {N}K of {budget}K"
145
+ node ~/.claude/bin/qualia-ui.js end "VISUAL POLISH LOOP — {SUCCESS|KILLED-AT-N|OUT-OF-BUDGET}" "/qualia-verify or /qualia-ship"
146
+ ```
147
+
148
+ ## Hard rules
149
+
150
+ 1. **Vision-eval is anchored.** The rubric criteria are inlined in the eval prompt verbatim. Never spawn the evaluator with "tell me what you think" — that's how you get "looks great!" hallucinations. The verbatim contract is in REFERENCE.md.
151
+ 2. **Per-iteration commits.** Every fix gets `qpl-{N}: {slug}` prefix so the user can `git revert` any iteration cleanly. The orchestrator enforces this via `loop.mjs commit-fix`.
152
+ 3. **Slop-detect gate.** Critical findings BLOCK the commit. The fix-builder must retry (or the loop kills if the same fingerprint recurs 3x).
153
+ 4. **No silent destruction.** All edits go through git. Failed iterations leave clear `qpl-N:` commit trail. No working-tree side effects.
154
+ 5. **`prefers-reduced-motion` honored.** If reduced motion is set, the evaluator scores motion on CSS-declaration quality, not visible animation. Don't penalize "no motion" when reduced motion is on.
155
+ 6. **Budget discipline.** Token usage is tracked in state. KILL at cap. Surface partial progress.
156
+ 7. **Cleanup on exit.** No orphan browser processes (the capture script spawns short-lived headless processes, but verify nothing lingers via `pgrep -f chrome --headless`). Temp dirs left for forensic debugging until the next run.
157
+
158
+ ## Failure modes
159
+
160
+ | Symptom | Likely cause | Action |
161
+ |---|---|---|
162
+ | `playwright-capture.mjs` exits 2 with `no_browser_backend` | No Chrome/Chromium found anywhere | `npx playwright install chromium` or install Google Chrome |
163
+ | Screenshot is blank | Page not done rendering | Increase `--wait` to 3000ms; retry once |
164
+ | Vision agent says "looks great!" to everything | Rubric not inlined / prompt drift | Verify REFERENCE.md prompt is being used verbatim. Check `agents/visual-evaluator.md` is on disk. |
165
+ | Same issue every iteration | Fix-builder not addressing root cause | Kill at 3 recurrences (automatic). Read `state.json.fingerprints[*]` for diagnosis. Hand-fix and resume. |
166
+ | Dev server died mid-loop | Port conflict / crash | Detect via curl heartbeat in step 5. HALT — restart server, run loop with `--resume STATE`. |
167
+ | Token budget blown | Too many iterations needed | KILL at cap (automatic). Report partial progress. Consider tightening DESIGN.md or pre-running `/qualia-polish` once first. |
168
+
169
+ ## Setup notes for users
170
+
171
+ The loop requires headless Chrome/Chromium. Two ways to get it:
172
+
173
+ ```bash
174
+ # A. Playwright (recommended — best stability + waiting semantics)
175
+ npx playwright install chromium
176
+
177
+ # B. System Chrome (fallback — works if google-chrome or chromium is on PATH)
178
+ # Already installed on most dev machines; nothing to do.
179
+ ```
180
+
181
+ The capture script tries Playwright first, then the cached chromium binary, then PATH lookups. No npm dependency on Playwright is added to your project — it's optional.
182
+
183
+ ## Self-tests (the spec mandates 3 scenarios)
184
+
185
+ `docs/playwright-loop-pilot-results.md` records the outcome of running the loop against:
186
+ 1. `fixtures/clean.html` (well-designed page) — expect SUCCESS in 1-2 iterations
187
+ 2. `fixtures/broken.html` (deliberately bad page) — expect SUCCESS in 4-6 iterations after fixes
188
+ 3. Synthetic kill-switch test — verify regression detection fires at iteration 4
189
+
190
+ Run them with `bash skills/qualia-polish-loop/scripts/_self-tests.sh` (if present) or follow the manual instructions in REFERENCE.md.
191
+
192
+ ## Token-budget discipline
193
+
194
+ | Max iterations | Estimated tokens | Notes |
195
+ |---|---|---|
196
+ | 2 | ~30K | tight — only for known-clean pages |
197
+ | 4 | ~60K | standard |
198
+ | 6 | ~90K | default |
199
+ | 8 | ~120K | maximum — set `--budget 150000` to allow |
200
+
201
+ Each iteration costs roughly: 3 PNG vision reads (~9K), rubric prompt (~2K), 3 fix-builder spawns with file reads (~3.5K). Plus the orchestrator's deterministic CLI calls (~negligible). The state file persists outside Claude's context entirely — only the per-iteration eval JSON and fix outcomes flow back in.
@@ -0,0 +1,117 @@
1
+ <!doctype html>
2
+ <!--
3
+ Deliberately broken page used by /qualia-polish-loop self-test Scenario 2.
4
+ Hits multiple absolute-ban patterns from rules/design-laws.md and
5
+ rules/design-brand.md so the vision evaluator has to identify them all.
6
+ Banned font (Inter), pure white + pure black, blue-purple gradient,
7
+ gradient text, identical 3-column card grid, "Get Started" / "Learn More"
8
+ generic CTAs, side-stripe border-left:4px decorative, max-width:1280
9
+ fixed container, outline:none without focus replacement. This fixture
10
+ is intentional shipped slop. Do not lift patterns from it.
11
+ -->
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width,initial-scale=1">
16
+ <title>BrandX</title>
17
+ <style>
18
+ body {
19
+ margin: 0;
20
+ background: #ffffff;
21
+ color: #000000;
22
+ font-family: Inter, sans-serif;
23
+ font-weight: 400;
24
+ }
25
+ .container { max-width: 1280px; margin: 0 auto; padding: 24px; }
26
+ header { display: flex; justify-content: space-between; align-items: center; padding: 16px 0; }
27
+ .logo { font-size: 18px; font-weight: 700; }
28
+ nav a { color: #000; margin-left: 24px; text-decoration: none; outline: none; }
29
+ nav a:hover { text-decoration: underline; }
30
+
31
+ .hero {
32
+ text-align: center;
33
+ padding: 80px 16px;
34
+ background: linear-gradient(135deg, #2563eb 0%, #9333ea 100%);
35
+ color: #ffffff;
36
+ }
37
+ .hero h1 {
38
+ font-size: 56px;
39
+ font-family: Inter, sans-serif;
40
+ font-weight: 800;
41
+ margin: 0 0 16px;
42
+ background: linear-gradient(90deg, #fff, #c4b5fd);
43
+ -webkit-background-clip: text;
44
+ background-clip: text;
45
+ color: transparent;
46
+ }
47
+ .hero p { font-size: 18px; max-width: 620px; margin: 0 auto 32px; }
48
+
49
+ .cta-row { display: flex; gap: 12px; justify-content: center; }
50
+ .btn {
51
+ background: #ffffff;
52
+ color: #000;
53
+ padding: 12px 24px;
54
+ border: 0;
55
+ font-family: Inter, sans-serif;
56
+ font-size: 16px;
57
+ cursor: pointer;
58
+ outline: none;
59
+ }
60
+ .btn-secondary { background: transparent; color: #fff; border: 1px solid #fff; }
61
+
62
+ .features { padding: 80px 0; }
63
+ .features h2 { font-size: 32px; text-align: center; margin: 0 0 48px; font-family: Inter, sans-serif; }
64
+ .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
65
+ .card {
66
+ background: #f8f9fa;
67
+ padding: 32px;
68
+ border-left: 4px solid #2563eb;
69
+ }
70
+ .card h3 { font-size: 18px; margin: 0 0 8px; font-family: Inter, sans-serif; }
71
+ .card p { color: #666; font-size: 14px; margin: 0; }
72
+
73
+ footer { background: #000; color: #fff; padding: 32px 0; text-align: center; font-size: 14px; }
74
+ </style>
75
+ </head>
76
+ <body>
77
+ <div class="container">
78
+ <header>
79
+ <span class="logo">BrandX</span>
80
+ <nav>
81
+ <a href="#">Features</a>
82
+ <a href="#">Pricing</a>
83
+ <a href="#">About</a>
84
+ </nav>
85
+ </header>
86
+ </div>
87
+
88
+ <section class="hero">
89
+ <h1>Welcome to BrandX</h1>
90
+ <p>The AI-powered platform that helps you do more with less, built for modern teams who care about results.</p>
91
+ <div class="cta-row">
92
+ <button class="btn">Get Started</button>
93
+ <button class="btn btn-secondary">Learn More</button>
94
+ </div>
95
+ </section>
96
+
97
+ <div class="container features">
98
+ <h2>Everything you need to succeed</h2>
99
+ <div class="grid">
100
+ <div class="card">
101
+ <h3>Fast</h3>
102
+ <p>Lightning-fast performance that scales with your business needs and grows alongside you.</p>
103
+ </div>
104
+ <div class="card">
105
+ <h3>Secure</h3>
106
+ <p>Bank-grade security with end-to-end encryption keeping your data safe from prying eyes.</p>
107
+ </div>
108
+ <div class="card">
109
+ <h3>Easy</h3>
110
+ <p>Intuitive interface designed for everyone, from beginners to power users.</p>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <footer>2026 BrandX. All rights reserved.</footer>
116
+ </body>
117
+ </html>