()
| 570 | console.log(` [dry-run] Would copy skill tree: ${sourceDir} -> ${targetDir}`); |
| 571 | return; |
| 572 | } |
| 573 | // fs.cpSync requires Node 16.7+. The project documents Node 18+ as the |
| 574 | // minimum (see Codex install guide), so this is safe. |
| 575 | fs.cpSync(sourceDir, targetDir, { |
| 576 | recursive: true, |
| 577 | force: true, |
| 578 | errorOnExist: false, |
| 579 | filter: (src) => !SKILL_COPY_EXCLUDES.has(path.basename(src)), |
| 580 | }); |
| 581 | } |
| 582 | |
| 583 | function syncSkills() { |
| 584 | console.log('Syncing skills to .agents/skills/...'); |
| 585 | |
| 586 | const sourceDir = path.join(CITADEL_ROOT, 'skills'); |
| 587 | if (!fs.existsSync(sourceDir)) { |
| 588 | console.log(' No skills/ directory found, skipping.'); |
| 589 | return; |
| 590 | } |
| 591 | |
| 592 | const targetBase = path.join(PROJECT_ROOT, '.agents', 'skills'); |
| 593 | const skillDirs = fs.readdirSync(sourceDir).filter(d => |
| 594 | fs.existsSync(path.join(sourceDir, d, 'SKILL.md')) |
| 595 | ); |
| 596 | |
| 597 | let synced = 0; |
| 598 | for (const skillName of skillDirs) { |
| 599 | const sourceSkillDir = path.join(sourceDir, skillName); |
| 600 | const sourcePath = path.join(sourceSkillDir, 'SKILL.md'); |
| 601 | const targetDir = path.join(targetBase, skillName); |
| 602 | |
| 603 | // Copy the entire skill directory (SKILL.md + scripts/, references/, |
| 604 | // assets/, __benchmarks__/, etc.). Excluded names from SKILL_COPY_EXCLUDES |
| 605 | // are skipped at every depth via the filter callback. |
| 606 | copySkillTree(sourceSkillDir, targetDir); |
| 607 | |
| 608 | // Generate openai.yaml from frontmatter — written after the copy so |
| 609 | // it overlays anything that might have slipped in. |
| 610 | const content = fs.readFileSync(sourcePath, 'utf8'); |
| 611 | const fm = parseFrontmatter(content); |
| 612 | if (fm.name || fm.description) { |
no test coverage detected