qualia-framework 2.6.0 → 3.2.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 (321) hide show
  1. package/CLAUDE.md +64 -0
  2. package/README.md +103 -30
  3. package/agents/builder.md +110 -0
  4. package/agents/planner.md +134 -0
  5. package/agents/qa-browser.md +186 -0
  6. package/agents/verifier.md +221 -0
  7. package/bin/cli.js +336 -531
  8. package/bin/install.js +570 -0
  9. package/bin/qualia-ui.js +299 -0
  10. package/bin/state.js +630 -0
  11. package/bin/statusline.js +252 -0
  12. package/guide.md +63 -0
  13. package/hooks/auto-update.js +139 -0
  14. package/hooks/branch-guard.js +47 -0
  15. package/hooks/migration-guard.js +60 -0
  16. package/hooks/pre-compact.js +32 -0
  17. package/hooks/pre-deploy-gate.js +110 -0
  18. package/hooks/pre-push.js +33 -0
  19. package/hooks/session-start.js +170 -0
  20. package/package.json +29 -20
  21. package/rules/design-reference.md +179 -0
  22. package/rules/frontend.md +126 -0
  23. package/skills/qualia/SKILL.md +87 -0
  24. package/skills/qualia-build/SKILL.md +97 -0
  25. package/skills/qualia-debug/SKILL.md +87 -0
  26. package/skills/qualia-design/SKILL.md +93 -0
  27. package/skills/qualia-handoff/SKILL.md +66 -0
  28. package/skills/qualia-idk/SKILL.md +8 -0
  29. package/skills/qualia-learn/SKILL.md +88 -0
  30. package/skills/qualia-new/SKILL.md +323 -0
  31. package/{framework/skills → skills}/qualia-optimize/SKILL.md +1 -1
  32. package/skills/qualia-pause/SKILL.md +63 -0
  33. package/skills/qualia-plan/SKILL.md +101 -0
  34. package/skills/qualia-polish/SKILL.md +157 -0
  35. package/skills/qualia-quick/SKILL.md +37 -0
  36. package/skills/qualia-report/SKILL.md +105 -0
  37. package/skills/qualia-resume/SKILL.md +49 -0
  38. package/skills/qualia-review/SKILL.md +76 -0
  39. package/skills/qualia-ship/SKILL.md +90 -0
  40. package/skills/qualia-skill-new/SKILL.md +167 -0
  41. package/skills/qualia-task/SKILL.md +91 -0
  42. package/skills/qualia-verify/SKILL.md +113 -0
  43. package/templates/DESIGN.md +137 -0
  44. package/templates/plan.md +28 -0
  45. package/templates/project.md +22 -0
  46. package/templates/state.md +27 -0
  47. package/templates/tracking.json +20 -0
  48. package/tests/bin.test.sh +673 -0
  49. package/tests/hooks.test.sh +315 -0
  50. package/tests/state.test.sh +535 -0
  51. package/tests/statusline.test.sh +243 -0
  52. package/bin/collect-metrics.sh +0 -62
  53. package/framework/.claudeignore +0 -51
  54. package/framework/CLAUDE.md +0 -51
  55. package/framework/MCP_SETUP.md +0 -229
  56. package/framework/agents/architecture-strategist.md +0 -53
  57. package/framework/agents/backend-agent.md +0 -150
  58. package/framework/agents/code-simplicity-reviewer.md +0 -86
  59. package/framework/agents/frontend-agent.md +0 -111
  60. package/framework/agents/kieran-typescript-reviewer.md +0 -96
  61. package/framework/agents/performance-oracle.md +0 -111
  62. package/framework/agents/qualia-codebase-mapper.md +0 -761
  63. package/framework/agents/qualia-debugger.md +0 -1204
  64. package/framework/agents/qualia-executor.md +0 -882
  65. package/framework/agents/qualia-integration-checker.md +0 -424
  66. package/framework/agents/qualia-phase-researcher.md +0 -457
  67. package/framework/agents/qualia-plan-checker.md +0 -700
  68. package/framework/agents/qualia-planner.md +0 -1245
  69. package/framework/agents/qualia-project-researcher.md +0 -603
  70. package/framework/agents/qualia-research-synthesizer.md +0 -200
  71. package/framework/agents/qualia-roadmapper.md +0 -606
  72. package/framework/agents/qualia-verifier.md +0 -686
  73. package/framework/agents/red-team-qa.md +0 -130
  74. package/framework/agents/security-auditor.md +0 -72
  75. package/framework/agents/team-orchestrator.md +0 -229
  76. package/framework/agents/teams/framework-audit-team.md +0 -66
  77. package/framework/agents/teams/full-stack-team.md +0 -48
  78. package/framework/agents/teams/optimize-team.md +0 -53
  79. package/framework/agents/teams/review-team.md +0 -70
  80. package/framework/agents/teams/ship-team.md +0 -86
  81. package/framework/agents/test-agent.md +0 -182
  82. package/framework/hooks/auto-format.sh +0 -54
  83. package/framework/hooks/block-env-edit.sh +0 -42
  84. package/framework/hooks/branch-guard.sh +0 -43
  85. package/framework/hooks/confirm-delete.sh +0 -59
  86. package/framework/hooks/migration-validate.sh +0 -77
  87. package/framework/hooks/notification-speak.sh +0 -16
  88. package/framework/hooks/pre-commit.sh +0 -100
  89. package/framework/hooks/pre-compact.sh +0 -56
  90. package/framework/hooks/pre-deploy-gate.sh +0 -160
  91. package/framework/hooks/qualia-colors.sh +0 -32
  92. package/framework/hooks/retention-cleanup.sh +0 -62
  93. package/framework/hooks/save-session-state.sh +0 -185
  94. package/framework/hooks/session-context-loader.sh +0 -96
  95. package/framework/hooks/session-learn.sh +0 -32
  96. package/framework/hooks/skill-announce.sh +0 -123
  97. package/framework/hooks/tool-error-announce.sh +0 -27
  98. package/framework/install.ps1 +0 -323
  99. package/framework/install.sh +0 -313
  100. package/framework/qualia-framework/VERSION +0 -1
  101. package/framework/qualia-framework/assets/qualia-logo.png +0 -0
  102. package/framework/qualia-framework/bin/collect-metrics.sh +0 -67
  103. package/framework/qualia-framework/bin/generate-report-docx.py +0 -429
  104. package/framework/qualia-framework/bin/qualia-tools.js +0 -2201
  105. package/framework/qualia-framework/bin/qualia-tools.test.js +0 -1054
  106. package/framework/qualia-framework/references/checkpoints.md +0 -775
  107. package/framework/qualia-framework/references/completion-checklists.md +0 -359
  108. package/framework/qualia-framework/references/continuation-format.md +0 -249
  109. package/framework/qualia-framework/references/continuation-prompt.md +0 -97
  110. package/framework/qualia-framework/references/decimal-phase-calculation.md +0 -65
  111. package/framework/qualia-framework/references/design-quality.md +0 -56
  112. package/framework/qualia-framework/references/employee-guide.md +0 -167
  113. package/framework/qualia-framework/references/git-integration.md +0 -254
  114. package/framework/qualia-framework/references/git-planning-commit.md +0 -50
  115. package/framework/qualia-framework/references/model-profile-resolution.md +0 -32
  116. package/framework/qualia-framework/references/model-profiles.md +0 -73
  117. package/framework/qualia-framework/references/phase-argument-parsing.md +0 -61
  118. package/framework/qualia-framework/references/planning-config.md +0 -195
  119. package/framework/qualia-framework/references/questioning.md +0 -141
  120. package/framework/qualia-framework/references/tdd.md +0 -263
  121. package/framework/qualia-framework/references/ui-brand.md +0 -160
  122. package/framework/qualia-framework/references/verification-patterns.md +0 -612
  123. package/framework/qualia-framework/templates/DEBUG.md +0 -159
  124. package/framework/qualia-framework/templates/DESIGN.md +0 -81
  125. package/framework/qualia-framework/templates/UAT.md +0 -247
  126. package/framework/qualia-framework/templates/codebase/architecture.md +0 -255
  127. package/framework/qualia-framework/templates/codebase/concerns.md +0 -310
  128. package/framework/qualia-framework/templates/codebase/conventions.md +0 -307
  129. package/framework/qualia-framework/templates/codebase/integrations.md +0 -280
  130. package/framework/qualia-framework/templates/codebase/stack.md +0 -186
  131. package/framework/qualia-framework/templates/codebase/structure.md +0 -285
  132. package/framework/qualia-framework/templates/codebase/testing.md +0 -480
  133. package/framework/qualia-framework/templates/config.json +0 -35
  134. package/framework/qualia-framework/templates/context.md +0 -283
  135. package/framework/qualia-framework/templates/continue-here.md +0 -78
  136. package/framework/qualia-framework/templates/debug-subagent-prompt.md +0 -91
  137. package/framework/qualia-framework/templates/discovery.md +0 -146
  138. package/framework/qualia-framework/templates/lab-notes.md +0 -16
  139. package/framework/qualia-framework/templates/milestone-archive.md +0 -123
  140. package/framework/qualia-framework/templates/milestone.md +0 -115
  141. package/framework/qualia-framework/templates/phase-prompt.md +0 -567
  142. package/framework/qualia-framework/templates/planner-subagent-prompt.md +0 -117
  143. package/framework/qualia-framework/templates/project.md +0 -184
  144. package/framework/qualia-framework/templates/projects/ai-agent.md +0 -156
  145. package/framework/qualia-framework/templates/projects/mobile-app.md +0 -181
  146. package/framework/qualia-framework/templates/projects/voice-agent.md +0 -134
  147. package/framework/qualia-framework/templates/projects/website.md +0 -137
  148. package/framework/qualia-framework/templates/requirements.md +0 -231
  149. package/framework/qualia-framework/templates/research-project/ARCHITECTURE.md +0 -204
  150. package/framework/qualia-framework/templates/research-project/FEATURES.md +0 -147
  151. package/framework/qualia-framework/templates/research-project/PITFALLS.md +0 -200
  152. package/framework/qualia-framework/templates/research-project/STACK.md +0 -120
  153. package/framework/qualia-framework/templates/research-project/SUMMARY.md +0 -170
  154. package/framework/qualia-framework/templates/research.md +0 -552
  155. package/framework/qualia-framework/templates/roadmap.md +0 -206
  156. package/framework/qualia-framework/templates/state.md +0 -179
  157. package/framework/qualia-framework/templates/summary-complex.md +0 -59
  158. package/framework/qualia-framework/templates/summary-minimal.md +0 -41
  159. package/framework/qualia-framework/templates/summary-standard.md +0 -48
  160. package/framework/qualia-framework/templates/summary.md +0 -246
  161. package/framework/qualia-framework/templates/user-setup.md +0 -311
  162. package/framework/qualia-framework/templates/verification-report.md +0 -322
  163. package/framework/qualia-framework/workflows/add-phase.md +0 -179
  164. package/framework/qualia-framework/workflows/add-todo.md +0 -157
  165. package/framework/qualia-framework/workflows/audit-milestone.md +0 -241
  166. package/framework/qualia-framework/workflows/check-todos.md +0 -176
  167. package/framework/qualia-framework/workflows/complete-milestone.md +0 -858
  168. package/framework/qualia-framework/workflows/diagnose-issues.md +0 -219
  169. package/framework/qualia-framework/workflows/discovery-phase.md +0 -289
  170. package/framework/qualia-framework/workflows/discuss-phase.md +0 -534
  171. package/framework/qualia-framework/workflows/execute-phase.md +0 -559
  172. package/framework/qualia-framework/workflows/execute-plan.md +0 -438
  173. package/framework/qualia-framework/workflows/help.md +0 -470
  174. package/framework/qualia-framework/workflows/insert-phase.md +0 -220
  175. package/framework/qualia-framework/workflows/list-phase-assumptions.md +0 -178
  176. package/framework/qualia-framework/workflows/map-codebase.md +0 -327
  177. package/framework/qualia-framework/workflows/new-milestone.md +0 -363
  178. package/framework/qualia-framework/workflows/new-project.md +0 -982
  179. package/framework/qualia-framework/workflows/pause-work.md +0 -122
  180. package/framework/qualia-framework/workflows/plan-milestone-gaps.md +0 -256
  181. package/framework/qualia-framework/workflows/plan-phase.md +0 -422
  182. package/framework/qualia-framework/workflows/progress.md +0 -389
  183. package/framework/qualia-framework/workflows/quick.md +0 -252
  184. package/framework/qualia-framework/workflows/remove-phase.md +0 -326
  185. package/framework/qualia-framework/workflows/research-phase.md +0 -74
  186. package/framework/qualia-framework/workflows/resume-project.md +0 -306
  187. package/framework/qualia-framework/workflows/set-profile.md +0 -80
  188. package/framework/qualia-framework/workflows/settings.md +0 -145
  189. package/framework/qualia-framework/workflows/transition.md +0 -556
  190. package/framework/qualia-framework/workflows/update.md +0 -197
  191. package/framework/qualia-framework/workflows/verify-phase.md +0 -195
  192. package/framework/qualia-framework/workflows/verify-work.md +0 -625
  193. package/framework/rules/context7.md +0 -14
  194. package/framework/rules/frontend.md +0 -33
  195. package/framework/rules/speed.md +0 -23
  196. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  197. package/framework/scripts/apply-retention.sh +0 -120
  198. package/framework/scripts/bootstrap-pop-os.sh +0 -354
  199. package/framework/scripts/claude-voice +0 -13
  200. package/framework/scripts/cleanup.sh +0 -131
  201. package/framework/scripts/cowork-mode.sh +0 -141
  202. package/framework/scripts/generate-project-claude-md.sh +0 -153
  203. package/framework/scripts/load-test-webhook.js +0 -172
  204. package/framework/scripts/say.py +0 -236
  205. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +0 -167
  206. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +0 -216
  207. package/framework/scripts/speak.py +0 -55
  208. package/framework/scripts/speak.sh +0 -18
  209. package/framework/scripts/status.sh +0 -138
  210. package/framework/scripts/sync-to-framework.sh +0 -65
  211. package/framework/scripts/voice-hotkey.py +0 -227
  212. package/framework/scripts/voice-input.sh +0 -51
  213. package/framework/skills/animate/SKILL.md +0 -202
  214. package/framework/skills/bolder/SKILL.md +0 -144
  215. package/framework/skills/browser-qa/SKILL.md +0 -536
  216. package/framework/skills/clarify/SKILL.md +0 -179
  217. package/framework/skills/client-handoff/SKILL.md +0 -135
  218. package/framework/skills/collab-onboard/SKILL.md +0 -111
  219. package/framework/skills/colorize/SKILL.md +0 -170
  220. package/framework/skills/critique/SKILL.md +0 -126
  221. package/framework/skills/deep-research/SKILL.md +0 -240
  222. package/framework/skills/delight/SKILL.md +0 -329
  223. package/framework/skills/deploy/SKILL.md +0 -261
  224. package/framework/skills/deploy-verify/SKILL.md +0 -377
  225. package/framework/skills/deploy-verify/scripts/canary-check.sh +0 -206
  226. package/framework/skills/deploy-verify/scripts/check-console-errors.js +0 -147
  227. package/framework/skills/deploy-verify/scripts/check-cwv.js +0 -139
  228. package/framework/skills/deploy-verify/scripts/project-detect.sh +0 -84
  229. package/framework/skills/deploy-verify/scripts/verify.sh +0 -548
  230. package/framework/skills/design-quieter/SKILL.md +0 -130
  231. package/framework/skills/distill/SKILL.md +0 -149
  232. package/framework/skills/docs-lookup/SKILL.md +0 -79
  233. package/framework/skills/fcm-notifications/SKILL.md +0 -125
  234. package/framework/skills/financial-ledger/SKILL.md +0 -1039
  235. package/framework/skills/frontend-master/NOTICE.md +0 -4
  236. package/framework/skills/frontend-master/SKILL.md +0 -127
  237. package/framework/skills/frontend-master/reference/color-and-contrast.md +0 -132
  238. package/framework/skills/frontend-master/reference/interaction-design.md +0 -123
  239. package/framework/skills/frontend-master/reference/motion-design.md +0 -99
  240. package/framework/skills/frontend-master/reference/responsive-design.md +0 -114
  241. package/framework/skills/frontend-master/reference/spatial-design.md +0 -100
  242. package/framework/skills/frontend-master/reference/typography.md +0 -131
  243. package/framework/skills/frontend-master/reference/ux-writing.md +0 -107
  244. package/framework/skills/harden/SKILL.md +0 -357
  245. package/framework/skills/i18n-rtl/SKILL.md +0 -752
  246. package/framework/skills/learn/SKILL.md +0 -95
  247. package/framework/skills/memory/SKILL.md +0 -50
  248. package/framework/skills/mobile-expo/SKILL.md +0 -977
  249. package/framework/skills/mobile-expo/references/store-checklist.md +0 -550
  250. package/framework/skills/nestjs-backend/README.md +0 -73
  251. package/framework/skills/nestjs-backend/SKILL.md +0 -446
  252. package/framework/skills/nestjs-backend/references/templates.md +0 -1173
  253. package/framework/skills/normalize/SKILL.md +0 -79
  254. package/framework/skills/onboard/SKILL.md +0 -242
  255. package/framework/skills/openrouter-agent/SKILL.md +0 -922
  256. package/framework/skills/polish/SKILL.md +0 -209
  257. package/framework/skills/pr/SKILL.md +0 -66
  258. package/framework/skills/qualia/SKILL.md +0 -199
  259. package/framework/skills/qualia-add-todo/SKILL.md +0 -68
  260. package/framework/skills/qualia-audit-milestone/SKILL.md +0 -95
  261. package/framework/skills/qualia-check-todos/SKILL.md +0 -55
  262. package/framework/skills/qualia-complete-milestone/SKILL.md +0 -134
  263. package/framework/skills/qualia-debug/SKILL.md +0 -149
  264. package/framework/skills/qualia-design/SKILL.md +0 -203
  265. package/framework/skills/qualia-discuss-phase/SKILL.md +0 -72
  266. package/framework/skills/qualia-evolve/SKILL.md +0 -200
  267. package/framework/skills/qualia-execute-phase/SKILL.md +0 -89
  268. package/framework/skills/qualia-framework-audit/SKILL.md +0 -604
  269. package/framework/skills/qualia-guide/SKILL.md +0 -32
  270. package/framework/skills/qualia-help/SKILL.md +0 -114
  271. package/framework/skills/qualia-idk/SKILL.md +0 -352
  272. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +0 -67
  273. package/framework/skills/qualia-new-milestone/SKILL.md +0 -72
  274. package/framework/skills/qualia-new-project/SKILL.md +0 -232
  275. package/framework/skills/qualia-pause-work/SKILL.md +0 -96
  276. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +0 -57
  277. package/framework/skills/qualia-plan-phase/SKILL.md +0 -104
  278. package/framework/skills/qualia-production-check/SKILL.md +0 -0
  279. package/framework/skills/qualia-progress/SKILL.md +0 -53
  280. package/framework/skills/qualia-quick/SKILL.md +0 -89
  281. package/framework/skills/qualia-report/SKILL.md +0 -166
  282. package/framework/skills/qualia-research-phase/SKILL.md +0 -88
  283. package/framework/skills/qualia-resume-work/SKILL.md +0 -62
  284. package/framework/skills/qualia-review/SKILL.md +0 -263
  285. package/framework/skills/qualia-start/SKILL.md +0 -161
  286. package/framework/skills/qualia-verify-work/SKILL.md +0 -132
  287. package/framework/skills/rag/SKILL.md +0 -750
  288. package/framework/skills/responsive/SKILL.md +0 -231
  289. package/framework/skills/retro/SKILL.md +0 -284
  290. package/framework/skills/sakani-conventions/SKILL.md +0 -136
  291. package/framework/skills/sakani-conventions/evals/evals.json +0 -23
  292. package/framework/skills/sakani-conventions/references/entities.md +0 -365
  293. package/framework/skills/sakani-conventions/references/error-codes.md +0 -95
  294. package/framework/skills/seo-master/SKILL.md +0 -490
  295. package/framework/skills/seo-master/references/checklist.md +0 -199
  296. package/framework/skills/seo-master/references/structured-data.md +0 -609
  297. package/framework/skills/ship/SKILL.md +0 -239
  298. package/framework/skills/stack-researcher/SKILL.md +0 -215
  299. package/framework/skills/status/SKILL.md +0 -154
  300. package/framework/skills/status/scripts/health-check.sh +0 -562
  301. package/framework/skills/subscription-payments/SKILL.md +0 -250
  302. package/framework/skills/supabase/SKILL.md +0 -973
  303. package/framework/skills/supabase/references/templates.md +0 -159
  304. package/framework/skills/team/SKILL.md +0 -67
  305. package/framework/skills/test-runner/SKILL.md +0 -202
  306. package/framework/skills/voice-agent/SKILL.md +0 -1312
  307. package/framework/skills/zoho-workflow/SKILL.md +0 -51
  308. package/framework/statusline-command.sh +0 -117
  309. package/framework/teams/default/inboxes/plan-04.json +0 -9
  310. package/framework/teams/review-team.md +0 -75
  311. package/framework/teams/ship-team.md +0 -86
  312. package/profiles/fawzi.json +0 -16
  313. package/profiles/hasan.json +0 -16
  314. package/profiles/moayad.json +0 -16
  315. package/templates/CLAUDE-owner.md +0 -52
  316. package/templates/CLAUDE.md.hbs +0 -58
  317. package/templates/env.claude.template +0 -12
  318. package/templates/settings.json +0 -172
  319. package/uninstall.sh +0 -90
  320. /package/{framework/rules → rules}/deployment.md +0 -0
  321. /package/{framework/rules → rules}/security.md +0 -0
package/bin/cli.js CHANGED
@@ -1,614 +1,419 @@
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;
51
- }
52
-
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;
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");
64
33
  }
65
34
 
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;
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}`);
74
39
  }
75
40
 
76
- function renderCLAUDEmd(profile) {
77
- let template = fs.readFileSync(path.join(TEMPLATES_DIR, 'CLAUDE.md.hbs'), 'utf8');
41
+ // ─── Commands ────────────────────────────────────────────
78
42
 
79
- const isOwner = profile.role === 'OWNER';
80
- const isDeveloper = profile.role === 'DEVELOPER';
81
- const isArabic = profile.language && profile.language.includes('ar');
82
-
83
- const roleTitle = isOwner ? 'Founder' : (profile.type === 'full-time' ? 'Full-time Developer' : 'Developer');
43
+ function cmdInstall() {
44
+ require("./install.js");
45
+ }
84
46
 
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
- };
47
+ function cmdVersion() {
48
+ banner();
49
+ const cfg = readConfig();
92
50
 
93
- for (const [key, val] of Object.entries(replacements)) {
94
- template = template.replace(new RegExp(escapeRegex(key), 'g'), val);
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}`);
95
57
  }
96
58
 
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);
103
-
104
- // Clean up empty lines
105
- template = template.replace(/\n{3,}/g, '\n\n');
106
-
107
- return template;
108
- }
109
-
110
- function escapeRegex(str) {
111
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112
- }
113
-
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 || '');
118
- });
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}`);
86
+ }
87
+ console.log("");
119
88
  }
120
89
 
121
- function mergeSettings(existingPath, templatePath) {
122
- const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
123
-
124
- if (!fs.existsSync(existingPath)) return template;
125
-
126
- const existing = JSON.parse(fs.readFileSync(existingPath, 'utf8'));
90
+ function cmdUpdate() {
91
+ banner();
92
+ const cfg = readConfig();
127
93
 
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;
135
- }
136
- }
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);
137
98
  }
138
99
 
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;
147
- }
100
+ console.log(` ${DIM}Current:${RESET} ${WHITE}${PKG.version}${RESET}`);
101
+ console.log(` ${DIM}Updating...${RESET}`);
102
+ console.log("");
148
103
 
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;
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);
155
120
  }
156
-
157
- return merged;
158
121
  }
159
122
 
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 => {
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
+ // 7 Qualia hook filenames — only these are removed from ~/.claude/hooks/,
130
+ // any other hooks the user dropped in there are left alone.
131
+ // Note: block-env-edit.js was retired in v3.2.0; the installer removes any
132
+ // lingering copy, and this uninstall list no longer references it.
133
+ const QUALIA_HOOK_FILES = [
134
+ "session-start.js",
135
+ "auto-update.js",
136
+ "branch-guard.js",
137
+ "pre-push.js",
138
+ "migration-guard.js",
139
+ "pre-deploy-gate.js",
140
+ "pre-compact.js",
141
+ ];
142
+
143
+ // 4 Qualia agents — only these are removed.
144
+ const QUALIA_AGENT_FILES = ["planner.md", "builder.md", "verifier.md", "qa-browser.md"];
145
+
146
+ // 3 Qualia bin scripts.
147
+ const QUALIA_BIN_FILES = ["state.js", "qualia-ui.js", "statusline.js"];
148
+
149
+ // 4 Qualia rules.
150
+ const QUALIA_RULE_FILES = ["security.md", "frontend.md", "design-reference.md", "deployment.md"];
151
+
152
+ function promptYesNo(question, defaultYes) {
153
+ return new Promise((resolve) => {
154
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
155
+ const suffix = defaultYes ? " (Y/n)" : " (y/N)";
156
+ rl.question(` ${WHITE}${question}${RESET}${suffix} `, (answer) => {
164
157
  rl.close();
165
- resolve(answer.trim());
158
+ const a = String(answer || "").trim().toLowerCase();
159
+ if (!a) return resolve(defaultYes);
160
+ resolve(a === "y" || a === "yes");
166
161
  });
167
162
  });
168
163
  }
169
164
 
170
- // ── Uninstall ──
171
- async function runUninstall() {
172
- log('');
173
- log(`${c.red}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
174
- log(`${c.red}\u2551${c.reset}${c.bold} QUALIA FRAMEWORK \u2014 UNINSTALLER ${c.reset}${c.red}\u2551${c.reset}`);
175
- log(`${c.red}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
176
- log('');
177
-
178
- if (!fs.existsSync(CLAUDE_DIR)) {
179
- warn('Nothing to uninstall \u2014 ~/.claude/ does not exist.');
180
- return;
181
- }
182
-
183
- // Count everything in ~/.claude/
184
- const totalFiles = countFilesRecursive(CLAUDE_DIR);
185
-
186
- log(`${c.bold} NUCLEAR UNINSTALL \u2014 deleting EVERYTHING in ~/.claude/${c.reset}`);
187
- log('');
188
- log(` ${c.red}\u2022${c.reset} ${totalFiles} files will be permanently deleted`);
189
- log(` ${c.red}\u2022${c.reset} settings.json will be reset to {}`);
190
- log(` ${c.red}\u2022${c.reset} All skills, agents, hooks, rules, knowledge \u2014 gone`);
191
- log(` ${c.red}\u2022${c.reset} All memory, projects, plans, cache \u2014 gone`);
192
- log(` ${c.red}\u2022${c.reset} CLAUDE.md, .env.claude, all config \u2014 gone`);
193
- log('');
194
-
195
- // Confirmation
196
- const force = process.argv.includes('--force');
197
- if (!force) {
198
- log(`${c.red} \u26A0 THIS CANNOT BE UNDONE.${c.reset}`);
199
- log(`${c.red} \u26A0 ~/.claude/ will be wiped clean. Nothing survives.${c.reset}`);
200
- log('');
201
- const answer = await prompt(` Type 'UNINSTALL' to confirm: `);
202
- if (answer !== 'UNINSTALL') {
203
- log('');
204
- ok('Cancelled. Nothing was deleted.');
205
- return;
165
+ function safeUnlink(p, counters) {
166
+ try {
167
+ if (fs.existsSync(p)) {
168
+ fs.unlinkSync(p);
169
+ counters.filesRemoved++;
206
170
  }
207
- log('');
171
+ } catch (e) {
172
+ counters.errors.push(`${p}: ${e.message}`);
208
173
  }
174
+ }
209
175
 
210
- // Step 1: Nuke everything in ~/.claude/ except settings.json (we handle that separately)
211
- step(1, 2, 'Nuking ~/.claude/ ...');
212
-
213
- for (const entry of fs.readdirSync(CLAUDE_DIR, { withFileTypes: true })) {
214
- const p = path.join(CLAUDE_DIR, entry.name);
215
-
216
- // Skip settings.json — we reset it in step 2
217
- if (entry.name === 'settings.json') continue;
218
-
219
- try {
220
- if (entry.isDirectory()) {
221
- fs.rmSync(p, { recursive: true, force: true });
222
- ok(`${entry.name}/`);
223
- } else if (isSymlink(p)) {
224
- fs.unlinkSync(p);
225
- ok(`${entry.name} (symlink)`);
226
- } else {
227
- fs.unlinkSync(p);
228
- ok(entry.name);
229
- }
230
- } catch (e) {
231
- fail(`${entry.name}: ${e.message}`);
176
+ function safeRmDir(p, counters) {
177
+ try {
178
+ if (fs.existsSync(p)) {
179
+ fs.rmSync(p, { recursive: true, force: true });
180
+ counters.dirsRemoved++;
232
181
  }
182
+ } catch (e) {
183
+ counters.errors.push(`${p}: ${e.message}`);
233
184
  }
234
-
235
- // Step 2: Reset settings.json to empty
236
- step(2, 2, 'Resetting settings.json ...');
237
- const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
238
- if (fs.existsSync(settingsPath)) {
239
- fs.writeFileSync(settingsPath, '{}\n');
240
- ok('settings.json reset to {}');
241
- } else {
242
- ok('No settings.json found');
243
- }
244
-
245
- log('');
246
- log(`${c.green}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
247
- log(`${c.green}\u2551${c.reset}${c.bold} \u2713 NUKED \u2014 ~/.claude/ is clean ${c.reset}${c.green}\u2551${c.reset}`);
248
- log(`${c.green}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
249
- log('');
250
- log(` ${c.dim}Claude Code will use default behavior on next launch.${c.reset}`);
251
- log('');
252
- }
253
-
254
- function isSymlink(p) {
255
- try { fs.lstatSync(p); return fs.lstatSync(p).isSymbolicLink(); } catch { return false; }
256
185
  }
257
186
 
258
- // ── Main ──
259
- async function main() {
260
- const command = process.argv[2];
261
-
262
- if (command === 'update') {
263
- await runUpdate();
264
- } else if (command === 'verify') {
265
- runVerify();
266
- } else if (command === 'uninstall') {
267
- await runUninstall();
268
- } else {
269
- await runInstall();
187
+ function cleanSettingsJson(counters) {
188
+ const settingsPath = path.join(CLAUDE_DIR, "settings.json");
189
+ if (!fs.existsSync(settingsPath)) return;
190
+ let settings;
191
+ try {
192
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
193
+ } catch (e) {
194
+ counters.errors.push(`settings.json: ${e.message}`);
195
+ return;
270
196
  }
271
- }
272
197
 
273
- async function runInstall() {
274
- log('');
275
- log(`${c.cyan}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
276
- log(`${c.cyan}\u2551${c.reset}${c.bold} QUALIA FRAMEWORK \u2014 INSTALLER ${c.reset}${c.cyan}\u2551${c.reset}`);
277
- log(`${c.cyan}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
278
- log('');
279
-
280
- const profiles = loadProfiles();
281
- const code = await prompt(` Enter your employee code: `);
282
- const profile = profiles[code.toUpperCase()];
283
-
284
- if (!profile) {
285
- log('');
286
- fail(`Unknown code "${code}". Contact Fawzi for your employee code.`);
287
- log(`${c.dim} Available format: QS-NAME-YEAR${c.reset}`);
288
- process.exit(1);
289
- }
198
+ // Only remove entries that point at qualia paths. Leave everything else.
199
+ const isQualiaCommand = (cmd) =>
200
+ typeof cmd === "string" && (cmd.includes("qualia") || cmd.includes(".claude/hooks/") || cmd.includes(".claude/bin/"));
290
201
 
291
- log('');
292
- log(` ${c.green}\u2192${c.reset} Welcome, ${c.bold}${profile.name}${c.reset}! Setting up your Qualia environment...`);
293
-
294
- const TOTAL = 6;
295
-
296
- // Step 1: Backup existing
297
- step(1, TOTAL, 'Checking existing ~/.claude/ ...');
298
- if (fs.existsSync(CLAUDE_DIR)) {
299
- const date = new Date().toISOString().split('T')[0];
300
- const backupDir = `${CLAUDE_DIR}.backup-${date}`;
301
- if (!fs.existsSync(backupDir)) {
302
- // Don't backup the whole thing just the config files we'll overwrite
303
- fs.mkdirSync(backupDir, { recursive: true });
304
- for (const f of ['CLAUDE.md', 'settings.json', '.env.claude']) {
305
- const src = path.join(CLAUDE_DIR, f);
306
- if (fs.existsSync(src)) {
307
- fs.copyFileSync(src, path.join(backupDir, f));
202
+ const filterHookArray = (arr) => {
203
+ if (!Array.isArray(arr)) return arr;
204
+ return arr
205
+ .map((entry) => {
206
+ if (!entry || !Array.isArray(entry.hooks)) return entry;
207
+ const hooks = entry.hooks.filter((h) => !isQualiaCommand(h && h.command));
208
+ return { ...entry, hooks };
209
+ })
210
+ .filter((entry) => Array.isArray(entry.hooks) && entry.hooks.length > 0);
211
+ };
212
+
213
+ if (settings.hooks && typeof settings.hooks === "object") {
214
+ for (const key of ["SessionStart", "PreToolUse", "PreCompact"]) {
215
+ if (settings.hooks[key]) {
216
+ const cleaned = filterHookArray(settings.hooks[key]);
217
+ if (cleaned && cleaned.length > 0) {
218
+ settings.hooks[key] = cleaned;
219
+ } else {
220
+ delete settings.hooks[key];
308
221
  }
309
222
  }
310
- ok(`Config backed up to ${c.dim}~/.claude.backup-${date}/${c.reset}`);
311
- } else {
312
- ok('Backup already exists for today');
313
223
  }
314
- } else {
315
- fs.mkdirSync(CLAUDE_DIR, { recursive: true });
316
- ok('Created ~/.claude/');
224
+ // If hooks is now empty, remove it entirely.
225
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
317
226
  }
318
227
 
319
- // Step 2: Copy framework files
320
- step(2, TOTAL, 'Installing framework files ...');
321
-
322
- const dirs = ['skills', 'hooks', 'agents', 'rules', 'qualia-framework', 'scripts', 'knowledge'];
323
- for (const dir of dirs) {
324
- const src = path.join(FRAMEWORK_DIR, dir);
325
- const dest = path.join(CLAUDE_DIR, dir);
326
- if (fs.existsSync(src)) {
327
- copyDirSync(src, dest);
228
+ // Status line only drop it if it points at our renderer.
229
+ if (settings.statusLine && typeof settings.statusLine === "object") {
230
+ const cmd = settings.statusLine.command || "";
231
+ if (isQualiaCommand(cmd) || cmd.includes("statusline.js") || cmd.includes("qualia-ui")) {
232
+ delete settings.statusLine;
328
233
  }
329
234
  }
330
235
 
331
- // Copy standalone files (both .sh and .js for cross-platform)
332
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
333
- const src = path.join(FRAMEWORK_DIR, f);
334
- if (fs.existsSync(src)) {
335
- fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
336
- }
337
- }
236
+ // Qualia-specific spinner overrides.
237
+ if (settings.spinnerVerbs) delete settings.spinnerVerbs;
238
+ if (settings.spinnerTipsOverride) delete settings.spinnerTipsOverride;
338
239
 
339
- // Make hooks executable
340
- const hooksDir = path.join(CLAUDE_DIR, 'hooks');
341
- if (fs.existsSync(hooksDir)) {
342
- for (const f of fs.readdirSync(hooksDir)) {
343
- if (f.endsWith('.sh')) {
344
- fs.chmodSync(path.join(hooksDir, f), 0o755);
345
- }
346
- }
347
- }
348
- // Make standalone scripts executable
349
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
350
- const p = path.join(CLAUDE_DIR, f);
351
- if (fs.existsSync(p)) try { fs.chmodSync(p, 0o755); } catch {}
352
- }
353
- // Make qualia-framework bin executable
354
- const engineBin = path.join(CLAUDE_DIR, 'qualia-framework', 'bin');
355
- if (fs.existsSync(engineBin)) {
356
- for (const f of fs.readdirSync(engineBin)) {
357
- const p = path.join(engineBin, f);
358
- try { fs.chmodSync(p, 0o755); } catch {}
359
- }
360
- }
361
-
362
- ok(`${countDir(path.join(CLAUDE_DIR, 'skills'), 'dirs')} skills`);
363
- ok(`${countDir(hooksDir, 'files')} hooks (chmod +x)`);
364
- ok(`${countDir(path.join(CLAUDE_DIR, 'agents'), 'files')} agents`);
365
- ok(`${countDir(path.join(CLAUDE_DIR, 'rules'), 'files')} rule files`);
366
- ok('qualia-framework');
367
- ok('knowledge base');
368
-
369
- // Step 3: Generate CLAUDE.md
370
- step(3, TOTAL, 'Generating your CLAUDE.md ...');
371
- const claudeMd = renderCLAUDEmd(profile);
372
- fs.writeFileSync(path.join(CLAUDE_DIR, 'CLAUDE.md'), claudeMd);
373
-
374
- const roleDesc = profile.role === 'OWNER' ? 'Full authority' :
375
- `Feature branches${profile.permissions?.canDeploy ? ', can deploy' : ', no prod deploy'}`;
376
- ok(`Identity: ${profile.name} \u2014 ${profile.role === 'OWNER' ? 'Founder' : 'Developer'} at Qualia Solutions`);
377
- ok(`Role: ${profile.role} (${roleDesc})`);
378
-
379
- // Step 4: Configure settings.json
380
- step(4, TOTAL, 'Configuring settings.json ...');
381
- const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
382
- const templateSettingsPath = path.join(TEMPLATES_DIR, 'settings.json');
383
- const mergedSettings = mergeSettings(settingsPath, templateSettingsPath);
384
-
385
- // Fix SUDO_ASKPASS to absolute path
386
- mergedSettings.env.SUDO_ASKPASS = path.join(CLAUDE_DIR, 'askpass.sh');
387
- mergedSettings.env.CLAUDE_ENV_FILE = path.join(CLAUDE_DIR, '.env.claude');
388
-
389
- fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n');
390
- ok('Hooks wired');
391
- ok('Preserved your existing API keys');
392
- ok('Settings configured');
393
-
394
- // Step 5: Environment
395
- step(5, TOTAL, 'Setting up environment ...');
396
- const envPath = path.join(CLAUDE_DIR, '.env.claude');
397
- if (fs.existsSync(envPath)) {
398
- ok('.env.claude already exists (preserved)');
399
- } else {
400
- fs.copyFileSync(path.join(TEMPLATES_DIR, 'env.claude.template'), envPath);
401
- fs.chmodSync(envPath, 0o600);
402
- warn(`Fill in your API keys: ${c.dim}~/.claude/.env.claude${c.reset}`);
403
- }
240
+ // Leave settings.env alone — the user may have other env vars in there.
404
241
 
405
- // Create required directories
406
- for (const d of ['projects', 'session-env', 'backups']) {
407
- fs.mkdirSync(path.join(CLAUDE_DIR, d), { recursive: true });
242
+ try {
243
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
244
+ counters.settingsCleaned = true;
245
+ } catch (e) {
246
+ counters.errors.push(`settings.json write: ${e.message}`);
408
247
  }
248
+ }
409
249
 
410
- // Step 6: Verify
411
- step(6, TOTAL, 'Verifying installation ...');
412
-
413
- let hooksOk = 0;
414
- const hooksTotal = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh')).length;
415
- for (const f of fs.readdirSync(hooksDir)) {
416
- if (!f.endsWith('.sh')) continue;
417
- try {
418
- const stat = fs.statSync(path.join(hooksDir, f));
419
- if (stat.mode & 0o111) hooksOk++;
420
- } catch {}
421
- }
422
- if (hooksOk === hooksTotal) {
423
- ok(`${hooksOk}/${hooksTotal} hooks executable`);
424
- } else {
425
- warn(`${hooksOk}/${hooksTotal} hooks executable`);
426
- }
250
+ async function cmdUninstall() {
251
+ banner();
427
252
 
428
- try {
429
- JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
430
- ok('settings.json valid JSON');
431
- } catch {
432
- fail('settings.json invalid JSON!');
433
- }
253
+ const args = process.argv.slice(3);
254
+ const skipConfirm = args.includes("-y") || args.includes("--yes");
434
255
 
435
- if (fs.existsSync(path.join(CLAUDE_DIR, 'CLAUDE.md'))) {
436
- ok('CLAUDE.md generated');
256
+ const cfg = readConfig();
257
+ console.log("");
258
+ if (cfg.installed_by) {
259
+ console.log(` ${DIM}User:${RESET} ${WHITE}${cfg.installed_by}${RESET} ${DIM}(${cfg.role || "?"})${RESET}`);
437
260
  } else {
438
- fail('CLAUDE.md missing!');
261
+ console.log(` ${DIM}No Qualia config found at${RESET} ${WHITE}${CONFIG_FILE}${RESET}`);
439
262
  }
263
+ console.log("");
440
264
 
441
- // Done
442
- log('');
443
- log(`${c.green}\u2554${'═'.repeat(54)}\u2557${c.reset}`);
444
- log(`${c.green}\u2551${c.reset}${c.bold} \u2713 INSTALLED \u2014 Run /qualia-start in Claude Code ${c.reset}${c.green}\u2551${c.reset}`);
445
- log(`${c.green}\u255A${'═'.repeat(54)}\u255D${c.reset}`);
446
- log('');
447
- log(` ${c.dim}Update later: npx github:Qualiasolutions/qualia-framework update${c.reset}`);
448
- log('');
449
- }
450
-
451
- async function runUpdate() {
452
- log('');
453
- log(`${c.cyan}\u25C6 Qualia Framework \u2014 Update${c.reset}`);
454
- log('');
455
-
456
- if (!fs.existsSync(CLAUDE_DIR)) {
457
- fail('No ~/.claude/ found. Run the installer first (without "update").');
458
- process.exit(1);
265
+ if (!skipConfirm) {
266
+ const confirm = await promptYesNo("Are you sure you want to uninstall the Qualia Framework?", false);
267
+ if (!confirm) {
268
+ console.log("");
269
+ console.log(` ${DIM}Aborted.${RESET}`);
270
+ console.log("");
271
+ return;
272
+ }
459
273
  }
460
274
 
461
- // Authenticate always require code on update
462
- const profiles = loadProfiles();
463
- const code = await prompt(` Enter your employee code: `);
464
- const profile = profiles[code.toUpperCase()];
465
-
466
- if (!profile) {
467
- log('');
468
- fail(`Unknown code "${code}". Contact Fawzi for your employee code.`);
469
- process.exit(1);
275
+ // Preserve knowledge base by default.
276
+ let preserveKnowledge = true;
277
+ if (!skipConfirm) {
278
+ preserveKnowledge = await promptYesNo(
279
+ "Preserve knowledge base? (your learned patterns, fixes, client prefs)",
280
+ true
281
+ );
470
282
  }
471
283
 
472
- log(` ${c.green}\u2192${c.reset} Updating for ${c.bold}${profile.name}${c.reset} (${profile.role})`);
473
- log('');
284
+ console.log("");
285
+ console.log(` ${DIM}Removing framework files...${RESET}`);
286
+ console.log("");
474
287
 
475
- // Count what's changing
476
- const frameworkDirs = ['skills', 'hooks', 'agents', 'rules', 'qualia-framework', 'scripts', 'knowledge'];
477
- const counts = {};
478
- let totalFiles = 0;
288
+ const counters = { filesRemoved: 0, dirsRemoved: 0, settingsCleaned: false, errors: [] };
479
289
 
480
- for (const dir of frameworkDirs) {
481
- const src = path.join(FRAMEWORK_DIR, dir);
482
- if (fs.existsSync(src)) {
483
- const count = countFilesRecursive(src);
484
- counts[dir] = count;
485
- totalFiles += count;
290
+ // Skills any directory starting with "qualia" under ~/.claude/skills/.
291
+ const skillsDir = path.join(CLAUDE_DIR, "skills");
292
+ try {
293
+ if (fs.existsSync(skillsDir)) {
294
+ for (const name of fs.readdirSync(skillsDir)) {
295
+ if (name === "qualia" || name.startsWith("qualia-")) {
296
+ safeRmDir(path.join(skillsDir, name), counters);
297
+ }
298
+ }
486
299
  }
300
+ } catch (e) {
301
+ counters.errors.push(`skills scan: ${e.message}`);
487
302
  }
488
303
 
489
- // Show what will be updated
490
- log(` ${c.bold}Components to update:${c.reset}`);
491
- for (const [dir, count] of Object.entries(counts)) {
492
- log(` ${c.dim}\u2022${c.reset} ${dir} (${count} files)`);
304
+ // Agents only the 4 Qualia ones.
305
+ for (const f of QUALIA_AGENT_FILES) {
306
+ safeUnlink(path.join(CLAUDE_DIR, "agents", f), counters);
493
307
  }
494
- log(` ${c.bold}Total: ${totalFiles} files${c.reset}`);
495
- log('');
496
-
497
- // Update framework files
498
- let updated = 0;
499
- for (const dir of frameworkDirs) {
500
- const src = path.join(FRAMEWORK_DIR, dir);
501
- const dest = path.join(CLAUDE_DIR, dir);
502
- if (fs.existsSync(src)) {
503
- copyDirSync(src, dest);
504
- updated++;
505
- ok(`${dir} (${counts[dir]} files)`);
506
- }
308
+
309
+ // Hooks — only the 8 Qualia ones.
310
+ for (const f of QUALIA_HOOK_FILES) {
311
+ safeUnlink(path.join(CLAUDE_DIR, "hooks", f), counters);
507
312
  }
508
313
 
509
- // Copy standalone files (both .sh and .js for cross-platform)
510
- for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
511
- const src = path.join(FRAMEWORK_DIR, f);
512
- if (fs.existsSync(src)) {
513
- fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
514
- try { fs.chmodSync(path.join(CLAUDE_DIR, f), 0o755); } catch {}
515
- }
314
+ // Bin scripts only the 3 Qualia ones.
315
+ for (const f of QUALIA_BIN_FILES) {
316
+ safeUnlink(path.join(CLAUDE_DIR, "bin", f), counters);
516
317
  }
517
318
 
518
- // Re-chmod hooks
519
- const hooksDir = path.join(CLAUDE_DIR, 'hooks');
520
- if (fs.existsSync(hooksDir)) {
521
- for (const f of fs.readdirSync(hooksDir)) {
522
- if (f.endsWith('.sh')) try { fs.chmodSync(path.join(hooksDir, f), 0o755); } catch {}
523
- }
319
+ // Rules — all 4.
320
+ for (const f of QUALIA_RULE_FILES) {
321
+ safeUnlink(path.join(CLAUDE_DIR, "rules", f), counters);
524
322
  }
525
323
 
526
- // Re-chmod engine bin
527
- const engineBin = path.join(CLAUDE_DIR, 'qualia-framework', 'bin');
528
- if (fs.existsSync(engineBin)) {
529
- for (const f of fs.readdirSync(engineBin)) {
530
- try { fs.chmodSync(path.join(engineBin, f), 0o755); } catch {}
531
- }
324
+ // Templates directory (entire).
325
+ safeRmDir(path.join(CLAUDE_DIR, "qualia-templates"), counters);
326
+
327
+ // Knowledge directory (optional preservation).
328
+ if (!preserveKnowledge) {
329
+ safeRmDir(path.join(CLAUDE_DIR, "knowledge"), counters);
532
330
  }
533
331
 
534
- // Regenerate CLAUDE.md for this person's role
535
- const claudeMd = renderCLAUDEmd(profile);
536
- fs.writeFileSync(path.join(CLAUDE_DIR, 'CLAUDE.md'), claudeMd);
537
- ok(`CLAUDE.md regenerated for ${profile.name} (${profile.role})`);
538
-
539
- // Merge settings.json hooks (add new hooks without overwriting user config)
540
- const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
541
- const templateSettingsPath = path.join(TEMPLATES_DIR, 'settings.json');
542
- if (fs.existsSync(settingsPath) && fs.existsSync(templateSettingsPath)) {
543
- const mergedSettings = mergeSettings(settingsPath, templateSettingsPath);
544
- mergedSettings.env.SUDO_ASKPASS = path.join(CLAUDE_DIR, 'askpass.sh');
545
- mergedSettings.env.CLAUDE_ENV_FILE = path.join(CLAUDE_DIR, '.env.claude');
546
- fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n');
547
- ok('settings.json hooks merged');
332
+ // Config + state files.
333
+ safeUnlink(path.join(CLAUDE_DIR, ".qualia-config.json"), counters);
334
+ safeUnlink(path.join(CLAUDE_DIR, ".qualia-last-update-check"), counters);
335
+ safeUnlink(path.join(CLAUDE_DIR, ".erp-api-key"), counters);
336
+ safeUnlink(path.join(CLAUDE_DIR, "qualia-guide.md"), counters);
337
+
338
+ // Clean settings.json surgically.
339
+ cleanSettingsJson(counters);
340
+
341
+ // Summary.
342
+ console.log("");
343
+ console.log(`${TEAL} ⬢ Uninstall complete${RESET}`);
344
+ console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
345
+ console.log(` ${DIM}Files removed:${RESET} ${WHITE}${counters.filesRemoved}${RESET}`);
346
+ console.log(` ${DIM}Directories removed:${RESET} ${WHITE}${counters.dirsRemoved}${RESET}`);
347
+ console.log(
348
+ ` ${DIM}settings.json:${RESET} ${counters.settingsCleaned ? `${GREEN}cleaned ✓${RESET}` : `${DIM}not present${RESET}`}`
349
+ );
350
+ if (preserveKnowledge) {
351
+ console.log(` ${DIM}Knowledge base:${RESET} ${GREEN}preserved ✓${RESET}`);
352
+ } else {
353
+ console.log(` ${DIM}Knowledge base:${RESET} ${YELLOW}removed${RESET}`);
548
354
  }
549
355
 
550
- // Clean up removed directories from old versions
551
- for (const stale of ['commands', 'config', 'core', 'qualia-engine']) {
552
- const p = path.join(CLAUDE_DIR, stale);
553
- if (fs.existsSync(p)) {
554
- fs.rmSync(p, { recursive: true, force: true });
555
- ok(`removed stale ${stale}/`);
356
+ if (counters.errors.length > 0) {
357
+ console.log("");
358
+ console.log(` ${YELLOW}${counters.errors.length} warning(s):${RESET}`);
359
+ for (const err of counters.errors.slice(0, 5)) {
360
+ console.log(` ${DIM}${err}${RESET}`);
556
361
  }
557
362
  }
558
363
 
559
- log('');
560
- log(`${c.green}\u25C6${c.reset} ${c.bold}Updated ${updated} components (${totalFiles} files)${c.reset}`);
561
- log(`${c.dim} .env.claude preserved. CLAUDE.md regenerated for ${profile.name}.${c.reset}`);
562
- log('');
364
+ console.log("");
365
+ console.log(
366
+ ` ${YELLOW}Manual step:${RESET} edit ${WHITE}~/.claude/CLAUDE.md${RESET} to remove the Qualia Framework section if desired.`
367
+ );
368
+ console.log("");
563
369
  }
564
370
 
565
- function countFilesRecursive(dir) {
566
- let count = 0;
567
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
568
- if (entry.isDirectory()) {
569
- count += countFilesRecursive(path.join(dir, entry.name));
570
- } else {
571
- count++;
572
- }
573
- }
574
- return count;
371
+ function cmdHelp() {
372
+ banner();
373
+ console.log("");
374
+ console.log(` ${WHITE}Commands:${RESET}`);
375
+ console.log(` npx qualia-framework ${TEAL}install${RESET} Install or reinstall the framework`);
376
+ console.log(` npx qualia-framework ${TEAL}update${RESET} Update to the latest version`);
377
+ console.log(` npx qualia-framework ${TEAL}version${RESET} Show installed version + check for updates`);
378
+ console.log(` npx qualia-framework ${TEAL}uninstall${RESET} Clean removal from ~/.claude/ (${DIM}-y to skip prompts${RESET})`);
379
+ console.log("");
380
+ console.log(` ${WHITE}After install:${RESET}`);
381
+ console.log(` ${TG}/qualia${RESET} What should I do next?`);
382
+ console.log(` ${TG}/qualia-new${RESET} Set up a new project`);
383
+ console.log(` ${TG}/qualia-plan${RESET} Plan a phase`);
384
+ console.log(` ${TG}/qualia-build${RESET} Build it (parallel tasks)`);
385
+ console.log(` ${TG}/qualia-verify${RESET} Verify it works`);
386
+ console.log(` ${TG}/qualia-design${RESET} One-shot design fix`);
387
+ console.log(` ${TG}/qualia-debug${RESET} Structured debugging`);
388
+ console.log(` ${TG}/qualia-review${RESET} Production audit`);
389
+ console.log(` ${TG}/qualia-ship${RESET} Deploy to production`);
390
+ console.log(` ${TG}/qualia-report${RESET} Log your work`);
391
+ console.log("");
575
392
  }
576
393
 
577
- function runVerify() {
578
- log('');
579
- log(`${c.cyan}Qualia Framework \u2014 Verify${c.reset}`);
580
- log('');
581
-
582
- let pass = 0;
583
- let total = 0;
584
-
585
- const checks = [
586
- ['CLAUDE.md', () => fs.existsSync(path.join(CLAUDE_DIR, 'CLAUDE.md'))],
587
- ['settings.json', () => { try { JSON.parse(fs.readFileSync(path.join(CLAUDE_DIR, 'settings.json'), 'utf8')); return true; } catch { return false; } }],
588
- ['hooks/', () => fs.existsSync(path.join(CLAUDE_DIR, 'hooks'))],
589
- ['skills/', () => countDir(path.join(CLAUDE_DIR, 'skills'), 'dirs') > 50],
590
- ['agents/', () => countDir(path.join(CLAUDE_DIR, 'agents'), 'files') > 15],
591
- ['qualia-framework/', () => fs.existsSync(path.join(CLAUDE_DIR, 'qualia-framework', 'bin'))],
592
- ['rules/', () => countDir(path.join(CLAUDE_DIR, 'rules'), 'files') >= 4],
593
- ['.env.claude', () => fs.existsSync(path.join(CLAUDE_DIR, '.env.claude'))],
594
- ];
595
-
596
- for (const [name, check] of checks) {
597
- total++;
598
- if (check()) { ok(name); pass++; }
599
- else { fail(name); }
600
- }
601
-
602
- log('');
603
- if (pass === total) {
604
- log(`${c.green}All ${total} checks passed.${c.reset}`);
605
- } else {
606
- log(`${c.yellow}${pass}/${total} checks passed.${c.reset}`);
607
- }
608
- log('');
394
+ // ─── Main ────────────────────────────────────────────────
395
+ const cmd = process.argv[2];
396
+
397
+ switch (cmd) {
398
+ case "install":
399
+ cmdInstall();
400
+ break;
401
+ case "version":
402
+ case "-v":
403
+ case "--version":
404
+ cmdVersion();
405
+ break;
406
+ case "update":
407
+ case "upgrade":
408
+ cmdUpdate();
409
+ break;
410
+ case "uninstall":
411
+ case "remove":
412
+ cmdUninstall().catch((e) => {
413
+ console.error(`${RED} Uninstall failed: ${e.message}${RESET}`);
414
+ process.exit(1);
415
+ });
416
+ break;
417
+ default:
418
+ cmdHelp();
609
419
  }
610
-
611
- main().catch(err => {
612
- console.error(`${c.red}Error:${c.reset}`, err.message);
613
- process.exit(1);
614
- });