claude-mem-lite 2.62.1 → 2.63.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/install.mjs +49 -21
- package/mem-cli.mjs +27 -2
- package/package.json +2 -1
- package/search-engine.mjs +5 -4
- package/server.mjs +9 -2
package/install.mjs
CHANGED
|
@@ -280,6 +280,25 @@ function ok(msg) { console.log(` ✓ ${msg}`); }
|
|
|
280
280
|
function warn(msg) { console.log(` ⚠ ${msg}`); }
|
|
281
281
|
function fail(msg) { console.log(` ✗ ${msg}`); }
|
|
282
282
|
|
|
283
|
+
// Pure JSON-version field bumper for the release pipeline. Reads `filePath`,
|
|
284
|
+
// walks `keyPath` (e.g. `['version']` or `['plugins', 0, 'version']`), and
|
|
285
|
+
// rewrites only when the new value differs. Returns `{ changed, prev }` so
|
|
286
|
+
// callers can log "X → Y" with the captured-before-mutation value — pre-2.63.0
|
|
287
|
+
// the plugin.json branch in syncVersions logged "Y → Y" because it read the
|
|
288
|
+
// field after assignment.
|
|
289
|
+
export function bumpJsonField(filePath, keyPath, newVal) {
|
|
290
|
+
const json = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
291
|
+
let parent = json;
|
|
292
|
+
for (let i = 0; i < keyPath.length - 1; i++) parent = parent?.[keyPath[i]];
|
|
293
|
+
if (!parent) return { changed: false, prev: undefined };
|
|
294
|
+
const lastKey = keyPath[keyPath.length - 1];
|
|
295
|
+
const prev = parent[lastKey];
|
|
296
|
+
if (prev === newVal) return { changed: false, prev };
|
|
297
|
+
parent[lastKey] = newVal;
|
|
298
|
+
writeFileSync(filePath, JSON.stringify(json, null, 2) + '\n');
|
|
299
|
+
return { changed: true, prev };
|
|
300
|
+
}
|
|
301
|
+
|
|
283
302
|
// Doctor's final summary line. Pure function so the 4-way contract
|
|
284
303
|
// (clean / warnings-only / issues / mixed) is unit-testable without spinning
|
|
285
304
|
// up the full doctor pipeline. `issues` are ✗-level (action required);
|
|
@@ -1593,34 +1612,19 @@ function syncVersions() {
|
|
|
1593
1612
|
const version = pkg.version;
|
|
1594
1613
|
log(`package.json version: ${version}`);
|
|
1595
1614
|
|
|
1596
|
-
// Sync plugin.json
|
|
1597
1615
|
const pluginJsonPath = join(PROJECT_DIR, '.claude-plugin', 'plugin.json');
|
|
1598
1616
|
if (existsSync(pluginJsonPath)) {
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
pluginJson.version = version;
|
|
1602
|
-
writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + '\n');
|
|
1603
|
-
ok(`plugin.json: ${pluginJson.version} → ${version}`);
|
|
1604
|
-
} else {
|
|
1605
|
-
ok(`plugin.json: already ${version}`);
|
|
1606
|
-
}
|
|
1617
|
+
const r = bumpJsonField(pluginJsonPath, ['version'], version);
|
|
1618
|
+
ok(r.changed ? `plugin.json: ${r.prev} → ${version}` : `plugin.json: already ${version}`);
|
|
1607
1619
|
} else {
|
|
1608
1620
|
warn('plugin.json not found');
|
|
1609
1621
|
}
|
|
1610
1622
|
|
|
1611
|
-
// Sync marketplace.json
|
|
1612
1623
|
const marketJsonPath = join(PROJECT_DIR, '.claude-plugin', 'marketplace.json');
|
|
1613
1624
|
if (existsSync(marketJsonPath)) {
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const prev = plugin.version;
|
|
1618
|
-
plugin.version = version;
|
|
1619
|
-
writeFileSync(marketJsonPath, JSON.stringify(marketJson, null, 2) + '\n');
|
|
1620
|
-
ok(`marketplace.json: ${prev} → ${version}`);
|
|
1621
|
-
} else if (plugin) {
|
|
1622
|
-
ok(`marketplace.json: already ${version}`);
|
|
1623
|
-
}
|
|
1625
|
+
const r = bumpJsonField(marketJsonPath, ['plugins', 0, 'version'], version);
|
|
1626
|
+
if (r.prev === undefined) warn('marketplace.json: plugins[0] not found');
|
|
1627
|
+
else ok(r.changed ? `marketplace.json: ${r.prev} → ${version}` : `marketplace.json: already ${version}`);
|
|
1624
1628
|
} else {
|
|
1625
1629
|
warn('marketplace.json not found');
|
|
1626
1630
|
}
|
|
@@ -1649,6 +1653,29 @@ function syncVersions() {
|
|
|
1649
1653
|
console.log('');
|
|
1650
1654
|
}
|
|
1651
1655
|
|
|
1656
|
+
// Regenerate package-lock.json via npm@10.9.2 to guarantee CI parity. The
|
|
1657
|
+
// drift this prevents: `npm install --package-lock-only` on npm@11+ silently
|
|
1658
|
+
// strips top-level `@emnapi/core` + `@emnapi/runtime` entries when those are
|
|
1659
|
+
// transitive deps of platform-optional bindings (e.g. `@oxc-parser/binding-*`
|
|
1660
|
+
// from knip), and CI's bundled npm@10 (Node 22 default in GitHub Actions)
|
|
1661
|
+
// then refuses `npm ci` with EUSAGE. Same recipe bit twice (#8271 / 2.58.2 /
|
|
1662
|
+
// 2.62.1) before this guard. The packageManager field in package.json
|
|
1663
|
+
// declares the same version for corepack-aware tooling. Network cost: ~5-30s
|
|
1664
|
+
// per release; release cadence makes this acceptable.
|
|
1665
|
+
function regenerateLockfile() {
|
|
1666
|
+
console.log('\nclaude-mem-lite release — regenerate lockfile (npm@10.9.2)\n');
|
|
1667
|
+
try {
|
|
1668
|
+
execFileSync('npx', ['--yes', 'npm@10.9.2', 'install'], {
|
|
1669
|
+
stdio: 'inherit',
|
|
1670
|
+
cwd: PROJECT_DIR,
|
|
1671
|
+
});
|
|
1672
|
+
ok('lockfile regenerated');
|
|
1673
|
+
} catch (e) {
|
|
1674
|
+
fail('lockfile regen failed: ' + e.message);
|
|
1675
|
+
throw e;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1652
1679
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
1653
1680
|
|
|
1654
1681
|
export async function main(argv = process.argv.slice(2)) {
|
|
@@ -1680,6 +1707,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1680
1707
|
break;
|
|
1681
1708
|
case 'release':
|
|
1682
1709
|
syncVersions();
|
|
1710
|
+
if (!flags.has('--no-lock')) regenerateLockfile();
|
|
1683
1711
|
break;
|
|
1684
1712
|
default:
|
|
1685
1713
|
if (IS_NPX) {
|
|
@@ -1699,7 +1727,7 @@ Usage:
|
|
|
1699
1727
|
node install.mjs cleanup Remove stale temp/staging files
|
|
1700
1728
|
node install.mjs cleanup-hooks Remove only claude-mem-lite hooks from settings.json
|
|
1701
1729
|
node install.mjs self-update Check for and install updates
|
|
1702
|
-
node install.mjs release Sync
|
|
1730
|
+
node install.mjs release Sync versions (plugin/marketplace/CLAUDE.md) + regen lockfile via npm@10.9.2 (use --no-lock to skip lock regen)
|
|
1703
1731
|
|
|
1704
1732
|
npx claude-mem-lite Install via npx (one-liner)
|
|
1705
1733
|
`);
|
package/mem-cli.mjs
CHANGED
|
@@ -447,6 +447,25 @@ function cmdRecall(db, args) {
|
|
|
447
447
|
|
|
448
448
|
const OBS_FIELDS = ['id', 'type', 'title', 'subtitle', 'narrative', 'text', 'facts', 'concepts', 'lesson_learned', 'search_aliases', 'files_read', 'files_modified', 'project', 'created_at', 'memory_session_id', 'prompt_number', 'importance', 'related_ids', 'access_count', 'branch', 'superseded_at', 'superseded_by', 'last_accessed_at'];
|
|
449
449
|
|
|
450
|
+
// Integer-typed time-epoch fields on the observations table that the `get`
|
|
451
|
+
// command renders. Callers expect raw ms (audit) AND a relative-time hint
|
|
452
|
+
// (human-scan), so formatObsFieldValue emits both. Other epoch fields like
|
|
453
|
+
// `created_at_epoch` / `optimized_at` / `last_injected_at` aren't in
|
|
454
|
+
// OBS_FIELDS so they don't surface via `get`.
|
|
455
|
+
export const OBS_TIME_FIELDS = ['superseded_at', 'last_accessed_at'];
|
|
456
|
+
|
|
457
|
+
// Pure formatter — null/undefined/non-time pass through; time fields on
|
|
458
|
+
// integer values render as `<raw> (<relative>)` mirroring the convention
|
|
459
|
+
// already used by `recent` / `timeline` / `recall`. Pre-2.63.0 the get
|
|
460
|
+
// path printed bare ms (e.g. `last_accessed_at: 1778357330957`).
|
|
461
|
+
export function formatObsFieldValue(field, val) {
|
|
462
|
+
if (val === null || val === undefined) return val;
|
|
463
|
+
if (OBS_TIME_FIELDS.includes(field) && typeof val === 'number') {
|
|
464
|
+
return `${val} (${relativeTime(val)})`;
|
|
465
|
+
}
|
|
466
|
+
return val;
|
|
467
|
+
}
|
|
468
|
+
|
|
450
469
|
function renderObsRows(db, ids, requestedFields) {
|
|
451
470
|
const placeholders = ids.map(() => '?').join(',');
|
|
452
471
|
try {
|
|
@@ -465,8 +484,9 @@ function renderObsRows(db, ids, requestedFields) {
|
|
|
465
484
|
const val = r[f];
|
|
466
485
|
if (val === null || val === undefined || val === '') continue;
|
|
467
486
|
if (f === 'text' && r.narrative && typeof val === 'string' && val.startsWith(r.narrative)) continue;
|
|
487
|
+
const formatted = formatObsFieldValue(f, val);
|
|
468
488
|
const maxLen = f === 'narrative' ? 1000 : f === 'lesson_learned' ? 500 : f === 'text' ? 500 : 200;
|
|
469
|
-
const display = typeof
|
|
489
|
+
const display = typeof formatted === 'string' && formatted.length > maxLen ? formatted.slice(0, maxLen) + '…' : formatted;
|
|
470
490
|
lines.push(`${f}: ${display}`);
|
|
471
491
|
}
|
|
472
492
|
parts.push(lines.join('\n'));
|
|
@@ -672,7 +692,12 @@ function cmdTimeline(db, args) {
|
|
|
672
692
|
if ((!anchorId || isNaN(anchorId)) && queryStr) {
|
|
673
693
|
const ftsQuery = sanitizeFtsQuery(queryStr);
|
|
674
694
|
const found = findFtsAnchor(db, { ftsQuery, project: project ?? null });
|
|
675
|
-
if (found)
|
|
695
|
+
if (found) {
|
|
696
|
+
anchorId = found.id;
|
|
697
|
+
if (found.relaxed && !anchorNote) {
|
|
698
|
+
anchorNote = `(query "${queryStr}" relaxed AND→OR — no row matched all terms)`;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
676
701
|
}
|
|
677
702
|
|
|
678
703
|
// No anchor: show most recent observations (aligned with MCP mem_timeline fallback)
|
package/package.json
CHANGED
package/search-engine.mjs
CHANGED
|
@@ -152,7 +152,7 @@ function expandObsByPRF(db, ctx, now, primaryCount, existingIds, results, includ
|
|
|
152
152
|
* 1. FTS5 MATCH with the sanitized query (AND-by-default), recency-weighted
|
|
153
153
|
* 2. If AND returns 0 → relaxFtsQueryToOr fallback (mirrors searchObservationsHybrid)
|
|
154
154
|
*
|
|
155
|
-
*
|
|
155
|
+
* Always skips compressed rows.
|
|
156
156
|
*
|
|
157
157
|
* @param {Database} db
|
|
158
158
|
* @param {object} opts
|
|
@@ -160,7 +160,8 @@ function expandObsByPRF(db, ctx, now, primaryCount, existingIds, results, includ
|
|
|
160
160
|
* @param {string|null} [opts.project] restrict to this project (boost-by-membership; null = no filter)
|
|
161
161
|
* @param {number} [opts.nowT] Date.now() override (for deterministic tests)
|
|
162
162
|
* @param {number} [opts.halfLifeMs] recency half-life (default DEFAULT_DECAY_HALF_LIFE_MS)
|
|
163
|
-
* @returns {number|null}
|
|
163
|
+
* @returns {{id:number, relaxed:boolean}|null} `relaxed:true` when AND returned 0 and OR rescued —
|
|
164
|
+
* callers should surface a "(relaxed AND→OR)" hint to mirror search transparency.
|
|
164
165
|
*/
|
|
165
166
|
export function findFtsAnchor(db, { ftsQuery, project = null, nowT = null, halfLifeMs = DEFAULT_DECAY_HALF_LIFE_MS } = {}) {
|
|
166
167
|
if (!ftsQuery) return null;
|
|
@@ -178,13 +179,13 @@ export function findFtsAnchor(db, { ftsQuery, project = null, nowT = null, halfL
|
|
|
178
179
|
const stmt = db.prepare(sql);
|
|
179
180
|
try {
|
|
180
181
|
const m = stmt.get(ftsQuery, project, project, now);
|
|
181
|
-
if (m) return m.id;
|
|
182
|
+
if (m) return { id: m.id, relaxed: false };
|
|
182
183
|
} catch (e) { debugCatch(e, 'findFtsAnchor-and'); }
|
|
183
184
|
const orQuery = relaxFtsQueryToOr(ftsQuery);
|
|
184
185
|
if (orQuery && orQuery !== ftsQuery) {
|
|
185
186
|
try {
|
|
186
187
|
const m = stmt.get(orQuery, project, project, now);
|
|
187
|
-
if (m) return m.id;
|
|
188
|
+
if (m) return { id: m.id, relaxed: true };
|
|
188
189
|
} catch (e) { debugCatch(e, 'findFtsAnchor-or'); }
|
|
189
190
|
}
|
|
190
191
|
return null;
|
package/server.mjs
CHANGED
|
@@ -614,11 +614,18 @@ server.registerTool(
|
|
|
614
614
|
|
|
615
615
|
// Auto-find anchor via FTS (with recency decay). Routes through shared
|
|
616
616
|
// findFtsAnchor so CLI `timeline --query` and MCP mem_timeline use
|
|
617
|
-
// identical AND→OR fallback semantics (paired-path per #8217).
|
|
617
|
+
// identical AND→OR fallback semantics (paired-path per #8217). When the
|
|
618
|
+
// OR fallback fired, surface a hint so the caller knows the match was
|
|
619
|
+
// not an exact AND coverage of the query — mirrors search transparency.
|
|
618
620
|
if (!anchorId && args.query) {
|
|
619
621
|
const ftsQuery = sanitizeFtsQuery(args.query);
|
|
620
622
|
const found = findFtsAnchor(db, { ftsQuery, project: args.project ?? null });
|
|
621
|
-
if (found)
|
|
623
|
+
if (found) {
|
|
624
|
+
anchorId = found.id;
|
|
625
|
+
if (found.relaxed && !anchorNote) {
|
|
626
|
+
anchorNote = `(query "${args.query}" relaxed AND→OR — no row matched all terms)`;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
622
629
|
}
|
|
623
630
|
|
|
624
631
|
// No anchor: return most recent
|