promptgraph-mcp 2.2.6 → 2.2.8

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.
Files changed (2) hide show
  1. package/index.js +91 -18
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -34,6 +34,7 @@ function showHelp() {
34
34
  ['import <owner/repo>', 'Import skills from GitHub'],
35
35
  ['status', 'Show installed skills, repos, and bundles'],
36
36
  ['marketplace', 'Interactive TUI: browse + search + install skills & bundles'],
37
+ ['bundle update [id]', 'Update all (or one) installed GitHub bundles'],
37
38
  ['validate <file.md>', 'Validate a skill before publishing'],
38
39
  ['doctor', 'Clean orphaned chunks/edges/ratings'],
39
40
  ['update', 'Update to the latest version from npm'],
@@ -208,35 +209,38 @@ if (args[0] === 'marketplace') {
208
209
 
209
210
  if (skills?.error) { error(skills.error); process.exit(1); }
210
211
 
211
- // Build installed set: bundle IDs from config sources + skill IDs from DB
212
+ // Build installed set source of truth is the FILESYSTEM, not DB/config
212
213
  const installedSet = new Set();
213
214
  try {
214
215
  const cfg = _lcMkt();
215
216
  const db = _getDbMkt();
216
-
217
- // Collect installed skill IDs from DB
218
- const dbSkillIds = new Set(db.prepare('SELECT id FROM skills').all().map(r => r.id));
219
-
220
- // Build set of cloned repo names from config sources (exact: github:owner-repo)
221
- const githubSources = new Set(
222
- cfg.sources.filter(s => s.source.startsWith('github:')).map(s => s.source.replace('github:', '').toLowerCase())
223
- );
217
+ const { SKILLS_STORE_DIR } = await import('./config.js');
218
+ const githubDir = path.join(SKILLS_STORE_DIR, 'github');
224
219
 
225
220
  for (const b of (Array.isArray(bundles) ? bundles : [])) {
226
221
  if (b.repo_url) {
227
- // repo_url = "owner/repo" → cloned as "owner-repo" in github: source
228
- const clonedName = b.repo_url.replace('/', '-').toLowerCase();
229
- if (githubSources.has(clonedName)) installedSet.add(b.id);
222
+ // Check actual cloned directory exists and has files
223
+ const owner = b.repo_url.split('/')[0];
224
+ const repo = b.repo_url.split('/')[1];
225
+ const clonedName = `${owner}-${repo}`;
226
+ const clonedDir = path.join(githubDir, clonedName);
227
+ const dirExists = fs.existsSync(clonedDir) &&
228
+ fs.readdirSync(clonedDir).length > 0; // not empty
229
+ if (dirExists) installedSet.add(b.id);
230
230
  } else if (Array.isArray(b.skills)) {
231
- // skill-list bundle: installed if ALL skills are in DB
232
- if (b.skills.length > 0 && b.skills.every(sid => dbSkillIds.has(sid))) {
233
- installedSet.add(b.id);
234
- }
231
+ // skill-list bundle: check files exist on disk via DB path column
232
+ const allOnDisk = b.skills.every(sid => {
233
+ const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
234
+ return row && fs.existsSync(row.path);
235
+ });
236
+ if (b.skills.length > 0 && allOnDisk) installedSet.add(b.id);
235
237
  }
236
238
  }
237
239
 
238
- // Individual skills
239
- for (const id of dbSkillIds) installedSet.add(id);
240
+ // Individual marketplace skills — check file exists on disk
241
+ for (const row of db.prepare('SELECT id, path FROM skills WHERE source = ?').all('marketplace')) {
242
+ if (fs.existsSync(row.path)) installedSet.add(row.id);
243
+ }
240
244
  } catch {}
241
245
 
242
246
  const { runTUI } = await import('./tui.js');
@@ -326,6 +330,75 @@ if (args[0] === 'search') {
326
330
  }
327
331
 
328
332
  if (args[0] === 'bundle') {
333
+ if (args[1] === 'update') {
334
+ const { loadConfig: _lcUpd, SKILLS_STORE_DIR: _ssDir } = await import('./config.js');
335
+ const { indexSource } = await import('./indexer.js');
336
+ const cfg = _lcUpd();
337
+ const githubSources = cfg.sources.filter(s => s.source.startsWith('github:'));
338
+
339
+ if (!githubSources.length) { info('No GitHub bundles installed.'); process.exit(0); }
340
+
341
+ const targetId = args[2]; // optional: pg bundle update <id>
342
+ const toUpdate = targetId
343
+ ? githubSources.filter(s => s.source.toLowerCase().includes(targetId.toLowerCase()))
344
+ : githubSources;
345
+
346
+ if (!toUpdate.length) { error(`No installed bundle matching "${targetId}"`); process.exit(1); }
347
+
348
+ let updated = 0, unchanged = 0, failed = 0;
349
+
350
+ for (const src of toUpdate) {
351
+ const repoName = src.source.replace('github:', '');
352
+ const dest = src.dir.replace(/[/\\]skills$|[/\\]commands$|[/\\]prompts$/, ''); // get repo root
353
+ const repoRoot = fs.existsSync(path.join(dest, '.git')) ? dest : src.dir;
354
+
355
+ if (!fs.existsSync(path.join(repoRoot, '.git'))) {
356
+ console.log(chalk.gray(` skip ${repoName} (not a git repo)`));
357
+ continue;
358
+ }
359
+
360
+ process.stdout.write(` Checking ${chalk.white(repoName)}... `);
361
+
362
+ // Get current HEAD hash
363
+ const before = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim();
364
+
365
+ // Fetch + reset (same as install)
366
+ const fetch = spawnSync('git', ['-C', repoRoot, 'fetch', '--depth=1', 'origin'], { stdio: 'pipe' });
367
+ if (fetch.status !== 0) {
368
+ console.log(chalk.red('fetch failed'));
369
+ failed++;
370
+ continue;
371
+ }
372
+ const reset = spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/HEAD'], { stdio: 'pipe' });
373
+ if (reset.status !== 0) {
374
+ spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/main'], { stdio: 'pipe' });
375
+ }
376
+
377
+ const after = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim();
378
+
379
+ if (before === after) {
380
+ console.log(chalk.gray('already up to date'));
381
+ unchanged++;
382
+ continue;
383
+ }
384
+
385
+ // Count changed .md files
386
+ const diff = spawnSync('git', ['-C', repoRoot, 'diff', '--name-only', before, after], { encoding: 'utf8' });
387
+ const changedMd = (diff.stdout || '').split('\n').filter(f => f.endsWith('.md')).length;
388
+ console.log(chalk.green(`${changedMd} files changed`) + chalk.gray(` (${before.slice(0,7)} → ${after.slice(0,7)})`));
389
+
390
+ // Reindex only this source — incremental hash check skips unchanged files
391
+ await indexSource(src.dir, src.source);
392
+ updated++;
393
+ }
394
+
395
+ console.log();
396
+ if (updated) success(`Updated ${updated} bundle(s)`);
397
+ if (unchanged) info(chalk.gray(`${unchanged} already up to date`));
398
+ if (failed) error(`${failed} failed`);
399
+ process.exit(failed > 0 ? 1 : 0);
400
+ }
401
+
329
402
  if (args[1] === 'install') {
330
403
  const { installBundle } = await import('./marketplace.js');
331
404
  const result = await installBundle(args[2]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {