promptgraph-mcp 2.9.52 → 2.9.53
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 +69 -52
- package/package.json +1 -1
package/github-import.js
CHANGED
|
@@ -205,70 +205,87 @@ export async function validateRepoSkills(ownerRepo) {
|
|
|
205
205
|
|
|
206
206
|
const SCRIPT_EXTS_API = new Set(['.py', '.sh', '.bash', '.js', '.ts', '.rb']);
|
|
207
207
|
|
|
208
|
-
function countValidMd(fileEntries) {
|
|
209
|
-
return fileEntries.filter(e =>
|
|
210
|
-
e.type === 'file' && e.name.endsWith('.md') &&
|
|
211
|
-
!SKIP_RE.test(e.name.replace(/\.md$/i, '').toLowerCase())
|
|
212
|
-
).length;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function hasScriptFiles(fileEntries) {
|
|
216
|
-
return fileEntries.some(e => e.type === 'file' && SCRIPT_EXTS_API.has(path.extname(e.name).toLowerCase()));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
208
|
// Ask GitHub API which subdir to use (without cloning anything). Exported for validation.
|
|
220
209
|
// Returns { subdir, label, validMdCount, hasScripts } or null (repo not found / no skills).
|
|
210
|
+
//
|
|
211
|
+
// Uses the recursive git-tree API (1 call) and counts .md files RECURSIVELY under each
|
|
212
|
+
// candidate dir. This handles nested layouts like skills/cloud/*.md or skills/<name>/SKILL.md
|
|
213
|
+
// where a skill dir holds category subfolders rather than .md files directly — the old
|
|
214
|
+
// shallow per-dir listing reported "0 skills" for those and blocked publishing.
|
|
221
215
|
export
|
|
222
216
|
async function detectSkillsDirFromAPI(ownerRepo) {
|
|
217
|
+
let tree;
|
|
223
218
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
219
|
+
tree = JSON.parse(await httpsGet(`https://api.github.com/repos/${ownerRepo}/git/trees/HEAD?recursive=1`));
|
|
220
|
+
} catch {
|
|
221
|
+
return null; // repo not found or inaccessible
|
|
222
|
+
}
|
|
223
|
+
if (!tree || !Array.isArray(tree.tree)) return null;
|
|
224
|
+
|
|
225
|
+
const blobs = tree.tree.filter(f => f.type === 'blob');
|
|
226
|
+
if (blobs.length === 0) return null;
|
|
227
|
+
|
|
228
|
+
// A path is a valid skill .md if the filename isn't meta (readme/license/…) and no
|
|
229
|
+
// path segment is a skip dir (docs/tests/assets/…).
|
|
230
|
+
const isValidMd = (p) => {
|
|
231
|
+
if (!p.endsWith('.md')) return false;
|
|
232
|
+
if (SKIP_RE.test(path.basename(p, '.md').toLowerCase())) return false;
|
|
233
|
+
const segs = p.split('/');
|
|
234
|
+
for (const seg of segs.slice(0, -1)) if (SKIP_DIRS_API.has(seg.toLowerCase())) return false;
|
|
235
|
+
return true;
|
|
236
|
+
};
|
|
237
|
+
const mdBlobs = blobs.filter(f => isValidMd(f.path));
|
|
238
|
+
if (mdBlobs.length === 0) return null;
|
|
239
|
+
|
|
240
|
+
const countUnder = (prefix) => mdBlobs.filter(f => f.path.startsWith(prefix)).length;
|
|
241
|
+
const scriptUnder = (prefix) => blobs.some(f => f.path.startsWith(prefix) && SCRIPT_EXTS_API.has(path.extname(f.path).toLowerCase()));
|
|
242
|
+
|
|
243
|
+
// Map of top-level dir names (lowercase -> real casing)
|
|
244
|
+
const topDirs = new Map();
|
|
245
|
+
for (const f of blobs) {
|
|
246
|
+
const idx = f.path.indexOf('/');
|
|
247
|
+
if (idx > 0) { const d = f.path.slice(0, idx); topDirs.set(d.toLowerCase(), d); }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 1. Known skill dir names (priority order) — counted recursively
|
|
251
|
+
for (const d of SKILL_DIRS) {
|
|
252
|
+
if (topDirs.has(d)) {
|
|
253
|
+
const real = topDirs.get(d);
|
|
254
|
+
const c = countUnder(`${real}/`);
|
|
255
|
+
if (c > 0) return { subdir: real, label: real, validMdCount: c, hasScripts: scriptUnder(`${real}/`) };
|
|
238
256
|
}
|
|
257
|
+
}
|
|
239
258
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (validMdCount > 0) return { subdir: nested, label: nested, validMdCount, hasScripts: hasScriptFiles(sub) };
|
|
249
|
-
} catch {}
|
|
250
|
-
}
|
|
259
|
+
// 1.5 Nested skill dirs (e.g. .claude/skills, .claude-plugin/commands)
|
|
260
|
+
for (const prefix of ['.claude', '.claude-plugin']) {
|
|
261
|
+
if (topDirs.has(prefix)) {
|
|
262
|
+
const real = topDirs.get(prefix);
|
|
263
|
+
for (const d of SKILL_DIRS) {
|
|
264
|
+
const nested = `${real}/${d}`;
|
|
265
|
+
const c = countUnder(`${nested}/`);
|
|
266
|
+
if (c > 0) return { subdir: nested, label: nested, validMdCount: c, hasScripts: scriptUnder(`${nested}/`) };
|
|
251
267
|
}
|
|
252
268
|
}
|
|
269
|
+
}
|
|
253
270
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
let best = null, bestCount = 0, bestScripts = false;
|
|
257
|
-
for (const dir of subdirCandidates) {
|
|
258
|
-
try {
|
|
259
|
-
const sub = JSON.parse(await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${dir.name}`));
|
|
260
|
-
const mdCount = countValidMd(sub);
|
|
261
|
-
if (mdCount > bestCount) { best = dir.name; bestCount = mdCount; bestScripts = hasScriptFiles(sub); }
|
|
262
|
-
} catch {}
|
|
263
|
-
}
|
|
264
|
-
if (best) return { subdir: best, label: best, validMdCount: bestCount, hasScripts: bestScripts };
|
|
271
|
+
// 2. Root-level .md files (skills kept directly at repo root)
|
|
272
|
+
const rootMd = mdBlobs.filter(f => !f.path.includes('/')).length;
|
|
265
273
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
274
|
+
// 3. Best non-skip top-level subdir by recursive .md count
|
|
275
|
+
let best = null, bestCount = 0;
|
|
276
|
+
for (const [low, real] of topDirs) {
|
|
277
|
+
if (SKIP_DIRS_API.has(low)) continue;
|
|
278
|
+
const c = countUnder(`${real}/`);
|
|
279
|
+
if (c > bestCount) { bestCount = c; best = real; }
|
|
280
|
+
}
|
|
269
281
|
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
// Prefer root when it holds the skills (and is at least as rich as any subdir)
|
|
283
|
+
if (rootMd >= 1 && rootMd >= bestCount) {
|
|
284
|
+
return { subdir: null, label: 'root', validMdCount: rootMd, hasScripts: scriptUnder('') };
|
|
285
|
+
}
|
|
286
|
+
if (best) return { subdir: best, label: best, validMdCount: bestCount, hasScripts: scriptUnder(`${best}/`) };
|
|
287
|
+
|
|
288
|
+
return null;
|
|
272
289
|
}
|
|
273
290
|
|
|
274
291
|
// Deep-validate a repo bundle via 1 API call (tree) + raw file fetches (not rate-limited).
|