@lh8ppl/claude-memory-kit 0.1.2 → 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.
- package/README.md +8 -5
- package/bin/cmk-auto-extract.mjs +13 -0
- package/bin/cmk-compress-session.mjs +31 -17
- package/bin/cmk-inject-context.mjs +12 -2
- package/bin/cmk-weekly-curate.mjs +14 -2
- package/package.json +3 -2
- package/src/audit-log.mjs +6 -0
- package/src/auto-drain.mjs +59 -0
- package/src/auto-extract.mjs +117 -6
- package/src/auto-persona.mjs +544 -0
- package/src/bullet-lookup.mjs +59 -0
- package/src/capture-turn.mjs +54 -0
- package/src/compress-session.mjs +6 -8
- package/src/compressor.mjs +19 -4
- package/src/conflict-queue.mjs +8 -1
- package/src/daily-distill.mjs +19 -11
- package/src/doctor.mjs +74 -23
- package/src/forget.mjs +14 -0
- package/src/graduate-session.mjs +65 -0
- package/src/graduation.mjs +179 -0
- package/src/inject-context.mjs +206 -59
- package/src/install.mjs +52 -7
- package/src/lessons-promote.mjs +137 -0
- package/src/memory-write.mjs +2 -2
- package/src/native-memory.mjs +98 -0
- package/src/persona-portability.mjs +253 -0
- package/src/provenance.mjs +23 -5
- package/src/read-hook-stdin.mjs +47 -0
- package/src/register-crons.mjs +17 -8
- package/src/scratchpad.mjs +247 -19
- package/src/session-end-tasks.mjs +127 -0
- package/src/settings-hooks.mjs +33 -3
- package/src/subcommands.mjs +339 -16
- package/src/weekly-curate.mjs +53 -6
- package/src/write-fact.mjs +14 -0
- package/template/.claude/skills/memory-write/SKILL.md +47 -88
- package/template/.gitignore.fragment +6 -0
- package/template/CLAUDE.md.template +15 -9
- package/template/local/machine-paths.md.template +1 -12
- package/template/local/overrides.md.template +1 -11
- package/template/project/MEMORY.md.template +5 -26
- package/template/project/SOUL.md.template +1 -10
- package/template/user/fragments/INDEX.md.template +1 -1
- package/template/.claude/hooks/pre-tool-memory.js +0 -78
- package/template/.claude/hooks/transcript-capture.js +0 -69
- package/template/.claude/settings.json +0 -27
- package/template/support/scripts/auto-extract-memory.sh +0 -102
- package/template/support/scripts/refresh-distill-timestamp.py +0 -35
- package/template/support/scripts/register-crons.py +0 -242
- package/template/support/scripts/run-daily-distill.sh +0 -67
- package/template/support/scripts/run-weekly-curate.sh +0 -58
|
@@ -1,117 +1,76 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory-write
|
|
3
|
-
description:
|
|
4
|
-
|
|
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
|
|
7
|
+
# Capturing durable memory
|
|
17
8
|
|
|
18
|
-
|
|
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
|
-
|
|
12
|
+
## MUST / NEVER
|
|
21
13
|
|
|
22
|
-
|
|
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
|
-
|
|
21
|
+
## Capture a fact (default)
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
When the fact is not active work, pick its section:
|
|
75
31
|
|
|
76
|
-
Action: add to `MEMORY.md` § Environment Notes:
|
|
77
32
|
```
|
|
78
|
-
|
|
33
|
+
cmk remember "<fact>" --section "Environment Notes"
|
|
79
34
|
```
|
|
80
35
|
|
|
81
|
-
|
|
36
|
+
Sections: `Active Threads` (default), `Environment Notes`, `Pending Decisions`.
|
|
82
37
|
|
|
83
|
-
|
|
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
|
-
|
|
44
|
+
cmk remember "<headline>" --type <type> --why "<why it holds>" --how "<how to apply it>" --title "<short title>"
|
|
88
45
|
```
|
|
89
46
|
|
|
90
|
-
|
|
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
|
-
|
|
54
|
+
This writes a granular fact file with the rationale attached, not just a bullet.
|
|
93
55
|
|
|
94
|
-
|
|
56
|
+
## Correct a fact
|
|
95
57
|
|
|
96
|
-
|
|
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
|
-
|
|
61
|
+
## Remove a fact
|
|
99
62
|
|
|
100
|
-
|
|
63
|
+
After confirming with the user (never remove a fact they did not ask to forget):
|
|
101
64
|
|
|
102
|
-
|
|
65
|
+
```
|
|
66
|
+
cmk forget "<substring or citation id>" --yes --reason "<why>"
|
|
67
|
+
```
|
|
103
68
|
|
|
104
|
-
|
|
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
|
-
##
|
|
71
|
+
## What NOT to capture
|
|
112
72
|
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
|
|
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.
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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}")
|