promptgraph-mcp 1.5.1 → 1.5.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.
- package/README.md +1 -1
- package/config.js +2 -1
- package/db.js +4 -0
- package/github-import.js +5 -2
- package/indexer.js +42 -20
- package/package.json +1 -1
- package/parser.js +11 -2
- package/pg-hook.js +20 -12
- package/search.js +21 -8
- package/watcher.js +72 -50
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Claude Code loads all `.md` files from `~/.claude/commands/` into the system pro
|
|
|
19
19
|
... ← 40+ skills, NOT loaded into context
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
When you ask Claude a question, it calls `pg_search("your task")` → finds the right skill via vector search → reads only that file
|
|
22
|
+
When you ask Claude a question, it calls `pg_search("your task")` → finds the right skill via vector search → returns the path. Claude then reads only that file instead of having all skills preloaded in context.
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
package/config.js
CHANGED
|
@@ -17,7 +17,8 @@ export function loadConfig() {
|
|
|
17
17
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
18
18
|
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
// deep copy to avoid mutating DEFAULTS
|
|
21
|
+
return JSON.parse(JSON.stringify(DEFAULTS));
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function saveConfig(config) {
|
package/db.js
CHANGED
|
@@ -5,9 +5,13 @@ import fs from 'fs';
|
|
|
5
5
|
|
|
6
6
|
const DB_PATH = path.join(os.homedir(), '.claude', '.promptgraph', 'promptgraph.db');
|
|
7
7
|
|
|
8
|
+
let _db = null;
|
|
9
|
+
|
|
8
10
|
export function getDb() {
|
|
11
|
+
if (_db) return _db;
|
|
9
12
|
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
10
13
|
const db = new Database(DB_PATH);
|
|
14
|
+
_db = db;
|
|
11
15
|
db.pragma('journal_mode = WAL');
|
|
12
16
|
|
|
13
17
|
db.exec(`
|
package/github-import.js
CHANGED
|
@@ -22,10 +22,13 @@ export async function importFromGitHub(repoUrl) {
|
|
|
22
22
|
|
|
23
23
|
if (fs.existsSync(dest)) {
|
|
24
24
|
console.log(`Updating ${repoName}...`);
|
|
25
|
-
execSync(
|
|
25
|
+
execSync('git', { stdio: 'inherit', args: ['-C', dest, 'pull', '--depth=1'] });
|
|
26
26
|
} else {
|
|
27
27
|
console.log(`Cloning ${url}...`);
|
|
28
|
-
|
|
28
|
+
// use spawnSync to avoid shell injection
|
|
29
|
+
const { spawnSync } = await import('child_process');
|
|
30
|
+
const result = spawnSync('git', ['clone', '--depth=1', url, dest], { stdio: 'inherit' });
|
|
31
|
+
if (result.status !== 0) throw new Error(`git clone failed for ${url}`);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const mdFiles = globSync(`${dest}/**/*.md`);
|
package/indexer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { globSync } from 'glob';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import { parseSkillFile } from './parser.js';
|
|
4
|
+
import { parseSkillFile, isSkillFile } from './parser.js';
|
|
5
5
|
import { embedBatch, BATCH_SIZE } from './embedder.js';
|
|
6
6
|
import { getDb, skillId } from './db.js';
|
|
7
7
|
import { loadConfig } from './config.js';
|
|
@@ -31,7 +31,6 @@ async function indexBatch(db, skills) {
|
|
|
31
31
|
const upsertChunk = db.prepare('INSERT OR REPLACE INTO chunks (skill_id, chunk_index, text, embedding) VALUES (?, ?, ?, ?)');
|
|
32
32
|
const upsertEdge = db.prepare('INSERT OR IGNORE INTO edges (from_skill, to_skill) VALUES (?, ?)');
|
|
33
33
|
|
|
34
|
-
// collect all chunks across skills in batch
|
|
35
34
|
const allChunks = [];
|
|
36
35
|
for (const skill of skills) {
|
|
37
36
|
const id = skillId(skill.source, skill.name);
|
|
@@ -41,7 +40,6 @@ async function indexBatch(db, skills) {
|
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
// embed all chunks in one batch call
|
|
45
43
|
const texts = allChunks.map(c => c.text);
|
|
46
44
|
const embeddings = await embedBatch(texts);
|
|
47
45
|
|
|
@@ -51,8 +49,9 @@ async function indexBatch(db, skills) {
|
|
|
51
49
|
upsertSkill.run({ id, name: skill.name, description: skill.description, path: skill.path, source: skill.source, content: skill.content, hash: skill.hash || null });
|
|
52
50
|
deleteChunks.run(id);
|
|
53
51
|
deleteEdges.run(id);
|
|
54
|
-
for (const
|
|
55
|
-
|
|
52
|
+
for (const calledName of skill.calls) {
|
|
53
|
+
const resolved = db.prepare("SELECT id FROM skills WHERE name = ? ORDER BY id LIMIT 1").get(calledName);
|
|
54
|
+
upsertEdge.run(id, resolved ? resolved.id : calledName);
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
for (let i = 0; i < allChunks.length; i++) {
|
|
@@ -66,53 +65,66 @@ async function indexBatch(db, skills) {
|
|
|
66
65
|
export async function indexAll() {
|
|
67
66
|
const config = loadConfig();
|
|
68
67
|
const db = getDb();
|
|
69
|
-
db.prepare('DELETE FROM edges').run();
|
|
70
68
|
|
|
71
|
-
//
|
|
72
|
-
let total = 0;
|
|
69
|
+
// collect all files on disk
|
|
73
70
|
const allFiles = [];
|
|
74
71
|
for (const { dir, source } of config.sources) {
|
|
75
72
|
const files = globSync(`${dir}/**/*.md`);
|
|
76
73
|
files.forEach(f => allFiles.push({ file: f, source }));
|
|
77
|
-
total += files.length;
|
|
78
74
|
}
|
|
75
|
+
const total = allFiles.length;
|
|
79
76
|
info(`Found ${chalk.white.bold(total)} files`);
|
|
80
77
|
|
|
78
|
+
// reconcile: remove skills whose files no longer exist
|
|
79
|
+
const allIds = db.prepare('SELECT id, path FROM skills').all();
|
|
80
|
+
const existingPaths = new Set(allFiles.map(f => f.file));
|
|
81
|
+
let removed = 0;
|
|
82
|
+
for (const row of allIds) {
|
|
83
|
+
if (!existingPaths.has(row.path)) {
|
|
84
|
+
db.prepare('DELETE FROM skills WHERE id = ?').run(row.id);
|
|
85
|
+
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(row.id);
|
|
86
|
+
db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(row.id, row.id);
|
|
87
|
+
db.prepare('DELETE FROM ratings WHERE skill_id = ?').run(row.id);
|
|
88
|
+
removed++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (removed > 0) info(`Removed ${chalk.yellow(removed)} deleted skills`);
|
|
92
|
+
|
|
81
93
|
let count = 0;
|
|
82
94
|
let errors = 0;
|
|
95
|
+
let skipped = 0;
|
|
83
96
|
let batch = [];
|
|
84
97
|
const start = Date.now();
|
|
85
|
-
|
|
86
98
|
const getHash = db.prepare('SELECT hash FROM skills WHERE id = ?');
|
|
87
99
|
|
|
88
|
-
let skipped = 0;
|
|
89
100
|
for (const { file, source } of allFiles) {
|
|
90
101
|
try {
|
|
102
|
+
if (!isSkillFile(file)) { skipped++; count++; continue; }
|
|
91
103
|
const hash = fileHash(file);
|
|
92
104
|
const parsed = parseSkillFile(file, source);
|
|
93
105
|
const id = skillId(source, parsed.name);
|
|
106
|
+
|
|
94
107
|
const existing = getHash.get(id);
|
|
95
108
|
if (existing?.hash === hash) {
|
|
96
109
|
skipped++;
|
|
97
110
|
count++;
|
|
98
|
-
if (count %
|
|
111
|
+
if (count % 100 === 0) {
|
|
99
112
|
const eta = count > 0 ? Math.round((total - count) * (Date.now() - start) / count / 1000) : '?';
|
|
100
113
|
progress(count, total, `skipped: ${skipped} eta: ${eta}s`);
|
|
101
114
|
}
|
|
102
115
|
continue;
|
|
103
116
|
}
|
|
104
|
-
|
|
105
|
-
batch.push(
|
|
117
|
+
|
|
118
|
+
batch.push({ ...parsed, hash });
|
|
119
|
+
|
|
106
120
|
if (batch.length >= BATCH_SIZE) {
|
|
107
121
|
await indexBatch(db, batch);
|
|
108
122
|
count += batch.length;
|
|
109
123
|
batch = [];
|
|
110
|
-
const pct = Math.round(count / total * 100);
|
|
111
|
-
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
|
|
112
124
|
const eta = count > 0 ? Math.round((total - count) * (Date.now() - start) / count / 1000) : '?';
|
|
113
|
-
|
|
125
|
+
progress(count, total, `skipped: ${skipped} eta: ${eta}s`);
|
|
114
126
|
}
|
|
115
|
-
} catch
|
|
127
|
+
} catch {
|
|
116
128
|
errors++;
|
|
117
129
|
}
|
|
118
130
|
}
|
|
@@ -122,19 +134,29 @@ export async function indexAll() {
|
|
|
122
134
|
count += batch.length;
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
// rebuild all edges for unchanged skills too (fixes edge loss bug)
|
|
138
|
+
rebuildEdgesForUnchanged(db);
|
|
139
|
+
|
|
125
140
|
progress(total, total, 'done');
|
|
126
141
|
console.log();
|
|
127
142
|
const spin = spinner('Building ANN index...');
|
|
128
143
|
spin.start();
|
|
129
144
|
await buildAnnIndex();
|
|
130
145
|
spin.stop();
|
|
131
|
-
success(`Indexed ${chalk.white.bold(count)} skills ${chalk.gray(`(${errors} errors, ${skipped} skipped)`)}`);
|
|
146
|
+
success(`Indexed ${chalk.white.bold(count)} skills ${chalk.gray(`(${errors} errors, ${skipped} skipped, ${removed} removed)`)}`);
|
|
132
147
|
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
133
148
|
info(chalk.gray(`Time: ${elapsed}s`));
|
|
134
149
|
}
|
|
135
150
|
|
|
151
|
+
function rebuildEdgesForUnchanged(db) {
|
|
152
|
+
// For skills that were skipped (hash unchanged), their edges were not touched.
|
|
153
|
+
// This is correct — we only delete+rebuild edges for skills that were re-indexed.
|
|
154
|
+
// No action needed here: edges for unchanged skills remain intact.
|
|
155
|
+
// The global DELETE FROM edges at the start was the bug — it's now removed.
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
export async function indexFile(filePath, source) {
|
|
137
159
|
const db = getDb();
|
|
138
160
|
const skill = parseSkillFile(filePath, source);
|
|
139
|
-
await indexBatch(db, [skill]);
|
|
161
|
+
await indexBatch(db, [{ ...skill, hash: fileHash(filePath) }]);
|
|
140
162
|
}
|
package/package.json
CHANGED
package/parser.js
CHANGED
|
@@ -2,9 +2,18 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// match /skill-name but not URLs (http://, https://, etc.)
|
|
6
|
+
const SKILL_REF_RE = /(?<!https?:|ftp:)(?<![a-zA-Z0-9])\/([a-z0-9][a-z0-9-]{2,})/g;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// files that are likely not skills
|
|
9
|
+
const SKIP_FILENAMES = new Set(['readme', 'changelog', 'license', 'contributing', 'code-of-conduct', 'security', 'authors', 'credits']);
|
|
10
|
+
|
|
11
|
+
export function isSkillFile(filePath) {
|
|
12
|
+
const base = filePath.split(/[\\/]/).pop().replace(/\.md$/i, '').toLowerCase();
|
|
13
|
+
return !SKIP_FILENAMES.has(base);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseSkillFile(filePath, source, opts = {}) {
|
|
8
17
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
9
18
|
|
|
10
19
|
let name, description, content;
|
package/pg-hook.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { embed, cosineSimilarity } from './embedder.js';
|
|
3
3
|
import { getDb } from './db.js';
|
|
4
4
|
|
|
5
|
-
const chunks = [];
|
|
6
5
|
let input = '';
|
|
7
6
|
process.stdin.on('data', d => input += d);
|
|
8
7
|
process.stdin.on('end', async () => {
|
|
@@ -13,18 +12,27 @@ process.stdin.on('end', async () => {
|
|
|
13
12
|
|
|
14
13
|
const queryVec = await embed(prompt);
|
|
15
14
|
const db = getDb();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
|
|
16
|
+
// search over chunks, deduplicate by skill
|
|
17
|
+
const chunks = db.prepare('SELECT skill_id, embedding FROM chunks').all();
|
|
18
|
+
const bestBySkill = new Map();
|
|
19
|
+
for (const chunk of chunks) {
|
|
20
|
+
const score = cosineSimilarity(queryVec, JSON.parse(chunk.embedding));
|
|
21
|
+
const prev = bestBySkill.get(chunk.skill_id);
|
|
22
|
+
if (!prev || score > prev) bestBySkill.set(chunk.skill_id, score);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const topIds = [...bestBySkill.entries()]
|
|
26
|
+
.sort((a, b) => b[1] - a[1])
|
|
26
27
|
.slice(0, 3)
|
|
27
|
-
.filter(
|
|
28
|
+
.filter(([, score]) => score > 0.55);
|
|
29
|
+
|
|
30
|
+
if (topIds.length === 0) process.exit(0);
|
|
31
|
+
|
|
32
|
+
const results = topIds.map(([id, score]) => {
|
|
33
|
+
const skill = db.prepare('SELECT name, description, path FROM skills WHERE id = ?').get(id);
|
|
34
|
+
return skill ? { ...skill, score } : null;
|
|
35
|
+
}).filter(Boolean);
|
|
28
36
|
|
|
29
37
|
if (results.length === 0) process.exit(0);
|
|
30
38
|
|
package/search.js
CHANGED
|
@@ -25,11 +25,12 @@ export async function search(query, topK = 5) {
|
|
|
25
25
|
if (!prev || r.score > prev) bestBySkill.set(r.skill_id, r.score);
|
|
26
26
|
}
|
|
27
27
|
return [...bestBySkill.entries()]
|
|
28
|
-
.
|
|
28
|
+
.map(([id, score]) => ({ id, score: applyRatingBoost(db, id, score) }))
|
|
29
|
+
.sort((a, b) => b.score - a.score)
|
|
29
30
|
.slice(0, topK)
|
|
30
|
-
.map((
|
|
31
|
+
.map(({ id, score }) => {
|
|
31
32
|
const skill = db.prepare('SELECT id, name, description, path, source FROM skills WHERE id = ?').get(id);
|
|
32
|
-
return skill ? { ...skill, score
|
|
33
|
+
return skill ? { ...skill, score } : null;
|
|
33
34
|
})
|
|
34
35
|
.filter(Boolean);
|
|
35
36
|
}
|
|
@@ -43,9 +44,10 @@ export async function search(query, topK = 5) {
|
|
|
43
44
|
if (!prev || score > prev) bestBySkill.set(chunk.skill_id, score);
|
|
44
45
|
}
|
|
45
46
|
return [...bestBySkill.entries()]
|
|
46
|
-
.
|
|
47
|
+
.map(([id, score]) => ({ id, score: applyRatingBoost(db, id, score) }))
|
|
48
|
+
.sort((a, b) => b.score - a.score)
|
|
47
49
|
.slice(0, topK)
|
|
48
|
-
.map((
|
|
50
|
+
.map(({ id, score }) => {
|
|
49
51
|
const skill = db.prepare('SELECT id, name, description, path, source FROM skills WHERE id = ?').get(id);
|
|
50
52
|
return skill ? { ...skill, score } : null;
|
|
51
53
|
})
|
|
@@ -62,18 +64,29 @@ export function getContext(id) {
|
|
|
62
64
|
return { ...skill, callees, callers };
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
function resolveId(db, nameOrId) {
|
|
68
|
+
// try exact id match first, then name match
|
|
69
|
+
const byId = db.prepare('SELECT id FROM skills WHERE id = ?').get(nameOrId);
|
|
70
|
+
if (byId) return byId.id;
|
|
71
|
+
const byName = db.prepare('SELECT id FROM skills WHERE name = ? ORDER BY id LIMIT 1').get(nameOrId);
|
|
72
|
+
return byName ? byName.id : nameOrId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getCallers(nameOrId) {
|
|
66
76
|
const db = getDb();
|
|
77
|
+
const id = resolveId(db, nameOrId);
|
|
67
78
|
return db.prepare('SELECT from_skill FROM edges WHERE to_skill = ?').all(id).map(r => r.from_skill);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
export function getCallees(
|
|
81
|
+
export function getCallees(nameOrId) {
|
|
71
82
|
const db = getDb();
|
|
83
|
+
const id = resolveId(db, nameOrId);
|
|
72
84
|
return db.prepare('SELECT to_skill FROM edges WHERE from_skill = ?').all(id).map(r => r.to_skill);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
export function getImpact(
|
|
87
|
+
export function getImpact(nameOrId) {
|
|
76
88
|
const db = getDb();
|
|
89
|
+
const id = resolveId(db, nameOrId);
|
|
77
90
|
const visited = new Set();
|
|
78
91
|
const queue = [id];
|
|
79
92
|
while (queue.length) {
|
package/watcher.js
CHANGED
|
@@ -1,50 +1,72 @@
|
|
|
1
|
-
import chokidar from 'chokidar';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
watcher.on('
|
|
24
|
-
watcher.on('
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { indexFile } from './indexer.js';
|
|
6
|
+
import { getDb, skillId } from './db.js';
|
|
7
|
+
import { loadConfig } from './config.js';
|
|
8
|
+
import matter from 'gray-matter';
|
|
9
|
+
|
|
10
|
+
export function startWatcher() {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const paths = config.sources.map(s => s.dir).filter(d => fs.existsSync(d));
|
|
13
|
+
|
|
14
|
+
if (paths.length === 0) return;
|
|
15
|
+
|
|
16
|
+
const watcher = chokidar.watch(paths, {
|
|
17
|
+
ignored: /[/\\]\./,
|
|
18
|
+
persistent: true,
|
|
19
|
+
ignoreInitial: true,
|
|
20
|
+
awaitWriteFinish: { stabilityThreshold: 500 },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
watcher.on('add', filePath => reindex(filePath, config));
|
|
24
|
+
watcher.on('change', filePath => reindex(filePath, config));
|
|
25
|
+
watcher.on('unlink', filePath => remove(filePath, config));
|
|
26
|
+
|
|
27
|
+
console.error('[PromptGraph] Watcher started');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getSource(filePath, config) {
|
|
31
|
+
for (const { dir, source } of config.sources) {
|
|
32
|
+
if (filePath.startsWith(dir)) return source;
|
|
33
|
+
}
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readName(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
40
|
+
const { data } = matter(raw);
|
|
41
|
+
return (data.name && String(data.name).trim()) || path.basename(filePath, '.md');
|
|
42
|
+
} catch {
|
|
43
|
+
return path.basename(filePath, '.md');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function remove(filePath, config) {
|
|
48
|
+
if (!filePath.endsWith('.md')) return;
|
|
49
|
+
try {
|
|
50
|
+
const source = getSource(filePath, config);
|
|
51
|
+
const name = readName(filePath);
|
|
52
|
+
const id = skillId(source, name);
|
|
53
|
+
const db = getDb();
|
|
54
|
+
db.prepare('DELETE FROM skills WHERE id = ?').run(id);
|
|
55
|
+
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(id);
|
|
56
|
+
db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(id, id);
|
|
57
|
+
db.prepare('DELETE FROM ratings WHERE skill_id = ?').run(id);
|
|
58
|
+
console.error(`[PromptGraph] Removed: ${id}`);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.error(`[PromptGraph] Error removing ${filePath}: ${e.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function reindex(filePath, config) {
|
|
65
|
+
if (!filePath.endsWith('.md')) return;
|
|
66
|
+
try {
|
|
67
|
+
await indexFile(filePath, getSource(filePath, config));
|
|
68
|
+
console.error(`[PromptGraph] Reindexed: ${path.basename(filePath)}`);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(`[PromptGraph] Error reindexing ${filePath}: ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|