@lh8ppl/claude-memory-kit 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +12 -5
  2. package/bin/cmk-auto-extract.mjs +13 -0
  3. package/bin/cmk-compress-session.mjs +31 -17
  4. package/bin/cmk-inject-context.mjs +12 -2
  5. package/bin/cmk-weekly-curate.mjs +14 -2
  6. package/package.json +3 -2
  7. package/src/audit-log.mjs +6 -0
  8. package/src/auto-drain.mjs +59 -0
  9. package/src/auto-extract.mjs +117 -6
  10. package/src/auto-persona.mjs +544 -0
  11. package/src/bullet-lookup.mjs +59 -0
  12. package/src/capture-turn.mjs +54 -0
  13. package/src/compress-session.mjs +6 -8
  14. package/src/compressor.mjs +19 -4
  15. package/src/conflict-queue.mjs +8 -1
  16. package/src/daily-distill.mjs +19 -11
  17. package/src/doctor.mjs +74 -23
  18. package/src/forget.mjs +14 -0
  19. package/src/graduate-session.mjs +65 -0
  20. package/src/graduation.mjs +179 -0
  21. package/src/inject-context.mjs +206 -59
  22. package/src/install.mjs +52 -7
  23. package/src/lessons-promote.mjs +137 -0
  24. package/src/memory-write.mjs +2 -2
  25. package/src/native-memory.mjs +98 -0
  26. package/src/persona-portability.mjs +253 -0
  27. package/src/provenance.mjs +23 -5
  28. package/src/read-hook-stdin.mjs +47 -0
  29. package/src/register-crons.mjs +17 -8
  30. package/src/scratchpad.mjs +247 -19
  31. package/src/session-end-tasks.mjs +127 -0
  32. package/src/settings-hooks.mjs +33 -3
  33. package/src/subcommands.mjs +339 -16
  34. package/src/weekly-curate.mjs +53 -6
  35. package/src/write-fact.mjs +14 -0
  36. package/template/.claude/skills/memory-write/SKILL.md +47 -88
  37. package/template/.gitignore.fragment +6 -0
  38. package/template/CLAUDE.md.template +15 -9
  39. package/template/local/machine-paths.md.template +1 -12
  40. package/template/local/overrides.md.template +1 -11
  41. package/template/project/MEMORY.md.template +5 -26
  42. package/template/project/SOUL.md.template +1 -10
  43. package/template/user/fragments/INDEX.md.template +1 -1
  44. package/template/.claude/hooks/pre-tool-memory.js +0 -78
  45. package/template/.claude/hooks/transcript-capture.js +0 -69
  46. package/template/.claude/settings.json +0 -27
  47. package/template/support/scripts/auto-extract-memory.sh +0 -102
  48. package/template/support/scripts/refresh-distill-timestamp.py +0 -35
  49. package/template/support/scripts/register-crons.py +0 -242
  50. package/template/support/scripts/run-daily-distill.sh +0 -67
  51. package/template/support/scripts/run-weekly-curate.sh +0 -58
@@ -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,18 +24,24 @@ 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
- Most capture is automatic — the Stop hook extracts durable facts each turn, no action needed. When you want to capture something **explicitly**:
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. **Use `cmk remember "<the fact>"`** — do NOT hand-write files under `context/memory/`. The command routes through the kit's safe write path: it screens for secrets (Poison_Guard), abstracts machine-specific home paths to `~` (so a committed fact never leaks the local username), dedups, and writes the correct schema. Hand-writing fact files bypasses all of that and produces files the index can't read.
32
- ```bash
33
- cmk remember "We deploy with Kamal to Hetzner; never to Vercel."
34
- cmk remember "Lior prefers terse responses, no preamble." --trust high
35
- ```
36
- 2. **Machine-specific config** (absolute tool paths that only make sense on this machine) → put it in `context.local/machine-paths.md` (gitignored, never committed). `cmk remember` writes the committed project tier; for machine-only paths, edit the local tier directly.
37
- 3. **Cross-project lesson** (true on every project, not just this one) → `cmk lessons promote <id>` moves a project fact to the user tier (`~/.claude-memory-kit/`).
38
- 4. **Confirm silently.** Don't announce captures. Frozen-snapshot semantics mean a 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.
39
45
 
40
46
  ### Privacy
41
47
 
@@ -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
- }
@@ -1,69 +0,0 @@
1
- #!/usr/bin/env node
2
- //
3
- // Stop hook — fires after every assistant turn.
4
- //
5
- // Does TWO things:
6
- // 1. Captures the first ~500 chars of the turn into
7
- // context/transcripts/{today}.md (verbatim transcript trail).
8
- // 2. Spawns the auto-extract helper in the background (detached, fire-
9
- // and-forget) which invokes `claude --print` on the turn and writes
10
- // any durable facts to MEMORY.md / USER.md / granular archive via
11
- // the memory-write skill.
12
- //
13
- // Step 1 is synchronous and fast. Step 2 is detached so the user never
14
- // waits for the auto-extract subprocess to complete. If either step fails,
15
- // the hook exits 0 — it must NEVER break the session.
16
-
17
- const fs = require('fs');
18
- const path = require('path');
19
- const { spawn } = require('child_process');
20
-
21
- try {
22
- const raw = fs.readFileSync(0, 'utf8');
23
- if (!raw) process.exit(0);
24
- const input = JSON.parse(raw);
25
-
26
- // Defensive: payload shape varies across Claude Code versions.
27
- const text =
28
- (typeof input.response === 'string' && input.response) ||
29
- (typeof input.assistant_message === 'string' && input.assistant_message) ||
30
- (typeof input.last_assistant_message === 'string' && input.last_assistant_message) ||
31
- '';
32
-
33
- if (!text.trim()) process.exit(0);
34
-
35
- const today = new Date().toISOString().slice(0, 10);
36
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
37
-
38
- // ---- Step 1 — transcript capture (synchronous) ----
39
- const tDir = path.join(projectDir, 'context', 'transcripts');
40
- const tFile = path.join(tDir, `${today}.md`);
41
- try {
42
- fs.mkdirSync(tDir, { recursive: true });
43
- const summary = text.slice(0, 500).replace(/\n{3,}/g, '\n\n');
44
- const timestamp = new Date().toISOString().slice(11, 19);
45
- fs.appendFileSync(tFile, `\n## ${timestamp}\n${summary}\n`);
46
- } catch {}
47
-
48
- // ---- Step 2 — auto-extract memory-worthy facts (background) ----
49
- try {
50
- const tmp = path.join(projectDir, 'context', 'transcripts', `.extract-${Date.now()}.tmp`);
51
- fs.writeFileSync(tmp, text, 'utf8');
52
-
53
- const extractScript = path.join(projectDir, 'scripts', 'auto-extract-memory.sh');
54
- if (fs.existsSync(extractScript)) {
55
- const child = spawn('bash', [extractScript, tmp], {
56
- detached: true,
57
- stdio: 'ignore',
58
- cwd: projectDir,
59
- });
60
- child.unref();
61
- } else {
62
- try { fs.unlinkSync(tmp); } catch {}
63
- }
64
- } catch {}
65
- } catch {
66
- // Fire-and-forget. Never break the session.
67
- }
68
-
69
- process.exit(0);
@@ -1,27 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": []
4
- },
5
- "hooks": {
6
- "Stop": [
7
- {
8
- "hooks": [
9
- {
10
- "type": "command",
11
- "command": "node .claude/hooks/transcript-capture.js"
12
- }
13
- ]
14
- }
15
- ],
16
- "PreToolUse": [
17
- {
18
- "hooks": [
19
- {
20
- "type": "command",
21
- "command": "node .claude/hooks/pre-tool-memory.js"
22
- }
23
- ]
24
- }
25
- ]
26
- }
27
- }
@@ -1,102 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Auto-extract: invoked by the Stop hook in the background after every
4
- # assistant turn. Reads the turn from a temp file, runs Claude headlessly
5
- # with a focused fact-extraction prompt, and lets Claude write any
6
- # durable facts to MEMORY.md / USER.md / granular archive via the
7
- # memory-write skill.
8
- #
9
- # This is the "make it automatic" piece: instead of the user having to
10
- # say "remember this", the system harvests memory-worthy content from
11
- # every turn on its own.
12
- #
13
- # Args:
14
- # $1 — path to a temp file containing the assistant turn's text
15
- #
16
- # Detached from the parent hook — runs in background, fire-and-forget.
17
- # Errors are swallowed (logged to context/sessions/{today}.extract.log).
18
-
19
- # Detached process contexts don't always inherit Git Bash's PATH on
20
- # Windows. Set it up explicitly. On Linux/macOS this is a no-op since
21
- # /usr/bin is already present.
22
- case ":$PATH:" in
23
- *":/usr/bin:"*) ;;
24
- *) export PATH="/usr/bin:/c/Program Files/Git/usr/bin:$PATH" ;;
25
- esac
26
-
27
- set -u
28
-
29
- TURN_FILE="${1:-}"
30
- [ -z "$TURN_FILE" ] && exit 0
31
- [ ! -f "$TURN_FILE" ] && exit 0
32
-
33
- REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
34
- cd "$REPO_ROOT"
35
-
36
- TODAY=$(date +%Y-%m-%d)
37
- LOG="${REPO_ROOT}/context/sessions/${TODAY}.extract.log"
38
-
39
- TURN_TEXT=$(cat "$TURN_FILE")
40
-
41
- trap "rm -f \"$TURN_FILE\"" EXIT
42
-
43
- # Skip very short turns — nothing to extract.
44
- TURN_LEN=${#TURN_TEXT}
45
- if [ "$TURN_LEN" -lt 100 ]; then
46
- echo "[$(date -Iseconds)] skip: turn too short ($TURN_LEN chars)" >> "$LOG"
47
- exit 0
48
- fi
49
-
50
- PROMPT=$(cat <<EOF
51
- You are running as a silent background auto-extract task for this project. No one is watching this output. Be CONSERVATIVE — only capture what is clearly durable. Most turns will have nothing to save. That is the correct outcome.
52
-
53
- GOAL: look at the assistant turn below and decide whether the user (in the conversation that produced it) said anything that should be saved to memory. If yes, write it to context/MEMORY.md, context/USER.md, or context/memory/<type>_<slug>.md using the memory-write skill.
54
-
55
- EXTRACT and SAVE only when one of these is clearly present:
56
- - User explicitly asked to remember ("remember this", "note that", "save this", "from now on", "going forward", "i prefer")
57
- - User made a concrete decision worth carrying forward ("we're using X not Y", "let's go with X")
58
- - User corrected the assistant ("actually it's X not Y", "you got that wrong, X is the right answer")
59
- - Assistant acknowledged a NEW environment fact not already in MEMORY.md / USER.md (new tool version, new path, new config)
60
- - Assistant identified a durable rule with "Why:" / "How to apply:" structure
61
-
62
- DO NOT save:
63
- - Conversational chatter, hello/goodbye
64
- - One-off task execution narration ("ran the script, got output X")
65
- - Information already in MEMORY.md / USER.md / context/memory/ (check INDEX.md)
66
- - Anything you would summarize as "we discussed X" without a concrete decision
67
-
68
- STEPS:
69
- 1. Read context/MEMORY.md, context/USER.md, and context/memory/INDEX.md to know current state.
70
- 2. Read the turn content (passed as the user message of this conversation).
71
- 3. If anything durable is present, use the memory-write skill rules:
72
- - Choose the right file (scratchpad vs USER.md vs granular archive)
73
- - Dedup-check against existing content
74
- - Cap-check (consolidate first if over)
75
- - Write a single bullet, concise (<200 chars)
76
- 4. If nothing durable: exit silently. Do not write.
77
- 5. Output ONE line of plain text: either "saved: <one-line summary of what>" or "skip: nothing durable" — for the log.
78
-
79
- CONSTRAINTS:
80
- - Use Read, Edit, and Bash(wc *) only.
81
- - Do NOT create new files outside context/memory/.
82
- - Do NOT commit, push, or run git operations.
83
- - Do NOT print anything except the one-line outcome.
84
-
85
- === TURN CONTENT ===
86
- ${TURN_TEXT}
87
- === END TURN ===
88
- EOF
89
- )
90
-
91
- mkdir -p "$(dirname "$LOG")"
92
- echo "[$(date -Iseconds)] auto-extract fired on turn len=$TURN_LEN" >> "$LOG"
93
-
94
- OUTPUT=$(echo "$PROMPT" | claude --print \
95
- --add-dir "$REPO_ROOT" \
96
- --allowed-tools "Read" "Edit" "Bash(wc *)" \
97
- --output-format text \
98
- 2>&1)
99
- EXIT=$?
100
-
101
- echo "[$(date -Iseconds)] auto-extract exit=$EXIT output: $OUTPUT" >> "$LOG"
102
- exit 0
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Tiny helper for the daily-memory-distill cron job.
4
-
5
- Rewrites the `<!-- Last distilled: YYYY-MM-DD -->` line in
6
- context/MEMORY.md to today's date. Keeps HC-3 green even when the real
7
- distillation (Claude-driven extraction of facts from sessions/) isn't
8
- wired up yet.
9
-
10
- Usage:
11
- python scripts/refresh-distill-timestamp.py
12
- """
13
- from __future__ import annotations
14
-
15
- import datetime
16
- import re
17
- import sys
18
- from pathlib import Path
19
-
20
- REPO_ROOT = Path(__file__).resolve().parent.parent
21
- MEMORY = REPO_ROOT / "context" / "MEMORY.md"
22
-
23
- if not MEMORY.exists():
24
- print(f"ERROR: {MEMORY} not found", file=sys.stderr)
25
- sys.exit(1)
26
-
27
- today = datetime.date.today().isoformat()
28
- text = MEMORY.read_text(encoding="utf-8")
29
- new_text, n = re.subn(r"Last distilled: \d{4}-\d{2}-\d{2}",
30
- f"Last distilled: {today}", text, count=1)
31
- if n == 0:
32
- print("WARN: no 'Last distilled:' line found in MEMORY.md; nothing to update", file=sys.stderr)
33
- sys.exit(0)
34
- MEMORY.write_text(new_text, encoding="utf-8")
35
- print(f"OK: updated Last distilled to {today}")