@lh8ppl/claude-memory-kit 0.1.1 → 0.2.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 (58) hide show
  1. package/README.md +8 -5
  2. package/bin/cmk-auto-extract.mjs +13 -0
  3. package/bin/cmk-capture-prompt.mjs +0 -0
  4. package/bin/cmk-capture-turn.mjs +0 -0
  5. package/bin/cmk-compress-session.mjs +31 -17
  6. package/bin/cmk-inject-context.mjs +12 -2
  7. package/bin/cmk-observe-edit.mjs +0 -0
  8. package/bin/cmk-weekly-curate.mjs +14 -2
  9. package/package.json +3 -2
  10. package/src/audit-log.mjs +6 -0
  11. package/src/auto-drain.mjs +59 -0
  12. package/src/auto-extract.mjs +117 -6
  13. package/src/auto-persona.mjs +544 -0
  14. package/src/bullet-lookup.mjs +59 -0
  15. package/src/capture-turn.mjs +54 -0
  16. package/src/compress-session.mjs +6 -8
  17. package/src/compressor.mjs +37 -22
  18. package/src/conflict-queue.mjs +8 -1
  19. package/src/daily-distill.mjs +19 -11
  20. package/src/doctor.mjs +79 -26
  21. package/src/forget.mjs +14 -0
  22. package/src/graduate-session.mjs +65 -0
  23. package/src/graduation.mjs +179 -0
  24. package/src/index-rebuild.mjs +26 -4
  25. package/src/inject-context.mjs +352 -65
  26. package/src/install.mjs +52 -7
  27. package/src/lessons-promote.mjs +137 -0
  28. package/src/mcp-server.mjs +17 -0
  29. package/src/memory-write.mjs +20 -7
  30. package/src/native-memory.mjs +98 -0
  31. package/src/persona-portability.mjs +253 -0
  32. package/src/provenance.mjs +23 -5
  33. package/src/read-hook-stdin.mjs +47 -0
  34. package/src/register-crons.mjs +17 -8
  35. package/src/sanitize.mjs +39 -0
  36. package/src/scratchpad.mjs +247 -19
  37. package/src/session-end-tasks.mjs +127 -0
  38. package/src/settings-hooks.mjs +33 -3
  39. package/src/spawn-bin.mjs +83 -0
  40. package/src/subcommands.mjs +472 -26
  41. package/src/weekly-curate.mjs +53 -6
  42. package/src/write-fact.mjs +60 -3
  43. package/template/.claude/skills/memory-write/SKILL.md +47 -88
  44. package/template/.gitignore.fragment +6 -0
  45. package/template/CLAUDE.md.template +17 -7
  46. package/template/local/machine-paths.md.template +1 -12
  47. package/template/local/overrides.md.template +1 -11
  48. package/template/project/MEMORY.md.template +5 -26
  49. package/template/project/SOUL.md.template +1 -10
  50. package/template/user/fragments/INDEX.md.template +1 -1
  51. package/template/.claude/hooks/pre-tool-memory.js +0 -78
  52. package/template/.claude/hooks/transcript-capture.js +0 -69
  53. package/template/.claude/settings.json +0 -27
  54. package/template/support/scripts/auto-extract-memory.sh +0 -102
  55. package/template/support/scripts/refresh-distill-timestamp.py +0 -35
  56. package/template/support/scripts/register-crons.py +0 -242
  57. package/template/support/scripts/run-daily-distill.sh +0 -67
  58. package/template/support/scripts/run-weekly-curate.sh +0 -58
@@ -45,6 +45,9 @@ import {
45
45
  touchCooldownMarker,
46
46
  } from './cooldown.mjs';
47
47
  import { dailyDistill } from './daily-distill.mjs';
48
+ import { autoPersona } from './auto-persona.mjs';
49
+ import { initUserTier } from './install.mjs';
50
+ import { autoDrainQueues } from './auto-drain.mjs';
48
51
 
49
52
  const DEFAULT_ARCHIVE_MAX_BYTES = 4096;
50
53
  const DEFAULT_RECENT_MAX_BYTES = 4096;
@@ -69,11 +72,12 @@ function buildCurateInstructions(archiveMaxBytes) {
69
72
  'Under each week heading, emit bullets that summarize the work across the days in that week. Each bullet is a single line ≤120 chars. Bullets within a week appear in chronological order.',
70
73
  '',
71
74
  'HARD RULES:',
72
- ' 1. Preserve every citation ID matching /#[ULP]-[A-Z0-9]{6,8}/ verbatim. Never invent new IDs.',
73
- ` 2. Total output ${archiveMaxBytes} bytes.`,
74
- ' 3. Deduplicate aggressively: if the same fact appears across multiple days, emit it ONCE. The deterministic dedup pass after your output will collapse exact-after-canonical duplicates; YOUR job is to catch the looser semantic duplicates (paraphrases, restatements).',
75
- ' 4. No prose between bullets only the bulleted list per week section.',
76
- ' 5. Your output goes directly into archive.md. Do not address the user, do not refer to yourself.',
75
+ ' 1. Every bullet must be grounded in the daily summaries below. Do not infer or add any fact not explicitly present in them. When the summaries show a fact was later corrected, replaced, or reversed, keep ONLY the latest version of that fact — never list the superseded one alongside it (this resolves contradictions, NOT coexisting facts on different points). If unsure, omit it.',
76
+ ' 2. Preserve every citation ID matching /#[ULP]-[A-Z0-9]{6,8}/ verbatim. Never invent new IDs.',
77
+ ` 3. Total output ${archiveMaxBytes} bytes.`,
78
+ ' 4. Deduplicate aggressively: if the same fact appears across multiple days, emit it ONCE. The deterministic dedup pass after your output will collapse exact-after-canonical duplicates; YOUR job is to catch the looser semantic duplicates (paraphrases, restatements).',
79
+ ' 5. No prose between bullets only the bulleted list per week section.',
80
+ ' 6. Your output goes directly into archive.md. Do not address the user, do not refer to yourself.',
77
81
  '',
78
82
  '=== BEGIN OLD DAILY SUMMARIES TO ARCHIVE ===',
79
83
  ].join('\n');
@@ -242,6 +246,7 @@ function writeCurateLogEntry({ projectRoot, date, entry }) {
242
246
  */
243
247
  export async function weeklyCurate({
244
248
  projectRoot,
249
+ userDir,
245
250
  backend,
246
251
  now,
247
252
  cooldownMs = DEFAULT_COOLDOWN_MS,
@@ -277,6 +282,12 @@ export async function weeklyCurate({
277
282
  };
278
283
  }
279
284
 
285
+ // Auto-drain the review + conflict queues (v0.2 Phase 2, D-6) — project
286
+ // tier always, user tier when a userDir is supplied (persona queues).
287
+ // Non-Haiku file IO; runs every weekly pass regardless of the cooldown.
288
+ const drained = { P: await autoDrainQueues({ tier: 'P', projectRoot }) };
289
+ if (userDir) drained.U = await autoDrainQueues({ tier: 'U', userDir });
290
+
280
291
  if (isCooldownActive({ projectRoot, now: ts, cooldownMs })) {
281
292
  const duration_ms = Date.now() - t0;
282
293
  writeCurateLogEntry({
@@ -297,7 +308,38 @@ export async function weeklyCurate({
297
308
  skipped_reason: 'cooldown',
298
309
  },
299
310
  });
300
- return { action: 'skipped', reason: 'cooldown', duration_ms };
311
+ return { action: 'skipped', reason: 'cooldown', drained, duration_ms };
312
+ }
313
+
314
+ // Design-B auto-persona hook (Task 45). The weekly cycle is the natural
315
+ // trigger: once past the shared cooldown gate, synthesize cross-project
316
+ // doctrine from the granular fact archive and auto-promote it into the
317
+ // user tier. cooldownMs:0 — this Haiku call belongs to the SAME weekly
318
+ // cycle as the curate compress below (per §8.7.2, like the inline
319
+ // dailyDistill call). Skipped silently if userDir wasn't supplied (so
320
+ // existing project-only callers/tests are unaffected). D-14/D-15.
321
+ let persona;
322
+ if (userDir) {
323
+ // Ensure the user tier exists before auto-persona tries to promote into
324
+ // it. Without this, a user who never ran `cmk init-user-tier` has no
325
+ // USER.md/HABITS.md/LESSONS.md, so every promotion would silently route
326
+ // to `queued[]` (NOT_FOUND) and the cross-project tier would stay empty
327
+ // — the exact friend-handoff failure auto-persona exists to fix.
328
+ // initUserTier is idempotent (skips existing files).
329
+ try {
330
+ initUserTier({ userTier: userDir });
331
+ } catch {
332
+ // Best-effort: if scaffolding fails (perms, etc.), autoPersona still
333
+ // degrades gracefully (promotions route to queued[]); don't abort the
334
+ // whole curate cycle over a user-tier scaffold hiccup.
335
+ }
336
+ persona = await autoPersona({
337
+ projectRoot,
338
+ userDir,
339
+ backend,
340
+ now: ts,
341
+ cooldownMs: 0,
342
+ });
301
343
  }
302
344
 
303
345
  const files = listAllTodayFiles(projectRoot);
@@ -329,6 +371,8 @@ export async function weeklyCurate({
329
371
  action: 'skipped',
330
372
  reason: 'no-old-files',
331
373
  currentDays: current.length,
374
+ persona,
375
+ drained,
332
376
  duration_ms,
333
377
  };
334
378
  }
@@ -415,6 +459,7 @@ export async function weeklyCurate({
415
459
  now: ts,
416
460
  cooldownMs: 0,
417
461
  maxOutputBytes: recentMaxBytes,
462
+ skipDrain: true, // weeklyCurate already drained above; don't double-drain
418
463
  });
419
464
  if (recentResult?.outputPath) recentPath = recentResult.outputPath;
420
465
  }
@@ -449,6 +494,8 @@ export async function weeklyCurate({
449
494
  recentPath,
450
495
  bytesIn: input_bytes,
451
496
  bytesOut: output_bytes,
497
+ persona,
498
+ drained,
452
499
  duration_ms,
453
500
  };
454
501
  }
@@ -17,8 +17,11 @@ import { join } from 'node:path';
17
17
  import { generateId } from '@lh8ppl/cmk-canonicalize';
18
18
  import { VALID_TIERS, resolveTierRoot, resolveFactDir } from './tier-paths.mjs';
19
19
  import { parse, format } from './frontmatter.mjs';
20
+ import { reindex } from './reindex.mjs';
20
21
  import { appendAuditEntry, nowIso, REASON_CODES } from './audit-log.mjs';
21
22
  import { ERROR_CATEGORIES, errorResult } from './result-shapes.mjs';
23
+ import { sanitizeHomePaths } from './sanitize.mjs';
24
+ import { checkPoisonGuard, logPoisonGuardRejection } from './poison-guard.mjs';
22
25
 
23
26
  const VALID_TYPES = new Set(['user', 'feedback', 'project', 'reference']);
24
27
  const VALID_WRITE_SOURCES = new Set([
@@ -148,7 +151,48 @@ export function writeFact(opts = {}) {
148
151
  });
149
152
  }
150
153
 
151
- const id = opts.id ?? generateId(opts.tier, opts.body);
154
+ // Privacy (write-path fix #1): abstract absolute home-dir paths to `~` in
155
+ // committed/shared tiers (P/U) so a fact never ships the local username
156
+ // and stays portable. Local tier (L) keeps machine-specific paths verbatim
157
+ // — that's its purpose. The id hashes the SANITIZED body, so dedup keys on
158
+ // what actually lands on disk.
159
+ let { body, title } = opts;
160
+ if (opts.tier === 'P' || opts.tier === 'U') {
161
+ body = sanitizeHomePaths(body);
162
+ title = sanitizeHomePaths(title);
163
+ }
164
+
165
+ // Poison_Guard (write-path fix #1): fact files previously bypassed the
166
+ // secret/poison screen that scratchpad writes get via memoryWrite. Screen
167
+ // the (sanitized) body before any disk write; a rejection logs the redacted
168
+ // excerpt to .locks/poison-guard.log and returns a poison_guard error.
169
+ const guard = checkPoisonGuard(body);
170
+ if (guard.rejected) {
171
+ // Best-effort log; guard on projectRoot so a U-tier write with no
172
+ // project context can't turn a clean rejection into a crash.
173
+ if (guard.pattern_id !== 'schema' && opts.projectRoot) {
174
+ logPoisonGuardRejection({
175
+ projectRoot: opts.projectRoot,
176
+ ts: opts.createdAt ?? nowIso(),
177
+ pattern_id: guard.pattern_id,
178
+ source_file: `write-fact:${opts.type}_${opts.slug}`,
179
+ source_line: 1,
180
+ redacted_excerpt: guard.redacted_excerpt,
181
+ });
182
+ }
183
+ return errorResult({
184
+ category: ERROR_CATEGORIES.POISON_GUARD,
185
+ errors: [`Poison_Guard rejected write: pattern_id=${guard.pattern_id}`],
186
+ pattern_id: guard.pattern_id,
187
+ redacted_excerpt: guard.redacted_excerpt,
188
+ id: null,
189
+ path: null,
190
+ });
191
+ }
192
+
193
+ // Use the sanitized body/title for id, frontmatter, and the file body.
194
+ const factOpts = { ...opts, body, title };
195
+ const id = opts.id ?? generateId(opts.tier, body);
152
196
  const createdAt = opts.createdAt ?? nowIso();
153
197
  const tierRoot = resolveTierRoot(opts);
154
198
  const factDir = resolveFactDir(opts.tier, tierRoot);
@@ -198,8 +242,21 @@ export function writeFact(opts = {}) {
198
242
  }
199
243
 
200
244
  mkdirSync(factDir, { recursive: true });
201
- const frontmatter = buildFrontmatterObject(opts, { id, createdAt });
202
- writeFileSync(path, format({ frontmatter, body: `\n${opts.body}\n` }), 'utf8');
245
+ const frontmatter = buildFrontmatterObject(factOpts, { id, createdAt });
246
+ writeFileSync(path, format({ frontmatter, body: `\n${factOpts.body}\n` }), 'utf8');
247
+
248
+ // Keep INDEX.md consistent on every create — the index is a derived view of
249
+ // the fact files, so the writer owns keeping it current. Without this, a fresh
250
+ // `cmk remember` left INDEX.md stale until a manual `cmk reindex`, and
251
+ // `cmk doctor` HC-5 failed from the first capture (Task 85; lior-test-7
252
+ // 2026-06-03 — "users should get it working from the start"). Best-effort: the
253
+ // fact is already durably on disk, so an index-rebuild hiccup must not turn a
254
+ // successful capture into an error — the next reindex/search self-heals.
255
+ try {
256
+ reindex({ tier: opts.tier, projectRoot: opts.projectRoot, userDir: opts.userDir, warn: () => {} });
257
+ } catch {
258
+ // index rebuild is best-effort; capture already succeeded
259
+ }
203
260
 
204
261
  return { action: 'created', id, path };
205
262
  }
@@ -1,117 +1,76 @@
1
1
  ---
2
2
  name: memory-write
3
- description: >
4
- Saves durable facts to context/MEMORY.md or context/USER.md. Auto-triggers
5
- on phrases the user uses to flag worth-remembering content: "remember this",
6
- "remember that", "note this", "note that", "save this", "update memory",
7
- "forget about", "let's remember", "going forward", "from now on", "i prefer",
8
- "i don't like". Also triggers automatically from the auto-extract Stop hook
9
- after every assistant turn when a durable fact is detected.
10
- Three actions: add (append under correct section), replace (substring match
11
- + swap), remove (confirm with user first). Enforces caps (MEMORY.md 2,500
12
- chars, USER.md 1,375 chars) with a dedup guard so duplicate or near-duplicate
13
- facts don't accumulate.
3
+ description: Captures a durable fact to project memory by running `cmk remember`, the kit's safe write path (Poison_Guard secret/injection screening + home-path sanitization + dedup + conflict detection). Use when the user says "remember this", "note this", "save this", "from now on", "going forward", "I prefer", "I don't like", "we decided", "we agreed", or otherwise states a durable preference, decision, or environment fact worth carrying across sessions. Also covers correcting a saved fact ("update memory: X is now Y") and removing one ("forget about X"). Skip throwaway chatter and facts that only matter to the current task.
4
+ allowed-tools: Bash(cmk remember *) Bash(cmk forget *) Read
14
5
  ---
15
6
 
16
- # memory-write
7
+ # Capturing durable memory
17
8
 
18
- ## Purpose
9
+ Durable facts — preferences, decisions, environment state — are saved by running
10
+ the kit's CLI. The CLI is the only safe way to write memory.
19
11
 
20
- Make memory writes **automatic and reliable**. Instead of the user reminding Claude to "save this to memory," the skill captures durable facts the moment they're spoken or decided, with the right structure and the right file.
12
+ ## MUST / NEVER
21
13
 
22
- ## When this skill fires
14
+ - **MUST** capture every durable fact by running `cmk remember` (or `cmk forget`
15
+ to remove). Run the command as shown; do not modify its shape.
16
+ - **NEVER** hand-edit `context/MEMORY.md`, `context/USER.md`, or any file under
17
+ `context/memory/`. Direct edits bypass secret screening and home-path
18
+ sanitization and can leak a credential or a local path into a committed file.
19
+ - **Silent on success.** Do not announce "saved to memory" unless the user asked.
23
20
 
24
- **User-explicit signals**:
21
+ ## Capture a fact (default)
25
22
 
26
- - "remember this" / "remember that" / "remember our X"
27
- - "note this" / "note that"
28
- - "save this" / "save that"
29
- - "update memory" / "add to memory"
30
- - "forget about X" → remove operation
31
- - "from now on" / "going forward" / "i prefer" / "i don't like" → preference signals
32
- - "we decided" / "we agreed" / "let's use X not Y" → decision signals
33
-
34
- **Auto-extract signals** (from the Stop hook):
35
-
36
- - Assistant turn contains a "Why:" or "How to apply:" line implying a durable rule
37
- - Assistant turn explicitly acknowledges a user correction ("you're right", "fair point", "i was wrong")
38
- - Assistant turn states a new decision or environment fact that wasn't in MEMORY.md before
39
-
40
- ## Where to write — file routing
41
-
42
- | Content type | File | Section |
43
- |---|---|---|
44
- | Current active work / open threads | `context/MEMORY.md` | `## Active Threads` |
45
- | Tool versions, paths, URLs, env state | `context/MEMORY.md` | `## Environment Notes` |
46
- | Things the user still has to decide | `context/MEMORY.md` | `## Pending Decisions` |
47
- | Stable user identity, role, expertise | `context/USER.md` | `## About` |
48
- | Persistent preferences ("i prefer X") | `context/USER.md` | `## Preferences` |
49
- | How the user approaches work | `context/USER.md` | `## Working Style` |
50
- | Typed durable fact with rationale | `context/memory/<type>_<slug>.md` | Granular archive, with frontmatter + `**Why:**` + `**How to apply:**` |
51
-
52
- If unsure: scratchpad (MEMORY.md) is the default. If the fact has long-term reasoning, promote it to a granular file later.
53
-
54
- ## How to write — the steps
55
-
56
- 1. Read the target file in full (MEMORY.md or USER.md). Need current state to dedup against.
57
- 2. **Dedup check**: scan for substring or near-paraphrase match. If the fact already exists, skip; don't append a duplicate.
58
- 3. **Cap check**: `wc -c context/MEMORY.md` (or USER.md). If over the cap:
59
- - For MEMORY.md: consolidate existing entries first (merge similar bullets, drop stale ones older than 14 days with no current reference), THEN add the new fact.
60
- - For USER.md: same pattern. USER.md should rarely change — most additions go to MEMORY.md.
61
- 4. **Write** the new fact under the appropriate section. Single bullet, concise (< 200 chars).
62
- 5. **Confirm silently** — do not announce "saved to memory" unless the user explicitly asked. Auto-extract should be invisible.
63
-
64
- ## Actions
65
-
66
- - **add** — default. Append a new bullet under the appropriate section.
67
- - **replace** — when the user says "update memory: X is now Y", or "we decided to switch from X to Y". Find the existing bullet by substring match and swap.
68
- - **remove** — when the user says "forget about X" or "we changed our mind on X". Confirm with the user FIRST before deleting. Removal is the only action that requires confirmation; add and replace are silent.
69
-
70
- ## Examples
23
+ ```
24
+ cmk remember "<the fact, one sentence>"
25
+ ```
71
26
 
72
- **Explicit user trigger:**
27
+ Writes a bullet to `context/MEMORY.md`. Use it for in-flight work, decisions,
28
+ tool versions, paths, and environment facts.
73
29
 
74
- > User: "remember that we're standardizing on Python 3.13"
30
+ When the fact is not active work, pick its section:
75
31
 
76
- Action: add to `MEMORY.md` § Environment Notes:
77
32
  ```
78
- - Python 3.13 is the standard. Older 3.10 envs should be uninstalled to avoid PATH conflicts.
33
+ cmk remember "<fact>" --section "Environment Notes"
79
34
  ```
80
35
 
81
- **Auto-extract trigger (silent):**
36
+ Sections: `Active Threads` (default), `Environment Notes`, `Pending Decisions`.
82
37
 
83
- > Assistant turn (after a debugging session): "...so the issue was the system PATH resolving wrong. Fix uses absolute executable paths. Going forward, scheduled tasks should never rely on bare command names."
38
+ ## Capture a fact WITH rationale (preferences, working style, lasting rules)
39
+
40
+ When the fact carries a reason or a how-to — a user preference, a working-style
41
+ rule, a project constraint — capture it richly so the reasoning survives:
84
42
 
85
- Auto-extract recognizes "Going forward" + concrete rule → add to `MEMORY.md` § Environment Notes:
86
43
  ```
87
- - Scheduled tasks must use absolute executable paths (not bare command names) system PATH can resolve to the wrong binary.
44
+ cmk remember "<headline>" --type <type> --why "<why it holds>" --how "<how to apply it>" --title "<short title>"
88
45
  ```
89
46
 
90
- **Replace trigger:**
47
+ `--type` is one of:
48
+
49
+ - `feedback` — how the user wants you to work
50
+ - `user` — who the user is (role, expertise)
51
+ - `project` — an ongoing goal or constraint
52
+ - `reference` — a pointer to an external resource (URL, ticket, dashboard)
91
53
 
92
- > User: "actually we bumped to v2.6.16 from v2.5.27"
54
+ This writes a granular fact file with the rationale attached, not just a bullet.
93
55
 
94
- Action: find existing "v2.5.27" bullet in MEMORY.md, replace the version. Silent.
56
+ ## Correct a fact
95
57
 
96
- **Remove trigger:**
58
+ Capture the corrected version with `cmk remember`, then remove the stale entry
59
+ with `cmk forget` (below) if it is now wrong. Do not hand-edit the old bullet.
97
60
 
98
- > User: "forget about the daily-distill cron — we're going to do something different"
61
+ ## Remove a fact
99
62
 
100
- Action: Ask the user "Remove the daily-distill entries from MEMORY.md Active Threads? (y/n)" before deleting.
63
+ After confirming with the user (never remove a fact they did not ask to forget):
101
64
 
102
- ## Rules
65
+ ```
66
+ cmk forget "<substring or citation id>" --yes --reason "<why>"
67
+ ```
103
68
 
104
- - Never exceed the cap. Consolidate first if needed.
105
- - Always check for duplicates / near-duplicates before adding.
106
- - Replace is preferred over add when updating existing facts (avoids accumulating contradictory bullets).
107
- - Removal requires user confirmation.
108
- - For typed durable facts with explicit `**Why:**` / `**How to apply:**` structure, write to `context/memory/<type>_<slug>.md` and add a one-line entry to INDEX.md instead of MEMORY.md.
109
- - **Silent by default.** Do not narrate memory writes unless the user explicitly asked. The whole point is invisible bookkeeping.
69
+ Tombstones the fact it keeps an audit trail and is never a silent delete.
110
70
 
111
- ## Common mistakes to avoid
71
+ ## What NOT to capture
112
72
 
113
- - Don't write conversational chatter to MEMORY.md ("user said hello"). Only durable facts.
114
- - Don't duplicate. Always check first.
115
- - Don't blow the cap. Consolidate first.
116
- - Don't announce. Silent unless asked.
117
- - Don't promote everything to the granular archive — the scratchpad is the default. Granular files are for facts with explicit rationale that have long shelf life.
73
+ - Throwaway chatter ("user said hi").
74
+ - Facts about the current task only — those die with the task; they are not memory.
75
+ - Anything you would not want committed to git. Poison_Guard screens secrets, but
76
+ do not lean on it as the first line of defense.
@@ -10,3 +10,9 @@ context/.index/
10
10
 
11
11
  # Run-time locks + transient state
12
12
  context/.locks/
13
+
14
+ # Diagnostic NDJSON logs (observability only; carry raw turn excerpts —
15
+ # e.g. Task 92's low_trust_discarded traces — that are NOT Poison_Guard-
16
+ # screened, so they must never be committed). The durable memory lives in
17
+ # the scratchpads + fact files, not these logs.
18
+ context/sessions/*.extract.log
@@ -24,19 +24,29 @@ Precedence at session start: local > project > user (most-specific wins, others
24
24
 
25
25
  Health checks (HC-1..HC-8) verify each layer is wired correctly: install integrity, hook registration, transcript capture freshness, INDEX accuracy, cron registration, semantic search backend, native Auto Memory coexistence. See [`docs/adr/`](docs/adr/) and [`specs/v0.1.0/design.md`](specs/v0.1.0/design.md) for the full contract.
26
26
 
27
+ ### Recalling memory (for Claude)
28
+
29
+ The snapshot injected at session start is a **bounded hot index, not everything** — there is a deeper, queryable archive. When a question is "what did we decide / what's our X / how does the user work / what's the setup," **query your memory instead of re-deriving the answer from scratch**:
30
+
31
+ - **`cmk search "<topic>"`** — find any captured fact (decisions, preferences, config, lessons) across the project + user tiers.
32
+ - **`context/memory/<type>_<slug>.md`** — the granular fact archive with full **Why / How** rationale (`context/memory/INDEX.md` lists them).
33
+ - **`~/.claude-memory-kit/` (`USER.md` / `HABITS.md` / `LESSONS.md`)** — how this user works across *all* their projects.
34
+
35
+ Reach for these *first* — re-deriving an answer the project already recorded (by re-reading files, re-searching, or working it out again) wastes the memory that exists precisely so you don't have to. Recall from memory first, then verify against the source if needed.
36
+
27
37
  ### Memory write rules (for Claude)
28
38
 
29
- When you learn something durable about this project or the user:
39
+ Most capture is automatic — the Stop hook extracts durable facts each turn, no action needed. To capture something **explicitly**, the **`memory-write` skill** carries the full procedure; it loads on demand when you save a fact. The invariants it enforces:
30
40
 
31
- 1. **Working state** (current threads, today's environment, open decisions) write to `context/MEMORY.md` (≤2,500 chars). Consolidate at the cap.
32
- 2. **Typed durable fact** (user role / project decision / feedback / external reference) → create `context/memory/<type>_<slug>.md` with full YAML frontmatter; add a one-line entry to `context/memory/INDEX.md`.
33
- 3. **Cross-project lesson** → user-tier `~/.claude-memory-kit/LESSONS.md` (via `cmk lessons promote`).
34
- 4. **Never duplicate** between scratchpad and granular archive. If a working-state item becomes durable, MOVE it.
35
- 5. **Confirm silently.** Frozen-snapshot semantics mean the write takes effect next session.
41
+ - **Capture through `cmk remember`** never hand-write `MEMORY.md`, `USER.md`, or files under `context/memory/`. The command routes through the kit's safe path (Poison_Guard secret screen, home-path → `~` abstraction so a committed fact never leaks your username, dedup, correct schema). Add `--why` / `--how` / `--type` to record a durable preference or decision richly — a bare bullet loses the *why*, which is the part worth keeping.
42
+ - **Machine-specific config** (absolute paths only valid on this machine) → `context.local/machine-paths.md` (gitignored), not `cmk remember`.
43
+ - **Cross-project lesson** (true on every project) `cmk lessons promote <id>` moves a project fact to the user tier; never hand-edit the user-tier files (`LESSONS.md` / `HABITS.md` / `USER.md`).
44
+ - **Confirm silently.** Don't announce captures. Frozen-snapshot semantics mean a write takes effect next session.
36
45
 
37
46
  ### Privacy
38
47
 
39
- Anything inside `<private>...</private>` tags in a user prompt is stripped before any disk write — never persisted in any form. Per-fact `private: true` frontmatter excludes a fact from the session-start digest.
48
+ - Anything inside `<private>...</private>` tags in a user prompt is stripped before any disk write — never persisted in any form.
49
+ - `cmk remember` (and auto-extract) abstract absolute home-dir paths (`C:\Users\you\…`, `/home/you/…`, `/Users/you/…`) to `~` before writing to a committed/shared tier, so a fact never ships your username and stays portable across machines. Genuinely machine-specific paths belong in `context.local/` (gitignored).
40
50
 
41
51
  ### Uninstall / remove this block
42
52
 
@@ -1,17 +1,6 @@
1
1
  <!-- Cap: 1500 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
2
 
3
- <!--
4
- machine-paths.md = absolute paths specific to THIS machine for THIS project.
5
- Local tier (gitignored — never committed). Highest priority in the 3-tier
6
- precedence model — overrides anything in context/ or ~/.claude-memory-kit/.
7
- 3 fixed sections per design §2.1.
8
-
9
- Bullet+provenance format (universal — see provenance.mjs):
10
- - (L-XXXXXXXX) the bullet text on one line
11
- <!-- source, source_line, sha1, write, trust, at -->
12
-
13
- Auto-populated by `cmk persona generate` (Task N, design §16.16); empty is fine.
14
- -->
3
+ <!-- machine-paths.md = absolute tool/project paths specific to THIS machine (local tier — gitignored, highest precedence). Replace the examples; empty sections are fine. -->
15
4
 
16
5
  # Machine paths (local tier)
17
6
 
@@ -1,16 +1,6 @@
1
1
  <!-- Cap: 1500 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
2
 
3
- <!--
4
- overrides.md = machine-specific overrides of preferences declared elsewhere.
5
- Local tier (gitignored). Highest precedence in the 3-tier model.
6
- 3 fixed sections per design §2.1.
7
-
8
- Bullet+provenance format (universal — see provenance.mjs):
9
- - (L-XXXXXXXX) the bullet text on one line
10
- <!-- source, source_line, sha1, write, trust, at -->
11
-
12
- Auto-populated by `cmk persona generate` (Task N, design §16.16); empty is fine.
13
- -->
3
+ <!-- overrides.md = machine-specific overrides of preferences declared elsewhere (local tier — gitignored, highest precedence). Replace the examples; empty sections are fine. -->
14
4
 
15
5
  # Machine-specific overrides (local tier)
16
6
 
@@ -1,47 +1,26 @@
1
1
  <!-- Cap: 2500 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
2
 
3
- <!--
4
- MEMORY.md is the working scratchpad. Cap measured by `wc -c` over the whole
5
- file (header + comments + bullets). Consolidation triggers at >95% (Task 12);
6
- stale bullets (>14d without `trust: high`) drop on consolidate. Three fixed
7
- sections per design §2.1.
8
-
9
- Bullet+provenance format (universal across all scratchpads):
10
- - (P-XXXXXXXX) the bullet text on one line
11
- <!-- source: <file>, source_line: <int>, sha1: <40-hex>, write: <enum>, trust: <enum>, at: <ISO 8601> -->
12
-
13
- Each section ships with a placeholder seed bullet — replace with real project
14
- state. Empty sections are fine if you haven't captured anything yet.
15
- -->
16
-
17
3
  # Working Memory
18
4
 
5
+ <!-- Your project's working scratchpad. Replace the example bullets with real state; empty sections are fine. -->
6
+
19
7
  ## Active Threads
20
8
 
21
- <!--
22
- Current work in progress. Drop bullets when work resolves. Auto-extract
23
- (Task 23) writes here; manual edits welcome too.
24
- -->
9
+ <!-- Current work in progress. Drop bullets as work resolves. -->
25
10
 
26
11
  - (P-T6M95JXF) (example) reviewing PR #142 for the auth refactor
27
12
  <!-- source: MEMORY.md, source_line: 22, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
28
13
 
29
14
  ## Environment Notes
30
15
 
31
- <!--
32
- Tool versions, paths, URLs, env state. Update on change. Auto-extract picks
33
- these up from PostToolUse hook events; manual edits welcome.
34
- -->
16
+ <!-- Tool versions, paths, URLs, env state. -->
35
17
 
36
18
  - (P-R662a95Y) (example) Node 20.x; Python 3.13; Postgres 16 in the test environment
37
19
  <!-- source: MEMORY.md, source_line: 30, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
38
20
 
39
21
  ## Pending Decisions
40
22
 
41
- <!--
42
- Things the user still has to decide. Remove when resolved. Auto-populated by
43
- `cmk persona generate` (Task N, design §16.16); empty is fine.
44
- -->
23
+ <!-- Things still to decide. Remove when resolved. -->
45
24
 
46
25
  - (P-KU3aNBX9) (example) decide whether to deprecate /api/v1 by Q3 2026
47
26
  <!-- source: MEMORY.md, source_line: 38, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
@@ -1,15 +1,6 @@
1
1
  <!-- Cap: 1800 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
2
 
3
- <!--
4
- SOUL.md = project persona / disposition / norms. Where USER.md = "who the user
5
- is", SOUL.md = "how Claude should show up". 3 fixed sections per design §2.1.
6
-
7
- Bullet+provenance format (universal — see provenance.mjs):
8
- - (P-XXXXXXXX) the bullet text on one line
9
- <!-- source, source_line, sha1, write, trust, at -->
10
-
11
- Auto-populated by `cmk persona generate` (Task N, design §16.16); empty is fine.
12
- -->
3
+ <!-- SOUL.md = how Claude should show up in this project (tone, defaults, boundaries). Replace the example bullets; empty sections are fine. -->
13
4
 
14
5
  # Project Soul
15
6
 
@@ -8,7 +8,7 @@ This file is auto-maintained by `cmk reindex`. Do not hand-edit unless
8
8
  you also delete the fact file it points to (or `cmk reindex` will
9
9
  overwrite your changes).
10
10
 
11
- Type taxonomy per design §2.2:
11
+ Type taxonomy:
12
12
  - user_* facts about the user
13
13
  - feedback_* corrections / preferences
14
14
  - project_* decisions with rationale (rare in user tier — most
@@ -1,78 +0,0 @@
1
- #!/usr/bin/env node
2
- //
3
- // PreToolUse hook — guarantees the frozen memory snapshot loads
4
- // before the first tool call of each session, without relying on
5
- // Claude obeying CLAUDE.md's session-startup checklist.
6
- //
7
- // Reads context/USER.md, context/SOUL.md, context/MEMORY.md,
8
- // context/memory/INDEX.md, and today's session log (if exists),
9
- // formats them as a single block, and emits the block as
10
- // additionalContext via the hook's output protocol.
11
- //
12
- // Fires once per session — uses a /tmp flag file to suppress subsequent
13
- // firings within the same session. The flag is keyed by session_id when
14
- // available, falling back to a per-day flag.
15
-
16
- const fs = require('fs');
17
- const path = require('path');
18
- const os = require('os');
19
-
20
- try {
21
- const raw = fs.readFileSync(0, 'utf8');
22
- if (!raw) process.exit(0);
23
- const input = JSON.parse(raw);
24
-
25
- // Resolve project dir.
26
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
27
- const ctx = path.join(projectDir, 'context');
28
-
29
- // Per-project flag prefix. Allows multiple projects to coexist on the
30
- // same machine without their session flags colliding.
31
- const projectSlug = path.basename(projectDir).replace(/[^a-z0-9-]/gi, '_');
32
-
33
- // One-per-session guard.
34
- const sid = input.session_id || input.sessionId || `day-${new Date().toISOString().slice(0, 10)}`;
35
- const flagPath = path.join(os.tmpdir(), `cmk-${projectSlug}-mem-injected-${sid.replace(/[^a-z0-9-]/gi, '_')}`);
36
- if (fs.existsSync(flagPath)) {
37
- process.exit(0);
38
- }
39
-
40
- const parts = [];
41
- const safeRead = (p, label) => {
42
- if (fs.existsSync(p)) {
43
- const content = fs.readFileSync(p, 'utf8');
44
- if (content.trim()) {
45
- parts.push(`\n--- ${label} (${p.replace(projectDir, '').replace(/^[/\\]+/, '')}) ---\n${content}`);
46
- }
47
- }
48
- };
49
-
50
- safeRead(path.join(ctx, 'SOUL.md'), 'Project soul (persona / disposition)');
51
- safeRead(path.join(ctx, 'USER.md'), 'User profile');
52
- safeRead(path.join(ctx, 'MEMORY.md'), 'Working memory (scratchpad)');
53
- safeRead(path.join(ctx, 'memory', 'INDEX.md'), 'Granular memory index');
54
- const today = new Date().toISOString().slice(0, 10);
55
- safeRead(path.join(ctx, 'sessions', `${today}.md`), `Today's session log`);
56
-
57
- if (parts.length === 0) {
58
- process.exit(0);
59
- }
60
-
61
- const block = [
62
- '# Memory snapshot (auto-injected on first tool call)',
63
- '',
64
- 'The following files form this session\'s frozen memory snapshot. Reference them when responding; mid-session edits to these files persist to disk but take effect at the NEXT session.',
65
- parts.join('\n'),
66
- ].join('\n');
67
-
68
- process.stdout.write(JSON.stringify({
69
- hookSpecificOutput: {
70
- hookEventName: 'PreToolUse',
71
- additionalContext: block,
72
- },
73
- }));
74
-
75
- try { fs.writeFileSync(flagPath, String(Date.now())); } catch {}
76
- } catch (e) {
77
- process.exit(0);
78
- }