claude-mem-lite 2.63.0 → 2.65.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/cli/fts-check.mjs +1 -1
- package/install.mjs +41 -14
- package/lib/doctor-drift.mjs +1 -0
- package/lib/save-observation.mjs +1 -1
- package/mem-cli.mjs +68 -26
- package/package.json +1 -1
- package/server.mjs +6 -1
package/cli/fts-check.mjs
CHANGED
|
@@ -8,7 +8,7 @@ export function cmdFtsCheck(db, args) {
|
|
|
8
8
|
const { positional } = parseArgs(args);
|
|
9
9
|
const action = positional[0];
|
|
10
10
|
if (!action || !['check', 'rebuild'].includes(action)) {
|
|
11
|
-
fail('[mem] Usage: mem fts-check <check|rebuild>');
|
|
11
|
+
fail('[mem] Usage: claude-mem-lite fts-check <check|rebuild>');
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
|
package/install.mjs
CHANGED
|
@@ -1295,14 +1295,29 @@ async function doctor() {
|
|
|
1295
1295
|
dwarn('Database: not found (will be created)');
|
|
1296
1296
|
}
|
|
1297
1297
|
|
|
1298
|
-
// Check for stale processes
|
|
1298
|
+
// Check for stale processes — extends beyond legacy chroma/worker to
|
|
1299
|
+
// catch MCP launchers / servers from cached old plugin versions. Auto-update
|
|
1300
|
+
// bumps installed_plugins.json but cannot kill the MCP process spawned for
|
|
1301
|
+
// an active session, so v2.60.0/v2.61.0 launchers commonly outlive their
|
|
1302
|
+
// version (recurrent pattern, see #2580 for the gsd analogue). Filtering
|
|
1303
|
+
// strategy: legacy chroma/worker = always stale; cache-path launchers = only
|
|
1304
|
+
// when their version segment ≠ current package.json version; dev-install
|
|
1305
|
+
// paths (no version segment) are never flagged.
|
|
1299
1306
|
try {
|
|
1300
|
-
const procs = execFileSync('pgrep', ['-af', 'chroma|claude-mem.*worker'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim();
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1307
|
+
const procs = execFileSync('pgrep', ['-af', 'chroma|claude-mem-lite.*(scripts/launch|server)\\.mjs|claude-mem.*worker'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim();
|
|
1308
|
+
const lines = procs.split('\n').filter(l => l && !l.includes('pgrep'));
|
|
1309
|
+
let currentVersion = '';
|
|
1310
|
+
try { currentVersion = JSON.parse(readFileSync(join(PROJECT_DIR, 'package.json'), 'utf8')).version; } catch { /* fall through with empty version */ }
|
|
1311
|
+
const stale = lines.filter(l => {
|
|
1312
|
+
if (/chroma|claude-mem.*worker/.test(l)) return true;
|
|
1313
|
+
const m = l.match(/claude-mem-lite\/(\d+\.\d+\.\d+)\/(scripts\/launch|server)\.mjs/);
|
|
1314
|
+
return m && currentVersion && m[1] !== currentVersion;
|
|
1315
|
+
});
|
|
1316
|
+
if (stale.length > 0) {
|
|
1317
|
+
warn(`Old processes running${currentVersion ? ` (current: v${currentVersion})` : ''}:\n ` + stale.join('\n '));
|
|
1305
1318
|
issues++;
|
|
1319
|
+
} else {
|
|
1320
|
+
ok('No stale processes');
|
|
1306
1321
|
}
|
|
1307
1322
|
} catch {
|
|
1308
1323
|
ok('No stale processes');
|
|
@@ -1333,19 +1348,31 @@ async function doctor() {
|
|
|
1333
1348
|
}
|
|
1334
1349
|
|
|
1335
1350
|
// Dev drift: in dev-mode installs, all SOURCE_FILES entries should be
|
|
1336
|
-
// symlinks. A plain file means an earlier install (or manual cp) copied it
|
|
1337
|
-
//
|
|
1338
|
-
//
|
|
1351
|
+
// symlinks. A plain file means an earlier install (or manual cp) copied it
|
|
1352
|
+
// (edits in the repo won't propagate). A missing entry (neither symlink nor
|
|
1353
|
+
// plain) means an earlier install never wrote the file — same divergence
|
|
1354
|
+
// class. Per #8043: "is this file present ≠ is this install consistent" —
|
|
1355
|
+
// missing is tracked separately by checkDevDrift but the caller MUST surface
|
|
1356
|
+
// it to honour #8268's "gate the all-green string on every counter" rule.
|
|
1339
1357
|
try {
|
|
1340
1358
|
const { checkDevDrift } = await import('./lib/doctor-drift.mjs');
|
|
1341
1359
|
const r = checkDevDrift(INSTALL_DIR, SOURCE_FILES);
|
|
1342
|
-
if (r.drift) {
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1360
|
+
if (r.drift || (r.devMode && r.missingCount > 0)) {
|
|
1361
|
+
const parts = [];
|
|
1362
|
+
if (r.plainCount > 0) {
|
|
1363
|
+
const names = r.plainFiles.slice(0, 5).join(', ');
|
|
1364
|
+
const suffix = r.plainCount > 5 ? ` +${r.plainCount - 5} more` : '';
|
|
1365
|
+
parts.push(`${r.plainCount} non-symlink: ${names}${suffix}`);
|
|
1366
|
+
}
|
|
1367
|
+
if (r.missingCount > 0) {
|
|
1368
|
+
const names = r.missingFiles.join(', ');
|
|
1369
|
+
const suffix = r.missingCount > r.missingFiles.length ? ` +${r.missingCount - r.missingFiles.length} more` : '';
|
|
1370
|
+
parts.push(`${r.missingCount} missing: ${names}${suffix}`);
|
|
1371
|
+
}
|
|
1372
|
+
warn(`Dev drift: ${parts.join('; ')} (re-run: node ${join(PROJECT_DIR, 'install.mjs')} install --dev)`);
|
|
1346
1373
|
issues++;
|
|
1347
1374
|
} else if (r.devMode) {
|
|
1348
|
-
ok(`Dev drift: clean (${r.symlinkCount} symlinks, 0 plain)`);
|
|
1375
|
+
ok(`Dev drift: clean (${r.symlinkCount} symlinks, 0 plain, 0 missing)`);
|
|
1349
1376
|
}
|
|
1350
1377
|
// Prod (all plain) install: no message — dev-drift is a dev-only concern.
|
|
1351
1378
|
} catch (e) {
|
package/lib/doctor-drift.mjs
CHANGED
package/lib/save-observation.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Shared "save one observation" pipeline — used by both mem-cli.mjs::cmdSave
|
|
2
|
-
// (CLI `mem save`) and server.mjs::mem_save (MCP tool).
|
|
2
|
+
// (CLI `claude-mem-lite save`) and server.mjs::mem_save (MCP tool).
|
|
3
3
|
//
|
|
4
4
|
// Pre-extraction (v2.60.0) the same dedup → scrub → minhash → CJK-bigram →
|
|
5
5
|
// transactional INSERT block lived inline in both call sites (~110 lines × 2,
|
package/mem-cli.mjs
CHANGED
|
@@ -34,12 +34,17 @@ function cmdSearch(db, args) {
|
|
|
34
34
|
const { positional, flags } = parseArgs(args);
|
|
35
35
|
const query = positional.join(' ');
|
|
36
36
|
if (!query) {
|
|
37
|
-
fail('[mem] Usage: mem search <query> [--type TYPE] [--source SOURCE] [--limit N] [--project P] [--from DATE] [--to DATE] [--importance N] [--branch B] [--offset N] [--sort relevance|time|importance] [--include-noise]');
|
|
37
|
+
fail('[mem] Usage: claude-mem-lite search <query> [--type TYPE] [--source SOURCE] [--limit N] [--project P] [--from DATE] [--to DATE] [--importance N] [--branch B] [--offset N] [--sort relevance|time|importance] [--include-noise]');
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const rawLimit = flags.limit !== undefined ? parseInt(flags.limit, 10) : NaN;
|
|
42
|
-
|
|
42
|
+
// Distinguish missing/non-integer (use default) from non-positive (silently clamping to 1
|
|
43
|
+
// produced confusing "Found 1 of 44 result" output for --limit 0/-N — warn instead).
|
|
44
|
+
if (flags.limit !== undefined && (!Number.isInteger(rawLimit) || rawLimit < 1)) {
|
|
45
|
+
process.stderr.write(`[mem] Invalid --limit "${flags.limit}" (must be a positive integer); using default 20\n`);
|
|
46
|
+
}
|
|
47
|
+
const limit = Number.isInteger(rawLimit) && rawLimit >= 1 ? rawLimit : 20;
|
|
43
48
|
const type = flags.type || null;
|
|
44
49
|
const validObsTypes = new Set(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']);
|
|
45
50
|
if (type && !validObsTypes.has(type)) {
|
|
@@ -53,13 +58,22 @@ function cmdSearch(db, args) {
|
|
|
53
58
|
if (dateTo && flags.to && /^\d{4}-\d{2}-\d{2}$/.test(flags.to)) dateTo += 86400000 - 1;
|
|
54
59
|
if (flags.from && isNaN(dateFrom)) { fail(`[mem] Invalid --from date: "${flags.from}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
55
60
|
if (flags.to && isNaN(dateTo)) { fail(`[mem] Invalid --to date: "${flags.to}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
61
|
+
// Inverted range silently returns 0 rows; warn so users see the cause, don't error
|
|
62
|
+
// (a deliberate "search for nothing in this window" is not malformed input).
|
|
63
|
+
if (dateFrom !== null && dateTo !== null && dateFrom > dateTo) {
|
|
64
|
+
process.stderr.write(`[mem] Note: --from "${flags.from}" is after --to "${flags.to}"; this range is empty\n`);
|
|
65
|
+
}
|
|
56
66
|
const minImportance = flags.importance !== undefined ? parseInt(flags.importance, 10) : null;
|
|
57
67
|
if (minImportance !== null && (isNaN(minImportance) || minImportance < 1 || minImportance > 3)) {
|
|
58
68
|
fail(`[mem] Invalid --importance "${flags.importance}". Must be 1, 2, or 3.`);
|
|
59
69
|
return;
|
|
60
70
|
}
|
|
61
71
|
const branch = flags.branch || null;
|
|
62
|
-
const
|
|
72
|
+
const rawOffset = flags.offset !== undefined ? parseInt(flags.offset, 10) : NaN;
|
|
73
|
+
if (flags.offset !== undefined && (!Number.isInteger(rawOffset) || rawOffset < 0)) {
|
|
74
|
+
process.stderr.write(`[mem] Invalid --offset "${flags.offset}" (must be a non-negative integer); using 0\n`);
|
|
75
|
+
}
|
|
76
|
+
const offset = Number.isInteger(rawOffset) && rawOffset >= 0 ? rawOffset : 0;
|
|
63
77
|
const tier = flags.tier || null;
|
|
64
78
|
if (tier && !['working', 'active', 'archive'].includes(tier)) {
|
|
65
79
|
fail(`[mem] Invalid --tier "${tier}". Use: working, active, archive`);
|
|
@@ -338,7 +352,9 @@ function cmdSearch(db, args) {
|
|
|
338
352
|
}
|
|
339
353
|
|
|
340
354
|
const countLabel = total > paged.length ? `${paged.length} of ${total}` : `${paged.length}`;
|
|
341
|
-
|
|
355
|
+
// Pluralize on total — "Found 1 of 44 result" reads wrong; the population (44) drives
|
|
356
|
+
// grammatical number, not the page slice (1).
|
|
357
|
+
out(`[mem] Found ${countLabel} result${total !== 1 ? 's' : ''} for "${query}"${fallbackHint}:${hasMixed ? ' (# observation, S# session, P# prompt)' : ''}`);
|
|
342
358
|
for (const r of paged) {
|
|
343
359
|
const timeStr = showTime && r.created_at_epoch ? ` (${relativeTime(r.created_at_epoch)})` : '';
|
|
344
360
|
if (r._source === 'session') {
|
|
@@ -400,13 +416,16 @@ function cmdRecall(db, args) {
|
|
|
400
416
|
const { positional, flags } = parseArgs(args);
|
|
401
417
|
const file = positional.join(' ');
|
|
402
418
|
if (!file) {
|
|
403
|
-
fail('[mem] Usage: mem recall <file> [--limit N] [--include-noise]');
|
|
419
|
+
fail('[mem] Usage: claude-mem-lite recall <file> [--limit N] [--include-noise]');
|
|
404
420
|
return;
|
|
405
421
|
}
|
|
406
422
|
|
|
407
423
|
const filename = basename(file);
|
|
408
424
|
const rawLimit = flags.limit !== undefined ? parseInt(flags.limit, 10) : NaN;
|
|
409
|
-
|
|
425
|
+
if (flags.limit !== undefined && (!Number.isInteger(rawLimit) || rawLimit < 1)) {
|
|
426
|
+
process.stderr.write(`[mem] Invalid --limit "${flags.limit}" (must be a positive integer); using default 10\n`);
|
|
427
|
+
}
|
|
428
|
+
const limit = Number.isInteger(rawLimit) && rawLimit >= 1 ? rawLimit : 10;
|
|
410
429
|
const includeNoise = flags['include-noise'] === true || flags['include-noise'] === 'true';
|
|
411
430
|
|
|
412
431
|
// Search via observation_files junction table for indexed filename lookups
|
|
@@ -530,7 +549,7 @@ function cmdGet(db, args) {
|
|
|
530
549
|
const { positional, flags } = parseArgs(args);
|
|
531
550
|
const idStr = positional.join(',');
|
|
532
551
|
if (!idStr) {
|
|
533
|
-
fail('[mem] Usage: mem get <id1,id2,...> [--source obs|session|prompt] [--fields f1,f2,...]\n' +
|
|
552
|
+
fail('[mem] Usage: claude-mem-lite get <id1,id2,...> [--source obs|session|prompt] [--fields f1,f2,...]\n' +
|
|
534
553
|
' IDs accept prefix from search output: #123 (obs), P#123 (prompt), S#123 (session).');
|
|
535
554
|
return;
|
|
536
555
|
}
|
|
@@ -602,8 +621,19 @@ function cmdGet(db, args) {
|
|
|
602
621
|
|
|
603
622
|
function cmdTimeline(db, args) {
|
|
604
623
|
const { positional, flags } = parseArgs(args);
|
|
605
|
-
|
|
606
|
-
|
|
624
|
+
// parseInt('-5') === -5 is truthy, so `|| 5` doesn't rescue negative input.
|
|
625
|
+
// Match cmdSearch's warn-then-default pattern for consistency across CLI flags.
|
|
626
|
+
const parseWindow = (label, raw) => {
|
|
627
|
+
if (raw === undefined) return 5;
|
|
628
|
+
const n = parseInt(raw, 10);
|
|
629
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
630
|
+
process.stderr.write(`[mem] Invalid --${label} "${raw}" (must be a non-negative integer); using default 5\n`);
|
|
631
|
+
return 5;
|
|
632
|
+
}
|
|
633
|
+
return n;
|
|
634
|
+
};
|
|
635
|
+
const before = parseWindow('before', flags.before);
|
|
636
|
+
const after = parseWindow('after', flags.after);
|
|
607
637
|
const project = flags.project ? resolveProject(db, flags.project) : null;
|
|
608
638
|
|
|
609
639
|
// Parse --anchor, accepting P#/S#/# prefix so callers can paste search-result IDs verbatim.
|
|
@@ -785,7 +815,7 @@ function cmdSave(db, args) {
|
|
|
785
815
|
const { positional, flags } = parseArgs(args);
|
|
786
816
|
const text = positional.join(' ');
|
|
787
817
|
if (!text) {
|
|
788
|
-
fail('[mem] Usage: mem save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2] [--lesson T]');
|
|
818
|
+
fail('[mem] Usage: claude-mem-lite save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2] [--lesson T]');
|
|
789
819
|
return;
|
|
790
820
|
}
|
|
791
821
|
|
|
@@ -967,6 +997,9 @@ async function cmdStats(db, args) {
|
|
|
967
997
|
const compressedCount = db.prepare(
|
|
968
998
|
`SELECT COUNT(*) as c FROM observations WHERE compressed_into IS NOT NULL ${projectFilter}`
|
|
969
999
|
).get(...baseParams);
|
|
1000
|
+
const supersededOnlyCount = db.prepare(
|
|
1001
|
+
`SELECT COUNT(*) as c FROM observations WHERE superseded_at IS NOT NULL AND compressed_into IS NULL ${projectFilter}`
|
|
1002
|
+
).get(...baseParams);
|
|
970
1003
|
|
|
971
1004
|
// Tier distribution (aligned with MCP mem_stats)
|
|
972
1005
|
const tierCtx = { now, currentProject: project || inferProject(), currentSessionId: '' };
|
|
@@ -1005,7 +1038,11 @@ async function cmdStats(db, args) {
|
|
|
1005
1038
|
out(` Compressed: ${compressedCount.c}`);
|
|
1006
1039
|
if (noiseRatio > 0.6) out(' ⚠️ High noise ratio — consider running mem compress');
|
|
1007
1040
|
out('');
|
|
1008
|
-
|
|
1041
|
+
// Tier counts only live (uncompressed, non-superseded) observations — surface the
|
|
1042
|
+
// full decomposition so live + compressed + superseded = Total adds up cleanly.
|
|
1043
|
+
const tierTotal = (tierMap.working ?? 0) + (tierMap.active ?? 0) + (tierMap.archive ?? 0);
|
|
1044
|
+
const supersededLabel = supersededOnlyCount.c > 0 ? ` + ${supersededOnlyCount.c} superseded` : '';
|
|
1045
|
+
out(`Tier distribution (live ${tierTotal}, excludes ${compressedCount.c} compressed${supersededLabel}):`);
|
|
1009
1046
|
out(` 🔴 Working: ${tierMap.working ?? 0} | 🟡 Active: ${tierMap.active ?? 0} | 🔵 Archive: ${tierMap.archive ?? 0}`);
|
|
1010
1047
|
}
|
|
1011
1048
|
|
|
@@ -1137,7 +1174,7 @@ function cmdDelete(db, args) {
|
|
|
1137
1174
|
const { positional, flags } = parseArgs(args);
|
|
1138
1175
|
const idStr = positional.join(',');
|
|
1139
1176
|
if (!idStr) {
|
|
1140
|
-
fail('[mem] Usage: mem delete <id1,id2,...> [--confirm]');
|
|
1177
|
+
fail('[mem] Usage: claude-mem-lite delete <id1,id2,...> [--confirm]');
|
|
1141
1178
|
return;
|
|
1142
1179
|
}
|
|
1143
1180
|
|
|
@@ -1147,7 +1184,7 @@ function cmdDelete(db, args) {
|
|
|
1147
1184
|
const nonObs = tokens.filter(t => /^[PpSs]#?\d+$/.test(t));
|
|
1148
1185
|
if (nonObs.length > 0) {
|
|
1149
1186
|
fail(`[mem] delete only works on observations. Rejected: ${nonObs.join(', ')}. ` +
|
|
1150
|
-
`Prompts and sessions are append-only — inspect with \`mem get P#N --source prompt\` / \`--source session\`.`);
|
|
1187
|
+
`Prompts and sessions are append-only — inspect with \`claude-mem-lite get P#N --source prompt\` / \`--source session\`.`);
|
|
1151
1188
|
return;
|
|
1152
1189
|
}
|
|
1153
1190
|
const ids = tokens.map(t => {
|
|
@@ -1215,7 +1252,7 @@ function cmdUpdate(db, args) {
|
|
|
1215
1252
|
const parsed = raw ? parseIdToken(raw) : null;
|
|
1216
1253
|
const id = parsed && parsed.source === null ? parsed.id : parseInt(raw, 10);
|
|
1217
1254
|
if (!id || isNaN(id)) {
|
|
1218
|
-
fail('[mem] Usage: mem update <id> [--title T] [--type T] [--importance N] [--lesson T] [--narrative T] [--concepts T]');
|
|
1255
|
+
fail('[mem] Usage: claude-mem-lite update <id> [--title T] [--type T] [--importance N] [--lesson T] [--narrative T] [--concepts T]');
|
|
1219
1256
|
return;
|
|
1220
1257
|
}
|
|
1221
1258
|
|
|
@@ -1297,16 +1334,21 @@ function cmdExport(db, args) {
|
|
|
1297
1334
|
const project = flags.project ? resolveProject(db, flags.project) : null;
|
|
1298
1335
|
if (project) { wheres.push('project = ?'); params.push(project); }
|
|
1299
1336
|
if (flags.type) { wheres.push('type = ?'); params.push(flags.type); }
|
|
1337
|
+
let exportFromEpoch = null;
|
|
1338
|
+
let exportToEpoch = null;
|
|
1300
1339
|
if (flags.from) {
|
|
1301
|
-
|
|
1302
|
-
if (isNaN(
|
|
1303
|
-
wheres.push('created_at_epoch >= ?'); params.push(
|
|
1340
|
+
exportFromEpoch = new Date(flags.from).getTime();
|
|
1341
|
+
if (isNaN(exportFromEpoch)) { fail(`[mem] Invalid --from date: "${flags.from}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
1342
|
+
wheres.push('created_at_epoch >= ?'); params.push(exportFromEpoch);
|
|
1304
1343
|
}
|
|
1305
1344
|
if (flags.to) {
|
|
1306
|
-
|
|
1307
|
-
if (isNaN(
|
|
1308
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(flags.to))
|
|
1309
|
-
wheres.push('created_at_epoch <= ?'); params.push(
|
|
1345
|
+
exportToEpoch = new Date(flags.to).getTime();
|
|
1346
|
+
if (isNaN(exportToEpoch)) { fail(`[mem] Invalid --to date: "${flags.to}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
1347
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(flags.to)) exportToEpoch += 86400000 - 1;
|
|
1348
|
+
wheres.push('created_at_epoch <= ?'); params.push(exportToEpoch);
|
|
1349
|
+
}
|
|
1350
|
+
if (exportFromEpoch !== null && exportToEpoch !== null && exportFromEpoch > exportToEpoch) {
|
|
1351
|
+
process.stderr.write(`[mem] Note: --from "${flags.from}" is after --to "${flags.to}"; this range is empty\n`);
|
|
1310
1352
|
}
|
|
1311
1353
|
|
|
1312
1354
|
const rawLimit = flags.limit !== undefined ? parseInt(flags.limit, 10) : NaN;
|
|
@@ -1442,7 +1484,7 @@ function cmdMaintain(db, args) {
|
|
|
1442
1484
|
const { positional, flags } = parseArgs(args);
|
|
1443
1485
|
const action = positional[0];
|
|
1444
1486
|
if (!action || !['scan', 'execute'].includes(action)) {
|
|
1445
|
-
fail(
|
|
1487
|
+
fail("[mem] Usage: claude-mem-lite maintain <scan|execute> [--ops cleanup,decay,boost,dedup,purge_stale,rebuild_vectors] [--project P] [--retain-days N] [--merge-ids keepId:removeId,...] — 'scan' previews, 'execute' applies.");
|
|
1446
1488
|
return;
|
|
1447
1489
|
}
|
|
1448
1490
|
|
|
@@ -1722,7 +1764,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1722
1764
|
const { positional, flags } = parseArgs(args);
|
|
1723
1765
|
const action = positional[0];
|
|
1724
1766
|
if (!action || !['list', 'stats', 'search', 'import', 'remove', 'reindex'].includes(action)) {
|
|
1725
|
-
fail('[mem] Usage: mem registry <list|stats|search|import|remove|reindex> [--type skill|agent] [--query Q] [--name N] [--resource-type T]');
|
|
1767
|
+
fail('[mem] Usage: claude-mem-lite registry <list|stats|search|import|remove|reindex> [--type skill|agent] [--query Q] [--name N] [--resource-type T]');
|
|
1726
1768
|
return;
|
|
1727
1769
|
}
|
|
1728
1770
|
|
|
@@ -1738,7 +1780,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1738
1780
|
try {
|
|
1739
1781
|
if (action === 'search') {
|
|
1740
1782
|
const query = flags.query || positional.slice(1).join(' ');
|
|
1741
|
-
if (!query) { fail('[mem] Usage: mem registry search <query> [--type skill|agent] [--category C] [--quality Q]'); return; }
|
|
1783
|
+
if (!query) { fail('[mem] Usage: claude-mem-lite registry search <query> [--type skill|agent] [--category C] [--quality Q]'); return; }
|
|
1742
1784
|
let results = searchResources(rdb, query, {
|
|
1743
1785
|
type: flags.type || undefined,
|
|
1744
1786
|
limit: (flags.category || flags.quality) ? 20 : 10,
|
|
@@ -1828,7 +1870,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1828
1870
|
if (action === 'import') {
|
|
1829
1871
|
const name = flags.name;
|
|
1830
1872
|
const resourceType = flags['resource-type'];
|
|
1831
|
-
if (!name || !resourceType) { fail('[mem] Usage: mem registry import --name N --resource-type skill|agent [--invocation-name I] [--capability-summary S]'); return; }
|
|
1873
|
+
if (!name || !resourceType) { fail('[mem] Usage: claude-mem-lite registry import --name N --resource-type skill|agent [--invocation-name I] [--capability-summary S]'); return; }
|
|
1832
1874
|
const fields = { name, type: resourceType, status: 'active', source: flags.source || 'user' };
|
|
1833
1875
|
for (const f of ['repo-url', 'local-path', 'invocation-name', 'intent-tags', 'domain-tags', 'trigger-patterns', 'capability-summary', 'keywords', 'tech-stack', 'use-cases']) {
|
|
1834
1876
|
const camel = f.replace(/-([a-z])/g, (_, c) => '_' + c);
|
|
@@ -1849,7 +1891,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1849
1891
|
if (action === 'remove') {
|
|
1850
1892
|
const name = flags.name;
|
|
1851
1893
|
const resourceType = flags['resource-type'];
|
|
1852
|
-
if (!name || !resourceType) { fail('[mem] Usage: mem registry remove --name N --resource-type skill|agent'); return; }
|
|
1894
|
+
if (!name || !resourceType) { fail('[mem] Usage: claude-mem-lite registry remove --name N --resource-type skill|agent'); return; }
|
|
1853
1895
|
const result = rdb.prepare('DELETE FROM resources WHERE type = ? AND name = ?').run(resourceType, name);
|
|
1854
1896
|
out(result.changes > 0 ? `[mem] Removed: ${resourceType}:${name}` : '[mem] Not found.');
|
|
1855
1897
|
return;
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -1005,6 +1005,9 @@ server.registerTool(
|
|
|
1005
1005
|
const compressedCount = db.prepare(`
|
|
1006
1006
|
SELECT COUNT(*) as c FROM observations WHERE compressed_into IS NOT NULL ${projectFilter}
|
|
1007
1007
|
`).get(...baseParams);
|
|
1008
|
+
const supersededOnlyCount = db.prepare(`
|
|
1009
|
+
SELECT COUNT(*) as c FROM observations WHERE superseded_at IS NOT NULL AND compressed_into IS NULL ${projectFilter}
|
|
1010
|
+
`).get(...baseParams);
|
|
1008
1011
|
|
|
1009
1012
|
// Tier distribution
|
|
1010
1013
|
const tierCtx = { now: Date.now(), currentProject: args.project || inferProject(), currentSessionId: '' };
|
|
@@ -1038,7 +1041,9 @@ server.registerTool(
|
|
|
1038
1041
|
` Compressed: ${compressedCount.c}`,
|
|
1039
1042
|
...(noiseRatio > 0.6 ? [' ⚠️ High noise ratio — consider running mem_compress'] : []),
|
|
1040
1043
|
'',
|
|
1041
|
-
|
|
1044
|
+
// Tier counts only live (uncompressed, non-superseded) observations — surface
|
|
1045
|
+
// the full decomposition so live + compressed + superseded = Total adds up cleanly.
|
|
1046
|
+
`Tier distribution (live ${(tierMap.working ?? 0) + (tierMap.active ?? 0) + (tierMap.archive ?? 0)}, excludes ${compressedCount.c} compressed${supersededOnlyCount.c > 0 ? ` + ${supersededOnlyCount.c} superseded` : ''}):`,
|
|
1042
1047
|
` 🔴 Working: ${tierMap.working ?? 0} | 🟡 Active: ${tierMap.active ?? 0} | 🔵 Archive: ${tierMap.archive ?? 0}`,
|
|
1043
1048
|
];
|
|
1044
1049
|
|