qualia-framework-v2 2.0.0 → 2.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/README.md +52 -13
- package/bin/cli.js +20 -0
- package/bin/install.js +352 -207
- package/hooks/block-env-edit.sh +5 -2
- package/hooks/migration-guard.sh +43 -0
- package/hooks/pre-deploy-gate.sh +18 -0
- package/package.json +3 -4
- package/skills/qualia-build/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +1 -1
- package/skills/qualia-task/SKILL.md +92 -0
- package/skills/qualia-verify/SKILL.md +1 -1
- package/install.sh +0 -223
package/README.md
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# Qualia Framework v2
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A prompt orchestration framework for [Claude Code](https://claude.ai/code). It installs into `~/.claude/` and wraps your AI-assisted development workflow with structured planning, execution, verification, and deployment gates.
|
|
4
|
+
|
|
5
|
+
It is not an application framework like Rails or Next.js. It doesn't generate code, run servers, or process data. It's an opinionated workflow layer that tells Claude how to plan, build, and verify your projects.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
9
|
-
cd qualia-framework-v2
|
|
10
|
-
chmod +x install.sh
|
|
11
|
-
./install.sh
|
|
10
|
+
npx qualia-framework-v2 install
|
|
12
11
|
```
|
|
13
12
|
|
|
13
|
+
Enter your team code when prompted. Get your code from Fawzi.
|
|
14
|
+
|
|
14
15
|
## Usage
|
|
15
16
|
|
|
16
17
|
Open Claude Code in any project directory:
|
|
@@ -29,20 +30,58 @@ See `guide.md` for the full developer guide.
|
|
|
29
30
|
|
|
30
31
|
## What's Inside
|
|
31
32
|
|
|
32
|
-
- **
|
|
33
|
+
- **11 skills** — slash commands that guide you from setup to handoff
|
|
33
34
|
- **3 agents** — planner, builder, verifier (each in fresh context)
|
|
34
|
-
- **
|
|
35
|
+
- **7 hooks** — branch guard, pre-push tracking sync, env protection, migration guard, deploy gate, pre-compact state save, session start
|
|
35
36
|
- **3 rules** — security, frontend, deployment
|
|
36
37
|
- **4 templates** — tracking.json, state.md, project.md, plan.md
|
|
37
38
|
|
|
39
|
+
## Why It Works
|
|
40
|
+
|
|
41
|
+
### Goal-Backward Verification
|
|
42
|
+
|
|
43
|
+
Most CI checks "did the task run." Qualia checks "does the outcome actually work." The verifier doesn't trust summaries — it greps the codebase for stubs, placeholders, unwired imports. When Claude says "I built the chat component," this catches the cases where it wrote a skeleton with `// TODO` inside.
|
|
44
|
+
|
|
45
|
+
### Agent Separation
|
|
46
|
+
|
|
47
|
+
Splitting planner, builder, and verifier into separate agents with separate contexts prevents the "God prompt" problem where one massive context tries to plan AND code AND test. Each agent gets fresh context. This directly addresses Claude's quality degradation curve — task 50 gets the same quality as task 1.
|
|
48
|
+
|
|
49
|
+
### Production-Grade Hooks
|
|
50
|
+
|
|
51
|
+
The `settings.json` hooks are real ops engineering, not theoretical:
|
|
52
|
+
|
|
53
|
+
- **Pre-deploy gate** — TypeScript, lint, tests, build, and `service_role` leak scan before `vercel --prod`
|
|
54
|
+
- **Branch guard** — Role-aware: owner can push to main, employees can't
|
|
55
|
+
- **Migration guard** — Catches `DROP TABLE` without `IF EXISTS`, `DELETE` without `WHERE`, `CREATE TABLE` without RLS
|
|
56
|
+
- **Env block** — Prevents Claude from touching `.env` files
|
|
57
|
+
- **Pre-compact** — Saves state before context compression
|
|
58
|
+
|
|
59
|
+
### Wave-Based Parallelization
|
|
60
|
+
|
|
61
|
+
Plans are grouped into waves for parallel execution. No fancy DAG solver — the planner assigns wave numbers, the orchestrator spawns agents per wave. Pragmatic over clever.
|
|
62
|
+
|
|
63
|
+
### Plans Are Prompts
|
|
64
|
+
|
|
65
|
+
Plan files aren't documents that get translated into prompts — they ARE the prompts. `@file` references, explicit task actions, and verification criteria baked in. This eliminates translation loss between "what we planned" and "what Claude actually reads."
|
|
66
|
+
|
|
38
67
|
## Architecture
|
|
39
68
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
69
|
+
```
|
|
70
|
+
npx qualia-framework-v2 install
|
|
71
|
+
|
|
|
72
|
+
v
|
|
73
|
+
~/.claude/
|
|
74
|
+
├── skills/ 11 slash commands
|
|
75
|
+
├── agents/ planner.md, builder.md, verifier.md
|
|
76
|
+
├── hooks/ 7 shell scripts (branch, env, migration, deploy, push, compact, session)
|
|
77
|
+
├── rules/ security.md, frontend.md, deployment.md
|
|
78
|
+
├── qualia-templates/ tracking.json, state.md, project.md, plan.md
|
|
79
|
+
├── CLAUDE.md global instructions (role-configured per team member)
|
|
80
|
+
└── statusline.sh teal-branded 2-line status bar
|
|
81
|
+
```
|
|
45
82
|
|
|
46
83
|
## For Qualia Solutions Team
|
|
47
84
|
|
|
48
|
-
Stack: Next.js
|
|
85
|
+
Stack: Next.js 16+, React 19, TypeScript, Supabase, Vercel.
|
|
86
|
+
|
|
87
|
+
Built by [Qualia Solutions](https://qualiasolutions.net) — Nicosia, Cyprus.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const TEAL = "\x1b[38;2;0;206;209m";
|
|
4
|
+
const DIM = "\x1b[38;2;80;90;100m";
|
|
5
|
+
const WHITE = "\x1b[38;2;220;225;230m";
|
|
6
|
+
const RESET = "\x1b[0m";
|
|
7
|
+
|
|
8
|
+
const cmd = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (cmd === "install") {
|
|
11
|
+
require("./install.js");
|
|
12
|
+
} else {
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(`${TEAL} ◆ Qualia Framework v2${RESET}`);
|
|
15
|
+
console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
16
|
+
console.log("");
|
|
17
|
+
console.log(` ${WHITE}Usage:${RESET}`);
|
|
18
|
+
console.log(` npx qualia-framework-v2 ${TEAL}install${RESET} Install the framework`);
|
|
19
|
+
console.log("");
|
|
20
|
+
}
|
package/bin/install.js
CHANGED
|
@@ -1,248 +1,393 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { createInterface } = require("readline");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
|
|
7
|
+
// ─── Colors ──────────────────────────────────────────────
|
|
7
8
|
const TEAL = "\x1b[38;2;0;206;209m";
|
|
8
9
|
const DIM = "\x1b[38;2;80;90;100m";
|
|
9
10
|
const GREEN = "\x1b[38;2;52;211;153m";
|
|
10
11
|
const WHITE = "\x1b[38;2;220;225;230m";
|
|
12
|
+
const YELLOW = "\x1b[38;2;234;179;8m";
|
|
13
|
+
const RED = "\x1b[38;2;239;68;68m";
|
|
11
14
|
const RESET = "\x1b[0m";
|
|
12
15
|
|
|
16
|
+
// ─── Team codes ──────────────────────────────────────────
|
|
17
|
+
const TEAM = {
|
|
18
|
+
"QS-FAWZI-01": {
|
|
19
|
+
name: "Fawzi Goussous",
|
|
20
|
+
role: "OWNER",
|
|
21
|
+
description: "Company owner. Full access. Can push to main, approve deploys, edit secrets.",
|
|
22
|
+
},
|
|
23
|
+
"QS-HASAN-02": {
|
|
24
|
+
name: "Hasan",
|
|
25
|
+
role: "EMPLOYEE",
|
|
26
|
+
description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
|
|
27
|
+
},
|
|
28
|
+
"QS-MOAYAD-03": {
|
|
29
|
+
name: "Moayad",
|
|
30
|
+
role: "EMPLOYEE",
|
|
31
|
+
description: "Developer. Feature branches only. Cannot push to main or edit .env files.",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
13
35
|
const CLAUDE_DIR = path.join(require("os").homedir(), ".claude");
|
|
14
36
|
const FRAMEWORK_DIR = path.resolve(__dirname, "..");
|
|
15
37
|
|
|
38
|
+
let installed = 0;
|
|
39
|
+
let errors = 0;
|
|
40
|
+
|
|
16
41
|
function log(msg) {
|
|
17
42
|
console.log(` ${msg}`);
|
|
18
43
|
}
|
|
19
|
-
|
|
44
|
+
function ok(label) {
|
|
45
|
+
installed++;
|
|
46
|
+
log(`${GREEN}✓${RESET} ${label}`);
|
|
47
|
+
}
|
|
48
|
+
function warn(label) {
|
|
49
|
+
errors++;
|
|
50
|
+
log(`${YELLOW}✗${RESET} ${label}`);
|
|
51
|
+
}
|
|
20
52
|
function copy(src, dest) {
|
|
21
53
|
const destDir = path.dirname(dest);
|
|
22
54
|
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
23
55
|
fs.copyFileSync(src, dest);
|
|
24
56
|
}
|
|
25
57
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
// ─── Prompt for code ─────────────────────────────────────
|
|
59
|
+
function askCode() {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
+
console.log("");
|
|
63
|
+
console.log(`${TEAL} ◆ Qualia Framework v2${RESET}`);
|
|
64
|
+
console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
65
|
+
console.log("");
|
|
66
|
+
rl.question(` ${WHITE}Enter install code:${RESET} `, (answer) => {
|
|
67
|
+
rl.close();
|
|
68
|
+
resolve(answer.trim());
|
|
69
|
+
});
|
|
70
|
+
});
|
|
34
71
|
}
|
|
35
72
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(` Installing to ${WHITE}${CLAUDE_DIR}${RESET}`);
|
|
41
|
-
console.log("");
|
|
42
|
-
|
|
43
|
-
// Skills
|
|
44
|
-
const skillsDir = path.join(FRAMEWORK_DIR, "skills");
|
|
45
|
-
const skills = fs.readdirSync(skillsDir).filter((d) =>
|
|
46
|
-
fs.statSync(path.join(skillsDir, d)).isDirectory()
|
|
47
|
-
);
|
|
48
|
-
log(`${WHITE}Skills${RESET}`);
|
|
49
|
-
for (const skill of skills) {
|
|
50
|
-
const src = path.join(skillsDir, skill, "SKILL.md");
|
|
51
|
-
const dest = path.join(CLAUDE_DIR, "skills", skill, "SKILL.md");
|
|
52
|
-
copy(src, dest);
|
|
53
|
-
log(` ${GREEN}✓${RESET} ${skill}`);
|
|
54
|
-
}
|
|
73
|
+
// ─── Main ────────────────────────────────────────────────
|
|
74
|
+
async function main() {
|
|
75
|
+
const code = await askCode();
|
|
76
|
+
const member = TEAM[code];
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
log(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
78
|
+
if (!member) {
|
|
79
|
+
console.log("");
|
|
80
|
+
log(`${RED}✗${RESET} Invalid code. Get your install code from Fawzi.`);
|
|
81
|
+
console.log("");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
63
84
|
|
|
64
|
-
|
|
65
|
-
log(`${WHITE}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
log(` ${GREEN}✓${RESET} ${file}`);
|
|
70
|
-
}
|
|
85
|
+
console.log("");
|
|
86
|
+
log(`${GREEN}✓${RESET} ${WHITE}${member.name}${RESET} ${DIM}(${member.role})${RESET}`);
|
|
87
|
+
console.log("");
|
|
88
|
+
log(`Installing to ${WHITE}${CLAUDE_DIR}${RESET}`);
|
|
89
|
+
console.log("");
|
|
71
90
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (const file of fs.readdirSync(hooksDir)) {
|
|
78
|
-
const dest = path.join(hooksDest, file);
|
|
79
|
-
copy(path.join(hooksDir, file), dest);
|
|
80
|
-
fs.chmodSync(dest, 0o755);
|
|
81
|
-
log(` ${GREEN}✓${RESET} ${file}`);
|
|
82
|
-
}
|
|
91
|
+
// ─── Skills ──────────────────────────────────────────
|
|
92
|
+
const skillsDir = path.join(FRAMEWORK_DIR, "skills");
|
|
93
|
+
const skills = fs
|
|
94
|
+
.readdirSync(skillsDir)
|
|
95
|
+
.filter((d) => fs.statSync(path.join(skillsDir, d)).isDirectory());
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
copy(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for (const file of fs.readdirSync(tmplDir)) {
|
|
97
|
-
copy(path.join(tmplDir, file), path.join(tmplDest, file));
|
|
98
|
-
log(` ${GREEN}✓${RESET} ${file}`);
|
|
99
|
-
}
|
|
97
|
+
log(`${WHITE}Skills${RESET}`);
|
|
98
|
+
for (const skill of skills) {
|
|
99
|
+
try {
|
|
100
|
+
copy(
|
|
101
|
+
path.join(skillsDir, skill, "SKILL.md"),
|
|
102
|
+
path.join(CLAUDE_DIR, "skills", skill, "SKILL.md")
|
|
103
|
+
);
|
|
104
|
+
ok(skill);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
warn(`${skill} — ${e.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
100
109
|
|
|
101
|
-
//
|
|
102
|
-
log(`${WHITE}
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
// ─── Agents ────────────────────────────────────────────
|
|
111
|
+
log(`${WHITE}Agents${RESET}`);
|
|
112
|
+
const agentsDir = path.join(FRAMEWORK_DIR, "agents");
|
|
113
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
114
|
+
try {
|
|
115
|
+
copy(path.join(agentsDir, file), path.join(CLAUDE_DIR, "agents", file));
|
|
116
|
+
ok(file);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
warn(`${file} — ${e.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
105
121
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
// ─── Rules ─────────────────────────────────────────────
|
|
123
|
+
log(`${WHITE}Rules${RESET}`);
|
|
124
|
+
const rulesDir = path.join(FRAMEWORK_DIR, "rules");
|
|
125
|
+
for (const file of fs.readdirSync(rulesDir)) {
|
|
126
|
+
try {
|
|
127
|
+
copy(path.join(rulesDir, file), path.join(CLAUDE_DIR, "rules", file));
|
|
128
|
+
ok(file);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
warn(`${file} — ${e.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
109
133
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
// ─── Hooks ─────────────────────────────────────────────
|
|
135
|
+
log(`${WHITE}Hooks${RESET}`);
|
|
136
|
+
const hooksSource = path.join(FRAMEWORK_DIR, "hooks");
|
|
137
|
+
const hooksDest = path.join(CLAUDE_DIR, "hooks");
|
|
138
|
+
if (!fs.existsSync(hooksDest)) fs.mkdirSync(hooksDest, { recursive: true });
|
|
139
|
+
for (const file of fs.readdirSync(hooksSource)) {
|
|
140
|
+
try {
|
|
141
|
+
const dest = path.join(hooksDest, file);
|
|
142
|
+
copy(path.join(hooksSource, file), dest);
|
|
143
|
+
fs.chmodSync(dest, 0o755);
|
|
144
|
+
ok(file);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
warn(`${file} — ${e.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
113
149
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (fs.existsSync(settingsPath)) {
|
|
150
|
+
// ─── Status line ───────────────────────────────────────
|
|
151
|
+
log(`${WHITE}Status line${RESET}`);
|
|
117
152
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
const slDest = path.join(CLAUDE_DIR, "statusline.sh");
|
|
154
|
+
copy(path.join(FRAMEWORK_DIR, "statusline.sh"), slDest);
|
|
155
|
+
fs.chmodSync(slDest, 0o755);
|
|
156
|
+
ok("statusline.sh");
|
|
157
|
+
} catch (e) {
|
|
158
|
+
warn(`statusline.sh — ${e.message}`);
|
|
159
|
+
}
|
|
121
160
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
// ─── Templates ─────────────────────────────────────────
|
|
162
|
+
log(`${WHITE}Templates${RESET}`);
|
|
163
|
+
const tmplDir = path.join(FRAMEWORK_DIR, "templates");
|
|
164
|
+
const tmplDest = path.join(CLAUDE_DIR, "qualia-templates");
|
|
165
|
+
if (!fs.existsSync(tmplDest)) fs.mkdirSync(tmplDest, { recursive: true });
|
|
166
|
+
for (const file of fs.readdirSync(tmplDir)) {
|
|
167
|
+
try {
|
|
168
|
+
copy(path.join(tmplDir, file), path.join(tmplDest, file));
|
|
169
|
+
ok(file);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
warn(`${file} — ${e.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
130
174
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
175
|
+
// ─── CLAUDE.md with role ───────────────────────────────
|
|
176
|
+
log(`${WHITE}CLAUDE.md${RESET}`);
|
|
177
|
+
try {
|
|
178
|
+
let claudeMd = fs.readFileSync(
|
|
179
|
+
path.join(FRAMEWORK_DIR, "CLAUDE.md"),
|
|
180
|
+
"utf8"
|
|
181
|
+
);
|
|
182
|
+
claudeMd = claudeMd.replace("{{ROLE}}", member.role);
|
|
183
|
+
claudeMd = claudeMd.replace("{{ROLE_DESCRIPTION}}", member.description);
|
|
184
|
+
const claudeDest = path.join(CLAUDE_DIR, "CLAUDE.md");
|
|
185
|
+
fs.writeFileSync(claudeDest, claudeMd, "utf8");
|
|
186
|
+
ok(`Configured as ${member.role}`);
|
|
187
|
+
} catch (e) {
|
|
188
|
+
warn(`CLAUDE.md — ${e.message}`);
|
|
189
|
+
}
|
|
143
190
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"◆ Read before write — no exceptions",
|
|
155
|
-
"◆ MVP first — build what's asked, nothing extra",
|
|
156
|
-
"◆ tracking.json syncs to ERP on every push",
|
|
157
|
-
],
|
|
158
|
-
};
|
|
191
|
+
// ─── Guide ─────────────────────────────────────────────
|
|
192
|
+
try {
|
|
193
|
+
copy(
|
|
194
|
+
path.join(FRAMEWORK_DIR, "guide.md"),
|
|
195
|
+
path.join(CLAUDE_DIR, "qualia-guide.md")
|
|
196
|
+
);
|
|
197
|
+
ok("guide.md");
|
|
198
|
+
} catch (e) {
|
|
199
|
+
warn(`guide.md — ${e.message}`);
|
|
200
|
+
}
|
|
159
201
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
settings.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}],
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
SubagentStart: [
|
|
209
|
-
{
|
|
210
|
-
matcher: ".*",
|
|
211
|
-
hooks: [{
|
|
212
|
-
type: "command",
|
|
213
|
-
command: 'echo \'{"additionalContext": "◆ Qualia agent spawned"}\'',
|
|
214
|
-
}],
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
};
|
|
202
|
+
// ─── Configure settings.json ───────────────────────────
|
|
203
|
+
console.log("");
|
|
204
|
+
log(`${WHITE}Configuring settings.json...${RESET}`);
|
|
205
|
+
|
|
206
|
+
const settingsPath = path.join(CLAUDE_DIR, "settings.json");
|
|
207
|
+
let settings = {};
|
|
208
|
+
if (fs.existsSync(settingsPath)) {
|
|
209
|
+
try {
|
|
210
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Env
|
|
215
|
+
if (!settings.env) settings.env = {};
|
|
216
|
+
Object.assign(settings.env, {
|
|
217
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
218
|
+
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "0",
|
|
219
|
+
MAX_MCP_OUTPUT_TOKENS: "25000",
|
|
220
|
+
CLAUDE_CODE_NO_FLICKER: "1",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Status line
|
|
224
|
+
settings.statusLine = {
|
|
225
|
+
type: "command",
|
|
226
|
+
command: "~/.claude/statusline.sh",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Spinner
|
|
230
|
+
settings.spinnerVerbs = {
|
|
231
|
+
mode: "replace",
|
|
232
|
+
verbs: [
|
|
233
|
+
"Qualia-fying",
|
|
234
|
+
"Solution-ing",
|
|
235
|
+
"Teal-crafting",
|
|
236
|
+
"Vibe-forging",
|
|
237
|
+
"Shipping",
|
|
238
|
+
"Wiring",
|
|
239
|
+
"Polishing",
|
|
240
|
+
"Verifying",
|
|
241
|
+
"Orchestrating",
|
|
242
|
+
"Architecting",
|
|
243
|
+
"Deploying",
|
|
244
|
+
"Hardening",
|
|
245
|
+
],
|
|
246
|
+
};
|
|
218
247
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
settings.spinnerTipsOverride = {
|
|
249
|
+
excludeDefault: true,
|
|
250
|
+
tips: [
|
|
251
|
+
"◆ Lost? Type /qualia for the next step",
|
|
252
|
+
"◆ Small fix? Use /qualia-quick to skip planning",
|
|
253
|
+
"◆ End of day? /qualia-report before you clock out",
|
|
254
|
+
"◆ Context isolation: every task gets a fresh AI brain",
|
|
255
|
+
"◆ The verifier doesn't trust claims — it greps the code",
|
|
256
|
+
"◆ Plans are prompts — the plan IS what the builder reads",
|
|
257
|
+
"◆ Feature branches only — never push to main",
|
|
258
|
+
"◆ Read before write — no exceptions",
|
|
259
|
+
"◆ MVP first — build what's asked, nothing extra",
|
|
260
|
+
"◆ tracking.json syncs to ERP on every push",
|
|
261
|
+
],
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Hooks — full system
|
|
265
|
+
const hd = path.join(CLAUDE_DIR, "hooks");
|
|
266
|
+
settings.hooks = {
|
|
267
|
+
PreToolUse: [
|
|
268
|
+
{
|
|
269
|
+
matcher: "Bash",
|
|
270
|
+
hooks: [
|
|
271
|
+
{
|
|
272
|
+
type: "command",
|
|
273
|
+
if: "Bash(git push*)",
|
|
274
|
+
command: `${hd}/branch-guard.sh`,
|
|
275
|
+
timeout: 10,
|
|
276
|
+
statusMessage: "◆ Checking branch permissions...",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: "command",
|
|
280
|
+
if: "Bash(git push*)",
|
|
281
|
+
command: `${hd}/pre-push.sh`,
|
|
282
|
+
timeout: 15,
|
|
283
|
+
statusMessage: "◆ Syncing tracking...",
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
matcher: "Edit|Write",
|
|
289
|
+
hooks: [
|
|
290
|
+
{
|
|
291
|
+
type: "command",
|
|
292
|
+
if: "Edit(*.env*)|Write(*.env*)",
|
|
293
|
+
command: `${hd}/block-env-edit.sh`,
|
|
294
|
+
timeout: 5,
|
|
295
|
+
statusMessage: "◆ Checking file permissions...",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
type: "command",
|
|
299
|
+
if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)",
|
|
300
|
+
command: `${hd}/migration-guard.sh`,
|
|
301
|
+
timeout: 10,
|
|
302
|
+
statusMessage: "◆ Checking migration safety...",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
PostToolUse: [
|
|
308
|
+
{
|
|
309
|
+
matcher: "Bash",
|
|
310
|
+
hooks: [
|
|
311
|
+
{
|
|
312
|
+
type: "command",
|
|
313
|
+
if: "Bash(vercel --prod*)",
|
|
314
|
+
command: `${hd}/pre-deploy-gate.sh`,
|
|
315
|
+
timeout: 120,
|
|
316
|
+
statusMessage: "◆ Running quality gates...",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
PreCompact: [
|
|
322
|
+
{
|
|
323
|
+
matcher: "compact",
|
|
324
|
+
hooks: [
|
|
325
|
+
{
|
|
326
|
+
type: "command",
|
|
327
|
+
command: `${hd}/pre-compact.sh`,
|
|
328
|
+
timeout: 15,
|
|
329
|
+
statusMessage: "◆ Saving state...",
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
SubagentStart: [
|
|
335
|
+
{
|
|
336
|
+
matcher: ".*",
|
|
337
|
+
hooks: [
|
|
338
|
+
{
|
|
339
|
+
type: "command",
|
|
340
|
+
command:
|
|
341
|
+
'echo \'{"additionalContext": "◆ Qualia agent spawned"}\'',
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Permissions
|
|
349
|
+
if (!settings.permissions) settings.permissions = {};
|
|
350
|
+
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
351
|
+
if (!settings.permissions.deny) {
|
|
352
|
+
settings.permissions.deny = [
|
|
353
|
+
"Read(./.env)",
|
|
354
|
+
"Read(./.env.*)",
|
|
355
|
+
"Read(./secrets/**)",
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
settings.effortLevel = "high";
|
|
360
|
+
|
|
361
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
362
|
+
|
|
363
|
+
ok("Hooks: branch-guard, pre-push, env-block, migration-guard, deploy-gate, pre-compact");
|
|
364
|
+
ok("Status line + spinner configured");
|
|
365
|
+
ok("Environment variables + permissions");
|
|
366
|
+
|
|
367
|
+
// ─── Summary ───────────────────────────────────────────
|
|
368
|
+
console.log("");
|
|
369
|
+
console.log(`${TEAL} ◆ Installed ✓${RESET}`);
|
|
370
|
+
console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
371
|
+
console.log(` ${WHITE}${member.name}${RESET} ${DIM}(${member.role})${RESET}`);
|
|
372
|
+
console.log(` Skills: ${WHITE}${skills.length}${RESET}`);
|
|
373
|
+
console.log(` Agents: ${WHITE}3${RESET} ${DIM}(planner, builder, verifier)${RESET}`);
|
|
374
|
+
console.log(` Hooks: ${WHITE}6${RESET} ${DIM}(branch-guard, pre-push, env-block, migration-guard, deploy-gate, pre-compact)${RESET}`);
|
|
375
|
+
console.log(` Rules: ${WHITE}3${RESET} ${DIM}(security, frontend, deployment)${RESET}`);
|
|
376
|
+
console.log(` Templates: ${WHITE}4${RESET}`);
|
|
377
|
+
console.log(` Status line: ${GREEN}✓${RESET}`);
|
|
378
|
+
console.log(` CLAUDE.md: ${GREEN}✓${RESET} ${DIM}(${member.role})${RESET}`);
|
|
379
|
+
|
|
380
|
+
if (errors > 0) {
|
|
381
|
+
console.log("");
|
|
382
|
+
console.log(` ${YELLOW}${errors} warning(s)${RESET} — check output above`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log(` Restart Claude Code, then type ${TEAL}/qualia${RESET} in any project.`);
|
|
387
|
+
console.log("");
|
|
228
388
|
}
|
|
229
389
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Done
|
|
236
|
-
console.log("");
|
|
237
|
-
console.log(`${TEAL} ◆ Installed ✓${RESET}`);
|
|
238
|
-
console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
239
|
-
console.log(` Skills: ${WHITE}${skills.length}${RESET}`);
|
|
240
|
-
console.log(` Agents: ${WHITE}3${RESET} ${DIM}(planner, builder, verifier)${RESET}`);
|
|
241
|
-
console.log(` Hooks: ${WHITE}5${RESET} ${DIM}(branch-guard, env-block, deploy-gate, pre-compact, subagent-label)${RESET}`);
|
|
242
|
-
console.log(` Rules: ${WHITE}3${RESET} ${DIM}(security, frontend, deployment)${RESET}`);
|
|
243
|
-
console.log(` Templates: ${WHITE}4${RESET}`);
|
|
244
|
-
console.log(` Status line: ${GREEN}✓${RESET}`);
|
|
245
|
-
console.log(` CLAUDE.md: ${GREEN}✓${RESET} ${DIM}(user-level)${RESET}`);
|
|
246
|
-
console.log("");
|
|
247
|
-
console.log(` Restart Claude Code, then type ${TEAL}/qualia${RESET} in any project.`);
|
|
248
|
-
console.log("");
|
|
390
|
+
main().catch((e) => {
|
|
391
|
+
console.error(`${RED} ✗ Installation failed: ${e.message}${RESET}`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
});
|
package/hooks/block-env-edit.sh
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Prevent Claude from editing .env files
|
|
3
|
+
# Claude Code hooks receive JSON on stdin with tool_input.file_path
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // ""' 2>/dev/null)
|
|
3
7
|
|
|
4
|
-
FILE="$1"
|
|
5
8
|
if [[ "$FILE" == *.env* ]] || [[ "$FILE" == *".env.local"* ]] || [[ "$FILE" == *".env.production"* ]]; then
|
|
6
9
|
echo "BLOCKED: Cannot edit environment files. Ask Fawzi to update secrets."
|
|
7
|
-
exit
|
|
10
|
+
exit 2
|
|
8
11
|
fi
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Catch dangerous SQL patterns in migration files
|
|
3
|
+
# Runs as PreToolUse hook on Write/Edit of migration files
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
|
|
7
|
+
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
|
|
8
|
+
|
|
9
|
+
# Only check migration/SQL files
|
|
10
|
+
case "$FILE" in
|
|
11
|
+
*migration*|*migrate*|*.sql) ;;
|
|
12
|
+
*) exit 0 ;;
|
|
13
|
+
esac
|
|
14
|
+
|
|
15
|
+
ERRORS=""
|
|
16
|
+
|
|
17
|
+
# DROP TABLE without safeguards
|
|
18
|
+
if echo "$CONTENT" | grep -qi "DROP TABLE" && ! echo "$CONTENT" | grep -qi "IF EXISTS"; then
|
|
19
|
+
ERRORS="${ERRORS}\n ✗ DROP TABLE without IF EXISTS"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# DELETE without WHERE
|
|
23
|
+
if echo "$CONTENT" | grep -qi "DELETE FROM" && ! echo "$CONTENT" | grep -qi "WHERE"; then
|
|
24
|
+
ERRORS="${ERRORS}\n ✗ DELETE FROM without WHERE clause"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# TRUNCATE (almost always wrong in migrations)
|
|
28
|
+
if echo "$CONTENT" | grep -qi "TRUNCATE"; then
|
|
29
|
+
ERRORS="${ERRORS}\n ✗ TRUNCATE detected — are you sure?"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# CREATE TABLE without RLS
|
|
33
|
+
if echo "$CONTENT" | grep -qi "CREATE TABLE" && ! echo "$CONTENT" | grep -qi "ENABLE ROW LEVEL SECURITY"; then
|
|
34
|
+
ERRORS="${ERRORS}\n ✗ CREATE TABLE without ENABLE ROW LEVEL SECURITY"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ -n "$ERRORS" ]; then
|
|
38
|
+
echo "◆ Migration guard — dangerous patterns found:"
|
|
39
|
+
echo -e "$ERRORS"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Fix these before proceeding. If intentional, ask Fawzi to approve."
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
package/hooks/pre-deploy-gate.sh
CHANGED
|
@@ -12,6 +12,24 @@ if [ -f "tsconfig.json" ]; then
|
|
|
12
12
|
echo " ✓ TypeScript"
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
+
# Lint check
|
|
16
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
|
|
17
|
+
if ! npm run lint 2>/dev/null; then
|
|
18
|
+
echo "BLOCKED: Lint errors. Fix before deploying."
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
echo " ✓ Lint"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Test check
|
|
25
|
+
if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
|
26
|
+
if ! npm test 2>/dev/null; then
|
|
27
|
+
echo "BLOCKED: Tests failed. Fix before deploying."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
echo " ✓ Tests"
|
|
31
|
+
fi
|
|
32
|
+
|
|
15
33
|
# Build check
|
|
16
34
|
if [ -f "package.json" ] && grep -q '"build"' package.json; then
|
|
17
35
|
if ! npm run build 2>/dev/null; then
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qualia-framework-v2",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"qualia-framework-v2": "./bin/
|
|
6
|
+
"qualia-framework-v2": "./bin/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"keywords": [
|
|
9
9
|
"claude-code",
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
"templates/",
|
|
32
32
|
"CLAUDE.md",
|
|
33
33
|
"guide.md",
|
|
34
|
-
"statusline.sh"
|
|
35
|
-
"install.sh"
|
|
34
|
+
"statusline.sh"
|
|
36
35
|
],
|
|
37
36
|
"engines": {
|
|
38
37
|
"node": ">=18"
|
|
@@ -49,7 +49,7 @@ YOUR TASK:
|
|
|
49
49
|
{paste the single task block from the plan — title, files, action, context refs, done-when}
|
|
50
50
|
|
|
51
51
|
Execute this task. Read all @file references before writing. Commit when done.
|
|
52
|
-
", subagent_type="
|
|
52
|
+
", subagent_type="qualia-builder", description="Task {N}: {title}")
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
**After each task completes:**
|
|
@@ -48,7 +48,7 @@ Phase {N} success criteria: {criteria from STATE.md}
|
|
|
48
48
|
{If --gaps: Also read @.planning/phase-{N}-verification.md for failures to fix}
|
|
49
49
|
|
|
50
50
|
Create the plan file at .planning/phase-{N}-plan.md
|
|
51
|
-
", subagent_type="
|
|
51
|
+
", subagent_type="qualia-planner", description="Plan phase {N}")
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### 3. Review Plan
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qualia-task
|
|
3
|
+
description: "Build a single task — more structured than /qualia-quick, lighter than /qualia-build. Spawns a fresh builder agent for one focused task."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /qualia-task — Single Task Builder
|
|
7
|
+
|
|
8
|
+
Build one thing properly. Fresh builder context, atomic commit, but no phase plan needed.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
`/qualia-task` — describe what to build interactively
|
|
12
|
+
`/qualia-task {description}` — build it directly
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
- Too big for a quick fix, too small for a full phase
|
|
16
|
+
- Adding a single feature, component, API route, or integration
|
|
17
|
+
- Refactoring one module
|
|
18
|
+
- Building something specific someone asked for
|
|
19
|
+
|
|
20
|
+
## Process
|
|
21
|
+
|
|
22
|
+
### 1. Clarify
|
|
23
|
+
|
|
24
|
+
If no description provided, ask: **"What do you want to build?"**
|
|
25
|
+
|
|
26
|
+
Then use AskUserQuestion:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
question: "How complex is this task?"
|
|
30
|
+
header: "Scope"
|
|
31
|
+
options:
|
|
32
|
+
- label: "Small (30min-1hr)"
|
|
33
|
+
description: "Single file or component, straightforward implementation"
|
|
34
|
+
- label: "Medium (1-3hrs)"
|
|
35
|
+
description: "Multiple files, some integration work, needs testing"
|
|
36
|
+
- label: "Large (3hrs+)"
|
|
37
|
+
description: "Significant feature, multiple components, consider /qualia-plan instead"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If "Large" — suggest `/qualia-plan` instead. Ask if they want to proceed anyway.
|
|
41
|
+
|
|
42
|
+
### 2. Task Spec
|
|
43
|
+
|
|
44
|
+
Write a quick task spec (don't save to file, just confirm with user):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
◆ QUALIA ► TASK
|
|
48
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
49
|
+
|
|
50
|
+
What: {what to build}
|
|
51
|
+
Files: {files to create/modify}
|
|
52
|
+
Done: {what "done" looks like}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Ask: **"Good to build?"**
|
|
56
|
+
|
|
57
|
+
### 3. Build
|
|
58
|
+
|
|
59
|
+
Spawn a builder agent with the task:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Agent(subagent_type: "qualia-builder")
|
|
63
|
+
|
|
64
|
+
Task: {task description}
|
|
65
|
+
Files: {files to create/modify}
|
|
66
|
+
Done when: {completion criteria}
|
|
67
|
+
|
|
68
|
+
Context: Read PROJECT.md if it exists. Follow all rules (security, frontend, deployment).
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The builder runs in fresh context — reads before writing, follows rules, commits atomically.
|
|
72
|
+
|
|
73
|
+
### 4. Verify
|
|
74
|
+
|
|
75
|
+
After the builder finishes:
|
|
76
|
+
- Run `npx tsc --noEmit` if TypeScript
|
|
77
|
+
- Quick smoke test if applicable
|
|
78
|
+
- Review what was built
|
|
79
|
+
|
|
80
|
+
### 5. Report
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
◆ QUALIA ► TASK COMPLETE
|
|
84
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
85
|
+
|
|
86
|
+
Task {description}
|
|
87
|
+
Files {files changed}
|
|
88
|
+
Commit {commit hash}
|
|
89
|
+
Status ✓ Done
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Update `.planning/tracking.json` notes field if it exists.
|
|
@@ -41,7 +41,7 @@ Phase plan with success criteria:
|
|
|
41
41
|
{@.planning/phase-{N}-verification.md}
|
|
42
42
|
|
|
43
43
|
Verify this phase. Write report to .planning/phase-{N}-verification.md
|
|
44
|
-
", subagent_type="
|
|
44
|
+
", subagent_type="qualia-verifier", description="Verify phase {N}")
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
### 3. Present Results
|
package/install.sh
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Qualia Framework v2 — Installer
|
|
3
|
-
# Installs skills, agents, rules, hooks, templates, status line, and configures settings.json
|
|
4
|
-
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
FRAMEWORK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
CLAUDE_DIR="$HOME/.claude"
|
|
9
|
-
|
|
10
|
-
echo "◆ Qualia Framework v2"
|
|
11
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
12
|
-
echo " Installing to $CLAUDE_DIR"
|
|
13
|
-
echo ""
|
|
14
|
-
|
|
15
|
-
# Skills
|
|
16
|
-
echo " Installing skills..."
|
|
17
|
-
for skill_dir in "$FRAMEWORK_DIR"/skills/*/; do
|
|
18
|
-
skill_name=$(basename "$skill_dir")
|
|
19
|
-
mkdir -p "$CLAUDE_DIR/skills/$skill_name"
|
|
20
|
-
cp "$skill_dir/SKILL.md" "$CLAUDE_DIR/skills/$skill_name/SKILL.md"
|
|
21
|
-
echo " ✓ $skill_name"
|
|
22
|
-
done
|
|
23
|
-
|
|
24
|
-
# Agents
|
|
25
|
-
echo " Installing agents..."
|
|
26
|
-
mkdir -p "$CLAUDE_DIR/agents"
|
|
27
|
-
for agent in "$FRAMEWORK_DIR"/agents/*.md; do
|
|
28
|
-
cp "$agent" "$CLAUDE_DIR/agents/"
|
|
29
|
-
echo " ✓ $(basename "$agent")"
|
|
30
|
-
done
|
|
31
|
-
|
|
32
|
-
# Rules
|
|
33
|
-
echo " Installing rules..."
|
|
34
|
-
mkdir -p "$CLAUDE_DIR/rules"
|
|
35
|
-
for rule in "$FRAMEWORK_DIR"/rules/*.md; do
|
|
36
|
-
cp "$rule" "$CLAUDE_DIR/rules/"
|
|
37
|
-
echo " ✓ $(basename "$rule")"
|
|
38
|
-
done
|
|
39
|
-
|
|
40
|
-
# Hook scripts
|
|
41
|
-
echo " Installing hook scripts..."
|
|
42
|
-
mkdir -p "$CLAUDE_DIR/hooks"
|
|
43
|
-
for hook in "$FRAMEWORK_DIR"/hooks/*.sh; do
|
|
44
|
-
cp "$hook" "$CLAUDE_DIR/hooks/"
|
|
45
|
-
chmod +x "$CLAUDE_DIR/hooks/$(basename "$hook")"
|
|
46
|
-
echo " ✓ $(basename "$hook")"
|
|
47
|
-
done
|
|
48
|
-
|
|
49
|
-
# Status line
|
|
50
|
-
echo " Installing status line..."
|
|
51
|
-
cp "$FRAMEWORK_DIR/statusline.sh" "$CLAUDE_DIR/statusline.sh"
|
|
52
|
-
chmod +x "$CLAUDE_DIR/statusline.sh"
|
|
53
|
-
echo " ✓ statusline.sh"
|
|
54
|
-
|
|
55
|
-
# Templates
|
|
56
|
-
echo " Installing templates..."
|
|
57
|
-
mkdir -p "$CLAUDE_DIR/qualia-templates"
|
|
58
|
-
for tmpl in "$FRAMEWORK_DIR"/templates/*; do
|
|
59
|
-
cp "$tmpl" "$CLAUDE_DIR/qualia-templates/"
|
|
60
|
-
echo " ✓ $(basename "$tmpl")"
|
|
61
|
-
done
|
|
62
|
-
|
|
63
|
-
# CLAUDE.md (user-level — applies to all projects)
|
|
64
|
-
echo " Installing CLAUDE.md..."
|
|
65
|
-
cp "$FRAMEWORK_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
|
|
66
|
-
echo " ✓ CLAUDE.md"
|
|
67
|
-
|
|
68
|
-
# Guide
|
|
69
|
-
cp "$FRAMEWORK_DIR/guide.md" "$CLAUDE_DIR/qualia-guide.md"
|
|
70
|
-
echo " ✓ guide.md"
|
|
71
|
-
|
|
72
|
-
# Configure settings.json with hooks, status line, and env
|
|
73
|
-
echo ""
|
|
74
|
-
echo " Configuring settings.json..."
|
|
75
|
-
|
|
76
|
-
python3 << 'PYEOF'
|
|
77
|
-
import json, os
|
|
78
|
-
|
|
79
|
-
settings_path = os.path.expanduser("~/.claude/settings.json")
|
|
80
|
-
|
|
81
|
-
# Load existing settings or start fresh
|
|
82
|
-
if os.path.exists(settings_path):
|
|
83
|
-
with open(settings_path) as f:
|
|
84
|
-
settings = json.load(f)
|
|
85
|
-
else:
|
|
86
|
-
settings = {}
|
|
87
|
-
|
|
88
|
-
# Env vars
|
|
89
|
-
settings.setdefault("env", {})
|
|
90
|
-
settings["env"].update({
|
|
91
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
92
|
-
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "0",
|
|
93
|
-
"MAX_MCP_OUTPUT_TOKENS": "25000",
|
|
94
|
-
"CLAUDE_CODE_NO_FLICKER": "1"
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
# Status line — teal branded, 2-line
|
|
98
|
-
settings["statusLine"] = {
|
|
99
|
-
"type": "command",
|
|
100
|
-
"command": "~/.claude/statusline.sh"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# Spinner verbs — Qualia branded
|
|
104
|
-
settings["spinnerVerbs"] = {
|
|
105
|
-
"mode": "replace",
|
|
106
|
-
"verbs": [
|
|
107
|
-
"Qualia-fying", "Solution-ing", "Teal-crafting", "Vibe-forging",
|
|
108
|
-
"Shipping", "Wiring", "Polishing", "Verifying",
|
|
109
|
-
"Orchestrating", "Architecting", "Deploying", "Hardening"
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
# Spinner tips — Qualia tips
|
|
114
|
-
settings["spinnerTipsOverride"] = {
|
|
115
|
-
"excludeDefault": True,
|
|
116
|
-
"tips": [
|
|
117
|
-
"◆ Lost? Type /qualia for the next step",
|
|
118
|
-
"◆ Small fix? Use /qualia-quick to skip planning",
|
|
119
|
-
"◆ End of day? /qualia-report before you clock out",
|
|
120
|
-
"◆ Context isolation: every task gets a fresh AI brain",
|
|
121
|
-
"◆ The verifier doesn't trust claims — it greps the code",
|
|
122
|
-
"◆ Plans are prompts — the plan IS what the builder reads",
|
|
123
|
-
"◆ Feature branches only — never push to main",
|
|
124
|
-
"◆ Read before write — no exceptions",
|
|
125
|
-
"◆ MVP first — build what's asked, nothing extra",
|
|
126
|
-
"◆ tracking.json syncs to ERP on every push"
|
|
127
|
-
]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
# Hooks
|
|
131
|
-
hooks_dir = os.path.expanduser("~/.claude/hooks")
|
|
132
|
-
settings["hooks"] = {
|
|
133
|
-
"PreToolUse": [
|
|
134
|
-
{
|
|
135
|
-
"matcher": "Bash",
|
|
136
|
-
"hooks": [{
|
|
137
|
-
"type": "command",
|
|
138
|
-
"if": "Bash(git push*)",
|
|
139
|
-
"command": f"{hooks_dir}/branch-guard.sh",
|
|
140
|
-
"timeout": 10,
|
|
141
|
-
"statusMessage": "◆ Checking branch permissions..."
|
|
142
|
-
}]
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
"matcher": "Edit|Write",
|
|
146
|
-
"hooks": [{
|
|
147
|
-
"type": "command",
|
|
148
|
-
"if": "Edit(*.env*)",
|
|
149
|
-
"command": f"{hooks_dir}/block-env-edit.sh",
|
|
150
|
-
"timeout": 5,
|
|
151
|
-
"statusMessage": "◆ Checking file permissions..."
|
|
152
|
-
}]
|
|
153
|
-
}
|
|
154
|
-
],
|
|
155
|
-
"PostToolUse": [
|
|
156
|
-
{
|
|
157
|
-
"matcher": "Bash",
|
|
158
|
-
"hooks": [{
|
|
159
|
-
"type": "command",
|
|
160
|
-
"if": "Bash(vercel --prod*)",
|
|
161
|
-
"command": f"{hooks_dir}/pre-deploy-gate.sh",
|
|
162
|
-
"timeout": 120,
|
|
163
|
-
"statusMessage": "◆ Running quality gates..."
|
|
164
|
-
}]
|
|
165
|
-
}
|
|
166
|
-
],
|
|
167
|
-
"PreCompact": [
|
|
168
|
-
{
|
|
169
|
-
"matcher": "compact",
|
|
170
|
-
"hooks": [{
|
|
171
|
-
"type": "command",
|
|
172
|
-
"command": f"{hooks_dir}/pre-compact.sh",
|
|
173
|
-
"timeout": 15,
|
|
174
|
-
"statusMessage": "◆ Saving state..."
|
|
175
|
-
}]
|
|
176
|
-
}
|
|
177
|
-
],
|
|
178
|
-
"SubagentStart": [
|
|
179
|
-
{
|
|
180
|
-
"matcher": ".*",
|
|
181
|
-
"hooks": [{
|
|
182
|
-
"type": "command",
|
|
183
|
-
"command": "echo '{\"additionalContext\": \"◆ Qualia agent spawned\"}'"
|
|
184
|
-
}]
|
|
185
|
-
}
|
|
186
|
-
]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
# Permissions
|
|
190
|
-
settings.setdefault("permissions", {})
|
|
191
|
-
settings["permissions"].setdefault("allow", [])
|
|
192
|
-
settings["permissions"].setdefault("deny", [
|
|
193
|
-
"Read(./.env)",
|
|
194
|
-
"Read(./.env.*)",
|
|
195
|
-
"Read(./secrets/**)"
|
|
196
|
-
])
|
|
197
|
-
|
|
198
|
-
# Effort level
|
|
199
|
-
settings["effortLevel"] = "high"
|
|
200
|
-
|
|
201
|
-
with open(settings_path, "w") as f:
|
|
202
|
-
json.dump(settings, f, indent=2)
|
|
203
|
-
|
|
204
|
-
print(" ✓ Hooks registered (branch-guard, env-block, deploy-gate, pre-compact, subagent-label)")
|
|
205
|
-
print(" ✓ Status line configured (2-line teal branded)")
|
|
206
|
-
print(" ✓ Spinner verbs: Qualia branded")
|
|
207
|
-
print(" ✓ Spinner tips: Qualia tips")
|
|
208
|
-
print(" ✓ Environment variables set")
|
|
209
|
-
PYEOF
|
|
210
|
-
|
|
211
|
-
echo ""
|
|
212
|
-
echo "◆ Installed ✓"
|
|
213
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
214
|
-
echo " Skills: 10"
|
|
215
|
-
echo " Agents: 3 (planner, builder, verifier)"
|
|
216
|
-
echo " Hooks: 4 (branch-guard, env-block, deploy-gate, pre-compact)"
|
|
217
|
-
echo " Rules: 3 (security, frontend, deployment)"
|
|
218
|
-
echo " Templates: 4"
|
|
219
|
-
echo " Status line: ✓"
|
|
220
|
-
echo " CLAUDE.md: ✓ (user-level)"
|
|
221
|
-
echo ""
|
|
222
|
-
echo " Restart Claude Code, then type /qualia in any project."
|
|
223
|
-
echo ""
|