From 38cd8cf8282808e3a7c83cf65dd9c7058a82634c Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 8 Jul 2026 12:53:52 +0200 Subject: [PATCH] Add CUST-WP-0055 fleet workplan terminology refactor plan Inventory 22k+ legacy workstream occurrences across 73 registered repos and add a reproducible scan tool plus an eight-task workplan to migrate prose, events, templates, and code to workplan while preserving compatibility bridges. --- tools/scan_workstream_terminology.py | 215 +++++++++++++++ ...055-workplan-terminology-fleet-refactor.md | 252 ++++++++++++++++++ 2 files changed, 467 insertions(+) create mode 100755 tools/scan_workstream_terminology.py create mode 100644 workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md diff --git a/tools/scan_workstream_terminology.py b/tools/scan_workstream_terminology.py new file mode 100755 index 0000000..1f59fc0 --- /dev/null +++ b/tools/scan_workstream_terminology.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +"""Scan registered repos for legacy ``workstream`` terminology. + +Usage: + python tools/scan_workstream_terminology.py [--api-base URL] [--json] + python tools/scan_workstream_terminology.py --repo the-custodian + +Reads repo slugs from State Hub ``GET /repos/`` and scans checkouts under +``/home/worsch/`` (or host_paths when available). Excludes VCS/vendor +directories. Emits per-repo occurrence counts and pattern buckets for fleet +terminology refactor planning (CUST-WP-0055). +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +import urllib.request +from collections import defaultdict +from pathlib import Path + +DEFAULT_API_BASE = "http://127.0.0.1:8000" +HOME = Path("/home/worsch") +EXCLUDE_DIRS = { + ".git", + "node_modules", + "__pycache__", + ".venv", + "venv", + "dist", + "build", + ".next", + "target", +} +TEXT_EXTENSIONS = { + ".md", + ".py", + ".ts", + ".tsx", + ".js", + ".jsx", + ".yaml", + ".yml", + ".json", + ".sql", + ".sh", + ".toml", + ".rs", + ".go", + ".java", + ".html", + ".css", + ".scss", + ".vue", + ".rb", + ".php", + ".cs", + ".swift", + ".kt", + ".hcl", + ".tf", + ".mdx", + ".jinja", + ".j2", + ".template", + ".cfg", + ".ini", + ".txt", + ".graphql", + ".proto", +} +PATTERN_LABELS = [ + ("state_hub_workstream_id", "frontmatter:state_hub_workstream_id"), + ("workstream_id", "api:workstream_id"), + ("create_workstream", "mcp:create_workstream"), + ("update_workstream", "mcp:update_workstream"), + ("list_workstreams", "mcp:list_workstreams"), + ("open_workstreams", "api:open_workstreams"), + ("workstreams/", "path:workstreams/"), + ("workstream ", "prose:workstream "), + ("workstreams", "term:workstreams"), +] +NAMED_FILES = {"Makefile", "Dockerfile", "AGENTS.md", "CLAUDE.md", "SCOPE.md", "INTENT.md", "README"} + + +def bucket(path: str) -> str: + p = path.lower() + if p.startswith("workplans/"): + return "workplans" + if p in {"agents.md", "claude.md", "scope.md", "intent.md", "readme.md"}: + return "agent-guidance" + if "/tests/" in p or p.startswith("tests/"): + return "tests" + if "/migrations/" in p or p.startswith("migrations/"): + return "migrations" + if p.endswith(".py"): + return "python" + if p.endswith((".ts", ".tsx", ".js", ".jsx")): + return "frontend" + if p.endswith((".yaml", ".yml")): + return "yaml" + if p.endswith(".md"): + return "markdown-other" + return "other" + + +def fetch_repos(api_base: str) -> list[dict]: + with urllib.request.urlopen(f"{api_base.rstrip('/')}/repos/") as resp: + return json.load(resp) + + +def scan_repo(root: Path) -> dict: + patterns: dict[str, int] = defaultdict(int) + buckets: dict[str, int] = defaultdict(int) + total = 0 + files = 0 + top_files: list[tuple[int, str]] = [] + for path in root.rglob("*"): + if not path.is_file() or any(part in EXCLUDE_DIRS for part in path.parts): + continue + if path.suffix.lower() not in TEXT_EXTENSIONS and path.name not in NAMED_FILES: + continue + try: + text = path.read_text(encoding="utf-8", errors="ignore") + except OSError: + continue + matches = re.findall(r"workstream", text, flags=re.I) + if not matches: + continue + count = len(matches) + total += count + files += 1 + rel = str(path.relative_to(root)) + top_files.append((count, rel)) + buckets[bucket(rel)] += count + low = text.lower() + for needle, label in PATTERN_LABELS: + hits = low.count(needle.lower()) + if hits: + patterns[label] += hits + top_files.sort(reverse=True) + return { + "occurrences": total, + "files": files, + "patterns": dict(patterns), + "buckets": dict(buckets), + "top_files": top_files[:10], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--api-base", default=DEFAULT_API_BASE) + parser.add_argument("--repo", help="Scan a single repo slug") + parser.add_argument("--json", action="store_true", help="Emit JSON report") + args = parser.parse_args() + + repos = fetch_repos(args.api_base) + if args.repo: + repos = [r for r in repos if r["slug"] == args.repo] + if not repos: + print(f"Repo '{args.repo}' not registered", file=sys.stderr) + return 1 + + report: dict = {"repos": {}, "totals": {"occurrences": 0, "files": 0, "repos_with_hits": 0}} + pattern_totals: dict[str, int] = defaultdict(int) + + for repo in sorted(repos, key=lambda r: r["slug"]): + slug = repo["slug"] + root = HOME / slug + if not root.is_dir(): + report["repos"][slug] = {"missing_checkout": True} + continue + data = scan_repo(root) + if data["occurrences"]: + report["repos"][slug] = data + report["totals"]["occurrences"] += data["occurrences"] + report["totals"]["files"] += data["files"] + report["totals"]["repos_with_hits"] += 1 + for label, count in data["patterns"].items(): + pattern_totals[label] += count + + report["pattern_totals"] = dict(sorted(pattern_totals.items(), key=lambda x: -x[1])) + + if args.json: + print(json.dumps(report, indent=2)) + return 0 + + totals = report["totals"] + print(f"Repos scanned: {len(repos)}") + print(f"Repos with hits: {totals['repos_with_hits']}") + print(f"Total occurrences: {totals['occurrences']}") + print(f"Total files: {totals['files']}") + print("\nTop repos:") + ranked = sorted( + ((slug, data) for slug, data in report["repos"].items() if data.get("occurrences")), + key=lambda item: item[1]["occurrences"], + reverse=True, + ) + for slug, data in ranked[:25]: + print(f" {data['occurrences']:5d} in {data['files']:4d} files {slug}") + print("\nPattern totals:") + for label, count in report["pattern_totals"].items(): + print(f" {count:5d} {label}") + missing = [slug for slug, data in report["repos"].items() if data.get("missing_checkout")] + if missing: + print("\nMissing checkouts:") + for slug in missing: + print(f" {slug}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md b/workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md new file mode 100644 index 0000000..92d980b --- /dev/null +++ b/workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md @@ -0,0 +1,252 @@ +--- +id: CUST-WP-0055 +type: workplan +title: "Fleet-wide workplan terminology refactor (workstream → workplan)" +domain: infotech +repo: the-custodian +status: proposed +owner: codex +topic_slug: custodian +planning_priority: high +planning_order: 55 +created: "2026-07-08" +updated: "2026-07-08" +state_hub_workstream_id: "d96b72d5-24f2-492b-8bb4-50c39058848a" +--- + +# CUST-WP-0055 — Fleet-wide workplan terminology refactor + +## Goal + +Make **workplan** the consistent product and documentation term across all +Coulomb-registered repositories, while preserving compatibility bridges where +clients, events, or frontmatter still depend on legacy `workstream` identifiers. + +## Context + +State Hub already completed the spine rename (`STATE-WP-0065`) and the +compatibility-first terminology transition (`STATE-WP-0054`, +`docs/workplan-terminology-transition.md`). Preferred REST/MCP surfaces expose +`workplan`; legacy `workstream` paths remain metered via `legacy-meter`. + +A fleet scan on **2026-07-08** (see inventory below) shows the term is still +widespread outside State Hub internals: + +| Metric | Value | +| --- | --- | +| Registered repos scanned | 76 | +| Repos with `workstream` hits | 73 | +| Total occurrences | 22,237 | +| Files touched | 7,823 | +| Missing local checkouts | `markitect-project`, `vergabe_teilnahme` | + +**Top repos by hit count** + +| Repo | Occurrences | Files | Notes | +| --- | ---: | ---: | --- | +| `state-hub` | 13,418 | 4,229 | Legacy compat layer, tests, migrations, dashboard | +| `agentic-resources` | 5,307 | 2,226 | Bulk mirrored agent assets (`other` bucket) | +| `the-custodian` | 754 | 143 | Canon, workplans, governance docs | +| `repo-scoping` | 318 | 123 | Generated classification artefacts | +| `railiance-fabric` | 306 | 54 | Graph/read-model payloads | +| `activity-core` | 214 | 46 | Event contracts + State Hub resolver code | + +**Pattern totals (all repos)** + +| Pattern | Count | Refactor stance | +| --- | ---: | --- | +| `workstreams` (generic) | 13,108 | Prose/docs → `workplans`; code paths case-by-case | +| `workstream_id` | 3,830 | Keep API alias until legacy-meter retires | +| `workstream ` (prose) | 2,420 | **Replace** in user-facing text | +| `state_hub_workstream_id` | 1,065 | **Keep** frontmatter bridge until dedicated migration | +| `/workstreams/` routes | 490 | Keep compat routes; docs point to `/workplans/` | +| `create_workstream` MCP | 346 | Keep alias; guidance prefers `create_workplan` | +| `open_workstreams` | 145 | Internal summary cache — rename when clients move | +| `update_workstream` MCP | 128 | Keep alias | +| `list_workstreams` MCP | 7 | Keep alias | + +Re-run the inventory anytime: + +```bash +python tools/scan_workstream_terminology.py +python tools/scan_workstream_terminology.py --repo the-custodian --json +``` + +## Terminology policy (fleet) + +| Surface | Canonical term | Legacy bridge | Action in this plan | +| --- | --- | --- | --- | +| Human docs, SCOPE, AGENTS, workplan bodies | **workplan** | — | Replace prose | +| Workplan frontmatter link field | `state_hub_workstream_id` | holds workplan UUID | Document; rename field in later WP | +| REST/MCP params | `workplan_id` preferred | `workstream_id` alias | Guidance only; retire per legacy-meter | +| REST routes | `/workplans/` | `/workstreams/` | Docs + dashboard; retire per meter | +| NATS / activity events | `org.statehub.workplan.completed` | `org.workstream.completed` | Dual-publish then retire | +| Python/TS identifiers | `workplan_*` | `workstream_*` | Refactor when behaviour unchanged | +| DB tables / ORM models | `workplan` | — | Done in STATE-WP-0065 | + +**Do not** mass-rename `state_hub_workstream_id` in workplan files or DB UUID +columns in this plan — that is a separate bridge-field migration. + +## Task: Canon and agent-template alignment + +```task +id: CUST-WP-0055-T01 +status: todo +priority: high +state_hub_task_id: "9db434bd-55c5-4499-a365-8ac6a47726c8" +``` + +Publish a short canon addendum (or ADR supplement) in `the-custodian/canon/` +defining workplan as the fleet term and listing the legacy bridges above. +Update `state-hub/scripts/project_rules/*.template` so regenerated +`AGENTS.md` / session-protocol files are workplan-first (templates already +partially note the legacy mapping — close remaining gaps). + +Done when `update_agent_instruction_files` output uses workplan in prose and +only mentions `workstream` in an explicit compatibility footnote. + +## Task: State Hub and hub-core legacy surface retirement plan + +```task +id: CUST-WP-0055-T02 +status: todo +priority: high +state_hub_task_id: "2bb01721-a86b-43a0-ab4c-e5966743d295" +``` + +Child implementation workplan in `state-hub` (proposed `STATE-WP-0069`): +inventory remaining `workstream` strings in dashboard, tests, flows +(`flows/workstream.yaml`), and compat routers; tie each to a `legacy-meter` +key; set retirement order after weekly review shows zero callers. + +Deliverables: ranked retirement backlog, dashboard route rename plan, and grep +budget targets per release (e.g. reduce `state-hub` hit count by 50% per phase). + +## Task: activity-core event and resolver migration + +```task +id: CUST-WP-0055-T03 +status: todo +priority: high +state_hub_task_id: "72c2ecf3-c0c1-4241-b0f2-339a97ccf949" +``` + +Migrate `org.workstream.completed` → `org.workplan.completed` with a +dual-publish window documented in `activity-core/event-types/`. Update +`activity_core/context_resolvers/state_hub.py` log messages, k8s manifests, and +workplan prose. Register legacy subject in State Hub legacy-meter. + +Done when new automations subscribe to the workplan subject and the workstream +subject is marked legacy with a published sunset date. + +## Task: Domain repo prose sweep (template-driven) + +```task +id: CUST-WP-0055-T04 +status: todo +priority: medium +state_hub_task_id: "2ff6cef9-7ec2-4d44-bce0-b232b1f889dc" +``` + +Mechanical pass on the ~60 domain repos with the standard bootstrap shape +(typically 25–80 hits each): `AGENTS.md`, `SCOPE.md`, `INTENT.md`, `README.md`, +and active root workplans. Replace user-facing `workstream` with `workplan`; +leave `state_hub_workstream_id` and API examples that demonstrate legacy aliases. + +Use `scan_workstream_terminology.py --json` before/after per repo; target zero +`prose:workstream ` hits in agent-guidance buckets. + +## Task: Code and integration sweep (activity-core, issue-core, railiance-*) + +```task +id: CUST-WP-0055-T05 +status: todo +priority: medium +state_hub_task_id: "120a8075-3d1f-426d-8800-aa9edac32043" +``` + +Repos with non-trivial Python/TS code references: `activity-core`, `issue-core`, +`railiance-platform`, `railiance-infra`, `hub-core`, `core-hub`, `ops-warden`, +`reuse-surface`, `inter-hub`. Rename variables, comments, and client payloads +to `workplan` where they denote the domain concept; keep wire-compat keys until +T02 retires the API alias. + +## Task: Generated and bulk-content repos + +```task +id: CUST-WP-0055-T06 +status: todo +priority: medium +state_hub_task_id: "726c12bf-e6e0-4293-b675-e2c0fd10800e" +``` + +Address high-volume generated trees: + +- `agentic-resources` (~5.3k hits, mostly `other` bucket) — fix upstream + generator templates, not files by hand. +- `repo-scoping` / `railiance-fabric` — fix generators or export schemas so + new artefacts are workplan-first. + +Done when regenerating those repos drops terminology hits by ≥90% without +manual per-file edits. + +## Task: Historical workplan and archive hygiene + +```task +id: CUST-WP-0055-T07 +status: todo +priority: low +state_hub_task_id: "362790c8-cf27-4042-81e4-533a6b48fb26" +``` + +Update **active** workplan prose only; for `workplans/archived/`, add a +single header note that historical text may say workstream. Optionally normalize +titles in archived files when the edit is mechanical (no ID renames). + +Grandfathered filenames containing `workstream` (e.g. +`CUST-WP-0010-workstream-lifecycle-docs.md`) keep their paths per ADR-001 +non-rename policy. + +## Task: Verification gate and legacy-meter criteria + +```task +id: CUST-WP-0055-T08 +status: todo +priority: high +state_hub_task_id: "5b6596ef-fd04-4469-b421-f548d292cb0d" +``` + +Define fleet exit criteria: + +1. `scan_workstream_terminology.py` — zero `prose:workstream ` across domain + repos; `state-hub` under agreed grep budget. +2. State Hub `legacy-meter` weekly review — no new prose-only legacy keys. +3. `fix-consistency` / interface-change registry — no new `workstream`-named + public tools without a workplan alias. +4. Add a CI or activity-core scheduled check that fails when prose hits regress. + +Done when all four checks are automated and the 2026-07-08 baseline is stored +in the scan JSON artefact committed beside this workplan's T01 completion. + +## Sequencing + +``` +T01 canon/templates + ├─ T02 state-hub retirement plan (STATE-WP-0069) + ├─ T03 activity-core events + ├─ T04 domain prose sweep (parallel batches) + ├─ T05 code sweep + └─ T06 generated repos +T07 archives (low priority, anytime) +T08 verification gate (continuous; closes plan) +``` + +## Relationship to existing workplans + +- **Done:** `STATE-WP-0054`, `STATE-WP-0065`, `STATE-WP-0046`, `CUST-WP-0053` + (C-26 prefix lint), `CUST-WP-0050` (classification spine coordination). +- **This plan** owns cross-repo coordination; implementation splits into + `state-hub`, `activity-core`, and per-domain PRs tracked as child tasks or + linked workplans. +- **Out of scope:** renaming `state_hub_workstream_id` frontmatter field; + database table re-migration (already completed). \ No newline at end of file