claude-mem-lite 2.26.0 → 2.28.1
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/.mcp.json +0 -0
- package/LICENSE +0 -0
- package/README.md +22 -5
- package/README.zh-CN.md +47 -63
- package/bash-utils.mjs +0 -0
- package/cli.mjs +1 -1
- package/commands/mem.md +1 -1
- package/commands/memory.md +1 -1
- package/commands/recall.md +1 -1
- package/commands/recent.md +1 -1
- package/commands/search.md +1 -1
- package/commands/timeline.md +1 -1
- package/commands/tools.md +9 -20
- package/commands/update.md +1 -1
- package/format-utils.mjs +0 -0
- package/haiku-client.mjs +0 -0
- package/hash-utils.mjs +0 -0
- package/hook-context.mjs +0 -0
- package/hook-episode.mjs +0 -0
- package/hook-handoff.mjs +1 -2
- package/hook-llm.mjs +35 -8
- package/hook-memory.mjs +9 -3
- package/hook-semaphore.mjs +0 -0
- package/hook-shared.mjs +0 -0
- package/hook-update.mjs +0 -0
- package/hook.mjs +19 -1
- package/hooks/hooks.json +10 -0
- package/install-metadata.mjs +0 -0
- package/install.mjs +23 -3
- package/mem-cli.mjs +155 -23
- package/nlp.mjs +0 -0
- package/package.json +5 -1
- package/project-utils.mjs +0 -0
- package/registry/preinstalled.json +0 -0
- package/registry-enricher.mjs +101 -0
- package/registry-github.mjs +54 -0
- package/registry-importer.mjs +352 -0
- package/registry-indexer.mjs +0 -0
- package/registry-retriever.mjs +0 -0
- package/registry-scanner.mjs +0 -0
- package/registry.mjs +36 -2
- package/resource-discovery.mjs +0 -0
- package/schema.mjs +0 -0
- package/scoring-sql.mjs +0 -0
- package/scripts/launch.mjs +0 -0
- package/scripts/pre-skill-bridge.js +78 -0
- package/scripts/pre-tool-recall.js +0 -0
- package/scripts/prompt-search-utils.mjs +47 -2
- package/scripts/user-prompt-search.js +105 -5
- package/secret-scrub.mjs +0 -0
- package/server-internals.mjs +0 -0
- package/server.mjs +184 -23
- package/skill.md +0 -0
- package/skip-tools.mjs +0 -0
- package/stop-words.mjs +0 -0
- package/synonyms.mjs +0 -0
- package/tfidf.mjs +0 -0
- package/tier.mjs +0 -0
- package/tool-schemas.mjs +9 -2
- package/utils.mjs +9 -2
package/install.mjs
CHANGED
|
@@ -452,7 +452,7 @@ async function install() {
|
|
|
452
452
|
]
|
|
453
453
|
};
|
|
454
454
|
|
|
455
|
-
const
|
|
455
|
+
const memPreToolRecall = {
|
|
456
456
|
matcher: 'Edit|Write|NotebookEdit',
|
|
457
457
|
hooks: [
|
|
458
458
|
{
|
|
@@ -463,10 +463,30 @@ async function install() {
|
|
|
463
463
|
]
|
|
464
464
|
};
|
|
465
465
|
|
|
466
|
+
const memPreSkillBridge = {
|
|
467
|
+
matcher: 'Skill',
|
|
468
|
+
hooks: [
|
|
469
|
+
{
|
|
470
|
+
type: 'command',
|
|
471
|
+
command: `node "${join(SCRIPTS_PATH, 'pre-skill-bridge.js')}"`,
|
|
472
|
+
timeout: 3
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
|
|
466
477
|
// Filter out existing mem hooks, then append fresh ones
|
|
467
|
-
|
|
478
|
+
// PreToolUse has two separate matchers, so we register both
|
|
479
|
+
const hookConfigs = {
|
|
480
|
+
PreToolUse: [memPreToolRecall, memPreSkillBridge],
|
|
481
|
+
PostToolUse: [memPostToolUse],
|
|
482
|
+
SessionStart: [memSessionStart],
|
|
483
|
+
Stop: [memStop],
|
|
484
|
+
UserPromptSubmit: [memUserPrompt],
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
for (const [event, configs] of Object.entries(hookConfigs)) {
|
|
468
488
|
const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event].filter(cfg => !isMemHook(cfg)) : [];
|
|
469
|
-
settings.hooks[event] = [...existing,
|
|
489
|
+
settings.hooks[event] = [...existing, ...configs];
|
|
470
490
|
}
|
|
471
491
|
|
|
472
492
|
writeSettings(settings);
|
package/mem-cli.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// claude-mem-lite CLI — lightweight command layer for direct memory access
|
|
3
3
|
// No MCP SDK or heavy deps — only imports schema.mjs and utils.mjs
|
|
4
4
|
|
|
5
|
+
import { homedir } from 'os';
|
|
5
6
|
import { ensureDb, DB_PATH, REGISTRY_DB_PATH, checkFTSIntegrity, rebuildFTS } from './schema.mjs';
|
|
6
7
|
import { sanitizeFtsQuery, relaxFtsQueryToOr, truncate, typeIcon, inferProject, jaccardSimilarity, computeMinHash, estimateJaccardFromMinHash, scrubSecrets, cjkBigrams, isoWeekKey, COMPRESSED_PENDING_PURGE, OBS_BM25, SESS_BM25, TYPE_DECAY_CASE, TYPE_QUALITY_CASE, DEFAULT_DECAY_HALF_LIFE_MS, getCurrentBranch } from './utils.mjs';
|
|
7
8
|
import { resolveProject } from './project-utils.mjs';
|
|
@@ -164,20 +165,6 @@ function cmdSearch(db, args) {
|
|
|
164
165
|
LIMIT ?
|
|
165
166
|
`).all(...typeParams);
|
|
166
167
|
}
|
|
167
|
-
// Tier post-filter
|
|
168
|
-
if (tier && obsRows.length > 0) {
|
|
169
|
-
const rowIds = obsRows.map(r => r.id);
|
|
170
|
-
const ph = rowIds.map(() => '?').join(',');
|
|
171
|
-
const fullRows = db.prepare(
|
|
172
|
-
`SELECT id, compressed_into, superseded_at, memory_session_id, project, importance, last_accessed_at, created_at_epoch, type FROM observations WHERE id IN (${ph})`
|
|
173
|
-
).all(...rowIds);
|
|
174
|
-
const rowMap = new Map(fullRows.map(r => [r.id, r]));
|
|
175
|
-
const tierCtx = { now: Date.now(), currentProject: project || inferProject(), currentSessionId: '' };
|
|
176
|
-
obsRows = obsRows.filter(r => {
|
|
177
|
-
const full = rowMap.get(r.id);
|
|
178
|
-
return full && computeTier(full, tierCtx) === tier;
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
168
|
for (const r of obsRows) results.push({ ...r, _source: 'obs', score: r.score ?? 0 });
|
|
182
169
|
|
|
183
170
|
// Concept co-occurrence + PRF expansion (aligned with MCP searchObservations)
|
|
@@ -222,6 +209,27 @@ function cmdSearch(db, args) {
|
|
|
222
209
|
}
|
|
223
210
|
}
|
|
224
211
|
}
|
|
212
|
+
|
|
213
|
+
// Tier post-filter — applied to ALL obs results (initial + expansion + PRF)
|
|
214
|
+
if (tier) {
|
|
215
|
+
const obsInResults = results.filter(r => r._source === 'obs');
|
|
216
|
+
if (obsInResults.length > 0) {
|
|
217
|
+
const obsIds = obsInResults.map(r => r.id);
|
|
218
|
+
const ph = obsIds.map(() => '?').join(',');
|
|
219
|
+
const fullRows = db.prepare(
|
|
220
|
+
`SELECT id, compressed_into, superseded_at, memory_session_id, project, importance, last_accessed_at, created_at_epoch, type FROM observations WHERE id IN (${ph})`
|
|
221
|
+
).all(...obsIds);
|
|
222
|
+
const rowMap = new Map(fullRows.map(r => [r.id, r]));
|
|
223
|
+
const tierCtx = { now: Date.now(), currentProject: project || inferProject(), currentSessionId: '' };
|
|
224
|
+
const allowedIds = new Set();
|
|
225
|
+
for (const [id, full] of rowMap) {
|
|
226
|
+
if (computeTier(full, tierCtx) === tier) allowedIds.add(id);
|
|
227
|
+
}
|
|
228
|
+
for (let i = results.length - 1; i >= 0; i--) {
|
|
229
|
+
if (results[i]._source === 'obs' && !allowedIds.has(results[i].id)) results.splice(i, 1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
// Search sessions (aligned with MCP mem_search)
|
|
@@ -1650,30 +1658,48 @@ function cmdRegistry(_memDb, args) {
|
|
|
1650
1658
|
results = results.slice(0, 5);
|
|
1651
1659
|
if (results.length === 0) { out(`[mem] No matching resources for: "${query}"`); return; }
|
|
1652
1660
|
out(`[mem] ${results.length} resource(s) for "${query}":`);
|
|
1661
|
+
const home = homedir();
|
|
1653
1662
|
for (const r of results) {
|
|
1654
1663
|
const badge = r.quality_tier === 'installed' ? '[✓]' : r.quality_tier === 'verified' ? '[★]' : '[○]';
|
|
1655
1664
|
const categoryLabel = r.category ? ` [${r.category}]` : '';
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1665
|
+
const isManaged = r.local_path && r.local_path.includes('/.claude-mem-lite/managed/');
|
|
1666
|
+
const portablePath = isManaged && r.local_path.startsWith(home) ? '~' + r.local_path.slice(home.length) : (r.local_path || '');
|
|
1667
|
+
let howToUse;
|
|
1668
|
+
if (isManaged) {
|
|
1669
|
+
const resolvedPath = portablePath.endsWith('.md') ? portablePath : `${portablePath}/SKILL.md`;
|
|
1670
|
+
howToUse = `Read("${resolvedPath}") or mem_use(name="${r.name}"${r.type === 'agent' ? ', type="agent"' : ''})`;
|
|
1671
|
+
} else if (r.invocation_name) {
|
|
1672
|
+
howToUse = r.type === 'skill'
|
|
1673
|
+
? `Skill("${r.invocation_name}")`
|
|
1674
|
+
: `Agent(subagent_type="${r.invocation_name}")`;
|
|
1675
|
+
} else {
|
|
1676
|
+
howToUse = `mem_use(name="${r.name}"${r.type === 'agent' ? ', type="agent"' : ''})`;
|
|
1677
|
+
}
|
|
1678
|
+
const pathLine = portablePath ? `\n Path: ${portablePath}` : '';
|
|
1679
|
+
out(` ${badge} ${r.type === 'skill' ? 'S' : 'A'} ${r.name}${categoryLabel} — ${truncate(r.capability_summary || '', 80)}${pathLine}\n Use: ${howToUse}`);
|
|
1660
1680
|
}
|
|
1661
1681
|
return;
|
|
1662
1682
|
}
|
|
1663
1683
|
|
|
1664
1684
|
if (action === 'list') {
|
|
1665
1685
|
const typeFilter = flags.type;
|
|
1686
|
+
const rawLimit = parseInt(flags.limit, 10);
|
|
1687
|
+
const listLimit = Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : 20;
|
|
1666
1688
|
const where = typeFilter ? 'WHERE type = ? AND status = ?' : 'WHERE status = ?';
|
|
1667
1689
|
const params = typeFilter ? [typeFilter, 'active'] : ['active'];
|
|
1668
|
-
const
|
|
1690
|
+
const allResources = rdb.prepare(`
|
|
1669
1691
|
SELECT name, type, invocation_name, recommend_count, adopt_count, capability_summary
|
|
1670
|
-
FROM resources ${where} ORDER BY type, name
|
|
1692
|
+
FROM resources ${where} ORDER BY adopt_count DESC, recommend_count DESC, type, name
|
|
1671
1693
|
`).all(...params);
|
|
1672
|
-
if (
|
|
1673
|
-
|
|
1694
|
+
if (allResources.length === 0) { out('[mem] No resources found.'); return; }
|
|
1695
|
+
const resources = allResources.slice(0, listLimit);
|
|
1696
|
+
out(`[mem] Resources (showing ${resources.length} of ${allResources.length}):`);
|
|
1674
1697
|
for (const r of resources) {
|
|
1675
1698
|
out(` ${r.type === 'skill' ? 'S' : 'A'} ${r.name}${r.invocation_name ? ` (${r.invocation_name})` : ''} — rec:${r.recommend_count} adopt:${r.adopt_count} — ${truncate(r.capability_summary || '', 50)}`);
|
|
1676
1699
|
}
|
|
1700
|
+
if (allResources.length > listLimit) {
|
|
1701
|
+
out(`[mem] Use --limit N to see more, or "registry search <query>" to find specific resources.`);
|
|
1702
|
+
}
|
|
1677
1703
|
return;
|
|
1678
1704
|
}
|
|
1679
1705
|
|
|
@@ -1831,7 +1857,7 @@ Commands:
|
|
|
1831
1857
|
--limit N Max entries per tier (default 5)
|
|
1832
1858
|
|
|
1833
1859
|
registry <action> Manage tool resource registry
|
|
1834
|
-
list List
|
|
1860
|
+
list List resources [--type skill|agent] [--limit N] (default 20)
|
|
1835
1861
|
stats Registry statistics
|
|
1836
1862
|
search <query> Search resources [--type skill|agent] [--category C] [--quality Q]
|
|
1837
1863
|
import Import resource --name N --resource-type T [--repo-url U] [--local-path P] [--use-cases U]
|
|
@@ -1841,6 +1867,110 @@ Commands:
|
|
|
1841
1867
|
DB: ${DB_PATH}`);
|
|
1842
1868
|
}
|
|
1843
1869
|
|
|
1870
|
+
// ─── Import (GitHub) ────────────────────────────────────────────────────────
|
|
1871
|
+
|
|
1872
|
+
async function cmdImport(db, argv) {
|
|
1873
|
+
const { positional, flags } = parseArgs(argv);
|
|
1874
|
+
const url = positional[0];
|
|
1875
|
+
|
|
1876
|
+
if (!url) { fail('[mem] Usage: claude-mem-lite import <github-url> [--enrich]'); return; }
|
|
1877
|
+
|
|
1878
|
+
let rdb;
|
|
1879
|
+
try {
|
|
1880
|
+
rdb = ensureRegistryDb(REGISTRY_DB_PATH);
|
|
1881
|
+
rdb.pragma('busy_timeout = 3000');
|
|
1882
|
+
} catch (e) {
|
|
1883
|
+
fail(`[mem] Registry DB error: ${e.message}`);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
try {
|
|
1888
|
+
const { importFromGitHub } = await import('./registry-importer.mjs');
|
|
1889
|
+
out(`[mem] Importing from ${url}...`);
|
|
1890
|
+
const results = await importFromGitHub(rdb, url);
|
|
1891
|
+
|
|
1892
|
+
if (results.length === 0) {
|
|
1893
|
+
out('[mem] No skills/agents found in this repository.');
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
out(`[mem] Imported ${results.length} resource(s):`);
|
|
1898
|
+
for (const r of results) {
|
|
1899
|
+
out(` ${r.type === 'skill' ? 'S' : 'A'} ${r.name} (id=${r.id})`);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (flags.enrich) {
|
|
1903
|
+
out('[mem] Running LLM enrichment...');
|
|
1904
|
+
const { enrichResource } = await import('./registry-enricher.mjs');
|
|
1905
|
+
let enriched = 0;
|
|
1906
|
+
for (const r of results) {
|
|
1907
|
+
const row = rdb.prepare('SELECT local_path FROM resources WHERE id = ?').get(r.id);
|
|
1908
|
+
if (!row?.local_path) continue;
|
|
1909
|
+
try {
|
|
1910
|
+
const content = readFileSync(row.local_path, 'utf8');
|
|
1911
|
+
const ok = await enrichResource(rdb, r.name, r.type, content);
|
|
1912
|
+
if (ok) enriched++;
|
|
1913
|
+
} catch {}
|
|
1914
|
+
}
|
|
1915
|
+
out(`[mem] Enriched ${enriched}/${results.length} resources.`);
|
|
1916
|
+
}
|
|
1917
|
+
} catch (e) {
|
|
1918
|
+
fail(`[mem] Import failed: ${e.message}`);
|
|
1919
|
+
} finally {
|
|
1920
|
+
try { rdb.close(); } catch {}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// ─── Enrich ─────────────────────────────────────────────────────────────────
|
|
1925
|
+
|
|
1926
|
+
async function cmdEnrich(db, argv) {
|
|
1927
|
+
const { positional, flags } = parseArgs(argv);
|
|
1928
|
+
const name = positional[0];
|
|
1929
|
+
|
|
1930
|
+
let rdb;
|
|
1931
|
+
try {
|
|
1932
|
+
rdb = ensureRegistryDb(REGISTRY_DB_PATH);
|
|
1933
|
+
rdb.pragma('busy_timeout = 3000');
|
|
1934
|
+
} catch (e) {
|
|
1935
|
+
fail(`[mem] Registry DB error: ${e.message}`);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
try {
|
|
1940
|
+
const { enrichResource } = await import('./registry-enricher.mjs');
|
|
1941
|
+
|
|
1942
|
+
if (flags.all) {
|
|
1943
|
+
const rows = rdb.prepare("SELECT name, type, local_path FROM resources WHERE status = 'active' AND (enrichment_status IS NULL OR enrichment_status = 'failed')").all();
|
|
1944
|
+
if (rows.length === 0) { out('[mem] All resources already enriched.'); return; }
|
|
1945
|
+
out(`[mem] Enriching ${rows.length} resources...`);
|
|
1946
|
+
let ok = 0, failCount = 0;
|
|
1947
|
+
for (const r of rows) {
|
|
1948
|
+
if (!r.local_path) { failCount++; continue; }
|
|
1949
|
+
try {
|
|
1950
|
+
const content = readFileSync(r.local_path, 'utf8');
|
|
1951
|
+
const success = await enrichResource(rdb, r.name, r.type, content);
|
|
1952
|
+
if (success) ok++; else failCount++;
|
|
1953
|
+
if (!flags.batch) await new Promise(resolve => setTimeout(resolve, 500));
|
|
1954
|
+
} catch { failCount++; }
|
|
1955
|
+
}
|
|
1956
|
+
out(`[mem] Done: ${ok} enriched, ${failCount} failed.`);
|
|
1957
|
+
} else if (name) {
|
|
1958
|
+
const row = rdb.prepare("SELECT name, type, local_path FROM resources WHERE name = ? AND status = 'active'").get(name);
|
|
1959
|
+
if (!row) { fail(`[mem] Resource not found: ${name}`); return; }
|
|
1960
|
+
if (!row.local_path) { fail(`[mem] No local_path for ${name}`); return; }
|
|
1961
|
+
const content = readFileSync(row.local_path, 'utf8');
|
|
1962
|
+
const success = await enrichResource(rdb, row.name, row.type, content);
|
|
1963
|
+
out(success ? `[mem] Enriched: ${name}` : `[mem] Enrichment failed for ${name}`);
|
|
1964
|
+
} else {
|
|
1965
|
+
fail('[mem] Usage: claude-mem-lite enrich <name> OR claude-mem-lite enrich --all [--batch]');
|
|
1966
|
+
}
|
|
1967
|
+
} catch (e) {
|
|
1968
|
+
fail(`[mem] Enrich error: ${e.message}`);
|
|
1969
|
+
} finally {
|
|
1970
|
+
try { rdb.close(); } catch {}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1844
1974
|
// ─── Main Entry Point ────────────────────────────────────────────────────────
|
|
1845
1975
|
|
|
1846
1976
|
export async function run(argv) {
|
|
@@ -1886,6 +2016,8 @@ export async function run(argv) {
|
|
|
1886
2016
|
case 'context': cmdContext(db, cmdArgs); break;
|
|
1887
2017
|
case 'browse': cmdBrowse(db, cmdArgs); break;
|
|
1888
2018
|
case 'registry': cmdRegistry(db, cmdArgs); break;
|
|
2019
|
+
case 'import': await cmdImport(db, cmdArgs); break;
|
|
2020
|
+
case 'enrich': await cmdEnrich(db, cmdArgs); break;
|
|
1889
2021
|
default:
|
|
1890
2022
|
out(`[mem] Unknown command: ${cmd}`);
|
|
1891
2023
|
out('[mem] Run "claude-mem-lite help" for usage');
|
package/nlp.mjs
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.1",
|
|
4
4
|
"description": "Lightweight persistent memory system for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"registry-retriever.mjs",
|
|
41
41
|
"registry-indexer.mjs",
|
|
42
42
|
"registry-scanner.mjs",
|
|
43
|
+
"registry-github.mjs",
|
|
44
|
+
"registry-importer.mjs",
|
|
45
|
+
"registry-enricher.mjs",
|
|
43
46
|
"resource-discovery.mjs",
|
|
44
47
|
"install.mjs",
|
|
45
48
|
"install-metadata.mjs",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"scripts/post-tool-use.sh",
|
|
74
77
|
"scripts/user-prompt-search.js",
|
|
75
78
|
"scripts/pre-tool-recall.js",
|
|
79
|
+
"scripts/pre-skill-bridge.js",
|
|
76
80
|
"scripts/prompt-search-utils.mjs",
|
|
77
81
|
".mcp.json",
|
|
78
82
|
".claude-plugin/plugin.json",
|
package/project-utils.mjs
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// claude-mem-lite: LLM enrichment for registry resources (Stage 2)
|
|
2
|
+
// Sends resource content to Haiku for semantic metadata generation
|
|
3
|
+
// Graceful degradation: failure preserves existing data
|
|
4
|
+
|
|
5
|
+
import { callHaikuJSON } from './haiku-client.mjs';
|
|
6
|
+
import { truncate, debugCatch } from './utils.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build the enrichment prompt for Haiku.
|
|
10
|
+
* @param {string} name Resource name
|
|
11
|
+
* @param {string} content SKILL.md/AGENT.md content
|
|
12
|
+
* @param {object} existingMeta Existing metadata from DB
|
|
13
|
+
* @returns {string} Prompt string
|
|
14
|
+
*/
|
|
15
|
+
export function buildEnrichPrompt(name, content, existingMeta) {
|
|
16
|
+
const truncated = truncate(content, 3000);
|
|
17
|
+
const existing = existingMeta.intent_tags ? `\n<existing-metadata>\ncurrent_tags: ${existingMeta.intent_tags}\n</existing-metadata>` : '';
|
|
18
|
+
|
|
19
|
+
return `You are a tool classification expert. Analyze this Claude Code skill and extract structured metadata.
|
|
20
|
+
|
|
21
|
+
<skill-name>${name}</skill-name>
|
|
22
|
+
<skill-content>
|
|
23
|
+
${truncated}
|
|
24
|
+
</skill-content>
|
|
25
|
+
${existing}
|
|
26
|
+
Return JSON only:
|
|
27
|
+
{"capability_summary":"One sentence (<80 chars)","intent_tags":"comma-separated intents","domain_tags":"comma-separated domains","trigger_patterns":"when to recommend","use_cases":"comma-separated scenarios","tech_stack":"comma-separated tech","quality_assessment":{"has_clear_instructions":true,"has_examples":false,"specificity":"high","estimated_utility":"high"}}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply LLM enrichment results to a resource.
|
|
32
|
+
* Only fills empty fields — never overwrites existing non-empty data.
|
|
33
|
+
* @param {Database} db Registry database
|
|
34
|
+
* @param {string} name Resource name
|
|
35
|
+
* @param {string} type Resource type
|
|
36
|
+
* @param {object} enrichResult LLM result JSON
|
|
37
|
+
*/
|
|
38
|
+
export function applyEnrichment(db, name, type, enrichResult) {
|
|
39
|
+
const row = db.prepare('SELECT * FROM resources WHERE name = ? AND type = ?').get(name, type);
|
|
40
|
+
if (!row) return;
|
|
41
|
+
|
|
42
|
+
const updates = [];
|
|
43
|
+
const params = [];
|
|
44
|
+
|
|
45
|
+
// Only fill empty fields
|
|
46
|
+
const fields = ['capability_summary', 'intent_tags', 'domain_tags', 'trigger_patterns', 'use_cases', 'tech_stack'];
|
|
47
|
+
for (const f of fields) {
|
|
48
|
+
if ((!row[f] || row[f].trim() === '') && enrichResult[f]) {
|
|
49
|
+
updates.push(`${f} = ?`);
|
|
50
|
+
params.push(enrichResult[f]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Always update enrichment status
|
|
55
|
+
updates.push("enrichment_status = 'done'");
|
|
56
|
+
updates.push('enriched_at = ?');
|
|
57
|
+
params.push(Date.now());
|
|
58
|
+
|
|
59
|
+
// Quality tier upgrade based on assessment
|
|
60
|
+
const qa = enrichResult.quality_assessment;
|
|
61
|
+
if (qa && row.quality_tier === 'community') {
|
|
62
|
+
if (qa.has_clear_instructions && (qa.specificity === 'high' || qa.specificity === 'medium')) {
|
|
63
|
+
updates.push("quality_tier = 'verified'");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
params.push(name, type);
|
|
68
|
+
db.prepare(`UPDATE resources SET ${updates.join(', ')} WHERE name = ? AND type = ?`).run(...params);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Enrich a single resource via Haiku LLM.
|
|
73
|
+
* @param {Database} db Registry database
|
|
74
|
+
* @param {string} name Resource name
|
|
75
|
+
* @param {string} type Resource type
|
|
76
|
+
* @param {string} content SKILL.md/AGENT.md content
|
|
77
|
+
* @returns {Promise<boolean>} true if enriched successfully
|
|
78
|
+
*/
|
|
79
|
+
export async function enrichResource(db, name, type, content) {
|
|
80
|
+
const existing = db.prepare('SELECT intent_tags, capability_summary FROM resources WHERE name = ? AND type = ?').get(name, type);
|
|
81
|
+
if (!existing) return false;
|
|
82
|
+
|
|
83
|
+
db.prepare("UPDATE resources SET enrichment_status = 'pending' WHERE name = ? AND type = ?").run(name, type);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const prompt = buildEnrichPrompt(name, content, existing);
|
|
87
|
+
const result = await callHaikuJSON(prompt, { timeout: 15000, maxTokens: 500 });
|
|
88
|
+
|
|
89
|
+
if (!result || !result.capability_summary) {
|
|
90
|
+
db.prepare("UPDATE resources SET enrichment_status = 'failed' WHERE name = ? AND type = ?").run(name, type);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
applyEnrichment(db, name, type, result);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
debugCatch(e, 'enricher');
|
|
98
|
+
db.prepare("UPDATE resources SET enrichment_status = 'failed' WHERE name = ? AND type = ?").run(name, type);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// claude-mem-lite: GitHub API helpers for smart import
|
|
2
|
+
// Pure functions for URL parsing and API URL construction
|
|
3
|
+
// Actual HTTP calls are in registry-importer.mjs
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a GitHub URL into owner, repo, branch, path.
|
|
7
|
+
* @param {string} url GitHub URL
|
|
8
|
+
* @returns {{ owner: string, repo: string, branch: string, path: string } | null}
|
|
9
|
+
*/
|
|
10
|
+
export function parseGitHubUrl(url) {
|
|
11
|
+
if (!url || typeof url !== 'string') return null;
|
|
12
|
+
const match = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?(?:\/tree\/([^/]+)(\/.*)?)?$/);
|
|
13
|
+
if (!match) return null;
|
|
14
|
+
const [, owner, repo, branch, pathRaw] = match;
|
|
15
|
+
return {
|
|
16
|
+
owner,
|
|
17
|
+
repo,
|
|
18
|
+
branch: branch || 'main',
|
|
19
|
+
path: pathRaw ? pathRaw.replace(/^\//, '') : '',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build GitHub API tree URL (recursive).
|
|
25
|
+
*/
|
|
26
|
+
export function buildTreeUrl(owner, repo, branch) {
|
|
27
|
+
return `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build raw content URL for a file.
|
|
32
|
+
*/
|
|
33
|
+
export function buildContentUrl(owner, repo, branch, path) {
|
|
34
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build GitHub API repo metadata URL.
|
|
39
|
+
*/
|
|
40
|
+
export function buildRepoUrl(owner, repo) {
|
|
41
|
+
return `https://api.github.com/repos/${owner}/${repo}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build headers for GitHub API requests.
|
|
46
|
+
* Uses GITHUB_TOKEN env var if available.
|
|
47
|
+
* @returns {Record<string, string>}
|
|
48
|
+
*/
|
|
49
|
+
export function buildHeaders() {
|
|
50
|
+
const headers = { 'User-Agent': 'claude-mem-lite', Accept: 'application/vnd.github.v3+json' };
|
|
51
|
+
const token = process.env.GITHUB_TOKEN;
|
|
52
|
+
if (token) headers.Authorization = `token ${token}`;
|
|
53
|
+
return headers;
|
|
54
|
+
}
|