promptgraph-mcp 2.1.3 → 2.1.4
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/chunker.js +1 -1
- package/db.js +9 -0
- package/embedder.js +1 -1
- package/index.js +3 -1
- package/indexer.js +36 -22
- package/package.json +1 -1
- package/search.js +27 -12
package/chunker.js
CHANGED
package/db.js
CHANGED
|
@@ -46,6 +46,15 @@ export function getDb() {
|
|
|
46
46
|
success INTEGER DEFAULT 0,
|
|
47
47
|
fail INTEGER DEFAULT 0
|
|
48
48
|
);
|
|
49
|
+
|
|
50
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS skills_fts USING fts5(
|
|
51
|
+
id UNINDEXED,
|
|
52
|
+
name,
|
|
53
|
+
description,
|
|
54
|
+
content,
|
|
55
|
+
content='skills',
|
|
56
|
+
content_rowid='rowid'
|
|
57
|
+
);
|
|
49
58
|
`);
|
|
50
59
|
|
|
51
60
|
// migrate: add hash column if missing
|
package/embedder.js
CHANGED
package/index.js
CHANGED
|
@@ -565,7 +565,9 @@ if (args[0] === 'update') {
|
|
|
565
565
|
|
|
566
566
|
if (args[0] === 'reindex') {
|
|
567
567
|
const { indexAll } = await import('./indexer.js');
|
|
568
|
-
|
|
568
|
+
const fast = args.includes('--fast');
|
|
569
|
+
if (fast) info(chalk.yellow('Fast mode — skipping embeddings (keyword search only)'));
|
|
570
|
+
await indexAll({ fast });
|
|
569
571
|
process.exit(0);
|
|
570
572
|
}
|
|
571
573
|
|
package/indexer.js
CHANGED
|
@@ -11,7 +11,7 @@ import { buildAnnIndex } from './ann.js';
|
|
|
11
11
|
import { progress, progressDone, success, info, spinner } from './cli.js';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
|
|
14
|
-
async function indexBatch(db, skills) {
|
|
14
|
+
async function indexBatch(db, skills, { fast = false } = {}) {
|
|
15
15
|
const upsertSkill = db.prepare(`
|
|
16
16
|
INSERT INTO skills (id, name, description, path, source, content, hash)
|
|
17
17
|
VALUES (@id, @name, @description, @path, @source, @content, @hash)
|
|
@@ -26,32 +26,43 @@ async function indexBatch(db, skills) {
|
|
|
26
26
|
const deleteEdges = db.prepare('DELETE FROM edges WHERE from_skill = ?');
|
|
27
27
|
const upsertChunk = db.prepare('INSERT OR REPLACE INTO chunks (skill_id, chunk_index, text, embedding) VALUES (?, ?, ?, ?)');
|
|
28
28
|
const upsertEdge = db.prepare('INSERT OR IGNORE INTO edges (from_skill, to_skill) VALUES (?, ?)');
|
|
29
|
+
const upsertFts = db.prepare(`INSERT OR REPLACE INTO skills_fts(id, name, description, content) VALUES (?, ?, ?, ?)`);
|
|
29
30
|
|
|
30
31
|
const allChunks = [];
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if (!fast) {
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
const id = skillId(skill.source, skill.name);
|
|
35
|
+
const chunks = chunkText(skill.name + ' ' + skill.description + '\n' + skill.content);
|
|
36
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
37
|
+
allChunks.push({ id, skill, chunkIndex: i, text: chunks[i] });
|
|
38
|
+
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
let embeddings = [];
|
|
43
|
+
if (!fast && allChunks.length) {
|
|
44
|
+
const texts = allChunks.map(c => c.text);
|
|
45
|
+
process.stdout.write(` Embedding ${texts.length} chunks...`);
|
|
46
|
+
embeddings = await embedBatch(texts);
|
|
47
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
48
|
+
}
|
|
43
49
|
|
|
44
50
|
// pass 1: upsert all skills + chunks (no edges yet)
|
|
45
51
|
db.transaction(() => {
|
|
46
52
|
for (const skill of skills) {
|
|
47
53
|
const id = skillId(skill.source, skill.name);
|
|
48
54
|
upsertSkill.run({ id, name: skill.name, description: skill.description, path: skill.path, source: skill.source, content: skill.content, hash: skill.hash || null });
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
upsertFts.run(id, skill.name, skill.description || '', skill.content || '');
|
|
56
|
+
if (!fast) {
|
|
57
|
+
deleteChunks.run(id);
|
|
58
|
+
deleteEdges.run(id);
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
if (!fast) {
|
|
62
|
+
for (let i = 0; i < allChunks.length; i++) {
|
|
63
|
+
const { id, chunkIndex, text } = allChunks[i];
|
|
64
|
+
upsertChunk.run(id, chunkIndex, text, vecToBlob(embeddings[i]));
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
})();
|
|
57
68
|
|
|
@@ -71,7 +82,7 @@ async function indexBatch(db, skills) {
|
|
|
71
82
|
})();
|
|
72
83
|
}
|
|
73
84
|
|
|
74
|
-
export async function indexAll() {
|
|
85
|
+
export async function indexAll({ fast = false } = {}) {
|
|
75
86
|
const config = loadConfig();
|
|
76
87
|
const db = getDb();
|
|
77
88
|
|
|
@@ -163,7 +174,7 @@ export async function indexAll() {
|
|
|
163
174
|
batch.push({ ...parsed, hash });
|
|
164
175
|
|
|
165
176
|
if (batch.length >= BATCH_SIZE) {
|
|
166
|
-
await indexBatch(db, batch);
|
|
177
|
+
await indexBatch(db, batch, { fast });
|
|
167
178
|
count += batch.length;
|
|
168
179
|
batch = [];
|
|
169
180
|
const eta = count > 0 ? Math.round((total - count) * (Date.now() - start) / count / 1000) : '?';
|
|
@@ -185,17 +196,20 @@ export async function indexAll() {
|
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
if (batch.length > 0) {
|
|
188
|
-
await indexBatch(db, batch);
|
|
199
|
+
await indexBatch(db, batch, { fast });
|
|
189
200
|
count += batch.length;
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
progress(total, total, { skipped, errors });
|
|
193
204
|
progressDone();
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
if (!fast) {
|
|
206
|
+
const spin = spinner('Building ANN index...');
|
|
207
|
+
spin.start();
|
|
208
|
+
await buildAnnIndex();
|
|
209
|
+
spin.stop();
|
|
210
|
+
}
|
|
198
211
|
success(`Indexed ${chalk.white.bold(count)} skills ${chalk.gray(`(${errors} errors, ${skipped} skipped, ${removed} removed)`)}`);
|
|
212
|
+
if (fast) info(chalk.yellow('Fast mode: keyword search only. Run `pg reindex` for semantic search.'));
|
|
199
213
|
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
200
214
|
info(chalk.gray(`Time: ${elapsed}s`));
|
|
201
215
|
}
|
package/package.json
CHANGED
package/search.js
CHANGED
|
@@ -32,20 +32,35 @@ export async function search(query, topK = 5) {
|
|
|
32
32
|
.filter(Boolean);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Fallback: brute force (used before first reindex)
|
|
35
|
+
// Fallback: brute force cosine (used before first reindex)
|
|
36
36
|
const chunks = db.prepare('SELECT skill_id, embedding FROM chunks').all();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
if (chunks.length > 0) {
|
|
38
|
+
const bestBySkill = new Map();
|
|
39
|
+
for (const chunk of chunks) {
|
|
40
|
+
const score = cosineSimilarity(queryVec, blobToVec(chunk.embedding));
|
|
41
|
+
const prev = bestBySkill.get(chunk.skill_id);
|
|
42
|
+
if (!prev || score > prev) bestBySkill.set(chunk.skill_id, score);
|
|
43
|
+
}
|
|
44
|
+
return [...bestBySkill.entries()]
|
|
45
|
+
.map(([id, score]) => ({ id, score: applyRatingBoost(db, id, score) }))
|
|
46
|
+
.sort((a, b) => b.score - a.score)
|
|
47
|
+
.slice(0, topK)
|
|
48
|
+
.map(({ id, score }) => skillWithSnippet(db, id, score))
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fast-mode fallback: FTS5 keyword search (no embeddings)
|
|
53
|
+
try {
|
|
54
|
+
const terms = query.replace(/[^\w\s]/g, ' ').trim().split(/\s+/).filter(Boolean).join(' OR ');
|
|
55
|
+
const rows = db.prepare(
|
|
56
|
+
`SELECT s.id, bm25(skills_fts) AS score FROM skills_fts
|
|
57
|
+
JOIN skills s ON skills_fts.id = s.id
|
|
58
|
+
WHERE skills_fts MATCH ? ORDER BY score LIMIT ?`
|
|
59
|
+
).all(terms, topK);
|
|
60
|
+
return rows.map(r => skillWithSnippet(db, r.id, Math.max(0, 1 + r.score / 10))).filter(Boolean);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
42
63
|
}
|
|
43
|
-
return [...bestBySkill.entries()]
|
|
44
|
-
.map(([id, score]) => ({ id, score: applyRatingBoost(db, id, score) }))
|
|
45
|
-
.sort((a, b) => b.score - a.score)
|
|
46
|
-
.slice(0, topK)
|
|
47
|
-
.map(({ id, score }) => skillWithSnippet(db, id, score))
|
|
48
|
-
.filter(Boolean);
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
function skillWithSnippet(db, id, score) {
|