claude-mem-lite 2.0.2 → 2.0.3

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.
@@ -247,6 +247,56 @@ export function buildQueryFromText(text) {
247
247
  return expanded.join(' OR ');
248
248
  }
249
249
 
250
+ // ─── Project Domain Filtering ─────────────────────────────────────────────────
251
+
252
+ // Platform/language tags that indicate a resource is technology-specific.
253
+ // Only resources with these tags AND no overlap with project domains get filtered out.
254
+ // Resources with only functional tags (testing, quality, review) always pass.
255
+ const TECHNOLOGY_TAGS = new Set([
256
+ 'javascript', 'typescript', 'node', 'react', 'vue', 'svelte', 'angular',
257
+ 'python', 'django', 'flask', 'fastapi',
258
+ 'rust', 'go', 'java', 'kotlin', 'ruby', 'php', 'swift', 'dart', 'flutter',
259
+ 'ios', 'macos', 'android',
260
+ 'cpp', 'c', 'csharp', 'dotnet', 'aspnet',
261
+ 'elixir', 'erlang', 'lua', 'zig', 'solidity',
262
+ 'html', 'css', 'frontend', 'backend',
263
+ ]);
264
+
265
+ /**
266
+ * Post-filter FTS5 results by project domain overlap.
267
+ * - Resources with empty domain_tags (universal) always pass.
268
+ * - Resources with only functional tags (no technology-specific tags) always pass.
269
+ * - Resources with technology tags must overlap with project domains or tech_stack.
270
+ * @param {object[]} results FTS5 results
271
+ * @param {string[]} projectDomains Detected project domains
272
+ * @returns {object[]} Filtered results
273
+ */
274
+ export function filterByProjectDomain(results, projectDomains) {
275
+ if (!projectDomains || projectDomains.length === 0) return results;
276
+ const domainSet = new Set(projectDomains.map(d => d.toLowerCase()));
277
+ return results.filter(r => {
278
+ // Universal: no domain_tags
279
+ if (!r.domain_tags || r.domain_tags.trim() === '') return true;
280
+
281
+ const tags = r.domain_tags.split(/[\s,]+/).map(t => t.trim().toLowerCase()).filter(Boolean);
282
+
283
+ // Check if any tag is a technology tag
284
+ const hasTechTag = tags.some(t => TECHNOLOGY_TAGS.has(t));
285
+ if (!hasTechTag) return true; // Only functional tags — always pass
286
+
287
+ // Has tech tags — check overlap with project domains
288
+ if (tags.some(t => domainSet.has(t))) return true;
289
+
290
+ // Also check tech_stack column (broader tech info)
291
+ if (r.tech_stack) {
292
+ const techTags = r.tech_stack.split(/[\s,]+/).map(t => t.trim().toLowerCase()).filter(Boolean);
293
+ if (techTags.some(t => domainSet.has(t))) return true;
294
+ }
295
+
296
+ return false;
297
+ });
298
+ }
299
+
250
300
  // ─── FTS5 Retrieval ──────────────────────────────────────────────────────────
251
301
 
252
302
  // BM25 weights (8 columns, positional — must match FTS5 column order in registry.mjs):
@@ -327,30 +377,48 @@ const SEARCH_BY_TYPE_SQL = `
327
377
 
328
378
  /**
329
379
  * Search for resources using FTS5 with composite scoring.
380
+ * When projectDomains is provided, fetches extra results internally to allow
381
+ * headroom after domain filtering, then slices to requested limit.
330
382
  * @param {Database} db Registry database
331
383
  * @param {string} query FTS5 query string (already expanded)
332
384
  * @param {object} [opts] Options
333
385
  * @param {'skill'|'agent'} [opts.type] Filter by type
334
386
  * @param {number} [opts.limit=3] Max results
387
+ * @param {string[]} [opts.projectDomains] Project domains for post-filtering
335
388
  * @returns {object[]} Array of matching resources with relevance scores
336
389
  */
337
- export function retrieveResources(db, query, { type, limit = 3 } = {}) {
390
+ export function retrieveResources(db, query, { type, limit = 3, projectDomains } = {}) {
338
391
  if (!query) return [];
339
392
 
393
+ // Fetch extra when domain filtering is active to ensure enough results after filtering
394
+ const fetchLimit = (projectDomains && projectDomains.length > 0) ? Math.max(limit * 3, 10) : limit;
395
+
340
396
  try {
397
+ let results;
341
398
  if (type) {
342
- return db.prepare(SEARCH_BY_TYPE_SQL).all(query, type, limit);
399
+ results = db.prepare(SEARCH_BY_TYPE_SQL).all(query, type, fetchLimit);
400
+ } else {
401
+ results = db.prepare(SEARCH_SQL).all(query, fetchLimit);
343
402
  }
344
- return db.prepare(SEARCH_SQL).all(query, limit);
403
+ if (projectDomains && projectDomains.length > 0) {
404
+ results = filterByProjectDomain(results, projectDomains);
405
+ }
406
+ return results.slice(0, limit);
345
407
  } catch (e) {
346
408
  // FTS5 query syntax error — try simpler query
347
409
  debugCatch(e, 'retrieveResources');
348
410
  try {
349
411
  const simpleQuery = query.replace(/[()]/g, '').split(/\s+OR\s+/).slice(0, 3).join(' OR ');
412
+ let results;
350
413
  if (type) {
351
- return db.prepare(SEARCH_BY_TYPE_SQL).all(simpleQuery, type, limit);
414
+ results = db.prepare(SEARCH_BY_TYPE_SQL).all(simpleQuery, type, fetchLimit);
415
+ } else {
416
+ results = db.prepare(SEARCH_SQL).all(simpleQuery, fetchLimit);
417
+ }
418
+ if (projectDomains && projectDomains.length > 0) {
419
+ results = filterByProjectDomain(results, projectDomains);
352
420
  }
353
- return db.prepare(SEARCH_SQL).all(simpleQuery, limit);
421
+ return results.slice(0, limit);
354
422
  } catch {
355
423
  return [];
356
424
  }
package/registry.mjs CHANGED
@@ -21,6 +21,7 @@ const RESOURCES_SCHEMA = `
21
21
  local_path TEXT NOT NULL,
22
22
  file_hash TEXT,
23
23
  parent_plugin TEXT,
24
+ invocation_name TEXT DEFAULT '',
24
25
  intent_tags TEXT DEFAULT '',
25
26
  domain_tags TEXT DEFAULT '',
26
27
  action_type TEXT DEFAULT '',
@@ -154,6 +155,14 @@ export function ensureRegistryDb(dbPath) {
154
155
 
155
156
  db.exec(RESOURCES_SCHEMA);
156
157
 
158
+ // Migrate: add invocation_name column if missing (safe for existing DBs)
159
+ try {
160
+ const cols = db.prepare("PRAGMA table_info(resources)").all();
161
+ if (!cols.some(c => c.name === 'invocation_name')) {
162
+ db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
163
+ }
164
+ } catch {}
165
+
157
166
  // FTS5 + triggers: only create if not exists
158
167
  const hasFts = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='resources_fts'`).get();
159
168
  if (!hasFts) {
@@ -189,13 +198,14 @@ export function ensureRegistryDb(dbPath) {
189
198
 
190
199
  const UPSERT_SQL = `
191
200
  INSERT INTO resources (name, type, status, source, repo_url, repo_stars, local_path, file_hash,
192
- intent_tags, domain_tags, action_type, trigger_patterns, capability_summary,
201
+ invocation_name, intent_tags, domain_tags, action_type, trigger_patterns, capability_summary,
193
202
  input_type, output_type, prerequisites, keywords, tech_stack, use_cases, complexity,
194
203
  indexed_at, updated_at)
195
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
204
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
196
205
  ON CONFLICT(type, name) DO UPDATE SET
197
206
  status=excluded.status, source=excluded.source, repo_url=excluded.repo_url,
198
207
  repo_stars=excluded.repo_stars, local_path=excluded.local_path, file_hash=excluded.file_hash,
208
+ invocation_name=CASE WHEN excluded.invocation_name != '' THEN excluded.invocation_name ELSE invocation_name END,
199
209
  intent_tags=excluded.intent_tags, domain_tags=excluded.domain_tags,
200
210
  action_type=excluded.action_type, trigger_patterns=excluded.trigger_patterns,
201
211
  capability_summary=excluded.capability_summary, input_type=excluded.input_type,
@@ -216,7 +226,8 @@ export function upsertResource(db, r) {
216
226
  const result = db.prepare(UPSERT_SQL).run(
217
227
  r.name, r.type, r.status || 'active', r.source || 'preinstalled',
218
228
  r.repo_url || null, r.repo_stars || 0, r.local_path,
219
- r.file_hash || null, r.intent_tags || '', r.domain_tags || '',
229
+ r.file_hash || null, r.invocation_name || '',
230
+ r.intent_tags || '', r.domain_tags || '',
220
231
  r.action_type || '', r.trigger_patterns || '', r.capability_summary || '',
221
232
  r.input_type || '', r.output_type || '', r.prerequisites || '{}',
222
233
  r.keywords || '', r.tech_stack || '', r.use_cases || '', r.complexity || 'intermediate',
@@ -318,7 +329,8 @@ export function getResourceSuccessRates(db, days = 30) {
318
329
  */
319
330
  export function getSessionInvocations(db, sessionId) {
320
331
  return db.prepare(`
321
- SELECT i.*, r.name as resource_name, r.type as resource_type
332
+ SELECT i.*, r.name as resource_name, r.type as resource_type,
333
+ r.invocation_name as invocation_name
322
334
  FROM invocations i
323
335
  JOIN resources r ON r.id = i.resource_id
324
336
  WHERE i.session_id = ?