#!/usr/bin/env python3 """Add decision_refs provenance frontmatter to workshop outcomes (pql migration, Phase 5). Workshop -> decision provenance was prose-only. This injects a `decision_refs:` YAML frontmatter list (the confirmed D-records each workshop-outcomes.md touches) so pql can answer "which workshop produced D-NNN" via pql query "SELECT name, fm.workshop WHERE fm.decision_refs CONTAINS 'D-238'" decision_refs = distinct valid confirmed-D ids mentioned in the file body (filtered against the governance decision set, so typos/multi-refs/stale ids are dropped). Files without frontmatter get a minimal block. Idempotent: re-running rewrites the same line. Default is a dry run. Pass --apply to write. """ import glob import os import re import sqlite3 import subprocess import sys APPLY = "--apply" in sys.argv # Derive the repo root from git so the script targets the checkout it's run from. REPO = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip() DB = f"{REPO}/.pql/pql.db" D_IDS = set(r[0] for r in sqlite3.connect(DB).execute( "SELECT id FROM decisions WHERE type='confirmed'")) D_REF = re.compile(r"\bD-\d{3}\b") FILES = sorted(glob.glob(f"{REPO}/docs/workshops/*/workshop-outcomes.md")) def split_frontmatter(text): """Return (fm_lines, body) where fm_lines excludes the --- fences; fm is None if absent.""" if text.startswith("---\n"): end = text.find("\n---\n", 4) if end != -1: return text[4:end].splitlines(), text[end + 5:] return None, text def refs_in(body): return sorted({m for m in D_REF.findall(body) if m in D_IDS}, key=lambda d: int(d[2:])) def main(): print(f"=== add_workshop_provenance.py ({'APPLY' if APPLY else 'DRY-RUN'}) ===\n") for f in FILES: text = open(f, encoding="utf-8").read() fm, body = split_frontmatter(text) refs = refs_in(body if fm is not None else text) rel = os.path.relpath(f, REPO) wname = os.path.basename(os.path.dirname(f)) refs_yaml = "[" + ", ".join(refs) + "]" if fm is not None: fm = [ln for ln in fm if not ln.startswith("decision_refs:")] fm.append(f"decision_refs: {refs_yaml}") new = "---\n" + "\n".join(fm) + "\n---\n" + body action = f"inject decision_refs ({len(refs)})" else: new = (f"---\ntitle: \"Workshop Outcomes: {wname}\"\ntype: workshop\n" f"workshop: {wname}\nstatus: archived\ndecision_refs: {refs_yaml}\n---\n\n" + text) action = f"ADD frontmatter + decision_refs ({len(refs)})" print(f"{rel}: {action}") if refs: print(f" {refs_yaml}") if APPLY: open(f, "w", encoding="utf-8").write(new) print("\nDone." + ("" if APPLY else " (dry run — pass --apply)")) if __name__ == "__main__": main()