@lh8ppl/claude-memory-kit 0.1.0 → 0.1.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.
- package/README.md +77 -0
- package/bin/cmk-auto-extract.mjs +62 -0
- package/bin/cmk-capture-prompt.mjs +65 -0
- package/bin/cmk-capture-turn.mjs +76 -0
- package/bin/cmk-compress-lazy.mjs +0 -0
- package/bin/cmk-compress-session.mjs +64 -0
- package/bin/cmk-daily-distill.mjs +0 -0
- package/bin/cmk-inject-context.mjs +69 -0
- package/bin/cmk-observe-edit.mjs +57 -0
- package/bin/cmk-weekly-curate.mjs +0 -0
- package/bin/cmk.mjs +11 -11
- package/package.json +10 -2
- package/src/audit-log.mjs +1 -0
- package/src/claude-md.mjs +212 -212
- package/src/doctor.mjs +16 -5
- package/src/frontmatter.mjs +73 -73
- package/src/install.mjs +49 -1
- package/src/merge-facts.mjs +213 -213
- package/src/provenance.mjs +217 -217
- package/src/reindex.mjs +134 -134
- package/src/repair.mjs +26 -96
- package/src/settings-hooks.mjs +186 -0
- package/src/subcommands.mjs +13 -2
- package/template/.gitignore.fragment +12 -12
- package/template/CLAUDE.md.template +49 -49
- package/template/docs/journey/journey-log.md.template +292 -292
- package/template/project/memory/INDEX.md.template +47 -47
- package/template/support/cron-jobs/daily-memory-distill.md +15 -15
- package/template/support/cron-jobs/nightly-memsearch-index.md +17 -17
- package/template/support/cron-jobs/weekly-memory-curator.md +15 -15
- package/template/support/milvus-deploy/README.md +57 -57
- package/template/support/milvus-deploy/docker-compose.yml +66 -66
- package/template/support/scripts/auto-extract-memory.sh +102 -102
- package/template/support/scripts/memsearch-index-with-flush.sh +59 -59
- package/template/support/scripts/refresh-distill-timestamp.py +35 -35
- package/template/support/scripts/register-crons.py +242 -242
- package/template/support/scripts/run-daily-distill.sh +67 -67
- package/template/support/scripts/run-weekly-curate.sh +58 -58
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @lh8ppl/claude-memory-kit
|
|
2
|
+
|
|
3
|
+
**`cmk`** — the CLI for [claude-memory-kit](https://github.com/LH8PPL/claude-memory-kit), a per-project, in-repo memory system for [Claude Code](https://docs.claude.com/en/docs/claude-code). It fixes Claude's per-session amnesia so you don't have to re-tell the backstory every time you start a new session.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Frozen snapshot at session start** — MEMORY.md + USER.md + SOUL.md + INDEX.md + today's session log inject once at the first tool call, so Claude sees your context every session without you re-telling it.
|
|
8
|
+
- **Auto-extract on every assistant turn** — a background `claude --print` subagent reads each turn and saves durable facts (decisions, preferences, environment) to memory. No manual writes needed.
|
|
9
|
+
- **`memory-write` skill** — say "remember this", "from now on", "we decided", or "forget X" and the skill dedups, caps, and writes silently.
|
|
10
|
+
- **Per-project, in-repo** — `context/` lives inside your project and travels with `git clone`. Each project keeps its own memory.
|
|
11
|
+
- **9 health checks** — `cmk doctor` validates install, hook wiring, distill freshness, INDEX consistency, cron registration, and stale locks.
|
|
12
|
+
|
|
13
|
+
## Install — pick ONE route
|
|
14
|
+
|
|
15
|
+
Each route is complete on its own. **Don't run both** — they wire the same hooks.
|
|
16
|
+
|
|
17
|
+
### Route A — npm (recommended)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @lh8ppl/claude-memory-kit
|
|
21
|
+
cd ~/my-project
|
|
22
|
+
cmk install # scaffolds context/ AND wires the lifecycle hooks into .claude/settings.json
|
|
23
|
+
cmk doctor # verify, then restart Claude Code
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`cmk install` is a complete entry point: it scaffolds `context/` and writes the 5 lifecycle hooks (PATH-resolved, cross-OS) into the project's `.claude/settings.json`. No separate `/plugin` step needed. Use `cmk install --no-hooks` for a scaffold-only install.
|
|
27
|
+
|
|
28
|
+
> Installing the package globally adds the `cmk` CLI **and** the installer. It's the `cmk install` *subcommand* that wires the hooks — not the bare `npm install`.
|
|
29
|
+
|
|
30
|
+
### Route B — Claude Code plugin marketplace
|
|
31
|
+
|
|
32
|
+
Inside Claude Code:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
/plugin marketplace add LH8PPL/claude-memory-kit
|
|
36
|
+
/plugin install claude-memory-kit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then say *"bootstrap the memory system"* to scaffold this project's `context/`. The plugin bundles the hooks + the `bootstrap` and `memory-write` skills, so it's complete without the npm CLI (add the CLI later only if you want `cmk search` / `cmk doctor` / cron).
|
|
40
|
+
|
|
41
|
+
## CLI
|
|
42
|
+
|
|
43
|
+
Most-used commands (full list via `cmk --help`):
|
|
44
|
+
|
|
45
|
+
| Command | Purpose |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `cmk install` | Scaffold `context/` + `.gitignore` + CLAUDE.md block + wire hooks (`--no-hooks` for scaffold-only) |
|
|
48
|
+
| `cmk doctor` | Run HC-1..HC-9 health checks, surface repair commands |
|
|
49
|
+
| `cmk repair --hooks` / `--locks` / `--index` / `--all` | Idempotent self-repair |
|
|
50
|
+
| `cmk search "<query>" [--mode keyword\|semantic\|hybrid]` | Search accumulated memory (keyword default) |
|
|
51
|
+
| `cmk roll --scope now\|today\|recent` | Manually trigger a compression pipeline |
|
|
52
|
+
| `cmk register-crons [--dry-run] [--unregister]` | Register daily + weekly jobs with cron / launchd / Task Scheduler |
|
|
53
|
+
| `cmk forget <id>` | Tombstone a fact (preserves audit trail) |
|
|
54
|
+
| `cmk import-anthropic-memory [--dry-run] [--yes]` | Merge bullets from Anthropic's native auto-memory into MEMORY.md |
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Node.js ≥ 20
|
|
59
|
+
- Claude Code (for the hook-driven auto-memory loop)
|
|
60
|
+
- Optional: Python 3.12+ for Layer 5b semantic search (deferred to a later release; keyword search ships today)
|
|
61
|
+
|
|
62
|
+
## Three-tier model
|
|
63
|
+
|
|
64
|
+
| Tier | Location | Scope |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| **P** (project) | `<repo>/context/` | committed to git, travels with `clone` |
|
|
67
|
+
| **L** (local) | `<repo>/context.local/` | gitignored, per-machine |
|
|
68
|
+
| **U** (user) | `~/.claude-memory-kit/` | cross-project per-user |
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Full docs, architecture, and design live in the repository:
|
|
73
|
+
**<https://github.com/LH8PPL/claude-memory-kit>**
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT © Lior Hollander
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Auto-extract subagent entrypoint — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-auto-extract.mjs (Task 23).
|
|
5
|
+
// NOT declared in package.json `bin` (it's not invoked by Claude Code
|
|
6
|
+
// directly) — it ships in bin/ so the sibling cmk-capture-turn.mjs can
|
|
7
|
+
// spawn it by absolute path. Only the src module paths differ from the
|
|
8
|
+
// plugin copy (../src/ vs ../../packages/cli/src/).
|
|
9
|
+
//
|
|
10
|
+
// Spawned detached by the Stop hook (cmk-capture-turn.mjs):
|
|
11
|
+
// node <thisfile> <turnFile>
|
|
12
|
+
// argv[2] is the turn buffer file path; cwd / CMK_PROJECT_DIR env gives
|
|
13
|
+
// the project root. Never throws to the parent (the spawn is detached);
|
|
14
|
+
// every error path writes an extract.log entry and exits 0.
|
|
15
|
+
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
|
|
22
|
+
const turnFile = process.argv[2];
|
|
23
|
+
const projectRoot = process.env.CMK_PROJECT_DIR ?? process.cwd();
|
|
24
|
+
|
|
25
|
+
if (!turnFile) {
|
|
26
|
+
process.stderr.write('cmk-auto-extract: missing turnFile argv[2]\n');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const autoExtractModulePath = join(__dirname, '..', 'src', 'auto-extract.mjs');
|
|
31
|
+
const compressorModulePath = join(__dirname, '..', 'src', 'compressor.mjs');
|
|
32
|
+
|
|
33
|
+
let runAutoExtract;
|
|
34
|
+
let HaikuViaAnthropicApi;
|
|
35
|
+
try {
|
|
36
|
+
({ runAutoExtract } = await import(pathToFileURL(autoExtractModulePath).href));
|
|
37
|
+
({ HaikuViaAnthropicApi } = await import(pathToFileURL(compressorModulePath).href));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
`cmk-auto-extract: failed to load modules: ${err?.message ?? err}\n`,
|
|
41
|
+
);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const haikuBackend = new HaikuViaAnthropicApi();
|
|
47
|
+
const r = await runAutoExtract({
|
|
48
|
+
turnFile,
|
|
49
|
+
projectRoot,
|
|
50
|
+
haikuBackend,
|
|
51
|
+
sessionId: process.env.CMK_SESSION_ID,
|
|
52
|
+
});
|
|
53
|
+
process.stderr.write(
|
|
54
|
+
`cmk-auto-extract: ${r.action} (observations: ${r.observation_count ?? 0}, ms: ${r.duration_ms ?? 0})\n`,
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
process.stderr.write(
|
|
58
|
+
`cmk-auto-extract: unexpected error: ${err?.message ?? err}\n`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.exit(0);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// UserPromptSubmit hook handler — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-capture-prompt.mjs (Task 19).
|
|
5
|
+
// Ships in the @lh8ppl/claude-memory-kit npm package so `cmk install`
|
|
6
|
+
// can wire a PATH-resolved `cmk-capture-prompt` command. Only the src
|
|
7
|
+
// module path differs from the plugin copy (../src/ vs ../../packages/cli/src/).
|
|
8
|
+
//
|
|
9
|
+
// Protocol: payload arrives on stdin as JSON ({prompt, session_id, ...}).
|
|
10
|
+
// Sanitize <private> blocks, preserve <retain> tags, append to the daily
|
|
11
|
+
// transcript, emit {"continue": true}. Always exit 0 — a hook that errors
|
|
12
|
+
// would interrupt the user mid-prompt.
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
|
+
|
|
18
|
+
function emitContinue() {
|
|
19
|
+
process.stdout.write('{"continue": true}');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let rawInput = '';
|
|
23
|
+
try {
|
|
24
|
+
rawInput = readFileSync(0, 'utf8');
|
|
25
|
+
} catch {
|
|
26
|
+
emitContinue();
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let payload;
|
|
31
|
+
try {
|
|
32
|
+
payload = rawInput.trim() === '' ? {} : JSON.parse(rawInput);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
`cmk-capture-prompt: failed to parse stdin JSON: ${err?.message ?? err}\n`,
|
|
36
|
+
);
|
|
37
|
+
emitContinue();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
42
|
+
const __dirname = dirname(__filename);
|
|
43
|
+
const modulePath = join(__dirname, '..', 'src', 'capture-prompt.mjs');
|
|
44
|
+
|
|
45
|
+
let capturePrompt;
|
|
46
|
+
try {
|
|
47
|
+
({ capturePrompt } = await import(pathToFileURL(modulePath).href));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`cmk-capture-prompt: failed to load module: ${err?.message ?? err}\n`,
|
|
51
|
+
);
|
|
52
|
+
emitContinue();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
capturePrompt({ payload, projectRoot: process.cwd() });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`cmk-capture-prompt: handler failed: ${err?.message ?? err}\n`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
emitContinue();
|
|
65
|
+
process.exit(0);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Stop hook handler — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-capture-turn.mjs (Task 21).
|
|
5
|
+
// Ships in the @lh8ppl/claude-memory-kit npm package so `cmk install`
|
|
6
|
+
// can wire a PATH-resolved `cmk-capture-turn` command. Two differences
|
|
7
|
+
// from the plugin copy:
|
|
8
|
+
// 1. src module path resolves ../src/ (not ../../packages/cli/src/).
|
|
9
|
+
// 2. the detached auto-extract subagent resolves to the sibling
|
|
10
|
+
// cmk-auto-extract.mjs in THIS bin/ dir (it ships alongside).
|
|
11
|
+
//
|
|
12
|
+
// Protocol: payload arrives on stdin as JSON. Honor stop_hook_active,
|
|
13
|
+
// append to transcripts, spawn detached auto-extract, emit
|
|
14
|
+
// {"continue": true}, exit 0 within ~50ms (NFR-1). Always exit 0.
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
+
|
|
20
|
+
function emitContinue() {
|
|
21
|
+
process.stdout.write('{"continue": true}');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let raw = '';
|
|
25
|
+
try {
|
|
26
|
+
raw = readFileSync(0, 'utf8');
|
|
27
|
+
} catch {
|
|
28
|
+
emitContinue();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let payload;
|
|
33
|
+
try {
|
|
34
|
+
payload = raw.trim() === '' ? {} : JSON.parse(raw);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`cmk-capture-turn: failed to parse stdin JSON: ${err?.message ?? err}\n`,
|
|
38
|
+
);
|
|
39
|
+
emitContinue();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
44
|
+
const __dirname = dirname(__filename);
|
|
45
|
+
const modulePath = join(__dirname, '..', 'src', 'capture-turn.mjs');
|
|
46
|
+
|
|
47
|
+
// Auto-extract path: env override → sibling cmk-auto-extract.mjs (ships
|
|
48
|
+
// in this same bin/ dir). Absent only in a corrupt install; the spawn
|
|
49
|
+
// step is skipped if missing.
|
|
50
|
+
const autoExtractPath =
|
|
51
|
+
process.env.CMK_AUTO_EXTRACT_PATH ??
|
|
52
|
+
(existsSync(join(__dirname, 'cmk-auto-extract.mjs'))
|
|
53
|
+
? join(__dirname, 'cmk-auto-extract.mjs')
|
|
54
|
+
: null);
|
|
55
|
+
|
|
56
|
+
let captureTurn;
|
|
57
|
+
try {
|
|
58
|
+
({ captureTurn } = await import(pathToFileURL(modulePath).href));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`cmk-capture-turn: failed to load module: ${err?.message ?? err}\n`,
|
|
62
|
+
);
|
|
63
|
+
emitContinue();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
captureTurn({ payload, projectRoot: process.cwd(), autoExtractPath });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
process.stderr.write(
|
|
71
|
+
`cmk-capture-turn: handler failed: ${err?.message ?? err}\n`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
emitContinue();
|
|
76
|
+
process.exit(0);
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SessionEnd hook handler — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-compress-session.mjs (Task 22).
|
|
5
|
+
// Ships in the @lh8ppl/claude-memory-kit npm package so `cmk install`
|
|
6
|
+
// can wire a PATH-resolved `cmk-compress-session` command. Only the src
|
|
7
|
+
// module paths differ from the plugin copy (../src/ vs ../../packages/cli/src/).
|
|
8
|
+
//
|
|
9
|
+
// Protocol: drain stdin, resolve project root from CMK_PROJECT_DIR env
|
|
10
|
+
// or cwd, run compressSession() with a real HaikuViaAnthropicApi, emit
|
|
11
|
+
// {"continue": true}. Always exit 0 — a crashed SessionEnd hook would
|
|
12
|
+
// block the user from closing their terminal.
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
|
+
|
|
18
|
+
function emitContinue() {
|
|
19
|
+
process.stdout.write('{"continue": true}');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let rawInput = '';
|
|
23
|
+
try {
|
|
24
|
+
rawInput = readFileSync(0, 'utf8');
|
|
25
|
+
} catch {
|
|
26
|
+
// stdin not connected — fine; SessionEnd still proceeds.
|
|
27
|
+
}
|
|
28
|
+
void rawInput;
|
|
29
|
+
|
|
30
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
31
|
+
const __dirname = dirname(__filename);
|
|
32
|
+
|
|
33
|
+
const compressSessionModulePath = join(__dirname, '..', 'src', 'compress-session.mjs');
|
|
34
|
+
const compressorModulePath = join(__dirname, '..', 'src', 'compressor.mjs');
|
|
35
|
+
|
|
36
|
+
let compressSession;
|
|
37
|
+
let HaikuViaAnthropicApi;
|
|
38
|
+
try {
|
|
39
|
+
({ compressSession } = await import(pathToFileURL(compressSessionModulePath).href));
|
|
40
|
+
({ HaikuViaAnthropicApi } = await import(pathToFileURL(compressorModulePath).href));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`cmk-compress-session: failed to load modules: ${err?.message ?? err}\n`,
|
|
44
|
+
);
|
|
45
|
+
emitContinue();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const projectRoot = process.env.CMK_PROJECT_DIR ?? process.cwd();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const backend = new HaikuViaAnthropicApi();
|
|
53
|
+
const r = await compressSession({ projectRoot, backend });
|
|
54
|
+
process.stderr.write(
|
|
55
|
+
`cmk-compress-session: ${r.action}${r.reason ? ` (${r.reason})` : ''}${r.bytesIn ? ` (in: ${r.bytesIn}b, out: ${r.bytesOut}b)` : ''} ms: ${r.duration_ms ?? 0}\n`,
|
|
56
|
+
);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
process.stderr.write(
|
|
59
|
+
`cmk-compress-session: unexpected error: ${err?.message ?? err}\n`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
emitContinue();
|
|
64
|
+
process.exit(0);
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SessionStart hook handler — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-inject-context.mjs (Task 18).
|
|
5
|
+
// This copy lives in the published @lh8ppl/claude-memory-kit npm package
|
|
6
|
+
// (declared in package.json `bin`), so `cmk install` can wire a
|
|
7
|
+
// PATH-resolved `cmk-inject-context` hook command into settings.json
|
|
8
|
+
// WITHOUT the plugin's `${CLAUDE_PLUGIN_ROOT}` / bash dependency. The
|
|
9
|
+
// only difference from the plugin copy is the src module path: here it
|
|
10
|
+
// resolves ../src/ (bin/ → src/), not ../../packages/cli/src/.
|
|
11
|
+
//
|
|
12
|
+
// Protocol: payload arrives on stdin as JSON (drained, not consumed).
|
|
13
|
+
// Emit the Anthropic SessionStart hookOutput JSON on stdout. Exit 0
|
|
14
|
+
// unconditionally — a throwing SessionStart hook would interrupt
|
|
15
|
+
// session start, worse than an empty additionalContext.
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
|
+
|
|
21
|
+
// Drain stdin so callers blocking on EPIPE don't hang.
|
|
22
|
+
try {
|
|
23
|
+
readFileSync(0, 'utf8');
|
|
24
|
+
} catch {
|
|
25
|
+
// stdin not connected; fine.
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = dirname(__filename);
|
|
30
|
+
const modulePath = join(__dirname, '..', 'src', 'inject-context.mjs');
|
|
31
|
+
|
|
32
|
+
let injectContext;
|
|
33
|
+
try {
|
|
34
|
+
({ injectContext } = await import(pathToFileURL(modulePath).href));
|
|
35
|
+
} catch (err) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`cmk-inject-context: failed to load module at ${modulePath}: ${
|
|
38
|
+
err?.message ?? String(err)
|
|
39
|
+
}\n`,
|
|
40
|
+
);
|
|
41
|
+
process.stdout.write(
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
hookSpecificOutput: {
|
|
44
|
+
hookEventName: 'SessionStart',
|
|
45
|
+
additionalContext: '',
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const r = injectContext({ cwd: process.cwd() });
|
|
54
|
+
process.stdout.write(JSON.stringify(r.hookOutput));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
process.stderr.write(
|
|
58
|
+
`cmk-inject-context: handler failed: ${err?.message ?? String(err)}\n`,
|
|
59
|
+
);
|
|
60
|
+
process.stdout.write(
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
hookSpecificOutput: {
|
|
63
|
+
hookEventName: 'SessionStart',
|
|
64
|
+
additionalContext: '',
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook handler — npm-route bin (Task 49, T-037).
|
|
3
|
+
//
|
|
4
|
+
// De-plugin-ified twin of plugin/bin/cmk-observe-edit.mjs (Task 20).
|
|
5
|
+
// Ships in the @lh8ppl/claude-memory-kit npm package so `cmk install`
|
|
6
|
+
// can wire a PATH-resolved `cmk-observe-edit` command (registered
|
|
7
|
+
// async: true in the hooks block). Only the src module path differs
|
|
8
|
+
// from the plugin copy (../src/ vs ../../packages/cli/src/).
|
|
9
|
+
//
|
|
10
|
+
// All errors are swallowed + logged to stderr — a hook child crashing
|
|
11
|
+
// must never surface in the user's session. The append is
|
|
12
|
+
// fire-and-forget by design.
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
|
+
|
|
18
|
+
let raw = '';
|
|
19
|
+
try {
|
|
20
|
+
raw = readFileSync(0, 'utf8');
|
|
21
|
+
} catch {
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let payload;
|
|
26
|
+
try {
|
|
27
|
+
payload = raw.trim() === '' ? {} : JSON.parse(raw);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
process.stderr.write(
|
|
30
|
+
`cmk-observe-edit: failed to parse stdin JSON: ${err?.message ?? err}\n`,
|
|
31
|
+
);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = dirname(__filename);
|
|
37
|
+
const modulePath = join(__dirname, '..', 'src', 'observe-edit.mjs');
|
|
38
|
+
|
|
39
|
+
let observeEdit;
|
|
40
|
+
try {
|
|
41
|
+
({ observeEdit } = await import(pathToFileURL(modulePath).href));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
`cmk-observe-edit: failed to load module: ${err?.message ?? err}\n`,
|
|
45
|
+
);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
observeEdit({ payload, projectRoot: process.cwd() });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`cmk-observe-edit: handler failed: ${err?.message ?? err}\n`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(0);
|
|
File without changes
|
package/bin/cmk.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// cmk — claude-memory-kit CLI entry point.
|
|
3
|
-
// Thin shim: defers all argv parsing + dispatch to src/index.mjs.
|
|
4
|
-
// Kept thin so the bin file rarely needs to change once installed.
|
|
5
|
-
|
|
6
|
-
import { run } from '../src/index.mjs';
|
|
7
|
-
|
|
8
|
-
run(process.argv).catch((err) => {
|
|
9
|
-
console.error('cmk: unexpected error');
|
|
10
|
-
console.error(err && err.stack ? err.stack : err);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
});
|
|
2
|
+
// cmk — claude-memory-kit CLI entry point.
|
|
3
|
+
// Thin shim: defers all argv parsing + dispatch to src/index.mjs.
|
|
4
|
+
// Kept thin so the bin file rarely needs to change once installed.
|
|
5
|
+
|
|
6
|
+
import { run } from '../src/index.mjs';
|
|
7
|
+
|
|
8
|
+
run(process.argv).catch((err) => {
|
|
9
|
+
console.error('cmk: unexpected error');
|
|
10
|
+
console.error(err && err.stack ? err.stack : err);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lh8ppl/claude-memory-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "cmk — the CLI for claude-memory-kit. Per-project, in-repo memory system for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cmk": "./bin/cmk.mjs",
|
|
8
8
|
"cmk-daily-distill": "./bin/cmk-daily-distill.mjs",
|
|
9
9
|
"cmk-weekly-curate": "./bin/cmk-weekly-curate.mjs",
|
|
10
|
-
"cmk-compress-lazy": "./bin/cmk-compress-lazy.mjs"
|
|
10
|
+
"cmk-compress-lazy": "./bin/cmk-compress-lazy.mjs",
|
|
11
|
+
"cmk-inject-context": "./bin/cmk-inject-context.mjs",
|
|
12
|
+
"cmk-capture-prompt": "./bin/cmk-capture-prompt.mjs",
|
|
13
|
+
"cmk-observe-edit": "./bin/cmk-observe-edit.mjs",
|
|
14
|
+
"cmk-capture-turn": "./bin/cmk-capture-turn.mjs",
|
|
15
|
+
"cmk-compress-session": "./bin/cmk-compress-session.mjs"
|
|
11
16
|
},
|
|
12
17
|
"files": [
|
|
13
18
|
"bin/",
|
|
@@ -40,6 +45,9 @@
|
|
|
40
45
|
"url": "https://github.com/LH8PPL/claude-memory-kit.git",
|
|
41
46
|
"directory": "packages/cli"
|
|
42
47
|
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/LH8PPL/claude-memory-kit/issues"
|
|
50
|
+
},
|
|
43
51
|
"keywords": [
|
|
44
52
|
"claude",
|
|
45
53
|
"claude-code",
|
package/src/audit-log.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export const REASON_CODES = Object.freeze({
|
|
|
44
44
|
IMPORT_SKIPPED_DUPLICATE: 'import-skipped-duplicate', // import-anthropic-memory: candidate canonicalize-matched existing fact, skipped (Task 38)
|
|
45
45
|
REPAIR_HOOKS_APPLIED: 'repair-hooks-applied', // cmk repair --hooks: settings.json updated with canonical kit hooks (Task 39)
|
|
46
46
|
REPAIR_HOOKS_NOOP: 'repair-hooks-noop', // cmk repair --hooks: settings.json already canonical, no-op (Task 39)
|
|
47
|
+
INSTALL_HOOKS_WIRED: 'install-hooks-wired', // cmk install: settings.json wired with npm-route hooks (Task 49). NOTE: no NOOP counterpart — install audits only on change, to keep re-runs byte-idempotent (the audit.log is append-only).
|
|
47
48
|
REPAIR_LOCK_REMOVED: 'repair-lock-removed', // cmk repair --locks: stale lock unlinked (Task 39)
|
|
48
49
|
});
|
|
49
50
|
|