qualia-framework 6.2.7 → 6.2.10

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 (76) hide show
  1. package/README.md +18 -11
  2. package/agents/builder.md +7 -7
  3. package/agents/planner.md +39 -3
  4. package/agents/research-synthesizer.md +1 -1
  5. package/agents/researcher.md +3 -3
  6. package/agents/roadmapper.md +7 -7
  7. package/agents/verifier.md +18 -6
  8. package/agents/visual-evaluator.md +8 -7
  9. package/bin/cli.js +111 -14
  10. package/bin/codex-goal.js +92 -0
  11. package/bin/contract-runner.js +219 -0
  12. package/bin/host-adapters.js +66 -0
  13. package/bin/install.js +171 -124
  14. package/bin/plan-contract.js +99 -2
  15. package/bin/planning-hygiene.js +262 -0
  16. package/bin/runtime-manifest.js +32 -0
  17. package/bin/state-ledger.js +184 -0
  18. package/bin/state.js +299 -20
  19. package/bin/trust-score.js +276 -0
  20. package/docs/onboarding.html +5 -4
  21. package/guide.md +3 -2
  22. package/hooks/pre-deploy-gate.js +27 -0
  23. package/hooks/pre-push.js +19 -0
  24. package/package.json +1 -1
  25. package/qualia-design/design-rubric.md +17 -5
  26. package/qualia-design/frontend.md +5 -1
  27. package/qualia-design/graphics.md +47 -0
  28. package/rules/codex-goal.md +46 -0
  29. package/rules/command-output.md +35 -0
  30. package/skills/qualia/SKILL.md +10 -10
  31. package/skills/qualia-build/SKILL.md +24 -14
  32. package/skills/qualia-debug/SKILL.md +16 -8
  33. package/skills/qualia-discuss/SKILL.md +10 -10
  34. package/skills/qualia-doctor/SKILL.md +140 -0
  35. package/skills/qualia-feature/SKILL.md +27 -21
  36. package/skills/qualia-fix/SKILL.md +216 -0
  37. package/skills/qualia-flush/SKILL.md +9 -9
  38. package/skills/qualia-handoff/SKILL.md +9 -9
  39. package/skills/qualia-help/SKILL.md +3 -3
  40. package/skills/qualia-hook-gen/SKILL.md +1 -1
  41. package/skills/qualia-idk/SKILL.md +4 -4
  42. package/skills/qualia-issues/SKILL.md +2 -2
  43. package/skills/qualia-learn/SKILL.md +10 -10
  44. package/skills/qualia-map/SKILL.md +2 -2
  45. package/skills/qualia-milestone/SKILL.md +15 -15
  46. package/skills/qualia-new/REFERENCE.md +9 -9
  47. package/skills/qualia-new/SKILL.md +14 -14
  48. package/skills/qualia-optimize/REFERENCE.md +1 -1
  49. package/skills/qualia-optimize/SKILL.md +23 -16
  50. package/skills/qualia-pause/SKILL.md +2 -2
  51. package/skills/qualia-plan/SKILL.md +27 -13
  52. package/skills/qualia-polish/REFERENCE.md +14 -14
  53. package/skills/qualia-polish/SKILL.md +64 -19
  54. package/skills/qualia-polish/scripts/loop.mjs +3 -3
  55. package/skills/qualia-polish/scripts/score.mjs +9 -3
  56. package/skills/qualia-postmortem/SKILL.md +9 -9
  57. package/skills/qualia-report/SKILL.md +23 -23
  58. package/skills/qualia-research/SKILL.md +5 -5
  59. package/skills/qualia-resume/SKILL.md +4 -4
  60. package/skills/qualia-review/SKILL.md +28 -12
  61. package/skills/qualia-road/SKILL.md +18 -5
  62. package/skills/qualia-ship/SKILL.md +22 -22
  63. package/skills/qualia-skill-new/SKILL.md +13 -13
  64. package/skills/qualia-test/SKILL.md +5 -5
  65. package/skills/qualia-triage/SKILL.md +1 -1
  66. package/skills/qualia-verify/SKILL.md +37 -23
  67. package/skills/qualia-vibe/SKILL.md +13 -10
  68. package/skills/qualia-vibe/scripts/extract.mjs +1 -1
  69. package/skills/zoho-workflow/SKILL.md +1 -1
  70. package/templates/help.html +12 -10
  71. package/tests/bin.test.sh +35 -5
  72. package/tests/install-smoke.test.sh +23 -3
  73. package/tests/lib.test.sh +290 -0
  74. package/tests/runner.js +3 -0
  75. package/tests/skills.test.sh +4 -4
  76. package/tests/state.test.sh +65 -3
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ // Qualia trust score — compact harness health summary.
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const os = require("os");
7
+ const pc = require("./plan-contract.js");
8
+ const ledger = require("./state-ledger.js");
9
+ const { binFiles } = require("./runtime-manifest.js");
10
+
11
+ const HOMES = [
12
+ { name: "Claude", dir: path.join(os.homedir(), ".claude") },
13
+ { name: "Codex", dir: path.join(os.homedir(), ".codex") },
14
+ ];
15
+
16
+ const REQUIRED_BIN = binFiles();
17
+
18
+ const REQUIRED_HOOKS = [
19
+ "session-start.js", "auto-update.js", "branch-guard.js", "pre-push.js",
20
+ "pre-deploy-gate.js", "migration-guard.js", "git-guardrails.js",
21
+ "stop-session-log.js", "vercel-account-guard.js", "env-empty-guard.js",
22
+ "supabase-destructive-guard.js",
23
+ ];
24
+
25
+ const REQUIRED_DESIGN_FILES = [
26
+ "design-laws.md",
27
+ "design-rubric.md",
28
+ "design-brand.md",
29
+ "design-product.md",
30
+ "design-reference.md",
31
+ "frontend.md",
32
+ "graphics.md",
33
+ ];
34
+
35
+ const REQUIRED_EMPLOYEE_SKILLS = [
36
+ "qualia-doctor",
37
+ "qualia-road",
38
+ "qualia-resume",
39
+ "qualia-pause",
40
+ "qualia-report",
41
+ ];
42
+
43
+ function exists(p) {
44
+ try { return fs.existsSync(p); } catch { return false; }
45
+ }
46
+ function readJson(p) {
47
+ try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
48
+ }
49
+ function readText(p) {
50
+ try { return fs.readFileSync(p, "utf8"); } catch { return ""; }
51
+ }
52
+
53
+ function installedHomes() {
54
+ return HOMES.filter((h) => exists(path.join(h.dir, ".qualia-config.json")));
55
+ }
56
+
57
+ function inspectInstall(homes) {
58
+ if (homes.length === 0) {
59
+ return { status: "fail", score: 0, issues: ["no Qualia install config found"], targets: [] };
60
+ }
61
+ const issues = [];
62
+ for (const home of homes) {
63
+ for (const f of REQUIRED_BIN) {
64
+ if (!exists(path.join(home.dir, "bin", f))) issues.push(`${home.name}: missing bin/${f}`);
65
+ }
66
+ if (home.name === "Claude") {
67
+ if (!exists(path.join(home.dir, "CLAUDE.md"))) issues.push("Claude: missing CLAUDE.md");
68
+ if (!exists(path.join(home.dir, "settings.json"))) issues.push("Claude: missing settings.json");
69
+ } else {
70
+ if (!exists(path.join(home.dir, "AGENTS.md"))) issues.push("Codex: missing AGENTS.md");
71
+ if (!exists(path.join(home.dir, "hooks.json"))) issues.push("Codex: missing hooks.json");
72
+ if (!exists(path.join(home.dir, "config.toml"))) issues.push("Codex: missing config.toml");
73
+ }
74
+ }
75
+ return {
76
+ status: issues.length ? "degraded" : "pass",
77
+ score: issues.length ? Math.max(6, 20 - issues.length * 2) : 20,
78
+ issues,
79
+ targets: homes.map((h) => h.name),
80
+ };
81
+ }
82
+
83
+ function inspectHooks(homes) {
84
+ if (homes.length === 0) return { status: "fail", score: 0, issues: ["no install targets"] };
85
+ const issues = [];
86
+ for (const home of homes) {
87
+ for (const h of REQUIRED_HOOKS) {
88
+ if (!exists(path.join(home.dir, "hooks", h))) issues.push(`${home.name}: missing hooks/${h}`);
89
+ }
90
+ }
91
+ return {
92
+ status: issues.length ? "degraded" : "pass",
93
+ score: issues.length ? Math.max(4, 12 - issues.length) : 12,
94
+ issues,
95
+ };
96
+ }
97
+
98
+ function inspectProject(cwd) {
99
+ const planning = path.join(cwd, ".planning");
100
+ if (!exists(planning)) return { status: "not_applicable", score: 12, issues: [], phase: null };
101
+ const tracking = readJson(path.join(planning, "tracking.json"));
102
+ const stateText = readText(path.join(planning, "STATE.md"));
103
+ const issues = [];
104
+ if (!tracking) issues.push("tracking.json missing or invalid");
105
+ if (!stateText) issues.push("STATE.md missing");
106
+ const phaseMatch = stateText.match(/^Phase:\s*(\d+)/m);
107
+ const statusMatch = stateText.match(/^Status:\s*(.+)$/m);
108
+ const phase = phaseMatch ? Number(phaseMatch[1]) : (tracking && Number(tracking.phase)) || 1;
109
+ if (!phaseMatch && stateText) issues.push("STATE.md phase header missing");
110
+ if (!statusMatch && stateText) issues.push("STATE.md status missing");
111
+ return {
112
+ status: issues.length ? "degraded" : "pass",
113
+ score: issues.length ? Math.max(4, 12 - issues.length * 4) : 12,
114
+ issues,
115
+ phase,
116
+ project_status: statusMatch ? statusMatch[1].trim() : tracking?.status || "",
117
+ };
118
+ }
119
+
120
+ function inspectContracts(cwd, phase) {
121
+ const planning = path.join(cwd, ".planning");
122
+ if (!exists(planning) || !phase) return { status: "not_applicable", score: 18, issues: [] };
123
+ const planPath = path.join(planning, `phase-${phase}-plan.md`);
124
+ const contractPath = path.join(planning, `phase-${phase}-contract.json`);
125
+ if (!exists(planPath)) return { status: "not_applicable", score: 18, issues: [] };
126
+ if (!exists(contractPath)) return { status: "degraded", score: 6, issues: [`phase ${phase}: JSON contract missing`] };
127
+ const loaded = pc.readContractFile(contractPath);
128
+ if (!loaded.ok) return { status: "fail", score: 0, issues: [`phase ${phase}: contract unreadable (${loaded.message || loaded.error})`] };
129
+ const errors = pc.validate(loaded.contract);
130
+ if (errors.length) return { status: "fail", score: 0, issues: [`phase ${phase}: contract invalid (${errors.length} issue(s))`] };
131
+ const drift = pc.checkDrift(contractPath, planPath);
132
+ if (drift.ok && drift.drift) return { status: "fail", score: 0, issues: [`phase ${phase}: contract drifted from plan`] };
133
+ return { status: "pass", score: 18, issues: [], contract: path.relative(cwd, contractPath) };
134
+ }
135
+
136
+ function inspectLedger(cwd) {
137
+ const planning = path.join(cwd, ".planning");
138
+ if (!exists(planning)) return { status: "not_applicable", score: 10, issues: [] };
139
+ const file = ledger.ledgerPath(cwd);
140
+ if (!exists(file)) return { status: "degraded", score: 4, issues: ["state ledger missing"] };
141
+ const result = ledger.validate(cwd);
142
+ if (!result.ok) return { status: "fail", score: 0, issues: result.errors };
143
+ if (result.count === 0) return { status: "degraded", score: 4, issues: ["state ledger empty"] };
144
+ return { status: "pass", score: 10, issues: [], events: result.count };
145
+ }
146
+
147
+ function inspectMemory(homes) {
148
+ if (homes.length === 0) return { status: "fail", score: 0, issues: ["no install targets"] };
149
+ const issues = [];
150
+ for (const home of homes) {
151
+ if (!exists(path.join(home.dir, "knowledge", "index.md"))) issues.push(`${home.name}: missing knowledge/index.md`);
152
+ if (!exists(path.join(home.dir, "knowledge", "agents.md"))) issues.push(`${home.name}: missing knowledge/agents.md`);
153
+ if (!exists(path.join(home.dir, "knowledge", "daily-log"))) issues.push(`${home.name}: missing knowledge/daily-log`);
154
+ }
155
+ return {
156
+ status: issues.length ? "degraded" : "pass",
157
+ score: issues.length ? Math.max(2, 5 - issues.length) : 5,
158
+ issues,
159
+ };
160
+ }
161
+
162
+ function inspectDesign(homes) {
163
+ if (homes.length === 0) return { status: "fail", score: 0, issues: ["no install targets"] };
164
+ const issues = [];
165
+ for (const home of homes) {
166
+ for (const f of REQUIRED_DESIGN_FILES) {
167
+ if (!exists(path.join(home.dir, "qualia-design", f))) issues.push(`${home.name}: missing qualia-design/${f}`);
168
+ }
169
+ if (!exists(path.join(home.dir, "skills", "qualia-polish", "SKILL.md"))) {
170
+ issues.push(`${home.name}: missing qualia-polish skill`);
171
+ }
172
+ if (!exists(path.join(home.dir, "skills", "qualia-vibe", "SKILL.md"))) {
173
+ issues.push(`${home.name}: missing qualia-vibe skill`);
174
+ }
175
+ if (!exists(path.join(home.dir, "agents", home.name === "Codex" ? "visual-evaluator.toml" : "visual-evaluator.md"))) {
176
+ issues.push(`${home.name}: missing visual-evaluator agent`);
177
+ }
178
+ if (!exists(path.join(home.dir, "bin", "slop-detect.mjs"))) {
179
+ issues.push(`${home.name}: missing slop-detect.mjs`);
180
+ }
181
+ }
182
+ return {
183
+ status: issues.length ? "degraded" : "pass",
184
+ score: issues.length ? Math.max(2, 8 - issues.length) : 8,
185
+ issues,
186
+ };
187
+ }
188
+
189
+ function inspectEmployeeExperience(homes) {
190
+ if (homes.length === 0) return { status: "fail", score: 0, issues: ["no install targets"] };
191
+ const issues = [];
192
+ for (const home of homes) {
193
+ for (const skill of REQUIRED_EMPLOYEE_SKILLS) {
194
+ if (!exists(path.join(home.dir, "skills", skill, "SKILL.md"))) issues.push(`${home.name}: missing ${skill} skill`);
195
+ }
196
+ if (!exists(path.join(home.dir, "qualia-guide.md"))) issues.push(`${home.name}: missing qualia-guide.md`);
197
+ if (!exists(path.join(home.dir, "qualia-templates", "help.html"))) issues.push(`${home.name}: missing help.html`);
198
+ }
199
+ return {
200
+ status: issues.length ? "degraded" : "pass",
201
+ score: issues.length ? Math.max(1, 5 - issues.length) : 5,
202
+ issues,
203
+ };
204
+ }
205
+
206
+ function inspectErp(homes) {
207
+ if (homes.length === 0) return { status: "not_applicable", score: 10, issues: [] };
208
+ const issues = [];
209
+ let enabled = false;
210
+ let queueCount = 0;
211
+ for (const home of homes) {
212
+ const cfg = readJson(path.join(home.dir, ".qualia-config.json")) || {};
213
+ if (cfg.erp && cfg.erp.enabled !== false) {
214
+ enabled = true;
215
+ const keyFile = path.join(home.dir, cfg.erp.api_key_file || ".erp-api-key");
216
+ if (!exists(keyFile)) issues.push(`${home.name}: ERP enabled but API key missing`);
217
+ }
218
+ const queue = readJson(path.join(home.dir, ".erp-retry-queue.json"));
219
+ if (Array.isArray(queue)) queueCount += queue.length;
220
+ }
221
+ if (!enabled) return { status: "not_applicable", score: 10, issues: [], queue_count: queueCount };
222
+ if (queueCount > 0) issues.push(`ERP retry queue has ${queueCount} item(s)`);
223
+ return {
224
+ status: issues.length ? "degraded" : "pass",
225
+ score: issues.length ? Math.max(3, 10 - issues.length * 3) : 10,
226
+ issues,
227
+ queue_count: queueCount,
228
+ };
229
+ }
230
+
231
+ function buildTrustScore(cwd = process.cwd()) {
232
+ const homes = installedHomes();
233
+ const install = inspectInstall(homes);
234
+ const hooks = inspectHooks(homes);
235
+ const project = inspectProject(cwd);
236
+ const contracts = inspectContracts(cwd, project.phase);
237
+ const state_ledger = inspectLedger(cwd);
238
+ const memory = inspectMemory(homes);
239
+ const erp = inspectErp(homes);
240
+ const design = inspectDesign(homes);
241
+ const employee_experience = inspectEmployeeExperience(homes);
242
+ const checks = { install, hooks, project, contracts, state_ledger, memory, erp, design, employee_experience };
243
+ const score = Math.max(0, Math.min(100, Object.values(checks).reduce((n, c) => n + (c.score || 0), 0)));
244
+ const failCount = Object.values(checks).filter((c) => c.status === "fail").length;
245
+ const degradedCount = Object.values(checks).filter((c) => c.status === "degraded").length;
246
+ const status = failCount ? "FAIL" : degradedCount ? "DEGRADED" : "PASS";
247
+ return { ok: failCount === 0, status, score, generated_at: new Date().toISOString(), checks };
248
+ }
249
+
250
+ function printHuman(result) {
251
+ console.log(`Trust score: ${result.score}/100 (${result.status})`);
252
+ for (const [name, check] of Object.entries(result.checks)) {
253
+ const label = `${name[0].toUpperCase()}${name.slice(1)}`;
254
+ console.log(`${label}: ${check.status} (${check.score})`);
255
+ for (const issue of check.issues || []) console.log(` - ${issue}`);
256
+ }
257
+ }
258
+
259
+ function main(argv) {
260
+ const result = buildTrustScore(process.cwd());
261
+ if (argv.includes("--json")) console.log(JSON.stringify(result, null, 2));
262
+ else printHuman(result);
263
+ return result.status === "FAIL" ? 1 : 0;
264
+ }
265
+
266
+ module.exports = {
267
+ buildTrustScore,
268
+ inspectInstall,
269
+ inspectProject,
270
+ inspectContracts,
271
+ inspectLedger,
272
+ inspectDesign,
273
+ inspectEmployeeExperience,
274
+ };
275
+
276
+ if (require.main === module) process.exit(main(process.argv));
@@ -496,9 +496,10 @@
496
496
  <div class="kit-group">
497
497
  <h4>Diagnose &amp; fix</h4>
498
498
  <dl>
499
- <dt>/qualia-debug</dt><dd>Investigative debugging. Finds root cause, applies a minimal fix, writes a debug report.</dd>
500
- <dt>/qualia-review</dt><dd>Production audit with severity-scored findings. Run before a big deploy.</dd>
501
- <dt>/qualia-optimize</dt><dd>Deep pass for performance, design, architecture. Spawns parallel specialists.</dd>
499
+ <dt>/qualia-fix</dt><dd>Repair lane. Finds root cause, applies a minimal fix, verifies it, writes a fix report.</dd>
500
+ <dt>/qualia-debug</dt><dd>Investigative debugging. Use when the failure is unclear and you need evidence before repair.</dd>
501
+ <dt>/qualia-review</dt><dd>Read-only production audit with severity-scored findings. Run before a big deploy.</dd>
502
+ <dt>/qualia-optimize</dt><dd>Deep improvement map for performance, design, alignment, and architecture. Spawns parallel specialists.</dd>
502
503
  <dt>/qualia-postmortem</dt><dd>Self-healing layer. After a failed verify, identifies which agent or rule should have caught it and proposes a delta.</dd>
503
504
  </dl>
504
505
  </div>
@@ -521,7 +522,7 @@
521
522
  <div class="kit-group">
522
523
  <h4>Build something small</h4>
523
524
  <dl>
524
- <dt>/qualia-feature</dt><dd>Auto-scoped: inline for trivia (typo, config tweak), fresh builder spawn for 1-5 file features. Refuses and routes to /qualia-plan when scope is phase-sized.</dd>
525
+ <dt>/qualia-feature</dt><dd>Auto-scoped: inline for trivia (copy, config tweak), fresh builder spawn for 1-5 file features. Broken existing behavior routes to /qualia-fix.</dd>
525
526
  </dl>
526
527
  </div>
527
528
  <div class="kit-group">
package/guide.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  **v6.2.2 carries forward.** Framework builds, Memory remembers, ERP operates. ERP work packets can seed Claude/Codex sessions, `/qualia-report` can carry ERP-native IDs, and release verification now has a public `@latest` install smoke.
15
15
 
16
- **v6.2.1 carries forward.** Active docs match the v6.2 no-bot-commit model, the explicit `/qualia-report` ERP contract, the current 33-skill surface, and fail-closed `INSUFFICIENT EVIDENCE` behavior. `tests/refs.test.sh` guards those claims.
16
+ **v6.2.1 carries forward.** Active docs match the v6.2 no-bot-commit model, the explicit `/qualia-report` ERP contract, the current skill surface, and fail-closed `INSUFFICIENT EVIDENCE` behavior. `tests/refs.test.sh` guards those claims.
17
17
 
18
18
  **v6.1.0 ships the design-pivot path you were missing.** New `/qualia-vibe` is fast aesthetic pivot (~3 min): swap design tokens, keep layout. Default proposes ONE direction per `rules/one-opinion.md` (the EventMaster discipline — never give the user a menu). Sub-modes: `--variants N` for the opt-in menu, `--extract URL` reverse-engineers DESIGN.md from a reference site, `--sync` shows code↔DESIGN.md drift and can patch DESIGN.md from code. Slop-detect grew banned fonts (Montserrat/Poppins/Lato/Open Sans) and a `--watch` flag for proactive single-file mode. Several design-surface bugs from v6.0 audit are fixed too (viewport mismatch, slop-detect path resolution in the polish loop, dead `/qualia-design` references, the bounce-easing token that contradicted design-laws).
19
19
 
@@ -25,7 +25,7 @@
25
25
 
26
26
  **v5.9.0 carries forward.** `tests/refs.test.sh` catches dead command references in user-facing surfaces on every release. `bin/erp-retry.js` is a real persistent retry queue for ERP report uploads. Four structured agents (verifier, plan-checker, roadmapper, qa-browser) run on Sonnet for ~40% per-phase cost cut, while builder/planner/researcher/visual-evaluator stay on Opus where the architectural and vision reasoning lives. The verifier downgrades to FAIL on any `INSUFFICIENT EVIDENCE` line.
27
27
 
28
- **Surface is 33 skills.** Use `/qualia-feature` for single-feature work and `/qualia-discuss` in PROJECT MODE for kickoff capture; `/qualia-polish --loop` for the autonomous visual loop; `/qualia-vibe` for fast layout-preserving aesthetic pivots.
28
+ **Surface is 35 installed skills.** Use `/qualia-fix` for broken existing behavior, `/qualia-feature` for new single-feature work, and `/qualia-discuss` in PROJECT MODE for kickoff capture; `/qualia-polish --loop` for the autonomous visual loop; `/qualia-vibe` for fast layout-preserving aesthetic pivots.
29
29
 
30
30
  ## The Road
31
31
 
@@ -91,6 +91,7 @@ Append `--auto` to `/qualia-new` and the framework chains every step:
91
91
  | Issues | `/qualia-issues` | Break a phase plan into vertical-slice GitHub issues |
92
92
  | Triage | `/qualia-triage` | Triage open issues through the ready-for-agent state machine |
93
93
  | Optimize | `/qualia-optimize --deepen` | Find shallow modules; v5.3+ spawns 3 parallel interface-design variants per candidate |
94
+ | Planning hygiene | `qualia-framework planning-hygiene scan` | Detect loose `.planning/` reports/assets before they turn into folder bloat |
94
95
  | Hook gen | `/qualia-hook-gen` | Convert a CLAUDE.md/rules instruction into a deterministic hook (v5.3+) |
95
96
  | Road view | `/qualia-road` | View and navigate journey/milestone/phase status |
96
97
  | Lost? | `/qualia` | Mechanical next-command router |
@@ -22,6 +22,33 @@ function qualiaHome() {
22
22
 
23
23
  const QUALIA_HOME = qualiaHome();
24
24
 
25
+ // Self-filter on the proposed bash command — only act when the user is
26
+ // actually trying to deploy. Claude Code's `if: "Bash(vercel --prod*)"` does
27
+ // substring matching (not glob), so commands that contain the literal text
28
+ // "vercel --prod" inside a for-loop or grep argument trip the gate. Codex
29
+ // ignores the `if` field entirely. Both runtimes therefore need this hook to
30
+ // inspect tool_input.command itself before doing any work.
31
+ //
32
+ // Direct invocation (no stdin — `node pre-deploy-gate.js`) is treated as
33
+ // "run the gate" so the test suite and manual `qualia-framework verify` flows
34
+ // keep working. Only when stdin contains a parseable tool_input.command that
35
+ // does NOT match a deploy pattern do we exit early.
36
+ (function selfFilter() {
37
+ if (process.stdin.isTTY) return; // direct invocation — run full gate
38
+ let command = null; // null = no stdin payload, "" = empty command, "str" = parsed
39
+ try {
40
+ const raw = fs.readFileSync(0, "utf8");
41
+ if (raw) {
42
+ const payload = JSON.parse(raw);
43
+ command = (payload && payload.tool_input && payload.tool_input.command) || "";
44
+ }
45
+ } catch {}
46
+ if (command === null) return; // malformed or empty stdin — run full gate
47
+ if (!/^\s*(npx\s+)?vercel\s+(--prod|deploy\s+--prod)\b/.test(command)) {
48
+ process.exit(0);
49
+ }
50
+ })();
51
+
25
52
  function _trace(hookName, result, extra) {
26
53
  try {
27
54
  const os = require("os");
package/hooks/pre-push.js CHANGED
@@ -40,6 +40,25 @@ const QUALIA_HOME = qualiaHome();
40
40
  const TRACKING = path.join(".planning", "tracking.json");
41
41
  const SHELL = process.platform === "win32";
42
42
 
43
+ // Self-filter — only stamp on actual `git push`. Claude's `if` matcher does
44
+ // substring matching (so unrelated commands with "git push" as a literal
45
+ // argument trip the hook) and Codex ignores the `if` field entirely. Direct
46
+ // invocation (no stdin) falls through to the legacy behavior so tests and
47
+ // manual runs still stamp tracking.json.
48
+ (function selfFilter() {
49
+ if (process.stdin.isTTY) return;
50
+ let command = null;
51
+ try {
52
+ const raw = fs.readFileSync(0, "utf8");
53
+ if (raw) {
54
+ const payload = JSON.parse(raw);
55
+ command = (payload && payload.tool_input && payload.tool_input.command) || "";
56
+ }
57
+ } catch {}
58
+ if (command === null) return;
59
+ if (!/^\s*git\s+push\b/.test(command)) process.exit(0);
60
+ })();
61
+
43
62
  function git(args) {
44
63
  return spawnSync("git", args, { encoding: "utf8", timeout: 5000, shell: SHELL });
45
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "6.2.7",
3
+ "version": "6.2.10",
4
4
  "description": "Claude Code and Codex workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -4,7 +4,7 @@ globs: ["*.tsx", "*.jsx", "*.css", "*.scss", "*.html", "*.vue", "*.svelte"]
4
4
 
5
5
  # Design Rubric
6
6
 
7
- Anchored 1-5 scoring across 8 dimensions. Used by the verifier agent to score frontend phases. Used by `/qualia-polish --critique` for read-only audits.
7
+ Anchored 1-5 scoring across 9 dimensions. Used by the verifier agent to score frontend phases. Used by `/qualia-polish --critique` for read-only audits.
8
8
 
9
9
  ## How to score
10
10
 
@@ -25,9 +25,9 @@ If a vision model is critiquing screenshots, it must score against this rubric.
25
25
 
26
26
  Score only what is scope-relevant. A `/qualia-polish src/components/Button.tsx` review scores Typography, Color, States, Motion, Microcopy. Skip Layout Originality and Container Depth — they're component-internal concerns at most.
27
27
 
28
- A whole-app `/qualia-polish` scores all 8 dimensions across multiple representative routes.
28
+ A whole-app `/qualia-polish` scores all 9 dimensions across multiple representative routes.
29
29
 
30
- ## The 8 dimensions
30
+ ## The 9 dimensions
31
31
 
32
32
  ### 1. Typography
33
33
 
@@ -125,10 +125,22 @@ Evidence: grep for banned phrases, sample of empty/error states
125
125
 
126
126
  Evidence: max DOM nesting depth on cards/panels, grep for `border-left` decorative usage
127
127
 
128
+ ### 9. Visual system & graphics
129
+
130
+ | Score | Criteria |
131
+ |---|---|
132
+ | 1 | Page is only generic cards/text where product, state, data, or brand object should be visible. Or visual is decorative noise unrelated to meaning. |
133
+ | 2 | Simple icon/illustration exists but could fit any project. No product/domain specificity. |
134
+ | 3 | Visual supports the page: screenshot, product image, simple diagram, chart, or meaningful icon system. |
135
+ | 4 | Visual system is custom to the product/domain: annotated diagram, varied chart, bespoke hero composition, real media, or meaningful motion. |
136
+ | 5 | Complex visual is memorable and useful: product-in-context scene, interactive graph/map/timeline, 3D/canvas/WebGL, or data visualization with clear insight and accessible fallback. |
137
+
138
+ Evidence: visual type, file:line references, relationship to PRODUCT.md/CONTEXT.md, reduced-motion or static fallback
139
+
128
140
  ## Aggregate score
129
141
 
130
142
  ```
131
- total = sum of dimension scores (max 40)
143
+ total = sum of dimension scores (max 45)
132
144
  average = total / count_of_scored_dimensions
133
145
  phase_pass = ALL scored dimensions ≥ 3
134
146
  phase_fail = ANY scored dimension < 3
@@ -149,7 +161,7 @@ A phase fails if any dimension is below 3. Same gate as functional verification.
149
161
  | Layout originality | 2 | `app/page.tsx:42-78` is a three-column feature grid in section 2 — first-order slop. Rework. |
150
162
  | ... | ... | ... |
151
163
 
152
- **Aggregate:** 28/40 (avg 3.5)
164
+ **Aggregate:** 32/45 (avg 3.56)
153
165
  **Phase verdict:** FAIL — Layout Originality at 2 blocks the phase.
154
166
  **Fix priority:** Rework section 2. Replace three-column grid with varied-height layout per `design-brand.md` §Layout.
155
167
  ```
@@ -109,10 +109,14 @@ These are Qualia brand standards — mandatory for every frontend component. Not
109
109
  If `.planning/DESIGN.md` exists in the project, it takes precedence over these defaults.
110
110
  Read it before any frontend work. It contains project-specific: palette, typography, spacing, component patterns.
111
111
 
112
+ For redesigns and full-app polish, also read:
113
+ - `qualia-design/design-reference.md` for motion, accessibility, responsive, and performance depth
114
+ - `qualia-design/graphics.md` for complex graphics, charts, canvas, 3D, visual systems, and media rules
115
+
112
116
  ## Qualia design commands
113
117
  - `/qualia-polish` — design pass, scope-adaptive (component / section / app / redesign / critique / quick / loop)
114
118
  - `/qualia-vibe` — fast aesthetic pivot (swap tokens, keep layout) + reverse-engineer from URL + code↔DESIGN.md sync
115
119
  - `/qualia-review` — scored production audit
116
120
 
117
121
  ### Recommended workflow
118
- 1. Build feature → 2. `/qualia-polish` to polish within the current vibe → 3. `/qualia-vibe` when the vibe itself needs to change → 4. `/qualia-polish --loop` for autonomous visual QA → ship.
122
+ 1. Build feature → 2. `/qualia-polish` to polish within the current vibe → 3. `/qualia-vibe` when the vibe itself needs to change → 4. `/qualia-polish --redesign` when the whole surface needs stronger visual concept/graphics → 5. `/qualia-polish --loop` for autonomous visual QA → ship.
@@ -0,0 +1,47 @@
1
+ ---
2
+ globs: ["*.tsx", "*.jsx", "*.css", "*.scss", "*.html", "*.svg", "*.canvas", "*.webgl"]
3
+ ---
4
+
5
+ # Complex Graphics & Visual Systems
6
+
7
+ Loaded by `/qualia-polish --redesign`, `/qualia-polish --loop`, and any polish pass where the page needs stronger visual identity than typography and spacing alone can provide.
8
+
9
+ ## When To Add Complex Visuals
10
+
11
+ Use richer graphics when they clarify the product, reveal state, or create a memorable first-viewport signal:
12
+
13
+ - Product object, venue, person, or service that needs immediate recognition
14
+ - Operational dashboard where relationships, flow, status, or hierarchy matter
15
+ - Brand site whose current page is only text blocks and cards
16
+ - Demo or portfolio page where the work itself should be visible
17
+ - Game, spatial tool, voice/AI workflow, analytics surface, map, timeline, graph, or process flow
18
+
19
+ Do not add complex visuals as decoration. A visual must either explain, orient, compare, or make the brand/product unforgettable.
20
+
21
+ ## Visual Types
22
+
23
+ Pick the strongest type for the job:
24
+
25
+ | Need | Prefer |
26
+ |---|---|
27
+ | Show real product/place/person | Photo, screenshot, generated bitmap, or image search asset |
28
+ | Show relationships | Node graph, map, flow field, timeline, Sankey, chord, matrix |
29
+ | Show data | Custom chart with annotations, small multiples, sparklines, heatmap |
30
+ | Show process | Step path, layered diagram, animated system map |
31
+ | Create brand memory | Bespoke hero composition, editorial collage, product-in-context scene |
32
+ | Need depth/spatiality | Three.js full-bleed scene, CSS 3D, canvas/WebGL |
33
+ | Need icons | One icon family, ideally lucide if available |
34
+
35
+ ## Quality Rules
36
+
37
+ 1. Use real or generated bitmap imagery when the user needs to inspect a real thing. Do not substitute abstract SVG blobs.
38
+ 2. For 3D, use Three.js and verify the canvas is nonblank at desktop and mobile viewports.
39
+ 3. For charts, annotate the insight. A chart without labels or a takeaway is decoration.
40
+ 4. For diagrams, keep text short and legible at 375px width.
41
+ 5. For generated assets, save them under project assets, not `.planning/`.
42
+ 6. For motion, connect animation to meaning: reveal sequence, state change, data transition, or spatial relationship.
43
+ 7. Respect reduced motion. Complex visuals must have a static fallback.
44
+
45
+ ## Redesign Expectation
46
+
47
+ `/qualia-polish --redesign` must consider at least one complex-visual concept for the first viewport. It may reject the concept only if it explains why typography/layout alone is stronger for that product.
@@ -0,0 +1,46 @@
1
+ # Codex /goal integration
2
+
3
+ When this skill spawns a unit of work on **Codex** (not Claude Code), set the thread goal at the start so Codex's native token-budget + status tracking takes over.
4
+
5
+ ## Runtime detection
6
+
7
+ You are on Codex when `~/.codex/` exists and `~/.claude/` is absent or stale. The simplest probe:
8
+
9
+ ```bash
10
+ test -f ~/.codex/AGENTS.md && echo codex || echo claude
11
+ ```
12
+
13
+ If the answer is `claude`, **skip this entire rule** — Claude Code has no equivalent surface and emitting `/goal` text would just be noise.
14
+
15
+ ## How to set the goal
16
+
17
+ 1. Run the helper to produce the objective string + suggested token budget:
18
+
19
+ ```bash
20
+ node ~/.codex/bin/codex-goal.js {scope}
21
+ ```
22
+
23
+ `{scope}` is one of: `phase` · `task` · `feature` · `quick`. Use the scope of the current skill.
24
+
25
+ 2. The output is two lines:
26
+
27
+ ```
28
+ /goal {objective text from STATE.md + ROADMAP.md}
29
+ # token_budget suggestion: {N}
30
+ ```
31
+
32
+ 3. **If the `update_goal` tool is available** to you (Codex exposes it as a model-callable tool), call it directly with:
33
+ - `objective` = the text after `/goal ` on line 1
34
+ - `token_budget` = the integer suggestion on line 2
35
+
36
+ 4. **If `update_goal` is not available**, surface the `/goal` line to the user in your next message and let them paste it. Do not silently skip — the goal-set takes 1 second and is the only way Codex's budget telemetry knows what to track.
37
+
38
+ ## When NOT to set a goal
39
+
40
+ - The user is on Claude Code (no `/goal` surface).
41
+ - A goal is already active for this thread (Codex rejects `update_goal` when one exists — call `thread/goal/get` first if you're using the tool API directly).
42
+ - The work is open-ended exploration with no clear objective (e.g. `/qualia-idk`, `/qualia-discuss`). Goals are for executing a defined scope.
43
+
44
+ ## Why
45
+
46
+ Codex's `thread_goals` table tracks `objective`, `token_budget`, `tokens_used`, and a `status` enum (`active | paused | blocked | usage_limited | budget_limited | complete`). Setting the goal lets the user see burn-vs-budget in the TUI without the framework reinventing it. The token-budget number also makes the model self-aware of how much context it has left for the current unit of work.
@@ -0,0 +1,35 @@
1
+ ---
2
+ alwaysApply: true
3
+ ---
4
+
5
+ # Command Output Contract
6
+
7
+ Every Qualia command must be transparent before, during, and after work. Users should never wonder what the command is doing, what it can change, or what the next step is.
8
+
9
+ ## Required Shape
10
+
11
+ Every command should expose these seven lines or their equivalent:
12
+
13
+ ```text
14
+ Command: /qualia-{name} {mode}
15
+ Scope: {files/routes/project area}
16
+ Intent: {audit|investigate|repair|build|polish|ship|report}
17
+ Mutation: none | planned | active | blocked
18
+ Evidence: {files read, commands run, sources checked}
19
+ Output: {files/reports/commits produced}
20
+ Next: {next command or done}
21
+ ```
22
+
23
+ ## Rules
24
+
25
+ 1. Start with a `qualia-ui.js banner` whenever the command has an existing banner mode.
26
+ 2. State whether the command is read-only or mutating before editing.
27
+ 3. For mutating commands, name the exact files or route family before writing.
28
+ 4. For audit or investigation commands, name the evidence commands and report path.
29
+ 5. For design commands, name the design substrate loaded from `qualia-design/` and `.planning/DESIGN.md`.
30
+ 6. End with a `qualia-ui.js end` next command. If there is no next command, say `done`.
31
+ 7. If the command blocks, say why and route to the smallest useful next command.
32
+
33
+ ## Anti-Pattern
34
+
35
+ Do not answer with only a prose paragraph like "Done, I fixed it." The command must tell the user what happened and where the evidence lives.
@@ -17,7 +17,7 @@ Read project state. Classify your situation. Tell you the exact next command.
17
17
  ### 1. Get State
18
18
 
19
19
  ```bash
20
- node ~/.claude/bin/state.js check 2>/dev/null
20
+ node ${QUALIA_BIN}/state.js check 2>/dev/null
21
21
  ```
22
22
 
23
23
  Also gather context:
@@ -50,8 +50,8 @@ Use the state.js JSON output plus gathered context:
50
50
  | `polished` | status == "polished" | → `/qualia-ship` |
51
51
  | `shipped` | status == "shipped" | → `/qualia-handoff` |
52
52
  | `handed-off` | status == "handed_off" | → `/qualia-report` then done |
53
- | `blocked` | STATE.md lists blockers or same error 3+ times | → Analyze, suggest `/qualia-debug` |
54
- | `bug-loop` | Same files edited 3+ times, user frustrated | → Different approach, `/qualia-debug` |
53
+ | `blocked` | STATE.md lists blockers or same error 3+ times | → Analyze; `/qualia-fix` if expected behavior is known, `/qualia-debug` if not |
54
+ | `bug-loop` | Same files edited 3+ times, user frustrated | → Different approach, `/qualia-fix` if actionable, `/qualia-debug` if evidence is missing |
55
55
  | `need-tests` | User mentions "tests", "coverage", "test this" | → `/qualia-test` |
56
56
 
57
57
  **Employee escalation:** If role is EMPLOYEE and situation is `gap-limit` or `bug-loop`, suggest: "Want to flag this for Fawzi?"
@@ -60,16 +60,16 @@ Use the state.js JSON output plus gathered context:
60
60
 
61
61
  **Clear next step** (use the UI helper — it reads state.js itself):
62
62
  ```bash
63
- node ~/.claude/bin/qualia-ui.js banner router
63
+ node ${QUALIA_BIN}/qualia-ui.js banner router
64
64
  # If a project is loaded, show the journey position first (one-glance orientation)
65
- test -f .planning/JOURNEY.md && node ~/.claude/bin/qualia-ui.js journey-tree .planning/JOURNEY.md
66
- node ~/.claude/bin/qualia-ui.js next "{next_command from state.js}"
65
+ test -f .planning/JOURNEY.md && node ${QUALIA_BIN}/qualia-ui.js journey-tree .planning/JOURNEY.md
66
+ node ${QUALIA_BIN}/qualia-ui.js next "{next_command from state.js}"
67
67
  ```
68
68
 
69
69
  **Ambiguous situation (multiple options):**
70
70
  Print the banner first, then use plain markdown for the options:
71
71
  ```bash
72
- node ~/.claude/bin/qualia-ui.js banner router
72
+ node ${QUALIA_BIN}/qualia-ui.js banner router
73
73
  ```
74
74
  ```
75
75
  ## Where You Are
@@ -87,9 +87,9 @@ node ~/.claude/bin/qualia-ui.js banner router
87
87
 
88
88
  **Blocker detected** (gap-limit, bug-loop, employee escalation):
89
89
  ```bash
90
- node ~/.claude/bin/qualia-ui.js banner router
91
- node ~/.claude/bin/qualia-ui.js fail "{blocker description}"
92
- node ~/.claude/bin/qualia-ui.js warn "Escalate to Fawzi or re-plan from scratch"
90
+ node ${QUALIA_BIN}/qualia-ui.js banner router
91
+ node ${QUALIA_BIN}/qualia-ui.js fail "{blocker description}"
92
+ node ${QUALIA_BIN}/qualia-ui.js warn "Escalate to Fawzi or re-plan from scratch"
93
93
  ```
94
94
 
95
95
  User can respond with a number, "just do it", or natural language.