qualia-framework 3.1.0 → 3.2.1
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/bin/install.js +12 -11
- package/bin/qualia-ui.js +16 -1
- package/hooks/auto-update.js +30 -8
- package/hooks/session-start.js +17 -0
- package/package.json +1 -1
- package/skills/qualia-optimize/SKILL.md +417 -0
- package/tests/runner.js +5 -46
- package/hooks/block-env-edit.js +0 -52
package/bin/install.js
CHANGED
|
@@ -26,22 +26,22 @@ const DEFAULT_TEAM = {
|
|
|
26
26
|
"QS-HASAN-02": {
|
|
27
27
|
name: "Hasan",
|
|
28
28
|
role: "EMPLOYEE",
|
|
29
|
-
description: "Developer. Feature branches only. Cannot push to main
|
|
29
|
+
description: "Developer. Feature branches only. Cannot push to main.",
|
|
30
30
|
},
|
|
31
31
|
"QS-MOAYAD-03": {
|
|
32
32
|
name: "Moayad",
|
|
33
33
|
role: "EMPLOYEE",
|
|
34
|
-
description: "Developer. Feature branches only. Cannot push to main
|
|
34
|
+
description: "Developer. Feature branches only. Cannot push to main.",
|
|
35
35
|
},
|
|
36
36
|
"QS-RAMA-04": {
|
|
37
37
|
name: "Rama",
|
|
38
38
|
role: "EMPLOYEE",
|
|
39
|
-
description: "Developer. Feature branches only. Cannot push to main
|
|
39
|
+
description: "Developer. Feature branches only. Cannot push to main.",
|
|
40
40
|
},
|
|
41
41
|
"QS-SALLY-05": {
|
|
42
42
|
name: "Sally",
|
|
43
43
|
role: "EMPLOYEE",
|
|
44
|
-
description: "Developer. Feature branches only. Cannot push to main
|
|
44
|
+
description: "Developer. Feature branches only. Cannot push to main.",
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
47
|
|
|
@@ -208,6 +208,13 @@ async function main() {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
} catch {}
|
|
211
|
+
// v3.2.0: purge deprecated hooks from existing installs on upgrade.
|
|
212
|
+
// block-env-edit.js was retired — team now has full read/write on .env*.
|
|
213
|
+
const DEPRECATED_HOOKS = ["block-env-edit.js"];
|
|
214
|
+
for (const f of DEPRECATED_HOOKS) {
|
|
215
|
+
const p = path.join(hooksDest, f);
|
|
216
|
+
try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch {}
|
|
217
|
+
}
|
|
211
218
|
for (const file of fs.readdirSync(hooksSource)) {
|
|
212
219
|
try {
|
|
213
220
|
const dest = path.join(hooksDest, file);
|
|
@@ -521,12 +528,6 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
521
528
|
{
|
|
522
529
|
matcher: "Edit|Write",
|
|
523
530
|
hooks: [
|
|
524
|
-
{
|
|
525
|
-
type: "command",
|
|
526
|
-
command: nodeCmd("block-env-edit.js"),
|
|
527
|
-
timeout: 5,
|
|
528
|
-
statusMessage: "⬢ Checking file permissions...",
|
|
529
|
-
},
|
|
530
531
|
{
|
|
531
532
|
type: "command",
|
|
532
533
|
if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)",
|
|
@@ -572,7 +573,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
572
573
|
|
|
573
574
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
574
575
|
|
|
575
|
-
ok("Hooks: session-start, auto-update, branch-guard, pre-push,
|
|
576
|
+
ok("Hooks: session-start, auto-update, branch-guard, pre-push, migration-guard, deploy-gate, pre-compact");
|
|
576
577
|
ok("Status line + spinner configured");
|
|
577
578
|
ok("Environment variables + permissions");
|
|
578
579
|
|
package/bin/qualia-ui.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// done <N> <title> [commit] — task line (completed)
|
|
17
17
|
// next <command> — "Run: /qualia-X" footer
|
|
18
18
|
// end <status> [next-command] — closing banner with optional next
|
|
19
|
+
// update <current> <latest> — sticky framework update banner
|
|
19
20
|
|
|
20
21
|
const fs = require("fs");
|
|
21
22
|
const path = require("path");
|
|
@@ -258,6 +259,19 @@ function cmdEnd(status, nextCmd) {
|
|
|
258
259
|
console.log("");
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
function cmdUpdate(current, latest) {
|
|
263
|
+
if (!current || !latest) return;
|
|
264
|
+
console.log("");
|
|
265
|
+
console.log(` ${YELLOW}${BOLD}▲${RESET} ${WHITE}${BOLD}QUALIA FRAMEWORK UPDATE AVAILABLE${RESET}`);
|
|
266
|
+
console.log(` ${DIM2}${RULE}${RESET}`);
|
|
267
|
+
console.log(` ${pad(DIM + "Current" + RESET, 20)}${DIM}${current}${RESET}`);
|
|
268
|
+
console.log(` ${pad(DIM + "Latest" + RESET, 20)}${GREEN}${BOLD}${latest}${RESET}`);
|
|
269
|
+
console.log(` ${pad(DIM + "Update" + RESET, 20)}${TEAL}npx qualia-framework@latest install${RESET}`);
|
|
270
|
+
console.log(` ${DIM2}${RULE}${RESET}`);
|
|
271
|
+
console.log(` ${DIM}This notice shows every session until you update.${RESET}`);
|
|
272
|
+
console.log("");
|
|
273
|
+
}
|
|
274
|
+
|
|
261
275
|
// ─── Main ────────────────────────────────────────────────
|
|
262
276
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
263
277
|
switch (cmd) {
|
|
@@ -276,9 +290,10 @@ switch (cmd) {
|
|
|
276
290
|
case "done": cmdDone(rest[0], rest[1], rest[2]); break;
|
|
277
291
|
case "next": cmdNext(rest.join(" ")); break;
|
|
278
292
|
case "end": cmdEnd(rest[0], rest.slice(1).join(" ")); break;
|
|
293
|
+
case "update": cmdUpdate(rest[0], rest[1]); break;
|
|
279
294
|
default:
|
|
280
295
|
console.error(
|
|
281
|
-
`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]`
|
|
282
297
|
);
|
|
283
298
|
process.exit(1);
|
|
284
299
|
}
|
package/hooks/auto-update.js
CHANGED
|
@@ -66,6 +66,11 @@ try {
|
|
|
66
66
|
|
|
67
67
|
// Fork the check-and-update into a detached background process so the hook
|
|
68
68
|
// returns immediately and Claude Code is never blocked.
|
|
69
|
+
//
|
|
70
|
+
// OWNER: silent auto-install (unchanged behavior).
|
|
71
|
+
// EMPLOYEE: write a sticky notification file — session-start.js renders a
|
|
72
|
+
// banner every session until they run the update manually. Fawzi (OWNER)
|
|
73
|
+
// never sees the banner because his framework auto-updates ahead of it.
|
|
69
74
|
const script = `
|
|
70
75
|
const fs = require("fs");
|
|
71
76
|
const path = require("path");
|
|
@@ -73,6 +78,7 @@ try {
|
|
|
73
78
|
const CLAUDE_DIR = ${JSON.stringify(CLAUDE_DIR)};
|
|
74
79
|
const LOCK_FILE = ${JSON.stringify(LOCK_FILE)};
|
|
75
80
|
const CONFIG_FILE = ${JSON.stringify(CONFIG_FILE)};
|
|
81
|
+
const NOTIF_FILE = path.join(CLAUDE_DIR, ".qualia-update-available.json");
|
|
76
82
|
const cfg = ${JSON.stringify(cfg)};
|
|
77
83
|
try {
|
|
78
84
|
fs.writeFileSync(LOCK_FILE, String(process.pid));
|
|
@@ -82,7 +88,7 @@ try {
|
|
|
82
88
|
shell: process.platform === "win32",
|
|
83
89
|
});
|
|
84
90
|
const latest = ((r.stdout || "").trim());
|
|
85
|
-
if (!latest) { fs.unlinkSync(LOCK_FILE); process.exit(0); }
|
|
91
|
+
if (!latest) { try { fs.unlinkSync(LOCK_FILE); } catch {} process.exit(0); }
|
|
86
92
|
const cmp = (a, b) => {
|
|
87
93
|
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
88
94
|
for (let i = 0; i < 3; i++) {
|
|
@@ -92,13 +98,29 @@ try {
|
|
|
92
98
|
return 0;
|
|
93
99
|
};
|
|
94
100
|
if (cmp(latest, cfg.version) > 0) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
if (cfg.role === "OWNER") {
|
|
102
|
+
// Silent auto-install for OWNER — no notification banner ever shown.
|
|
103
|
+
spawnSync("npx", ["qualia-framework@latest", "install"], {
|
|
104
|
+
input: cfg.code + "\\n",
|
|
105
|
+
timeout: 120000,
|
|
106
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
107
|
+
shell: process.platform === "win32",
|
|
108
|
+
});
|
|
109
|
+
try { fs.unlinkSync(NOTIF_FILE); } catch {}
|
|
110
|
+
} else {
|
|
111
|
+
// EMPLOYEE: write sticky notification. session-start.js will render
|
|
112
|
+
// a visible banner every session until the employee runs the update.
|
|
113
|
+
try {
|
|
114
|
+
fs.writeFileSync(NOTIF_FILE, JSON.stringify({
|
|
115
|
+
current: cfg.version,
|
|
116
|
+
latest: latest,
|
|
117
|
+
detected_at: new Date().toISOString(),
|
|
118
|
+
}, null, 2));
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Already up to date — clear any stale notification file.
|
|
123
|
+
try { fs.unlinkSync(NOTIF_FILE); } catch {}
|
|
102
124
|
}
|
|
103
125
|
} catch {}
|
|
104
126
|
try { fs.unlinkSync(LOCK_FILE); } catch {}
|
package/hooks/session-start.js
CHANGED
|
@@ -18,6 +18,7 @@ const HOME = os.homedir();
|
|
|
18
18
|
const UI = path.join(HOME, ".claude", "bin", "qualia-ui.js");
|
|
19
19
|
const STATE_FILE = path.join(".planning", "STATE.md");
|
|
20
20
|
const CONTINUE_HERE = ".continue-here.md";
|
|
21
|
+
const NOTIF_FILE = path.join(HOME, ".claude", ".qualia-update-available.json");
|
|
21
22
|
|
|
22
23
|
function runUi(...args) {
|
|
23
24
|
if (!fs.existsSync(UI)) return;
|
|
@@ -65,7 +66,23 @@ function fallbackText() {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function maybeRenderUpdateBanner() {
|
|
70
|
+
// EMPLOYEE-only sticky banner. auto-update.js writes NOTIF_FILE when a new
|
|
71
|
+
// version is detected; we render it every session until the user actually
|
|
72
|
+
// runs `npx qualia-framework@latest install`. The file is cleared by
|
|
73
|
+
// auto-update.js once the install completes or the version catches up.
|
|
74
|
+
if (!fs.existsSync(NOTIF_FILE) || !fs.existsSync(UI)) return;
|
|
75
|
+
try {
|
|
76
|
+
const notif = JSON.parse(fs.readFileSync(NOTIF_FILE, "utf8"));
|
|
77
|
+
if (notif && notif.current && notif.latest) {
|
|
78
|
+
runUi("update", notif.current, notif.latest);
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
try {
|
|
84
|
+
maybeRenderUpdateBanner();
|
|
85
|
+
|
|
69
86
|
if (!fs.existsSync(UI)) {
|
|
70
87
|
fallbackText();
|
|
71
88
|
} else if (fs.existsSync(STATE_FILE)) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qualia-optimize
|
|
3
|
+
description: "Deep optimization pass — reads .planning/ AND codebase to find performance, design, UI, backend, and frontend issues. Spawns parallel specialist agents. Use this skill whenever the user says 'optimize', 'optimization pass', 'find issues', 'qualia-optimize', 'deep optimize', 'performance audit', 'design alignment check', 'speed up', 'slow', 'bundle size', or wants a comprehensive quality sweep. Supports --perf, --ui, --backend, --alignment, --fix flags."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Qualia Optimize — Deep Codebase + Planning Optimization
|
|
7
|
+
|
|
8
|
+
Comprehensive optimization that reads BOTH `.planning/` docs AND the actual codebase. Never analyze one without the other.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
- `/qualia-optimize` — Full optimization (all 6 dimensions)
|
|
13
|
+
- `/qualia-optimize --perf` — Performance only (queries, bundle, render, latency)
|
|
14
|
+
- `/qualia-optimize --ui` — Frontend/design/UI only
|
|
15
|
+
- `/qualia-optimize --backend` — Backend only (RLS, auth, queries, edge functions)
|
|
16
|
+
- `/qualia-optimize --alignment` — Planning-code alignment check only
|
|
17
|
+
- `/qualia-optimize --fix` — Auto-fix LOW/MEDIUM findings from existing OPTIMIZE.md
|
|
18
|
+
|
|
19
|
+
## Process
|
|
20
|
+
|
|
21
|
+
### Step 1: Parse Arguments
|
|
22
|
+
|
|
23
|
+
Extract mode from $ARGUMENTS. Default to `full`.
|
|
24
|
+
|
|
25
|
+
Supported modes: `full`, `perf`, `ui`, `backend`, `alignment`, `fix`.
|
|
26
|
+
|
|
27
|
+
If `--fix`: skip to Step 9 (requires existing `.planning/OPTIMIZE.md`).
|
|
28
|
+
|
|
29
|
+
### Step 2: Load Planning Context (MANDATORY — never skip)
|
|
30
|
+
|
|
31
|
+
Read ALL of these (skip silently if file doesn't exist, but always attempt):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cat .planning/PROJECT.md 2>/dev/null || echo "NO_PROJECT"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cat .planning/REQUIREMENTS.md 2>/dev/null || echo "NO_REQUIREMENTS"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cat .planning/ROADMAP.md 2>/dev/null || echo "NO_ROADMAP"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cat .planning/STATE.md 2>/dev/null || echo "NO_STATE"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cat .planning/DESIGN.md 2>/dev/null || echo "NO_DESIGN"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Also read the rules:
|
|
54
|
+
```bash
|
|
55
|
+
cat ~/.claude/rules/frontend.md 2>/dev/null
|
|
56
|
+
cat ~/.claude/rules/security.md 2>/dev/null
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Store all content — you will inline it into agent prompts.
|
|
60
|
+
|
|
61
|
+
**If NO planning docs exist at all**: warn the user but proceed. The optimization still works on raw codebase — it just can't check alignment.
|
|
62
|
+
|
|
63
|
+
### Step 3: Discover Codebase
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pwd && node -e "try{const p=require('./package.json');console.log(JSON.stringify({name:p.name,deps:Object.keys(p.dependencies||{}),devDeps:Object.keys(p.devDependencies||{})}))}catch(e){console.log('{}')}" 2>/dev/null
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git log --oneline -15 2>/dev/null
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git diff --stat HEAD~10..HEAD 2>/dev/null | tail -5
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Project structure
|
|
79
|
+
ls -d app/ src/ pages/ components/ lib/ actions/ hooks/ supabase/ types/ 2>/dev/null
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Classify project type: `web` | `voice` | `mobile` | `agent` | `edge-functions` | `unknown`
|
|
83
|
+
|
|
84
|
+
### Step 4: Spawn Wave 1 Agents (parallel)
|
|
85
|
+
|
|
86
|
+
Based on mode, spawn agents in a **single message** with multiple Agent() calls.
|
|
87
|
+
|
|
88
|
+
| Mode | Agents |
|
|
89
|
+
|------|--------|
|
|
90
|
+
| `full` | frontend-agent + backend-agent + performance-oracle (3 parallel) |
|
|
91
|
+
| `perf` | performance-oracle only |
|
|
92
|
+
| `ui` | frontend-agent only |
|
|
93
|
+
| `backend` | backend-agent only |
|
|
94
|
+
| `alignment` | general-purpose with alignment prompt |
|
|
95
|
+
|
|
96
|
+
**CRITICAL**: Inline ALL planning context into each agent prompt. `@` references don't work across Agent() boundaries.
|
|
97
|
+
|
|
98
|
+
#### Frontend Agent Prompt
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Agent(
|
|
102
|
+
prompt="You are optimizing a project's frontend. Read the planning docs and codebase rules below, then analyze the actual code.
|
|
103
|
+
|
|
104
|
+
<planning>
|
|
105
|
+
{PROJECT.md content}
|
|
106
|
+
{REQUIREMENTS.md content}
|
|
107
|
+
{DESIGN.md content}
|
|
108
|
+
</planning>
|
|
109
|
+
|
|
110
|
+
<rules>
|
|
111
|
+
{rules/frontend.md content}
|
|
112
|
+
</rules>
|
|
113
|
+
|
|
114
|
+
<task>
|
|
115
|
+
Analyze the frontend codebase for issues in these categories:
|
|
116
|
+
|
|
117
|
+
1. **UI Quality**
|
|
118
|
+
- Loading states: every async operation should show a loading indicator
|
|
119
|
+
- Error states: every data-fetching component should handle errors gracefully
|
|
120
|
+
- Empty states: lists/tables should handle zero items with helpful messaging
|
|
121
|
+
- Responsive: check for fixed pixel widths on containers, missing breakpoint handling
|
|
122
|
+
- Accessibility: alt text on images, ARIA labels on interactive elements, keyboard navigation
|
|
123
|
+
|
|
124
|
+
2. **Design Alignment**
|
|
125
|
+
- Compare actual components against DESIGN.md decisions (colors, typography, spacing)
|
|
126
|
+
- Check rules/frontend.md compliance: distinctive fonts? sharp accents? transitions? No card grids or blue-purple gradients?
|
|
127
|
+
- Consistency: are the same patterns used throughout? (button styles, spacing, color usage)
|
|
128
|
+
|
|
129
|
+
3. **Frontend Performance**
|
|
130
|
+
- Bundle: large library imports that could be tree-shaken or dynamically imported
|
|
131
|
+
- Images: using next/image? width/height set? lazy loading below fold?
|
|
132
|
+
- Fonts: using next/font? No render-blocking font loads?
|
|
133
|
+
- CSS: unused Tailwind classes? conflicting styles?
|
|
134
|
+
- Rendering: unnecessary re-renders, missing React.memo on list items, heavy computations in render
|
|
135
|
+
|
|
136
|
+
For EVERY finding, output in this exact format:
|
|
137
|
+
- **What**: [description]
|
|
138
|
+
- **Where**: [file:line]
|
|
139
|
+
- **Why**: [impact on users/performance]
|
|
140
|
+
- **Fix**: [concrete fix suggestion]
|
|
141
|
+
- **Severity**: CRITICAL | HIGH | MEDIUM | LOW
|
|
142
|
+
</task>",
|
|
143
|
+
subagent_type="frontend-agent",
|
|
144
|
+
description="Frontend optimization analysis"
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Backend Agent Prompt
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Agent(
|
|
152
|
+
prompt="You are optimizing a project's backend. Read the planning docs and security rules below, then analyze the actual code.
|
|
153
|
+
|
|
154
|
+
<planning>
|
|
155
|
+
{PROJECT.md content}
|
|
156
|
+
{REQUIREMENTS.md content}
|
|
157
|
+
</planning>
|
|
158
|
+
|
|
159
|
+
<rules>
|
|
160
|
+
{rules/security.md content}
|
|
161
|
+
</rules>
|
|
162
|
+
|
|
163
|
+
<task>
|
|
164
|
+
Analyze the backend codebase for issues:
|
|
165
|
+
|
|
166
|
+
1. **Security**
|
|
167
|
+
- RLS: every Supabase table must have ROW LEVEL SECURITY enabled with policies
|
|
168
|
+
- Service role: grep for service_role key usage in client-side code (app/, components/, src/) — should be ZERO
|
|
169
|
+
- Auth: all mutations use server-side auth check (supabase.auth.getUser())
|
|
170
|
+
- Validation: input validated with Zod before database operations
|
|
171
|
+
- No dangerouslySetInnerHTML or eval()
|
|
172
|
+
|
|
173
|
+
2. **Data Access Patterns**
|
|
174
|
+
- Server actions vs client mutations: data writes should use 'use server' actions, not direct Supabase client calls
|
|
175
|
+
- Proper error handling: try/catch with meaningful error messages
|
|
176
|
+
- Revalidation: revalidatePath/revalidateTag after mutations
|
|
177
|
+
|
|
178
|
+
3. **Edge Functions** (if supabase/functions/ exists)
|
|
179
|
+
- Cold start optimization: bundle size, dependency count
|
|
180
|
+
- Error handling and logging
|
|
181
|
+
- CORS configuration
|
|
182
|
+
- Timeout protection (maxDuration)
|
|
183
|
+
|
|
184
|
+
4. **API Quality**
|
|
185
|
+
- Rate limiting on public endpoints
|
|
186
|
+
- Proper HTTP status codes
|
|
187
|
+
- Consistent error response format
|
|
188
|
+
|
|
189
|
+
For EVERY finding, output:
|
|
190
|
+
- **What**: [description]
|
|
191
|
+
- **Where**: [file:line]
|
|
192
|
+
- **Why**: [impact]
|
|
193
|
+
- **Fix**: [concrete suggestion]
|
|
194
|
+
- **Severity**: CRITICAL | HIGH | MEDIUM | LOW
|
|
195
|
+
</task>",
|
|
196
|
+
subagent_type="backend-agent",
|
|
197
|
+
description="Backend optimization analysis"
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Performance Oracle Prompt
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
Agent(
|
|
205
|
+
prompt="You are analyzing cross-cutting performance issues. Read the project context, then analyze the codebase.
|
|
206
|
+
|
|
207
|
+
<planning>
|
|
208
|
+
{PROJECT.md content}
|
|
209
|
+
</planning>
|
|
210
|
+
|
|
211
|
+
<task>
|
|
212
|
+
Analyze for performance issues across the full stack:
|
|
213
|
+
|
|
214
|
+
1. **Database Queries**
|
|
215
|
+
- N+1 queries: Supabase .from() calls inside loops or .map()
|
|
216
|
+
- Missing indexes: .eq()/.filter()/.order() columns without corresponding indexes in migrations
|
|
217
|
+
- Sequential queries that could be parallel (Promise.all)
|
|
218
|
+
- Over-fetching: SELECT * when only specific columns needed
|
|
219
|
+
|
|
220
|
+
2. **API Latency**
|
|
221
|
+
- Sequential API calls from client that could be batched
|
|
222
|
+
- Missing caching (SWR/React Query stale times, HTTP cache headers)
|
|
223
|
+
- Large payloads without pagination
|
|
224
|
+
|
|
225
|
+
3. **Bundle Size**
|
|
226
|
+
- Barrel exports (index.ts re-exporting everything) preventing tree-shaking
|
|
227
|
+
- Large libraries imported for single functions (lodash, moment)
|
|
228
|
+
- Missing dynamic imports for heavy components (charts, editors, maps)
|
|
229
|
+
|
|
230
|
+
4. **Render Performance**
|
|
231
|
+
- Expensive computations in render path without useMemo
|
|
232
|
+
- Event handlers recreated on every render without useCallback
|
|
233
|
+
- Large lists without virtualization
|
|
234
|
+
- Context providers causing unnecessary re-renders
|
|
235
|
+
|
|
236
|
+
For EVERY finding, output:
|
|
237
|
+
- **What**: [description]
|
|
238
|
+
- **Where**: [file:line]
|
|
239
|
+
- **Why**: [performance impact, quantified if possible]
|
|
240
|
+
- **Fix**: [concrete suggestion]
|
|
241
|
+
- **Severity**: CRITICAL | HIGH | MEDIUM | LOW
|
|
242
|
+
</task>",
|
|
243
|
+
subagent_type="performance-oracle",
|
|
244
|
+
description="Performance optimization analysis"
|
|
245
|
+
)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Step 5: Spawn Wave 2 Agent (after Wave 1 completes)
|
|
249
|
+
|
|
250
|
+
After all Wave 1 agents return, spawn the architecture strategist with their combined findings:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
Agent(
|
|
254
|
+
prompt="You are synthesizing optimization findings from 3 specialist agents. Look for cross-cutting architectural issues.
|
|
255
|
+
|
|
256
|
+
<wave1_findings>
|
|
257
|
+
{All findings from frontend-agent, backend-agent, performance-oracle}
|
|
258
|
+
</wave1_findings>
|
|
259
|
+
|
|
260
|
+
<planning>
|
|
261
|
+
{PROJECT.md content}
|
|
262
|
+
{REQUIREMENTS.md content}
|
|
263
|
+
</planning>
|
|
264
|
+
|
|
265
|
+
<task>
|
|
266
|
+
1. Identify patterns across findings — recurring issues that point to a structural problem
|
|
267
|
+
2. Find coupling issues between frontend and backend
|
|
268
|
+
3. Check for inconsistent patterns (e.g., some routes use server actions, others use API routes)
|
|
269
|
+
4. Identify missing abstractions (same pattern repeated 3+ times)
|
|
270
|
+
5. Check for dead code and unused exports
|
|
271
|
+
|
|
272
|
+
Output:
|
|
273
|
+
- **Structural findings** (architectural issues, not covered by Wave 1)
|
|
274
|
+
- **Pattern consolidation** (where Wave 1 findings share a root cause)
|
|
275
|
+
- Each finding in the same format: What/Where/Why/Fix/Severity
|
|
276
|
+
</task>",
|
|
277
|
+
subagent_type="architecture-strategist",
|
|
278
|
+
description="Architecture synthesis"
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Skip Wave 2 for single-mode runs** (`--perf`, `--ui`, `--backend`). Only run for `full` mode.
|
|
283
|
+
|
|
284
|
+
### Step 6: Alignment Check (always runs in `full` and `alignment` modes)
|
|
285
|
+
|
|
286
|
+
For `alignment` mode, this is the sole analysis. For `full` mode, run alongside Wave 1.
|
|
287
|
+
|
|
288
|
+
Read REQUIREMENTS.md and ROADMAP.md. For each requirement marked "Complete" or mapped to a completed phase:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Find completed requirements
|
|
292
|
+
grep -E "Complete|✓" .planning/REQUIREMENTS.md 2>/dev/null
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
For each completed requirement:
|
|
296
|
+
- Grep the codebase for evidence it actually exists (routes, components, API endpoints)
|
|
297
|
+
- If not found: flag as "Claimed complete but not implemented"
|
|
298
|
+
|
|
299
|
+
Then scan for orphan features:
|
|
300
|
+
```bash
|
|
301
|
+
# Find all routes/pages
|
|
302
|
+
find app -name "page.tsx" -o -name "route.ts" 2>/dev/null
|
|
303
|
+
# Find all API routes
|
|
304
|
+
find app/api -name "route.ts" 2>/dev/null
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Cross-reference with REQUIREMENTS.md — any route/feature NOT in requirements is flagged as "Undocumented feature".
|
|
308
|
+
|
|
309
|
+
### Step 7: Collect and Score Findings
|
|
310
|
+
|
|
311
|
+
After all agents return:
|
|
312
|
+
1. Deduplicate (same file:line from multiple agents → keep the most detailed one)
|
|
313
|
+
2. Sort by severity: CRITICAL first
|
|
314
|
+
3. Group by dimension: Performance, Design Alignment, UI Quality, Backend, Frontend, Planning-Code Alignment, Architecture
|
|
315
|
+
|
|
316
|
+
Count totals per severity.
|
|
317
|
+
|
|
318
|
+
### Step 8: Write OPTIMIZE.md and Present Results
|
|
319
|
+
|
|
320
|
+
Write to `.planning/OPTIMIZE.md`:
|
|
321
|
+
|
|
322
|
+
```markdown
|
|
323
|
+
---
|
|
324
|
+
date: {YYYY-MM-DD HH:MM}
|
|
325
|
+
mode: {full|perf|ui|backend|alignment}
|
|
326
|
+
critical: {N}
|
|
327
|
+
high: {N}
|
|
328
|
+
medium: {N}
|
|
329
|
+
low: {N}
|
|
330
|
+
status: {clean|needs_attention|critical_issues}
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
# Optimization Report
|
|
334
|
+
|
|
335
|
+
**Project:** {name} | **Mode:** {mode} | **Date:** {date}
|
|
336
|
+
|
|
337
|
+
## Summary
|
|
338
|
+
|
|
339
|
+
{2-3 sentence overview}
|
|
340
|
+
|
|
341
|
+
{If status is clean: "No critical issues found. Project is in good shape."}
|
|
342
|
+
|
|
343
|
+
## Critical Issues
|
|
344
|
+
|
|
345
|
+
| # | Dimension | Finding | Location | Fix |
|
|
346
|
+
|---|-----------|---------|----------|-----|
|
|
347
|
+
{findings}
|
|
348
|
+
|
|
349
|
+
## High Priority
|
|
350
|
+
|
|
351
|
+
| # | Dimension | Finding | Location | Fix |
|
|
352
|
+
|---|-----------|---------|----------|-----|
|
|
353
|
+
{findings}
|
|
354
|
+
|
|
355
|
+
## Medium Priority
|
|
356
|
+
|
|
357
|
+
| # | Dimension | Finding | Location | Fix |
|
|
358
|
+
|---|-----------|---------|----------|-----|
|
|
359
|
+
{findings}
|
|
360
|
+
|
|
361
|
+
## Low Priority
|
|
362
|
+
|
|
363
|
+
| # | Dimension | Finding | Location | Fix |
|
|
364
|
+
|---|-----------|---------|----------|-----|
|
|
365
|
+
{findings}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Commit:
|
|
369
|
+
```bash
|
|
370
|
+
node ~/.claude/qualia-framework/bin/qualia-tools.js commit "docs: optimization report ({mode} mode, {critical} critical)" --files .planning/OPTIMIZE.md
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Present results:**
|
|
374
|
+
|
|
375
|
+
If CRITICAL findings exist:
|
|
376
|
+
```
|
|
377
|
+
{critical} critical issues found. Options:
|
|
378
|
+
|
|
379
|
+
1. Create a fix phase in ROADMAP.md for critical + high findings
|
|
380
|
+
2. Auto-fix LOW/MEDIUM findings: /qualia-optimize --fix
|
|
381
|
+
3. Review full report: cat .planning/OPTIMIZE.md
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
If no CRITICAL:
|
|
385
|
+
```
|
|
386
|
+
Optimization complete. {total} findings ({high} high, {medium} medium, {low} low).
|
|
387
|
+
Report: .planning/OPTIMIZE.md
|
|
388
|
+
|
|
389
|
+
Run /qualia-optimize --fix to auto-fix LOW/MEDIUM findings.
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Step 9: --fix Mode
|
|
393
|
+
|
|
394
|
+
When `--fix` is provided:
|
|
395
|
+
|
|
396
|
+
1. Read existing `.planning/OPTIMIZE.md` — if not found, error: "Run /qualia-optimize first"
|
|
397
|
+
2. Filter to LOW and MEDIUM findings only
|
|
398
|
+
3. For each finding with a clear, safe fix:
|
|
399
|
+
- Read the target file
|
|
400
|
+
- Apply the fix
|
|
401
|
+
- Verify it doesn't break (npx tsc --noEmit if TypeScript)
|
|
402
|
+
4. Update OPTIMIZE.md: mark fixed findings, recount severities
|
|
403
|
+
5. Commit changes
|
|
404
|
+
6. Report what was fixed and what remains
|
|
405
|
+
|
|
406
|
+
**Never auto-fix CRITICAL or HIGH** — those require human judgment.
|
|
407
|
+
|
|
408
|
+
### Step 10: Gap Phase Creation (if user selects option 1 from Step 8)
|
|
409
|
+
|
|
410
|
+
If user wants a fix phase for critical issues:
|
|
411
|
+
|
|
412
|
+
1. Read ROADMAP.md, find current phase number
|
|
413
|
+
2. Insert a decimal phase: `Phase {N}.1: Optimization Fixes (INSERTED)`
|
|
414
|
+
3. Map CRITICAL and HIGH findings as success criteria
|
|
415
|
+
4. Update STATE.md
|
|
416
|
+
5. Commit
|
|
417
|
+
6. Suggest: "Fix phase created. Run `/qualia-plan {N}.1`"
|
package/tests/runner.js
CHANGED
|
@@ -806,7 +806,7 @@ waves: 1
|
|
|
806
806
|
describe("Hooks", () => {
|
|
807
807
|
it("all hooks pass syntax check", () => {
|
|
808
808
|
const hooks = fs.readdirSync(HOOKS).filter(f => f.endsWith(".js"));
|
|
809
|
-
assert.ok(hooks.length >=
|
|
809
|
+
assert.ok(hooks.length >= 7, `Expected 7+ hooks, found ${hooks.length}`);
|
|
810
810
|
for (const hook of hooks) {
|
|
811
811
|
const r = spawnSync(process.execPath, ["--check", path.join(HOOKS, hook)], {
|
|
812
812
|
encoding: "utf8", timeout: 5000,
|
|
@@ -880,49 +880,8 @@ describe("Hooks", () => {
|
|
|
880
880
|
assert.equal(r.status, 0);
|
|
881
881
|
});
|
|
882
882
|
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
it("block-env-edit blocks .env files", () => {
|
|
886
|
-
const r = runHook("block-env-edit.js", {
|
|
887
|
-
tool_input: { file_path: "/project/.env" },
|
|
888
|
-
});
|
|
889
|
-
assert.equal(r.status, 2);
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
it("block-env-edit blocks .env.local files", () => {
|
|
893
|
-
const r = runHook("block-env-edit.js", {
|
|
894
|
-
tool_input: { file_path: "/project/.env.local" },
|
|
895
|
-
});
|
|
896
|
-
assert.equal(r.status, 2);
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
it("block-env-edit blocks .env.production files", () => {
|
|
900
|
-
const r = runHook("block-env-edit.js", {
|
|
901
|
-
tool_input: { file_path: ".env.production" },
|
|
902
|
-
});
|
|
903
|
-
assert.equal(r.status, 2);
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
it("block-env-edit blocks Windows-style .env paths", () => {
|
|
907
|
-
const r = runHook("block-env-edit.js", {
|
|
908
|
-
tool_input: { file_path: "C:\\project\\.env.local" },
|
|
909
|
-
});
|
|
910
|
-
assert.equal(r.status, 2);
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it("block-env-edit allows non-env files", () => {
|
|
914
|
-
const r = runHook("block-env-edit.js", {
|
|
915
|
-
tool_input: { file_path: "src/app.tsx" },
|
|
916
|
-
});
|
|
917
|
-
assert.equal(r.status, 0);
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
it("block-env-edit allows component files", () => {
|
|
921
|
-
const r = runHook("block-env-edit.js", {
|
|
922
|
-
tool_input: { file_path: "components/Footer.tsx" },
|
|
923
|
-
});
|
|
924
|
-
assert.equal(r.status, 0);
|
|
925
|
-
});
|
|
883
|
+
// block-env-edit.js was retired in v3.2.0 — team now has full read/write on
|
|
884
|
+
// .env* files. See CHANGELOG v3.2.0 and bin/install.js DEPRECATED_HOOKS.
|
|
926
885
|
|
|
927
886
|
// --- pre-push.js ---
|
|
928
887
|
|
|
@@ -1756,12 +1715,12 @@ describe("install.js", () => {
|
|
|
1756
1715
|
}
|
|
1757
1716
|
});
|
|
1758
1717
|
|
|
1759
|
-
it("
|
|
1718
|
+
it("7 hooks installed (block-env-edit removed in v3.2.0)", () => {
|
|
1760
1719
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
1761
1720
|
try {
|
|
1762
1721
|
runInstall("QS-FAWZI-01", tmpHome);
|
|
1763
1722
|
const hooks = fs.readdirSync(path.join(tmpHome, ".claude", "hooks")).filter(f => f.endsWith(".js"));
|
|
1764
|
-
assert.equal(hooks.length,
|
|
1723
|
+
assert.equal(hooks.length, 7);
|
|
1765
1724
|
} finally {
|
|
1766
1725
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
1767
1726
|
}
|
package/hooks/block-env-edit.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ~/.claude/hooks/block-env-edit.js — prevent editing .env files.
|
|
3
|
-
// PreToolUse hook on Edit/Write tool calls. Reads tool input as JSON on stdin.
|
|
4
|
-
// Exits 2 to BLOCK the tool call. Exits 0 to allow it.
|
|
5
|
-
// Cross-platform (Windows/macOS/Linux).
|
|
6
|
-
|
|
7
|
-
const fs = require("fs");
|
|
8
|
-
|
|
9
|
-
const _traceStart = Date.now();
|
|
10
|
-
|
|
11
|
-
function readInput() {
|
|
12
|
-
try {
|
|
13
|
-
const raw = fs.readFileSync(0, "utf8");
|
|
14
|
-
return JSON.parse(raw);
|
|
15
|
-
} catch {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const input = readInput();
|
|
21
|
-
const file = (input.tool_input && (input.tool_input.file_path || input.tool_input.command)) || "";
|
|
22
|
-
|
|
23
|
-
// Match .env, .env.local, .env.production, .env.*, etc.
|
|
24
|
-
// Normalize separators so Windows paths (C:\project\.env.local) also match.
|
|
25
|
-
const normalized = String(file).replace(/\\/g, "/");
|
|
26
|
-
|
|
27
|
-
function _trace(hookName, result, extra) {
|
|
28
|
-
try {
|
|
29
|
-
const os = require("os");
|
|
30
|
-
const path = require("path");
|
|
31
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
32
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
33
|
-
const entry = {
|
|
34
|
-
hook: hookName,
|
|
35
|
-
result,
|
|
36
|
-
timestamp: new Date().toISOString(),
|
|
37
|
-
duration_ms: Date.now() - _traceStart,
|
|
38
|
-
...extra,
|
|
39
|
-
};
|
|
40
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
41
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
42
|
-
} catch {}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (/\.env(\.|$)/.test(normalized)) {
|
|
46
|
-
console.log("BLOCKED: Cannot edit environment files. Ask Fawzi to update secrets.");
|
|
47
|
-
_trace("block-env-edit", "block", { file: normalized });
|
|
48
|
-
process.exit(2);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
_trace("block-env-edit", "allow");
|
|
52
|
-
process.exit(0);
|