qualia-framework 2.5.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/CLAUDE.md +63 -0
  2. package/README.md +108 -30
  3. package/agents/builder.md +110 -0
  4. package/agents/planner.md +186 -0
  5. package/agents/qa-browser.md +186 -0
  6. package/agents/verifier.md +369 -0
  7. package/bin/cli.js +706 -417
  8. package/bin/install.js +622 -0
  9. package/bin/qualia-ui.js +284 -0
  10. package/bin/state.js +824 -0
  11. package/bin/statusline.js +252 -0
  12. package/docs/erp-contract.md +161 -0
  13. package/guide.md +63 -0
  14. package/hooks/auto-update.js +117 -0
  15. package/hooks/block-env-edit.js +52 -0
  16. package/hooks/branch-guard.js +68 -0
  17. package/hooks/migration-guard.js +83 -0
  18. package/hooks/pre-compact.js +52 -0
  19. package/hooks/pre-deploy-gate.js +149 -0
  20. package/hooks/pre-push.js +53 -0
  21. package/hooks/session-start.js +126 -0
  22. package/package.json +31 -17
  23. package/rules/design-reference.md +179 -0
  24. package/rules/frontend.md +126 -0
  25. package/rules/infrastructure.md +87 -0
  26. package/skills/qualia/SKILL.md +88 -0
  27. package/skills/qualia-build/SKILL.md +115 -0
  28. package/skills/qualia-debug/SKILL.md +87 -0
  29. package/skills/qualia-design/SKILL.md +99 -0
  30. package/skills/qualia-handoff/SKILL.md +66 -0
  31. package/skills/qualia-help/SKILL.md +60 -0
  32. package/skills/qualia-idk/SKILL.md +8 -0
  33. package/skills/qualia-learn/SKILL.md +111 -0
  34. package/skills/qualia-new/SKILL.md +323 -0
  35. package/skills/qualia-pause/SKILL.md +63 -0
  36. package/skills/qualia-plan/SKILL.md +101 -0
  37. package/skills/qualia-polish/SKILL.md +207 -0
  38. package/skills/qualia-quick/SKILL.md +37 -0
  39. package/skills/qualia-report/SKILL.md +114 -0
  40. package/skills/qualia-resume/SKILL.md +49 -0
  41. package/skills/qualia-review/SKILL.md +161 -0
  42. package/skills/qualia-ship/SKILL.md +90 -0
  43. package/skills/qualia-skill-new/SKILL.md +167 -0
  44. package/skills/qualia-task/SKILL.md +91 -0
  45. package/skills/qualia-test/SKILL.md +134 -0
  46. package/skills/qualia-verify/SKILL.md +113 -0
  47. package/templates/DESIGN.md +475 -0
  48. package/templates/help.html +476 -0
  49. package/templates/plan.md +42 -0
  50. package/templates/project.md +22 -0
  51. package/templates/state.md +27 -0
  52. package/templates/tracking.json +20 -0
  53. package/tests/bin.test.sh +687 -0
  54. package/tests/hooks.test.sh +384 -0
  55. package/tests/runner.js +1956 -0
  56. package/tests/state.test.sh +713 -0
  57. package/tests/statusline.test.sh +243 -0
  58. package/bin/collect-metrics.sh +0 -62
  59. package/framework/.claudeignore +0 -51
  60. package/framework/CLAUDE.md +0 -51
  61. package/framework/MCP_SETUP.md +0 -229
  62. package/framework/agents/architecture-strategist.md +0 -53
  63. package/framework/agents/backend-agent.md +0 -150
  64. package/framework/agents/code-simplicity-reviewer.md +0 -86
  65. package/framework/agents/frontend-agent.md +0 -111
  66. package/framework/agents/kieran-typescript-reviewer.md +0 -96
  67. package/framework/agents/performance-oracle.md +0 -111
  68. package/framework/agents/qualia-codebase-mapper.md +0 -761
  69. package/framework/agents/qualia-debugger.md +0 -1204
  70. package/framework/agents/qualia-executor.md +0 -882
  71. package/framework/agents/qualia-integration-checker.md +0 -424
  72. package/framework/agents/qualia-phase-researcher.md +0 -457
  73. package/framework/agents/qualia-plan-checker.md +0 -700
  74. package/framework/agents/qualia-planner.md +0 -1245
  75. package/framework/agents/qualia-project-researcher.md +0 -603
  76. package/framework/agents/qualia-research-synthesizer.md +0 -200
  77. package/framework/agents/qualia-roadmapper.md +0 -606
  78. package/framework/agents/qualia-verifier.md +0 -686
  79. package/framework/agents/red-team-qa.md +0 -130
  80. package/framework/agents/security-auditor.md +0 -72
  81. package/framework/agents/team-orchestrator.md +0 -229
  82. package/framework/agents/teams/framework-audit-team.md +0 -66
  83. package/framework/agents/teams/full-stack-team.md +0 -48
  84. package/framework/agents/teams/optimize-team.md +0 -53
  85. package/framework/agents/teams/review-team.md +0 -70
  86. package/framework/agents/teams/ship-team.md +0 -86
  87. package/framework/agents/test-agent.md +0 -182
  88. package/framework/hooks/auto-format.sh +0 -54
  89. package/framework/hooks/block-env-edit.sh +0 -42
  90. package/framework/hooks/branch-guard.sh +0 -43
  91. package/framework/hooks/confirm-delete.sh +0 -59
  92. package/framework/hooks/migration-validate.sh +0 -77
  93. package/framework/hooks/notification-speak.sh +0 -16
  94. package/framework/hooks/pre-commit.sh +0 -100
  95. package/framework/hooks/pre-compact.sh +0 -56
  96. package/framework/hooks/pre-deploy-gate.sh +0 -160
  97. package/framework/hooks/qualia-colors.sh +0 -32
  98. package/framework/hooks/retention-cleanup.sh +0 -62
  99. package/framework/hooks/save-session-state.sh +0 -185
  100. package/framework/hooks/session-context-loader.sh +0 -96
  101. package/framework/hooks/session-learn.sh +0 -32
  102. package/framework/hooks/skill-announce.sh +0 -123
  103. package/framework/hooks/tool-error-announce.sh +0 -27
  104. package/framework/install.ps1 +0 -323
  105. package/framework/install.sh +0 -313
  106. package/framework/qualia-framework/VERSION +0 -1
  107. package/framework/qualia-framework/assets/qualia-logo.png +0 -0
  108. package/framework/qualia-framework/bin/collect-metrics.sh +0 -67
  109. package/framework/qualia-framework/bin/generate-report-docx.py +0 -429
  110. package/framework/qualia-framework/bin/qualia-tools.js +0 -2201
  111. package/framework/qualia-framework/bin/qualia-tools.test.js +0 -1054
  112. package/framework/qualia-framework/references/checkpoints.md +0 -775
  113. package/framework/qualia-framework/references/completion-checklists.md +0 -359
  114. package/framework/qualia-framework/references/continuation-format.md +0 -249
  115. package/framework/qualia-framework/references/continuation-prompt.md +0 -97
  116. package/framework/qualia-framework/references/decimal-phase-calculation.md +0 -65
  117. package/framework/qualia-framework/references/design-quality.md +0 -56
  118. package/framework/qualia-framework/references/employee-guide.md +0 -167
  119. package/framework/qualia-framework/references/git-integration.md +0 -254
  120. package/framework/qualia-framework/references/git-planning-commit.md +0 -50
  121. package/framework/qualia-framework/references/model-profile-resolution.md +0 -32
  122. package/framework/qualia-framework/references/model-profiles.md +0 -73
  123. package/framework/qualia-framework/references/phase-argument-parsing.md +0 -61
  124. package/framework/qualia-framework/references/planning-config.md +0 -195
  125. package/framework/qualia-framework/references/questioning.md +0 -141
  126. package/framework/qualia-framework/references/tdd.md +0 -263
  127. package/framework/qualia-framework/references/ui-brand.md +0 -160
  128. package/framework/qualia-framework/references/verification-patterns.md +0 -612
  129. package/framework/qualia-framework/templates/DEBUG.md +0 -159
  130. package/framework/qualia-framework/templates/DESIGN.md +0 -81
  131. package/framework/qualia-framework/templates/UAT.md +0 -247
  132. package/framework/qualia-framework/templates/codebase/architecture.md +0 -255
  133. package/framework/qualia-framework/templates/codebase/concerns.md +0 -310
  134. package/framework/qualia-framework/templates/codebase/conventions.md +0 -307
  135. package/framework/qualia-framework/templates/codebase/integrations.md +0 -280
  136. package/framework/qualia-framework/templates/codebase/stack.md +0 -186
  137. package/framework/qualia-framework/templates/codebase/structure.md +0 -285
  138. package/framework/qualia-framework/templates/codebase/testing.md +0 -480
  139. package/framework/qualia-framework/templates/config.json +0 -35
  140. package/framework/qualia-framework/templates/context.md +0 -283
  141. package/framework/qualia-framework/templates/continue-here.md +0 -78
  142. package/framework/qualia-framework/templates/debug-subagent-prompt.md +0 -91
  143. package/framework/qualia-framework/templates/discovery.md +0 -146
  144. package/framework/qualia-framework/templates/lab-notes.md +0 -16
  145. package/framework/qualia-framework/templates/milestone-archive.md +0 -123
  146. package/framework/qualia-framework/templates/milestone.md +0 -115
  147. package/framework/qualia-framework/templates/phase-prompt.md +0 -567
  148. package/framework/qualia-framework/templates/planner-subagent-prompt.md +0 -117
  149. package/framework/qualia-framework/templates/project.md +0 -184
  150. package/framework/qualia-framework/templates/projects/ai-agent.md +0 -156
  151. package/framework/qualia-framework/templates/projects/mobile-app.md +0 -181
  152. package/framework/qualia-framework/templates/projects/voice-agent.md +0 -134
  153. package/framework/qualia-framework/templates/projects/website.md +0 -137
  154. package/framework/qualia-framework/templates/requirements.md +0 -231
  155. package/framework/qualia-framework/templates/research-project/ARCHITECTURE.md +0 -204
  156. package/framework/qualia-framework/templates/research-project/FEATURES.md +0 -147
  157. package/framework/qualia-framework/templates/research-project/PITFALLS.md +0 -200
  158. package/framework/qualia-framework/templates/research-project/STACK.md +0 -120
  159. package/framework/qualia-framework/templates/research-project/SUMMARY.md +0 -170
  160. package/framework/qualia-framework/templates/research.md +0 -552
  161. package/framework/qualia-framework/templates/roadmap.md +0 -206
  162. package/framework/qualia-framework/templates/state.md +0 -179
  163. package/framework/qualia-framework/templates/summary-complex.md +0 -59
  164. package/framework/qualia-framework/templates/summary-minimal.md +0 -41
  165. package/framework/qualia-framework/templates/summary-standard.md +0 -48
  166. package/framework/qualia-framework/templates/summary.md +0 -246
  167. package/framework/qualia-framework/templates/user-setup.md +0 -311
  168. package/framework/qualia-framework/templates/verification-report.md +0 -322
  169. package/framework/qualia-framework/workflows/add-phase.md +0 -179
  170. package/framework/qualia-framework/workflows/add-todo.md +0 -157
  171. package/framework/qualia-framework/workflows/audit-milestone.md +0 -241
  172. package/framework/qualia-framework/workflows/check-todos.md +0 -176
  173. package/framework/qualia-framework/workflows/complete-milestone.md +0 -858
  174. package/framework/qualia-framework/workflows/diagnose-issues.md +0 -219
  175. package/framework/qualia-framework/workflows/discovery-phase.md +0 -289
  176. package/framework/qualia-framework/workflows/discuss-phase.md +0 -534
  177. package/framework/qualia-framework/workflows/execute-phase.md +0 -559
  178. package/framework/qualia-framework/workflows/execute-plan.md +0 -438
  179. package/framework/qualia-framework/workflows/help.md +0 -470
  180. package/framework/qualia-framework/workflows/insert-phase.md +0 -220
  181. package/framework/qualia-framework/workflows/list-phase-assumptions.md +0 -178
  182. package/framework/qualia-framework/workflows/map-codebase.md +0 -327
  183. package/framework/qualia-framework/workflows/new-milestone.md +0 -363
  184. package/framework/qualia-framework/workflows/new-project.md +0 -982
  185. package/framework/qualia-framework/workflows/pause-work.md +0 -122
  186. package/framework/qualia-framework/workflows/plan-milestone-gaps.md +0 -256
  187. package/framework/qualia-framework/workflows/plan-phase.md +0 -422
  188. package/framework/qualia-framework/workflows/progress.md +0 -389
  189. package/framework/qualia-framework/workflows/quick.md +0 -252
  190. package/framework/qualia-framework/workflows/remove-phase.md +0 -326
  191. package/framework/qualia-framework/workflows/research-phase.md +0 -74
  192. package/framework/qualia-framework/workflows/resume-project.md +0 -306
  193. package/framework/qualia-framework/workflows/set-profile.md +0 -80
  194. package/framework/qualia-framework/workflows/settings.md +0 -145
  195. package/framework/qualia-framework/workflows/transition.md +0 -556
  196. package/framework/qualia-framework/workflows/update.md +0 -197
  197. package/framework/qualia-framework/workflows/verify-phase.md +0 -195
  198. package/framework/qualia-framework/workflows/verify-work.md +0 -625
  199. package/framework/rules/context7.md +0 -14
  200. package/framework/rules/frontend.md +0 -33
  201. package/framework/rules/speed.md +0 -23
  202. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  203. package/framework/scripts/apply-retention.sh +0 -120
  204. package/framework/scripts/bootstrap-pop-os.sh +0 -354
  205. package/framework/scripts/claude-voice +0 -13
  206. package/framework/scripts/cleanup.sh +0 -131
  207. package/framework/scripts/cowork-mode.sh +0 -141
  208. package/framework/scripts/generate-project-claude-md.sh +0 -153
  209. package/framework/scripts/load-test-webhook.js +0 -172
  210. package/framework/scripts/say.py +0 -236
  211. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +0 -167
  212. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +0 -216
  213. package/framework/scripts/speak.py +0 -55
  214. package/framework/scripts/speak.sh +0 -18
  215. package/framework/scripts/status.sh +0 -138
  216. package/framework/scripts/sync-to-framework.sh +0 -65
  217. package/framework/scripts/voice-hotkey.py +0 -227
  218. package/framework/scripts/voice-input.sh +0 -51
  219. package/framework/skills/animate/SKILL.md +0 -202
  220. package/framework/skills/bolder/SKILL.md +0 -144
  221. package/framework/skills/browser-qa/SKILL.md +0 -536
  222. package/framework/skills/clarify/SKILL.md +0 -179
  223. package/framework/skills/client-handoff/SKILL.md +0 -135
  224. package/framework/skills/collab-onboard/SKILL.md +0 -111
  225. package/framework/skills/colorize/SKILL.md +0 -170
  226. package/framework/skills/critique/SKILL.md +0 -126
  227. package/framework/skills/deep-research/SKILL.md +0 -240
  228. package/framework/skills/delight/SKILL.md +0 -329
  229. package/framework/skills/deploy/SKILL.md +0 -261
  230. package/framework/skills/deploy-verify/SKILL.md +0 -377
  231. package/framework/skills/deploy-verify/scripts/canary-check.sh +0 -206
  232. package/framework/skills/deploy-verify/scripts/check-console-errors.js +0 -147
  233. package/framework/skills/deploy-verify/scripts/check-cwv.js +0 -139
  234. package/framework/skills/deploy-verify/scripts/project-detect.sh +0 -84
  235. package/framework/skills/deploy-verify/scripts/verify.sh +0 -548
  236. package/framework/skills/design-quieter/SKILL.md +0 -130
  237. package/framework/skills/distill/SKILL.md +0 -149
  238. package/framework/skills/docs-lookup/SKILL.md +0 -79
  239. package/framework/skills/fcm-notifications/SKILL.md +0 -125
  240. package/framework/skills/financial-ledger/SKILL.md +0 -1039
  241. package/framework/skills/frontend-master/NOTICE.md +0 -4
  242. package/framework/skills/frontend-master/SKILL.md +0 -127
  243. package/framework/skills/frontend-master/reference/color-and-contrast.md +0 -132
  244. package/framework/skills/frontend-master/reference/interaction-design.md +0 -123
  245. package/framework/skills/frontend-master/reference/motion-design.md +0 -99
  246. package/framework/skills/frontend-master/reference/responsive-design.md +0 -114
  247. package/framework/skills/frontend-master/reference/spatial-design.md +0 -100
  248. package/framework/skills/frontend-master/reference/typography.md +0 -131
  249. package/framework/skills/frontend-master/reference/ux-writing.md +0 -107
  250. package/framework/skills/harden/SKILL.md +0 -357
  251. package/framework/skills/i18n-rtl/SKILL.md +0 -752
  252. package/framework/skills/learn/SKILL.md +0 -95
  253. package/framework/skills/memory/SKILL.md +0 -50
  254. package/framework/skills/mobile-expo/SKILL.md +0 -977
  255. package/framework/skills/mobile-expo/references/store-checklist.md +0 -550
  256. package/framework/skills/nestjs-backend/README.md +0 -73
  257. package/framework/skills/nestjs-backend/SKILL.md +0 -446
  258. package/framework/skills/nestjs-backend/references/templates.md +0 -1173
  259. package/framework/skills/normalize/SKILL.md +0 -79
  260. package/framework/skills/onboard/SKILL.md +0 -242
  261. package/framework/skills/openrouter-agent/SKILL.md +0 -922
  262. package/framework/skills/polish/SKILL.md +0 -209
  263. package/framework/skills/pr/SKILL.md +0 -66
  264. package/framework/skills/qualia/SKILL.md +0 -199
  265. package/framework/skills/qualia-add-todo/SKILL.md +0 -68
  266. package/framework/skills/qualia-audit-milestone/SKILL.md +0 -95
  267. package/framework/skills/qualia-check-todos/SKILL.md +0 -55
  268. package/framework/skills/qualia-complete-milestone/SKILL.md +0 -134
  269. package/framework/skills/qualia-debug/SKILL.md +0 -149
  270. package/framework/skills/qualia-design/SKILL.md +0 -203
  271. package/framework/skills/qualia-discuss-phase/SKILL.md +0 -72
  272. package/framework/skills/qualia-evolve/SKILL.md +0 -200
  273. package/framework/skills/qualia-execute-phase/SKILL.md +0 -89
  274. package/framework/skills/qualia-framework-audit/SKILL.md +0 -604
  275. package/framework/skills/qualia-guide/SKILL.md +0 -32
  276. package/framework/skills/qualia-help/SKILL.md +0 -114
  277. package/framework/skills/qualia-idk/SKILL.md +0 -352
  278. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +0 -67
  279. package/framework/skills/qualia-new-milestone/SKILL.md +0 -72
  280. package/framework/skills/qualia-new-project/SKILL.md +0 -232
  281. package/framework/skills/qualia-optimize/SKILL.md +0 -417
  282. package/framework/skills/qualia-pause-work/SKILL.md +0 -96
  283. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +0 -57
  284. package/framework/skills/qualia-plan-phase/SKILL.md +0 -104
  285. package/framework/skills/qualia-production-check/SKILL.md +0 -0
  286. package/framework/skills/qualia-progress/SKILL.md +0 -53
  287. package/framework/skills/qualia-quick/SKILL.md +0 -89
  288. package/framework/skills/qualia-report/SKILL.md +0 -166
  289. package/framework/skills/qualia-research-phase/SKILL.md +0 -88
  290. package/framework/skills/qualia-resume-work/SKILL.md +0 -62
  291. package/framework/skills/qualia-review/SKILL.md +0 -263
  292. package/framework/skills/qualia-start/SKILL.md +0 -161
  293. package/framework/skills/qualia-verify-work/SKILL.md +0 -132
  294. package/framework/skills/rag/SKILL.md +0 -750
  295. package/framework/skills/responsive/SKILL.md +0 -231
  296. package/framework/skills/retro/SKILL.md +0 -284
  297. package/framework/skills/sakani-conventions/SKILL.md +0 -136
  298. package/framework/skills/sakani-conventions/evals/evals.json +0 -23
  299. package/framework/skills/sakani-conventions/references/entities.md +0 -365
  300. package/framework/skills/sakani-conventions/references/error-codes.md +0 -95
  301. package/framework/skills/seo-master/SKILL.md +0 -490
  302. package/framework/skills/seo-master/references/checklist.md +0 -199
  303. package/framework/skills/seo-master/references/structured-data.md +0 -609
  304. package/framework/skills/ship/SKILL.md +0 -239
  305. package/framework/skills/stack-researcher/SKILL.md +0 -215
  306. package/framework/skills/status/SKILL.md +0 -154
  307. package/framework/skills/status/scripts/health-check.sh +0 -562
  308. package/framework/skills/subscription-payments/SKILL.md +0 -250
  309. package/framework/skills/supabase/SKILL.md +0 -973
  310. package/framework/skills/supabase/references/templates.md +0 -159
  311. package/framework/skills/team/SKILL.md +0 -67
  312. package/framework/skills/test-runner/SKILL.md +0 -202
  313. package/framework/skills/voice-agent/SKILL.md +0 -1312
  314. package/framework/skills/zoho-workflow/SKILL.md +0 -51
  315. package/framework/statusline-command.sh +0 -117
  316. package/framework/teams/default/inboxes/plan-04.json +0 -9
  317. package/framework/teams/review-team.md +0 -75
  318. package/framework/teams/ship-team.md +0 -86
  319. package/profiles/fawzi.json +0 -16
  320. package/profiles/hasan.json +0 -16
  321. package/profiles/moayad.json +0 -16
  322. package/templates/CLAUDE-owner.md +0 -52
  323. package/templates/CLAUDE.md.hbs +0 -58
  324. package/templates/env.claude.template +0 -12
  325. package/templates/settings.json +0 -172
  326. /package/{framework/rules → rules}/deployment.md +0 -0
  327. /package/{framework/rules → rules}/security.md +0 -0
package/bin/cli.js CHANGED
@@ -1,524 +1,813 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const readline = require('readline');
6
- const { execSync } = require('child_process');
7
-
8
- const CLAUDE_DIR = path.join(require('os').homedir(), '.claude');
9
- const PKG_ROOT = path.resolve(__dirname, '..');
10
- const FRAMEWORK_DIR = path.join(PKG_ROOT, 'framework');
11
- const PROFILES_DIR = path.join(PKG_ROOT, 'profiles');
12
- const TEMPLATES_DIR = path.join(PKG_ROOT, 'templates');
13
-
14
- // ── Colors ──
15
- const c = {
16
- reset: '\x1b[0m',
17
- bold: '\x1b[1m',
18
- dim: '\x1b[2m',
19
- green: '\x1b[32m',
20
- yellow: '\x1b[33m',
21
- red: '\x1b[31m',
22
- cyan: '\x1b[36m',
23
- magenta: '\x1b[35m',
24
- };
25
-
26
- function log(msg) { console.log(msg); }
27
- function ok(msg) { log(` ${c.green}\u2713${c.reset} ${msg}`); }
28
- function warn(msg) { log(` ${c.yellow}\u26A0${c.reset} ${msg}`); }
29
- function fail(msg) { log(` ${c.red}\u2717${c.reset} ${msg}`); }
30
- function step(n, total, msg) { log(`\n${c.cyan}[${n}/${total}]${c.reset} ${msg}`); }
31
-
32
- // ── Helpers ──
33
- function copyDirSync(src, dest) {
34
- fs.mkdirSync(dest, { recursive: true });
35
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
36
- const srcPath = path.join(src, entry.name);
37
- const destPath = path.join(dest, entry.name);
38
- if (entry.isDirectory()) {
39
- copyDirSync(srcPath, destPath);
40
- } else {
41
- fs.copyFileSync(srcPath, destPath);
42
- }
3
+ const { spawnSync } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const readline = require("readline");
7
+
8
+ const TEAL = "\x1b[38;2;0;206;209m";
9
+ const TG = "\x1b[38;2;0;170;175m";
10
+ const DIM = "\x1b[38;2;80;90;100m";
11
+ const GREEN = "\x1b[38;2;52;211;153m";
12
+ const WHITE = "\x1b[38;2;220;225;230m";
13
+ const YELLOW = "\x1b[38;2;234;179;8m";
14
+ const RED = "\x1b[38;2;239;68;68m";
15
+ const RESET = "\x1b[0m";
16
+ const BOLD = "\x1b[1m";
17
+
18
+ const CLAUDE_DIR = path.join(require("os").homedir(), ".claude");
19
+ const PKG = require("../package.json");
20
+ const CONFIG_FILE = path.join(CLAUDE_DIR, ".qualia-config.json");
21
+
22
+ function readConfig() {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
25
+ } catch {
26
+ return {};
43
27
  }
44
28
  }
45
29
 
46
- function countDir(dir, type) {
47
- if (!fs.existsSync(dir)) return 0;
48
- const entries = fs.readdirSync(dir, { withFileTypes: true });
49
- if (type === 'dirs') return entries.filter(e => e.isDirectory()).length;
50
- return entries.filter(e => e.isFile()).length;
30
+ function writeConfig(cfg) {
31
+ if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
32
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
51
33
  }
52
34
 
53
- function deepMerge(target, source) {
54
- const result = { ...target };
55
- for (const key of Object.keys(source)) {
56
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])
57
- && target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
58
- result[key] = deepMerge(target[key], source[key]);
59
- } else if (result[key] === undefined || result[key] === null || result[key] === '') {
60
- result[key] = source[key];
61
- }
62
- }
63
- return result;
35
+ function banner() {
36
+ console.log("");
37
+ console.log(` ${TEAL}${BOLD}⬢${RESET} ${WHITE}${BOLD}Qualia Framework${RESET} ${DIM}v${PKG.version}${RESET}`);
38
+ console.log(` ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
64
39
  }
65
40
 
66
- function loadProfiles() {
67
- const profiles = {};
68
- for (const file of fs.readdirSync(PROFILES_DIR)) {
69
- if (!file.endsWith('.json')) continue;
70
- const profile = JSON.parse(fs.readFileSync(path.join(PROFILES_DIR, file), 'utf8'));
71
- profiles[profile.code.toUpperCase()] = profile;
72
- }
73
- return profiles;
74
- }
41
+ // ─── Commands ────────────────────────────────────────────
75
42
 
76
- function renderCLAUDEmd(profile) {
77
- let template = fs.readFileSync(path.join(TEMPLATES_DIR, 'CLAUDE.md.hbs'), 'utf8');
78
-
79
- const isOwner = profile.role === 'OWNER';
80
- const isDeveloper = profile.role === 'DEVELOPER';
81
- const isArabic = profile.language && profile.language.includes('ar');
43
+ function cmdInstall() {
44
+ require("./install.js");
45
+ }
82
46
 
83
- const roleTitle = isOwner ? 'Founder' : (profile.type === 'full-time' ? 'Full-time Developer' : 'Developer');
47
+ function cmdVersion() {
48
+ banner();
49
+ const cfg = readConfig();
84
50
 
85
- const replacements = {
86
- '{{fullName}}': profile.fullName || profile.name,
87
- '{{name}}': profile.name,
88
- '{{role}}': profile.role,
89
- '{{roleTitle}}': roleTitle,
90
- '{{location}}': profile.location || '',
91
- };
51
+ console.log(` ${DIM}Installed:${RESET} ${WHITE}${PKG.version}${RESET}`);
52
+ if (cfg.installed_by) {
53
+ console.log(` ${DIM}User:${RESET} ${WHITE}${cfg.installed_by}${RESET} ${DIM}(${cfg.role})${RESET}`);
54
+ }
55
+ if (cfg.installed_at) {
56
+ console.log(` ${DIM}Date:${RESET} ${WHITE}${cfg.installed_at}${RESET}`);
57
+ }
92
58
 
93
- for (const [key, val] of Object.entries(replacements)) {
94
- template = template.replace(new RegExp(escapeRegex(key), 'g'), val);
59
+ // Check for updates
60
+ try {
61
+ // spawnSync with argv — no bash-only `2>/dev/null` redirect, no shell
62
+ // interpolation. stdio: "ignore" on stderr silences any npm warnings
63
+ // (offline, proxy, etc.) without a shell redirect. shell: true on
64
+ // Windows because `npm` is a .cmd shim that only resolves through cmd.
65
+ const r = spawnSync("npm", ["view", "qualia-framework", "version"], {
66
+ stdio: ["ignore", "pipe", "ignore"],
67
+ shell: process.platform === "win32",
68
+ timeout: 5000,
69
+ encoding: "utf8",
70
+ });
71
+ const latest = (r.stdout || "").trim();
72
+ const semverGt = (a, b) => {
73
+ const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
74
+ for (let i = 0; i < 3; i++) { if (pa[i] > pb[i]) return true; if (pa[i] < pb[i]) return false; }
75
+ return false;
76
+ };
77
+ if (latest && semverGt(latest, PKG.version)) {
78
+ console.log("");
79
+ console.log(` ${YELLOW}Update available:${RESET} ${WHITE}${latest}${RESET}`);
80
+ console.log(` ${DIM}Run:${RESET} npx qualia-framework update`);
81
+ } else if (latest) {
82
+ console.log(` ${DIM}Latest:${RESET} ${GREEN}${latest} ✓${RESET} ${DIM}(up to date)${RESET}`);
83
+ }
84
+ } catch {
85
+ console.log(` ${DIM}Latest:${RESET} ${DIM}(offline — couldn't check)${RESET}`);
95
86
  }
87
+ console.log("");
88
+ }
96
89
 
97
- // Handle conditionals — inner first, then outer
98
- template = processConditional(template, 'canDeploy', profile.permissions?.canDeploy);
99
- template = processConditional(template, 'canAccessAllProjects', profile.permissions?.canAccessAllProjects);
100
- template = processConditional(template, 'isOwner', isOwner);
101
- template = processConditional(template, 'isDeveloper', isDeveloper);
102
- template = processConditional(template, 'isArabic', isArabic);
90
+ function cmdUpdate() {
91
+ banner();
92
+ const cfg = readConfig();
103
93
 
104
- // Clean up empty lines
105
- template = template.replace(/\n{3,}/g, '\n\n');
94
+ if (!cfg.code) {
95
+ console.log(` ${RED}✗${RESET} No install code saved. Run ${TEAL}install${RESET} first.`);
96
+ console.log("");
97
+ process.exit(1);
98
+ }
106
99
 
107
- return template;
108
- }
100
+ console.log(` ${DIM}Current:${RESET} ${WHITE}${PKG.version}${RESET}`);
101
+ console.log(` ${DIM}Updating...${RESET}`);
102
+ console.log("");
109
103
 
110
- function escapeRegex(str) {
111
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
104
+ try {
105
+ const r = spawnSync("npx", ["qualia-framework@latest", "install"], {
106
+ input: cfg.code + "\n",
107
+ stdio: ["pipe", "inherit", "inherit"],
108
+ shell: process.platform === "win32", // npx is a .cmd shim on Windows — must go through shell
109
+ timeout: 120000,
110
+ encoding: "utf8",
111
+ });
112
+ if (r.status !== 0) {
113
+ console.log(` ${RED}✗${RESET} Update failed. Run manually: npx qualia-framework@latest install`);
114
+ process.exit(1);
115
+ }
116
+ } catch (e) {
117
+ console.log(` ${RED}✗${RESET} Update failed: ${e.message}`);
118
+ console.log(` ${DIM}Run manually:${RESET} npx qualia-framework@latest install`);
119
+ process.exit(1);
120
+ }
112
121
  }
113
122
 
114
- function processConditional(text, name, value) {
115
- const ifRegex = new RegExp(`\\{\\{#if ${name}\\}\\}([\\s\\S]*?)(?:\\{\\{else\\}\\}([\\s\\S]*?))?\\{\\{/if\\}\\}`, 'g');
116
- return text.replace(ifRegex, (_, ifBlock, elseBlock) => {
117
- return value ? ifBlock : (elseBlock || '');
123
+ // ─── Uninstall ───────────────────────────────────────────
124
+ // Surgical removal of the Qualia Framework from ~/.claude/.
125
+ // Preserves CLAUDE.md (user may have customized it) and preserves any
126
+ // non-Qualia entries in settings.json (other hooks, user env vars, etc.).
127
+ // --yes / -y skips the confirmation prompt for scripted use.
128
+
129
+ // 8 Qualia hook filenames — only these are removed from ~/.claude/hooks/,
130
+ // any other hooks the user dropped in there are left alone.
131
+ const QUALIA_HOOK_FILES = [
132
+ "session-start.js",
133
+ "auto-update.js",
134
+ "branch-guard.js",
135
+ "pre-push.js",
136
+ "block-env-edit.js",
137
+ "migration-guard.js",
138
+ "pre-deploy-gate.js",
139
+ "pre-compact.js",
140
+ ];
141
+
142
+ // 4 Qualia agents — only these are removed.
143
+ const QUALIA_AGENT_FILES = ["planner.md", "builder.md", "verifier.md", "qa-browser.md"];
144
+
145
+ // 3 Qualia bin scripts.
146
+ const QUALIA_BIN_FILES = ["state.js", "qualia-ui.js", "statusline.js"];
147
+
148
+ // 5 Qualia rules.
149
+ const QUALIA_RULE_FILES = ["security.md", "frontend.md", "design-reference.md", "deployment.md", "infrastructure.md"];
150
+
151
+ function promptYesNo(question, defaultYes) {
152
+ return new Promise((resolve) => {
153
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
154
+ const suffix = defaultYes ? " (Y/n)" : " (y/N)";
155
+ rl.question(` ${WHITE}${question}${RESET}${suffix} `, (answer) => {
156
+ rl.close();
157
+ const a = String(answer || "").trim().toLowerCase();
158
+ if (!a) return resolve(defaultYes);
159
+ resolve(a === "y" || a === "yes");
160
+ });
118
161
  });
119
162
  }
120
163
 
121
- function mergeSettings(existingPath, templatePath) {
122
- const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
164
+ function safeUnlink(p, counters) {
165
+ try {
166
+ if (fs.existsSync(p)) {
167
+ fs.unlinkSync(p);
168
+ counters.filesRemoved++;
169
+ }
170
+ } catch (e) {
171
+ counters.errors.push(`${p}: ${e.message}`);
172
+ }
173
+ }
123
174
 
124
- if (!fs.existsSync(existingPath)) return template;
175
+ function safeRmDir(p, counters) {
176
+ try {
177
+ if (fs.existsSync(p)) {
178
+ fs.rmSync(p, { recursive: true, force: true });
179
+ counters.dirsRemoved++;
180
+ }
181
+ } catch (e) {
182
+ counters.errors.push(`${p}: ${e.message}`);
183
+ }
184
+ }
125
185
 
126
- const existing = JSON.parse(fs.readFileSync(existingPath, 'utf8'));
186
+ function cleanSettingsJson(counters) {
187
+ const settingsPath = path.join(CLAUDE_DIR, "settings.json");
188
+ if (!fs.existsSync(settingsPath)) return;
189
+ let settings;
190
+ try {
191
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
192
+ } catch (e) {
193
+ counters.errors.push(`settings.json: ${e.message}`);
194
+ return;
195
+ }
196
+
197
+ // Only remove entries that point at qualia paths. Leave everything else.
198
+ const isQualiaCommand = (cmd) =>
199
+ typeof cmd === "string" && (cmd.includes("qualia") || cmd.includes(".claude/hooks/") || cmd.includes(".claude/bin/"));
200
+
201
+ const filterHookArray = (arr) => {
202
+ if (!Array.isArray(arr)) return arr;
203
+ return arr
204
+ .map((entry) => {
205
+ if (!entry || !Array.isArray(entry.hooks)) return entry;
206
+ const hooks = entry.hooks.filter((h) => !isQualiaCommand(h && h.command));
207
+ return { ...entry, hooks };
208
+ })
209
+ .filter((entry) => Array.isArray(entry.hooks) && entry.hooks.length > 0);
210
+ };
127
211
 
128
- // Preserve existing env values (API keys), merge in new ones
129
- const merged = { ...template };
130
- if (existing.env) {
131
- merged.env = { ...template.env };
132
- for (const [key, val] of Object.entries(existing.env)) {
133
- if (val && val !== '' && !val.startsWith('$')) {
134
- merged.env[key] = val;
212
+ if (settings.hooks && typeof settings.hooks === "object") {
213
+ for (const key of ["SessionStart", "PreToolUse", "PreCompact"]) {
214
+ if (settings.hooks[key]) {
215
+ const cleaned = filterHookArray(settings.hooks[key]);
216
+ if (cleaned && cleaned.length > 0) {
217
+ settings.hooks[key] = cleaned;
218
+ } else {
219
+ delete settings.hooks[key];
220
+ }
135
221
  }
136
222
  }
223
+ // If hooks is now empty, remove it entirely.
224
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
137
225
  }
138
226
 
139
- // Overwrite hooks, permissions, statusLine from template (framework-managed)
140
- merged.hooks = template.hooks;
141
- merged.permissions = template.permissions;
142
- // Use node-based statusline on Windows (no bash), bash on Unix
143
- if (process.platform === 'win32') {
144
- merged.statusLine = { type: 'command', command: 'node ~/.claude/statusline-command.js' };
145
- } else {
146
- merged.statusLine = template.statusLine;
227
+ // Status line only drop it if it points at our renderer.
228
+ if (settings.statusLine && typeof settings.statusLine === "object") {
229
+ const cmd = settings.statusLine.command || "";
230
+ if (isQualiaCommand(cmd) || cmd.includes("statusline.js") || cmd.includes("qualia-ui")) {
231
+ delete settings.statusLine;
232
+ }
147
233
  }
148
234
 
149
- // Preserve user's existing plugins and MCP servers
150
- if (existing.enabledPlugins) {
151
- merged.enabledPlugins = existing.enabledPlugins;
152
- }
153
- if (existing.mcpServers) {
154
- merged.mcpServers = existing.mcpServers;
155
- }
235
+ // Qualia-specific spinner overrides.
236
+ if (settings.spinnerVerbs) delete settings.spinnerVerbs;
237
+ if (settings.spinnerTipsOverride) delete settings.spinnerTipsOverride;
156
238
 
157
- return merged;
158
- }
239
+ // Leave settings.env alone — the user may have other env vars in there.
159
240
 
160
- function prompt(question) {
161
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
162
- return new Promise(resolve => {
163
- rl.question(question, answer => {
164
- rl.close();
165
- resolve(answer.trim());
166
- });
167
- });
241
+ try {
242
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
243
+ counters.settingsCleaned = true;
244
+ } catch (e) {
245
+ counters.errors.push(`settings.json write: ${e.message}`);
246
+ }
168
247
  }
169
248
 
170
- // ── Main ──
171
- async function main() {
172
- const command = process.argv[2];
249
+ async function cmdUninstall() {
250
+ banner();
173
251
 
174
- if (command === 'update') {
175
- await runUpdate();
176
- } else if (command === 'verify') {
177
- runVerify();
252
+ const args = process.argv.slice(3);
253
+ const skipConfirm = args.includes("-y") || args.includes("--yes");
254
+
255
+ const cfg = readConfig();
256
+ console.log("");
257
+ if (cfg.installed_by) {
258
+ console.log(` ${DIM}User:${RESET} ${WHITE}${cfg.installed_by}${RESET} ${DIM}(${cfg.role || "?"})${RESET}`);
178
259
  } else {
179
- await runInstall();
260
+ console.log(` ${DIM}No Qualia config found at${RESET} ${WHITE}${CONFIG_FILE}${RESET}`);
180
261
  }
181
- }
262
+ console.log("");
182
263
 
183
- async function runInstall() {
184
- log('');
185
- log(`${c.cyan}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
186
- log(`${c.cyan}\u2551${c.reset}${c.bold} QUALIA FRAMEWORK \u2014 INSTALLER ${c.reset}${c.cyan}\u2551${c.reset}`);
187
- log(`${c.cyan}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
188
- log('');
189
-
190
- const profiles = loadProfiles();
191
- const code = await prompt(` Enter your employee code: `);
192
- const profile = profiles[code.toUpperCase()];
193
-
194
- if (!profile) {
195
- log('');
196
- fail(`Unknown code "${code}". Contact Fawzi for your employee code.`);
197
- log(`${c.dim} Available format: QS-NAME-YEAR${c.reset}`);
198
- process.exit(1);
264
+ if (!skipConfirm) {
265
+ const confirm = await promptYesNo("Are you sure you want to uninstall the Qualia Framework?", false);
266
+ if (!confirm) {
267
+ console.log("");
268
+ console.log(` ${DIM}Aborted.${RESET}`);
269
+ console.log("");
270
+ return;
271
+ }
199
272
  }
200
273
 
201
- log('');
202
- log(` ${c.green}\u2192${c.reset} Welcome, ${c.bold}${profile.name}${c.reset}! Setting up your Qualia environment...`);
274
+ // Preserve knowledge base by default.
275
+ let preserveKnowledge = true;
276
+ if (!skipConfirm) {
277
+ preserveKnowledge = await promptYesNo(
278
+ "Preserve knowledge base? (your learned patterns, fixes, client prefs)",
279
+ true
280
+ );
281
+ }
282
+
283
+ console.log("");
284
+ console.log(` ${DIM}Removing framework files...${RESET}`);
285
+ console.log("");
203
286
 
204
- const TOTAL = 6;
287
+ const counters = { filesRemoved: 0, dirsRemoved: 0, settingsCleaned: false, errors: [] };
205
288
 
206
- // Step 1: Backup existing
207
- step(1, TOTAL, 'Checking existing ~/.claude/ ...');
208
- if (fs.existsSync(CLAUDE_DIR)) {
209
- const date = new Date().toISOString().split('T')[0];
210
- const backupDir = `${CLAUDE_DIR}.backup-${date}`;
211
- if (!fs.existsSync(backupDir)) {
212
- // Don't backup the whole thing — just the config files we'll overwrite
213
- fs.mkdirSync(backupDir, { recursive: true });
214
- for (const f of ['CLAUDE.md', 'settings.json', '.env.claude']) {
215
- const src = path.join(CLAUDE_DIR, f);
216
- if (fs.existsSync(src)) {
217
- fs.copyFileSync(src, path.join(backupDir, f));
289
+ // Skills any directory starting with "qualia" under ~/.claude/skills/.
290
+ const skillsDir = path.join(CLAUDE_DIR, "skills");
291
+ try {
292
+ if (fs.existsSync(skillsDir)) {
293
+ for (const name of fs.readdirSync(skillsDir)) {
294
+ if (name === "qualia" || name.startsWith("qualia-")) {
295
+ safeRmDir(path.join(skillsDir, name), counters);
218
296
  }
219
297
  }
220
- ok(`Config backed up to ${c.dim}~/.claude.backup-${date}/${c.reset}`);
221
- } else {
222
- ok('Backup already exists for today');
223
298
  }
224
- } else {
225
- fs.mkdirSync(CLAUDE_DIR, { recursive: true });
226
- ok('Created ~/.claude/');
299
+ } catch (e) {
300
+ counters.errors.push(`skills scan: ${e.message}`);
227
301
  }
228
302
 
229
- // Step 2: Copy framework files
230
- step(2, TOTAL, 'Installing framework files ...');
303
+ // Agents only the 4 Qualia ones.
304
+ for (const f of QUALIA_AGENT_FILES) {
305
+ safeUnlink(path.join(CLAUDE_DIR, "agents", f), counters);
306
+ }
231
307
 
232
- const dirs = ['skills', 'hooks', 'agents', 'rules', 'qualia-framework', 'scripts', 'knowledge'];
233
- for (const dir of dirs) {
234
- const src = path.join(FRAMEWORK_DIR, dir);
235
- const dest = path.join(CLAUDE_DIR, dir);
236
- if (fs.existsSync(src)) {
237
- copyDirSync(src, dest);
238
- }
308
+ // Hooks only the 8 Qualia ones.
309
+ for (const f of QUALIA_HOOK_FILES) {
310
+ safeUnlink(path.join(CLAUDE_DIR, "hooks", f), counters);
239
311
  }
240
312
 
241
- // Copy standalone files (both .sh and .js for cross-platform)
242
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
243
- const src = path.join(FRAMEWORK_DIR, f);
244
- if (fs.existsSync(src)) {
245
- fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
246
- }
313
+ // Bin scripts only the 3 Qualia ones.
314
+ for (const f of QUALIA_BIN_FILES) {
315
+ safeUnlink(path.join(CLAUDE_DIR, "bin", f), counters);
247
316
  }
248
317
 
249
- // Make hooks executable
250
- const hooksDir = path.join(CLAUDE_DIR, 'hooks');
251
- if (fs.existsSync(hooksDir)) {
252
- for (const f of fs.readdirSync(hooksDir)) {
253
- if (f.endsWith('.sh')) {
254
- fs.chmodSync(path.join(hooksDir, f), 0o755);
255
- }
256
- }
318
+ // Rules all 4.
319
+ for (const f of QUALIA_RULE_FILES) {
320
+ safeUnlink(path.join(CLAUDE_DIR, "rules", f), counters);
257
321
  }
258
- // Make standalone scripts executable
259
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
260
- const p = path.join(CLAUDE_DIR, f);
261
- if (fs.existsSync(p)) try { fs.chmodSync(p, 0o755); } catch {}
262
- }
263
- // Make qualia-framework bin executable
264
- const engineBin = path.join(CLAUDE_DIR, 'qualia-framework', 'bin');
265
- if (fs.existsSync(engineBin)) {
266
- for (const f of fs.readdirSync(engineBin)) {
267
- const p = path.join(engineBin, f);
268
- try { fs.chmodSync(p, 0o755); } catch {}
269
- }
322
+
323
+ // Templates directory (entire).
324
+ safeRmDir(path.join(CLAUDE_DIR, "qualia-templates"), counters);
325
+
326
+ // Knowledge directory (optional preservation).
327
+ if (!preserveKnowledge) {
328
+ safeRmDir(path.join(CLAUDE_DIR, "knowledge"), counters);
270
329
  }
271
330
 
272
- ok(`${countDir(path.join(CLAUDE_DIR, 'skills'), 'dirs')} skills`);
273
- ok(`${countDir(hooksDir, 'files')} hooks (chmod +x)`);
274
- ok(`${countDir(path.join(CLAUDE_DIR, 'agents'), 'files')} agents`);
275
- ok(`${countDir(path.join(CLAUDE_DIR, 'rules'), 'files')} rule files`);
276
- ok('qualia-framework');
277
- ok('knowledge base');
278
-
279
- // Step 3: Generate CLAUDE.md
280
- step(3, TOTAL, 'Generating your CLAUDE.md ...');
281
- const claudeMd = renderCLAUDEmd(profile);
282
- fs.writeFileSync(path.join(CLAUDE_DIR, 'CLAUDE.md'), claudeMd);
283
-
284
- const roleDesc = profile.role === 'OWNER' ? 'Full authority' :
285
- `Feature branches${profile.permissions?.canDeploy ? ', can deploy' : ', no prod deploy'}`;
286
- ok(`Identity: ${profile.name} \u2014 ${profile.role === 'OWNER' ? 'Founder' : 'Developer'} at Qualia Solutions`);
287
- ok(`Role: ${profile.role} (${roleDesc})`);
288
-
289
- // Step 4: Configure settings.json
290
- step(4, TOTAL, 'Configuring settings.json ...');
291
- const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
292
- const templateSettingsPath = path.join(TEMPLATES_DIR, 'settings.json');
293
- const mergedSettings = mergeSettings(settingsPath, templateSettingsPath);
294
-
295
- // Fix SUDO_ASKPASS to absolute path
296
- mergedSettings.env.SUDO_ASKPASS = path.join(CLAUDE_DIR, 'askpass.sh');
297
- mergedSettings.env.CLAUDE_ENV_FILE = path.join(CLAUDE_DIR, '.env.claude');
298
-
299
- fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n');
300
- ok('Hooks wired');
301
- ok('Preserved your existing API keys');
302
- ok('Settings configured');
303
-
304
- // Step 5: Environment
305
- step(5, TOTAL, 'Setting up environment ...');
306
- const envPath = path.join(CLAUDE_DIR, '.env.claude');
307
- if (fs.existsSync(envPath)) {
308
- ok('.env.claude already exists (preserved)');
331
+ // Config + state files.
332
+ safeUnlink(path.join(CLAUDE_DIR, ".qualia-config.json"), counters);
333
+ safeUnlink(path.join(CLAUDE_DIR, ".qualia-last-update-check"), counters);
334
+ safeUnlink(path.join(CLAUDE_DIR, ".erp-api-key"), counters);
335
+ safeUnlink(path.join(CLAUDE_DIR, ".qualia-team.json"), counters);
336
+ safeUnlink(path.join(CLAUDE_DIR, "qualia-guide.md"), counters);
337
+
338
+ // Traces directory.
339
+ safeRmDir(path.join(CLAUDE_DIR, ".qualia-traces"), counters);
340
+
341
+ // Clean settings.json surgically.
342
+ cleanSettingsJson(counters);
343
+
344
+ // Summary.
345
+ console.log("");
346
+ console.log(`${TEAL} Uninstall complete${RESET}`);
347
+ console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
348
+ console.log(` ${DIM}Files removed:${RESET} ${WHITE}${counters.filesRemoved}${RESET}`);
349
+ console.log(` ${DIM}Directories removed:${RESET} ${WHITE}${counters.dirsRemoved}${RESET}`);
350
+ console.log(
351
+ ` ${DIM}settings.json:${RESET} ${counters.settingsCleaned ? `${GREEN}cleaned ✓${RESET}` : `${DIM}not present${RESET}`}`
352
+ );
353
+ if (preserveKnowledge) {
354
+ console.log(` ${DIM}Knowledge base:${RESET} ${GREEN}preserved ✓${RESET}`);
309
355
  } else {
310
- fs.copyFileSync(path.join(TEMPLATES_DIR, 'env.claude.template'), envPath);
311
- fs.chmodSync(envPath, 0o600);
312
- warn(`Fill in your API keys: ${c.dim}~/.claude/.env.claude${c.reset}`);
356
+ console.log(` ${DIM}Knowledge base:${RESET} ${YELLOW}removed${RESET}`);
313
357
  }
314
358
 
315
- // Create required directories
316
- for (const d of ['projects', 'session-env', 'backups']) {
317
- fs.mkdirSync(path.join(CLAUDE_DIR, d), { recursive: true });
359
+ if (counters.errors.length > 0) {
360
+ console.log("");
361
+ console.log(` ${YELLOW}${counters.errors.length} warning(s):${RESET}`);
362
+ for (const err of counters.errors.slice(0, 5)) {
363
+ console.log(` ${DIM}${err}${RESET}`);
364
+ }
318
365
  }
319
366
 
320
- // Step 6: Verify
321
- step(6, TOTAL, 'Verifying installation ...');
367
+ console.log("");
368
+ console.log(
369
+ ` ${YELLOW}Manual step:${RESET} edit ${WHITE}~/.claude/CLAUDE.md${RESET} to remove the Qualia Framework section if desired.`
370
+ );
371
+ console.log("");
372
+ }
322
373
 
323
- let hooksOk = 0;
324
- const hooksTotal = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh')).length;
325
- for (const f of fs.readdirSync(hooksDir)) {
326
- if (!f.endsWith('.sh')) continue;
327
- try {
328
- const stat = fs.statSync(path.join(hooksDir, f));
329
- if (stat.mode & 0o111) hooksOk++;
330
- } catch {}
331
- }
332
- if (hooksOk === hooksTotal) {
333
- ok(`${hooksOk}/${hooksTotal} hooks executable`);
334
- } else {
335
- warn(`${hooksOk}/${hooksTotal} hooks executable`);
336
- }
374
+ // ─── Team Management ────────────────────────────────────
375
+ // External team file at ~/.claude/.qualia-team.json.
376
+ // Falls back to embedded defaults in install.js.
377
+
378
+ function getDefaultTeam() {
379
+ return {
380
+ "QS-FAWZI-01": { name: "Fawzi Goussous", role: "OWNER", description: "Company owner. Full access. Can push to main, approve deploys, edit secrets." },
381
+ "QS-HASAN-02": { name: "Hasan", role: "EMPLOYEE", description: "Developer. Feature branches only. Cannot push to main or edit .env files." },
382
+ "QS-MOAYAD-03": { name: "Moayad", role: "EMPLOYEE", description: "Developer. Feature branches only. Cannot push to main or edit .env files." },
383
+ "QS-RAMA-04": { name: "Rama", role: "EMPLOYEE", description: "Developer. Feature branches only. Cannot push to main or edit .env files." },
384
+ "QS-SALLY-05": { name: "Sally", role: "EMPLOYEE", description: "Developer. Feature branches only. Cannot push to main or edit .env files." },
385
+ };
386
+ }
337
387
 
388
+ function readTeamFile() {
389
+ const teamFile = path.join(CLAUDE_DIR, ".qualia-team.json");
338
390
  try {
339
- JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
340
- ok('settings.json valid JSON');
341
- } catch {
342
- fail('settings.json invalid JSON!');
391
+ if (fs.existsSync(teamFile)) {
392
+ const data = JSON.parse(fs.readFileSync(teamFile, "utf8"));
393
+ if (data && typeof data === "object" && Object.keys(data).length > 0) return data;
394
+ }
395
+ } catch {}
396
+ return null;
397
+ }
398
+
399
+ function writeTeamFile(team) {
400
+ if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
401
+ fs.writeFileSync(path.join(CLAUDE_DIR, ".qualia-team.json"), JSON.stringify(team, null, 2) + "\n");
402
+ }
403
+
404
+ function parseTeamArgs(argv) {
405
+ const args = {};
406
+ for (let i = 0; i < argv.length; i++) {
407
+ if (argv[i] === "--code" && argv[i + 1]) { args.code = argv[++i]; }
408
+ else if (argv[i] === "--name" && argv[i + 1]) { args.name = argv[++i]; }
409
+ else if (argv[i] === "--role" && argv[i + 1]) { args.role = argv[++i].toUpperCase(); }
343
410
  }
411
+ return args;
412
+ }
344
413
 
345
- if (fs.existsSync(path.join(CLAUDE_DIR, 'CLAUDE.md'))) {
346
- ok('CLAUDE.md generated');
347
- } else {
348
- fail('CLAUDE.md missing!');
414
+ function cmdTeam() {
415
+ const sub = process.argv[3];
416
+
417
+ switch (sub) {
418
+ case "list": {
419
+ banner();
420
+ console.log("");
421
+ const team = readTeamFile();
422
+ const source = team || getDefaultTeam();
423
+ const label = team ? "team file" : "embedded defaults";
424
+ console.log(` ${DIM}Source: ${label}${RESET}`);
425
+ console.log("");
426
+ for (const [code, member] of Object.entries(source)) {
427
+ const roleColor = member.role === "OWNER" ? TEAL : WHITE;
428
+ console.log(` ${WHITE}${code}${RESET} ${roleColor}${member.role}${RESET} ${DIM}${member.name}${RESET}`);
429
+ }
430
+ console.log("");
431
+ break;
432
+ }
433
+
434
+ case "add": {
435
+ const args = parseTeamArgs(process.argv.slice(4));
436
+ if (!args.code || !args.name) {
437
+ console.log(` ${RED}Usage:${RESET} qualia-framework team add --code QS-NAME-NN --name "Full Name" [--role EMPLOYEE|OWNER]`);
438
+ process.exit(1);
439
+ }
440
+ const team = readTeamFile() || getDefaultTeam();
441
+ const code = args.code.toUpperCase();
442
+ const role = args.role || "EMPLOYEE";
443
+ team[code] = {
444
+ name: args.name,
445
+ role,
446
+ description: role === "OWNER"
447
+ ? "Company owner. Full access. Can push to main, approve deploys, edit secrets."
448
+ : "Developer. Feature branches only. Cannot push to main or edit .env files.",
449
+ };
450
+ writeTeamFile(team);
451
+ console.log(` ${GREEN}+${RESET} ${WHITE}${code}${RESET} ${DIM}(${args.name}, ${role})${RESET}`);
452
+ break;
453
+ }
454
+
455
+ case "remove": {
456
+ const code = (process.argv[4] || "").toUpperCase();
457
+ if (!code) {
458
+ console.log(` ${RED}Usage:${RESET} qualia-framework team remove QS-NAME-NN`);
459
+ process.exit(1);
460
+ }
461
+ const team = readTeamFile();
462
+ if (!team || !team[code]) {
463
+ console.log(` ${YELLOW}!${RESET} ${code} not found in team file.`);
464
+ process.exit(1);
465
+ }
466
+ delete team[code];
467
+ writeTeamFile(team);
468
+ console.log(` ${RED}-${RESET} ${WHITE}${code}${RESET} removed`);
469
+ break;
470
+ }
471
+
472
+ default:
473
+ console.log(` ${RED}Usage:${RESET} qualia-framework team <list|add|remove>`);
474
+ process.exit(1);
349
475
  }
476
+ }
350
477
 
351
- // Done
352
- log('');
353
- log(`${c.green}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
354
- log(`${c.green}\u2551${c.reset}${c.bold} \u2713 INSTALLED \u2014 Run /qualia-start in Claude Code ${c.reset}${c.green}\u2551${c.reset}`);
355
- log(`${c.green}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
356
- log('');
357
- log(` ${c.dim}Update later: npx github:Qualiasolutions/qualia-framework update${c.reset}`);
358
- log('');
478
+ // ─── Traces ─────────────────────────────────────────────
479
+
480
+ function cmdTraces() {
481
+ banner();
482
+ console.log("");
483
+ const tracesDir = path.join(CLAUDE_DIR, ".qualia-traces");
484
+ if (!fs.existsSync(tracesDir)) {
485
+ console.log(` ${DIM}No traces found. Traces are written by hooks during normal operation.${RESET}`);
486
+ console.log("");
487
+ return;
488
+ }
489
+ const files = fs.readdirSync(tracesDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
490
+ if (files.length === 0) {
491
+ console.log(` ${DIM}No trace files found.${RESET}`);
492
+ console.log("");
493
+ return;
494
+ }
495
+ const latest = path.join(tracesDir, files[0]);
496
+ const lines = fs.readFileSync(latest, "utf8").trim().split("\n").slice(-20);
497
+ console.log(` ${WHITE}Recent traces${RESET} ${DIM}(${files[0]})${RESET}`);
498
+ console.log("");
499
+ for (const line of lines) {
500
+ try {
501
+ const e = JSON.parse(line);
502
+ const color = e.result === "block" ? RED : e.result === "allow" ? GREEN : DIM;
503
+ const time = (e.timestamp || "").split("T")[1] || "";
504
+ const ts = time.split(".")[0] || "";
505
+ console.log(` ${DIM}${ts}${RESET} ${color}${e.result}${RESET} ${WHITE}${e.hook}${RESET} ${DIM}${e.duration_ms || 0}ms${RESET}`);
506
+ } catch {}
507
+ }
508
+ console.log("");
359
509
  }
360
510
 
361
- async function runUpdate() {
362
- log('');
363
- log(`${c.cyan}\u25C6 Qualia Framework \u2014 Update${c.reset}`);
364
- log('');
511
+ // ─── Migrate ────────────────────────────────────────────
512
+
513
+ function cmdMigrate() {
514
+ banner();
515
+ console.log("");
365
516
 
366
- if (!fs.existsSync(CLAUDE_DIR)) {
367
- fail('No ~/.claude/ found. Run the installer first (without "update").');
517
+ const settingsPath = path.join(CLAUDE_DIR, "settings.json");
518
+ if (!fs.existsSync(settingsPath)) {
519
+ console.log(` ${RED}✗${RESET} No settings.json found. Run ${TEAL}qualia-framework install${RESET} first.`);
520
+ console.log("");
368
521
  process.exit(1);
369
522
  }
370
523
 
371
- // Authenticate — always require code on update
372
- const profiles = loadProfiles();
373
- const code = await prompt(` Enter your employee code: `);
374
- const profile = profiles[code.toUpperCase()];
375
-
376
- if (!profile) {
377
- log('');
378
- fail(`Unknown code "${code}". Contact Fawzi for your employee code.`);
524
+ let settings;
525
+ try {
526
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
527
+ } catch (e) {
528
+ console.log(` ${RED}✗${RESET} Failed to parse settings.json: ${e.message}`);
379
529
  process.exit(1);
380
530
  }
381
531
 
382
- log(` ${c.green}\u2192${c.reset} Updating for ${c.bold}${profile.name}${c.reset} (${profile.role})`);
383
- log('');
532
+ const cfg = readConfig();
533
+ const fromVersion = cfg.version || "unknown";
534
+ let changes = 0;
384
535
 
385
- // Count what's changing
386
- const frameworkDirs = ['skills', 'hooks', 'agents', 'rules', 'qualia-framework', 'scripts', 'knowledge'];
387
- const counts = {};
388
- let totalFiles = 0;
536
+ console.log(` ${DIM}Current version:${RESET} ${WHITE}${fromVersion}${RESET}`);
537
+ console.log(` ${DIM}Target version:${RESET} ${WHITE}${PKG.version}${RESET}`);
538
+ console.log("");
389
539
 
390
- for (const dir of frameworkDirs) {
391
- const src = path.join(FRAMEWORK_DIR, dir);
392
- if (fs.existsSync(src)) {
393
- const count = countFilesRecursive(src);
394
- counts[dir] = count;
395
- totalFiles += count;
396
- }
540
+ // 1. Ensure all 8 hooks are wired (v2 missed block-env-edit and branch-guard)
541
+ const hd = path.join(CLAUDE_DIR, "hooks");
542
+ const nodeCmd = (hookFile) => `node "${path.join(hd, hookFile)}"`;
543
+
544
+ if (!settings.hooks) settings.hooks = {};
545
+
546
+ // Check SessionStart hooks
547
+ if (!settings.hooks.SessionStart || !Array.isArray(settings.hooks.SessionStart)) {
548
+ settings.hooks.SessionStart = [{ matcher: ".*", hooks: [{ type: "command", command: nodeCmd("session-start.js"), timeout: 5 }] }];
549
+ changes++;
550
+ console.log(` ${GREEN}+${RESET} Added SessionStart hook`);
397
551
  }
398
552
 
399
- // Show what will be updated
400
- log(` ${c.bold}Components to update:${c.reset}`);
401
- for (const [dir, count] of Object.entries(counts)) {
402
- log(` ${c.dim}\u2022${c.reset} ${dir} (${count} files)`);
553
+ // Check PreToolUse hooks ensure all critical hooks are present
554
+ const requiredBashHooks = ["auto-update.js", "branch-guard.js", "pre-push.js", "pre-deploy-gate.js"];
555
+ const requiredEditHooks = ["block-env-edit.js", "migration-guard.js"];
556
+
557
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
558
+
559
+ // Find or create Bash matcher entry
560
+ let bashEntry = settings.hooks.PreToolUse.find(e => e.matcher === "Bash");
561
+ if (!bashEntry) {
562
+ bashEntry = { matcher: "Bash", hooks: [] };
563
+ settings.hooks.PreToolUse.push(bashEntry);
403
564
  }
404
- log(` ${c.bold}Total: ${totalFiles} files${c.reset}`);
405
- log('');
565
+ if (!bashEntry.hooks) bashEntry.hooks = [];
406
566
 
407
- // Update framework files
408
- let updated = 0;
409
- for (const dir of frameworkDirs) {
410
- const src = path.join(FRAMEWORK_DIR, dir);
411
- const dest = path.join(CLAUDE_DIR, dir);
412
- if (fs.existsSync(src)) {
413
- copyDirSync(src, dest);
414
- updated++;
415
- ok(`${dir} (${counts[dir]} files)`);
567
+ for (const hookFile of requiredBashHooks) {
568
+ const cmd = nodeCmd(hookFile);
569
+ const exists = bashEntry.hooks.some(h => h.command && h.command.includes(hookFile));
570
+ if (!exists) {
571
+ const hookDef = { type: "command", command: cmd, timeout: hookFile === "pre-deploy-gate.js" ? 180 : 5 };
572
+ if (hookFile === "branch-guard.js") hookDef.if = "Bash(git push*)";
573
+ if (hookFile === "pre-push.js") { hookDef.if = "Bash(git push*)"; hookDef.timeout = 15; }
574
+ if (hookFile === "pre-deploy-gate.js") hookDef.if = "Bash(vercel --prod*)";
575
+ bashEntry.hooks.push(hookDef);
576
+ changes++;
577
+ console.log(` ${GREEN}+${RESET} Wired ${hookFile} into PreToolUse/Bash`);
416
578
  }
417
579
  }
418
580
 
419
- // Copy standalone files (both .sh and .js for cross-platform)
420
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
421
- const src = path.join(FRAMEWORK_DIR, f);
422
- if (fs.existsSync(src)) {
423
- fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
424
- try { fs.chmodSync(path.join(CLAUDE_DIR, f), 0o755); } catch {}
425
- }
581
+ // Find or create Edit|Write matcher entry
582
+ let editEntry = settings.hooks.PreToolUse.find(e => e.matcher === "Edit|Write");
583
+ if (!editEntry) {
584
+ editEntry = { matcher: "Edit|Write", hooks: [] };
585
+ settings.hooks.PreToolUse.push(editEntry);
426
586
  }
587
+ if (!editEntry.hooks) editEntry.hooks = [];
427
588
 
428
- // Re-chmod hooks
429
- const hooksDir = path.join(CLAUDE_DIR, 'hooks');
430
- if (fs.existsSync(hooksDir)) {
431
- for (const f of fs.readdirSync(hooksDir)) {
432
- if (f.endsWith('.sh')) try { fs.chmodSync(path.join(hooksDir, f), 0o755); } catch {}
589
+ for (const hookFile of requiredEditHooks) {
590
+ const cmd = nodeCmd(hookFile);
591
+ const exists = editEntry.hooks.some(h => h.command && h.command.includes(hookFile));
592
+ if (!exists) {
593
+ const hookDef = { type: "command", command: cmd, timeout: hookFile === "migration-guard.js" ? 10 : 5 };
594
+ if (hookFile === "migration-guard.js") hookDef.if = "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)";
595
+ editEntry.hooks.push(hookDef);
596
+ changes++;
597
+ console.log(` ${GREEN}+${RESET} Wired ${hookFile} into PreToolUse/Edit|Write`);
433
598
  }
434
599
  }
435
600
 
436
- // Re-chmod engine bin
437
- const engineBin = path.join(CLAUDE_DIR, 'qualia-framework', 'bin');
438
- if (fs.existsSync(engineBin)) {
439
- for (const f of fs.readdirSync(engineBin)) {
440
- try { fs.chmodSync(path.join(engineBin, f), 0o755); } catch {}
441
- }
601
+ // Check PreCompact hook
602
+ if (!settings.hooks.PreCompact) {
603
+ settings.hooks.PreCompact = [{ matcher: "compact", hooks: [{ type: "command", command: nodeCmd("pre-compact.js"), timeout: 15 }] }];
604
+ changes++;
605
+ console.log(` ${GREEN}+${RESET} Added PreCompact hook`);
442
606
  }
443
607
 
444
- // Regenerate CLAUDE.md for this person's role
445
- const claudeMd = renderCLAUDEmd(profile);
446
- fs.writeFileSync(path.join(CLAUDE_DIR, 'CLAUDE.md'), claudeMd);
447
- ok(`CLAUDE.md regenerated for ${profile.name} (${profile.role})`);
608
+ // 2. Ensure env vars are up to date
609
+ if (!settings.env) settings.env = {};
610
+ const requiredEnv = {
611
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
612
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: "0",
613
+ MAX_MCP_OUTPUT_TOKENS: "25000",
614
+ CLAUDE_CODE_NO_FLICKER: "1",
615
+ };
616
+ for (const [k, v] of Object.entries(requiredEnv)) {
617
+ if (settings.env[k] !== v) {
618
+ settings.env[k] = v;
619
+ changes++;
620
+ console.log(` ${GREEN}+${RESET} Set env.${k}`);
621
+ }
622
+ }
448
623
 
449
- // Merge settings.json hooks (add new hooks without overwriting user config)
450
- const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
451
- const templateSettingsPath = path.join(TEMPLATES_DIR, 'settings.json');
452
- if (fs.existsSync(settingsPath) && fs.existsSync(templateSettingsPath)) {
453
- const mergedSettings = mergeSettings(settingsPath, templateSettingsPath);
454
- mergedSettings.env.SUDO_ASKPASS = path.join(CLAUDE_DIR, 'askpass.sh');
455
- mergedSettings.env.CLAUDE_ENV_FILE = path.join(CLAUDE_DIR, '.env.claude');
456
- fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n');
457
- ok('settings.json hooks merged');
624
+ // 3. Update status line if missing
625
+ if (!settings.statusLine) {
626
+ settings.statusLine = { type: "command", command: `node "${path.join(CLAUDE_DIR, "bin", "statusline.js")}"` };
627
+ changes++;
628
+ console.log(` ${GREEN}+${RESET} Added status line`);
458
629
  }
459
630
 
460
- // Clean up removed directories from old versions
461
- for (const stale of ['commands', 'config', 'core', 'qualia-engine']) {
462
- const p = path.join(CLAUDE_DIR, stale);
463
- if (fs.existsSync(p)) {
464
- fs.rmSync(p, { recursive: true, force: true });
465
- ok(`removed stale ${stale}/`);
466
- }
631
+ // 3b. Add next-devtools MCP if not present
632
+ if (!settings.mcpServers) settings.mcpServers = {};
633
+ if (!settings.mcpServers["next-devtools"]) {
634
+ settings.mcpServers["next-devtools"] = {
635
+ command: "npx",
636
+ args: ["next-devtools-mcp@0.3.10"],
637
+ disabled: false,
638
+ };
639
+ changes++;
640
+ console.log(` ${GREEN}+${RESET} Added next-devtools MCP server`);
467
641
  }
468
642
 
469
- log('');
470
- log(`${c.green}\u25C6${c.reset} ${c.bold}Updated ${updated} components (${totalFiles} files)${c.reset}`);
471
- log(`${c.dim} .env.claude preserved. CLAUDE.md regenerated for ${profile.name}.${c.reset}`);
472
- log('');
643
+ // 4. Update config version
644
+ cfg.version = PKG.version;
645
+ cfg.migrated_at = new Date().toISOString().split("T")[0];
646
+ writeConfig(cfg);
647
+
648
+ // Write settings
649
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
650
+
651
+ console.log("");
652
+ if (changes === 0) {
653
+ console.log(` ${GREEN}✓${RESET} Already up to date — no migration needed.`);
654
+ } else {
655
+ console.log(` ${GREEN}✓${RESET} Applied ${WHITE}${changes}${RESET} changes. Restart Claude Code to take effect.`);
656
+ }
657
+ console.log("");
473
658
  }
474
659
 
475
- function countFilesRecursive(dir) {
476
- let count = 0;
477
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
478
- if (entry.isDirectory()) {
479
- count += countFilesRecursive(path.join(dir, entry.name));
480
- } else {
481
- count++;
660
+ // ─── Analytics ──────────────────────────────────────────
661
+
662
+ function cmdAnalytics() {
663
+ banner();
664
+ console.log("");
665
+
666
+ const tracesDir = path.join(CLAUDE_DIR, ".qualia-traces");
667
+ if (!fs.existsSync(tracesDir)) {
668
+ console.log(` ${DIM}No traces found. Analytics require hook telemetry data.${RESET}`);
669
+ console.log(` ${DIM}Traces are collected automatically during normal framework use.${RESET}`);
670
+ console.log("");
671
+ return;
672
+ }
673
+
674
+ const files = fs.readdirSync(tracesDir).filter(f => f.endsWith(".jsonl")).sort();
675
+ if (files.length === 0) {
676
+ console.log(` ${DIM}No trace data yet.${RESET}`);
677
+ console.log("");
678
+ return;
679
+ }
680
+
681
+ // Parse all traces
682
+ const traces = [];
683
+ for (const file of files) {
684
+ const lines = fs.readFileSync(path.join(tracesDir, file), "utf8").trim().split("\n");
685
+ for (const line of lines) {
686
+ try { traces.push(JSON.parse(line)); } catch {}
482
687
  }
483
688
  }
484
- return count;
689
+
690
+ // Aggregate stats
691
+ const hookStats = {};
692
+ let totalBlocks = 0;
693
+ let totalAllows = 0;
694
+ let totalDuration = 0;
695
+
696
+ for (const t of traces) {
697
+ const hook = t.hook || "unknown";
698
+ if (!hookStats[hook]) hookStats[hook] = { allow: 0, block: 0, total_ms: 0 };
699
+ if (t.result === "block") { hookStats[hook].block++; totalBlocks++; }
700
+ else { hookStats[hook].allow++; totalAllows++; }
701
+ hookStats[hook].total_ms += t.duration_ms || 0;
702
+ totalDuration += t.duration_ms || 0;
703
+ }
704
+
705
+ // Verification outcomes (from traces that include verification data)
706
+ const verifications = traces.filter(t => t.hook === "state-transition" && t.extra && t.extra.verification);
707
+ const passes = verifications.filter(t => t.extra.verification === "pass").length;
708
+ const fails = verifications.filter(t => t.extra.verification === "fail").length;
709
+
710
+ // Gap cycle data
711
+ const gapTraces = traces.filter(t => t.hook === "state-transition" && t.extra && t.extra.gap_closure);
712
+ const totalGapCycles = gapTraces.length;
713
+
714
+ // Display
715
+ console.log(` ${WHITE}Framework Analytics${RESET}`);
716
+ console.log(` ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
717
+ console.log("");
718
+ console.log(` ${WHITE}Overview${RESET}`);
719
+ console.log(` ${DIM}Trace files:${RESET} ${WHITE}${files.length}${RESET} ${DIM}(${files[0]} → ${files[files.length - 1]})${RESET}`);
720
+ console.log(` ${DIM}Total events:${RESET} ${WHITE}${traces.length}${RESET}`);
721
+ console.log(` ${DIM}Total blocks:${RESET} ${RED}${totalBlocks}${RESET}`);
722
+ console.log(` ${DIM}Total allows:${RESET} ${GREEN}${totalAllows}${RESET}`);
723
+ console.log(` ${DIM}Avg hook time:${RESET} ${WHITE}${traces.length ? Math.round(totalDuration / traces.length) : 0}ms${RESET}`);
724
+ console.log("");
725
+
726
+ // Verification stats
727
+ if (passes + fails > 0) {
728
+ const rate = Math.round((passes / (passes + fails)) * 100);
729
+ console.log(` ${WHITE}Verification Outcomes${RESET}`);
730
+ console.log(` ${DIM}First-pass rate:${RESET} ${rate >= 70 ? GREEN : rate >= 50 ? YELLOW : RED}${rate}%${RESET} ${DIM}(${passes} pass / ${fails} fail)${RESET}`);
731
+ console.log(` ${DIM}Gap cycles:${RESET} ${WHITE}${totalGapCycles}${RESET}`);
732
+ console.log("");
733
+ }
734
+
735
+ // Per-hook breakdown
736
+ console.log(` ${WHITE}Per-Hook Breakdown${RESET}`);
737
+ const sorted = Object.entries(hookStats).sort((a, b) => (b[1].allow + b[1].block) - (a[1].allow + a[1].block));
738
+ for (const [hook, stats] of sorted) {
739
+ const total = stats.allow + stats.block;
740
+ const avg = Math.round(stats.total_ms / total);
741
+ const blockRate = stats.block > 0 ? ` ${RED}${stats.block} blocked${RESET}` : "";
742
+ console.log(` ${DIM}${hook}:${RESET} ${WHITE}${total}${RESET} calls, ${DIM}avg ${avg}ms${RESET}${blockRate}`);
743
+ }
744
+ console.log("");
485
745
  }
486
746
 
487
- function runVerify() {
488
- log('');
489
- log(`${c.cyan}Qualia Framework \u2014 Verify${c.reset}`);
490
- log('');
491
-
492
- let pass = 0;
493
- let total = 0;
494
-
495
- const checks = [
496
- ['CLAUDE.md', () => fs.existsSync(path.join(CLAUDE_DIR, 'CLAUDE.md'))],
497
- ['settings.json', () => { try { JSON.parse(fs.readFileSync(path.join(CLAUDE_DIR, 'settings.json'), 'utf8')); return true; } catch { return false; } }],
498
- ['hooks/', () => fs.existsSync(path.join(CLAUDE_DIR, 'hooks'))],
499
- ['skills/', () => countDir(path.join(CLAUDE_DIR, 'skills'), 'dirs') > 50],
500
- ['agents/', () => countDir(path.join(CLAUDE_DIR, 'agents'), 'files') > 15],
501
- ['qualia-framework/', () => fs.existsSync(path.join(CLAUDE_DIR, 'qualia-framework', 'bin'))],
502
- ['rules/', () => countDir(path.join(CLAUDE_DIR, 'rules'), 'files') >= 4],
503
- ['.env.claude', () => fs.existsSync(path.join(CLAUDE_DIR, '.env.claude'))],
504
- ];
505
-
506
- for (const [name, check] of checks) {
507
- total++;
508
- if (check()) { ok(name); pass++; }
509
- else { fail(name); }
510
- }
511
-
512
- log('');
513
- if (pass === total) {
514
- log(`${c.green}All ${total} checks passed.${c.reset}`);
515
- } else {
516
- log(`${c.yellow}${pass}/${total} checks passed.${c.reset}`);
517
- }
518
- log('');
747
+ function cmdHelp() {
748
+ banner();
749
+ console.log("");
750
+ console.log(` ${WHITE}Commands:${RESET}`);
751
+ console.log(` qualia-framework ${TEAL}install${RESET} Install or reinstall the framework`);
752
+ console.log(` qualia-framework ${TEAL}update${RESET} Update to the latest version`);
753
+ console.log(` qualia-framework ${TEAL}version${RESET} Show installed version + check for updates`);
754
+ console.log(` qualia-framework ${TEAL}uninstall${RESET} Clean removal from ~/.claude/ (${DIM}-y to skip prompts${RESET})`);
755
+ console.log(` qualia-framework ${TEAL}migrate${RESET} Migrate settings from v2 to v3`);
756
+ console.log(` qualia-framework ${TEAL}team${RESET} Manage team members (${DIM}list|add|remove${RESET})`);
757
+ console.log(` qualia-framework ${TEAL}traces${RESET} View recent hook telemetry`);
758
+ console.log(` qualia-framework ${TEAL}analytics${RESET} Show outcome scoring & gap cycle stats`);
759
+ console.log("");
760
+ console.log(` ${WHITE}After install:${RESET}`);
761
+ console.log(` ${TG}/qualia${RESET} What should I do next?`);
762
+ console.log(` ${TG}/qualia-new${RESET} Set up a new project`);
763
+ console.log(` ${TG}/qualia-plan${RESET} Plan a phase`);
764
+ console.log(` ${TG}/qualia-build${RESET} Build it (parallel tasks)`);
765
+ console.log(` ${TG}/qualia-verify${RESET} Verify it works`);
766
+ console.log(` ${TG}/qualia-design${RESET} One-shot design fix`);
767
+ console.log(` ${TG}/qualia-debug${RESET} Structured debugging`);
768
+ console.log(` ${TG}/qualia-review${RESET} Production audit`);
769
+ console.log(` ${TG}/qualia-ship${RESET} Deploy to production`);
770
+ console.log(` ${TG}/qualia-report${RESET} Log your work`);
771
+ console.log("");
519
772
  }
520
773
 
521
- main().catch(err => {
522
- console.error(`${c.red}Error:${c.reset}`, err.message);
523
- process.exit(1);
524
- });
774
+
775
+ // ─── Main ────────────────────────────────────────────────
776
+ const cmd = process.argv[2];
777
+
778
+ switch (cmd) {
779
+ case "install":
780
+ cmdInstall();
781
+ break;
782
+ case "version":
783
+ case "-v":
784
+ case "--version":
785
+ cmdVersion();
786
+ break;
787
+ case "update":
788
+ case "upgrade":
789
+ cmdUpdate();
790
+ break;
791
+ case "uninstall":
792
+ case "remove":
793
+ cmdUninstall().catch((e) => {
794
+ console.error(`${RED} ✗ Uninstall failed: ${e.message}${RESET}`);
795
+ process.exit(1);
796
+ });
797
+ break;
798
+ case "team":
799
+ cmdTeam();
800
+ break;
801
+ case "traces":
802
+ cmdTraces();
803
+ break;
804
+ case "migrate":
805
+ cmdMigrate();
806
+ break;
807
+ case "analytics":
808
+ case "stats":
809
+ cmdAnalytics();
810
+ break;
811
+ default:
812
+ cmdHelp();
813
+ }