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 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 or edit .env files.",
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 or edit .env files.",
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 or edit .env files.",
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 or edit .env files.",
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, block-env-edit, migration-guard, deploy-gate, pre-compact");
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
  }
@@ -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
- // Silent update pipe the install code via stdin
96
- const child = spawnSync("npx", ["qualia-framework@latest", "install"], {
97
- input: cfg.code + "\\n",
98
- timeout: 120000,
99
- stdio: ["pipe", "ignore", "ignore"],
100
- shell: process.platform === "win32",
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 {}
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -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 >= 8, `Expected 8+ hooks, found ${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
- // --- block-env-edit.js ---
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("8 hooks installed", () => {
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, 8);
1723
+ assert.equal(hooks.length, 7);
1765
1724
  } finally {
1766
1725
  fs.rmSync(tmpHome, { recursive: true, force: true });
1767
1726
  }
@@ -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);