qualia-framework-v2 2.8.1 → 2.10.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 CHANGED
@@ -16,6 +16,7 @@ Enter your team code when prompted. Get your code from Fawzi.
16
16
  ```bash
17
17
  npx qualia-framework-v2 version # Check installed version + updates
18
18
  npx qualia-framework-v2 update # Update to latest (remembers your code)
19
+ npx qualia-framework-v2 uninstall # Clean removal from ~/.claude/
19
20
  ```
20
21
 
21
22
  ## Usage
@@ -115,4 +116,8 @@ npx qualia-framework-v2 install
115
116
 
116
117
  Stack: Next.js 16+, React 19, TypeScript, Supabase, Vercel.
117
118
 
119
+ ## Changelog
120
+
121
+ See [CHANGELOG.md](./CHANGELOG.md) for the full version history.
122
+
118
123
  Built by [Qualia Solutions](https://qualiasolutions.net) — Nicosia, Cyprus.
package/bin/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync } = require("child_process");
3
+ const { spawnSync } = require("child_process");
4
4
  const path = require("path");
5
5
  const fs = require("fs");
6
+ const readline = require("readline");
6
7
 
7
8
  const TEAL = "\x1b[38;2;0;206;209m";
8
9
  const TG = "\x1b[38;2;0;170;175m";
@@ -33,7 +34,7 @@ function writeConfig(cfg) {
33
34
 
34
35
  function banner() {
35
36
  console.log("");
36
- console.log(` ${TEAL}${BOLD}◆${RESET} ${WHITE}${BOLD}Qualia Framework${RESET} ${DIM}v${PKG.version}${RESET}`);
37
+ console.log(` ${TEAL}${BOLD}⬢${RESET} ${WHITE}${BOLD}Qualia Framework${RESET} ${DIM}v${PKG.version}${RESET}`);
37
38
  console.log(` ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
38
39
  }
39
40
 
@@ -57,10 +58,17 @@ function cmdVersion() {
57
58
 
58
59
  // Check for updates
59
60
  try {
60
- const latest = execSync("npm view qualia-framework-v2 version 2>/dev/null", {
61
- encoding: "utf8",
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-v2", "version"], {
66
+ stdio: ["ignore", "pipe", "ignore"],
67
+ shell: process.platform === "win32",
62
68
  timeout: 5000,
63
- }).trim();
69
+ encoding: "utf8",
70
+ });
71
+ const latest = (r.stdout || "").trim();
64
72
  const semverGt = (a, b) => {
65
73
  const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
66
74
  for (let i = 0; i < 3; i++) { if (pa[i] > pb[i]) return true; if (pa[i] < pb[i]) return false; }
@@ -94,7 +102,6 @@ function cmdUpdate() {
94
102
  console.log("");
95
103
 
96
104
  try {
97
- const { spawnSync } = require("child_process");
98
105
  const r = spawnSync("npx", ["qualia-framework-v2@latest", "install"], {
99
106
  input: cfg.code + "\n",
100
107
  stdio: ["pipe", "inherit", "inherit"],
@@ -113,6 +120,253 @@ function cmdUpdate() {
113
120
  }
114
121
  }
115
122
 
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
+ // 4 Qualia rules.
149
+ const QUALIA_RULE_FILES = ["security.md", "frontend.md", "design-reference.md", "deployment.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
+ });
161
+ });
162
+ }
163
+
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
+ }
174
+
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
+ }
185
+
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
+ };
211
+
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
+ }
221
+ }
222
+ }
223
+ // If hooks is now empty, remove it entirely.
224
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
225
+ }
226
+
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
+ }
233
+ }
234
+
235
+ // Qualia-specific spinner overrides.
236
+ if (settings.spinnerVerbs) delete settings.spinnerVerbs;
237
+ if (settings.spinnerTipsOverride) delete settings.spinnerTipsOverride;
238
+
239
+ // Leave settings.env alone — the user may have other env vars in there.
240
+
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
+ }
247
+ }
248
+
249
+ async function cmdUninstall() {
250
+ banner();
251
+
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}`);
259
+ } else {
260
+ console.log(` ${DIM}No Qualia config found at${RESET} ${WHITE}${CONFIG_FILE}${RESET}`);
261
+ }
262
+ console.log("");
263
+
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
+ }
272
+ }
273
+
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("");
286
+
287
+ const counters = { filesRemoved: 0, dirsRemoved: 0, settingsCleaned: false, errors: [] };
288
+
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);
296
+ }
297
+ }
298
+ }
299
+ } catch (e) {
300
+ counters.errors.push(`skills scan: ${e.message}`);
301
+ }
302
+
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
+ }
307
+
308
+ // Hooks — only the 8 Qualia ones.
309
+ for (const f of QUALIA_HOOK_FILES) {
310
+ safeUnlink(path.join(CLAUDE_DIR, "hooks", f), counters);
311
+ }
312
+
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);
316
+ }
317
+
318
+ // Rules — all 4.
319
+ for (const f of QUALIA_RULE_FILES) {
320
+ safeUnlink(path.join(CLAUDE_DIR, "rules", f), counters);
321
+ }
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);
329
+ }
330
+
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-guide.md"), counters);
336
+
337
+ // Clean settings.json surgically.
338
+ cleanSettingsJson(counters);
339
+
340
+ // Summary.
341
+ console.log("");
342
+ console.log(`${TEAL} ⬢ Uninstall complete${RESET}`);
343
+ console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
344
+ console.log(` ${DIM}Files removed:${RESET} ${WHITE}${counters.filesRemoved}${RESET}`);
345
+ console.log(` ${DIM}Directories removed:${RESET} ${WHITE}${counters.dirsRemoved}${RESET}`);
346
+ console.log(
347
+ ` ${DIM}settings.json:${RESET} ${counters.settingsCleaned ? `${GREEN}cleaned ✓${RESET}` : `${DIM}not present${RESET}`}`
348
+ );
349
+ if (preserveKnowledge) {
350
+ console.log(` ${DIM}Knowledge base:${RESET} ${GREEN}preserved ✓${RESET}`);
351
+ } else {
352
+ console.log(` ${DIM}Knowledge base:${RESET} ${YELLOW}removed${RESET}`);
353
+ }
354
+
355
+ if (counters.errors.length > 0) {
356
+ console.log("");
357
+ console.log(` ${YELLOW}${counters.errors.length} warning(s):${RESET}`);
358
+ for (const err of counters.errors.slice(0, 5)) {
359
+ console.log(` ${DIM}${err}${RESET}`);
360
+ }
361
+ }
362
+
363
+ console.log("");
364
+ console.log(
365
+ ` ${YELLOW}Manual step:${RESET} edit ${WHITE}~/.claude/CLAUDE.md${RESET} to remove the Qualia Framework section if desired.`
366
+ );
367
+ console.log("");
368
+ }
369
+
116
370
  function cmdHelp() {
117
371
  banner();
118
372
  console.log("");
@@ -120,6 +374,7 @@ function cmdHelp() {
120
374
  console.log(` npx qualia-framework-v2 ${TEAL}install${RESET} Install or reinstall the framework`);
121
375
  console.log(` npx qualia-framework-v2 ${TEAL}update${RESET} Update to the latest version`);
122
376
  console.log(` npx qualia-framework-v2 ${TEAL}version${RESET} Show installed version + check for updates`);
377
+ console.log(` npx qualia-framework-v2 ${TEAL}uninstall${RESET} Clean removal from ~/.claude/ (${DIM}-y to skip prompts${RESET})`);
123
378
  console.log("");
124
379
  console.log(` ${WHITE}After install:${RESET}`);
125
380
  console.log(` ${TG}/qualia${RESET} What should I do next?`);
@@ -151,6 +406,13 @@ switch (cmd) {
151
406
  case "upgrade":
152
407
  cmdUpdate();
153
408
  break;
409
+ case "uninstall":
410
+ case "remove":
411
+ cmdUninstall().catch((e) => {
412
+ console.error(`${RED} ✗ Uninstall failed: ${e.message}${RESET}`);
413
+ process.exit(1);
414
+ });
415
+ break;
154
416
  default:
155
417
  cmdHelp();
156
418
  }
package/bin/install.js CHANGED
@@ -70,7 +70,7 @@ function askCode() {
70
70
  return new Promise((resolve) => {
71
71
  const rl = createInterface({ input: process.stdin, output: process.stdout });
72
72
  console.log("");
73
- console.log(`${TEAL} Qualia Framework v2${RESET}`);
73
+ console.log(`${TEAL} Qualia Framework v2${RESET}`);
74
74
  console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
75
75
  console.log("");
76
76
  rl.question(` ${WHITE}Enter install code:${RESET} `, (answer) => {
@@ -266,9 +266,84 @@ async function main() {
266
266
  const knowledgeDir = path.join(CLAUDE_DIR, "knowledge");
267
267
  if (!fs.existsSync(knowledgeDir)) fs.mkdirSync(knowledgeDir, { recursive: true });
268
268
  const knowledgeFiles = {
269
- "learned-patterns.md": "# Learned Patterns\n\nPatterns discovered across projects. Updated by `/qualia-learn` and manual notes.\n",
270
- "common-fixes.md": "# Common Fixes\n\nRecurring issues and their solutions.\n",
271
- "client-prefs.md": "# Client Preferences\n\nClient-specific preferences, design choices, and requirements.\n",
269
+ "learned-patterns.md": `# Learned Patterns
270
+
271
+ Patterns discovered across projects. Updated by \`/qualia-learn\` and manual notes.
272
+
273
+ ---
274
+
275
+ ## Cross-platform Node: always spawnSync with argv, never execSync with shell strings
276
+ **Why:** \`execSync(\\\`node \${path}/state.js check 2>/dev/null\\\`)\` breaks on Windows when the path contains spaces (common: \`C:\\\\Users\\\\John Doe\`) and the \`2>/dev/null\` redirect is bash-only. Windows cmd.exe tries to create \`\\\\dev\\\\null\` at drive root.
277
+ **How:** Use \`spawnSync(process.execPath, [path, "check"], { stdio: ["ignore","pipe","ignore"] })\`. Argv array is immune to path splitting; \`stdio: "ignore"\` silences stderr without shell redirection.
278
+
279
+ ---
280
+
281
+ ## Cross-platform stdin piping: spawnSync with input:, not bash <<< here-strings
282
+ **Why:** The \`<<<\` bash here-string works on bash + zsh but fails silently on Windows cmd.exe AND on Debian/Ubuntu where \`/bin/sh\` is dash (no \`<<<\` support).
283
+ **How:** \`spawnSync("npx", ["cmd"], { input: "data\\\\n", stdio: ["pipe","inherit","inherit"], shell: process.platform === "win32" })\`. The \`input:\` option pipes stdin directly. \`shell: process.platform === "win32"\` is required because npm/npx are \`.cmd\` shims on Windows that only resolve through a shell.
284
+
285
+ ---
286
+
287
+ ## Fresh-context isolation beats shared-context compression
288
+ **Why:** Claude's output quality degrades as context fills. A single massive context doing plan + build + verify hits the degradation curve on the later tasks.
289
+ **How:** Spawn separate subagents for planner / builder (per task) / verifier. Each gets fresh context. Task 50 gets the same quality as task 1. Cost: PROJECT.md + STATE.md get re-loaded into each subagent context, but the quality win dominates.
290
+
291
+ ---
292
+
293
+ ## Goal-backward verification beats task-completion tracking
294
+ **Why:** A task "create chat component" can be marked complete with a placeholder file. The task ran; the goal didn't.
295
+ **How:** For each phase success criterion, do a 3-level check: (1) what must be TRUE, (2) what files/functions must EXIST and be substantive (not stubs), (3) what must be CONNECTED (imported and called). Grep the codebase. Never trust summaries.
296
+ `,
297
+ "common-fixes.md": `# Common Fixes
298
+
299
+ Recurring issues and their solutions.
300
+
301
+ ---
302
+
303
+ ## Install code "Invalid" — user typed letter O instead of digit 0
304
+ **Symptom:** \`npx qualia-framework-v2 install\` rejects \`QS-NAME-O1\` (letter O in suffix).
305
+ **Cause:** Team codes use digit zero (\`-01\`, \`-02\`, etc.), not letter O.
306
+ **Fix:** Since v2.8.1, install.js auto-normalizes: \`QS-FAWZI-O1\` → \`QS-FAWZI-01\`. The normalization only touches the segment after the last dash, so \`QS-MOAYAD-03\` (real O in name) is preserved.
307
+ **Framework version:** Fixed in v2.8.1.
308
+
309
+ ---
310
+
311
+ ## Windows banner shows "No project detected" inside a real project
312
+ **Symptom:** The session-start banner from qualia-ui.js displays the router panel but without phase/status, even in a project with \`.planning/\`.
313
+ **Cause:** Before v2.8.0, \`qualia-ui.js\` called state.js via \`execSync(\\\`node \${path} check 2>/dev/null\\\`)\`. Windows cmd.exe couldn't parse the \`2>/dev/null\` redirect and/or split the path on spaces in the username.
314
+ **Fix:** v2.8.0 switched to \`spawnSync(process.execPath, [statePath, "check"], { stdio: ["ignore","pipe","ignore"] })\`. Argv array + silent stdio = cross-platform safe.
315
+ **Framework version:** Fixed in v2.8.0.
316
+
317
+ ---
318
+
319
+ ## \`npx qualia-framework-v2 update\` fails on Windows or Ubuntu
320
+ **Symptom:** Manual update command fails silently or with a shell parse error on Windows and Debian/Ubuntu.
321
+ **Cause:** Before v2.8.0, cli.js cmdUpdate used \`execSync(\\\`npx ... install <<< "\${code}"\\\`, { shell: true })\`. The \`<<<\` here-string is bash-only; cmd.exe doesn't understand it, and \`/bin/sh\` on Debian/Ubuntu is \`dash\` which also lacks it.
322
+ **Fix:** v2.8.0 replaced with \`spawnSync("npx", [...], { input: code + "\\\\n", shell: process.platform === "win32" })\`. Uses stdin pipe instead of here-string.
323
+ **Framework version:** Fixed in v2.8.0.
324
+
325
+ ---
326
+
327
+ ## Pre-deploy gate false-positive on Next.js Server Components using service_role
328
+ **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).
329
+ **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.
330
+ **Workaround:** Rename to \`.server.tsx\` OR move to a \`server/\` subdirectory OR extract the service_role usage into a helper in \`lib/server/\`.
331
+ **Framework version:** Known issue as of v2.8.1; better heuristic planned for v3.0.
332
+ `,
333
+ "client-prefs.md": `# Client Preferences
334
+
335
+ Client-specific preferences, design choices, and requirements. Loaded by \`/qualia-new\` when starting a project for a known client.
336
+
337
+ ---
338
+
339
+ ## Example Client (template)
340
+ **Industry:** {e.g., fintech, healthcare, SaaS}
341
+ **Contact:** {email}
342
+ **Design:** {dark-bold | clean-minimal | colorful-playful | corporate-professional}
343
+ **Stack preferences:** {anything non-default}
344
+ **Hard constraints:** {things they've explicitly said no to}
345
+ **Source of notes:** {date or conversation reference}
346
+ `,
272
347
  };
273
348
  for (const [name, defaultContent] of Object.entries(knowledgeFiles)) {
274
349
  const dest = path.join(knowledgeDir, name);
@@ -350,16 +425,16 @@ async function main() {
350
425
  settings.spinnerTipsOverride = {
351
426
  excludeDefault: true,
352
427
  tips: [
353
- " Lost? Type /qualia for the next step",
354
- " Small fix? Use /qualia-quick to skip planning",
355
- " End of day? /qualia-report before you clock out",
356
- " Context isolation: every task gets a fresh AI brain",
357
- " The verifier doesn't trust claims — it greps the code",
358
- " Plans are prompts — the plan IS what the builder reads",
359
- " Feature branches only — never push to main",
360
- " Read before write — no exceptions",
361
- " MVP first — build what's asked, nothing extra",
362
- " tracking.json syncs to ERP on every push",
428
+ " Lost? Type /qualia for the next step",
429
+ " Small fix? Use /qualia-quick to skip planning",
430
+ " End of day? /qualia-report before you clock out",
431
+ " Context isolation: every task gets a fresh AI brain",
432
+ " The verifier doesn't trust claims — it greps the code",
433
+ " Plans are prompts — the plan IS what the builder reads",
434
+ " Feature branches only — never push to main",
435
+ " Read before write — no exceptions",
436
+ " MVP first — build what's asked, nothing extra",
437
+ " tracking.json syncs to ERP on every push",
363
438
  ],
364
439
  };
365
440
 
@@ -395,21 +470,21 @@ async function main() {
395
470
  if: "Bash(git push*)",
396
471
  command: nodeCmd("branch-guard.js"),
397
472
  timeout: 10,
398
- statusMessage: " Checking branch permissions...",
473
+ statusMessage: " Checking branch permissions...",
399
474
  },
400
475
  {
401
476
  type: "command",
402
477
  if: "Bash(git push*)",
403
478
  command: nodeCmd("pre-push.js"),
404
479
  timeout: 15,
405
- statusMessage: " Syncing tracking...",
480
+ statusMessage: " Syncing tracking...",
406
481
  },
407
482
  {
408
483
  type: "command",
409
484
  if: "Bash(vercel --prod*)",
410
485
  command: nodeCmd("pre-deploy-gate.js"),
411
486
  timeout: 180,
412
- statusMessage: " Running quality gates...",
487
+ statusMessage: " Running quality gates...",
413
488
  },
414
489
  ],
415
490
  },
@@ -421,14 +496,14 @@ async function main() {
421
496
  if: "Edit(*.env*)|Write(*.env*)",
422
497
  command: nodeCmd("block-env-edit.js"),
423
498
  timeout: 5,
424
- statusMessage: " Checking file permissions...",
499
+ statusMessage: " Checking file permissions...",
425
500
  },
426
501
  {
427
502
  type: "command",
428
503
  if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)",
429
504
  command: nodeCmd("migration-guard.js"),
430
505
  timeout: 10,
431
- statusMessage: " Checking migration safety...",
506
+ statusMessage: " Checking migration safety...",
432
507
  },
433
508
  ],
434
509
  },
@@ -441,7 +516,7 @@ async function main() {
441
516
  type: "command",
442
517
  command: nodeCmd("pre-compact.js"),
443
518
  timeout: 15,
444
- statusMessage: " Saving state...",
519
+ statusMessage: " Saving state...",
445
520
  },
446
521
  ],
447
522
  },
@@ -467,7 +542,7 @@ async function main() {
467
542
 
468
543
  // ─── Summary ───────────────────────────────────────────
469
544
  console.log("");
470
- console.log(`${TEAL} Installed ✓${RESET}`);
545
+ console.log(`${TEAL} Installed ✓${RESET}`);
471
546
  console.log(`${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
472
547
  console.log(` ${WHITE}${member.name}${RESET} ${DIM}(${member.role})${RESET}`);
473
548
  console.log(` Skills: ${WHITE}${skills.length}${RESET}`);
package/bin/qualia-ui.js CHANGED
@@ -40,25 +40,25 @@ const RULE_DIM = `${DIM2}${RULE}${RESET}`;
40
40
 
41
41
  // ─── Action Labels ───────────────────────────────────────
42
42
  const ACTIONS = {
43
- router: { label: "SMART ROUTER", glyph: "" },
44
- new: { label: "NEW PROJECT", glyph: "" },
45
- plan: { label: "PLANNING", glyph: "" },
46
- build: { label: "BUILDING", glyph: "" },
47
- verify: { label: "VERIFYING", glyph: "" },
48
- polish: { label: "POLISHING", glyph: "" },
49
- ship: { label: "SHIPPING", glyph: "" },
50
- handoff: { label: "HANDING OFF", glyph: "" },
51
- report: { label: "SESSION REPORT", glyph: "" },
52
- debug: { label: "DEBUGGING", glyph: "" },
53
- learn: { label: "LEARNING", glyph: "" },
54
- pause: { label: "PAUSING", glyph: "" },
55
- resume: { label: "RESUMING", glyph: "" },
56
- review: { label: "REVIEW", glyph: "" },
57
- design: { label: "DESIGN PASS", glyph: "" },
58
- quick: { label: "QUICK FIX", glyph: "" },
59
- task: { label: "TASK", glyph: "" },
60
- "skill-new": { label: "NEW SKILL", glyph: "" },
61
- gaps: { label: "GAP CLOSURE", glyph: "" },
43
+ router: { label: "SMART ROUTER", glyph: "" },
44
+ new: { label: "NEW PROJECT", glyph: "" },
45
+ plan: { label: "PLANNING", glyph: "" },
46
+ build: { label: "BUILDING", glyph: "" },
47
+ verify: { label: "VERIFYING", glyph: "" },
48
+ polish: { label: "POLISHING", glyph: "" },
49
+ ship: { label: "SHIPPING", glyph: "" },
50
+ handoff: { label: "HANDING OFF", glyph: "" },
51
+ report: { label: "SESSION REPORT", glyph: "" },
52
+ debug: { label: "DEBUGGING", glyph: "" },
53
+ learn: { label: "LEARNING", glyph: "" },
54
+ pause: { label: "PAUSING", glyph: "" },
55
+ resume: { label: "RESUMING", glyph: "" },
56
+ review: { label: "REVIEW", glyph: "" },
57
+ design: { label: "DESIGN PASS", glyph: "" },
58
+ quick: { label: "QUICK FIX", glyph: "" },
59
+ task: { label: "TASK", glyph: "" },
60
+ "skill-new": { label: "NEW SKILL", glyph: "" },
61
+ gaps: { label: "GAP CLOSURE", glyph: "" },
62
62
  };
63
63
 
64
64
  // ─── State Reading ───────────────────────────────────────
@@ -126,7 +126,7 @@ function pad(str, width) {
126
126
 
127
127
  // ─── Commands ────────────────────────────────────────────
128
128
  function cmdBanner(action, phase, subtitle) {
129
- const spec = ACTIONS[action] || { label: (action || "qualia").toUpperCase(), glyph: "" };
129
+ const spec = ACTIONS[action] || { label: (action || "qualia").toUpperCase(), glyph: "" };
130
130
  const state = readState();
131
131
  const config = readConfig();
132
132
  const project = projectName();
@@ -136,7 +136,7 @@ function cmdBanner(action, phase, subtitle) {
136
136
  : spec.label;
137
137
 
138
138
  console.log("");
139
- console.log(` ${TEAL}${BOLD}${spec.glyph}${RESET} ${WHITE}${BOLD}QUALIA${RESET} ${DIM}►${RESET} ${WHITE}${title}${RESET}`);
139
+ console.log(` ${TEAL}${BOLD}${spec.glyph}${RESET} ${WHITE}${BOLD}QUALIA${RESET} ${DIM}▸${RESET} ${WHITE}${title}${RESET}`);
140
140
  console.log(` ${RULE_DIM}`);
141
141
 
142
142
  // Context panel
@@ -218,7 +218,7 @@ function cmdInfo(msg) {
218
218
  function cmdSpawn(agent, desc) {
219
219
  const name = agent || "agent";
220
220
  const d = desc ? ` ${DIM}— ${desc}${RESET}` : "";
221
- console.log(` ${TEAL}⟐${RESET} ${WHITE}Spawning${RESET} ${TEAL}${name}${RESET}${d}`);
221
+ console.log(` ${TEAL}⬡${RESET} ${WHITE}Spawning${RESET} ${TEAL}${name}${RESET}${d}`);
222
222
  }
223
223
 
224
224
  function cmdWave(num, total, taskCount) {
@@ -226,7 +226,7 @@ function cmdWave(num, total, taskCount) {
226
226
  const n = parseInt(num) || 0;
227
227
  const t = parseInt(total) || 0;
228
228
  const c = parseInt(taskCount) || 0;
229
- console.log(` ${TEAL}▸${RESET} ${WHITE}${BOLD}Wave ${n}/${t}${RESET} ${DIM}(${c} ${c === 1 ? "task" : "tasks"}, parallel)${RESET}`);
229
+ console.log(` ${TEAL}»${RESET} ${WHITE}${BOLD}Wave ${n}/${t}${RESET} ${DIM}(${c} ${c === 1 ? "task" : "tasks"}, parallel)${RESET}`);
230
230
  }
231
231
 
232
232
  function cmdTask(num, title) {
@@ -241,16 +241,16 @@ function cmdDone(num, title, commit) {
241
241
  function cmdNext(cmd) {
242
242
  if (!cmd) return;
243
243
  console.log("");
244
- console.log(` ${TEAL}→${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${cmd}${RESET}`);
244
+ console.log(` ${TEAL}⟶${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${cmd}${RESET}`);
245
245
  console.log("");
246
246
  }
247
247
 
248
248
  function cmdEnd(status, nextCmd) {
249
249
  console.log("");
250
- console.log(` ${TEAL}${BOLD}◆${RESET} ${WHITE}${BOLD}${status || "DONE"}${RESET}`);
250
+ console.log(` ${TEAL}${BOLD}⬢${RESET} ${WHITE}${BOLD}${status || "DONE"}${RESET}`);
251
251
  console.log(` ${RULE_DIM}`);
252
252
  if (nextCmd) {
253
- console.log(` ${TEAL}→${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${nextCmd}${RESET}`);
253
+ console.log(` ${TEAL}⟶${RESET} ${WHITE}Next:${RESET} ${TEAL}${BOLD}${nextCmd}${RESET}`);
254
254
  }
255
255
  console.log("");
256
256
  }