Files
settled-reach/tooling/pql-migrate/repath_references.py
T
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

84 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Repoint decisions/ PATH references to the governance/ DQR tree (pql migration, Phase 1).
Operates on the active instruction layer (agent + skill markdown). Ordered, literal
string replacements — most-specific first, bare `decisions/` last so per-file paths
keep their decisions/ subdir. Does NOT touch command references (tooling/db/decision*,
decisions-sync) — those belong to the Phase 4 command cutover. Does NOT touch
historical archives (docs/sprints, docs/discussions, docs/workshops) or the separate
whatsinagame/ template distribution.
Default is a dry run. Pass --apply to write.
"""
import glob
import re
import sys
APPLY = "--apply" in sys.argv
# Ordered: specific globs/files first, per-domain D files next, bare dir LAST.
# The bare-dir rule is a regex with a negative lookbehind so it never re-matches
# the `decisions/` inside a `governance/decisions/...` path created by an earlier
# rule (which would corrupt it to `governance/governance/...`).
REPLACEMENTS = [
("decisions/questions-*.md", "governance/questions/*.md"),
("decisions/*.md", "governance/**/*.md"),
("decisions/README.md", "governance/README.md"),
("decisions/questions.md", "governance/README.md"),
("decisions/rejected.md", "governance/rejected/"),
("decisions/architecture.md", "governance/decisions/architecture.md"),
("decisions/content.md", "governance/decisions/content.md"),
("decisions/economics.md", "governance/decisions/economics.md"),
("decisions/perception.md", "governance/decisions/perception.md"),
("decisions/process.md", "governance/decisions/process.md"),
("decisions/scope.md", "governance/decisions/scope.md"),
(re.compile(r"(?<!governance/)decisions/"), "governance/"), # bare dir — must be last
]
# One-shot transform, already applied (decisions/ path refs -> governance/). The
# absolute paths below are the canonical main checkout where it was run; kept
# committed for provenance.
TARGETS = sorted(set(
glob.glob("/var/mnt/data/projects/settled-reach/main/.claude/agents/*.md")
+ glob.glob("/var/mnt/data/projects/settled-reach/main/.claude/skills/**/*.md", recursive=True)
))
def transform(text):
changes = []
for old, new in REPLACEMENTS:
if isinstance(old, re.Pattern):
n = len(old.findall(text))
if n:
text = old.sub(new, text)
changes.append((old.pattern, new, n))
elif old in text:
n = text.count(old)
text = text.replace(old, new)
changes.append((old, new, n))
return text, changes
def main():
print(f"=== repath_references.py ({'APPLY' if APPLY else 'DRY-RUN'}) ===\n")
total = 0
for path in TARGETS:
with open(path, encoding="utf-8") as fh:
text = fh.read()
new_text, changes = transform(text)
if not changes:
continue
rel = path.split("/main/", 1)[-1]
print(rel)
for old, new, n in changes:
print(f" {n}x {old!r} -> {new!r}")
total += n
if APPLY:
with open(path, "w", encoding="utf-8") as fh:
fh.write(new_text)
print(f"\n{total} replacement(s)." + ("" if APPLY else " (dry run — pass --apply)"))
if __name__ == "__main__":
main()