qualia-framework 4.1.1 → 4.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.
@@ -0,0 +1,263 @@
1
+ # Qualia Framework v4.1.0 — Production Review
2
+
3
+ **Date:** 2026-04-22
4
+ **Scope:** 26 skills, 8 agents, 7 hooks, 4 bin scripts, 6 rules, templates, docs, HTML surfaces
5
+ **Method:** 4 parallel specialist audits (commands/skills, hooks/state, memory/knowledge, design/UX)
6
+ **Findings:** 142 total across 4 dimensions
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ | Dimension | Critical | High | Medium | Low | Score |
13
+ |-----------|----------|------|--------|-----|-------|
14
+ | Commands & Skills | 5 | 14 | 17 | 9 | **2/5** |
15
+ | Hooks & State | 0 | 3 | 16 | 13 | **3/5** |
16
+ | Memory & Knowledge | 3 | 5 | 6 | 6 | **2/5** |
17
+ | Design & UX | 0 | 11 | 15 | 11 | **2/5** |
18
+ | **Total** | **8** | **33** | **54** | **47** | **2.25/5** |
19
+
20
+ **Verdict: FAIL** — 8 critical blockers, 33 high findings. Framework is functional for OWNER daily use but structurally unsound for multi-employee scale. Ship v4.2.0 with the critical-fix batch before onboarding new team members.
21
+
22
+ ---
23
+
24
+ ## Top 10 Critical & High Issues (fix first)
25
+
26
+ ### 1. `grounding.md` is a phantom for existing installs [CRITICAL]
27
+ **Where:** `skills/qualia-{build,plan,verify,design}/SKILL.md`, `agents/{builder,planner,plan-checker,verifier}.md` — all `@~/.claude/rules/grounding.md` references.
28
+ **Reality:** File exists in repo `rules/grounding.md` but **not in installed** `~/.claude/rules/` for users who upgraded from v4.0.x (current local install is missing it). install.js copies it on fresh install only; no self-healing check on version bump.
29
+ **Impact:** Every planner, builder, plan-checker, verifier spawn silently gets no Grounding Protocol and no Severity Rubric. All 1–5 scores by verifier are improvised. Affects every Road phase, every project, silently.
30
+ **Fix:** Add a hook or install.js post-upgrade routine that compares `rules/` contents and copies missing files. Emit a warning if critical files missing.
31
+
32
+ ### 2. Builder agent never reads knowledge base [CRITICAL]
33
+ **Where:** `agents/builder.md` — zero references to `~/.claude/knowledge/`.
34
+ **Impact:** Patterns in `learned-patterns.md`, `supabase-patterns.md`, `common-fixes.md` reach the planner but not the agent that writes code. "Always use `lib/supabase/server.ts` for mutations" never reaches the coder.
35
+ **Fix:** Add a "load relevant patterns" step to builder.md's preamble; pipe through `@~/.claude/knowledge/learned-patterns.md` like grounding.md.
36
+
37
+ ### 3. 11 of 14 knowledge files are invisible to every skill [CRITICAL]
38
+ **Where:** `~/.claude/knowledge/` has `qualia-context.md`, `deployment-map.md`, `supabase-patterns.md`, `voice-agent-patterns.md`, `employees.md`, `claudecode-bible.md`, `optimization-research-2026.md`, `session-digest.md`, `email-signature.html`, etc. — grepping the framework for these filenames returns zero results.
39
+ **Impact:** Every skill hardcodes filenames (`cat ~/.claude/knowledge/common-fixes.md`). New knowledge files are dead weight. `session-digest.md` hasn't updated since Apr 12 — the hook that wrote it no longer exists.
40
+ **Fix:** Replace hardcoded cats with `node ~/.claude/bin/knowledge.js load --type <X>` — a discovery utility that indexes and merges tiers. See Memory Architecture proposal §B below.
41
+
42
+ ### 4. No per-employee memory scoping [CRITICAL]
43
+ **Where:** All memory is in `~/.claude/knowledge/` with no user scope. Hasan, Fawzi, Moayad all share the same global knowledge files.
44
+ **Impact:** Personal preferences mix with universal patterns. `employees.md` is a flat roster, not a scoping mechanism.
45
+ **Fix:** 3-tier memory architecture — see §B.
46
+
47
+ ### 5. `qualia-report` ERP upload: silent injection & auth failures [CRITICAL]
48
+ **Where:** `skills/qualia-report/SKILL.md:84-87, 114-163`.
49
+ - Empty `API_KEY` → sends `Authorization: Bearer ` (blank); 401 handler says "Ask Fawzi" without diagnosing.
50
+ - `SUBMITTED_BY` shell-interpolated into `node -e` string → single quote in git user.name breaks silently.
51
+ - `CLIENT_REPORT_ID` fallback to empty string on state.js failure → commit message loses the ID; ERP payload corrupted.
52
+ **Fix:** Add `[ -z "$API_KEY" ] && fail "No ERP key"` guard. Pipe SUBMITTED_BY via env var, not string interp. Validate report_id non-empty before proceeding.
53
+
54
+ ### 6. Triple overlap: `qualia-optimize` vs `qualia-review` vs `qualia-polish` [HIGH]
55
+ **Where:** All three run the same grep patterns (fonts, max-width, gradients, service_role, empty catches, sequential awaits, `<img>` without next/image). Different output files (OPTIMIZE.md / REVIEW.md / inline fix). Different severity scales. User gets three reports about the same issues.
56
+ **Fix:** Pick one. Recommend: `qualia-review` = detect-only (grep + score), `qualia-polish` = fix-only (applies known transforms), delete `qualia-optimize` or repurpose it as a parallel-agent wrapper around review+polish.
57
+
58
+ ### 7. 4 skills are orphans — never invoked by the Road [HIGH]
59
+ **Where:** `qualia-test`, `qualia-debug`, `qualia-task`, `qualia-quick` are never auto-triggered by any Road step. The Road has no automated test gate between build and verify. If verification fails, the Road goes to `/qualia-plan --gaps`, not `/qualia-debug`.
60
+ **Fix:** Add `/qualia-test` as a hard gate inside `/qualia-build` post-wave. Route verify-fail → `/qualia-debug` → `/qualia-plan --gaps` chain. Kill `qualia-task` (absorbed into `/qualia-quick`).
61
+
62
+ ### 8. Color palette drift across surfaces [HIGH]
63
+ **Where:** `TEAL_DIM` differs `rgb(0,130,135)` (statusline.js:19) vs `rgb(0,140,145)` (qualia-ui.js:31). `DIM` differs `rgb(80,90,100)` vs `rgb(100,110,120)` across 3 files. Brand teal in demo.html `#00ced9` vs help.html `#00ced1`. Display fonts split: Fraunces (demo) vs Outfit (help).
64
+ **Fix:** Create `bin/colors.js` as single source of truth; import in all 5 surfaces. Unify brand token vocabulary between demo.html and help.html.
65
+
66
+ ### 9. Statusline shows installer name, not operator [HIGH]
67
+ **Where:** `bin/statusline.js:292` — reads `.qualia-config.json` which is set at install time.
68
+ **Impact:** On a shared machine, all operators see the installer's name. `Qualia member: Hasan` even when Fawzi is driving.
69
+ **Fix:** Read from a session-scoped identity (env var, git user.name) or add `/qualia-whoami` switch.
70
+
71
+ ### 10. `/qualia-ship` has no state guard and no security depth [HIGH]
72
+ **Where:** `skills/qualia-ship/SKILL.md`.
73
+ - No `state.js check` at start; can invoke from any state.
74
+ - Security check only greps `service_role` — misses hardcoded keys, tracked `.env`, `dangerouslySetInnerHTML`.
75
+ - Post-deploy verification uses literal `{domain}` placeholder — expects LLM to hallucinate URL.
76
+ **Fix:** Add state-preconditions block (must be `polished` or final-phase verified). Inline the full qualia-review CRITICAL checks. Read `deployed_url` from tracking.json like `/qualia-handoff` does.
77
+
78
+ ---
79
+
80
+ ## Dimension Deep-Dives
81
+
82
+ ### A. Silent Fails (selected — 15 most dangerous)
83
+
84
+ | # | File:line | Severity | Silent behavior |
85
+ |---|-----------|----------|-----------------|
86
+ | A1 | `state.js:179,229` | MED | `readTracking/readState` swallow corrupt JSON as "missing" → user told "No .planning/" when file is corrupt |
87
+ | A2 | `state.js:59` | MED | Corrupt journal silently cleared, no log |
88
+ | A3 | `state.js:107` | HIGH | Lock timeout falls through silently — concurrent writes possible |
89
+ | A4 | `pre-push.js:22,47` | HIGH | No lock coordination with state.js; assumes cwd is repo root |
90
+ | A5 | `pre-deploy-gate.js:51` | MED | `package.json` missing → ALL gates silently skipped |
91
+ | A6 | `pre-deploy-gate.js:146` | MED | File read failure → security scan silently skips that file |
92
+ | A7 | `session-start.js:128` | LOW | Top-level catch; crash inside try → no banner, no error, trace records "allow" anyway |
93
+ | A8 | `qualia-report/SKILL.md:84` | CRIT | Empty pipe chain → report_id becomes `""`, commit loses ID |
94
+ | A9 | `qualia-report/SKILL.md:114` | CRIT | Empty API key → blank Bearer token sent to ERP |
95
+ | A10 | `qualia-handoff/SKILL.md:39` | CRIT | Malformed tracking.json → empty URL; verification curl hits blank target |
96
+ | A11 | `qualia-help/SKILL.md:44` | HIGH | Missing template → sed writes empty file → blank help page opens; "ok" message still printed |
97
+ | A12 | `qualia-verify/SKILL.md:65` | HIGH | `FRONTEND=true` never set — frontend detection broken; browser QA never spawns |
98
+ | A13 | `migration-guard.js:96` | MED | DROP TABLE check is file-global, not per-statement — one `IF EXISTS` masks another unguarded DROP |
99
+ | A14 | `state.js:329` | MED | Progress bar: phase 1/5 shows 0%, phase 5/5 shows 80% — never 100% |
100
+ | A15 | `cli.js:827` | HIGH | ERP ping uses `curl` — unavailable on older Windows |
101
+
102
+ ### B. Memory Architecture — 3-Tier Proposal
103
+
104
+ Current state is flat, untiered, and half-invisible. Proposed structure:
105
+
106
+ ```
107
+ Tier 1 — FRAMEWORK (universal, ships with framework)
108
+ ~/.claude/knowledge/framework/
109
+ ├─ stack-patterns.md ← Next.js, Supabase, Vercel
110
+ ├─ voice-patterns.md ← Retell, ElevenLabs, Telnyx
111
+ ├─ security-patterns.md ← RLS, service_role, env
112
+ ├─ common-fixes.md ← cross-project fixes
113
+ └─ KNOWLEDGE-INDEX.md ← auto-generated manifest
114
+
115
+ Tier 2 — EMPLOYEE (per-user, ~/.claude/ scoped by team code)
116
+ ~/.claude/knowledge/employee/
117
+ ├─ preferences.md ← my coding style, timezone, tools
118
+ ├─ my-patterns.md ← things I've learned
119
+ ├─ my-fixes.md ← errors I've solved
120
+ └─ KNOWLEDGE-INDEX.md
121
+
122
+ Tier 3 — PROJECT (per-repo, version-controlled with project)
123
+ .planning/knowledge/
124
+ ├─ client-prefs.md ← this client's brand/stack
125
+ ├─ project-patterns.md ← project-specific gotchas
126
+ ├─ deployment.md ← this project's deploy notes
127
+ └─ KNOWLEDGE-INDEX.md
128
+ ```
129
+
130
+ **Discovery utility:** `bin/knowledge.js` — single CLI for all skills.
131
+ ```
132
+ knowledge.js load --type patterns # merges all 3 tiers, project overrides employee overrides framework
133
+ knowledge.js search "supabase RLS" # searches all tiers, labels source
134
+ knowledge.js index # regenerates INDEX files
135
+ knowledge.js promote --from employee --to framework # for /qualia-learn
136
+ ```
137
+
138
+ **Staleness:** INDEX tracks `last_verified` per file. Entries >30 days get `[STALE]` tag in merge output.
139
+
140
+ **Integration with Claude native memory:** Keep `CLAUDE_CODE_DISABLE_AUTO_MEMORY=0`. Native memory captures conversational context; Qualia knowledge captures structured reusable patterns. Add `/qualia-learn --promote` to migrate native → structured. Statusline shows both counts: `K:14 M:4`.
141
+
142
+ **Migration (one-off script):**
143
+ 1. Move universal bits of `learned-patterns.md` → `framework/stack-patterns.md`
144
+ 2. Move project-specific section → each project's `.planning/knowledge/`
145
+ 3. Move `email-signature.html` out of knowledge/ (not knowledge — asset)
146
+ 4. Delete `session-digest.md` (dead)
147
+ 5. Update 5 reader skills to use `knowledge.js load`
148
+
149
+ ### C. Commands That Don't Fit the Road
150
+
151
+ | Skill | Status | Recommendation |
152
+ |-------|--------|----------------|
153
+ | `qualia-task` | Orphan, overlaps `qualia-quick` | **Delete** — fold into `/qualia-quick --proper` flag |
154
+ | `qualia-quick` | Orphan | Keep, add `--proper` flag for atomic-commit behavior of task |
155
+ | `qualia-test` | Orphan | Integrate into `/qualia-build` post-wave as hard gate |
156
+ | `qualia-debug` | Orphan, only suggested | Auto-invoke after verify-fail before `/qualia-plan --gaps` |
157
+ | `qualia-design` | Duplicates polish ~90% | **Merge into** `/qualia-polish`; delete standalone |
158
+ | `qualia-optimize` | Overlaps review+polish | Repurpose as parallel-agent wrapper; specialists run review+polish in fan-out |
159
+ | `qualia-review` | Orphan, no state guard | Integrate as mandatory step between build and verify (code review before goal check) |
160
+ | `qualia-map` | Optional brownfield | Keep, but add auto-trigger on `/qualia-new` if `package.json` exists |
161
+ | `qualia-research` | Optional | Keep as phase-depth option |
162
+ | `qualia-discuss` | Optional | Keep |
163
+ | `qualia-idk` | Alternate router | Keep — genuine diagnostic, not alias (fix help.html:410 description) |
164
+ | `qualia-learn` | Meta | Keep, extend with tier promotion (see §B) |
165
+ | `qualia-help` | Reference | Keep |
166
+ | `qualia-skill-new` | Authoring | Keep |
167
+
168
+ ### D. Missing from the Road (gaps)
169
+
170
+ 1. **Automated test gate** — no skill runs tests after build, before verify. Add to `/qualia-build`.
171
+ 2. **Inter-phase code review** — goal-backward verify doesn't check code quality. Add `/qualia-review` between build and verify.
172
+ 3. **Adversarial/red-team build loop** — per harness-engineering research (Anthropic GAN pattern, Archon adversarial-dev). Generator ↔ evaluator with pre-negotiated sprint contract. Add `/qualia-adversarial` or fold into `/qualia-build --adversarial`.
173
+ 4. **Continuous reviewer agents** — no background persona reviewers (security, reliability, frontend-architect) on each commit. Stripe Minion pattern; Lopopolo "every push" pattern.
174
+ 5. **Component freshness** — no skill pulls latest shadcn/Radix versions or checks for staleness. Proposal below.
175
+
176
+ ### E. Component Fetching (user's explicit ask)
177
+
178
+ No current mechanism. Framework has no `shadcn`, `radix`, or `component` references.
179
+
180
+ **Proposal: `/qualia-components` skill**
181
+ ```
182
+ /qualia-components add button card dialog # pulls latest shadcn via `npx shadcn@latest add`
183
+ /qualia-components update # bumps versions; writes .planning/component-changelog.md
184
+ /qualia-components check # reports N components > X days stale
185
+ ```
186
+ - Integrates with existing `vercel-plugin:shadcn` skill for shadcn ops
187
+ - Uses WebFetch from researcher agent to pull latest docs (`https://ui.shadcn.com/docs/components/{name}`)
188
+ - Writes a diff summary so planner knows what changed
189
+ - Add a mandatory check inside `/qualia-verify`: flag components > 1 major version behind
190
+
191
+ **Do not auto-update during builds.** Component breaks should be explicit, versioned, verified.
192
+
193
+ ### F. Design Polish Roadmap (5 phases)
194
+
195
+ **Phase 1 — Color unification (1 day)**
196
+ Create `bin/colors.js`; delete inline palettes in statusline, qualia-ui, session-start, install, cli. Fix TEAL_DIM, DIM, brand teal mismatches. DIM must pass WCAG AA (4.5:1) on dark terminals — currently fails at ~3.2:1.
197
+
198
+ **Phase 2 — Statusline ergonomics (1 day)**
199
+ Terminal-width guard on Line 1 (progressive truncation: identity → memory → worktree → agent). Add milestone indicator `M2 P3/5 T2/4`. Add last-error from `tracking.json.last_error`. Format duration as `Xh Ym` over 60min. Add burn rate `$/min`.
200
+
201
+ **Phase 3 — HTML modernization (1 day)**
202
+ help.html: replace `@import` with `<link rel="preconnect"><link rel="stylesheet">`. Unify design tokens with demo.html. Adopt `@container` queries for card grids. Add `color-scheme: dark`. Fix **demo.html:506 `</span>` → `</div>`** (broken DOM). Fix help.html:410 `/qualia-idk` description (currently says "Alias for /qualia" — wrong).
203
+
204
+ **Phase 4 — Skill description cleanup (half day)**
205
+ Trim 10 skills over 30-word descriptions. Add trigger phrases to 4 that have none (report, ship, verify, build). Cap at 20 words + triggers for autocomplete readability.
206
+
207
+ **Phase 5 — Component fetching (1-2 days)** — see §E.
208
+
209
+ ### G. Team Ergonomics — What's Missing
210
+
211
+ For a 5-person team operating from a shared fleet:
212
+ - **Per-operator identity** on statusline (not installer)
213
+ - **Active agent name/count** — which subagent is running right now
214
+ - **Milestone progress** alongside phase progress (`M2/4 P3/5`)
215
+ - **Shared-state indicator** — is someone else editing this project?
216
+ - **Last verification score** from tracking.json
217
+ - **Blocker summary** beyond the pill (currently only shows count, no first-line)
218
+
219
+ ---
220
+
221
+ ## Action Roadmap (priority-ordered)
222
+
223
+ ### v4.1.1 Hotfix (ship this week)
224
+ 1. Auto-copy missing rules on version bump (fixes grounding.md phantom)
225
+ 2. Guard empty API_KEY in qualia-report
226
+ 3. Fix broken `</span>` in demo.html:506
227
+ 4. Fix help.html:410 `/qualia-idk` description
228
+ 5. Add state guard to `/qualia-ship`
229
+
230
+ ### v4.2.0 — Structural (2 weeks)
231
+ 1. 3-tier memory architecture + `bin/knowledge.js`
232
+ 2. Merge `qualia-design` into `qualia-polish`, delete orphan `qualia-task`
233
+ 3. Unified `bin/colors.js`
234
+ 4. Builder agent loads knowledge
235
+ 5. Integrate `/qualia-test` as build gate, `/qualia-debug` on verify-fail
236
+ 6. cwd → git root resolution for state.js, pre-push, pre-compact (fixes 3 MED findings at once)
237
+
238
+ ### v4.3.0 — Harness patterns (4 weeks)
239
+ 1. Adversarial build mode (generator↔evaluator GAN-style)
240
+ 2. Continuous reviewer agents (security/reliability/frontend personas on every push)
241
+ 3. Source-code structural tests (file<350 lines, schema dedup, no-unknown)
242
+ 4. `/qualia-components` skill
243
+
244
+ ### v4.4.0 — Team-scale (ongoing)
245
+ 1. Per-operator identity on statusline
246
+ 2. Shared-state coordination lock
247
+ 3. Token burn rate + milestone progress indicators
248
+ 4. Knowledge staleness tracking + auto-prune
249
+
250
+ ---
251
+
252
+ ## Verdict
253
+
254
+ **Strengths** — The Road skeleton is sound. Context isolation, goal-backward verification, story-file plans, wave-based parallelization, and the state machine are all genuinely good harness patterns (confirmed by comparison to Anthropic/OpenAI/Archon research).
255
+
256
+ **Structural debt** — Three clusters compound risk:
257
+ 1. **Invisible dependencies** — grounding.md, 11 knowledge files, builder↔knowledge disconnect. The framework believes things are loaded that aren't.
258
+ 2. **Silent failures** — 15 high-impact silent-fail paths where users get "ok" messages while the underlying operation produced garbage.
259
+ 3. **Orphan skills** — 4 commands never touch the Road; 3 triple-overlap. Mental overhead for the team is real.
260
+
261
+ **Shipping bar** — This review would FAIL `/qualia-ship` on a client project. The framework needs v4.1.1 hotfix before onboarding the next employee and v4.2.0 before the next major client delivery.
262
+
263
+ Raw audit transcripts (142 findings in full) are available via the 4 background agent outputs if deeper context is needed on any specific finding.
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ // ~/.claude/hooks/git-guardrails.js — block destructive git commands.
3
+ //
4
+ // PreToolUse hook on `Bash`. Reads the proposed command from the Claude Code
5
+ // hook payload (stdin JSON: tool_input.command) and exits 2 to BLOCK, 0 to
6
+ // allow. Cross-platform (Windows/macOS/Linux), zero shell dependencies.
7
+ //
8
+ // Patterns blocked unconditionally:
9
+ // • git push --force / -f to main or master (use --force-with-lease instead)
10
+ // • git push --force / -f from current branch when current branch is main
11
+ // • git reset --hard <anything> when on main/master (would discard uncommitted)
12
+ // • git clean -fd / -fdx (no recovery — irreversible)
13
+ // • git branch -D main / master (destroys the branch)
14
+ // • rm -rf .git (nukes repo history)
15
+ //
16
+ // Escape hatch:
17
+ // QUALIA_ALLOW_DESTRUCTIVE=1 in env → all checks pass through (use sparingly,
18
+ // document the reason in your commit message).
19
+ //
20
+ // This hook complements branch-guard.js, which blocks NORMAL pushes by EMPLOYEE
21
+ // to main. git-guardrails.js applies to EVERYONE (OWNER too) for irreversible
22
+ // operations — guardrails, not permissions.
23
+
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+ const os = require("os");
27
+ const { spawnSync } = require("child_process");
28
+
29
+ const _traceStart = Date.now();
30
+
31
+ function _trace(result, extra) {
32
+ try {
33
+ const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
34
+ if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
35
+ const entry = {
36
+ hook: "git-guardrails",
37
+ result,
38
+ timestamp: new Date().toISOString(),
39
+ duration_ms: Date.now() - _traceStart,
40
+ ...extra,
41
+ };
42
+ const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
43
+ fs.appendFileSync(file, JSON.stringify(entry) + "\n");
44
+ } catch {}
45
+ }
46
+
47
+ function block(reason, suggestion) {
48
+ const lines = [
49
+ `BLOCKED: ${reason}`,
50
+ suggestion ? `Suggestion: ${suggestion}` : null,
51
+ `Override (use sparingly): QUALIA_ALLOW_DESTRUCTIVE=1`,
52
+ ].filter(Boolean);
53
+ for (const l of lines) {
54
+ console.error(l);
55
+ console.log(l);
56
+ }
57
+ _trace("block", { reason });
58
+ process.exit(2);
59
+ }
60
+
61
+ // Honor escape hatch first — keeps tests and emergency overrides cheap.
62
+ if (process.env.QUALIA_ALLOW_DESTRUCTIVE === "1") {
63
+ _trace("allow", { reason: "QUALIA_ALLOW_DESTRUCTIVE=1" });
64
+ process.exit(0);
65
+ }
66
+
67
+ // Read hook payload from stdin (Claude Code passes JSON; if no stdin, allow).
68
+ let payload = "";
69
+ try {
70
+ if (!process.stdin.isTTY) payload = fs.readFileSync(0, "utf8");
71
+ } catch {}
72
+
73
+ let command = "";
74
+ try {
75
+ if (payload) command = (JSON.parse(payload).tool_input || {}).command || "";
76
+ } catch {}
77
+
78
+ if (!command) {
79
+ _trace("allow", { reason: "no-command" });
80
+ process.exit(0);
81
+ }
82
+
83
+ // Normalize for matching: collapse whitespace, lowercase the flags.
84
+ // We deliberately match on substrings — chained commands (`x && y`) and
85
+ // quoted arguments still get scanned for the dangerous primitive.
86
+ const cmd = command.replace(/\s+/g, " ").trim();
87
+
88
+ // Helper: detect current git branch (silent on failure).
89
+ function currentBranch() {
90
+ try {
91
+ const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
92
+ encoding: "utf8",
93
+ timeout: 2000,
94
+ shell: process.platform === "win32",
95
+ });
96
+ return (r.stdout || "").trim();
97
+ } catch {
98
+ return "";
99
+ }
100
+ }
101
+
102
+ // ── Rule 1: nuking .git ───────────────────────────────────
103
+ if (/\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)\b[^|;&]*\.git(\b|$|\/)/.test(cmd)) {
104
+ block(
105
+ "rm -rf .git would destroy all repository history",
106
+ "If you really need to reinitialize, do it deliberately — back up first",
107
+ );
108
+ }
109
+
110
+ // ── Rule 2: git clean -fd / -fdx ──────────────────────────
111
+ // `git clean -fd` deletes untracked files and directories with no recovery.
112
+ // We block this because the recovery cost is high and the agent rarely needs
113
+ // it — `git stash` or moving the file is almost always the right answer.
114
+ if (/\bgit\s+clean\s+(-[a-zA-Z]*f[a-zA-Z]*d|-[a-zA-Z]*d[a-zA-Z]*f)/.test(cmd)) {
115
+ block(
116
+ "git clean -fd permanently deletes untracked files",
117
+ "Use `git stash --include-untracked` if you need a clean tree but might want the files later",
118
+ );
119
+ }
120
+
121
+ // ── Rule 3: git branch -D main / master ───────────────────
122
+ if (/\bgit\s+branch\s+-D\s+(main|master)\b/.test(cmd)) {
123
+ block(
124
+ "deleting main/master branch is destructive",
125
+ "If this is intentional, run it manually outside Claude with QUALIA_ALLOW_DESTRUCTIVE=1",
126
+ );
127
+ }
128
+
129
+ // ── Rule 4: git push --force to main / master ─────────────
130
+ // Match `git push ... --force ... main` or `git push ... -f ... main` (and
131
+ // master). Excludes --force-with-lease which is the safe variant.
132
+ const isPush = /\bgit\s+push\b/.test(cmd);
133
+ const hasForce =
134
+ /\s(--force|-f)(\s|$)/.test(" " + cmd + " ") &&
135
+ !/--force-with-lease/.test(cmd);
136
+
137
+ if (isPush && hasForce) {
138
+ // Targets main/master explicitly: `git push origin main`, `git push --force origin main`
139
+ if (/\b(main|master)\b/.test(cmd)) {
140
+ block(
141
+ "force-pushing to main/master rewrites shared history",
142
+ "Use `git push --force-with-lease origin <branch>` on a feature branch instead",
143
+ );
144
+ }
145
+ // Or pushing the current branch when on main
146
+ const branch = currentBranch();
147
+ if (branch === "main" || branch === "master") {
148
+ block(
149
+ `force-pushing while on ${branch} (rewrites shared history)`,
150
+ "Switch to a feature branch first: `git checkout -b feat/<name>`",
151
+ );
152
+ }
153
+ }
154
+
155
+ // ── Rule 5: git reset --hard while on main / master ───────
156
+ if (/\bgit\s+reset\s+--hard\b/.test(cmd)) {
157
+ const branch = currentBranch();
158
+ if (branch === "main" || branch === "master") {
159
+ block(
160
+ `git reset --hard while on ${branch} discards uncommitted work and rewrites local history`,
161
+ "Switch to a feature branch first, or use `git stash` to preserve work",
162
+ );
163
+ }
164
+ }
165
+
166
+ _trace("allow", {});
167
+ process.exit(0);
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ // ~/.claude/hooks/stop-session-log.js — append a one-line session checkpoint
3
+ // to ~/.claude/knowledge/daily-log/{YYYY-MM-DD}.md.
4
+ //
5
+ // Stop hook (fires when a Claude Code turn ends). This is the seed of the
6
+ // memory layer (v4.2.0): every turn that produced something noteworthy gets a
7
+ // line in today's daily log. Future versions will run an LLM "flush" job over
8
+ // the daily log to promote durable knowledge into the wiki tier.
9
+ //
10
+ // Design constraints:
11
+ // • NEVER block — exit 0 always, even on internal failure.
12
+ // • Fast — under 100ms, no network, no LLM call. The log is mechanical.
13
+ // • Cheap to skip — if there's been no git activity since the last write,
14
+ // and we wrote a line within the last 5 minutes, we skip. Daily log
15
+ // stays scannable instead of becoming a turn-by-turn replay.
16
+ // • No PII / secrets — only project name, branch, phase, task counts,
17
+ // commit count. Never the contents of files or env vars.
18
+ //
19
+ // Format: append to today's file under a single H2 header per project:
20
+ //
21
+ // ## qualia-framework
22
+ // - 14:32 · branch=feat/x · phase=2/4 · tasks=3/5 · commits=2 · touched=src/foo.ts,src/bar.ts
23
+ // - 16:08 · branch=feat/x · phase=2/4 · tasks=4/5 · commits=4
24
+ //
25
+ // Cross-platform (Windows/macOS/Linux). Zero shell dependencies.
26
+
27
+ const fs = require("fs");
28
+ const path = require("path");
29
+ const os = require("os");
30
+ const { spawnSync } = require("child_process");
31
+
32
+ const _traceStart = Date.now();
33
+
34
+ const KNOWLEDGE_DIR = path.join(os.homedir(), ".claude", "knowledge");
35
+ const DAILY_DIR = path.join(KNOWLEDGE_DIR, "daily-log");
36
+ const LAST_WRITE_FILE = path.join(os.homedir(), ".claude", ".qualia-last-session-log");
37
+ const MIN_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
38
+
39
+ function _trace(result, extra) {
40
+ try {
41
+ const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
42
+ if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
43
+ fs.appendFileSync(
44
+ path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`),
45
+ JSON.stringify({
46
+ hook: "stop-session-log",
47
+ result,
48
+ timestamp: new Date().toISOString(),
49
+ duration_ms: Date.now() - _traceStart,
50
+ ...extra,
51
+ }) + "\n",
52
+ );
53
+ } catch {}
54
+ }
55
+
56
+ function git(args, opts = {}) {
57
+ try {
58
+ const r = spawnSync("git", args, {
59
+ encoding: "utf8",
60
+ timeout: 2000,
61
+ shell: process.platform === "win32",
62
+ ...opts,
63
+ });
64
+ if (r.status !== 0) return "";
65
+ return (r.stdout || "").trim();
66
+ } catch {
67
+ return "";
68
+ }
69
+ }
70
+
71
+ function readJson(p) {
72
+ try {
73
+ return JSON.parse(fs.readFileSync(p, "utf8"));
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ try {
80
+ // ── Skip if too soon since last write ────────────────────
81
+ const now = Date.now();
82
+ let lastWrite = 0;
83
+ try {
84
+ if (fs.existsSync(LAST_WRITE_FILE)) {
85
+ lastWrite = parseInt(fs.readFileSync(LAST_WRITE_FILE, "utf8").trim(), 10) || 0;
86
+ }
87
+ } catch {}
88
+ if (now - lastWrite < MIN_INTERVAL_MS) {
89
+ _trace("skip", { reason: "interval", since_last_ms: now - lastWrite });
90
+ process.exit(0);
91
+ }
92
+
93
+ // ── Resolve project context ──────────────────────────────
94
+ // Operate from the cwd; if not a git repo, fall back to basename.
95
+ const cwd = process.cwd();
96
+ const repoRoot = git(["rev-parse", "--show-toplevel"], { cwd }) || cwd;
97
+ const project = path.basename(repoRoot);
98
+ const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoRoot }) || "";
99
+
100
+ // Phase + task info from .planning/tracking.json (Qualia projects only).
101
+ let phase = "";
102
+ let tasks = "";
103
+ const tracking = readJson(path.join(repoRoot, ".planning", "tracking.json"));
104
+ if (tracking) {
105
+ const p = tracking.phase || 0;
106
+ const pt = tracking.phase_total || 0;
107
+ if (pt > 0) phase = `${p}/${pt}`;
108
+ const td = tracking.tasks_done || 0;
109
+ const tt = tracking.tasks_total || 0;
110
+ if (tt > 0) tasks = `${td}/${tt}`;
111
+ }
112
+
113
+ // Commits in this session window — heuristic: commits in last 8 hours by us.
114
+ // (Stop fires per turn, so a tighter window misses session boundaries.)
115
+ const commitCount = (() => {
116
+ const out = git(["log", "--since=8.hours", "--pretty=oneline"], { cwd: repoRoot });
117
+ if (!out) return 0;
118
+ return out.split("\n").filter(Boolean).length;
119
+ })();
120
+
121
+ // Top 3 touched files (uncommitted) — useful for resuming next session.
122
+ const touched = (() => {
123
+ const out = git(["diff", "--name-only", "HEAD"], { cwd: repoRoot });
124
+ if (!out) return [];
125
+ return out.split("\n").filter(Boolean).slice(0, 3);
126
+ })();
127
+
128
+ // Skip if literally nothing happened — no commits, no diff, no phase progress.
129
+ if (commitCount === 0 && touched.length === 0 && !phase) {
130
+ _trace("skip", { reason: "no-activity" });
131
+ process.exit(0);
132
+ }
133
+
134
+ // ── Compose the line ─────────────────────────────────────
135
+ const time = new Date().toTimeString().slice(0, 5); // HH:MM, local time
136
+ const parts = [`${time}`];
137
+ if (branch) parts.push(`branch=${branch}`);
138
+ if (phase) parts.push(`phase=${phase}`);
139
+ if (tasks) parts.push(`tasks=${tasks}`);
140
+ if (commitCount > 0) parts.push(`commits=${commitCount}`);
141
+ if (touched.length > 0) parts.push(`touched=${touched.join(",")}`);
142
+ const line = `- ${parts.join(" · ")}`;
143
+
144
+ // ── Append to today's file ───────────────────────────────
145
+ if (!fs.existsSync(DAILY_DIR)) fs.mkdirSync(DAILY_DIR, { recursive: true });
146
+ const today = new Date().toISOString().split("T")[0];
147
+ const file = path.join(DAILY_DIR, `${today}.md`);
148
+
149
+ let existing = "";
150
+ try {
151
+ if (fs.existsSync(file)) existing = fs.readFileSync(file, "utf8");
152
+ } catch {}
153
+
154
+ const projectHeader = `## ${project}`;
155
+ if (!existing) {
156
+ const header = `# Daily log — ${today}\n\n_Auto-generated by Qualia Framework. Each entry is one Stop-hook checkpoint per project per session._\n\n${projectHeader}\n${line}\n`;
157
+ fs.writeFileSync(file, header);
158
+ } else if (!existing.includes(projectHeader)) {
159
+ fs.appendFileSync(file, `\n${projectHeader}\n${line}\n`);
160
+ } else {
161
+ // Append the line under the existing project header. Insert after the last
162
+ // line that belongs to this project's section (next ## or EOF).
163
+ const headerIdx = existing.indexOf(projectHeader);
164
+ const afterHeader = headerIdx + projectHeader.length;
165
+ const nextHeaderIdx = existing.indexOf("\n## ", afterHeader);
166
+ const insertAt = nextHeaderIdx === -1 ? existing.length : nextHeaderIdx;
167
+ // Trim trailing newlines in the section so our append lands on a clean line.
168
+ const before = existing.slice(0, insertAt).replace(/\n+$/, "");
169
+ const after = existing.slice(insertAt);
170
+ fs.writeFileSync(file, `${before}\n${line}\n${after}`);
171
+ }
172
+
173
+ fs.writeFileSync(LAST_WRITE_FILE, String(now));
174
+ _trace("logged", { project, branch, phase, tasks, commits: commitCount });
175
+ process.exit(0);
176
+ } catch (err) {
177
+ _trace("error", { error: err && err.message ? err.message : String(err) });
178
+ // Stop hooks must never block the user — exit 0 even on internal error.
179
+ process.exit(0);
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "4.1.1",
3
+ "version": "4.3.0",
4
4
  "description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -40,7 +40,7 @@ node ~/.claude/bin/qualia-ui.js banner debug
40
40
  ### 2. Check Known Fixes First (cheap)
41
41
 
42
42
  ```bash
43
- cat ~/.claude/knowledge/common-fixes.md 2>/dev/null | grep -i "{symptom_keywords}"
43
+ node ~/.claude/bin/knowledge.js search "{symptom_keywords}"
44
44
  ```
45
45
 
46
46
  If a known fix matches, apply it and jump to step 5 (verify). Known fixes are pre-verified patterns — no need to re-investigate.
@@ -59,6 +59,21 @@ Apply fixes to every HIGH and CRITICAL item. MEDIUM items fixed if cheap (same f
59
59
 
60
60
  Split target files into batches of 5. Spawn one Agent per batch IN THE SAME RESPONSE TURN (parallel execution). Each agent receives DESIGN.md inlined + its 5 files + the Design Quality Rubric from `rules/grounding.md`. Agents return their batch's critique table + the actual edits applied. The skill orchestrator fans in the results and runs the final verification (step 5).
61
61
 
62
+ **Forked subagents (v4.2.0+):** if the current conversation already contains
63
+ design taste discussion (font choices, palette discussion, motion preferences,
64
+ or any color/typography critique threaded across multiple turns) AND
65
+ `CLAUDE_AGENT_FORK_ENABLED=1` is set in `~/.claude/settings.json` (the v4.2.0
66
+ default), prefer **forked subagents** over blank-context fan-out. Forks
67
+ inherit the entire conversation history + share the prompt cache, so the
68
+ batch agents see the 50k tokens of accumulated taste instead of a 2k-token
69
+ compression. Anthropic shipped this in 2026-04 specifically to solve the
70
+ "design subagent loses nuance" failure mode (NotebookLM 2026-04-25 source).
71
+ Tell Claude explicitly: "spawn forked subagents to handle these batches in
72
+ parallel." For variation-generation work (3 alternative homepage designs)
73
+ forks are almost always the right call. For mechanical anti-pattern fixes
74
+ (rip out `outline:none`, swap font tokens) blank context is fine — no
75
+ nuance to inherit. When in doubt, fork — the cost is the same prompt cache.
76
+
62
77
  ```
63
78
  Agent(prompt="
64
79
  Read your role: builder for design transformation.