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.
This commit is contained in:
parent
d0bcba96c9
commit
38cd8cf828
2 changed files with 467 additions and 0 deletions
215
tools/scan_workstream_terminology.py
Executable file
215
tools/scan_workstream_terminology.py
Executable file
|
|
@ -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/<slug>`` (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())
|
||||
252
workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md
Normal file
252
workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md
Normal file
|
|
@ -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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue