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 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
- const init = spawnSync('git', ['clone', '--depth=1', '--filter=blob:none', '--no-checkout', cloneUrl, destBase], { stdio: 'pipe', env: gitEnv, timeout: 60000 });
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
- const co = spawnSync('git', ['-C', destBase, 'checkout'], { stdio: 'pipe', env: gitEnv, timeout: 60000 });
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: 60000 });
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
- await classifierCleanup(destBase);
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 — much faster than indexAll after a bundle install
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
- await buildAnnIndex();
308
- const elapsed = ((Date.now() - start) / 1000).toFixed(1);
309
- success(`Indexed ${chalk.white.bold(count)} skills from ${sourceName} ${chalk.gray(`(${skipped} skipped, ${elapsed}s)`)}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.8.4",
3
+ "version": "2.8.6",
4
4
  "files": [
5
5
  "*.js",
6
6
  "commands/",