claude-mem-lite 4.0.3 → 5.0.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +0 -49
- package/README.zh-CN.md +0 -48
- package/adopt-content.mjs +0 -1
- package/cli/common.mjs +0 -17
- package/cli.mjs +19 -3
- package/commands/update.md +4 -11
- package/format-utils.mjs +0 -21
- package/hook-context.mjs +6 -1
- package/hook-handoff.mjs +9 -0
- package/hook-update.mjs +37 -12
- package/hook.mjs +5 -29
- package/hooks/hooks.json +0 -10
- package/install.mjs +24 -666
- package/lib/doctor-drift.mjs +0 -1
- package/lib/fast-summary.mjs +11 -0
- package/lib/frontmatter.mjs +1 -2
- package/lib/hook-prune.mjs +133 -0
- package/lib/hook-stdin.mjs +1 -1
- package/lib/hook-telemetry.mjs +1 -1
- package/lib/metrics.mjs +2 -2
- package/lib/shard-gc.mjs +11 -7
- package/mem-cli.mjs +2 -423
- package/nlp.mjs +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -15
- package/schema.mjs +0 -1
- package/scripts/hook-launcher.mjs +2 -2
- package/scripts/prompt-search-utils.mjs +0 -30
- package/scripts/user-prompt-search.js +3 -125
- package/server.mjs +11 -449
- package/source-files.mjs +11 -28
- package/synonyms.mjs +2 -1
- package/tool-schemas.mjs +0 -80
- package/utils.mjs +5 -13
- package/commands/tools.md +0 -67
- package/install-metadata.mjs +0 -2193
- package/lib/registry-core.mjs +0 -264
- package/registry/preinstalled.json +0 -2419
- package/registry-enricher.mjs +0 -124
- package/registry-github.mjs +0 -86
- package/registry-importer.mjs +0 -569
- package/registry-recommend.mjs +0 -503
- package/registry-retriever.mjs +0 -611
- package/registry-scanner.mjs +0 -261
- package/registry.mjs +0 -665
- package/resource-discovery.mjs +0 -199
- package/scripts/pre-skill-bridge.js +0 -146
package/lib/doctor-drift.mjs
CHANGED
package/lib/fast-summary.mjs
CHANGED
|
@@ -41,6 +41,17 @@ export const FAST_SUMMARY_LIMITS = {
|
|
|
41
41
|
/**
|
|
42
42
|
* The two reads every fast summary is built from: the session's opening prompt, and the
|
|
43
43
|
* titles of its most recent observations.
|
|
44
|
+
*
|
|
45
|
+
* The observation filter is `compressed_into` ONLY, deliberately — it is NOT a half-written
|
|
46
|
+
* `liveObsFilterSql` waiting to be completed. `completed` is the session's own history, and a
|
|
47
|
+
* lesson a later save overturned still happened; dropping it here would misreport the session
|
|
48
|
+
* that did the work. This is the same ruling audit 2026-08-14 F4 made for the sibling field
|
|
49
|
+
* `session_handoffs.completed`, where the scope guard lives in
|
|
50
|
+
* `tests/audit-silent-20260814.test.mjs`. Audit R8 §11.3 proposed adding `superseded_at IS
|
|
51
|
+
* NULL` to both; both were rejected on this reasoning. The filter that DOES belong on a
|
|
52
|
+
* superseded row is the one guarding standing policy re-presented to a later session
|
|
53
|
+
* (`key_decisions`), not history.
|
|
54
|
+
*
|
|
44
55
|
* @returns {{request: string, completed: string}} raw (unscrubbed, untruncated) values
|
|
45
56
|
*/
|
|
46
57
|
export function readFastSummarySource(db, sessionId) {
|
package/lib/frontmatter.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Audit 2026-09-02 P1-16. There were three, and they were not all the same:
|
|
4
4
|
//
|
|
5
|
-
// registry-importer.mjs the shipped one, full
|
|
6
5
|
// scripts/index-managed.mjs byte-identical to it apart from the `export` keyword —
|
|
7
6
|
// 30 lines, the largest duplicate block in the tree
|
|
8
7
|
// scripts/convert-commands.mjs a SIMPLIFIED cut with no `|` / `>` block support and no
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
// scripts reading the same files with different parsers produce different registry rows
|
|
14
13
|
// depending on which one last ran.
|
|
15
14
|
//
|
|
16
|
-
// Zero dependencies on purpose — it is imported by a shipped module
|
|
15
|
+
// Zero dependencies on purpose — it is imported by a shipped module
|
|
17
16
|
// and by two dev scripts, so it must not drag anything into either.
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// lib/hook-prune.mjs — settings.json hook-entry classification and reconciliation.
|
|
2
|
+
//
|
|
3
|
+
// Extracted here because TWO faces need it and a direct import would close a cycle:
|
|
4
|
+
// `install.mjs` owns hook registration, and `hook-update.mjs` (which install.mjs
|
|
5
|
+
// already imports) has to reconcile after a swap. Per CLAUDE.md's rule — logic two
|
|
6
|
+
// faces share moves to `lib/`, the big file keeps only wiring — rather than
|
|
7
|
+
// documenting `install.mjs -> hook-update.mjs -> install.mjs` as an allowed cycle.
|
|
8
|
+
//
|
|
9
|
+
// Zero local imports on purpose: `hook-update.mjs` reaches this from the auto-update
|
|
10
|
+
// path, which must stay cheap and must not drag install.mjs's dependency graph in.
|
|
11
|
+
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Identify a settings hook as one of OURS (to replace on install / strip on uninstall).
|
|
17
|
+
*
|
|
18
|
+
* Must be tight: the old `hook.mjs` + event-word test matched a user's OWN generic hook
|
|
19
|
+
* (`node ~/.config/hook.mjs session-start`) and install/uninstall silently deleted it.
|
|
20
|
+
* The launcher marker (`hook-launcher.mjs`, which every Node hook routes through since
|
|
21
|
+
* v2.84) replaces that clause; the product-name substring (our install-dir / legacy-direct
|
|
22
|
+
* hooks) and the bash prefilters round out the real markers.
|
|
23
|
+
*/
|
|
24
|
+
export function isMemHook(cfg) {
|
|
25
|
+
// `cfg?.hooks`, not `cfg.hooks`: a null entry in a hand-edited settings.json threw a
|
|
26
|
+
// TypeError here, and hook-update.mjs swallows it — so ONE malformed entry silently
|
|
27
|
+
// cancelled the whole reconcile and the dangling hook survived. Degraded, never
|
|
28
|
+
// destructive, but the reconcile is the point. (Found by adversarial review; the same
|
|
29
|
+
// exposure predates this module in `configureHooks`, which now shares this function.)
|
|
30
|
+
if (!cfg?.hooks) return false;
|
|
31
|
+
return cfg.hooks.some((h) => {
|
|
32
|
+
const cmd = h.command || '';
|
|
33
|
+
return (
|
|
34
|
+
cmd.includes('claude-mem-lite') ||
|
|
35
|
+
cmd.includes('hook-launcher.mjs') ||
|
|
36
|
+
cmd.includes('scripts/post-tool-use.sh') ||
|
|
37
|
+
// Same reason post-tool-use.sh is named here: a bash prefilter routes through
|
|
38
|
+
// NO launcher, and the product-name clause only fires when the install dir
|
|
39
|
+
// happens to contain it — which CLAUDE_MEM_DIR can relocate. Without this line
|
|
40
|
+
// an Agent|Task hook in a relocated install survives uninstall and duplicates
|
|
41
|
+
// on reinstall (audit 2026-08-22 P2-5 added the second prefilter).
|
|
42
|
+
cmd.includes('scripts/pre-agent-inject.sh')
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The launcher's ENTRY argument, resolved — or null when this command is not a
|
|
49
|
+
* launcher invocation we own.
|
|
50
|
+
*
|
|
51
|
+
* `nodeHook` writes `node "<INSTALL_DIR>/scripts/hook-launcher.mjs" scripts/<entry>.js`,
|
|
52
|
+
* and the launcher resolves that bare relative token against its own INSTALL_DIR
|
|
53
|
+
* (`hook-launcher.mjs:125`). Both halves matter for orphan detection: the quoted
|
|
54
|
+
* launcher path exists on a healthy install, so a scanner that stops at the first
|
|
55
|
+
* quoted token declares the entry healthy without ever looking at the file that
|
|
56
|
+
* actually runs. That is precisely how the `Skill` hook removal went unseen.
|
|
57
|
+
*
|
|
58
|
+
* Plugin-channel commands are excluded: `${CLAUDE_PLUGIN_ROOT}` is expanded by Claude
|
|
59
|
+
* Code, not by us, so nothing here can resolve them — and hooks/hooks.json owns them.
|
|
60
|
+
*/
|
|
61
|
+
export function launcherEntryPath(cmd, installDir) {
|
|
62
|
+
if (typeof cmd !== 'string' || cmd.includes('${CLAUDE_PLUGIN_ROOT}')) return null;
|
|
63
|
+
if (!cmd.includes('hook-launcher.mjs')) return null;
|
|
64
|
+
// Everything after the quoted launcher path; the entry is its first bare token.
|
|
65
|
+
const tail = cmd.split(/hook-launcher\.mjs"?\s*/)[1];
|
|
66
|
+
if (!tail) return null;
|
|
67
|
+
const entry = tail.split(/\s+/).find((t) => t && !t.startsWith('-'));
|
|
68
|
+
if (!entry) return null;
|
|
69
|
+
if (entry.startsWith('/')) return entry;
|
|
70
|
+
// Resolve against the base the LAUNCHER NAMED IN THIS COMMAND would use
|
|
71
|
+
// (`hook-launcher.mjs:36` — `join(dirname(launcherFile), '..')`), not against the
|
|
72
|
+
// caller's idea of the install dir. The two coincide in production today because both
|
|
73
|
+
// INSTALL_DIR constants are homedir-hardcoded — which is exactly why the coupling is
|
|
74
|
+
// worth removing rather than documenting: the day a caller passes a different
|
|
75
|
+
// targetDir, resolving against it deletes hooks that run fine. `installDir` stays the
|
|
76
|
+
// fallback for a command whose launcher path we could not parse.
|
|
77
|
+
const quoted = cmd.match(/"([^"]*hook-launcher\.mjs)"/);
|
|
78
|
+
const base = quoted ? dirname(dirname(quoted[1])) : installDir;
|
|
79
|
+
return join(base, entry);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Drop settings.json hook entries whose launcher target no longer exists.
|
|
84
|
+
*
|
|
85
|
+
* Auto-update only ever handled hook ADDITIONS. `configureHooks()` strips stale mem
|
|
86
|
+
* entries and rewrites every event, but its only caller is `install()`;
|
|
87
|
+
* `hook-update.mjs` swaps `scripts/` wholesale, so an upgrade that REMOVES a hook
|
|
88
|
+
* script leaves settings.json pointing at a file it just deleted. The launcher then
|
|
89
|
+
* treats each fire as a broken install: two stderr lines and the self-heal marker,
|
|
90
|
+
* for a file that is never coming back. First hit by the `PreToolUse:Skill`
|
|
91
|
+
* removal (R9 review P1-1); plugin-channel installs are unaffected because
|
|
92
|
+
* hooks/hooks.json is replaced wholesale.
|
|
93
|
+
*
|
|
94
|
+
* Narrow by construction — an entry is dropped only when it is ours (`isMemHook`),
|
|
95
|
+
* routes through our launcher, and its resolved target is absent. An entry that
|
|
96
|
+
* cannot be parsed, or whose target exists, is left exactly as found; a foreign
|
|
97
|
+
* tool's dead hook is not ours to remove.
|
|
98
|
+
*
|
|
99
|
+
* Pure: returns a new settings object plus the entry tokens removed, so the caller
|
|
100
|
+
* decides whether to write and can log what changed.
|
|
101
|
+
*
|
|
102
|
+
* @returns {{settings: object, removed: string[]}}
|
|
103
|
+
*/
|
|
104
|
+
export function pruneDanglingMemHooks(settings, installDir) {
|
|
105
|
+
if (!settings?.hooks) return { settings, removed: [] };
|
|
106
|
+
const removed = [];
|
|
107
|
+
const hooks = {};
|
|
108
|
+
for (const [event, configs] of Object.entries(settings.hooks)) {
|
|
109
|
+
if (!Array.isArray(configs)) {
|
|
110
|
+
hooks[event] = configs;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const kept = [];
|
|
114
|
+
for (const cfg of configs) {
|
|
115
|
+
if (!isMemHook(cfg)) {
|
|
116
|
+
kept.push(cfg);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const liveHooks = (cfg.hooks || []).filter((h) => {
|
|
120
|
+
const target = launcherEntryPath(h.command, installDir);
|
|
121
|
+
if (!target || existsSync(target)) return true;
|
|
122
|
+
// Record the token as written, not the resolved path — that is what a
|
|
123
|
+
// reader has to match against settings.json and the release manifest.
|
|
124
|
+
const tail = String(h.command).split(/hook-launcher\.mjs"?\s*/)[1] || '';
|
|
125
|
+
removed.push(tail.split(/\s+/).find((t) => t && !t.startsWith('-')) || target);
|
|
126
|
+
return false;
|
|
127
|
+
});
|
|
128
|
+
if (liveHooks.length > 0) kept.push({ ...cfg, hooks: liveHooks });
|
|
129
|
+
}
|
|
130
|
+
hooks[event] = kept;
|
|
131
|
+
}
|
|
132
|
+
return { settings: { ...settings, hooks }, removed };
|
|
133
|
+
}
|
package/lib/hook-stdin.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// (1.5 s / 262144 / never rejects).
|
|
9
9
|
//
|
|
10
10
|
// UNBOUNDED, three copies of `for await (const chunk of process.stdin) input += chunk`:
|
|
11
|
-
// pre-tool-recall.js,
|
|
11
|
+
// pre-tool-recall.js, post-tool-recall.js. No cap and no timeout.
|
|
12
12
|
// That matters most on `PreToolUse:Write`, whose `tool_input.content` is the ENTIRE file
|
|
13
13
|
// being written: writing a multi-megabyte file made pre-tool-recall buffer all of it and
|
|
14
14
|
// `JSON.parse` all of it, to read `file_path`. The only bound was the host's own 3 s
|
package/lib/hook-telemetry.mjs
CHANGED
|
@@ -113,7 +113,7 @@ export function recordHookError(scope, err, runtimeDir, ctx) {
|
|
|
113
113
|
pruneOldShards(dir);
|
|
114
114
|
|
|
115
115
|
// Every hook script funnels its failures through here — including the
|
|
116
|
-
// STANDALONE ones (scripts/pre-tool-recall.js, scripts/
|
|
116
|
+
// STANDALONE ones (scripts/pre-tool-recall.js, scripts/post-tool-recall.js)
|
|
117
117
|
// that never import hook.mjs and so never reach its dispatch catch. That gap
|
|
118
118
|
// is why the 2026-08-13 outage stayed invisible: 78 of that day's 79 entries
|
|
119
119
|
// were `pre-recall:db-open`, i.e. the ONE path whose errors nothing but this
|
package/lib/metrics.mjs
CHANGED
|
@@ -83,8 +83,8 @@ export function timed(dbDir, event, fn, extra = {}) {
|
|
|
83
83
|
* grows the dir unbounded. 90d keeps a full quarter for aggregate windows while
|
|
84
84
|
* bounding the dir. Runs regardless of the enable flag so a user who toggles metrics
|
|
85
85
|
* OFF still gets old shards cleaned. Best-effort, never throws — called from the
|
|
86
|
-
* SessionStart GC sweep. The sweep itself is `lib/shard-gc.mjs
|
|
87
|
-
*
|
|
86
|
+
* SessionStart GC sweep. The sweep itself is `lib/shard-gc.mjs`; this function is
|
|
87
|
+
* the metrics dir resolution.
|
|
88
88
|
* @returns {number} shards removed
|
|
89
89
|
*/
|
|
90
90
|
export function gcOldMetricShards(dbDir, retainDays = 90) {
|
package/lib/shard-gc.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// lib/shard-gc.mjs — retention sweep for daily JSONL shard directories.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// own
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
3
|
+
// `lib/metrics.mjs` (CLAUDE_MEM_METRICS) appends one `YYYY-MM-DD.jsonl` per day with
|
|
4
|
+
// no GC of its own, so a long-lived install grows that dir unbounded.
|
|
5
|
+
//
|
|
6
|
+
// This module exists because there used to be TWO such sinks — the second was
|
|
7
|
+
// `registry-recommend.mjs`'s shadow log — and the sweep was written twice, line for
|
|
8
|
+
// line, differing only in how the directory is resolved; audit 2026-09-05 P2-3 found
|
|
9
|
+
// it as the largest non-intentional cross-file duplicate. That is the twin-drift class
|
|
10
|
+
// CLAUDE.md names: the retention rule (90 days, the `YYYY-MM-DD.jsonl` name filter) had
|
|
11
|
+
// two homes and could disagree with itself. The skill-registry subsystem was removed in
|
|
12
|
+
// 2026-09 (docs/audits/20260906-145304.md), leaving one caller; the extraction stays
|
|
13
|
+
// because the retention rule is worth owning in one place, not because of the caller count.
|
|
10
14
|
//
|
|
11
15
|
// Directory resolution stays with each caller — it is the only part that differed.
|
|
12
16
|
|