Files
jpmschweitzerandClaude Opus 4.8 f8d9b777b0 fix(config): address PR #154 review — gitattributes glob + script roots
Tyre (architecture review):
- .gitattributes: `.pql/changelog/*.sql` matched nothing (files are one level
  deeper at .pql/changelog/<table>/<YYYY-MM>.sql), so the union-merge driver never
  applied — `git check-attr merge` returned `unspecified`. Fixed to
  `.pql/changelog/**/*.sql`; now resolves to `merge: union` for monthly + schema
  files. Restores the changelog's conflict-free merge guarantee.
- Migration scripts: the re-runnable ones (seed_tickets.py, add_workshop_provenance.py)
  now derive the repo root from `git rev-parse --show-toplevel` instead of a hardcoded
  /main path, so re-running from a worktree/clone targets the right checkout. The three
  one-shot transforms (restructure_decisions, repath_references, retag_ticket_refs)
  get a comment noting they're already-applied and unsafe to re-run (git mv on moved
  sources) — keeping the path honest rather than implying re-runnability.

Hoshe approved (all QA checks passed). ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:05:31 +02:00

77 lines
2.8 KiB
Python

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