#!/usr/bin/env python3 """Add provenance frontmatter (author/adapted-from) + missing versions to all skills/. Idempotent: only inserts lines that are absent. Preserves all existing content. Usage: python scripts/add-provenance.py (run from repo root) """ import os import re import sys ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") SKILLS = os.path.join(ROOT, "skills") # skill -> provenance line(s) to guarantee present in frontmatter PROVENANCE = { # ours — authored in this workshop/ecosystem "active-platform": ["author: ours", "version: 0.1.0"], "browser-cdp": ["author: ours"], "delegate-task": ["author: ours"], "inter-session-peer-discipline": ["author: ours"], "meta-host-routing": ["author: ours"], "private-dev-public-publish": ["author: ours"], "project-bootstrap": ["author: ours"], "project-discipline": ["author: ours"], "pulling-before-work": ["author: ours"], "ralph-loop-execution": ["author: ours"], "recommend-dont-menu": ["author: ours"], "session-handoff": ["author: ours"], "session-inbox-monitor": ["author: ours"], "setup-agents-task-runner": ["author: ours"], "setup-context7": ["author: ours"], "setup-interns": ["author: ours"], "setup-projects-meta": ["author: ours"], "setup-tasks": ["author: ours"], "setup-wiki": ["author: ours"], "task-format": ["author: ours"], "task-loop": ["author: ours"], "tdd-criteria": ["author: ours"], "update-skills": ["author: ours"], "using-context7": ["author: ours"], "using-interns": ["author: ours"], "using-markitdown": ["author: ours"], "using-projects-meta": ["author: ours"], "using-system-snapshot": ["author: ours"], "using-tasks": ["author: ours"], "using-vds-ops": ["author: ours"], "using-wiki": ["author: ours"], "using-wiki-graph": ["author: ours"], # adapted — vendored copies, sovereignly maintained; pins to backfill (see README "Adapted from") "caveman": ["adapted-from: JuliusBrussee/caveman (MIT) — vendored copy, upstream pin TBD", "version: 0.1.0"], "caveman-commit": ["adapted-from: JuliusBrussee/caveman (MIT) — vendored copy, upstream pin TBD", "version: 0.1.0"], "caveman-compress": ["adapted-from: JuliusBrussee/caveman (MIT) — vendored copy, upstream pin TBD"], "caveman-help": ["adapted-from: JuliusBrussee/caveman (MIT) — vendored copy, upstream pin TBD", "version: 0.1.0"], "caveman-review": ["adapted-from: JuliusBrussee/caveman (MIT) — vendored copy, upstream pin TBD", "version: 0.1.0"], "find-skills": ["adapted-from: vercel-labs/skills (MIT) — vendored copy, upstream pin TBD", "version: 0.1.0"], } FRONTMATTER_RE = re.compile(r"^(---\s*\n)(.*?)(\n---\s*\n)", re.DOTALL) changed = [] errors = [] for name, lines in sorted(PROVENANCE.items()): path = os.path.join(SKILLS, name, "SKILL.md") if not os.path.exists(path): errors.append(f"MISSING: {path}") continue with open(path, encoding="utf-8") as f: text = f.read() m = FRONTMATTER_RE.match(text) if not m: errors.append(f"{name}: no frontmatter") continue fm = m.group(2) new_lines = [] inserted = False for line in fm.splitlines(): # insert provenance after the name: line (keep name-first convention) if not inserted and line.startswith("name:"): new_lines.append(line) for prov in lines: key = prov.split(":", 1)[0] # skip if this key already present anywhere in frontmatter if not any(existing.startswith(key + ":") for existing in fm.splitlines()): new_lines.append(prov) inserted = True continue # drop old lines that we're replacing (e.g. duplicate version) new_lines.append(line) # dedupe: remove lines for keys that now appear twice seen = {} final = [] for line in new_lines: key = line.split(":", 1)[0] if key in ("name",): final.append(line) continue if key in seen and seen[key] >= 1: continue # keep first occurrence seen[key] = seen.get(key, 0) + 1 final.append(line) new_fm = "\n".join(final) if new_fm != fm: new_text = text[: m.start(2)] + new_fm + text[m.end(2):] with open(path, "w", encoding="utf-8", newline="\n") as f: f.write(new_text) changed.append(name) else: print(f" = {name}: no change") print(f"\nChanged ({len(changed)}): {', '.join(changed)}") if errors: print(f"\nERRORS ({len(errors)}):") for e in errors: print(" ", e) sys.exit(1)