feat(terminology): prose sweep tool and custodian workplan cleanup (CUST-WP-0055 T04)
Add sweep_workstream_prose.py for agent-guidance files, sweep active workplan prose in-repo, tighten scan allowlist exclusions, and update ADR-001 closure protocol to workplan-first terminology.
This commit is contained in:
parent
3bdefb3c4a
commit
2e0deee2ef
42 changed files with 312 additions and 186 deletions
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cancel duplicate CUST-WP-0054 hub tasks created during 2026-07-07 workstream recreation.
|
||||
# Cancel duplicate CUST-WP-0054 hub tasks created during 2026-07-07 workplan recreation.
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE="${API_BASE:-http://127.0.0.1:8000}"
|
||||
|
|
@ -21,7 +21,7 @@ for id in "${DUPLICATES[@]}"; do
|
|||
http=$(curl -sS -o /tmp/task-patch.json -w '%{http_code}' \
|
||||
-X PATCH "${API_BASE}/tasks/${id}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status":"cancel","intervention_note":"Duplicate from 2026-07-07 workstream recreation; canonical task retained in workplan."}')
|
||||
-d '{"status":"cancel","intervention_note":"Duplicate from 2026-07-07 workplan recreation; canonical task retained in workplan."}')
|
||||
if [[ "$http" == "200" ]]; then
|
||||
echo "CANCEL ${id}"
|
||||
elif [[ "$http" == "404" ]]; then
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ global:
|
|||
path_prefixes:
|
||||
- workplans/archived/
|
||||
- memory/
|
||||
- agents_backup_
|
||||
- agent-tools/
|
||||
|
||||
per_repo:
|
||||
the-custodian:
|
||||
|
|
@ -16,6 +18,7 @@ per_repo:
|
|||
- tools/scan_workstream_allowlist.yaml
|
||||
- docs/evidence/workstream-terminology-baseline-20260708.json
|
||||
- workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md
|
||||
- .custodian-brief.md
|
||||
- workplans/CUST-WP-0010-workstream-lifecycle-docs.md
|
||||
- wiki/
|
||||
- roadmap/
|
||||
|
|
|
|||
123
tools/sweep_workstream_prose.py
Normal file
123
tools/sweep_workstream_prose.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Mechanical workstream→workplan prose sweep for agent guidance (CUST-WP-0055 T04).
|
||||
|
||||
Usage:
|
||||
python tools/sweep_workstream_prose.py --repo ops-warden
|
||||
python tools/sweep_workstream_prose.py --repo ops-warden --repo hub-core --dry-run
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
HOME = Path("/home/worsch")
|
||||
TARGET_NAMES = {"AGENTS.md", "SCOPE.md", "INTENT.md", "README.md", "CLAUDE.md"}
|
||||
TARGET_RULES = ".claude/rules"
|
||||
|
||||
PRESERVE = (
|
||||
"state_hub_workstream_id",
|
||||
"workstream_id",
|
||||
"workstream_slug",
|
||||
"workstream_title",
|
||||
"workstream_status",
|
||||
"open_workstreams",
|
||||
"create_workstream",
|
||||
"update_workstream",
|
||||
"list_workstreams",
|
||||
"update_workstream_status",
|
||||
"/workstreams",
|
||||
"workstreams/",
|
||||
"workstream-dod",
|
||||
"workstream-health",
|
||||
"workstream-lifecycle",
|
||||
"workstream-kpi",
|
||||
"related_workstream",
|
||||
"by_workstream",
|
||||
)
|
||||
|
||||
REPLACEMENTS = (
|
||||
(re.compile(r"\bWorkstreams\b"), "Workplans"),
|
||||
(re.compile(r"\bworkstreams\b"), "workplans"),
|
||||
(re.compile(r"\bWorkstream\b"), "Workplan"),
|
||||
(re.compile(r"\bworkstream\b"), "workplan"),
|
||||
)
|
||||
|
||||
|
||||
def _protect(text: str) -> tuple[str, dict[str, str]]:
|
||||
tokens: dict[str, str] = {}
|
||||
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
key = f"__KEEP_{len(tokens)}__"
|
||||
tokens[key] = match.group(0)
|
||||
return key
|
||||
|
||||
for term in sorted(PRESERVE, key=len, reverse=True):
|
||||
text = re.sub(re.escape(term), repl, text)
|
||||
return text, tokens
|
||||
|
||||
|
||||
def _restore(text: str, tokens: dict[str, str]) -> str:
|
||||
for key, value in tokens.items():
|
||||
text = text.replace(key, value)
|
||||
return text
|
||||
|
||||
|
||||
def sweep_text(text: str) -> str:
|
||||
protected, tokens = _protect(text)
|
||||
updated = protected
|
||||
for pattern, replacement in REPLACEMENTS:
|
||||
updated = pattern.sub(replacement, updated)
|
||||
return _restore(updated, tokens)
|
||||
|
||||
|
||||
def target_files(repo_root: Path) -> list[Path]:
|
||||
paths: list[Path] = []
|
||||
for name in TARGET_NAMES:
|
||||
path = repo_root / name
|
||||
if path.is_file():
|
||||
paths.append(path)
|
||||
rules_dir = repo_root / TARGET_RULES
|
||||
if rules_dir.is_dir():
|
||||
paths.extend(sorted(rules_dir.glob("*.md")))
|
||||
return paths
|
||||
|
||||
|
||||
def sweep_repo(repo_root: Path, *, dry_run: bool) -> list[str]:
|
||||
changed: list[str] = []
|
||||
for path in target_files(repo_root):
|
||||
original = path.read_text(encoding="utf-8")
|
||||
updated = sweep_text(original)
|
||||
if updated != original:
|
||||
changed.append(str(path.relative_to(repo_root)))
|
||||
if not dry_run:
|
||||
path.write_text(updated, encoding="utf-8")
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo", action="append", required=True, help="Repo slug under ~/")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
exit_code = 0
|
||||
for slug in args.repo:
|
||||
root = HOME / slug
|
||||
if not root.is_dir():
|
||||
print(f"SKIP missing checkout: {slug}")
|
||||
exit_code = 1
|
||||
continue
|
||||
changed = sweep_repo(root, dry_run=args.dry_run)
|
||||
if changed:
|
||||
mode = "would update" if args.dry_run else "updated"
|
||||
print(f"{slug}: {mode} {len(changed)} file(s)")
|
||||
for rel in changed:
|
||||
print(f" {rel}")
|
||||
else:
|
||||
print(f"{slug}: no changes")
|
||||
return exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue