Files
skills/scripts/add-provenance.py
vitya ab51c2ef04 feat(catalog): sovereign catalog — provenance + de-superpowers (idea 12)
- author: ours / adapted-from frontmatter on all 38 skills (scripts/add-provenance.py, idempotent)
- versions added to vendor copies (caveman x5, find-skills) + active-platform
- README(en/ru): new Sovereignty/Adapted-from section, drop wiki-maintainer & superpowers refs
- project-bootstrap v1.12.0 -> v1.13.0: remove 'use superpowers' trigger from template
  + trigger table; recommend-dont-menu wording no longer references superpowers:brainstorming
- dist rebuilt to 38 (pruned stale update-claude-skills.skill)
- lint: 0 violations, 0 warnings
2026-08-12 16:02:11 +03:00

116 lines
4.6 KiB
Python

#!/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)