promptgraph-mcp 1.5.10 → 1.5.12
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/ann.js +18 -6
- package/config.js +1 -1
- package/doctor.js +48 -0
- package/index.js +25 -2
- package/indexer.js +5 -1
- package/package.json +6 -2
package/ann.js
CHANGED
|
@@ -23,14 +23,24 @@ export async function buildAnnIndex() {
|
|
|
23
23
|
const db = getDb();
|
|
24
24
|
const chunks = db.prepare('SELECT skill_id, chunk_index, embedding FROM chunks').all();
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// Batch ALL inserts into a single disk write — vectra otherwise
|
|
27
|
+
// persists the whole index on every insertItem (O(N^2) I/O).
|
|
28
|
+
await index.beginUpdate();
|
|
29
|
+
try {
|
|
30
|
+
for (const chunk of chunks) {
|
|
31
|
+
const vec = JSON.parse(chunk.embedding);
|
|
32
|
+
await index.insertItem({
|
|
33
|
+
vector: vec,
|
|
34
|
+
metadata: { skill_id: chunk.skill_id, chunk_index: chunk.chunk_index },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
await index.endUpdate();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
try { index.cancelUpdate(); } catch {}
|
|
40
|
+
throw e;
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
_index = null; // force reload so queries see fresh index
|
|
34
44
|
console.error(`[PromptGraph] ANN index built: ${chunks.length} chunks`);
|
|
35
45
|
}
|
|
36
46
|
|
|
@@ -38,6 +48,8 @@ export async function annSearch(queryVec, topK = 20) {
|
|
|
38
48
|
try {
|
|
39
49
|
const index = await getIndex();
|
|
40
50
|
if (!await index.isIndexCreated()) return null;
|
|
51
|
+
const items = await index.listItems();
|
|
52
|
+
if (!items || items.length === 0) return null;
|
|
41
53
|
|
|
42
54
|
const results = await index.queryItems(queryVec, topK);
|
|
43
55
|
return results.map(r => ({
|
package/config.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function promptConfig() {
|
|
|
37
37
|
const extra = await ask('\nAdd extra skill directories? (comma-separated paths, or press Enter to skip): ');
|
|
38
38
|
rl.close();
|
|
39
39
|
|
|
40
|
-
const config =
|
|
40
|
+
const config = structuredClone(DEFAULTS);
|
|
41
41
|
|
|
42
42
|
if (extra.trim()) {
|
|
43
43
|
const extraDirs = extra.split(',').map(d => d.trim()).filter(Boolean);
|
package/doctor.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getDb } from './db.js';
|
|
2
|
+
|
|
3
|
+
export function runDoctor() {
|
|
4
|
+
const db = getDb();
|
|
5
|
+
const report = {};
|
|
6
|
+
|
|
7
|
+
// orphaned chunks (skill_id not in skills)
|
|
8
|
+
const orphanChunks = db.prepare(`
|
|
9
|
+
DELETE FROM chunks WHERE skill_id NOT IN (SELECT id FROM skills)
|
|
10
|
+
`).run();
|
|
11
|
+
report.orphanChunks = orphanChunks.changes;
|
|
12
|
+
|
|
13
|
+
// orphaned ratings
|
|
14
|
+
const orphanRatings = db.prepare(`
|
|
15
|
+
DELETE FROM ratings WHERE skill_id NOT IN (SELECT id FROM skills)
|
|
16
|
+
`).run();
|
|
17
|
+
report.orphanRatings = orphanRatings.changes;
|
|
18
|
+
|
|
19
|
+
// orphaned edges where from_skill no longer exists
|
|
20
|
+
const orphanFromEdges = db.prepare(`
|
|
21
|
+
DELETE FROM edges WHERE from_skill NOT IN (SELECT id FROM skills)
|
|
22
|
+
`).run();
|
|
23
|
+
report.orphanFromEdges = orphanFromEdges.changes;
|
|
24
|
+
|
|
25
|
+
// dangling edges where to_skill is a bare name that never resolved to a real skill
|
|
26
|
+
// (keep edges that point to real ids OR bare names that match a skill name)
|
|
27
|
+
const danglingEdges = db.prepare(`
|
|
28
|
+
DELETE FROM edges
|
|
29
|
+
WHERE to_skill NOT IN (SELECT id FROM skills)
|
|
30
|
+
AND to_skill NOT IN (SELECT name FROM skills)
|
|
31
|
+
`).run();
|
|
32
|
+
report.danglingEdges = danglingEdges.changes;
|
|
33
|
+
|
|
34
|
+
// duplicate skills by path (should not happen, but check)
|
|
35
|
+
const dupPaths = db.prepare(`
|
|
36
|
+
SELECT path, COUNT(*) as c FROM skills GROUP BY path HAVING c > 1
|
|
37
|
+
`).all();
|
|
38
|
+
report.duplicatePaths = dupPaths.length;
|
|
39
|
+
|
|
40
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
41
|
+
db.exec('VACUUM');
|
|
42
|
+
|
|
43
|
+
report.totalSkills = db.prepare('SELECT COUNT(*) as c FROM skills').get().c;
|
|
44
|
+
report.totalChunks = db.prepare('SELECT COUNT(*) as c FROM chunks').get().c;
|
|
45
|
+
report.totalEdges = db.prepare('SELECT COUNT(*) as c FROM edges').get().c;
|
|
46
|
+
|
|
47
|
+
return report;
|
|
48
|
+
}
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const args = process.argv.slice(2);
|
|
|
20
20
|
const rawBin = process.argv[1]?.split(/[\\/]/).pop()?.replace(/\.js$/, '');
|
|
21
21
|
const bin = (rawBin && rawBin !== 'index') ? rawBin : 'pg';
|
|
22
22
|
|
|
23
|
-
const KNOWN_COMMANDS = new Set(['init', 'reindex', 'import', 'setup', 'validate', 'marketplace', 'help', '--help', '-h']);
|
|
23
|
+
const KNOWN_COMMANDS = new Set(['init', 'reindex', 'import', 'setup', 'validate', 'marketplace', 'doctor', 'help', '--help', '-h']);
|
|
24
24
|
|
|
25
25
|
function showHelp() {
|
|
26
26
|
console.log(
|
|
@@ -37,6 +37,7 @@ function showHelp() {
|
|
|
37
37
|
['import <owner/repo>', 'Import skills from GitHub'],
|
|
38
38
|
['marketplace [page]', 'Browse the community skill registry'],
|
|
39
39
|
['validate <file.md>', 'Validate a skill before publishing'],
|
|
40
|
+
['doctor', 'Clean orphaned chunks/edges/ratings'],
|
|
40
41
|
['setup <platform>', 'Register MCP in platform config'],
|
|
41
42
|
['help', 'Show this help'],
|
|
42
43
|
];
|
|
@@ -47,7 +48,16 @@ function showHelp() {
|
|
|
47
48
|
console.log(chalk.gray('\n github.com/NeiP4n/promptgraph · npmjs.com/package/promptgraph-mcp\n'));
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// Explicit help request always shows help.
|
|
52
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
|
|
53
|
+
showHelp();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// No args: if launched from an interactive terminal, show help.
|
|
58
|
+
// If stdin is a pipe (i.e. an MCP client like Claude), fall through and
|
|
59
|
+
// start the server — NEVER print to stdout here, it corrupts JSON-RPC.
|
|
60
|
+
if (!args[0] && process.stdin.isTTY) {
|
|
51
61
|
showHelp();
|
|
52
62
|
process.exit(0);
|
|
53
63
|
}
|
|
@@ -58,6 +68,19 @@ if (!KNOWN_COMMANDS.has(args[0])) {
|
|
|
58
68
|
process.exit(1);
|
|
59
69
|
}
|
|
60
70
|
|
|
71
|
+
if (args[0] === 'doctor') {
|
|
72
|
+
const { runDoctor } = await import('./doctor.js');
|
|
73
|
+
const spin = (await import('./cli.js')).spinner('Checking database...');
|
|
74
|
+
spin.start();
|
|
75
|
+
const r = runDoctor();
|
|
76
|
+
spin.stop();
|
|
77
|
+
success('Database checked');
|
|
78
|
+
info(`Removed: ${r.orphanChunks} chunks, ${r.orphanRatings} ratings, ${r.orphanFromEdges + r.danglingEdges} edges`);
|
|
79
|
+
if (r.duplicatePaths > 0) info(chalk.yellow(`Warning: ${r.duplicatePaths} duplicate paths`));
|
|
80
|
+
info(chalk.gray(`Now: ${r.totalSkills} skills, ${r.totalChunks} chunks, ${r.totalEdges} edges`));
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
if (args[0] === 'marketplace') {
|
|
62
85
|
const { browseMarketplace } = await import('./marketplace.js');
|
|
63
86
|
const PER_PAGE = 10;
|
package/indexer.js
CHANGED
|
@@ -58,11 +58,15 @@ async function indexBatch(db, skills) {
|
|
|
58
58
|
})();
|
|
59
59
|
|
|
60
60
|
// pass 2: resolve edges after all skills in batch are committed
|
|
61
|
+
const resolveSameSource = db.prepare("SELECT id FROM skills WHERE name = ? AND source = ? LIMIT 1");
|
|
62
|
+
const resolveAny = db.prepare("SELECT id FROM skills WHERE name = ? ORDER BY id LIMIT 1");
|
|
61
63
|
db.transaction(() => {
|
|
62
64
|
for (const skill of skills) {
|
|
63
65
|
const id = skillId(skill.source, skill.name);
|
|
64
66
|
for (const calledName of skill.calls) {
|
|
65
|
-
|
|
67
|
+
// prefer a skill in the same source, fall back to any, then bare name
|
|
68
|
+
const same = resolveSameSource.get(calledName, skill.source);
|
|
69
|
+
const resolved = same || resolveAny.get(calledName);
|
|
66
70
|
upsertEdge.run(id, resolved ? resolved.id : calledName);
|
|
67
71
|
}
|
|
68
72
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptgraph-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.12",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "node index.js",
|
|
13
13
|
"init": "node index.js init",
|
|
14
|
-
"reindex": "node index.js reindex"
|
|
14
|
+
"reindex": "node index.js reindex",
|
|
15
|
+
"test": "vitest run"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"claude",
|
|
@@ -43,5 +44,8 @@
|
|
|
43
44
|
"gray-matter": "^4.0.3",
|
|
44
45
|
"ora": "^9.4.0",
|
|
45
46
|
"vectra": "^0.15.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"vitest": "^4.1.8"
|
|
46
50
|
}
|
|
47
51
|
}
|