qualia-framework 3.2.0 → 3.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 (58) hide show
  1. package/CLAUDE.md +3 -4
  2. package/README.md +59 -23
  3. package/agents/plan-checker.md +158 -0
  4. package/agents/planner.md +52 -0
  5. package/agents/research-synthesizer.md +86 -0
  6. package/agents/researcher.md +119 -0
  7. package/agents/roadmapper.md +157 -0
  8. package/agents/verifier.md +180 -32
  9. package/bin/cli.js +403 -9
  10. package/bin/install.js +219 -70
  11. package/bin/qualia-ui.js +11 -11
  12. package/bin/state.js +200 -6
  13. package/bin/statusline.js +4 -4
  14. package/docs/erp-contract.md +161 -0
  15. package/hooks/branch-guard.js +23 -2
  16. package/hooks/migration-guard.js +23 -0
  17. package/hooks/pre-compact.js +20 -0
  18. package/hooks/pre-deploy-gate.js +39 -0
  19. package/hooks/pre-push.js +20 -0
  20. package/hooks/session-start.js +16 -43
  21. package/package.json +6 -4
  22. package/references/questioning.md +123 -0
  23. package/rules/infrastructure.md +87 -0
  24. package/skills/qualia/SKILL.md +1 -0
  25. package/skills/qualia-build/SKILL.md +18 -0
  26. package/skills/qualia-design/SKILL.md +14 -8
  27. package/skills/qualia-discuss/SKILL.md +115 -0
  28. package/skills/qualia-help/SKILL.md +60 -0
  29. package/skills/qualia-learn/SKILL.md +27 -4
  30. package/skills/qualia-map/SKILL.md +145 -0
  31. package/skills/qualia-milestone/SKILL.md +148 -0
  32. package/skills/qualia-new/SKILL.md +374 -229
  33. package/skills/qualia-plan/SKILL.md +135 -30
  34. package/skills/qualia-polish/SKILL.md +167 -117
  35. package/skills/qualia-report/SKILL.md +17 -8
  36. package/skills/qualia-research/SKILL.md +124 -0
  37. package/skills/qualia-review/SKILL.md +126 -41
  38. package/skills/qualia-test/SKILL.md +134 -0
  39. package/skills/qualia-verify/SKILL.md +1 -1
  40. package/templates/DESIGN.md +440 -102
  41. package/templates/help.html +476 -0
  42. package/templates/phase-context.md +48 -0
  43. package/templates/plan.md +14 -0
  44. package/templates/projects/ai-agent.md +55 -0
  45. package/templates/projects/mobile-app.md +56 -0
  46. package/templates/projects/voice-agent.md +55 -0
  47. package/templates/projects/website.md +58 -0
  48. package/templates/requirements.md +69 -0
  49. package/templates/research-project/ARCHITECTURE.md +70 -0
  50. package/templates/research-project/FEATURES.md +60 -0
  51. package/templates/research-project/PITFALLS.md +73 -0
  52. package/templates/research-project/STACK.md +51 -0
  53. package/templates/research-project/SUMMARY.md +86 -0
  54. package/templates/roadmap.md +71 -0
  55. package/tests/bin.test.sh +20 -6
  56. package/tests/hooks.test.sh +76 -7
  57. package/tests/runner.js +1915 -0
  58. package/tests/state.test.sh +189 -11
package/bin/install.js CHANGED
@@ -13,8 +13,11 @@ const YELLOW = "\x1b[38;2;234;179;8m";
13
13
  const RED = "\x1b[38;2;239;68;68m";
14
14
  const RESET = "\x1b[0m";
15
15
 
16
+ const CLAUDE_DIR = path.join(require("os").homedir(), ".claude");
17
+ const FRAMEWORK_DIR = path.resolve(__dirname, "..");
18
+
16
19
  // ─── Team codes ──────────────────────────────────────────
17
- const TEAM = {
20
+ const DEFAULT_TEAM = {
18
21
  "QS-FAWZI-01": {
19
22
  name: "Fawzi Goussous",
20
23
  role: "OWNER",
@@ -23,27 +26,40 @@ const TEAM = {
23
26
  "QS-HASAN-02": {
24
27
  name: "Hasan",
25
28
  role: "EMPLOYEE",
26
- description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
29
+ description: "Developer. Feature branches only. Cannot push to main.",
27
30
  },
28
31
  "QS-MOAYAD-03": {
29
32
  name: "Moayad",
30
33
  role: "EMPLOYEE",
31
- description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
34
+ description: "Developer. Feature branches only. Cannot push to main.",
32
35
  },
33
36
  "QS-RAMA-04": {
34
37
  name: "Rama",
35
38
  role: "EMPLOYEE",
36
- description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
39
+ description: "Developer. Feature branches only. Cannot push to main.",
37
40
  },
38
41
  "QS-SALLY-05": {
39
42
  name: "Sally",
40
43
  role: "EMPLOYEE",
41
- description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
44
+ description: "Developer. Feature branches only. Cannot push to main.",
42
45
  },
43
46
  };
44
47
 
45
- const CLAUDE_DIR = path.join(require("os").homedir(), ".claude");
46
- const FRAMEWORK_DIR = path.resolve(__dirname, "..");
48
+ // Load team from external file, fall back to embedded defaults.
49
+ function loadTeam() {
50
+ const teamFile = path.join(CLAUDE_DIR, ".qualia-team.json");
51
+ try {
52
+ if (fs.existsSync(teamFile)) {
53
+ const external = JSON.parse(fs.readFileSync(teamFile, "utf8"));
54
+ if (external && typeof external === "object" && Object.keys(external).length > 0) {
55
+ return external;
56
+ }
57
+ }
58
+ } catch {}
59
+ return DEFAULT_TEAM;
60
+ }
61
+
62
+ const TEAM = loadTeam();
47
63
 
48
64
  let installed = 0;
49
65
  let errors = 0;
@@ -65,14 +81,96 @@ function copy(src, dest) {
65
81
  fs.copyFileSync(src, dest);
66
82
  }
67
83
 
84
+ // Recursively copy a directory tree from src to dest.
85
+ // Skips hidden files (dot-prefixed) to avoid syncing .DS_Store, editor temp files, etc.
86
+ function copyTree(src, dest) {
87
+ if (!fs.existsSync(src)) return;
88
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
89
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
90
+ if (entry.name.startsWith(".")) continue;
91
+ const srcPath = path.join(src, entry.name);
92
+ const destPath = path.join(dest, entry.name);
93
+ if (entry.isDirectory()) {
94
+ copyTree(srcPath, destPath);
95
+ } else if (entry.isFile()) {
96
+ fs.copyFileSync(srcPath, destPath);
97
+ }
98
+ }
99
+ }
100
+
101
+ // Surgically remove orphaned v2.6 install cruft from ~/.claude/ on upgrade.
102
+ // v2.6 installed a separate ~/.claude/qualia-framework/ directory with workflows/,
103
+ // references/, assets/, bin/qualia-tools.js. v3 doesn't use any of that — it was
104
+ // just never cleaned up. Also removes broken qualia-*.md agents from the v2.6 era
105
+ // that reference /home/qualia/ paths which don't exist.
106
+ function cleanupLegacyV26() {
107
+ const removed = { dirs: [], files: [] };
108
+
109
+ // Remove the entire v2.6 framework leftover directory.
110
+ const v26Dir = path.join(CLAUDE_DIR, "qualia-framework");
111
+ try {
112
+ if (fs.existsSync(v26Dir)) {
113
+ const versionFile = path.join(v26Dir, "VERSION");
114
+ // Safety: only remove if it has the v2.6 shape (VERSION file exists)
115
+ if (fs.existsSync(versionFile)) {
116
+ fs.rmSync(v26Dir, { recursive: true, force: true });
117
+ removed.dirs.push("~/.claude/qualia-framework/ (v2.6 leftover)");
118
+ }
119
+ }
120
+ } catch {}
121
+
122
+ // Remove broken v2.6 agent files that reference /home/qualia/ paths.
123
+ // The canonical v3 agents ship with the framework (planner.md, builder.md, etc.)
124
+ // — scan all qualia-*.md files in agents/ and remove any that contain the
125
+ // /home/qualia/ signature (v2.6 broken absolute paths).
126
+ const agentsDest = path.join(CLAUDE_DIR, "agents");
127
+ try {
128
+ if (fs.existsSync(agentsDest)) {
129
+ for (const name of fs.readdirSync(agentsDest)) {
130
+ if (!name.startsWith("qualia-") || !name.endsWith(".md")) continue;
131
+ const p = path.join(agentsDest, name);
132
+ try {
133
+ const content = fs.readFileSync(p, "utf8");
134
+ if (content.includes("/home/qualia/.claude")) {
135
+ fs.unlinkSync(p);
136
+ removed.files.push(`agents/${name}`);
137
+ }
138
+ } catch {}
139
+ }
140
+ }
141
+ } catch {}
142
+
143
+ return removed;
144
+ }
145
+
146
+ // ─── Branded Header ─────────────────────────────────────
147
+ const BOLD = "\x1b[1m";
148
+ const TEAL_GLOW = "\x1b[38;2;0;170;175m";
149
+ const PKG_VERSION = require("../package.json").version;
150
+ const RULE = "━".repeat(48);
151
+
152
+ function printHeader() {
153
+ console.log("");
154
+ console.log("");
155
+ console.log(` ${TEAL}${BOLD}⬢ Q U A L I A${RESET}`);
156
+ console.log(` ${DIM}${RULE}${RESET}`);
157
+ console.log(` ${WHITE}Framework v${PKG_VERSION}${RESET} ${DIM}·${RESET} ${TEAL_GLOW}Qualia Solutions${RESET}`);
158
+ console.log(` ${DIM}Plan → Build → Verify → Ship${RESET}`);
159
+ console.log(` ${DIM}${RULE}${RESET}`);
160
+ console.log("");
161
+ }
162
+
163
+ function printSection(title) {
164
+ console.log("");
165
+ console.log(` ${TEAL}▸${RESET} ${WHITE}${BOLD}${title}${RESET}`);
166
+ console.log(` ${DIM}${"─".repeat(40)}${RESET}`);
167
+ }
168
+
68
169
  // ─── Prompt for code ─────────────────────────────────────
69
170
  function askCode() {
70
171
  return new Promise((resolve) => {
71
172
  const rl = createInterface({ input: process.stdin, output: process.stdout });
72
- console.log("");
73
- console.log(`${TEAL} ⬢ Qualia Framework v2${RESET}`);
74
- console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
75
- console.log("");
173
+ printHeader();
76
174
  rl.question(` ${WHITE}Enter install code:${RESET} `, (answer) => {
77
175
  rl.close();
78
176
  resolve(answer.trim());
@@ -111,10 +209,9 @@ async function main() {
111
209
  }
112
210
 
113
211
  console.log("");
114
- log(`${GREEN}✓${RESET} ${WHITE}${member.name}${RESET} ${DIM}(${member.role})${RESET}`);
115
- console.log("");
116
- log(`Installing to ${WHITE}${CLAUDE_DIR}${RESET}`);
117
- console.log("");
212
+ const roleColor = member.role === "OWNER" ? TEAL : GREEN;
213
+ console.log(` ${GREEN}✓${RESET} ${WHITE}${BOLD}Welcome, ${member.name}${RESET}`);
214
+ console.log(` ${DIM} Role:${RESET} ${roleColor}${member.role}${RESET} ${DIM}·${RESET} ${DIM}Target:${RESET} ${WHITE}${CLAUDE_DIR}${RESET}`);
118
215
 
119
216
  // ─── Skills ──────────────────────────────────────────
120
217
  const skillsDir = path.join(FRAMEWORK_DIR, "skills");
@@ -122,7 +219,7 @@ async function main() {
122
219
  .readdirSync(skillsDir)
123
220
  .filter((d) => fs.statSync(path.join(skillsDir, d)).isDirectory());
124
221
 
125
- log(`${WHITE}Skills${RESET}`);
222
+ printSection("Skills");
126
223
  for (const skill of skills) {
127
224
  try {
128
225
  copy(
@@ -136,7 +233,7 @@ async function main() {
136
233
  }
137
234
 
138
235
  // ─── Agents ────────────────────────────────────────────
139
- log(`${WHITE}Agents${RESET}`);
236
+ printSection("Agents");
140
237
  const agentsDir = path.join(FRAMEWORK_DIR, "agents");
141
238
  for (const file of fs.readdirSync(agentsDir)) {
142
239
  try {
@@ -148,7 +245,7 @@ async function main() {
148
245
  }
149
246
 
150
247
  // ─── Rules ─────────────────────────────────────────────
151
- log(`${WHITE}Rules${RESET}`);
248
+ printSection("Rules");
152
249
  const rulesDir = path.join(FRAMEWORK_DIR, "rules");
153
250
  for (const file of fs.readdirSync(rulesDir)) {
154
251
  try {
@@ -160,24 +257,26 @@ async function main() {
160
257
  }
161
258
 
162
259
  // ─── Hooks ─────────────────────────────────────────────
163
- log(`${WHITE}Hooks${RESET}`);
260
+ printSection("Hooks");
164
261
  const hooksSource = path.join(FRAMEWORK_DIR, "hooks");
165
262
  const hooksDest = path.join(CLAUDE_DIR, "hooks");
166
263
  if (!fs.existsSync(hooksDest)) fs.mkdirSync(hooksDest, { recursive: true });
167
- // Clean up legacy .sh hooks from previous installs so no orphans
168
- // remain on disk after upgrading to the pure-Node hooks.
169
- // Also purge explicitly-deprecated hooks by name (these are no longer
170
- // shipped in framework/hooks/ but may linger on existing installs).
171
- const DEPRECATED_HOOKS = [
172
- "block-env-edit.js", // v3.2.0: team decision to unblock .env read/write
173
- ];
264
+ // Clean up legacy .sh hooks from previous v2.5/v2.6 installs so no orphans
265
+ // remain on disk after upgrading to the pure-Node v2.7+ hooks.
174
266
  try {
175
267
  for (const f of fs.readdirSync(hooksDest)) {
176
- if (f.endsWith(".sh") || DEPRECATED_HOOKS.includes(f)) {
268
+ if (f.endsWith(".sh")) {
177
269
  try { fs.unlinkSync(path.join(hooksDest, f)); } catch {}
178
270
  }
179
271
  }
180
272
  } catch {}
273
+ // v3.2.0: purge deprecated hooks from existing installs on upgrade.
274
+ // block-env-edit.js was retired — team now has full read/write on .env*.
275
+ const DEPRECATED_HOOKS = ["block-env-edit.js"];
276
+ for (const f of DEPRECATED_HOOKS) {
277
+ const p = path.join(hooksDest, f);
278
+ try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch {}
279
+ }
181
280
  for (const file of fs.readdirSync(hooksSource)) {
182
281
  try {
183
282
  const dest = path.join(hooksDest, file);
@@ -190,32 +289,56 @@ async function main() {
190
289
  }
191
290
  }
192
291
 
193
- // ─── Status line ───────────────────────────────────────
194
- log(`${WHITE}Status line${RESET}`);
195
- try {
196
- const slDest = path.join(CLAUDE_DIR, "bin", "statusline.js");
197
- copy(path.join(FRAMEWORK_DIR, "bin", "statusline.js"), slDest);
198
- ok("statusline.js");
199
- } catch (e) {
200
- warn(`statusline.js — ${e.message}`);
201
- }
202
-
203
- // ─── Templates ─────────────────────────────────────────
204
- log(`${WHITE}Templates${RESET}`);
292
+ // ─── Templates (recursive — supports nested projects/ and research-project/) ─
293
+ printSection("Templates");
205
294
  const tmplDir = path.join(FRAMEWORK_DIR, "templates");
206
295
  const tmplDest = path.join(CLAUDE_DIR, "qualia-templates");
207
296
  if (!fs.existsSync(tmplDest)) fs.mkdirSync(tmplDest, { recursive: true });
208
- for (const file of fs.readdirSync(tmplDir)) {
297
+ for (const entry of fs.readdirSync(tmplDir, { withFileTypes: true })) {
298
+ if (entry.name.startsWith(".")) continue;
299
+ const srcPath = path.join(tmplDir, entry.name);
300
+ const destPath = path.join(tmplDest, entry.name);
209
301
  try {
210
- copy(path.join(tmplDir, file), path.join(tmplDest, file));
211
- ok(file);
302
+ if (entry.isDirectory()) {
303
+ copyTree(srcPath, destPath);
304
+ ok(`${entry.name}/ (directory)`);
305
+ } else {
306
+ copy(srcPath, destPath);
307
+ ok(entry.name);
308
+ }
212
309
  } catch (e) {
213
- warn(`${file} — ${e.message}`);
310
+ warn(`${entry.name} — ${e.message}`);
311
+ }
312
+ }
313
+
314
+ // ─── References (methodology docs loaded by skills at runtime) ────
315
+ printSection("References");
316
+ const refDir = path.join(FRAMEWORK_DIR, "references");
317
+ const refDest = path.join(CLAUDE_DIR, "qualia-references");
318
+ if (fs.existsSync(refDir)) {
319
+ if (!fs.existsSync(refDest)) fs.mkdirSync(refDest, { recursive: true });
320
+ for (const file of fs.readdirSync(refDir)) {
321
+ try {
322
+ copy(path.join(refDir, file), path.join(refDest, file));
323
+ ok(file);
324
+ } catch (e) {
325
+ warn(`${file} — ${e.message}`);
326
+ }
214
327
  }
328
+ } else {
329
+ log(`${DIM}(no references/ in framework — skipping)${RESET}`);
330
+ }
331
+
332
+ // ─── Cleanup legacy v2.6 install cruft ────────────────────
333
+ const legacy = cleanupLegacyV26();
334
+ if (legacy.dirs.length > 0 || legacy.files.length > 0) {
335
+ printSection("Cleanup (v2.6 leftover)");
336
+ for (const d of legacy.dirs) ok(`removed ${d}`);
337
+ for (const f of legacy.files) ok(`removed ${f}`);
215
338
  }
216
339
 
217
340
  // ─── CLAUDE.md with role ───────────────────────────────
218
- log(`${WHITE}CLAUDE.md${RESET}`);
341
+ printSection("Configuration");
219
342
  try {
220
343
  let claudeMd = fs.readFileSync(
221
344
  path.join(FRAMEWORK_DIR, "CLAUDE.md"),
@@ -231,7 +354,7 @@ async function main() {
231
354
  }
232
355
 
233
356
  // ─── Scripts ─────────────────────────────────────────────
234
- log(`${WHITE}Scripts${RESET}`);
357
+ printSection("Scripts");
235
358
  try {
236
359
  const binDest = path.join(CLAUDE_DIR, "bin");
237
360
  if (!fs.existsSync(binDest)) fs.mkdirSync(binDest, { recursive: true });
@@ -267,7 +390,7 @@ async function main() {
267
390
  }
268
391
 
269
392
  // ─── Knowledge directory ─────────────────────────────────
270
- log(`${WHITE}Knowledge${RESET}`);
393
+ printSection("Knowledge Base");
271
394
  const knowledgeDir = path.join(CLAUDE_DIR, "knowledge");
272
395
  if (!fs.existsSync(knowledgeDir)) fs.mkdirSync(knowledgeDir, { recursive: true });
273
396
  const knowledgeFiles = {
@@ -333,7 +456,7 @@ Recurring issues and their solutions.
333
456
  **Symptom:** \`/qualia-ship\` is blocked with "service_role found in client code" for a file that's actually a Server Component (runs server-side only).
334
457
  **Cause:** pre-deploy-gate.js skips files matching \`.server.\` filename pattern OR \`server/\` directory path. If the Server Component is at \`app/admin/page.tsx\` (no .server. marker, not in a server/ dir), the scan flags it.
335
458
  **Workaround:** Rename to \`.server.tsx\` OR move to a \`server/\` subdirectory OR extract the service_role usage into a helper in \`lib/server/\`.
336
- **Framework version:** Known issue; better heuristic planned for a future release.
459
+ **Framework version:** Known issue as of v2.8.1; better heuristic planned for v3.0.
337
460
  `,
338
461
  "client-prefs.md": `# Client Preferences
339
462
 
@@ -368,11 +491,16 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
368
491
  role: member.role,
369
492
  version: require("../package.json").version,
370
493
  installed_at: new Date().toISOString().split("T")[0],
494
+ erp: {
495
+ enabled: true,
496
+ url: "https://portal.qualiasolutions.net",
497
+ api_key_file: ".erp-api-key",
498
+ },
371
499
  };
372
500
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + "\n");
373
501
 
374
502
  // ─── ERP API key (for report uploads) ──────────────────
375
- log(`${WHITE}ERP integration${RESET}`);
503
+ printSection("ERP Integration");
376
504
  const erpKeyFile = path.join(CLAUDE_DIR, ".erp-api-key");
377
505
  if (!fs.existsSync(erpKeyFile)) {
378
506
  fs.writeFileSync(erpKeyFile, "qualia-claude-2026", { mode: 0o600 });
@@ -474,7 +602,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
474
602
  type: "command",
475
603
  if: "Bash(git push*)",
476
604
  command: nodeCmd("branch-guard.js"),
477
- timeout: 10,
605
+ timeout: 5,
478
606
  statusMessage: "⬢ Checking branch permissions...",
479
607
  },
480
608
  {
@@ -521,38 +649,44 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
521
649
  ],
522
650
  };
523
651
 
524
- // Permissions
652
+ // Permissions — no restrictions on env files or branches.
653
+ // Everyone can read/write .env, push to main.
525
654
  if (!settings.permissions) settings.permissions = {};
526
655
  if (!settings.permissions.allow) settings.permissions.allow = [];
527
- if (!settings.permissions.deny) {
528
- settings.permissions.deny = [
529
- "Read(./.env)",
530
- "Read(./.env.*)",
531
- "Read(./secrets/**)",
532
- ];
656
+ if (!settings.permissions.deny) settings.permissions.deny = [];
657
+
658
+ // ─── Optional: next-devtools MCP ─────────────────────────
659
+ // Wire next-devtools-mcp for runtime error visibility in Next.js projects
660
+ if (!settings.mcpServers) settings.mcpServers = {};
661
+ if (!settings.mcpServers["next-devtools"]) {
662
+ settings.mcpServers["next-devtools"] = {
663
+ command: "npx",
664
+ args: ["next-devtools-mcp@0.3.10"],
665
+ disabled: false,
666
+ };
667
+ ok("MCP: next-devtools (runtime error visibility for Next.js projects)");
533
668
  }
534
669
 
535
670
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
536
671
 
537
- ok("Hooks: session-start, auto-update, branch-guard, pre-push, env-block, migration-guard, deploy-gate, pre-compact");
672
+ ok("Hooks: session-start, auto-update, branch-guard, pre-push, migration-guard, deploy-gate, pre-compact");
538
673
  ok("Status line + spinner configured");
539
674
  ok("Environment variables + permissions");
540
675
 
541
676
  // ─── Summary ───────────────────────────────────────────
542
677
  console.log("");
543
- console.log(`${TEAL} ⬢ Installed ✓${RESET}`);
544
- console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
545
- console.log(` ${WHITE}${member.name}${RESET} ${DIM}(${member.role})${RESET}`);
546
- console.log(` Skills: ${WHITE}${skills.length}${RESET}`);
678
+ console.log(` ${DIM}${RULE}${RESET}`);
679
+ console.log(` ${TEAL}${BOLD}⬢ INSTALLED${RESET}`);
680
+ console.log(` ${DIM}${RULE}${RESET}`);
681
+ console.log("");
682
+ console.log(` ${WHITE}${BOLD}${member.name}${RESET} ${DIM}·${RESET} ${roleColor}${member.role}${RESET} ${DIM}·${RESET} ${DIM}v${PKG_VERSION}${RESET}`);
683
+ console.log("");
547
684
  const agentCount = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length;
548
- console.log(` Agents: ${WHITE}${agentCount}${RESET} ${DIM}(planner, builder, verifier, qa-browser)${RESET}`);
549
- console.log(` Hooks: ${WHITE}8${RESET} ${DIM}(session-start, auto-update, branch-guard, pre-push, env-block, migration-guard, deploy-gate, pre-compact)${RESET}`);
550
- console.log(` Rules: ${WHITE}${fs.readdirSync(rulesDir).length}${RESET} ${DIM}(security, frontend, design-reference, deployment)${RESET}`);
551
- console.log(` Scripts: ${WHITE}3${RESET} ${DIM}(state.js, qualia-ui.js, statusline.js)${RESET}`);
552
- console.log(` Knowledge: ${WHITE}3${RESET} ${DIM}(patterns, fixes, client prefs)${RESET}`);
553
- console.log(` Templates: ${WHITE}${fs.readdirSync(tmplDir).length}${RESET}`);
554
- console.log(` Status line: ${GREEN}✓${RESET}`);
555
- console.log(` CLAUDE.md: ${GREEN}✓${RESET} ${DIM}(${member.role})${RESET}`);
685
+ const hookCount = fs.readdirSync(hooksSource).length;
686
+ const ruleCount = fs.readdirSync(rulesDir).length;
687
+ const tmplCount = fs.readdirSync(tmplDir).length;
688
+ console.log(` ${DIM}Skills${RESET} ${TEAL}${skills.length}${RESET} ${DIM}Agents${RESET} ${TEAL}${agentCount}${RESET} ${DIM}Hooks${RESET} ${TEAL}${hookCount}${RESET}`);
689
+ console.log(` ${DIM}Rules${RESET} ${TEAL}${ruleCount}${RESET} ${DIM}Scripts${RESET} ${TEAL}3${RESET} ${DIM}Templates${RESET} ${TEAL}${tmplCount}${RESET}`);
556
690
 
557
691
  if (errors > 0) {
558
692
  console.log("");
@@ -560,7 +694,22 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
560
694
  }
561
695
 
562
696
  console.log("");
563
- console.log(` Restart Claude Code, then type ${TEAL}/qualia${RESET} in any project.`);
697
+ console.log(` ${DIM}${RULE}${RESET}`);
698
+ console.log(` ${WHITE}${BOLD}Quick Start${RESET}`);
699
+ console.log(` ${DIM}${RULE}${RESET}`);
700
+ console.log("");
701
+ console.log(` ${TEAL}1.${RESET} ${WHITE}Restart Claude Code${RESET} ${DIM}(loads new settings)${RESET}`);
702
+ console.log(` ${TEAL}2.${RESET} ${WHITE}cd into any project${RESET} ${DIM}and run${RESET} ${TEAL}claude${RESET}`);
703
+ console.log(` ${TEAL}3.${RESET} ${WHITE}Type${RESET} ${TEAL}${BOLD}/qualia${RESET} ${DIM}— it tells you what to do next${RESET}`);
704
+ console.log("");
705
+ console.log(` ${DIM}New project?${RESET} ${TEAL}/qualia-new${RESET}`);
706
+ console.log(` ${DIM}Quick fix?${RESET} ${TEAL}/qualia-quick${RESET}`);
707
+ console.log(` ${DIM}End of day?${RESET} ${TEAL}/qualia-report${RESET} ${DIM}(mandatory)${RESET}`);
708
+ console.log(` ${DIM}Stuck?${RESET} ${TEAL}/qualia${RESET}`);
709
+ console.log("");
710
+ console.log(` ${DIM}${RULE}${RESET}`);
711
+ console.log(` ${TEAL}${BOLD}Welcome to the future with Qualia.${RESET}`);
712
+ console.log(` ${DIM}${RULE}${RESET}`);
564
713
  console.log("");
565
714
  }
566
715
 
package/bin/qualia-ui.js CHANGED
@@ -249,6 +249,16 @@ function cmdNext(cmd) {
249
249
  console.log("");
250
250
  }
251
251
 
252
+ function cmdEnd(status, nextCmd) {
253
+ console.log("");
254
+ console.log(` ${TEAL}${BOLD}⬢${RESET} ${WHITE}${BOLD}${status || "DONE"}${RESET}`);
255
+ console.log(` ${RULE_DIM}`);
256
+ if (nextCmd) {
257
+ console.log(` ${TEAL}⟶${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${nextCmd}${RESET}`);
258
+ }
259
+ console.log("");
260
+ }
261
+
252
262
  function cmdUpdate(current, latest) {
253
263
  if (!current || !latest) return;
254
264
  console.log("");
@@ -262,16 +272,6 @@ function cmdUpdate(current, latest) {
262
272
  console.log("");
263
273
  }
264
274
 
265
- function cmdEnd(status, nextCmd) {
266
- console.log("");
267
- console.log(` ${TEAL}${BOLD}⬢${RESET} ${WHITE}${BOLD}${status || "DONE"}${RESET}`);
268
- console.log(` ${RULE_DIM}`);
269
- if (nextCmd) {
270
- console.log(` ${TEAL}⟶${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${nextCmd}${RESET}`);
271
- }
272
- console.log("");
273
- }
274
-
275
275
  // ─── Main ────────────────────────────────────────────────
276
276
  const [cmd, ...rest] = process.argv.slice(2);
277
277
  switch (cmd) {
@@ -293,7 +293,7 @@ switch (cmd) {
293
293
  case "update": cmdUpdate(rest[0], rest[1]); break;
294
294
  default:
295
295
  console.error(
296
- `Usage: qualia-ui.js <banner|context|divider|ok|fail|warn|info|spawn|wave|task|done|next|end> [args]`
296
+ `Usage: qualia-ui.js <banner|context|divider|ok|fail|warn|info|spawn|wave|task|done|next|end|update> [args]`
297
297
  );
298
298
  process.exit(1);
299
299
  }