- 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
116 lines
4.6 KiB
Python
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)
|