promptgraph-mcp 2.4.2 → 2.4.4

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
@@ -34,53 +34,80 @@ function repoExists(ownerRepo) {
34
34
  });
35
35
  }
36
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) {
37
+ // Download one .md file, run validateSkill on it, return errors/warnings.
38
+ async function validateMdFile(file, tmpDir) {
40
39
  const errors = [];
41
40
  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
41
  try {
52
- const json = await httpsGet(listUrl);
53
- entries = JSON.parse(json);
42
+ const content = await httpsGet(file.download_url);
43
+ const tmpPath = path.join(tmpDir, file.name);
44
+ fs.writeFileSync(tmpPath, content);
45
+ const result = validateSkill(tmpPath);
46
+ if (!result.ok) {
47
+ errors.push(`${file.name}: ${result.errors.join('; ')}`);
48
+ }
49
+ if (result.warnings?.length) {
50
+ warnings.push(...result.warnings.map(w => `${file.name}: ${w}`));
51
+ }
52
+ fs.unlinkSync(tmpPath);
54
53
  } 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: [] };
54
+ errors.push(`${file.name}: failed to validate ${e.message}`);
61
55
  }
56
+ return { errors, warnings };
57
+ }
62
58
 
59
+ // Validate all .md files in a repo's skills subdir against validateSkill().
60
+ // Falls back to root-level .md files if no skills subdirectory is found.
61
+ // Returns { ok, errors[], warnings[] }.
62
+ export async function validateRepoSkills(ownerRepo) {
63
+ const detected = await detectSkillsDirFromAPI(ownerRepo);
63
64
  const tmpDir = path.join(PROMPTGRAPH_DIR, '.validate-tmp');
64
65
  fs.mkdirSync(tmpDir, { recursive: true });
65
66
 
66
- for (const file of mdFiles) {
67
+ let mdFiles;
68
+ if (detected) {
69
+ // Has a skills subdirectory — list contents
70
+ const subdir = detected.subdir;
71
+ let entries;
67
72
  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);
73
+ const json = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${subdir}`);
74
+ entries = JSON.parse(json);
75
+ } catch (e) {
76
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
77
+ return { ok: false, errors: [`Failed to list ${subdir}/ contents: ${e.message}`], warnings: [] };
78
+ }
79
+ mdFiles = entries.filter(e => e.type === 'file' && e.name.endsWith('.md'));
80
+ } else {
81
+ // No skills subdir — fall back to root-level .md files
82
+ try {
83
+ const json = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents`);
84
+ const entries = JSON.parse(json);
85
+ mdFiles = entries.filter(e => e.type === 'file' && e.name.endsWith('.md'));
79
86
  } catch (e) {
80
- errors.push(`${file.name}: failed to validate ${e.message}`);
87
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
88
+ return { ok: false, errors: [`Failed to list repo root: ${e.message}`], warnings: [] };
81
89
  }
82
90
  }
83
91
 
92
+ if (!mdFiles || mdFiles.length === 0) {
93
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
94
+ return { ok: false, errors: [`No .md files found in ${ownerRepo}`], warnings: [] };
95
+ }
96
+
97
+ // Filter out docs-like filenames (README, LICENSE, CHANGELOG, etc.)
98
+ const SKIP_DOCS = /^(readme|license|changelog|contributing|code.?of.?conduct|security|authors|credits|install|faq|index|overview|summary|todo|notes|template|copying|warranty|funding|roadmap)/i;
99
+ const mdTrimmed = mdFiles.filter(f => !SKIP_DOCS.test(f.name.replace(/\.md$/i, '')));
100
+ const mdToValidate = mdTrimmed.length > 0 ? mdTrimmed : mdFiles;
101
+
102
+ let errors = [];
103
+ let warnings = [];
104
+
105
+ for (const file of mdToValidate) {
106
+ const r = await validateMdFile(file, tmpDir);
107
+ errors.push(...r.errors);
108
+ warnings.push(...r.warnings);
109
+ }
110
+
84
111
  try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
85
112
 
86
113
  return { ok: errors.length === 0, errors, warnings };
@@ -266,17 +293,13 @@ export async function importFromGitHub(repoUrl) {
266
293
  skillsSubdir = detected?.subdir || null;
267
294
 
268
295
  if (!skillsSubdir) {
269
- throw new Error(
270
- `No skill subdirectory found in ${ownerRepo}.\n` +
271
- `Expected one of: ${SKILL_DIRS.join(', ')}\n` +
272
- `Or any subfolder with .md files.\n` +
273
- `Visit https://github.com/${ownerRepo} to check the repo structure.`
274
- );
296
+ console.log(`found: (root) — no skills subdirectory, using full clone`);
297
+ cloneOk = fullClone(url, dest);
298
+ } else {
299
+ console.log(`found: ${detected.label}/`);
300
+ console.log(`Sparse-cloning ${url} (${skillsSubdir}/ only)...`);
301
+ cloneOk = sparseClone(url, dest, skillsSubdir);
275
302
  }
276
-
277
- console.log(`found: ${detected.label}/`);
278
- console.log(`Sparse-cloning ${url} (${skillsSubdir}/ only)...`);
279
- cloneOk = sparseClone(url, dest, skillsSubdir);
280
303
  if (!cloneOk) {
281
304
  fs.rmSync(dest, { recursive: true, force: true });
282
305
  throw new Error(`Sparse-checkout failed for ${url}`);
package/marketplace.js CHANGED
@@ -177,14 +177,20 @@ export async function installSkill(query) {
177
177
  }
178
178
  }
179
179
 
180
+ // ── filter skill files (exclude docs) ─────────────────────────────────────────
181
+ const SKIP_DOCS = /^(readme|license|changelog|contributing|code.?of.?conduct|security|authors|credits|install|faq|index|overview|summary|todo|notes|template|copying|warranty|funding|roadmap)/i;
182
+ function isSkillFile(path) {
183
+ const name = path.split('/').pop().toLowerCase();
184
+ return name.endsWith('.md') && !SKIP_DOCS.test(name.replace(/\.md$/i, ''));
185
+ }
186
+
180
187
  async function countRepoSkills(repoUrl) {
181
188
  try {
182
189
  const apiUrl = `https://api.github.com/repos/${repoUrl}/git/trees/HEAD?recursive=1`;
183
190
  const res = await fetch(apiUrl, { headers: { 'User-Agent': 'promptgraph-mcp' } });
184
191
  if (!res.ok) return null;
185
192
  const data = await res.json();
186
- const exts = ['.md', '.txt', '.yaml', '.yml', '.json'];
187
- return (data.tree || []).filter(f => f.type === 'blob' && exts.some(e => f.path.toLowerCase().endsWith(e))).length;
193
+ return (data.tree || []).filter(f => f.type === 'blob' && isSkillFile(f.path)).length;
188
194
  } catch { return null; }
189
195
  }
190
196
 
@@ -194,12 +200,11 @@ export async function browseBundles(topK = 20) {
194
200
  const registry = JSON.parse(text);
195
201
  const bundles = registry.bundles || [];
196
202
  await Promise.all(bundles.map(async b => {
197
- if (b.repo_url && b.skillCount === undefined) {
198
- const count = await countRepoSkills(b.repo_url);
199
- if (count !== null) b.skillCount = count;
200
- }
203
+ // Re-count all repo bundles with the new .md-only filter
204
+ if (b.repo_url) b.skillCount = await countRepoSkills(b.repo_url) ?? 0;
201
205
  }));
202
206
  return bundles
207
+ .filter(b => !b.repo_url || b.skillCount > 0) // hide bundles with 0 .md files
203
208
  .map(b => ({ ...b, code: b.code || codeFor(b.id) }))
204
209
  .sort((a, b) => (b.stars || 0) - (a.stars || 0))
205
210
  .slice(0, topK);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {