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.
- package/CLAUDE.md +63 -0
- package/README.md +108 -30
- package/agents/builder.md +110 -0
- package/agents/planner.md +186 -0
- package/agents/qa-browser.md +186 -0
- package/agents/verifier.md +369 -0
- package/bin/cli.js +706 -417
- package/bin/install.js +622 -0
- package/bin/qualia-ui.js +284 -0
- package/bin/state.js +824 -0
- package/bin/statusline.js +252 -0
- package/docs/erp-contract.md +161 -0
- package/guide.md +63 -0
- package/hooks/auto-update.js +117 -0
- package/hooks/block-env-edit.js +52 -0
- package/hooks/branch-guard.js +68 -0
- package/hooks/migration-guard.js +83 -0
- package/hooks/pre-compact.js +52 -0
- package/hooks/pre-deploy-gate.js +149 -0
- package/hooks/pre-push.js +53 -0
- package/hooks/session-start.js +126 -0
- package/package.json +31 -17
- package/rules/design-reference.md +179 -0
- package/rules/frontend.md +126 -0
- package/rules/infrastructure.md +87 -0
- package/skills/qualia/SKILL.md +88 -0
- package/skills/qualia-build/SKILL.md +115 -0
- package/skills/qualia-debug/SKILL.md +87 -0
- package/skills/qualia-design/SKILL.md +99 -0
- package/skills/qualia-handoff/SKILL.md +66 -0
- package/skills/qualia-help/SKILL.md +60 -0
- package/skills/qualia-idk/SKILL.md +8 -0
- package/skills/qualia-learn/SKILL.md +111 -0
- package/skills/qualia-new/SKILL.md +323 -0
- package/skills/qualia-pause/SKILL.md +63 -0
- package/skills/qualia-plan/SKILL.md +101 -0
- package/skills/qualia-polish/SKILL.md +207 -0
- package/skills/qualia-quick/SKILL.md +37 -0
- package/skills/qualia-report/SKILL.md +114 -0
- package/skills/qualia-resume/SKILL.md +49 -0
- package/skills/qualia-review/SKILL.md +161 -0
- package/skills/qualia-ship/SKILL.md +90 -0
- package/skills/qualia-skill-new/SKILL.md +167 -0
- package/skills/qualia-task/SKILL.md +91 -0
- package/skills/qualia-test/SKILL.md +134 -0
- package/skills/qualia-verify/SKILL.md +113 -0
- package/templates/DESIGN.md +475 -0
- package/templates/help.html +476 -0
- package/templates/plan.md +42 -0
- package/templates/project.md +22 -0
- package/templates/state.md +27 -0
- package/templates/tracking.json +20 -0
- package/tests/bin.test.sh +687 -0
- package/tests/hooks.test.sh +384 -0
- package/tests/runner.js +1956 -0
- package/tests/state.test.sh +713 -0
- package/tests/statusline.test.sh +243 -0
- package/bin/collect-metrics.sh +0 -62
- package/framework/.claudeignore +0 -51
- package/framework/CLAUDE.md +0 -51
- package/framework/MCP_SETUP.md +0 -229
- package/framework/agents/architecture-strategist.md +0 -53
- package/framework/agents/backend-agent.md +0 -150
- package/framework/agents/code-simplicity-reviewer.md +0 -86
- package/framework/agents/frontend-agent.md +0 -111
- package/framework/agents/kieran-typescript-reviewer.md +0 -96
- package/framework/agents/performance-oracle.md +0 -111
- package/framework/agents/qualia-codebase-mapper.md +0 -761
- package/framework/agents/qualia-debugger.md +0 -1204
- package/framework/agents/qualia-executor.md +0 -882
- package/framework/agents/qualia-integration-checker.md +0 -424
- package/framework/agents/qualia-phase-researcher.md +0 -457
- package/framework/agents/qualia-plan-checker.md +0 -700
- package/framework/agents/qualia-planner.md +0 -1245
- package/framework/agents/qualia-project-researcher.md +0 -603
- package/framework/agents/qualia-research-synthesizer.md +0 -200
- package/framework/agents/qualia-roadmapper.md +0 -606
- package/framework/agents/qualia-verifier.md +0 -686
- package/framework/agents/red-team-qa.md +0 -130
- package/framework/agents/security-auditor.md +0 -72
- package/framework/agents/team-orchestrator.md +0 -229
- package/framework/agents/teams/framework-audit-team.md +0 -66
- package/framework/agents/teams/full-stack-team.md +0 -48
- package/framework/agents/teams/optimize-team.md +0 -53
- package/framework/agents/teams/review-team.md +0 -70
- package/framework/agents/teams/ship-team.md +0 -86
- package/framework/agents/test-agent.md +0 -182
- package/framework/hooks/auto-format.sh +0 -54
- package/framework/hooks/block-env-edit.sh +0 -42
- package/framework/hooks/branch-guard.sh +0 -43
- package/framework/hooks/confirm-delete.sh +0 -59
- package/framework/hooks/migration-validate.sh +0 -77
- package/framework/hooks/notification-speak.sh +0 -16
- package/framework/hooks/pre-commit.sh +0 -100
- package/framework/hooks/pre-compact.sh +0 -56
- package/framework/hooks/pre-deploy-gate.sh +0 -160
- package/framework/hooks/qualia-colors.sh +0 -32
- package/framework/hooks/retention-cleanup.sh +0 -62
- package/framework/hooks/save-session-state.sh +0 -185
- package/framework/hooks/session-context-loader.sh +0 -96
- package/framework/hooks/session-learn.sh +0 -32
- package/framework/hooks/skill-announce.sh +0 -123
- package/framework/hooks/tool-error-announce.sh +0 -27
- package/framework/install.ps1 +0 -323
- package/framework/install.sh +0 -313
- package/framework/qualia-framework/VERSION +0 -1
- package/framework/qualia-framework/assets/qualia-logo.png +0 -0
- package/framework/qualia-framework/bin/collect-metrics.sh +0 -67
- package/framework/qualia-framework/bin/generate-report-docx.py +0 -429
- package/framework/qualia-framework/bin/qualia-tools.js +0 -2201
- package/framework/qualia-framework/bin/qualia-tools.test.js +0 -1054
- package/framework/qualia-framework/references/checkpoints.md +0 -775
- package/framework/qualia-framework/references/completion-checklists.md +0 -359
- package/framework/qualia-framework/references/continuation-format.md +0 -249
- package/framework/qualia-framework/references/continuation-prompt.md +0 -97
- package/framework/qualia-framework/references/decimal-phase-calculation.md +0 -65
- package/framework/qualia-framework/references/design-quality.md +0 -56
- package/framework/qualia-framework/references/employee-guide.md +0 -167
- package/framework/qualia-framework/references/git-integration.md +0 -254
- package/framework/qualia-framework/references/git-planning-commit.md +0 -50
- package/framework/qualia-framework/references/model-profile-resolution.md +0 -32
- package/framework/qualia-framework/references/model-profiles.md +0 -73
- package/framework/qualia-framework/references/phase-argument-parsing.md +0 -61
- package/framework/qualia-framework/references/planning-config.md +0 -195
- package/framework/qualia-framework/references/questioning.md +0 -141
- package/framework/qualia-framework/references/tdd.md +0 -263
- package/framework/qualia-framework/references/ui-brand.md +0 -160
- package/framework/qualia-framework/references/verification-patterns.md +0 -612
- package/framework/qualia-framework/templates/DEBUG.md +0 -159
- package/framework/qualia-framework/templates/DESIGN.md +0 -81
- package/framework/qualia-framework/templates/UAT.md +0 -247
- package/framework/qualia-framework/templates/codebase/architecture.md +0 -255
- package/framework/qualia-framework/templates/codebase/concerns.md +0 -310
- package/framework/qualia-framework/templates/codebase/conventions.md +0 -307
- package/framework/qualia-framework/templates/codebase/integrations.md +0 -280
- package/framework/qualia-framework/templates/codebase/stack.md +0 -186
- package/framework/qualia-framework/templates/codebase/structure.md +0 -285
- package/framework/qualia-framework/templates/codebase/testing.md +0 -480
- package/framework/qualia-framework/templates/config.json +0 -35
- package/framework/qualia-framework/templates/context.md +0 -283
- package/framework/qualia-framework/templates/continue-here.md +0 -78
- package/framework/qualia-framework/templates/debug-subagent-prompt.md +0 -91
- package/framework/qualia-framework/templates/discovery.md +0 -146
- package/framework/qualia-framework/templates/lab-notes.md +0 -16
- package/framework/qualia-framework/templates/milestone-archive.md +0 -123
- package/framework/qualia-framework/templates/milestone.md +0 -115
- package/framework/qualia-framework/templates/phase-prompt.md +0 -567
- package/framework/qualia-framework/templates/planner-subagent-prompt.md +0 -117
- package/framework/qualia-framework/templates/project.md +0 -184
- package/framework/qualia-framework/templates/projects/ai-agent.md +0 -156
- package/framework/qualia-framework/templates/projects/mobile-app.md +0 -181
- package/framework/qualia-framework/templates/projects/voice-agent.md +0 -134
- package/framework/qualia-framework/templates/projects/website.md +0 -137
- package/framework/qualia-framework/templates/requirements.md +0 -231
- package/framework/qualia-framework/templates/research-project/ARCHITECTURE.md +0 -204
- package/framework/qualia-framework/templates/research-project/FEATURES.md +0 -147
- package/framework/qualia-framework/templates/research-project/PITFALLS.md +0 -200
- package/framework/qualia-framework/templates/research-project/STACK.md +0 -120
- package/framework/qualia-framework/templates/research-project/SUMMARY.md +0 -170
- package/framework/qualia-framework/templates/research.md +0 -552
- package/framework/qualia-framework/templates/roadmap.md +0 -206
- package/framework/qualia-framework/templates/state.md +0 -179
- package/framework/qualia-framework/templates/summary-complex.md +0 -59
- package/framework/qualia-framework/templates/summary-minimal.md +0 -41
- package/framework/qualia-framework/templates/summary-standard.md +0 -48
- package/framework/qualia-framework/templates/summary.md +0 -246
- package/framework/qualia-framework/templates/user-setup.md +0 -311
- package/framework/qualia-framework/templates/verification-report.md +0 -322
- package/framework/qualia-framework/workflows/add-phase.md +0 -179
- package/framework/qualia-framework/workflows/add-todo.md +0 -157
- package/framework/qualia-framework/workflows/audit-milestone.md +0 -241
- package/framework/qualia-framework/workflows/check-todos.md +0 -176
- package/framework/qualia-framework/workflows/complete-milestone.md +0 -858
- package/framework/qualia-framework/workflows/diagnose-issues.md +0 -219
- package/framework/qualia-framework/workflows/discovery-phase.md +0 -289
- package/framework/qualia-framework/workflows/discuss-phase.md +0 -534
- package/framework/qualia-framework/workflows/execute-phase.md +0 -559
- package/framework/qualia-framework/workflows/execute-plan.md +0 -438
- package/framework/qualia-framework/workflows/help.md +0 -470
- package/framework/qualia-framework/workflows/insert-phase.md +0 -220
- package/framework/qualia-framework/workflows/list-phase-assumptions.md +0 -178
- package/framework/qualia-framework/workflows/map-codebase.md +0 -327
- package/framework/qualia-framework/workflows/new-milestone.md +0 -363
- package/framework/qualia-framework/workflows/new-project.md +0 -982
- package/framework/qualia-framework/workflows/pause-work.md +0 -122
- package/framework/qualia-framework/workflows/plan-milestone-gaps.md +0 -256
- package/framework/qualia-framework/workflows/plan-phase.md +0 -422
- package/framework/qualia-framework/workflows/progress.md +0 -389
- package/framework/qualia-framework/workflows/quick.md +0 -252
- package/framework/qualia-framework/workflows/remove-phase.md +0 -326
- package/framework/qualia-framework/workflows/research-phase.md +0 -74
- package/framework/qualia-framework/workflows/resume-project.md +0 -306
- package/framework/qualia-framework/workflows/set-profile.md +0 -80
- package/framework/qualia-framework/workflows/settings.md +0 -145
- package/framework/qualia-framework/workflows/transition.md +0 -556
- package/framework/qualia-framework/workflows/update.md +0 -197
- package/framework/qualia-framework/workflows/verify-phase.md +0 -195
- package/framework/qualia-framework/workflows/verify-work.md +0 -625
- package/framework/rules/context7.md +0 -14
- package/framework/rules/frontend.md +0 -33
- package/framework/rules/speed.md +0 -23
- package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
- package/framework/scripts/apply-retention.sh +0 -120
- package/framework/scripts/bootstrap-pop-os.sh +0 -354
- package/framework/scripts/claude-voice +0 -13
- package/framework/scripts/cleanup.sh +0 -131
- package/framework/scripts/cowork-mode.sh +0 -141
- package/framework/scripts/generate-project-claude-md.sh +0 -153
- package/framework/scripts/load-test-webhook.js +0 -172
- package/framework/scripts/say.py +0 -236
- package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +0 -167
- package/framework/scripts/showcase-video-recorder/playwright-helpers.js +0 -216
- package/framework/scripts/speak.py +0 -55
- package/framework/scripts/speak.sh +0 -18
- package/framework/scripts/status.sh +0 -138
- package/framework/scripts/sync-to-framework.sh +0 -65
- package/framework/scripts/voice-hotkey.py +0 -227
- package/framework/scripts/voice-input.sh +0 -51
- package/framework/skills/animate/SKILL.md +0 -202
- package/framework/skills/bolder/SKILL.md +0 -144
- package/framework/skills/browser-qa/SKILL.md +0 -536
- package/framework/skills/clarify/SKILL.md +0 -179
- package/framework/skills/client-handoff/SKILL.md +0 -135
- package/framework/skills/collab-onboard/SKILL.md +0 -111
- package/framework/skills/colorize/SKILL.md +0 -170
- package/framework/skills/critique/SKILL.md +0 -126
- package/framework/skills/deep-research/SKILL.md +0 -240
- package/framework/skills/delight/SKILL.md +0 -329
- package/framework/skills/deploy/SKILL.md +0 -261
- package/framework/skills/deploy-verify/SKILL.md +0 -377
- package/framework/skills/deploy-verify/scripts/canary-check.sh +0 -206
- package/framework/skills/deploy-verify/scripts/check-console-errors.js +0 -147
- package/framework/skills/deploy-verify/scripts/check-cwv.js +0 -139
- package/framework/skills/deploy-verify/scripts/project-detect.sh +0 -84
- package/framework/skills/deploy-verify/scripts/verify.sh +0 -548
- package/framework/skills/design-quieter/SKILL.md +0 -130
- package/framework/skills/distill/SKILL.md +0 -149
- package/framework/skills/docs-lookup/SKILL.md +0 -79
- package/framework/skills/fcm-notifications/SKILL.md +0 -125
- package/framework/skills/financial-ledger/SKILL.md +0 -1039
- package/framework/skills/frontend-master/NOTICE.md +0 -4
- package/framework/skills/frontend-master/SKILL.md +0 -127
- package/framework/skills/frontend-master/reference/color-and-contrast.md +0 -132
- package/framework/skills/frontend-master/reference/interaction-design.md +0 -123
- package/framework/skills/frontend-master/reference/motion-design.md +0 -99
- package/framework/skills/frontend-master/reference/responsive-design.md +0 -114
- package/framework/skills/frontend-master/reference/spatial-design.md +0 -100
- package/framework/skills/frontend-master/reference/typography.md +0 -131
- package/framework/skills/frontend-master/reference/ux-writing.md +0 -107
- package/framework/skills/harden/SKILL.md +0 -357
- package/framework/skills/i18n-rtl/SKILL.md +0 -752
- package/framework/skills/learn/SKILL.md +0 -95
- package/framework/skills/memory/SKILL.md +0 -50
- package/framework/skills/mobile-expo/SKILL.md +0 -977
- package/framework/skills/mobile-expo/references/store-checklist.md +0 -550
- package/framework/skills/nestjs-backend/README.md +0 -73
- package/framework/skills/nestjs-backend/SKILL.md +0 -446
- package/framework/skills/nestjs-backend/references/templates.md +0 -1173
- package/framework/skills/normalize/SKILL.md +0 -79
- package/framework/skills/onboard/SKILL.md +0 -242
- package/framework/skills/openrouter-agent/SKILL.md +0 -922
- package/framework/skills/polish/SKILL.md +0 -209
- package/framework/skills/pr/SKILL.md +0 -66
- package/framework/skills/qualia/SKILL.md +0 -199
- package/framework/skills/qualia-add-todo/SKILL.md +0 -68
- package/framework/skills/qualia-audit-milestone/SKILL.md +0 -95
- package/framework/skills/qualia-check-todos/SKILL.md +0 -55
- package/framework/skills/qualia-complete-milestone/SKILL.md +0 -134
- package/framework/skills/qualia-debug/SKILL.md +0 -149
- package/framework/skills/qualia-design/SKILL.md +0 -203
- package/framework/skills/qualia-discuss-phase/SKILL.md +0 -72
- package/framework/skills/qualia-evolve/SKILL.md +0 -200
- package/framework/skills/qualia-execute-phase/SKILL.md +0 -89
- package/framework/skills/qualia-framework-audit/SKILL.md +0 -604
- package/framework/skills/qualia-guide/SKILL.md +0 -32
- package/framework/skills/qualia-help/SKILL.md +0 -114
- package/framework/skills/qualia-idk/SKILL.md +0 -352
- package/framework/skills/qualia-list-phase-assumptions/SKILL.md +0 -67
- package/framework/skills/qualia-new-milestone/SKILL.md +0 -72
- package/framework/skills/qualia-new-project/SKILL.md +0 -232
- package/framework/skills/qualia-optimize/SKILL.md +0 -417
- package/framework/skills/qualia-pause-work/SKILL.md +0 -96
- package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +0 -57
- package/framework/skills/qualia-plan-phase/SKILL.md +0 -104
- package/framework/skills/qualia-production-check/SKILL.md +0 -0
- package/framework/skills/qualia-progress/SKILL.md +0 -53
- package/framework/skills/qualia-quick/SKILL.md +0 -89
- package/framework/skills/qualia-report/SKILL.md +0 -166
- package/framework/skills/qualia-research-phase/SKILL.md +0 -88
- package/framework/skills/qualia-resume-work/SKILL.md +0 -62
- package/framework/skills/qualia-review/SKILL.md +0 -263
- package/framework/skills/qualia-start/SKILL.md +0 -161
- package/framework/skills/qualia-verify-work/SKILL.md +0 -132
- package/framework/skills/rag/SKILL.md +0 -750
- package/framework/skills/responsive/SKILL.md +0 -231
- package/framework/skills/retro/SKILL.md +0 -284
- package/framework/skills/sakani-conventions/SKILL.md +0 -136
- package/framework/skills/sakani-conventions/evals/evals.json +0 -23
- package/framework/skills/sakani-conventions/references/entities.md +0 -365
- package/framework/skills/sakani-conventions/references/error-codes.md +0 -95
- package/framework/skills/seo-master/SKILL.md +0 -490
- package/framework/skills/seo-master/references/checklist.md +0 -199
- package/framework/skills/seo-master/references/structured-data.md +0 -609
- package/framework/skills/ship/SKILL.md +0 -239
- package/framework/skills/stack-researcher/SKILL.md +0 -215
- package/framework/skills/status/SKILL.md +0 -154
- package/framework/skills/status/scripts/health-check.sh +0 -562
- package/framework/skills/subscription-payments/SKILL.md +0 -250
- package/framework/skills/supabase/SKILL.md +0 -973
- package/framework/skills/supabase/references/templates.md +0 -159
- package/framework/skills/team/SKILL.md +0 -67
- package/framework/skills/test-runner/SKILL.md +0 -202
- package/framework/skills/voice-agent/SKILL.md +0 -1312
- package/framework/skills/zoho-workflow/SKILL.md +0 -51
- package/framework/statusline-command.sh +0 -117
- package/framework/teams/default/inboxes/plan-04.json +0 -9
- package/framework/teams/review-team.md +0 -75
- package/framework/teams/ship-team.md +0 -86
- package/profiles/fawzi.json +0 -16
- package/profiles/hasan.json +0 -16
- package/profiles/moayad.json +0 -16
- package/templates/CLAUDE-owner.md +0 -52
- package/templates/CLAUDE.md.hbs +0 -58
- package/templates/env.claude.template +0 -12
- package/templates/settings.json +0 -172
- /package/{framework/rules → rules}/deployment.md +0 -0
- /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
|
|
4
|
-
const path = require(
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
47
|
-
if (!fs.existsSync(
|
|
48
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
77
|
-
|
|
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
|
-
|
|
47
|
+
function cmdVersion() {
|
|
48
|
+
banner();
|
|
49
|
+
const cfg = readConfig();
|
|
84
50
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
}
|
|
100
|
+
console.log(` ${DIM}Current:${RESET} ${WHITE}${PKG.version}${RESET}`);
|
|
101
|
+
console.log(` ${DIM}Updating...${RESET}`);
|
|
102
|
+
console.log("");
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
//
|
|
150
|
-
if (
|
|
151
|
-
|
|
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
|
-
|
|
158
|
-
}
|
|
239
|
+
// Leave settings.env alone — the user may have other env vars in there.
|
|
159
240
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
const command = process.argv[2];
|
|
249
|
+
async function cmdUninstall() {
|
|
250
|
+
banner();
|
|
173
251
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
260
|
+
console.log(` ${DIM}No Qualia config found at${RESET} ${WHITE}${CONFIG_FILE}${RESET}`);
|
|
180
261
|
}
|
|
181
|
-
|
|
262
|
+
console.log("");
|
|
182
263
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
|
287
|
+
const counters = { filesRemoved: 0, dirsRemoved: 0, settingsCleaned: false, errors: [] };
|
|
205
288
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
}
|
|
225
|
-
|
|
226
|
-
ok('Created ~/.claude/');
|
|
299
|
+
} catch (e) {
|
|
300
|
+
counters.errors.push(`skills scan: ${e.message}`);
|
|
227
301
|
}
|
|
228
302
|
|
|
229
|
-
//
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
for (const
|
|
234
|
-
|
|
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
|
-
//
|
|
242
|
-
for (const f of
|
|
243
|
-
|
|
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
|
-
//
|
|
250
|
-
const
|
|
251
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
log(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
511
|
+
// ─── Migrate ────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
function cmdMigrate() {
|
|
514
|
+
banner();
|
|
515
|
+
console.log("");
|
|
365
516
|
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
383
|
-
|
|
532
|
+
const cfg = readConfig();
|
|
533
|
+
const fromVersion = cfg.version || "unknown";
|
|
534
|
+
let changes = 0;
|
|
384
535
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
405
|
-
log('');
|
|
565
|
+
if (!bashEntry.hooks) bashEntry.hooks = [];
|
|
406
566
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
//
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
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
|
|
488
|
-
|
|
489
|
-
log(
|
|
490
|
-
log(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
+
}
|