claude-mem-lite 3.96.0 → 3.97.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.
Files changed (143) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/adopt-cli.mjs +125 -51
  4. package/bash-utils.mjs +105 -29
  5. package/claudemd.mjs +63 -19
  6. package/cli/activity.mjs +36 -18
  7. package/cli/common.mjs +101 -18
  8. package/cli/doctor.mjs +29 -9
  9. package/cli.mjs +69 -10
  10. package/deep-search.mjs +63 -20
  11. package/format-utils.mjs +15 -7
  12. package/haiku-client.mjs +189 -111
  13. package/hash-utils.mjs +15 -6
  14. package/hook-context.mjs +135 -46
  15. package/hook-episode.mjs +76 -23
  16. package/hook-handoff.mjs +216 -84
  17. package/hook-llm.mjs +476 -216
  18. package/hook-memory.mjs +106 -43
  19. package/hook-optimize.mjs +272 -109
  20. package/hook-precompact.mjs +1 -1
  21. package/hook-semaphore.mjs +46 -12
  22. package/hook-shared.mjs +101 -131
  23. package/hook-update.mjs +286 -88
  24. package/hook.mjs +1207 -617
  25. package/install-metadata.mjs +246 -125
  26. package/install.mjs +1327 -917
  27. package/lib/activity.mjs +52 -29
  28. package/lib/atomic-write.mjs +23 -4
  29. package/lib/binding-probe.mjs +40 -38
  30. package/lib/browse-core.mjs +22 -8
  31. package/lib/citation-tracker.mjs +189 -79
  32. package/lib/cite-back-hint.mjs +37 -19
  33. package/lib/cite-recall-path.mjs +3 -1
  34. package/lib/cli-flags.mjs +4 -2
  35. package/lib/cli-project.mjs +8 -5
  36. package/lib/compress-core.mjs +34 -9
  37. package/lib/cooldown-path.mjs +3 -1
  38. package/lib/db-backup.mjs +33 -11
  39. package/lib/deferred-work.mjs +73 -32
  40. package/lib/delete-core.mjs +25 -9
  41. package/lib/doctor-benchmark.mjs +5 -1
  42. package/lib/doctor-drift.mjs +25 -8
  43. package/lib/edge-attribution.mjs +23 -9
  44. package/lib/env-number.mjs +7 -8
  45. package/lib/err-sampler.mjs +22 -11
  46. package/lib/error-recall-core.mjs +15 -17
  47. package/lib/events-injection.mjs +25 -8
  48. package/lib/export-columns.mjs +49 -10
  49. package/lib/fast-summary.mjs +22 -9
  50. package/lib/file-edge-match.mjs +4 -2
  51. package/lib/file-intel.mjs +38 -13
  52. package/lib/frontmatter.mjs +22 -5
  53. package/lib/get-core.mjs +78 -11
  54. package/lib/handoff-constants.mjs +18 -0
  55. package/lib/hook-stdin.mjs +10 -3
  56. package/lib/hook-stdout.mjs +21 -7
  57. package/lib/hook-telemetry.mjs +44 -17
  58. package/lib/id-routing.mjs +11 -6
  59. package/lib/import-jsonl.mjs +92 -41
  60. package/lib/inject-search-core.mjs +5 -2
  61. package/lib/injected-ids.mjs +19 -12
  62. package/lib/install-shape.mjs +47 -23
  63. package/lib/keyctx-marker.mjs +3 -1
  64. package/lib/lesson-bridge.mjs +8 -5
  65. package/lib/llm-call.mjs +76 -0
  66. package/lib/llm-provider-probe.mjs +24 -12
  67. package/lib/low-signal-patterns.mjs +25 -22
  68. package/lib/maintain-core.mjs +221 -92
  69. package/lib/mem-override.mjs +4 -2
  70. package/lib/metrics.mjs +30 -25
  71. package/lib/native-binding-hint.mjs +29 -8
  72. package/lib/observation-write.mjs +74 -19
  73. package/lib/patha-exclude-meter.mjs +10 -6
  74. package/lib/persist-reminder.mjs +22 -10
  75. package/lib/plan-reader.mjs +2 -2
  76. package/lib/private-strip.mjs +3 -3
  77. package/lib/proc-lock.mjs +15 -3
  78. package/lib/proxy-fetch.mjs +49 -13
  79. package/lib/quiet-scope.mjs +42 -0
  80. package/lib/recall-core.mjs +19 -7
  81. package/lib/recent-core.mjs +18 -5
  82. package/lib/registry-core.mjs +12 -6
  83. package/lib/release-digest.mjs +4 -1
  84. package/lib/relevance-floor.mjs +5 -2
  85. package/lib/reread-guard.mjs +16 -8
  86. package/lib/resolve-data-dir.mjs +1 -1
  87. package/lib/rrf.mjs +4 -1
  88. package/lib/save-enrich.mjs +36 -18
  89. package/lib/save-observation.mjs +78 -35
  90. package/lib/scrub-record.mjs +20 -6
  91. package/lib/search-core.mjs +456 -123
  92. package/lib/shard-gc.mjs +50 -0
  93. package/lib/startup-dashboard.mjs +20 -9
  94. package/lib/stats-core.mjs +101 -35
  95. package/lib/stats-quality.mjs +56 -20
  96. package/lib/summary-extractor.mjs +2 -2
  97. package/lib/task-imperative.mjs +12 -4
  98. package/lib/timeline-core.mjs +88 -28
  99. package/lib/tmp-fixture-sweep.mjs +32 -9
  100. package/lib/transcript-scan.mjs +20 -4
  101. package/lib/upgrade-banner.mjs +11 -7
  102. package/mem-cli.mjs +1314 -506
  103. package/memdir.mjs +53 -21
  104. package/nlp.mjs +68 -38
  105. package/npm-shrinkwrap.json +2 -2
  106. package/package.json +9 -2
  107. package/plugin-cache-guard.mjs +31 -15
  108. package/project-utils.mjs +44 -20
  109. package/registry-enricher.mjs +26 -6
  110. package/registry-github.mjs +4 -2
  111. package/registry-importer.mjs +151 -40
  112. package/registry-recommend.mjs +189 -96
  113. package/registry-retriever.mjs +229 -70
  114. package/registry-scanner.mjs +11 -6
  115. package/registry.mjs +163 -66
  116. package/rerank.mjs +18 -4
  117. package/resource-discovery.mjs +14 -4
  118. package/schema.mjs +258 -99
  119. package/scoring-sql.mjs +21 -10
  120. package/scripts/binding-probe-cli.mjs +17 -12
  121. package/scripts/hook-launcher.mjs +148 -31
  122. package/scripts/launch-preflight.mjs +5 -7
  123. package/scripts/launch.mjs +21 -9
  124. package/scripts/post-tool-recall.js +21 -5
  125. package/scripts/pre-agent-inject.js +51 -13
  126. package/scripts/pre-skill-bridge.js +22 -7
  127. package/scripts/pre-tool-recall.js +157 -77
  128. package/scripts/prompt-search-utils.mjs +70 -25
  129. package/scripts/setup.sh +16 -1
  130. package/scripts/user-prompt-search.js +238 -97
  131. package/search-engine.mjs +329 -96
  132. package/search-scoring.mjs +75 -33
  133. package/secret-scrub.mjs +48 -12
  134. package/server/fts-check.mjs +12 -9
  135. package/server.mjs +674 -243
  136. package/skip-tools.mjs +19 -8
  137. package/source-files.mjs +54 -21
  138. package/stop-words.mjs +152 -19
  139. package/synonyms.mjs +579 -164
  140. package/tfidf.mjs +130 -40
  141. package/tier.mjs +1 -1
  142. package/tool-schemas.mjs +381 -131
  143. package/utils.mjs +184 -36
package/registry.mjs CHANGED
@@ -136,8 +136,14 @@ const INVOCATIONS_SCHEMA = `
136
136
  // Canonical indexed-column list for resources_fts, in FTS5_SCHEMA order (see the BM25
137
137
  // weight note above — order is load-bearing). Drift from this list triggers a rebuild.
138
138
  const FTS5_COLUMNS = [
139
- 'trigger_patterns', 'keywords', 'capability_summary', 'intent_tags',
140
- 'use_cases', 'domain_tags', 'tech_stack', 'name',
139
+ 'trigger_patterns',
140
+ 'keywords',
141
+ 'capability_summary',
142
+ 'intent_tags',
143
+ 'use_cases',
144
+ 'domain_tags',
145
+ 'tech_stack',
146
+ 'name',
141
147
  ];
142
148
 
143
149
  const PREINSTALLED_SCHEMA = `
@@ -185,7 +191,7 @@ function assertRegistryNotNewer(db) {
185
191
  if (row && typeof row.version === 'number' && row.version > REGISTRY_SCHEMA_VERSION) {
186
192
  throw new Error(
187
193
  `Registry DB schema is v${row.version} but this claude-mem-lite binary supports up to v${REGISTRY_SCHEMA_VERSION}. ` +
188
- `A newer version wrote this DB; upgrade claude-mem-lite (npm i -g claude-mem-lite@latest) or point CLAUDE_MEM_DIR to a fresh directory.`
194
+ `A newer version wrote this DB; upgrade claude-mem-lite (npm i -g claude-mem-lite@latest) or point CLAUDE_MEM_DIR to a fresh directory.`,
189
195
  );
190
196
  }
191
197
  }
@@ -209,11 +215,17 @@ function ensureResourcesFts(db) {
209
215
  let recreated = false;
210
216
  if (ftsRow) {
211
217
  let existingCols = [];
212
- try { existingCols = db.prepare(`PRAGMA table_info(resources_fts)`).all().map(c => c.name); }
213
- catch { /* unreadable → treat as drifted, recreate */ }
218
+ try {
219
+ existingCols = db
220
+ .prepare(`PRAGMA table_info(resources_fts)`)
221
+ .all()
222
+ .map((c) => c.name);
223
+ } catch {
224
+ /* unreadable → treat as drifted, recreate */
225
+ }
214
226
  // Element-wise so additions, removals AND reordering all count as drift.
215
- const drifted = existingCols.length !== FTS5_COLUMNS.length
216
- || FTS5_COLUMNS.some((c, i) => existingCols[i] !== c);
227
+ const drifted =
228
+ existingCols.length !== FTS5_COLUMNS.length || FTS5_COLUMNS.some((c, i) => existingCols[i] !== c);
217
229
  if (drifted) {
218
230
  db.exec(`DROP TRIGGER IF EXISTS res_fts_insert`);
219
231
  db.exec(`DROP TRIGGER IF EXISTS res_fts_update`);
@@ -249,32 +261,56 @@ export function ensureRegistryDb(dbPath) {
249
261
 
250
262
  // Before ANY DDL: refuse a registry written by a newer client (see assertRegistryNotNewer).
251
263
  // Close the handle on the way out so the caller isn't left leaking an open file.
252
- try { assertRegistryNotNewer(db); } catch (e) { db.close(); throw e; }
264
+ try {
265
+ assertRegistryNotNewer(db);
266
+ } catch (e) {
267
+ db.close();
268
+ throw e;
269
+ }
253
270
 
254
271
  db.exec(RESOURCES_SCHEMA);
255
272
 
256
273
  // Migrate: add missing columns to resources (single PRAGMA call for all)
257
274
  try {
258
- const resCols = new Set(db.prepare("PRAGMA table_info(resources)").all().map(c => c.name));
259
- if (!resCols.has('invocation_name')) db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
260
- if (!resCols.has('silenced_until')) db.exec("ALTER TABLE resources ADD COLUMN silenced_until TEXT");
261
- if (!resCols.has('cooldown_hours')) db.exec("ALTER TABLE resources ADD COLUMN cooldown_hours INTEGER DEFAULT 0");
275
+ const resCols = new Set(
276
+ db
277
+ .prepare('PRAGMA table_info(resources)')
278
+ .all()
279
+ .map((c) => c.name),
280
+ );
281
+ if (!resCols.has('invocation_name'))
282
+ db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
283
+ if (!resCols.has('silenced_until')) db.exec('ALTER TABLE resources ADD COLUMN silenced_until TEXT');
284
+ if (!resCols.has('cooldown_hours'))
285
+ db.exec('ALTER TABLE resources ADD COLUMN cooldown_hours INTEGER DEFAULT 0');
262
286
  // recommendation_mode: 'proactive' (default, actively recommended), 'on_request' (only when explicitly asked)
263
- if (!resCols.has('recommendation_mode')) db.exec("ALTER TABLE resources ADD COLUMN recommendation_mode TEXT DEFAULT 'proactive'");
287
+ if (!resCols.has('recommendation_mode'))
288
+ db.exec("ALTER TABLE resources ADD COLUMN recommendation_mode TEXT DEFAULT 'proactive'");
264
289
  // weighted_adopt_sum: continuous adoption score accumulator (vs binary adopt_count)
265
- if (!resCols.has('weighted_adopt_sum')) db.exec("ALTER TABLE resources ADD COLUMN weighted_adopt_sum REAL DEFAULT 0");
290
+ if (!resCols.has('weighted_adopt_sum'))
291
+ db.exec('ALTER TABLE resources ADD COLUMN weighted_adopt_sum REAL DEFAULT 0');
266
292
  // Phase 2: Registry optimization columns
267
- if (!resCols.has('category')) db.exec("ALTER TABLE resources ADD COLUMN category TEXT");
268
- if (!resCols.has('quality_tier')) db.exec("ALTER TABLE resources ADD COLUMN quality_tier TEXT DEFAULT 'community'");
269
- if (!resCols.has('popularity_score')) db.exec("ALTER TABLE resources ADD COLUMN popularity_score REAL DEFAULT 0");
270
- if (!resCols.has('personal_score')) db.exec("ALTER TABLE resources ADD COLUMN personal_score REAL DEFAULT 0");
271
- if (!resCols.has('enrichment_status')) db.exec("ALTER TABLE resources ADD COLUMN enrichment_status TEXT DEFAULT NULL");
272
- if (!resCols.has('enriched_at')) db.exec("ALTER TABLE resources ADD COLUMN enriched_at INTEGER DEFAULT NULL");
273
- if (!resCols.has('repo_updated_at')) db.exec("ALTER TABLE resources ADD COLUMN repo_updated_at TEXT DEFAULT NULL");
274
- if (!resCols.has('repo_forks')) db.exec("ALTER TABLE resources ADD COLUMN repo_forks INTEGER DEFAULT 0");
293
+ if (!resCols.has('category')) db.exec('ALTER TABLE resources ADD COLUMN category TEXT');
294
+ if (!resCols.has('quality_tier'))
295
+ db.exec("ALTER TABLE resources ADD COLUMN quality_tier TEXT DEFAULT 'community'");
296
+ if (!resCols.has('popularity_score'))
297
+ db.exec('ALTER TABLE resources ADD COLUMN popularity_score REAL DEFAULT 0');
298
+ if (!resCols.has('personal_score'))
299
+ db.exec('ALTER TABLE resources ADD COLUMN personal_score REAL DEFAULT 0');
300
+ if (!resCols.has('enrichment_status'))
301
+ db.exec('ALTER TABLE resources ADD COLUMN enrichment_status TEXT DEFAULT NULL');
302
+ if (!resCols.has('enriched_at'))
303
+ db.exec('ALTER TABLE resources ADD COLUMN enriched_at INTEGER DEFAULT NULL');
304
+ if (!resCols.has('repo_updated_at'))
305
+ db.exec('ALTER TABLE resources ADD COLUMN repo_updated_at TEXT DEFAULT NULL');
306
+ if (!resCols.has('repo_forks')) db.exec('ALTER TABLE resources ADD COLUMN repo_forks INTEGER DEFAULT 0');
275
307
  // Auto-set quality_tier for installed preinstalled resources
276
- db.exec("UPDATE resources SET quality_tier = 'installed' WHERE source = 'preinstalled' AND quality_tier = 'community'");
277
- } catch (e) { debugCatch(e, 'resources-column-migration'); }
308
+ db.exec(
309
+ "UPDATE resources SET quality_tier = 'installed' WHERE source = 'preinstalled' AND quality_tier = 'community'",
310
+ );
311
+ } catch (e) {
312
+ debugCatch(e, 'resources-column-migration');
313
+ }
278
314
 
279
315
  // Migrate: add 'github' to source CHECK constraint (required for smart import)
280
316
  // Must disable FK checks during table recreation (RENAME triggers FK validation).
@@ -286,13 +322,17 @@ export function ensureRegistryDb(dbPath) {
286
322
  // the original name, which the freshly-created `resources` table then satisfies.
287
323
  let resourcesRebuilt = false;
288
324
  try {
289
- const resSchema = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='resources'`).get();
325
+ const resSchema = db
326
+ .prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='resources'`)
327
+ .get();
290
328
  if (resSchema?.sql && !resSchema.sql.includes("'github'")) {
291
329
  db.pragma('foreign_keys = OFF');
292
330
  db.pragma('legacy_alter_table = ON');
293
331
  try {
294
332
  db.transaction(() => {
295
- const hasOld = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='resources_old'`).get();
333
+ const hasOld = db
334
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='resources_old'`)
335
+ .get();
296
336
  if (hasOld) db.exec(`DROP TABLE resources_old`);
297
337
  // Drop FTS triggers first (reference resources table)
298
338
  db.exec(`DROP TRIGGER IF EXISTS res_fts_insert`);
@@ -301,9 +341,17 @@ export function ensureRegistryDb(dbPath) {
301
341
  db.exec(`ALTER TABLE resources RENAME TO resources_old`);
302
342
  db.exec(RESOURCES_SCHEMA);
303
343
  // Copy all existing data
304
- const cols = db.prepare("PRAGMA table_info(resources_old)").all().map(c => c.name);
305
- const newCols = new Set(db.prepare("PRAGMA table_info(resources)").all().map(c => c.name));
306
- const common = cols.filter(c => newCols.has(c)).join(', ');
344
+ const cols = db
345
+ .prepare('PRAGMA table_info(resources_old)')
346
+ .all()
347
+ .map((c) => c.name);
348
+ const newCols = new Set(
349
+ db
350
+ .prepare('PRAGMA table_info(resources)')
351
+ .all()
352
+ .map((c) => c.name),
353
+ );
354
+ const common = cols.filter((c) => newCols.has(c)).join(', ');
307
355
  db.exec(`INSERT INTO resources (${common}) SELECT ${common} FROM resources_old`);
308
356
  db.exec(`DROP TABLE resources_old`);
309
357
  // Recreate the table's indexes: the CREATE INDEX IF NOT EXISTS inside
@@ -319,7 +367,9 @@ export function ensureRegistryDb(dbPath) {
319
367
  }
320
368
  resourcesRebuilt = true;
321
369
  }
322
- } catch (e) { debugCatch(e, 'resources-source-check-migration'); }
370
+ } catch (e) {
371
+ debugCatch(e, 'resources-source-check-migration');
372
+ }
323
373
 
324
374
  // FTS5: create if absent, or drop+recreate if the indexed columns have drifted from
325
375
  // FTS5_COLUMNS (a stale narrow index silently kills every resources write — audit P2-6).
@@ -336,8 +386,11 @@ export function ensureRegistryDb(dbPath) {
336
386
  // repopulation for the opposite reason: it starts EMPTY, so rows written before the
337
387
  // drift would be unreachable through search forever.
338
388
  if (resourcesRebuilt || ftsRecreated) {
339
- try { db.exec("INSERT INTO resources_fts(resources_fts) VALUES('rebuild')"); }
340
- catch (e) { debugCatch(e, 'resources-fts-rebuild-after-source-check'); }
389
+ try {
390
+ db.exec("INSERT INTO resources_fts(resources_fts) VALUES('rebuild')");
391
+ } catch (e) {
392
+ debugCatch(e, 'resources-fts-rebuild-after-source-check');
393
+ }
341
394
  }
342
395
 
343
396
  db.exec(INVOCATIONS_SCHEMA);
@@ -345,30 +398,40 @@ export function ensureRegistryDb(dbPath) {
345
398
  // Migrate invocations CHECK constraint: add 'user_prompt' trigger value
346
399
  // SQLite cannot ALTER CHECK constraints, so recreate table if needed
347
400
  try {
348
- const schema = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`).get();
401
+ const schema = db
402
+ .prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`)
403
+ .get();
349
404
  if (schema?.sql && !schema.sql.includes('user_prompt')) {
350
405
  db.transaction(() => {
351
406
  // Clean up leftover from previous failed migration attempt
352
- const hasOld = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`).get();
407
+ const hasOld = db
408
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`)
409
+ .get();
353
410
  if (hasOld) db.exec(`DROP TABLE invocations_old`);
354
411
  db.exec(`ALTER TABLE invocations RENAME TO invocations_old`);
355
412
  db.exec(INVOCATIONS_SCHEMA);
356
413
  // Omit rejection_reason — column may not exist yet on old DBs; ADD COLUMN migration below handles it
357
- db.exec(`INSERT INTO invocations
414
+ db.exec(`INSERT INTO invocations
358
415
  (id, resource_id, session_id, trigger, tier, recommended, adopted, outcome, score, created_at)
359
416
  SELECT id, resource_id, session_id, trigger, tier, recommended, adopted, outcome, score, created_at
360
417
  FROM invocations_old`);
361
418
  db.exec(`DROP TABLE invocations_old`);
362
419
  })();
363
420
  }
364
- } catch (e) { debugCatch(e, 'ensureRegistryDb-migration'); }
421
+ } catch (e) {
422
+ debugCatch(e, 'ensureRegistryDb-migration');
423
+ }
365
424
 
366
425
  // Migrate invocations CHECK constraint: add 'ignored' outcome value
367
426
  try {
368
- const schema = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`).get();
427
+ const schema = db
428
+ .prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`)
429
+ .get();
369
430
  if (schema?.sql && !schema.sql.includes("'ignored'")) {
370
431
  db.transaction(() => {
371
- const hasOld = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`).get();
432
+ const hasOld = db
433
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`)
434
+ .get();
372
435
  if (hasOld) db.exec(`DROP TABLE invocations_old`);
373
436
  db.exec(`ALTER TABLE invocations RENAME TO invocations_old`);
374
437
  db.exec(INVOCATIONS_SCHEMA);
@@ -380,15 +443,19 @@ export function ensureRegistryDb(dbPath) {
380
443
  db.exec(`DROP TABLE invocations_old`);
381
444
  })();
382
445
  }
383
- } catch (e) { debugCatch(e, 'ensureRegistryDb-ignored-migration'); }
446
+ } catch (e) {
447
+ debugCatch(e, 'ensureRegistryDb-ignored-migration');
448
+ }
384
449
 
385
450
  // Migrate: add rejection_reason column if missing
386
451
  try {
387
- const cols = db.prepare("PRAGMA table_info(invocations)").all();
388
- if (!cols.some(c => c.name === 'rejection_reason')) {
389
- db.exec("ALTER TABLE invocations ADD COLUMN rejection_reason TEXT");
452
+ const cols = db.prepare('PRAGMA table_info(invocations)').all();
453
+ if (!cols.some((c) => c.name === 'rejection_reason')) {
454
+ db.exec('ALTER TABLE invocations ADD COLUMN rejection_reason TEXT');
390
455
  }
391
- } catch (e) { debugCatch(e, 'rejection_reason-migration'); }
456
+ } catch (e) {
457
+ debugCatch(e, 'rejection_reason-migration');
458
+ }
392
459
 
393
460
  // Migrate: add ON DELETE CASCADE to invocations.resource_id (audit P0 #4). Old DBs
394
461
  // declared the FK with no ON DELETE action, so deleting a resource that had
@@ -398,10 +465,14 @@ export function ensureRegistryDb(dbPath) {
398
465
  // invocations), so legacy_alter_table is not a concern here. Runs after the
399
466
  // rejection_reason ADD COLUMN so the column exists in both old and new tables.
400
467
  try {
401
- const schema = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`).get();
468
+ const schema = db
469
+ .prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='invocations'`)
470
+ .get();
402
471
  if (schema?.sql && !/ON DELETE CASCADE/i.test(schema.sql)) {
403
472
  db.transaction(() => {
404
- const hasOld = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`).get();
473
+ const hasOld = db
474
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='invocations_old'`)
475
+ .get();
405
476
  if (hasOld) db.exec(`DROP TABLE invocations_old`);
406
477
  db.exec(`ALTER TABLE invocations RENAME TO invocations_old`);
407
478
  db.exec(INVOCATIONS_SCHEMA);
@@ -421,7 +492,9 @@ export function ensureRegistryDb(dbPath) {
421
492
  db.exec(INVOCATIONS_SCHEMA);
422
493
  })();
423
494
  }
424
- } catch (e) { debugCatch(e, 'invocations-ondelete-cascade-migration'); }
495
+ } catch (e) {
496
+ debugCatch(e, 'invocations-ondelete-cascade-migration');
497
+ }
425
498
 
426
499
  // (Removed the separate idx_invocations_resource_created migration — it was a column-
427
500
  // identical duplicate of idx_inv_resource (resource_id, created_at) in INVOCATIONS_SCHEMA.
@@ -501,14 +574,28 @@ const UPSERT_SQL = `
501
574
  export function upsertResource(db, r) {
502
575
  return db.transaction(() => {
503
576
  db.prepare(UPSERT_SQL).run(
504
- r.name, r.type, r.status || 'active', r.source || 'preinstalled',
505
- r.repo_url || null, r.repo_stars || 0, r.local_path,
506
- r.file_hash || null, r.invocation_name || '',
507
- r.intent_tags || '', r.domain_tags || '',
508
- r.action_type || '', r.trigger_patterns || '', r.capability_summary || '',
509
- r.input_type || '', r.output_type || '', r.prerequisites || '{}',
510
- r.keywords || '', r.tech_stack || '', r.use_cases || '', r.complexity || 'intermediate',
511
- r.indexed_at || null
577
+ r.name,
578
+ r.type,
579
+ r.status || 'active',
580
+ r.source || 'preinstalled',
581
+ r.repo_url || null,
582
+ r.repo_stars || 0,
583
+ r.local_path,
584
+ r.file_hash || null,
585
+ r.invocation_name || '',
586
+ r.intent_tags || '',
587
+ r.domain_tags || '',
588
+ r.action_type || '',
589
+ r.trigger_patterns || '',
590
+ r.capability_summary || '',
591
+ r.input_type || '',
592
+ r.output_type || '',
593
+ r.prerequisites || '{}',
594
+ r.keywords || '',
595
+ r.tech_stack || '',
596
+ r.use_cases || '',
597
+ r.complexity || 'intermediate',
598
+ r.indexed_at || null,
512
599
  );
513
600
  const row = db.prepare('SELECT id FROM resources WHERE type = ? AND name = ?').get(r.type, r.name);
514
601
  return row?.id || 0;
@@ -528,16 +615,22 @@ export function upsertResource(db, r) {
528
615
  */
529
616
  export function collectRegistryStats(rdb) {
530
617
  const total = rdb.prepare('SELECT COUNT(*) as c FROM resources WHERE status = ?').get('active');
531
- const byType = rdb.prepare('SELECT type, COUNT(*) as c FROM resources WHERE status = ? GROUP BY type').all('active');
532
- const topAdopted = rdb.prepare(
533
- 'SELECT name, type, adopt_count, recommend_count FROM resources WHERE status = ? AND adopt_count > 0 ORDER BY adopt_count DESC LIMIT 10'
534
- ).all('active');
535
- const zeroAdopt = rdb.prepare(
536
- 'SELECT COUNT(*) as c FROM resources WHERE status = ? AND recommend_count > 0 AND adopt_count = 0'
537
- ).get('active');
538
- const userAdded = rdb.prepare(
539
- "SELECT COUNT(*) as c FROM resources WHERE status = ? AND source = 'user'"
540
- ).get('active');
618
+ const byType = rdb
619
+ .prepare('SELECT type, COUNT(*) as c FROM resources WHERE status = ? GROUP BY type')
620
+ .all('active');
621
+ const topAdopted = rdb
622
+ .prepare(
623
+ 'SELECT name, type, adopt_count, recommend_count FROM resources WHERE status = ? AND adopt_count > 0 ORDER BY adopt_count DESC LIMIT 10',
624
+ )
625
+ .all('active');
626
+ const zeroAdopt = rdb
627
+ .prepare(
628
+ 'SELECT COUNT(*) as c FROM resources WHERE status = ? AND recommend_count > 0 AND adopt_count = 0',
629
+ )
630
+ .get('active');
631
+ const userAdded = rdb
632
+ .prepare("SELECT COUNT(*) as c FROM resources WHERE status = ? AND source = 'user'")
633
+ .get('active');
541
634
  return { total: total.c, byType, topAdopted, zeroAdopt: zeroAdopt.c, userAdded: userAdded.c };
542
635
  }
543
636
 
@@ -552,11 +645,15 @@ export function collectRegistryStats(rdb) {
552
645
  export function listResourcesRanked(rdb, { type } = {}) {
553
646
  const where = type ? 'WHERE type = ? AND status = ?' : 'WHERE status = ?';
554
647
  const params = type ? [type, 'active'] : ['active'];
555
- return rdb.prepare(`
648
+ return rdb
649
+ .prepare(
650
+ `
556
651
  SELECT name, type, invocation_name, recommend_count, adopt_count, capability_summary
557
652
  FROM resources ${where}
558
653
  ORDER BY COALESCE(adopt_count, 0) DESC, COALESCE(recommend_count, 0) DESC, type, name
559
- `).all(...params);
654
+ `,
655
+ )
656
+ .all(...params);
560
657
  }
561
658
 
562
659
  /** One list row, shared shape: `S name (invocation) — rec:N adopt:N — summary…` (80-char summary cap).
package/rerank.mjs CHANGED
@@ -42,7 +42,12 @@ export function extractRanked(raw) {
42
42
  if (obj && Array.isArray(obj.ranked)) return obj.ranked; // {"ranked":[...]}
43
43
  const m = text.match(/\[\s*\d+(?:\s*,\s*\d+)*\s*\]/); // prose-wrapped [..]
44
44
  if (m) {
45
- try { const a = JSON.parse(m[0]); if (Array.isArray(a)) return a; } catch { /* fall through */ }
45
+ try {
46
+ const a = JSON.parse(m[0]);
47
+ if (Array.isArray(a)) return a;
48
+ } catch {
49
+ /* fall through */
50
+ }
46
51
  }
47
52
  return null;
48
53
  }
@@ -50,9 +55,16 @@ export function extractRanked(raw) {
50
55
  // Reorder candidate session ids per the LLM's chosen 1-based order; any failure →
51
56
  // original order ("never worse than baseline"). { order: sid[], parsed: bool }.
52
57
  export async function llmRerankOrder(query, cand /* [{sid,text}] */, llm) {
53
- const prompt = buildRerankPrompt(query, cand.map((c) => c.text));
58
+ const prompt = buildRerankPrompt(
59
+ query,
60
+ cand.map((c) => c.text),
61
+ );
54
62
  let raw;
55
- try { raw = await llm(prompt); } catch { raw = null; }
63
+ try {
64
+ raw = await llm(prompt);
65
+ } catch {
66
+ raw = null;
67
+ }
56
68
  const order = extractRanked(raw);
57
69
  if (!order) return { order: cand.map((c) => c.sid), parsed: false };
58
70
  const seen = new Set();
@@ -64,7 +76,9 @@ export async function llmRerankOrder(query, cand /* [{sid,text}] */, llm) {
64
76
  out.push(cand[idx].sid);
65
77
  }
66
78
  }
67
- cand.forEach((c, i) => { if (!seen.has(i)) out.push(c.sid); }); // append omitted, original order
79
+ cand.forEach((c, i) => {
80
+ if (!seen.has(i)) out.push(c.sid);
81
+ }); // append omitted, original order
68
82
  return { order: out, parsed: true };
69
83
  }
70
84
 
@@ -71,7 +71,11 @@ export function discoverFlat(dirPath, type, opts = {}) {
71
71
  if (name.startsWith('.') || name === 'node_modules' || skipNames.has(name)) continue;
72
72
  const fullPath = join(dirPath, name);
73
73
  let stat;
74
- try { stat = statSync(fullPath); } catch { continue; }
74
+ try {
75
+ stat = statSync(fullPath);
76
+ } catch {
77
+ continue;
78
+ }
75
79
 
76
80
  if (stat.isDirectory()) {
77
81
  if (type === 'skill') {
@@ -111,7 +115,9 @@ export function discoverPlugin(pluginDir, pluginName) {
111
115
  const absPath = join(agentSubDir, file);
112
116
  try {
113
117
  if (statSync(absPath).size === 0) continue;
114
- } catch { continue; }
118
+ } catch {
119
+ continue;
120
+ }
115
121
  items.push({
116
122
  type: 'agent',
117
123
  name: `${pluginName}/${basename(file, '.md')}`,
@@ -155,7 +161,11 @@ export function discoverPlugins(dirPath) {
155
161
  for (const pluginName of readdirSync(dirPath)) {
156
162
  if (pluginName.startsWith('.') || pluginName === 'node_modules') continue;
157
163
  const pluginDir = join(dirPath, pluginName);
158
- try { if (!statSync(pluginDir).isDirectory()) continue; } catch { continue; }
164
+ try {
165
+ if (!statSync(pluginDir).isDirectory()) continue;
166
+ } catch {
167
+ continue;
168
+ }
159
169
  items.push(...discoverPlugin(pluginDir, pluginName));
160
170
  }
161
171
  return items;
@@ -182,7 +192,7 @@ export function discoverAllManaged(managedDir) {
182
192
  * @returns {Array<DiscoveredResource & {filePath: string}>}
183
193
  */
184
194
  export function withRelativePaths(items, managedDir) {
185
- return items.map(item => ({
195
+ return items.map((item) => ({
186
196
  ...item,
187
197
  filePath: relative(managedDir, item.absPath),
188
198
  }));