* Concatenate the skill's scannable text — SKILL.md plus any support files whose * content the agent might read or execute (.md/.txt/.sh/.bash/.zsh/.py/.js/.ts/.rb/ * .pl/.ps1/.yaml/.yml/.json). Binary and large files are skipped; total is capped * so a giant repo can't blow up memory. Best-effor
(root: string)
| 287 | * so a giant repo can't blow up memory. Best-effort: unreadable files are ignored. |
| 288 | */ |
| 289 | async function gatherSkillText(root: string): Promise<string> { |
| 290 | const SCAN_EXT = new Set([ |
| 291 | '.md', '.markdown', '.txt', '.sh', '.bash', '.zsh', '.fish', '.py', '.js', '.mjs', |
| 292 | '.cjs', '.ts', '.rb', '.pl', '.ps1', '.yaml', '.yml', '.json', '.toml', '.env', |
| 293 | ]); |
| 294 | const MAX_FILE = 256 * 1024; // 256KB per file |
| 295 | const MAX_TOTAL = 2 * 1024 * 1024; // 2MB overall |
| 296 | const SKIP_DIR = new Set(['.git', 'node_modules', '.github', 'dist', 'build', '__pycache__', '.venv']); |
| 297 | let total = 0; |
| 298 | const chunks: string[] = []; |
| 299 | |
| 300 | async function walk(dir: string): Promise<void> { |
| 301 | let entries: import('fs').Dirent[]; |
| 302 | try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return; } |
| 303 | for (const e of entries) { |
| 304 | if (total >= MAX_TOTAL) return; |
| 305 | const full = path.join(dir, e.name); |
| 306 | if (e.isDirectory()) { |
| 307 | if (!SKIP_DIR.has(e.name)) await walk(full); |
| 308 | continue; |
| 309 | } |
| 310 | const ext = path.extname(e.name).toLowerCase(); |
| 311 | // SKILL.md has no "code" ext but is always scanned; otherwise gate by ext. |
| 312 | if (e.name !== 'SKILL.md' && !SCAN_EXT.has(ext)) continue; |
| 313 | try { |
| 314 | const stat = await fs.stat(full); |
| 315 | if (stat.size > MAX_FILE) continue; |
| 316 | const text = await fs.readFile(full, 'utf-8'); |
| 317 | chunks.push(`\n# ── ${path.relative(root, full)} ──\n${text}`); |
| 318 | total += text.length; |
| 319 | } catch { /* skip unreadable */ } |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | await walk(root); |
| 324 | return chunks.join('\n'); |
| 325 | } |