@lh8ppl/claude-memory-kit 0.4.0 → 0.4.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 +135 -54
- package/bin/cmk-approve-permission.mjs +62 -0
- package/bin/cmk-daily-distill.mjs +14 -0
- package/bin/cmk-inject-context.mjs +12 -0
- package/bin/cmk-weekly-curate.mjs +12 -0
- package/package.json +3 -2
- package/src/approve-permission.mjs +92 -0
- package/src/auto-extract.mjs +17 -10
- package/src/auto-persona.mjs +7 -3
- package/src/compaction-state.mjs +204 -0
- package/src/doctor.mjs +42 -1
- package/src/inject-context.mjs +8 -15
- package/src/install.mjs +37 -4
- package/src/lazy-compress.mjs +43 -110
- package/src/register-crons.mjs +31 -0
- package/src/settings-hooks.mjs +58 -1
- package/src/tier-paths.mjs +47 -13
package/src/settings-hooks.mjs
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from 'node:fs';
|
|
59
59
|
import { dirname, join } from 'node:path';
|
|
60
60
|
import { stripBom } from './read-json.mjs';
|
|
61
|
+
import { MCP_AUTO_APPROVE } from './kiro-constants.mjs';
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* Canonical npm-route hooks block. Shell form (no `args`), PATH-resolved
|
|
@@ -65,6 +66,20 @@ import { stripBom } from './read-json.mjs';
|
|
|
65
66
|
* (modulo command form) plugin/hooks/hooks.json.
|
|
66
67
|
*/
|
|
67
68
|
export const KIT_HOOKS_BLOCK = Object.freeze({
|
|
69
|
+
// PermissionRequest — the prompt-free auto-approver (Task 172, the v0.4.1
|
|
70
|
+
// cut-gate fix). Fires when Claude Code is about to show a permission dialog
|
|
71
|
+
// for one of the kit's OWN surfaces and answers "allow" so capture/recall stay
|
|
72
|
+
// seamless. Two matchers: the kit's MCP tools (`mcp__cmk__.*`) and the Skill
|
|
73
|
+
// tool (the "Use skill?" prompt). The handler (cmk-approve-permission)
|
|
74
|
+
// self-checks the tool name and approves ONLY kit tools/skills — the matcher
|
|
75
|
+
// is the first narrowing, the handler's check is the second (defence in depth).
|
|
76
|
+
// Needed because CC 2.1.x stopped honouring `permissions.allow` MCP rules +
|
|
77
|
+
// skill `allowed-tools` for these prompts (anthropics/claude-code#17499,
|
|
78
|
+
// #18837→#14956); the PermissionRequest hook is the documented, working path.
|
|
79
|
+
PermissionRequest: [
|
|
80
|
+
{ matcher: 'mcp__cmk__.*', hooks: [{ type: 'command', command: 'cmk-approve-permission', timeout: 5 }] },
|
|
81
|
+
{ matcher: 'Skill', hooks: [{ type: 'command', command: 'cmk-approve-permission', timeout: 5 }] },
|
|
82
|
+
],
|
|
68
83
|
// PreToolUse — the memory delete-guardrail (D-192). Blocks a destructive
|
|
69
84
|
// shell command (rm / Remove-Item / git clean …) aimed at a memory path
|
|
70
85
|
// BEFORE it runs. The only kit hook that can exit non-zero (2 = block).
|
|
@@ -86,6 +101,7 @@ export const KIT_HOOKS_BLOCK = Object.freeze({
|
|
|
86
101
|
*/
|
|
87
102
|
export const KIT_COMMAND_TOKENS = Object.freeze([
|
|
88
103
|
'cmk-version-check',
|
|
104
|
+
'cmk-approve-permission',
|
|
89
105
|
'cmk-guard-memory',
|
|
90
106
|
'cmk-inject-context',
|
|
91
107
|
'cmk-capture-prompt',
|
|
@@ -212,7 +228,33 @@ export function writeKitHooks(settingsPath) {
|
|
|
212
228
|
// Task 133: memory-search joins memory-write — every scaffolded skill needs
|
|
213
229
|
// its own allow entry or the model's invocation trips a "Use skill?" prompt
|
|
214
230
|
// (the Task-90 class; 75.1 scaffolded the skill but missed this).
|
|
215
|
-
|
|
231
|
+
// Task 169 (the v0.4.1 cut-gate find, 2026-06-26): Claude Code 2.1.x changed
|
|
232
|
+
// skill-permission matching — the bare `Skill(memory-write)` rule alone no
|
|
233
|
+
// longer suppressed the "Use skill?" prompt; when the user clicked "allow for
|
|
234
|
+
// this project", Claude Code wrote BOTH `Skill(memory-write)` AND
|
|
235
|
+
// `Skill(memory-write:*)` into settings.local.json. So the kit emits BOTH forms
|
|
236
|
+
// (bare for older CC, `:*` for 2.1.x+) — pending a docs re-verification of the
|
|
237
|
+
// current skill-permission syntax.
|
|
238
|
+
// Task 171 (v0.4.1 cut-gate ground-truth, 2026-06-26): the `mcp__cmk__*` server
|
|
239
|
+
// WILDCARD no longer suppresses the per-tool approval prompt on Claude Code
|
|
240
|
+
// 2.1.x — a DIRECT `mk_remember` call (the model using the MCP tool outside the
|
|
241
|
+
// skill's own `allowed-tools`) prompts "Do you want to proceed with
|
|
242
|
+
// mcp__cmk__mk_remember?", and CC writes the SPECIFIC tool name when allowed.
|
|
243
|
+
// (CC 2.1.x churned permission matching heavily — multiple changelog entries
|
|
244
|
+
// closed wildcard auto-approve holes, e.g. 2.1.145 "permission-prompt bypass".)
|
|
245
|
+
// The wildcard worked on older CC; it doesn't now. So allow-list each SPECIFIC
|
|
246
|
+
// cmk MCP tool (the canonical 11 in MCP_AUTO_APPROVE — shared with the Kiro
|
|
247
|
+
// pre-trust) AND keep `mcp__cmk__*` (harmless + future-proof). This is the same
|
|
248
|
+
// upstream-format-tracking the kit does for the Skill rule (D-209) and Kiro.
|
|
249
|
+
const KIT_ALLOW = [
|
|
250
|
+
'Bash(cmk:*)',
|
|
251
|
+
'Skill(memory-write)',
|
|
252
|
+
'Skill(memory-write:*)',
|
|
253
|
+
'Skill(memory-search)',
|
|
254
|
+
'Skill(memory-search:*)',
|
|
255
|
+
'mcp__cmk__*',
|
|
256
|
+
...MCP_AUTO_APPROVE.map((tool) => `mcp__cmk__${tool}`),
|
|
257
|
+
];
|
|
216
258
|
if (!settings.permissions || typeof settings.permissions !== 'object') {
|
|
217
259
|
settings.permissions = {};
|
|
218
260
|
}
|
|
@@ -225,6 +267,21 @@ export function writeKitHooks(settingsPath) {
|
|
|
225
267
|
}
|
|
226
268
|
}
|
|
227
269
|
|
|
270
|
+
// Task 172: pre-approve the kit's OWN project-scoped `.mcp.json` server so its
|
|
271
|
+
// tools connect without the per-project "approve this MCP server?" prompt.
|
|
272
|
+
// `enabledMcpjsonServers` names specific servers to approve (NOT
|
|
273
|
+
// `enableAllProjectMcpServers`, which would blanket-approve EVERY server in
|
|
274
|
+
// `.mcp.json` — too broad for a kit shipped to others; we vouch only for our
|
|
275
|
+
// own server). This clears the SERVER-approval gate; the PermissionRequest
|
|
276
|
+
// hook above clears the per-TOOL gate. Idempotent + preserves any servers the
|
|
277
|
+
// user already approved.
|
|
278
|
+
if (!Array.isArray(settings.enabledMcpjsonServers)) {
|
|
279
|
+
settings.enabledMcpjsonServers = [];
|
|
280
|
+
}
|
|
281
|
+
if (!settings.enabledMcpjsonServers.includes('cmk')) {
|
|
282
|
+
settings.enabledMcpjsonServers.push('cmk');
|
|
283
|
+
}
|
|
284
|
+
|
|
228
285
|
const after = JSON.stringify(settings);
|
|
229
286
|
const changed = before !== after;
|
|
230
287
|
|
package/src/tier-paths.mjs
CHANGED
|
@@ -9,10 +9,50 @@
|
|
|
9
9
|
// resolveTierRoot({tier, projectRoot, userDir}) → absolute path
|
|
10
10
|
// resolveFactDir(tier, tierRoot) → absolute path to <memory|fragments>
|
|
11
11
|
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
12
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import { dirname, join, resolve } from 'node:path';
|
|
15
15
|
|
|
16
|
+
// Canonicalize a path for comparison: resolve 8.3 short names (Windows
|
|
17
|
+
// `TAMIR~1.BN-`) + symlinks to their real long form, so a short-name path and
|
|
18
|
+
// its long-name twin compare equal. Falls back to `resolve(p)` if the path
|
|
19
|
+
// doesn't exist (realpathSync throws on a missing path). Exported so the project
|
|
20
|
+
// discovery in inject-context.mjs shares ONE implementation (Task 168 — the
|
|
21
|
+
// home-boundary + canonicalize logic must not drift across the two walkers).
|
|
22
|
+
export function canonicalPath(p) {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync.native ? realpathSync.native(p) : realpathSync(p);
|
|
25
|
+
} catch {
|
|
26
|
+
return resolve(p);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Walk up from `cwd` to the nearest ancestor that has one of the `markers`
|
|
32
|
+
* subdirs (a kit-installed project), STOPPING at the home directory — a stray
|
|
33
|
+
* `~/context/` (test debris, or a `cmk` run that scaffolded in home) must NOT be
|
|
34
|
+
* served as a project from an unrelated subdir (Task 168). Returns the discovered
|
|
35
|
+
* root, or `resolve(cwd)` if none found below home.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} cwd starting directory
|
|
38
|
+
* @param {string[]} markers subdir names that mark a project root (e.g.
|
|
39
|
+
* ['context'] or ['context', 'context.local'])
|
|
40
|
+
*/
|
|
41
|
+
export function discoverRootUpward(cwd, markers = ['context']) {
|
|
42
|
+
const home = canonicalPath(homedir());
|
|
43
|
+
let dir = resolve(cwd);
|
|
44
|
+
// Defensive bound: walk no more than 64 ancestors.
|
|
45
|
+
for (let i = 0; i < 64; i++) {
|
|
46
|
+
const atHome = canonicalPath(dir) === home;
|
|
47
|
+
if (!atHome && markers.some((m) => existsSync(join(dir, m)))) return dir;
|
|
48
|
+
const parent = dirname(dir);
|
|
49
|
+
if (parent === dir) break; // reached the filesystem root
|
|
50
|
+
if (atHome) break; // do not climb above $HOME into a stray ancestor context/
|
|
51
|
+
dir = parent;
|
|
52
|
+
}
|
|
53
|
+
return resolve(cwd); // last resort
|
|
54
|
+
}
|
|
55
|
+
|
|
16
56
|
export const VALID_TIERS = new Set(['U', 'P', 'L']);
|
|
17
57
|
|
|
18
58
|
/**
|
|
@@ -47,18 +87,12 @@ export function resolveMcpProjectRoot({ env = process.env, cwd = process.cwd() }
|
|
|
47
87
|
const fromClaude = env.CLAUDE_PROJECT_DIR;
|
|
48
88
|
if (fromClaude && fromClaude.trim() !== '') return resolve(fromClaude);
|
|
49
89
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const parent = dirname(dir);
|
|
57
|
-
if (parent === dir) break; // reached the root
|
|
58
|
-
dir = parent;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return resolve(cwd); // last resort
|
|
90
|
+
// Walk up to the nearest `context/`-bearing project, STOPPING at home (Task 168
|
|
91
|
+
// — a stray `~/context/` must not be served from an unrelated subdir; a real
|
|
92
|
+
// project's context/ lives below home, or via the explicit CLAUDE_PROJECT_DIR
|
|
93
|
+
// handled above). Shared with inject-context's discoverProjectRoot via the
|
|
94
|
+
// single discoverRootUpward implementation.
|
|
95
|
+
return discoverRootUpward(cwd, ['context']);
|
|
62
96
|
}
|
|
63
97
|
|
|
64
98
|
// Matches IDs produced by @lh8ppl/cmk-canonicalize.generateId(). Tier prefix +
|