promptgraph-mcp 2.4.0 → 2.4.2

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
@@ -4,7 +4,8 @@ import fs from 'fs';
4
4
  import https from 'https';
5
5
  import { globSync } from 'glob';
6
6
  import { indexAll, indexSource } from './indexer.js';
7
- import { loadConfig, saveConfig, SKILLS_STORE_DIR } from './config.js';
7
+ import { loadConfig, saveConfig, PROMPTGRAPH_DIR, SKILLS_STORE_DIR } from './config.js';
8
+ import { validateSkill } from './validator.js';
8
9
 
9
10
  const SKILL_DIRS = ['skills', 'commands', 'prompts', 'agents', 'skills-store', 'slash-commands', 'custom-commands', 'templates'];
10
11
 
@@ -33,6 +34,58 @@ function repoExists(ownerRepo) {
33
34
  });
34
35
  }
35
36
 
37
+ // Validate all .md files in a repo's skills subdir against validateSkill().
38
+ // Returns { ok, errors[], warnings[] }.
39
+ export async function validateRepoSkills(ownerRepo) {
40
+ const errors = [];
41
+ const warnings = [];
42
+
43
+ const detected = await detectSkillsDirFromAPI(ownerRepo);
44
+ if (!detected) {
45
+ return { ok: false, errors: [`No skills directory found in ${ownerRepo}`], warnings: [] };
46
+ }
47
+
48
+ const subdir = detected.subdir;
49
+ const listUrl = `https://api.github.com/repos/${ownerRepo}/contents/${subdir}`;
50
+ let entries;
51
+ try {
52
+ const json = await httpsGet(listUrl);
53
+ entries = JSON.parse(json);
54
+ } catch (e) {
55
+ return { ok: false, errors: [`Failed to list ${subdir}/ contents: ${e.message}`], warnings: [] };
56
+ }
57
+
58
+ const mdFiles = entries.filter(e => e.type === 'file' && e.name.endsWith('.md'));
59
+ if (mdFiles.length === 0) {
60
+ return { ok: false, errors: [`No .md files found in ${subdir}/`], warnings: [] };
61
+ }
62
+
63
+ const tmpDir = path.join(PROMPTGRAPH_DIR, '.validate-tmp');
64
+ fs.mkdirSync(tmpDir, { recursive: true });
65
+
66
+ for (const file of mdFiles) {
67
+ try {
68
+ const content = await httpsGet(file.download_url);
69
+ const tmpPath = path.join(tmpDir, file.name);
70
+ fs.writeFileSync(tmpPath, content);
71
+ const result = validateSkill(tmpPath);
72
+ if (!result.ok) {
73
+ errors.push(`${file.name}: ${result.errors.join('; ')}`);
74
+ }
75
+ if (result.warnings?.length) {
76
+ warnings.push(...result.warnings.map(w => `${file.name}: ${w}`));
77
+ }
78
+ fs.unlinkSync(tmpPath);
79
+ } catch (e) {
80
+ errors.push(`${file.name}: failed to validate — ${e.message}`);
81
+ }
82
+ }
83
+
84
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
85
+
86
+ return { ok: errors.length === 0, errors, warnings };
87
+ }
88
+
36
89
  // Ask GitHub API which subdir to use (without cloning anything). Exported for validation.
37
90
  export
38
91
  // Returns { subdir, label } or null (use root).
package/marketplace.js CHANGED
@@ -5,9 +5,10 @@ import { createHash } from 'crypto';
5
5
  import { spawnSync } from 'child_process';
6
6
  import { createRequire } from 'module';
7
7
  import { getDb } from './db.js';
8
+ import { globSync } from 'glob';
8
9
  import { validateSkill, validateBundle } from './validator.js';
9
10
  import { loadConfig, saveConfig, PROMPTGRAPH_DIR, SKILLS_STORE_DIR } from './config.js';
10
- import { importFromGitHub } from './github-import.js';
11
+ import { importFromGitHub, validateRepoSkills } from './github-import.js';
11
12
 
12
13
  const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
13
14
  const SKILLS_DIR = path.join(SKILLS_STORE_DIR, 'marketplace');
@@ -316,6 +317,19 @@ export async function publishBundle(bundleDef) {
316
317
  return { error: 'Bundle validation failed', issues: validation.errors, warnings: validation.warnings };
317
318
  }
318
319
 
320
+ // Validate repo skills content if repo_url is set
321
+ if (def.repo_url) {
322
+ const ownerRepo = def.repo_url.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '');
323
+ const repoValidation = await validateRepoSkills(ownerRepo);
324
+ if (!repoValidation.ok) {
325
+ return {
326
+ error: 'Repo skills validation failed',
327
+ issues: repoValidation.errors,
328
+ warnings: repoValidation.warnings,
329
+ };
330
+ }
331
+ }
332
+
319
333
  const bundleJson = JSON.stringify(def, null, 2);
320
334
  const tmpFile = path.join(PROMPTGRAPH_DIR, `bundle-${def.id}.json`);
321
335
  fs.mkdirSync(PROMPTGRAPH_DIR, { recursive: true });
@@ -385,3 +399,53 @@ export function recordFail(skillId) {
385
399
  ON CONFLICT(skill_id) DO UPDATE SET fail = fail + 1
386
400
  `).run(skillId);
387
401
  }
402
+
403
+ // Scan all imported repos, validate their .md files, and remove repos that fail.
404
+ // Returns { removed: string[], kept: string[], errors: string[] }.
405
+ export function pruneInvalidRepos() {
406
+ const config = loadConfig();
407
+ const removed = [];
408
+ const kept = [];
409
+ const errors = [];
410
+
411
+ const repoSources = config.sources.filter(s => s.source?.startsWith('github:'));
412
+ if (repoSources.length === 0) {
413
+ return { removed, kept, errors: [], message: 'No imported repos found.' };
414
+ }
415
+
416
+ for (const src of repoSources) {
417
+ const repoName = src.source.replace('github:', '');
418
+ if (!fs.existsSync(src.dir)) {
419
+ removed.push({ repo: repoName, reason: 'directory not found' });
420
+ config.sources = config.sources.filter(s => s !== src);
421
+ continue;
422
+ }
423
+
424
+ const mdFiles = globSync(`${src.dir}/**/*.md`);
425
+ if (mdFiles.length === 0) {
426
+ removed.push({ repo: repoName, reason: 'no .md files' });
427
+ config.sources = config.sources.filter(s => s !== src);
428
+ try { fs.rmSync(src.dir, { recursive: true, force: true }); } catch {}
429
+ continue;
430
+ }
431
+
432
+ const invalid = [];
433
+ for (const fp of mdFiles) {
434
+ const v = validateSkill(fp);
435
+ if (!v.ok) {
436
+ invalid.push({ file: path.relative(src.dir, fp), errors: v.errors });
437
+ }
438
+ }
439
+
440
+ if (invalid.length > 0) {
441
+ removed.push({ repo: repoName, reason: `${invalid.length}/${mdFiles.length} .md files failed validation`, invalid });
442
+ config.sources = config.sources.filter(s => s !== src);
443
+ try { fs.rmSync(src.dir, { recursive: true, force: true }); } catch (e) { errors.push(`Failed to remove ${src.dir}: ${e.message}`); }
444
+ } else {
445
+ kept.push(repoName);
446
+ }
447
+ }
448
+
449
+ saveConfig(config);
450
+ return { removed, kept, errors };
451
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,4 +51,4 @@
51
51
  "tar": "^6.2.1",
52
52
  "js-yaml": "^4.1.0"
53
53
  }
54
- }
54
+ }