qualia-framework 5.9.1 → 6.2.7

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 (81) hide show
  1. package/AGENTS.md +2 -1
  2. package/CLAUDE.md +2 -1
  3. package/README.md +45 -29
  4. package/agents/builder.md +1 -5
  5. package/agents/plan-checker.md +1 -1
  6. package/agents/planner.md +2 -6
  7. package/agents/qa-browser.md +3 -3
  8. package/agents/roadmapper.md +2 -2
  9. package/agents/verifier.md +7 -9
  10. package/agents/visual-evaluator.md +1 -3
  11. package/bin/cli.js +370 -205
  12. package/bin/erp-retry.js +11 -3
  13. package/bin/install.js +383 -55
  14. package/bin/knowledge-flush.js +25 -13
  15. package/bin/knowledge.js +11 -1
  16. package/bin/project-snapshot.js +293 -0
  17. package/bin/qualia-ui.js +13 -2
  18. package/bin/report-payload.js +137 -0
  19. package/bin/slop-detect.mjs +81 -9
  20. package/bin/state.js +8 -1
  21. package/bin/statusline.js +14 -2
  22. package/docs/archive/CHANGELOG-pre-v4.md +855 -0
  23. package/docs/changelog-v6.html +864 -0
  24. package/docs/ecosystem-operating-model.md +121 -0
  25. package/docs/erp-contract.md +74 -21
  26. package/docs/onboarding.html +2 -2
  27. package/docs/release.md +44 -0
  28. package/docs/reviews/v6.2.1-revival-audit.md +53 -0
  29. package/docs/reviews/v6.2.2-memory-erp-audit.md +41 -0
  30. package/docs/reviews/v6.2.3-erp-id-guard.md +15 -0
  31. package/guide.md +28 -3
  32. package/hooks/auto-update.js +20 -10
  33. package/hooks/branch-guard.js +10 -2
  34. package/hooks/env-empty-guard.js +15 -5
  35. package/hooks/git-guardrails.js +10 -1
  36. package/hooks/migration-guard.js +4 -1
  37. package/hooks/pre-deploy-gate.js +11 -1
  38. package/hooks/pre-push.js +43 -106
  39. package/hooks/session-start.js +22 -14
  40. package/hooks/stop-session-log.js +11 -3
  41. package/hooks/supabase-destructive-guard.js +11 -1
  42. package/hooks/vercel-account-guard.js +12 -3
  43. package/package.json +4 -3
  44. package/qualia-design/design-reference.md +2 -1
  45. package/qualia-design/frontend.md +4 -4
  46. package/rules/one-opinion.md +59 -0
  47. package/rules/trust-boundary.md +35 -0
  48. package/skills/qualia-feature/SKILL.md +5 -5
  49. package/skills/qualia-flush/SKILL.md +5 -7
  50. package/skills/qualia-hook-gen/SKILL.md +1 -1
  51. package/skills/qualia-learn/SKILL.md +1 -0
  52. package/skills/qualia-map/SKILL.md +2 -1
  53. package/skills/qualia-milestone/SKILL.md +2 -2
  54. package/skills/qualia-new/SKILL.md +6 -6
  55. package/skills/qualia-optimize/SKILL.md +1 -1
  56. package/skills/qualia-plan/SKILL.md +1 -1
  57. package/skills/qualia-polish/REFERENCE.md +8 -6
  58. package/skills/qualia-polish/SKILL.md +11 -9
  59. package/skills/qualia-polish/scripts/loop.mjs +18 -6
  60. package/skills/qualia-postmortem/SKILL.md +1 -1
  61. package/skills/qualia-report/SKILL.md +6 -42
  62. package/skills/qualia-road/SKILL.md +17 -5
  63. package/skills/qualia-verify/SKILL.md +3 -3
  64. package/skills/qualia-vibe/SKILL.md +226 -0
  65. package/skills/qualia-vibe/scripts/extract.mjs +141 -0
  66. package/skills/qualia-vibe/scripts/tokens.mjs +342 -0
  67. package/templates/help.html +10 -3
  68. package/templates/knowledge/agents.md +3 -3
  69. package/templates/knowledge/index.md +1 -1
  70. package/templates/tracking.json +3 -0
  71. package/templates/work-packet.md +46 -0
  72. package/tests/bin.test.sh +423 -25
  73. package/tests/hooks.test.sh +1 -8
  74. package/tests/install-smoke.test.sh +137 -0
  75. package/tests/published-install-smoke.test.sh +126 -0
  76. package/tests/refs.test.sh +43 -1
  77. package/tests/run-all.sh +49 -0
  78. package/tests/runner.js +19 -33
  79. package/tests/slop-detect.test.sh +11 -5
  80. package/tests/state.test.sh +4 -1
  81. package/hooks/pre-compact.js +0 -125
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tokens.mjs — read/write design tokens for /qualia-vibe.
4
+ *
5
+ * Commands:
6
+ * tokens.mjs sync --design .planning/DESIGN.md [--write]
7
+ * Diffs CSS vars + Tailwind tokens in code against DESIGN.md.
8
+ * Reports drift; with --write, patches DESIGN.md to match code.
9
+ *
10
+ * tokens.mjs propose-variants --product .planning/PRODUCT.md --design .planning/DESIGN.md --count 3
11
+ * Generates N variant direction briefs from PRODUCT.md context.
12
+ * This script only formats the prompt scaffold; the LLM does the
13
+ * actual creative work and writes the final brief.
14
+ *
15
+ * Exit codes:
16
+ * 0 success (drift report or variants emitted)
17
+ * 1 drift found AND --write not set (informational, not a hard fail)
18
+ * 2 invocation error (missing files, bad flags)
19
+ */
20
+
21
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
22
+ import { join, basename, extname } from "node:path";
23
+ import { argv, exit, cwd } from "node:process";
24
+
25
+ function flag(name, fallback) {
26
+ const i = argv.indexOf(name);
27
+ if (i < 0) return fallback;
28
+ const next = argv[i + 1];
29
+ if (!next || next.startsWith("--")) return true;
30
+ return next;
31
+ }
32
+ function hasFlag(name) {
33
+ return argv.includes(name);
34
+ }
35
+
36
+ const CMD = argv[2];
37
+
38
+ if (!CMD || CMD === "--help" || CMD === "-h") {
39
+ console.log(`tokens.mjs — design-token utility for /qualia-vibe
40
+
41
+ Usage:
42
+ tokens.mjs sync --design <path> [--write]
43
+ tokens.mjs propose-variants --product <path> --design <path> --count <N>
44
+
45
+ See skills/qualia-vibe/SKILL.md.
46
+ `);
47
+ exit(0);
48
+ }
49
+
50
+ // ─── CSS var extraction ───────────────────────────────────────────────
51
+
52
+ const CSS_ROOT_RE = /:root\s*\{([\s\S]*?)\}/g;
53
+ const CSS_VAR_RE = /--([a-z][a-z0-9-]*)\s*:\s*([^;]+);/gi;
54
+
55
+ function extractCssVarsFromFile(path) {
56
+ let content;
57
+ try { content = readFileSync(path, "utf8"); } catch { return []; }
58
+ const vars = [];
59
+ let m;
60
+ CSS_ROOT_RE.lastIndex = 0;
61
+ while ((m = CSS_ROOT_RE.exec(content)) !== null) {
62
+ const block = m[1];
63
+ let v;
64
+ CSS_VAR_RE.lastIndex = 0;
65
+ while ((v = CSS_VAR_RE.exec(block)) !== null) {
66
+ vars.push({ name: v[1], value: v[2].trim(), source: path });
67
+ }
68
+ }
69
+ return vars;
70
+ }
71
+
72
+ function findStyleFiles() {
73
+ const candidates = [
74
+ "app/globals.css", "app/global.css",
75
+ "src/app/globals.css", "src/app/global.css",
76
+ "src/styles/globals.css", "src/styles/global.css",
77
+ "styles/globals.css", "styles/global.css",
78
+ ];
79
+ return candidates.filter((p) => existsSync(p));
80
+ }
81
+
82
+ function findTailwindConfig() {
83
+ for (const name of ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs", "tailwind.config.cjs"]) {
84
+ if (existsSync(name)) return name;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function extractTailwindTokens(configPath) {
90
+ // Cheap parse: grep for color/font/spacing entries inside theme.extend.
91
+ // Not a real JS parser — but it catches the common cases without sandbox risk.
92
+ if (!configPath) return { colors: [], fonts: [], spacing: [] };
93
+ let content;
94
+ try { content = readFileSync(configPath, "utf8"); } catch { return { colors: [], fonts: [], spacing: [] }; }
95
+ const out = { colors: [], fonts: [], spacing: [] };
96
+ // Match: brand: '#abcdef' or brand: "oklch(...)"
97
+ const colorRe = /([a-zA-Z][\w-]*)\s*:\s*['"`]((?:#[0-9a-fA-F]{3,8}|oklch\([^)]+\)|hsl\([^)]+\)|rgb\([^)]+\))[^'"`]*)['"`]/g;
98
+ let m;
99
+ while ((m = colorRe.exec(content)) !== null) {
100
+ out.colors.push({ name: m[1], value: m[2], source: configPath });
101
+ }
102
+ // Match: fontFamily: { serif: ['Fraunces', ...], sans: [...] }
103
+ const fontRe = /fontFamily\s*:\s*\{([\s\S]*?)\}/;
104
+ const fontBlock = fontRe.exec(content);
105
+ if (fontBlock) {
106
+ const entryRe = /([a-zA-Z][\w-]*)\s*:\s*\[([^\]]+)\]/g;
107
+ let f;
108
+ while ((f = entryRe.exec(fontBlock[1])) !== null) {
109
+ const primary = f[2].split(",")[0].trim().replace(/['"`]/g, "");
110
+ out.fonts.push({ name: f[1], primary, source: configPath });
111
+ }
112
+ }
113
+ return out;
114
+ }
115
+
116
+ function extractFontImports() {
117
+ const fonts = new Set();
118
+ // CSS @import url(...)
119
+ for (const f of findStyleFiles()) {
120
+ const content = readFileSync(f, "utf8");
121
+ const importRe = /@import\s+url\(['"]?https?:\/\/fonts\.googleapis\.com\/css2?\?family=([A-Za-z0-9+%]+)/g;
122
+ let m;
123
+ while ((m = importRe.exec(content)) !== null) {
124
+ fonts.add(decodeURIComponent(m[1].replace(/\+/g, " ")));
125
+ }
126
+ }
127
+ // next/font usage in layout files
128
+ for (const layout of ["app/layout.tsx", "app/layout.jsx", "src/app/layout.tsx", "src/app/layout.jsx"]) {
129
+ if (!existsSync(layout)) continue;
130
+ const content = readFileSync(layout, "utf8");
131
+ const nextFontRe = /from\s+['"]next\/font\/google['"][\s\S]{0,400}?import\s*\{\s*([A-Z][a-zA-Z_]*)\s*\}/g;
132
+ // Reverse: import { Fraunces } from 'next/font/google'
133
+ const importRe = /import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]next\/font\/google['"]/g;
134
+ let m;
135
+ while ((m = importRe.exec(content)) !== null) {
136
+ for (const name of m[1].split(",").map((s) => s.trim()).filter(Boolean)) {
137
+ fonts.add(name.replace(/_/g, " "));
138
+ }
139
+ }
140
+ }
141
+ return [...fonts];
142
+ }
143
+
144
+ // ─── DESIGN.md parsing ────────────────────────────────────────────────
145
+
146
+ function readDesignMd(designPath) {
147
+ if (!existsSync(designPath)) {
148
+ console.error(`DESIGN.md not found at ${designPath}`);
149
+ exit(2);
150
+ }
151
+ const content = readFileSync(designPath, "utf8");
152
+ return content;
153
+ }
154
+
155
+ function extractDesignMdTokens(content) {
156
+ const tokens = { vars: [], fonts: [] };
157
+ // CSS-style vars anywhere in DESIGN.md
158
+ const varRe = /--([a-z][a-z0-9-]*)\s*:\s*([^;\n]+);/gi;
159
+ let m;
160
+ while ((m = varRe.exec(content)) !== null) {
161
+ tokens.vars.push({ name: m[1], value: m[2].trim() });
162
+ }
163
+ // Font-family lines
164
+ const fontFamilyRe = /font-family\s*:\s*['"`]?([A-Z][A-Za-z0-9 ]+)['"`]?/g;
165
+ while ((m = fontFamilyRe.exec(content)) !== null) {
166
+ tokens.fonts.push(m[1].trim());
167
+ }
168
+ // Bare font names in §3 (Typography). Match "Font: Fraunces" or "Fraunces" on its own line.
169
+ const namedFontRe = /^\s*(?:Font|font|Family|family)\s*:\s*([A-Z][A-Za-z0-9 ]+)\s*$/gm;
170
+ while ((m = namedFontRe.exec(content)) !== null) {
171
+ tokens.fonts.push(m[1].trim());
172
+ }
173
+ return tokens;
174
+ }
175
+
176
+ // ─── Sync ────────────────────────────────────────────────────────────
177
+
178
+ function cmdSync() {
179
+ const designPath = flag("--design", ".planning/DESIGN.md");
180
+ const write = hasFlag("--write");
181
+ const designContent = readDesignMd(designPath);
182
+ const designTokens = extractDesignMdTokens(designContent);
183
+ const designVarMap = new Map(designTokens.vars.map((v) => [v.name, v.value]));
184
+ const designFonts = new Set(designTokens.fonts.map((f) => f.toLowerCase()));
185
+
186
+ const codeVars = [];
187
+ for (const f of findStyleFiles()) codeVars.push(...extractCssVarsFromFile(f));
188
+ const codeVarMap = new Map();
189
+ for (const v of codeVars) {
190
+ if (!codeVarMap.has(v.name)) codeVarMap.set(v.name, v);
191
+ }
192
+
193
+ const tw = extractTailwindTokens(findTailwindConfig());
194
+ const codeFonts = new Set([
195
+ ...extractFontImports().map((f) => f.toLowerCase()),
196
+ ...tw.fonts.map((f) => f.primary.toLowerCase()),
197
+ ]);
198
+
199
+ const undocumented = []; // in code, not in DESIGN.md
200
+ const orphaned = []; // in DESIGN.md, not in code
201
+ const drifted = []; // both, but value differs
202
+
203
+ for (const [name, v] of codeVarMap) {
204
+ if (!designVarMap.has(name)) {
205
+ undocumented.push({ name, value: v.value, source: v.source });
206
+ } else if (designVarMap.get(name) !== v.value) {
207
+ drifted.push({ name, code: v.value, design: designVarMap.get(name), source: v.source });
208
+ }
209
+ }
210
+ for (const [name, value] of designVarMap) {
211
+ if (!codeVarMap.has(name)) orphaned.push({ name, value });
212
+ }
213
+
214
+ const fontUndocumented = [...codeFonts].filter((f) => !designFonts.has(f));
215
+ const fontOrphaned = [...designFonts].filter((f) => !codeFonts.has(f));
216
+
217
+ // Report
218
+ const report = {
219
+ design_md: designPath,
220
+ style_files: findStyleFiles(),
221
+ tailwind_config: findTailwindConfig(),
222
+ undocumented_vars: undocumented,
223
+ orphaned_vars: orphaned,
224
+ drifted_vars: drifted,
225
+ undocumented_fonts: fontUndocumented,
226
+ orphaned_fonts: fontOrphaned,
227
+ counts: {
228
+ undocumented: undocumented.length + fontUndocumented.length,
229
+ orphaned: orphaned.length + fontOrphaned.length,
230
+ drifted: drifted.length,
231
+ },
232
+ };
233
+
234
+ if (hasFlag("--json")) {
235
+ console.log(JSON.stringify(report, null, 2));
236
+ } else {
237
+ const c = report.counts;
238
+ console.log(`design ↔ code drift report`);
239
+ console.log(` DESIGN.md: ${designPath}`);
240
+ console.log(` Style files: ${report.style_files.join(", ") || "(none found)"}`);
241
+ console.log(` Tailwind config: ${report.tailwind_config || "(none found)"}`);
242
+ console.log(``);
243
+ console.log(` Undocumented in DESIGN.md: ${c.undocumented} (in code, not declared)`);
244
+ for (const v of undocumented.slice(0, 10)) console.log(` --${v.name}: ${v.value} (${v.source})`);
245
+ if (undocumented.length > 10) console.log(` …and ${undocumented.length - 10} more`);
246
+ for (const f of fontUndocumented.slice(0, 5)) console.log(` font: ${f} (loaded but not declared)`);
247
+ console.log(``);
248
+ console.log(` Orphaned in DESIGN.md: ${c.orphaned} (declared, not used in code)`);
249
+ for (const v of orphaned.slice(0, 10)) console.log(` --${v.name}: ${v.value}`);
250
+ if (orphaned.length > 10) console.log(` …and ${orphaned.length - 10} more`);
251
+ for (const f of fontOrphaned.slice(0, 5)) console.log(` font: ${f} (declared but never imported)`);
252
+ console.log(``);
253
+ console.log(` Drifted values: ${c.drifted} (different in code vs DESIGN.md)`);
254
+ for (const d of drifted.slice(0, 10)) {
255
+ console.log(` --${d.name}:`);
256
+ console.log(` code: ${d.code} (${d.source})`);
257
+ console.log(` DESIGN: ${d.design}`);
258
+ }
259
+ if (drifted.length > 10) console.log(` …and ${drifted.length - 10} more`);
260
+ }
261
+
262
+ // Patch
263
+ if (write && (undocumented.length + drifted.length > 0)) {
264
+ let patched = designContent;
265
+ // Append an Updated tokens section with the code-truth values.
266
+ const stamp = new Date().toISOString();
267
+ const section = [
268
+ ``,
269
+ `## §sync — auto-synced from code (${stamp})`,
270
+ ``,
271
+ `<!-- Generated by /qualia-vibe --sync --write. Reflects values actually present in code at sync time. -->`,
272
+ ``,
273
+ "```css",
274
+ `:root {`,
275
+ ...undocumented.map((v) => ` --${v.name}: ${v.value}; /* from ${v.source} */`),
276
+ ...drifted.map((d) => ` --${d.name}: ${d.code}; /* was: ${d.design} */`),
277
+ `}`,
278
+ "```",
279
+ ``,
280
+ ].join("\n");
281
+ patched = patched.trimEnd() + "\n" + section;
282
+ writeFileSync(designPath, patched, "utf8");
283
+ console.log(``);
284
+ console.log(`✓ DESIGN.md patched with ${undocumented.length + drifted.length} token updates`);
285
+ }
286
+
287
+ // Exit code: success if no drift OR --write applied; informational fail otherwise.
288
+ const totalDrift = report.counts.undocumented + report.counts.orphaned + report.counts.drifted;
289
+ if (totalDrift === 0) exit(0);
290
+ if (write) exit(0);
291
+ exit(1);
292
+ }
293
+
294
+ // ─── Variants scaffold ───────────────────────────────────────────────
295
+
296
+ function cmdProposeVariants() {
297
+ const productPath = flag("--product", ".planning/PRODUCT.md");
298
+ const designPath = flag("--design", ".planning/DESIGN.md");
299
+ const count = parseInt(flag("--count", "3"), 10);
300
+ if (!Number.isFinite(count) || count < 2 || count > 5) {
301
+ console.error("--count must be 2..5");
302
+ exit(2);
303
+ }
304
+
305
+ const product = existsSync(productPath) ? readFileSync(productPath, "utf8") : "";
306
+ const design = existsSync(designPath) ? readFileSync(designPath, "utf8") : "";
307
+
308
+ // Emit a structured scaffold the LLM uses to generate variants.
309
+ const scaffold = {
310
+ instruction: `Generate exactly ${count} aesthetic-direction variants for /qualia-vibe --variants. Each variant must be opinionated and concrete — no "modern minimal" hedging. Variants must be meaningfully different from each other AND from the current direction.`,
311
+ context: {
312
+ product_md: product.slice(0, 4000),
313
+ current_direction_lines: design
314
+ .split("\n")
315
+ .filter((l) => /aesthetic\s+direction|direction\s*:/i.test(l))
316
+ .slice(0, 5),
317
+ },
318
+ output_contract: {
319
+ shape: `One variant per block, exactly N=${count} blocks. Per variant: name, direction (1 sentence), color (1 sentence), typography (1 sentence), motion (1 sentence). No prose justification beyond those 4 lines.`,
320
+ },
321
+ rules: [
322
+ "No banned fonts (Inter/Roboto/Arial/Helvetica/system-ui/Space Grotesk/Montserrat/Poppins/Lato/Open Sans).",
323
+ "No purple-blue gradients.",
324
+ "No bounce/elastic easing.",
325
+ `Respect anti-references from PRODUCT.md if present.`,
326
+ "Each variant should be commit-able as-is — concrete enough that /qualia-vibe can apply it without further questions.",
327
+ ],
328
+ };
329
+
330
+ console.log(JSON.stringify(scaffold, null, 2));
331
+ exit(0);
332
+ }
333
+
334
+ // ─── Dispatch ────────────────────────────────────────────────────────
335
+
336
+ switch (CMD) {
337
+ case "sync": cmdSync(); break;
338
+ case "propose-variants": cmdProposeVariants(); break;
339
+ default:
340
+ console.error(`Unknown command: ${CMD}`);
341
+ exit(2);
342
+ }
@@ -297,7 +297,7 @@
297
297
  <div class="header-content">
298
298
  <h1><span>Qualia</span> Framework</h1>
299
299
  <p>Plan, build, verify, ship. The AI-powered workflow for Qualia Solutions.</p>
300
- <div class="version">{{VERSION}} &middot; 28 skills</div>
300
+ <div class="version">{{VERSION}} &middot; 33 skills</div>
301
301
  </div>
302
302
  </div>
303
303
 
@@ -366,6 +366,9 @@
366
366
  <div class="cmd"><span class="cmd-name">/qualia-review</span><span class="cmd-desc">Production audit with scored diagnostics. Runs real commands, scores findings by severity. Trigger on 'review', 'audit', 'code review', 'security check'.</span></div>
367
367
  <div class="cmd"><span class="cmd-name">/qualia-optimize</span><span class="cmd-desc">Deep optimization pass &mdash; reads .planning/ AND codebase to find performance, design, UI, backend, and frontend issues. Spawns parallel specialist agents. Supports --perf, --ui, --backend, --alignment, --fix flags.</span></div>
368
368
  <div class="cmd"><span class="cmd-name">/qualia-test</span><span class="cmd-desc">Generate or run tests for client projects. Trigger on 'write tests', 'add tests', 'test this', 'test coverage'.</span></div>
369
+ <div class="cmd"><span class="cmd-name">/qualia-zoom</span><span class="cmd-desc">Focus on a single file or function with full context (glossary terms, depending callers, ADRs touched). Use when a fresh agent needs surgical context for a tricky area.</span></div>
370
+ <div class="cmd"><span class="cmd-name">/qualia-issues</span><span class="cmd-desc">Break a phase plan into independent vertical-slice GitHub issues with needs-triage label. Externalizes work to the open queue so other sessions or contributors can pull from it.</span></div>
371
+ <div class="cmd"><span class="cmd-name">/qualia-triage</span><span class="cmd-desc">State machine over open GH issues — labels each as needs-triage, needs-info, ready-for-agent, ready-for-human, or wontfix. Pairs with /qualia-issues for the autonomous queue.</span></div>
369
372
  </div>
370
373
  </div>
371
374
 
@@ -376,6 +379,7 @@
376
379
  <div class="commands">
377
380
  <div class="cmd"><span class="cmd-name">/qualia-feature</span><span class="cmd-desc">Auto-scoped single-feature build. Inline for trivia (typo, config), fresh builder spawn for 1-5 file features. Refuses and routes to /qualia-plan for phase-sized work. Flags: --force-spawn, --force-inline.</span></div>
378
381
  <div class="cmd"><span class="cmd-name">/qualia-polish</span><span class="cmd-desc">Design pass, scope-adaptive &mdash; component, route, full app, redesign, critique, quick. Add --loop for the autonomous screenshot &rarr; score &rarr; fix loop.</span></div>
382
+ <div class="cmd"><span class="cmd-name">/qualia-vibe</span><span class="cmd-desc">Fast aesthetic pivot (~3 min) &mdash; swap design tokens (color, type, depth, motion), keep layout untouched. Default proposes ONE direction. Modes: --variants for A/B/C, --extract URL to reverse-engineer DESIGN.md from a reference site, --sync for code &harr; DESIGN.md back-sync.</span></div>
379
383
  </div>
380
384
  </div>
381
385
 
@@ -408,6 +412,7 @@
408
412
  <div class="cmd"><span class="cmd-name">/qualia</span><span class="cmd-desc">Smart router &mdash; reads project state, classifies your situation, tells you the exact next command. Use whenever you're unsure about your next step.</span></div>
409
413
  <div class="cmd"><span class="cmd-name">/qualia-idk</span><span class="cmd-desc">Diagnostic intelligence &mdash; spawns two isolated scans (planning + codebase) in parallel, cross-references against your confusion, explains the situation in plain language with a concrete next step. Use when something feels off or you need to understand what's going on.</span></div>
410
414
  <div class="cmd"><span class="cmd-name">/qualia-help</span><span class="cmd-desc">Open the Qualia Framework reference guide in the browser. A beautiful themed HTML page with all commands, rules, services, and the road.</span></div>
415
+ <div class="cmd"><span class="cmd-name">/qualia-road</span><span class="cmd-desc">Terminal workflow map — Project → Journey → Milestones → Phases → Tasks. Use in headless/SSH sessions or when you want the road in chat instead of the browser.</span></div>
411
416
  </div>
412
417
  </div>
413
418
 
@@ -418,6 +423,8 @@
418
423
  <div class="commands">
419
424
  <div class="cmd"><span class="cmd-name">/qualia-skill-new</span><span class="cmd-desc">Author a new Qualia skill or agent. Generates the SKILL.md, registers it in the right location, and optionally ships to the framework repo.</span></div>
420
425
  <div class="cmd"><span class="cmd-name">/qualia-postmortem</span><span class="cmd-desc">Analyze a verification failure and turn the lesson into a framework improvement.</span></div>
426
+ <div class="cmd"><span class="cmd-name">/qualia-hook-gen</span><span class="cmd-desc">Convert a CLAUDE.md or rules instruction into a deterministic Claude Code pre-tool-use hook. Lets you shrink your instruction budget instead of just hearing the advice.</span></div>
427
+ <div class="cmd"><span class="cmd-name">/zoho-workflow</span><span class="cmd-desc">Internal Qualia Solutions ops — Zoho Invoice and Mail integration via ERP-first routing.</span></div>
421
428
  </div>
422
429
  </div>
423
430
  </section>
@@ -430,7 +437,7 @@
430
437
  <div class="cmd"><span class="cmd-name">qualia-framework install</span><span class="cmd-desc">Install or reinstall the framework.</span></div>
431
438
  <div class="cmd"><span class="cmd-name">qualia-framework update</span><span class="cmd-desc">Update to the latest version.</span></div>
432
439
  <div class="cmd"><span class="cmd-name">qualia-framework version</span><span class="cmd-desc">Show installed version + check for updates.</span></div>
433
- <div class="cmd"><span class="cmd-name">qualia-framework uninstall</span><span class="cmd-desc">Clean removal from ~/.claude/ while preserving user-owned settings.</span></div>
440
+ <div class="cmd"><span class="cmd-name">qualia-framework uninstall</span><span class="cmd-desc">Clean removal from installed Claude/Codex homes while preserving user-owned settings.</span></div>
434
441
  <div class="cmd"><span class="cmd-name">qualia-framework migrate</span><span class="cmd-desc">Upgrade legacy settings.json to the current hook layout.</span></div>
435
442
  <div class="cmd"><span class="cmd-name">qualia-framework analytics</span><span class="cmd-desc">Hook telemetry, verification pass rates, gap cycles.</span></div>
436
443
  <div class="cmd"><span class="cmd-name">qualia-framework set-erp-key</span><span class="cmd-desc">Save and enable the ERP API key.</span></div>
@@ -541,7 +548,7 @@
541
548
  <div class="footer">
542
549
  <strong>Welcome to the future with Qualia.</strong><br>
543
550
  Qualia Solutions &mdash; Nicosia, Cyprus
544
- <span class="footer-version">qualia-framework {{VERSION}} &middot; 28 skills</span>
551
+ <span class="footer-version">qualia-framework {{VERSION}} &middot; 33 skills</span>
545
552
  </div>
546
553
 
547
554
  </body>
@@ -6,11 +6,11 @@ once at session start.
6
6
 
7
7
  ## What's here
8
8
 
9
- `~/.claude/knowledge/` is the project-spanning memory tier. It holds three
9
+ The installed `knowledge/` directory is the project-spanning memory tier. It holds three
10
10
  kinds of files, each with a clear purpose. Treat the structure as a contract.
11
11
 
12
12
  ```
13
- ~/.claude/knowledge/
13
+ knowledge/
14
14
  ├── agents.md ← this file (system overview)
15
15
  ├── index.md ← entry point — start here when answering questions
16
16
  ├── daily-log/
@@ -43,7 +43,7 @@ kinds of files, each with a clear purpose. Treat the structure as a contract.
43
43
  **Do not pretend something is in memory if it is not.** Better to say
44
44
  "INSUFFICIENT EVIDENCE: searched index.md and learned-patterns.md, no entry
45
45
  matches" than to hallucinate a recalled pattern. The grounding protocol
46
- (`~/.claude/rules/grounding.md`) applies here too.
46
+ (`rules/grounding.md`) applies here too.
47
47
 
48
48
  ## Tiers
49
49
 
@@ -1,6 +1,6 @@
1
1
  # Knowledge Index
2
2
 
3
- Entry point for `~/.claude/knowledge/`. When answering a question, **read this
3
+ Entry point for the installed `knowledge/` directory. When answering a question, **read this
4
4
  file first**, then jump to the specific file(s) that match.
5
5
 
6
6
  > Auto-maintained by `/qualia-learn`. Do not hand-edit unless the file is out
@@ -5,6 +5,9 @@
5
5
  "assigned_to": "",
6
6
  "team_id": "",
7
7
  "project_id": "",
8
+ "erp_project_id": "",
9
+ "client_id": "",
10
+ "workspace_id": "",
8
11
  "git_remote": "",
9
12
  "milestone": 1,
10
13
  "milestone_name": "",
@@ -0,0 +1,46 @@
1
+ # Work Packet — {{PROJECT_NAME}}
2
+
3
+ Purpose: ERP-approved context for the next Framework session. This is the handoff from operations/control into Claude Code or Codex execution.
4
+
5
+ ## Source
6
+
7
+ - ERP project:
8
+ - ERP project ID (UUID):
9
+ - Client:
10
+ - Client ID (UUID):
11
+ - Workspace ID (UUID):
12
+ - Git remote:
13
+ - Assigned to:
14
+ - Prepared by:
15
+ - Prepared at:
16
+
17
+ ## Current Truth
18
+
19
+ - Current milestone:
20
+ - Current phase:
21
+ - Open blocker:
22
+ - Latest report:
23
+ - Deadline:
24
+ - Client-visible status:
25
+
26
+ ## Approved Work
27
+
28
+ 1.
29
+ 2.
30
+ 3.
31
+
32
+ ## Guardrails
33
+
34
+ - Do not change deadlines, project status, or client-visible updates without approval.
35
+ - Do not treat chat transcripts as final truth unless the ERP record confirms them.
36
+ - Use Framework `.planning/` for execution state; use ERP as the operational source of truth.
37
+
38
+ ## Memory Context
39
+
40
+ - Relevant client preferences:
41
+ - Reusable lessons:
42
+ - Known mistakes to avoid:
43
+
44
+ ## Next Framework Command
45
+
46
+ `/qualia`