qualia-framework 4.5.0 → 5.3.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 (66) 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-tester-prompt.md +213 -0
  19. package/docs/polish-loop-supervised-run.md +111 -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-hook-gen/SKILL.md +206 -0
  38. package/skills/qualia-issues/SKILL.md +151 -0
  39. package/skills/qualia-map/SKILL.md +78 -35
  40. package/skills/qualia-new/REFERENCE.md +139 -0
  41. package/skills/qualia-new/SKILL.md +45 -121
  42. package/skills/qualia-optimize/REFERENCE.md +265 -0
  43. package/skills/qualia-optimize/SKILL.md +92 -232
  44. package/skills/qualia-plan/SKILL.md +58 -65
  45. package/skills/qualia-polish-loop/REFERENCE.md +265 -0
  46. package/skills/qualia-polish-loop/SKILL.md +201 -0
  47. package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
  48. package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
  49. package/skills/qualia-polish-loop/scripts/loop.mjs +323 -0
  50. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +206 -0
  51. package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
  52. package/skills/qualia-prd/SKILL.md +199 -0
  53. package/skills/qualia-report/SKILL.md +141 -200
  54. package/skills/qualia-research/SKILL.md +28 -33
  55. package/skills/qualia-road/SKILL.md +103 -0
  56. package/skills/qualia-ship/SKILL.md +1 -0
  57. package/skills/qualia-task/SKILL.md +1 -1
  58. package/skills/qualia-test/SKILL.md +50 -2
  59. package/skills/qualia-triage/SKILL.md +152 -0
  60. package/skills/qualia-verify/SKILL.md +63 -104
  61. package/skills/qualia-zoom/SKILL.md +51 -0
  62. package/skills/zoho-workflow/SKILL.md +1 -1
  63. package/templates/CONTEXT.md +36 -0
  64. package/templates/decisions/ADR-template.md +30 -0
  65. package/tests/bin.test.sh +598 -7
  66. package/tests/state.test.sh +58 -0
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * score.mjs -- Qualia visual-polish loop scoring utility.
4
+ *
5
+ * Takes a JSON object with 8 dimension scores and computes pass/fail
6
+ * per the design rubric formula from rules/design-rubric.md.
7
+ *
8
+ * Usage:
9
+ * echo '{"typography":3,"color":2,"spatial":3,"layout":3,"shadow":3,"motion":3,"microcopy":3,"container":3}' | node score.mjs
10
+ * node score.mjs --file scores.json
11
+ * node score.mjs --scores '{"typography":4,"color":3,...}'
12
+ *
13
+ * Output (JSON):
14
+ * { total, avg, pass, failing: [{dim, score}], verdict }
15
+ *
16
+ * Exit codes:
17
+ * 0 all dimensions >= 3 (pass)
18
+ * 1 one or more dimensions < 3 (fail)
19
+ * 2 invocation error
20
+ */
21
+
22
+ import { readFileSync } from "node:fs";
23
+ import { argv, stdin, exit } from "node:process";
24
+
25
+ const DIMENSIONS = [
26
+ "typography",
27
+ "color",
28
+ "spatial",
29
+ "layout",
30
+ "shadow",
31
+ "motion",
32
+ "microcopy",
33
+ "container",
34
+ ];
35
+
36
+ const DIMENSION_ALIASES = {
37
+ "color cohesion": "color",
38
+ "color_cohesion": "color",
39
+ "spatial rhythm": "spatial",
40
+ "spatial_rhythm": "spatial",
41
+ "layout originality": "layout",
42
+ "layout_originality": "layout",
43
+ "shadow & depth": "shadow",
44
+ "shadow_depth": "shadow",
45
+ "shadow & depth hierarchy": "shadow",
46
+ "motion intent": "motion",
47
+ "motion_intent": "motion",
48
+ "microcopy specificity": "microcopy",
49
+ "microcopy_specificity": "microcopy",
50
+ "container depth": "container",
51
+ "container_depth": "container",
52
+ "container depth & nesting": "container",
53
+ };
54
+
55
+ function normalizeKey(key) {
56
+ const lower = key.toLowerCase().trim();
57
+ return DIMENSION_ALIASES[lower] || lower;
58
+ }
59
+
60
+ function parseScores(raw) {
61
+ if (typeof raw === "string") {
62
+ try {
63
+ raw = JSON.parse(raw);
64
+ } catch (e) {
65
+ console.error(`Invalid JSON: ${e.message}`);
66
+ exit(2);
67
+ }
68
+ }
69
+
70
+ const scores = {};
71
+ for (const [key, value] of Object.entries(raw)) {
72
+ const normalized = normalizeKey(key);
73
+ if (DIMENSIONS.includes(normalized)) {
74
+ const num = parseInt(value, 10);
75
+ if (num < 1 || num > 5 || isNaN(num)) {
76
+ console.error(`Invalid score for ${key}: ${value} (must be 1-5)`);
77
+ exit(2);
78
+ }
79
+ scores[normalized] = num;
80
+ }
81
+ }
82
+
83
+ // Validate all dimensions present
84
+ const missing = DIMENSIONS.filter((d) => !(d in scores));
85
+ if (missing.length > 0) {
86
+ console.error(`Missing dimensions: ${missing.join(", ")}`);
87
+ exit(2);
88
+ }
89
+
90
+ return scores;
91
+ }
92
+
93
+ function computeResult(scores) {
94
+ const dims = DIMENSIONS.map((d) => scores[d]);
95
+ const total = dims.reduce((a, b) => a + b, 0);
96
+ const avg = +(total / dims.length).toFixed(2);
97
+ const failing = DIMENSIONS.filter((d) => scores[d] < 3).map((d) => ({
98
+ dim: d,
99
+ score: scores[d],
100
+ }));
101
+ const pass = failing.length === 0;
102
+
103
+ let verdict;
104
+ if (pass) {
105
+ verdict = "SUCCESS";
106
+ } else if (failing.some((f) => f.score === 1)) {
107
+ verdict = "CRITICAL_FAIL";
108
+ } else {
109
+ verdict = "FAIL";
110
+ }
111
+
112
+ return {
113
+ scores,
114
+ total,
115
+ max: 40,
116
+ avg,
117
+ pass,
118
+ failing,
119
+ verdict,
120
+ };
121
+ }
122
+
123
+ // -- CLI entry point --
124
+
125
+ async function main() {
126
+ let input = null;
127
+
128
+ // Parse args
129
+ for (let i = 2; i < argv.length; i++) {
130
+ if (argv[i] === "--file" && argv[i + 1]) {
131
+ try {
132
+ input = readFileSync(argv[i + 1], "utf8");
133
+ } catch (e) {
134
+ console.error(`Cannot read file: ${e.message}`);
135
+ exit(2);
136
+ }
137
+ i++;
138
+ } else if (argv[i] === "--scores" && argv[i + 1]) {
139
+ input = argv[i + 1];
140
+ i++;
141
+ } else if (argv[i] === "--help" || argv[i] === "-h") {
142
+ console.log(`score.mjs -- Qualia visual-polish loop scoring utility
143
+
144
+ Usage:
145
+ echo '{"typography":3,...}' | node score.mjs
146
+ node score.mjs --file scores.json
147
+ node score.mjs --scores '{"typography":4,...}'
148
+
149
+ Dimensions: ${DIMENSIONS.join(", ")}
150
+ Each scored 1-5. All must be >= 3 to pass.`);
151
+ exit(0);
152
+ }
153
+ }
154
+
155
+ // Read from stdin if no explicit input
156
+ if (!input) {
157
+ const chunks = [];
158
+ for await (const chunk of stdin) {
159
+ chunks.push(chunk);
160
+ }
161
+ input = Buffer.concat(chunks).toString("utf8").trim();
162
+ }
163
+
164
+ if (!input) {
165
+ console.error("No input provided. Use --file, --scores, or pipe JSON to stdin.");
166
+ exit(2);
167
+ }
168
+
169
+ const scores = parseScores(input);
170
+ const result = computeResult(scores);
171
+
172
+ console.log(JSON.stringify(result, null, 2));
173
+ exit(result.pass ? 0 : 1);
174
+ }
175
+
176
+ main();
@@ -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