promptgraph-mcp 2.3.1 → 2.3.3

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
@@ -33,19 +33,42 @@ function repoExists(ownerRepo) {
33
33
  });
34
34
  }
35
35
 
36
- // Ask GitHub API which skill subdir exists (without cloning anything)
36
+ // Ask GitHub API which subdir to use (without cloning anything).
37
+ // Returns { subdir, label } or null (use root).
37
38
  async function detectSkillsDirFromAPI(ownerRepo) {
38
39
  try {
39
40
  const json = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents`);
40
41
  const entries = JSON.parse(json);
41
- const dirs = new Set(entries.filter(e => e.type === 'dir').map(e => e.name.toLowerCase()));
42
+
43
+ // 1. Known skill dir names (priority order)
44
+ const dirMap = new Map(entries.filter(e => e.type === 'dir').map(e => [e.name.toLowerCase(), e.name]));
42
45
  for (const d of SKILL_DIRS) {
43
- if (dirs.has(d)) return d;
46
+ if (dirMap.has(d)) return { subdir: dirMap.get(d), label: d };
47
+ }
48
+
49
+ // 2. Any subdir with 2+ .md files — pick the one with most .md files
50
+ const subdirCandidates = entries.filter(e => e.type === 'dir' && !SKIP_DIRS_API.has(e.name.toLowerCase()));
51
+ let best = null, bestCount = 0;
52
+ for (const dir of subdirCandidates) {
53
+ try {
54
+ const sub = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${dir.name}`);
55
+ const subEntries = JSON.parse(sub);
56
+ const mdCount = subEntries.filter(e => e.type === 'file' && e.name.endsWith('.md')).length;
57
+ if (mdCount >= 2 && mdCount > bestCount) { best = dir.name; bestCount = mdCount; }
58
+ } catch {}
44
59
  }
60
+ if (best) return { subdir: best, label: best };
61
+
45
62
  } catch {}
46
- return null; // use repo root
63
+ return null; // no good subdir found — use root
47
64
  }
48
65
 
66
+ const SKIP_DIRS_API = new Set([
67
+ '.github', 'docs', 'doc', 'documentation', 'examples', 'example',
68
+ 'tests', 'test', 'assets', 'images', 'img', 'media', 'static',
69
+ 'node_modules', 'vendor', 'dist', 'build', '.git',
70
+ ]);
71
+
49
72
  function git(args, cwd, stdio = 'inherit') {
50
73
  return spawnSync('git', args, { cwd, stdio });
51
74
  }
@@ -142,21 +165,22 @@ export async function importFromGitHub(repoUrl) {
142
165
 
143
166
  if (isNew) {
144
167
  // Detect skills dir via API before cloning
145
- console.log(`Detecting skills directory for ${ownerRepo}...`);
146
- skillsSubdir = await detectSkillsDirFromAPI(ownerRepo);
168
+ process.stdout.write(`Detecting skills directory for ${ownerRepo}... `);
169
+ const detected = await detectSkillsDirFromAPI(ownerRepo);
170
+ skillsSubdir = detected?.subdir || null;
147
171
 
148
172
  if (skillsSubdir) {
173
+ console.log(`found: ${detected.label}/`);
149
174
  console.log(`Sparse-cloning ${url} (${skillsSubdir}/ only)...`);
150
175
  cloneOk = sparseClone(url, dest, skillsSubdir);
151
176
  if (!cloneOk) {
152
- // Sparse failed — fall back to full clone
153
177
  console.log('Sparse-checkout failed, falling back to full clone...');
154
178
  fs.rmSync(dest, { recursive: true, force: true });
155
179
  cloneOk = fullClone(url, dest);
156
180
  skillsSubdir = null;
157
181
  }
158
182
  } else {
159
- console.log(`Cloning ${url}...`);
183
+ console.log(`no subdir found, cloning root...`);
160
184
  cloneOk = fullClone(url, dest);
161
185
  }
162
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/parser.js CHANGED
@@ -19,7 +19,7 @@ const SKIP_FILENAMES = new Set([
19
19
  ]);
20
20
 
21
21
  // Filename patterns that are never skills
22
- const SKIP_FILENAME_RE = /^(_|\.)|^v?\d+[\.\-]\d+|^\d{4}[\-_]\d{2}/i; // _template, .hidden, v1.0, 2024-01
22
+ const SKIP_FILENAME_RE = /^(_|\.)|^v?\d+[\.\-]\d+|^\d{4}[\-_]\d{2}|^readme|^license|^changelog|^contributing/i;
23
23
 
24
24
  // Path segments that indicate the file is NOT a skill
25
25
  const SKIP_DIRS = new Set([