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