promptgraph-mcp 2.8.4 → 2.8.6
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/github-import.js +14 -4
- package/indexer.js +31 -8
- package/package.json +1 -1
package/github-import.js
CHANGED
|
@@ -678,18 +678,23 @@ export async function importFromGitHubLight(repoUrl) {
|
|
|
678
678
|
fs.mkdirSync(destBase, { recursive: true });
|
|
679
679
|
|
|
680
680
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
681
|
+
const prog = (msg) => process.stderr.write(`\r\x1b[K ${msg}`);
|
|
681
682
|
|
|
682
683
|
// Step 1: treeless clone — gets file tree instantly, no blob download, no API
|
|
683
|
-
|
|
684
|
+
prog(`Cloning ${ownerRepo}...`);
|
|
685
|
+
const init = spawnSync('git', ['clone', '--depth=1', '--filter=blob:none', '--no-checkout', '--progress', cloneUrl, destBase], { stdio: 'pipe', env: gitEnv, timeout: 120000 });
|
|
684
686
|
if (init.status !== 0) {
|
|
687
|
+
process.stderr.write('\n');
|
|
685
688
|
fs.rmSync(destBase, { recursive: true, force: true });
|
|
686
689
|
throw new Error(`Failed to clone ${ownerRepo}: ${(init.stderr?.toString() || '').trim().slice(0, 120)}`);
|
|
687
690
|
}
|
|
688
691
|
|
|
689
692
|
// Step 2: detect skills subdir from local tree — zero API calls
|
|
693
|
+
prog(`Detecting skill directory...`);
|
|
690
694
|
const subdir = detectSubdirFromTree(destBase);
|
|
691
695
|
|
|
692
696
|
// Step 3: sparse-checkout only the skills subdir .md files
|
|
697
|
+
prog(`Setting up sparse checkout${subdir ? ` (${subdir}/)` : ''}...`);
|
|
693
698
|
spawnSync('git', ['-C', destBase, 'sparse-checkout', 'init'], { stdio: 'pipe', env: gitEnv });
|
|
694
699
|
if (subdir) {
|
|
695
700
|
spawnSync('git', ['-C', destBase, 'sparse-checkout', 'set', '--no-cone', `${subdir}/*.md`, `${subdir}/**/*.md`], { stdio: 'pipe', env: gitEnv });
|
|
@@ -698,16 +703,20 @@ export async function importFromGitHubLight(repoUrl) {
|
|
|
698
703
|
}
|
|
699
704
|
|
|
700
705
|
// Step 4: checkout to materialize only the selected files
|
|
701
|
-
|
|
706
|
+
prog(`Downloading .md files...`);
|
|
707
|
+
const co = spawnSync('git', ['-C', destBase, 'checkout'], { stdio: 'pipe', env: gitEnv, timeout: 120000 });
|
|
702
708
|
if (co.status !== 0) {
|
|
709
|
+
process.stderr.write('\n');
|
|
703
710
|
fs.rmSync(destBase, { recursive: true, force: true });
|
|
704
711
|
throw new Error(`Checkout failed for ${ownerRepo}`);
|
|
705
712
|
}
|
|
706
713
|
// Force blob materialization (needed for partial clones on Windows)
|
|
707
|
-
spawnSync('git', ['-C', destBase, 'checkout', 'HEAD', '--', '.'], { stdio: 'pipe', env: gitEnv, timeout:
|
|
714
|
+
spawnSync('git', ['-C', destBase, 'checkout', 'HEAD', '--', '.'], { stdio: 'pipe', env: gitEnv, timeout: 120000 });
|
|
715
|
+
process.stderr.write('\n');
|
|
708
716
|
|
|
709
717
|
// Step 5: filter out non-skill files locally
|
|
710
718
|
const allMd = globSync(`${destBase}/**/*.md`);
|
|
719
|
+
prog(`Filtering ${allMd.length} files...`);
|
|
711
720
|
let removed = 0;
|
|
712
721
|
for (const fp of allMd) {
|
|
713
722
|
if (!isSkillFile(fp)) { try { fs.unlinkSync(fp); removed++; } catch {} }
|
|
@@ -715,6 +724,7 @@ export async function importFromGitHubLight(repoUrl) {
|
|
|
715
724
|
if (removed > 0) removeEmptyDirs(destBase);
|
|
716
725
|
|
|
717
726
|
const remaining = globSync(`${destBase}/**/*.md`);
|
|
727
|
+
prog(`Validating ${remaining.length} skill files...`);
|
|
718
728
|
let removedV = 0;
|
|
719
729
|
for (const fp of remaining) {
|
|
720
730
|
const v = validateSkill(fp);
|
|
@@ -722,7 +732,7 @@ export async function importFromGitHubLight(repoUrl) {
|
|
|
722
732
|
}
|
|
723
733
|
if (removedV > 0) removeEmptyDirs(destBase);
|
|
724
734
|
|
|
725
|
-
|
|
735
|
+
process.stderr.write('\n');
|
|
726
736
|
|
|
727
737
|
const realCount = globSync(`${destBase}/**/*.md`).length;
|
|
728
738
|
const cacheKey = url.replace(/\.git$/, '');
|
package/indexer.js
CHANGED
|
@@ -255,7 +255,7 @@ export async function indexFile(filePath, source) {
|
|
|
255
255
|
await indexBatch(db, [{ ...skill, hash }]);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
// Index only one source directory —
|
|
258
|
+
// Index only one source directory — fast mode first (no embeddings), then embed in background
|
|
259
259
|
export async function indexSource(dir, sourceName) {
|
|
260
260
|
const db = getDb();
|
|
261
261
|
const files = globSync(`${dir}/**/*.md`);
|
|
@@ -280,6 +280,7 @@ export async function indexSource(dir, sourceName) {
|
|
|
280
280
|
let count = 0, skipped = 0, errors = 0, batch = [];
|
|
281
281
|
const start = Date.now();
|
|
282
282
|
|
|
283
|
+
// Pass 1: fast — upsert skills into DB with keyword search only (instant)
|
|
283
284
|
for (const file of files) {
|
|
284
285
|
try {
|
|
285
286
|
const norm = sanitizePath(file);
|
|
@@ -293,18 +294,40 @@ export async function indexSource(dir, sourceName) {
|
|
|
293
294
|
const parsed = parseSkillFile(file, sourceName, { raw });
|
|
294
295
|
batch.push({ ...parsed, hash });
|
|
295
296
|
if (batch.length >= BATCH_SIZE) {
|
|
296
|
-
await indexBatch(db, batch);
|
|
297
|
+
await indexBatch(db, batch, { fast: true });
|
|
297
298
|
count += batch.length; batch = [];
|
|
298
299
|
progress(count, total, { skipped, errors });
|
|
299
|
-
await new Promise(r => setImmediate ? setImmediate(r) : setTimeout(r, 0));
|
|
300
300
|
}
|
|
301
301
|
} catch (e) { errors++; count++; }
|
|
302
302
|
}
|
|
303
|
-
|
|
304
|
-
if (batch.length > 0) { await indexBatch(db, batch); count += batch.length; }
|
|
303
|
+
if (batch.length > 0) { await indexBatch(db, batch, { fast: true }); count += batch.length; }
|
|
305
304
|
progress(total, total, { skipped, errors });
|
|
306
305
|
progressDone();
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
const elapsed1 = ((Date.now() - start) / 1000).toFixed(1);
|
|
307
|
+
success(`Indexed ${chalk.white.bold(count)} skills from ${sourceName} ${chalk.gray(`(${skipped} skipped, ${elapsed1}s)`)}`);
|
|
308
|
+
info(chalk.gray(' Semantic embeddings generating in background — keyword search available now.'));
|
|
309
|
+
|
|
310
|
+
// Pass 2: embed in background (non-blocking)
|
|
311
|
+
setImmediate(async () => {
|
|
312
|
+
try {
|
|
313
|
+
const toEmbed = [];
|
|
314
|
+
for (const file of files) {
|
|
315
|
+
try {
|
|
316
|
+
const norm = sanitizePath(file);
|
|
317
|
+
const stat = fs.statSync(norm);
|
|
318
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
319
|
+
const raw = fs.readFileSync(norm, 'utf8');
|
|
320
|
+
if (!isSkillFile(file, raw)) continue;
|
|
321
|
+
const hash = createHash('md5').update(raw).digest('hex');
|
|
322
|
+
const parsed = parseSkillFile(file, sourceName, { raw });
|
|
323
|
+
toEmbed.push({ ...parsed, hash });
|
|
324
|
+
} catch {}
|
|
325
|
+
}
|
|
326
|
+
if (toEmbed.length === 0) return;
|
|
327
|
+
for (let i = 0; i < toEmbed.length; i += BATCH_SIZE) {
|
|
328
|
+
await indexBatch(db, toEmbed.slice(i, i + BATCH_SIZE), { fast: false });
|
|
329
|
+
}
|
|
330
|
+
await buildAnnIndex();
|
|
331
|
+
} catch {}
|
|
332
|
+
});
|
|
310
333
|
}
|