feat(skills): author root SKILL.md for reference-curator suite + 7 sub-skills

Finish the migration for the dirs the bulk pass couldn't auto-handle:

- 90-reference-curator/SKILL.md: hand-authored suite orchestrator (pipeline overview,
  7-stage table, /reference-curator run modes, install) — the single loadable entry.
- 90-reference-curator/0{1..7}-*/SKILL.md: generated from each sub-skill's desktop/SKILL.md.
- scripts/migrate_skill_root.py: generalized discovery to find nested suite sub-skills
  (rglob desktop/code SKILL.md), so the migrator now handles suites too.

81-mac-optimizer, 91-multi-agent-guide, 94-dintel-bootstrap need NO root SKILL.md: they
are Claude Code plugins whose skill correctly lives at skills/<name>/SKILL.md (validated).
Adding a root SKILL.md there would violate plugin structure.

All SKILL.md repo-wide validate: flat-root=65, suite-sub=7, plugin-skills=3, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 01:14:46 +09:00
parent 60734dbde7
commit 0496262cd5
9 changed files with 1737 additions and 5 deletions

View File

@@ -45,6 +45,21 @@ def find_source(d):
return None
def discover_skill_dirs():
"""Top-level skill dirs PLUS any nested dir that has a desktop/ or code/ SKILL.md
(e.g. the sub-skills of a suite like 90-reference-curator). Plugins keep their skill
at skills/<name>/SKILL.md with no desktop/ or code/, so they are correctly left alone."""
found = {}
for d in SKILLS.iterdir(): # top-level: report SKIP/MANUAL/CREATE
if is_skill_dir(d):
found[d] = True
for src in list(SKILLS.rglob("desktop/SKILL.md")) + list(SKILLS.rglob("code/SKILL.md")):
d = src.parent.parent # nested sub-skills (suite members)
if is_skill_dir(d):
found[d] = True
return sorted(found)
def set_name(frontmatter, name):
"""Replace the single-line `name:` value (or prepend one) with `name`."""
if re.search(r"^name:.*$", frontmatter, re.M):
@@ -80,10 +95,8 @@ def main(argv=None):
rows = [] # (dir, status, detail)
created = skipped = manual = warned = 0
for d in sorted(SKILLS.iterdir()):
if not is_skill_dir(d):
continue
name = d.name
for d in discover_skill_dirs():
name = str(d.relative_to(SKILLS))
if (d / "SKILL.md").exists():
rows.append((name, "SKIP", "already has root SKILL.md"))
skipped += 1
@@ -93,7 +106,7 @@ def main(argv=None):
rows.append((name, "MANUAL", "no desktop/ or code/ SKILL.md source (commands/README only)"))
manual += 1
continue
text, issues = build_root_skill(src.read_text(encoding="utf-8"), name)
text, issues = build_root_skill(src.read_text(encoding="utf-8"), d.name)
rel = src.relative_to(d)
if text is None:
rows.append((name, "MANUAL", f"{rel}: {issues[0]}"))