promptgraph-mcp 2.8.1 → 2.8.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 +205 -205
- package/ann.js +33 -33
- package/api.js +202 -202
- package/bundle-counts.js +111 -111
- package/chunker.js +28 -28
- package/cli.js +115 -115
- package/commands/bundle.js +150 -150
- package/commands/doctor.js +15 -15
- package/commands/import.js +7 -7
- package/commands/init.js +37 -37
- package/commands/marketplace.js +146 -146
- package/commands/reindex.js +10 -10
- package/commands/search.js +55 -55
- package/commands/setup.js +19 -19
- package/commands/status.js +110 -110
- package/commands/train.js +18 -18
- package/commands/update.js +49 -49
- package/commands/validate.js +63 -63
- package/config.js +72 -72
- package/db.js +157 -157
- package/doctor.js +48 -48
- package/embedder.js +54 -54
- package/github-import.js +750 -745
- package/indexer.js +310 -310
- package/package.json +61 -61
- package/parser.js +69 -69
- package/pg-hook.js +70 -70
- package/platform.js +120 -120
- package/search.js +216 -216
- package/src/filter/classifier.js +88 -88
- package/src/filter/hard-filter.js +62 -62
- package/src/filter/train.js +66 -66
- package/src/reranker/reranker.js +92 -92
- package/src/store/flat-store.js +61 -61
- package/src/store/hnsw-store.js +187 -187
- package/src/store/index.js +19 -19
- package/src/store/vector-store.js +9 -9
- package/src/utils/rate-limiter.js +33 -33
- package/tui.js +418 -418
- package/validate-repo-action.js +139 -139
- package/watcher.js +84 -84
package/validate-repo-action.js
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { spawnSync } from 'child_process';
|
|
4
|
-
import { validateSkill } from './validator.js';
|
|
5
|
-
|
|
6
|
-
// ── doc filename filter — same as cleanupRepoRoot in github-import.js ──────────
|
|
7
|
-
const SKIP_RE = /^(readme|changelog|license|contributing|code\.?of\.?conduct|security|authors|credits|install|installation|usage|promotion|faq|glossary|index|overview|summary|roadmap|todo|notes|template|example|sample|demo|guide|tutorial|walkthrough|architecture|design|spec|requirements|privacy|terms|disclaimer|notice|copying|warranty|funding|changelog)/i;
|
|
8
|
-
|
|
9
|
-
const SKIP_DIRS_LOCAL = new Set([
|
|
10
|
-
'.github', 'docs', 'doc', 'documentation', 'assets', 'images', 'img',
|
|
11
|
-
'screenshots', 'media', 'static', 'scripts', 'ci_scripts',
|
|
12
|
-
'node_modules', 'vendor', 'dist', 'build', 'tests', 'test',
|
|
13
|
-
'examples', 'example', 'fixtures',
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
function isDocFile(name) {
|
|
17
|
-
const base = path.basename(name, '.md').toLowerCase();
|
|
18
|
-
return SKIP_RE.test(base);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isSkipDir(name) {
|
|
22
|
-
return SKIP_DIRS_LOCAL.has(name.toLowerCase());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ── find .md files in subdirectories only (never root) ────────────────────────
|
|
26
|
-
function findSubdirMdFiles(repoRoot) {
|
|
27
|
-
const files = [];
|
|
28
|
-
const entries = fs.readdirSync(repoRoot, { withFileTypes: true });
|
|
29
|
-
|
|
30
|
-
for (const entry of entries) {
|
|
31
|
-
if (entry.name === '.git') continue;
|
|
32
|
-
const fullPath = path.join(repoRoot, entry.name);
|
|
33
|
-
|
|
34
|
-
if (entry.isDirectory()) {
|
|
35
|
-
if (isSkipDir(entry.name)) continue;
|
|
36
|
-
// Walk subdirectory recursively
|
|
37
|
-
walkDir(fullPath, entry.name, files);
|
|
38
|
-
}
|
|
39
|
-
// Root files are SKIPPED — never count them
|
|
40
|
-
}
|
|
41
|
-
return files;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function walkDir(dirPath, relativePrefix, out) {
|
|
45
|
-
let entries;
|
|
46
|
-
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); } catch { return; }
|
|
47
|
-
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
50
|
-
const relativePath = relativePrefix + '/' + entry.name;
|
|
51
|
-
|
|
52
|
-
if (entry.isDirectory()) {
|
|
53
|
-
if (isSkipDir(entry.name)) continue;
|
|
54
|
-
walkDir(fullPath, relativePath, out);
|
|
55
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
56
|
-
if (isDocFile(entry.name)) continue;
|
|
57
|
-
out.push({ path: fullPath, relative: relativePath });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── main ──────────────────────────────────────────────────────────────────────
|
|
63
|
-
async function main() {
|
|
64
|
-
const args = process.argv.slice(2);
|
|
65
|
-
const repoArg = args.find(a => a.startsWith('--repo='))?.split('=').slice(1).join('=') || args[0];
|
|
66
|
-
|
|
67
|
-
if (!repoArg) {
|
|
68
|
-
console.error(JSON.stringify({ ok: false, errors: ['Usage: node validate-repo-action.js <owner/repo>'] }));
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const repo = repoArg.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '').replace(/\/$/, '');
|
|
73
|
-
const cloneUrl = `https://github.com/${repo}.git`;
|
|
74
|
-
const tmpDir = path.join(process.env.RUNNER_TEMP || process.env.TMPDIR || '/tmp', `validate-${repo.replace('/', '-')}`);
|
|
75
|
-
|
|
76
|
-
// Clean any previous run
|
|
77
|
-
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
78
|
-
|
|
79
|
-
// Clone with depth 1 (no API calls, no rate limits)
|
|
80
|
-
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
81
|
-
const clone = spawnSync('git', ['clone', '--depth=1', cloneUrl, tmpDir], {
|
|
82
|
-
stdio: 'pipe', env: gitEnv,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (clone.status !== 0) {
|
|
86
|
-
const msg = (clone.stderr?.toString() || 'unknown error').trim();
|
|
87
|
-
console.error(JSON.stringify({ ok: false, errors: [`Failed to clone ${repo}: ${msg}`] }));
|
|
88
|
-
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Find .md files in subdirectories only
|
|
93
|
-
const candidates = findSubdirMdFiles(tmpDir);
|
|
94
|
-
|
|
95
|
-
if (candidates.length === 0) {
|
|
96
|
-
console.error(JSON.stringify({
|
|
97
|
-
ok: false,
|
|
98
|
-
errors: ['No valid .md skill files found in subdirectories'],
|
|
99
|
-
detail: 'Root-level .md files are ignored. Skills must be in a subdirectory (skills/, prompts/, etc.).',
|
|
100
|
-
}));
|
|
101
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Validate each .md file via validateSkill()
|
|
106
|
-
const results = [];
|
|
107
|
-
let totalErrors = 0;
|
|
108
|
-
|
|
109
|
-
for (const file of candidates) {
|
|
110
|
-
const result = validateSkill(file.path);
|
|
111
|
-
results.push({
|
|
112
|
-
file: file.relative,
|
|
113
|
-
ok: result.ok,
|
|
114
|
-
errors: result.errors,
|
|
115
|
-
warnings: result.warnings,
|
|
116
|
-
});
|
|
117
|
-
if (!result.ok) totalErrors++;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Summary
|
|
121
|
-
const summary = {
|
|
122
|
-
ok: totalErrors === 0,
|
|
123
|
-
repo,
|
|
124
|
-
total_md_files: candidates.length,
|
|
125
|
-
passed: candidates.length - totalErrors,
|
|
126
|
-
failed: totalErrors,
|
|
127
|
-
results,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
131
|
-
|
|
132
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
133
|
-
process.exit(totalErrors === 0 ? 0 : 1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
main().catch(e => {
|
|
137
|
-
console.error(JSON.stringify({ ok: false, errors: [e.message] }));
|
|
138
|
-
process.exit(1);
|
|
139
|
-
});
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
import { validateSkill } from './validator.js';
|
|
5
|
+
|
|
6
|
+
// ── doc filename filter — same as cleanupRepoRoot in github-import.js ──────────
|
|
7
|
+
const SKIP_RE = /^(readme|changelog|license|contributing|code\.?of\.?conduct|security|authors|credits|install|installation|usage|promotion|faq|glossary|index|overview|summary|roadmap|todo|notes|template|example|sample|demo|guide|tutorial|walkthrough|architecture|design|spec|requirements|privacy|terms|disclaimer|notice|copying|warranty|funding|changelog)/i;
|
|
8
|
+
|
|
9
|
+
const SKIP_DIRS_LOCAL = new Set([
|
|
10
|
+
'.github', 'docs', 'doc', 'documentation', 'assets', 'images', 'img',
|
|
11
|
+
'screenshots', 'media', 'static', 'scripts', 'ci_scripts',
|
|
12
|
+
'node_modules', 'vendor', 'dist', 'build', 'tests', 'test',
|
|
13
|
+
'examples', 'example', 'fixtures',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function isDocFile(name) {
|
|
17
|
+
const base = path.basename(name, '.md').toLowerCase();
|
|
18
|
+
return SKIP_RE.test(base);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isSkipDir(name) {
|
|
22
|
+
return SKIP_DIRS_LOCAL.has(name.toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── find .md files in subdirectories only (never root) ────────────────────────
|
|
26
|
+
function findSubdirMdFiles(repoRoot) {
|
|
27
|
+
const files = [];
|
|
28
|
+
const entries = fs.readdirSync(repoRoot, { withFileTypes: true });
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.name === '.git') continue;
|
|
32
|
+
const fullPath = path.join(repoRoot, entry.name);
|
|
33
|
+
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
if (isSkipDir(entry.name)) continue;
|
|
36
|
+
// Walk subdirectory recursively
|
|
37
|
+
walkDir(fullPath, entry.name, files);
|
|
38
|
+
}
|
|
39
|
+
// Root files are SKIPPED — never count them
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function walkDir(dirPath, relativePrefix, out) {
|
|
45
|
+
let entries;
|
|
46
|
+
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); } catch { return; }
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
50
|
+
const relativePath = relativePrefix + '/' + entry.name;
|
|
51
|
+
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (isSkipDir(entry.name)) continue;
|
|
54
|
+
walkDir(fullPath, relativePath, out);
|
|
55
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
56
|
+
if (isDocFile(entry.name)) continue;
|
|
57
|
+
out.push({ path: fullPath, relative: relativePath });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── main ──────────────────────────────────────────────────────────────────────
|
|
63
|
+
async function main() {
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
const repoArg = args.find(a => a.startsWith('--repo='))?.split('=').slice(1).join('=') || args[0];
|
|
66
|
+
|
|
67
|
+
if (!repoArg) {
|
|
68
|
+
console.error(JSON.stringify({ ok: false, errors: ['Usage: node validate-repo-action.js <owner/repo>'] }));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const repo = repoArg.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '').replace(/\/$/, '');
|
|
73
|
+
const cloneUrl = `https://github.com/${repo}.git`;
|
|
74
|
+
const tmpDir = path.join(process.env.RUNNER_TEMP || process.env.TMPDIR || '/tmp', `validate-${repo.replace('/', '-')}`);
|
|
75
|
+
|
|
76
|
+
// Clean any previous run
|
|
77
|
+
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
78
|
+
|
|
79
|
+
// Clone with depth 1 (no API calls, no rate limits)
|
|
80
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
81
|
+
const clone = spawnSync('git', ['clone', '--depth=1', cloneUrl, tmpDir], {
|
|
82
|
+
stdio: 'pipe', env: gitEnv,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (clone.status !== 0) {
|
|
86
|
+
const msg = (clone.stderr?.toString() || 'unknown error').trim();
|
|
87
|
+
console.error(JSON.stringify({ ok: false, errors: [`Failed to clone ${repo}: ${msg}`] }));
|
|
88
|
+
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Find .md files in subdirectories only
|
|
93
|
+
const candidates = findSubdirMdFiles(tmpDir);
|
|
94
|
+
|
|
95
|
+
if (candidates.length === 0) {
|
|
96
|
+
console.error(JSON.stringify({
|
|
97
|
+
ok: false,
|
|
98
|
+
errors: ['No valid .md skill files found in subdirectories'],
|
|
99
|
+
detail: 'Root-level .md files are ignored. Skills must be in a subdirectory (skills/, prompts/, etc.).',
|
|
100
|
+
}));
|
|
101
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Validate each .md file via validateSkill()
|
|
106
|
+
const results = [];
|
|
107
|
+
let totalErrors = 0;
|
|
108
|
+
|
|
109
|
+
for (const file of candidates) {
|
|
110
|
+
const result = validateSkill(file.path);
|
|
111
|
+
results.push({
|
|
112
|
+
file: file.relative,
|
|
113
|
+
ok: result.ok,
|
|
114
|
+
errors: result.errors,
|
|
115
|
+
warnings: result.warnings,
|
|
116
|
+
});
|
|
117
|
+
if (!result.ok) totalErrors++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Summary
|
|
121
|
+
const summary = {
|
|
122
|
+
ok: totalErrors === 0,
|
|
123
|
+
repo,
|
|
124
|
+
total_md_files: candidates.length,
|
|
125
|
+
passed: candidates.length - totalErrors,
|
|
126
|
+
failed: totalErrors,
|
|
127
|
+
results,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
131
|
+
|
|
132
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
133
|
+
process.exit(totalErrors === 0 ? 0 : 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch(e => {
|
|
137
|
+
console.error(JSON.stringify({ ok: false, errors: [e.message] }));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
package/watcher.js
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import chokidar from 'chokidar';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { indexFile } from './indexer.js';
|
|
5
|
-
import { getDb } from './db.js';
|
|
6
|
-
import { loadConfig } from './config.js';
|
|
7
|
-
|
|
8
|
-
export function startWatcher() {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
const paths = config.sources.map(s => s.dir).filter(d => fs.existsSync(d));
|
|
11
|
-
if (paths.length === 0) return;
|
|
12
|
-
|
|
13
|
-
const watcher = chokidar.watch(paths, {
|
|
14
|
-
ignored: /[/\\]\./,
|
|
15
|
-
persistent: true,
|
|
16
|
-
ignoreInitial: true,
|
|
17
|
-
awaitWriteFinish: { stabilityThreshold: 500 },
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
watcher.on('add', filePath => reindex(filePath, config));
|
|
21
|
-
watcher.on('change', filePath => reindex(filePath, config));
|
|
22
|
-
watcher.on('unlink', filePath => remove(filePath));
|
|
23
|
-
|
|
24
|
-
console.error('[PromptGraph] Watcher started');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getSource(filePath, config) {
|
|
28
|
-
const normFile = path.resolve(filePath);
|
|
29
|
-
// Sort longest-first so skills-store/marketplace wins over skills-store
|
|
30
|
-
const sorted = [...config.sources].sort((a, b) => b.dir.length - a.dir.length);
|
|
31
|
-
for (const { dir, source } of sorted) {
|
|
32
|
-
if (normFile.startsWith(path.resolve(dir))) return source;
|
|
33
|
-
}
|
|
34
|
-
return 'unknown';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function deleteById(id) {
|
|
38
|
-
const db = getDb();
|
|
39
|
-
db.prepare('DELETE FROM skills WHERE id = ?').run(id);
|
|
40
|
-
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(id);
|
|
41
|
-
db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(id, id);
|
|
42
|
-
db.prepare('DELETE FROM ratings WHERE skill_id = ?').run(id);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function remove(filePath) {
|
|
46
|
-
if (!filePath.endsWith('.md')) return;
|
|
47
|
-
try {
|
|
48
|
-
const db = getDb();
|
|
49
|
-
// find by path — file is already deleted, can't read frontmatter
|
|
50
|
-
const row = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
51
|
-
if (row) {
|
|
52
|
-
deleteById(row.id);
|
|
53
|
-
console.error(`[PromptGraph] Removed: ${row.id}`);
|
|
54
|
-
}
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error(`[PromptGraph] Error removing ${filePath}: ${e.message}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function reindex(filePath, config) {
|
|
61
|
-
if (!filePath.endsWith('.md')) return;
|
|
62
|
-
try {
|
|
63
|
-
const db = getDb();
|
|
64
|
-
const source = getSource(filePath, config);
|
|
65
|
-
|
|
66
|
-
// check if path had a different id before (rename case)
|
|
67
|
-
const existing = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
68
|
-
|
|
69
|
-
await indexFile(filePath, source);
|
|
70
|
-
|
|
71
|
-
// if new id differs from old id — delete old record
|
|
72
|
-
if (existing) {
|
|
73
|
-
const updated = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
74
|
-
if (updated && updated.id !== existing.id) {
|
|
75
|
-
deleteById(existing.id);
|
|
76
|
-
console.error(`[PromptGraph] Renamed: ${existing.id} → ${updated.id}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
console.error(`[PromptGraph] Reindexed: ${path.basename(filePath)}`);
|
|
81
|
-
} catch (e) {
|
|
82
|
-
console.error(`[PromptGraph] Error reindexing ${filePath}: ${e.message}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { indexFile } from './indexer.js';
|
|
5
|
+
import { getDb } from './db.js';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
export function startWatcher() {
|
|
9
|
+
const config = loadConfig();
|
|
10
|
+
const paths = config.sources.map(s => s.dir).filter(d => fs.existsSync(d));
|
|
11
|
+
if (paths.length === 0) return;
|
|
12
|
+
|
|
13
|
+
const watcher = chokidar.watch(paths, {
|
|
14
|
+
ignored: /[/\\]\./,
|
|
15
|
+
persistent: true,
|
|
16
|
+
ignoreInitial: true,
|
|
17
|
+
awaitWriteFinish: { stabilityThreshold: 500 },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
watcher.on('add', filePath => reindex(filePath, config));
|
|
21
|
+
watcher.on('change', filePath => reindex(filePath, config));
|
|
22
|
+
watcher.on('unlink', filePath => remove(filePath));
|
|
23
|
+
|
|
24
|
+
console.error('[PromptGraph] Watcher started');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getSource(filePath, config) {
|
|
28
|
+
const normFile = path.resolve(filePath);
|
|
29
|
+
// Sort longest-first so skills-store/marketplace wins over skills-store
|
|
30
|
+
const sorted = [...config.sources].sort((a, b) => b.dir.length - a.dir.length);
|
|
31
|
+
for (const { dir, source } of sorted) {
|
|
32
|
+
if (normFile.startsWith(path.resolve(dir))) return source;
|
|
33
|
+
}
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deleteById(id) {
|
|
38
|
+
const db = getDb();
|
|
39
|
+
db.prepare('DELETE FROM skills WHERE id = ?').run(id);
|
|
40
|
+
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(id);
|
|
41
|
+
db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(id, id);
|
|
42
|
+
db.prepare('DELETE FROM ratings WHERE skill_id = ?').run(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function remove(filePath) {
|
|
46
|
+
if (!filePath.endsWith('.md')) return;
|
|
47
|
+
try {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
// find by path — file is already deleted, can't read frontmatter
|
|
50
|
+
const row = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
51
|
+
if (row) {
|
|
52
|
+
deleteById(row.id);
|
|
53
|
+
console.error(`[PromptGraph] Removed: ${row.id}`);
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error(`[PromptGraph] Error removing ${filePath}: ${e.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function reindex(filePath, config) {
|
|
61
|
+
if (!filePath.endsWith('.md')) return;
|
|
62
|
+
try {
|
|
63
|
+
const db = getDb();
|
|
64
|
+
const source = getSource(filePath, config);
|
|
65
|
+
|
|
66
|
+
// check if path had a different id before (rename case)
|
|
67
|
+
const existing = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
68
|
+
|
|
69
|
+
await indexFile(filePath, source);
|
|
70
|
+
|
|
71
|
+
// if new id differs from old id — delete old record
|
|
72
|
+
if (existing) {
|
|
73
|
+
const updated = db.prepare('SELECT id FROM skills WHERE path = ?').get(filePath);
|
|
74
|
+
if (updated && updated.id !== existing.id) {
|
|
75
|
+
deleteById(existing.id);
|
|
76
|
+
console.error(`[PromptGraph] Renamed: ${existing.id} → ${updated.id}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.error(`[PromptGraph] Reindexed: ${path.basename(filePath)}`);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(`[PromptGraph] Error reindexing ${filePath}: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|