qualia-framework 4.1.1 → 4.4.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.
Files changed (43) hide show
  1. package/README.md +15 -11
  2. package/agents/builder.md +28 -0
  3. package/agents/research-synthesizer.md +7 -0
  4. package/bin/agent-runs.js +233 -0
  5. package/bin/cli.js +355 -16
  6. package/bin/install.js +87 -6
  7. package/bin/knowledge-flush.js +164 -0
  8. package/bin/knowledge.js +317 -0
  9. package/bin/plan-contract.js +220 -0
  10. package/bin/state.js +15 -9
  11. package/docs/agent-runs.md +273 -0
  12. package/docs/journey-demo.html +1008 -0
  13. package/docs/plan-contract.md +321 -0
  14. package/docs/reviews/v4.1.0-audit.html +1488 -0
  15. package/docs/reviews/v4.1.0-audit.md +263 -0
  16. package/hooks/auto-update.js +3 -7
  17. package/hooks/git-guardrails.js +167 -0
  18. package/hooks/pre-compact.js +22 -11
  19. package/hooks/pre-deploy-gate.js +16 -2
  20. package/hooks/pre-push.js +22 -2
  21. package/hooks/stop-session-log.js +180 -0
  22. package/package.json +8 -2
  23. package/skills/qualia-build/SKILL.md +5 -5
  24. package/skills/qualia-debug/SKILL.md +1 -1
  25. package/skills/qualia-design/SKILL.md +15 -0
  26. package/skills/qualia-flush/SKILL.md +200 -0
  27. package/skills/qualia-learn/SKILL.md +47 -37
  28. package/skills/qualia-new/SKILL.md +1 -1
  29. package/skills/qualia-plan/SKILL.md +3 -2
  30. package/skills/qualia-postmortem/SKILL.md +238 -0
  31. package/skills/qualia-quick/SKILL.md +1 -1
  32. package/skills/qualia-report/SKILL.md +1 -1
  33. package/skills/qualia-review/SKILL.md +3 -2
  34. package/skills/qualia-ship/SKILL.md +12 -10
  35. package/skills/qualia-verify/SKILL.md +60 -0
  36. package/templates/help.html +13 -7
  37. package/templates/knowledge/agents.md +71 -0
  38. package/templates/knowledge/index.md +47 -0
  39. package/tests/bin.test.sh +322 -12
  40. package/tests/hooks.test.sh +131 -20
  41. package/tests/lib.test.sh +217 -0
  42. package/tests/runner.js +103 -77
  43. package/tests/state.test.sh +4 -3
@@ -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.total_phases || 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.4.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"
@@ -23,7 +23,13 @@
23
23
  },
24
24
  "homepage": "https://github.com/Qualiasolutions/qualia-framework#readme",
25
25
  "scripts": {
26
- "test": "node --test tests/runner.js"
26
+ "test": "npm run test:shell",
27
+ "test:state": "bash tests/state.test.sh",
28
+ "test:hooks": "bash tests/hooks.test.sh",
29
+ "test:bin": "bash tests/bin.test.sh",
30
+ "test:lib": "bash tests/lib.test.sh",
31
+ "test:statusline": "bash tests/statusline.test.sh",
32
+ "test:shell": "bash tests/statusline.test.sh && bash tests/state.test.sh && bash tests/hooks.test.sh && bash tests/bin.test.sh && bash tests/lib.test.sh"
27
33
  },
28
34
  "files": [
29
35
  "bin/",
@@ -32,9 +32,9 @@ cat .planning/phase-{N}-plan.md
32
32
 
33
33
  Parse: tasks, waves, file references.
34
34
 
35
- ### 1b. Create Recovery Point
35
+ ### 1b. Create Recovery Reference
36
36
 
37
- Before executing any tasks, tag current HEAD for rollback:
37
+ Before executing any tasks, record current HEAD for diagnosis. This is a reference, not an automatic rollback instruction.
38
38
 
39
39
  ```bash
40
40
  git tag -f "pre-build-phase-{N}" HEAD 2>/dev/null
@@ -44,10 +44,10 @@ git tag -f "pre-build-phase-{N}" HEAD 2>/dev/null
44
44
  node ~/.claude/bin/qualia-ui.js info "Recovery point: pre-build-phase-{N}"
45
45
  ```
46
46
 
47
- If a wave fails and the user needs to roll back:
47
+ If a wave fails, stop and inspect `git status` plus the failed task output. Do not run destructive rollback commands automatically. Preserve user work, then either fix forward or ask before reverting specific files.
48
48
  ```bash
49
- git reset --hard pre-build-phase-{N}
50
- node ~/.claude/bin/state.js transition --to planned --force
49
+ git status --short
50
+ git diff --stat
51
51
  ```
52
52
 
53
53
  ### 2. Execute Waves
@@ -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.
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: qualia-flush
3
+ description: "Promote daily-log raw entries to the curated knowledge tier — Karpathy-style raw→wiki flush. Reads ~/.claude/knowledge/daily-log/*.md, identifies recurring patterns and decisions, writes them to ~/.claude/knowledge/concepts/{topic}.md, updates index.md. Trigger on 'flush memory', 'promote learnings', 'consolidate logs', 'qualia-flush', 'process daily logs', or run weekly."
4
+ allowed-tools:
5
+ - Bash
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Grep
10
+ - Glob
11
+ ---
12
+
13
+ # /qualia-flush — Promote Raw Daily Logs to Curated Concepts
14
+
15
+ Closes the **raw → wiki** loop in the Qualia memory layer. The Stop hook
16
+ (`hooks/stop-session-log.js`, shipped in v4.2.0 foundation) appends
17
+ mechanical session checkpoints to `~/.claude/knowledge/daily-log/{date}.md`.
18
+ Those entries accumulate but stay raw — they describe what happened, not
19
+ what to do about it. This skill reads the recent daily-log entries with an
20
+ LLM (you) and writes durable concepts that the builder, planner, and
21
+ debug skills will surface later via `node ~/.claude/bin/knowledge.js`.
22
+
23
+ Inspired by Karpathy's LLM knowledge bases and Cole Medin's self-evolving
24
+ Claude memory pattern (NotebookLM, 2026-04-25). Both run a daily/weekly
25
+ flush that promotes raw observations into structured wiki articles. We do
26
+ the same — manually-triggered, internal-data only, no vector DB.
27
+
28
+ ## When to run
29
+
30
+ - **Manually:** `/qualia-flush` whenever the daily-log feels rich. Once a week
31
+ is the recommended cadence. More than once a day is wasteful — the
32
+ signal-to-noise ratio is too low at single-day windows.
33
+ - **CLI runner:** `qualia-framework flush` wraps the cron-friendly
34
+ `bin/knowledge-flush.js` non-interactive runner.
35
+
36
+ ## Inputs
37
+
38
+ - `--days N` (optional, default 14) — how many days of daily-log to consider
39
+ - `--project NAME` (optional) — only flush entries for one project
40
+ - `--dry-run` (optional) — print the proposed writes, don't touch disk
41
+
42
+ If the user invokes the skill bare (no args), default to `--days 14` and
43
+ all projects. Show a one-line preview before writing anything destructive.
44
+
45
+ ## Process
46
+
47
+ ### 1. Banner + check the floor
48
+
49
+ ```bash
50
+ node ~/.claude/bin/qualia-ui.js banner flush 2>/dev/null || true
51
+
52
+ # Resolve the knowledge dir. Fail loud if it doesn't exist — flush is
53
+ # meaningless without a daily-log to read.
54
+ KNOWLEDGE_DIR="$HOME/.claude/knowledge"
55
+ DAILY_DIR="$KNOWLEDGE_DIR/daily-log"
56
+ if [ ! -d "$DAILY_DIR" ]; then
57
+ echo "QUALIA: No daily-log at $DAILY_DIR — Stop hook hasn't run yet, or knowledge layer wasn't initialized."
58
+ echo "Run: npx qualia-framework@latest install"
59
+ exit 1
60
+ fi
61
+
62
+ # Default 14-day window. Date math is cross-platform-safe with Node.
63
+ WINDOW_DAYS="${WINDOW_DAYS:-14}"
64
+ node -e "
65
+ const d = new Date();
66
+ d.setDate(d.getDate() - $WINDOW_DAYS);
67
+ console.log(d.toISOString().split('T')[0]);
68
+ " > /tmp/qualia-flush-cutoff
69
+ CUTOFF=$(cat /tmp/qualia-flush-cutoff)
70
+ ```
71
+
72
+ ### 2. Collect the daily-log entries in window
73
+
74
+ ```bash
75
+ # Iterate every file in daily-log/ whose name (YYYY-MM-DD.md) is >= CUTOFF.
76
+ # Concatenate them into one stream so the LLM (you) can scan as one corpus.
77
+ ls "$DAILY_DIR"/*.md 2>/dev/null | while read -r f; do
78
+ base=$(basename "$f" .md)
79
+ if [ "$base" \> "$CUTOFF" ] || [ "$base" = "$CUTOFF" ]; then
80
+ echo "=== $base ==="
81
+ cat "$f"
82
+ echo ""
83
+ fi
84
+ done
85
+ ```
86
+
87
+ You now have the raw stream. Read it.
88
+
89
+ ### 3. Identify what's worth promoting
90
+
91
+ Read every entry. Group by project. Look for these signals — these are
92
+ the things that promote into the wiki:
93
+
94
+ | Signal in raw entry | What to extract | Goes to |
95
+ |---|---|---|
96
+ | Same fix appears in 2+ sessions | A common fix recipe | `common-fixes.md` (via `knowledge.js append --type fix`) |
97
+ | A pattern shows up in 3+ projects | A reusable pattern | `learned-patterns.md` (via `knowledge.js append --type pattern`) |
98
+ | A client-name or project preference recurs | A client preference | `client-prefs.md` (via `knowledge.js append --type client`) |
99
+ | A new technology/library used successfully | A stack note | `concepts/{tech}.md` (new file, Write directly) |
100
+ | A recurring failure mode (verify-fail, regression) | A pitfall | `learned-patterns.md` framed as "anti-pattern: …" |
101
+
102
+ Things to **NOT** promote:
103
+ - Single-occurrence quirks (they're noise until they recur).
104
+ - Bare commit/branch info — that's already in git, no value duplicating.
105
+ - Anything containing secrets, tokens, customer PII. The knowledge layer
106
+ is plain markdown, never put secrets here.
107
+ - Entries from `--dry-run` runs of other skills (they'll show as activity
108
+ but didn't actually do anything).
109
+
110
+ ### 4. Write the promotions
111
+
112
+ For each thing worth promoting, use the loader's `append`:
113
+
114
+ ```bash
115
+ node ~/.claude/bin/knowledge.js append \
116
+ --type {pattern|fix|client} \
117
+ --title "{Concise title — what's the recurring thing?}" \
118
+ --body "{The promoted lesson. Be specific. Include the project name(s) and dates where this pattern was observed so future you can verify.}" \
119
+ --project "{specific project, or 'general' if cross-project}" \
120
+ --context "Promoted by /qualia-flush from daily-log entries on {dates}"
121
+ ```
122
+
123
+ For a brand-new topic that doesn't fit pattern/fix/client (e.g. a Stripe
124
+ integration approach worth its own file), Write to
125
+ `~/.claude/knowledge/concepts/{topic}.md`. Then **update `index.md`** so the
126
+ new file is reachable — list it under "What's where" with one line:
127
+
128
+ ```bash
129
+ # After writing concepts/stripe-checkout.md:
130
+ node ~/.claude/bin/knowledge.js path stripe-checkout
131
+ # Returned path = ~/.claude/knowledge/stripe-checkout.md (NOTE: top-level, not concepts/)
132
+ ```
133
+
134
+ > **Subdirectory caveat:** the v4.2.0 loader resolves bare filenames at
135
+ > `~/.claude/knowledge/{name}.md`. Subdirectory `concepts/` files are not
136
+ > reachable via `knowledge.js load <name>` yet (deferred to v4.3.0). For
137
+ > now, write durable concept files at the top level — flat structure is
138
+ > fine, the index keeps it organized.
139
+
140
+ ### 5. Mark the window as flushed
141
+
142
+ Write a stamp file so subsequent flushes can default to "since the last
143
+ flush" instead of "last 14 days":
144
+
145
+ ```bash
146
+ date -u +%Y-%m-%dT%H:%M:%SZ > "$HOME/.claude/.qualia-last-flush"
147
+ ```
148
+
149
+ ### 6. Summarize
150
+
151
+ Print to the user, in plain language:
152
+
153
+ - N daily-log files scanned (date range)
154
+ - M promotions written (with file:title for each)
155
+ - K things considered but not promoted (single-occurrence — wait for them to recur)
156
+
157
+ Format:
158
+
159
+ ```
160
+ ⬢ Flushed daily-log {start} → {end} ({N} files, {total entries} entries)
161
+ Promoted to wiki:
162
+ + learned-patterns.md "Supabase RLS in same migration" (3 sessions, 2 projects)
163
+ + common-fixes.md "next/font crash on Vercel" (2 sessions)
164
+ + concepts/voice-agent-call-state.md (new file)
165
+ Skipped {K} single-occurrence entries — will revisit if they recur.
166
+ ```
167
+
168
+ ## Style
169
+
170
+ - **Be conservative.** False-positive promotions (writing noise as if it's a
171
+ pattern) pollute the wiki and erode trust. Better to skip a candidate and
172
+ let it recur next week than to inflate the curated tier.
173
+ - **Cite your sources.** Every promoted entry should reference the
174
+ daily-log dates that sourced it, in the `--context` field. If a future
175
+ flush wants to update it, the trail is there.
176
+ - **Keep titles short.** `--title "Supabase RLS same migration"` not `"You should always remember that when working with Supabase you need to..."`. The body is for nuance.
177
+ - **Don't promote private projects across boundaries.** A pattern from
178
+ Project A is fine to promote as cross-project ONLY if it generalizes.
179
+ Client-specific things stay client-specific (`--type client --project X`).
180
+
181
+ ## Anti-patterns
182
+
183
+ - **Mass-promoting everything:** if you found "promotions" for 90% of
184
+ daily-log entries, you're labeling, not promoting. Be selective.
185
+ - **Re-promoting on every flush:** before appending, run
186
+ `node ~/.claude/bin/knowledge.js search "{title keywords}"` to check if
187
+ the pattern already exists. If it does, either update it (find by `**ID:**`
188
+ line) or skip — never duplicate.
189
+ - **Hand-writing to `learned-patterns.md` etc. directly:** always go
190
+ through `knowledge.js append` so the canonical entry format and ID
191
+ generation stay consistent. This skill is the only sanctioned promoter,
192
+ but even it uses the loader for writes.
193
+
194
+ ## Output contract
195
+
196
+ If invoked with `--dry-run`, print the proposed writes and exit without
197
+ touching disk. Otherwise, after step 6 returns the summary, the skill is
198
+ done — no follow-up prompts. The user sees the summary and the wiki tier
199
+ has new entries that are immediately reachable to every other skill via
200
+ the loader.
@@ -54,49 +54,45 @@ What did you learn?
54
54
 
55
55
  ### 2. Check for Duplicates
56
56
 
57
- Before saving, check if a similar entry already exists:
57
+ Before saving, search the existing knowledge for a similar entry. Use the
58
+ unified loader, **never** raw `cat` or `grep` directly — the loader handles
59
+ missing-file edge cases and stays consistent across skills.
58
60
 
59
61
  ```bash
60
- # Search for the title (case-insensitive substring match)
61
- grep -i "{title keywords}" ~/.claude/knowledge/{type}.md 2>/dev/null
62
+ node ~/.claude/bin/knowledge.js search "{title keywords}"
62
63
  ```
63
64
 
64
- If a near-match exists (title is similar to an existing entry):
65
+ If a near-match exists:
65
66
  - Show the existing entry to the user
66
67
  - Ask: "A similar entry exists. Update it, create a new one, or skip?"
67
- - If update: replace the existing entry. If new: append. If skip: done.
68
+ - If update: edit the file directly (find the entry by `**ID:**` line). If
69
+ new: continue to step 3. If skip: done.
68
70
 
69
- ### 3. Format Entry
71
+ ### 3. Append the Entry
70
72
 
71
- Each entry gets a unique ID and ISO timestamp for dedup and ordering:
72
-
73
- ```markdown
74
-
75
- ---
76
-
77
- ### {Title}
78
- **ID:** {random 8-char hex, e.g. a3f7c1e9}
79
- **Date:** {ISO 8601, e.g. 2026-04-11}
80
- **Project:** {current project name or "general"}
81
- **Context:** {brief context — what you were building when you learned this}
82
-
83
- {The learning — be specific enough that future-you understands without context}
84
- ```
85
-
86
- ### 4. Append to Knowledge File
87
-
88
- Append-only — never overwrite the file, always add at the end:
73
+ The loader's `append` subcommand handles ID generation, ISO date, project
74
+ detection, and the canonical entry format — one call, no shell escaping
75
+ concerns:
89
76
 
90
77
  ```bash
91
- # Append to the right file
92
- echo "{formatted entry}" >> ~/.claude/knowledge/{type}.md
78
+ node ~/.claude/bin/knowledge.js append \
79
+ --type {pattern|fix|client} \
80
+ --title "{Title}" \
81
+ --body "{The learning — be specific enough that future-you understands without context}" \
82
+ --project "{current project name or 'general'}" \
83
+ --context "{brief context — what you were building when you learned this}"
93
84
  ```
94
85
 
95
- - Pattern `~/.claude/knowledge/learned-patterns.md`
96
- - Fix`~/.claude/knowledge/common-fixes.md`
97
- - Client pref `~/.claude/knowledge/client-prefs.md`
86
+ Typefile mapping (handled by the loader):
87
+ - `pattern``learned-patterns.md`
88
+ - `fix` `common-fixes.md`
89
+ - `client` → `client-prefs.md`
90
+
91
+ The loader prints `appended {id} to {file}` on success. If the destination
92
+ file does not exist, the loader creates it with a header — you do not need
93
+ to bootstrap it.
98
94
 
99
- ### 5. Confirm
95
+ ### 4. Confirm
100
96
 
101
97
  ```
102
98
  ⬢ Saved to {file}
@@ -105,13 +101,27 @@ echo "{formatted entry}" >> ~/.claude/knowledge/{type}.md
105
101
 
106
102
  ## Reading Knowledge
107
103
 
108
- Skills can read knowledge files for context:
104
+ **Always use the loader.** Hardcoded `cat ~/.claude/knowledge/X.md` is an
105
+ anti-pattern — it makes new files invisible (this was v4.1.0 audit finding
106
+ #3). The loader, by contrast, lets agents discover available knowledge via
107
+ the index.
108
+
109
109
  ```bash
110
- cat ~/.claude/knowledge/learned-patterns.md 2>/dev/null
111
- cat ~/.claude/knowledge/common-fixes.md 2>/dev/null
112
- cat ~/.claude/knowledge/client-prefs.md 2>/dev/null
110
+ # Print the index (entry point — read this first)
111
+ node ~/.claude/bin/knowledge.js
112
+
113
+ # Print a specific file (accepts aliases: patterns, fixes, client)
114
+ node ~/.claude/bin/knowledge.js load patterns
115
+ node ~/.claude/bin/knowledge.js load common-fixes.md
116
+
117
+ # List everything available
118
+ node ~/.claude/bin/knowledge.js list
119
+
120
+ # Search across all files
121
+ node ~/.claude/bin/knowledge.js search "RLS"
113
122
  ```
114
123
 
115
- The `/qualia-debug` skill should check `common-fixes.md` before investigating.
116
- The `/qualia-new` skill should check `client-prefs.md` when setting up client projects.
117
- The `/qualia-plan` skill should check `learned-patterns.md` when planning phases.
124
+ The `/qualia-debug` skill should check `common-fixes.md` (`load fixes`) before
125
+ investigating. The `/qualia-new` skill should check `client-prefs.md`
126
+ (`load client`) when setting up client projects. The `/qualia-plan` skill
127
+ should check `learned-patterns.md` (`load patterns`) when planning phases.
@@ -94,7 +94,7 @@ Plus free-text: "Any brand colors or reference sites I should look at?"
94
94
 
95
95
  If client, ask name. Check saved prefs:
96
96
  ```bash
97
- cat ~/.claude/knowledge/client-prefs.md 2>/dev/null | grep -A 10 "{client name}"
97
+ node ~/.claude/bin/knowledge.js search "{client name}"
98
98
  ```
99
99
 
100
100
  ### Step 5. Write PROJECT.md
@@ -33,8 +33,9 @@ Spawn a planner agent to break the current phase into executable tasks, then val
33
33
  cat .planning/STATE.md 2>/dev/null
34
34
  cat .planning/ROADMAP.md 2>/dev/null
35
35
  cat .planning/PROJECT.md 2>/dev/null
36
- cat ~/.claude/knowledge/learned-patterns.md 2>/dev/null
37
- cat ~/.claude/knowledge/client-prefs.md 2>/dev/null
36
+ node ~/.claude/bin/knowledge.js
37
+ node ~/.claude/bin/knowledge.js load patterns
38
+ node ~/.claude/bin/knowledge.js load client
38
39
  ```
39
40
 
40
41
  If no phase number given, use the current phase from STATE.md.