C-33: generated per-repo work-record index (CUST-WP-0061-T04)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 4s

First cut of the transclusion-style index named in the stage-3 seed: a
WORK-RECORDS.md at repo root listing kind/id/status/lane/source for
every workplan, task, intake, decision, and engagement found in the
repo -- derived purely from files (no hub query), matching the ADR-001
rebuild principle. Full markitect transclusion rendering is a
follow-on, not this first cut.

- _generate_work_record_index(): reuses iter_workplan_files/
  parse_frontmatter/get_tasks_from_workplan for workplan+task rows,
  and the repo-wide yaml-block scan already proven by C-31/C-32 for
  intake/decision/engagement rows. Archived workplans excluded (index
  is for current orientation, not history); closed decisions/intakes/
  engagements stay listed. Returns None (no file written) for repos
  with zero work records, to avoid clutter.
- _check_work_record_index_freshness(): C-33, WARN+fixable when
  WORK-RECORDS.md is missing or its content differs from a fresh
  regeneration.
- fix_repo C-33 dispatch: overwrites the file with the regenerated
  content.

13 new tests (generation across all kinds, sort order, archived
exclusion, self-scan exclusion, freshness detection incl. idempotence).
No regressions: full repo suite green (561 tests).

Live-verified at two scales: binky-control (5 workplans, 31 tasks, 3
intake, 4 decisions, 5 engagements) and the-custodian (52 workplans,
332 tasks) -- both generated correctly on first run, confirmed
idempotent (identical second run produces no C-33 fix), and the larger
repo's scan added no material overhead to the existing ~1.5min
fix-consistency run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-21 01:25:26 +02:00
parent 3dbbc753bc
commit b564ac7046
2 changed files with 307 additions and 0 deletions

View file

@ -35,6 +35,7 @@ Checks:
C-30 scope-current-state-stale WARN No SCOPE.md Current State contradicts live workplan statuses
C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060)
C-32 work-record-not-indexed WARN Yes kind: intake/decision YAML block has no hub id not indexed in DB (registration, CUST-WP-0061-T02)
C-33 work-record-index-stale WARN Yes WORK-RECORDS.md missing or stale generated per-repo index (CUST-WP-0061-T04)
Usage:
python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL]
@ -973,6 +974,116 @@ def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport")
)
_WORK_RECORD_INDEX_NAME = "WORK-RECORDS.md"
_WORK_RECORD_INDEX_KIND_ORDER = {
"workplan": 0, "task": 1, "intake": 2, "decision": 3, "engagement": 4,
}
def _generate_work_record_index(repo_dir: Path, repo_slug: str) -> str | None:
"""CUST-WP-0061-T04: the generated per-repo work-record index — every
registered work record (workplan, task, intake, decision, engagement)
with kind/id/status/lane/source, derivable purely from repo files (no
hub query matches the ADR-001 rebuild principle). Full transclusion
rendering (markitect integration) is a follow-on; this is the flat
first cut. Returns None (no file written) when the repo has no work
records at all, to avoid clutter in small repos.
"""
rows: list[tuple[str, str, str, str, str]] = [] # kind, id, status, lane, source
workplans_dir = repo_dir / "workplans"
if workplans_dir.is_dir():
for wp_file in iter_workplan_files(workplans_dir, include_archived=False):
try:
text = wp_file.read_text(encoding="utf-8", errors="replace")
except OSError:
continue
meta, body = parse_frontmatter(text)
wp_id = str(meta.get("id", "")).strip()
if not wp_id:
continue
source = str(wp_file.relative_to(repo_dir))
rows.append(("workplan", wp_id, str(meta.get("status", "")).strip() or "", "", source))
for task in get_tasks_from_workplan(meta, body):
if task.get("_parse_error"):
continue
t_id = str(task.get("id", "")).strip()
if not t_id:
continue
rows.append(("task", t_id, str(task.get("status", "")).strip() or "", "", source))
kind_registry = _load_work_record_kind_registry()
if kind_registry:
for md in sorted(repo_dir.rglob("*.md")):
if any(part in _WORK_RECORD_SKIP_DIRS for part in md.parts):
continue
if md.name == _WORK_RECORD_INDEX_NAME:
continue
try:
text = md.read_text(errors="replace")
except OSError:
continue
for block in _YAML_ONLY_FENCE_RE.findall(text):
meta = _parse_yaml_block(block.strip())
if not isinstance(meta, dict) or meta.get("_parse_error"):
continue
rid = str(meta.get("id", "")).strip()
if not rid or "NNN" in rid:
continue
kind = _classify_work_record_kind(rid, kind_registry)
if kind not in ("intake", "decision", "engagement"):
continue
status = str(meta.get("status", "")).strip() or ""
lane = str(meta.get("lane", "")).strip() or ""
source = str(md.relative_to(repo_dir))
rows.append((kind, rid, status, lane, source))
if not rows:
return None
rows.sort(key=lambda r: (_WORK_RECORD_INDEX_KIND_ORDER.get(r[0], 99), r[4], r[1]))
lines = [
f"# Work Records — {repo_slug}",
"",
"> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record",
"> stage 3). Do not edit by hand — edit the source file/block listed for",
"> each record and re-run fix-consistency to refresh this index. Archived",
"> workplans are omitted; closed decisions/intakes/engagements stay listed",
"> so recently-resolved work is still visible. [auto]",
"",
"| Kind | ID | Status | Lane | Source |",
"| --- | --- | --- | --- | --- |",
]
lines.extend(f"| {kind} | {rid} | {status} | {lane} | {source} |" for kind, rid, status, lane, source in rows)
lines.append("")
return "\n".join(lines)
def _check_work_record_index_freshness(
repo_dir: Path, repo_slug: str, report: "ConsistencyReport"
) -> None:
"""C-33: WORK-RECORDS.md missing or stale relative to the current
file-derived work-record set."""
content = _generate_work_record_index(repo_dir, repo_slug)
if content is None:
return
index_file = repo_dir / _WORK_RECORD_INDEX_NAME
existing = index_file.read_text(encoding="utf-8") if index_file.is_file() else None
if existing == content:
return
report.add(
severity="WARN", check_id="C-33",
message=(
f"{_WORK_RECORD_INDEX_NAME} is missing or stale — regenerate via "
f"fix-consistency (CUST-WP-0061-T04 generated work-record index)"
),
file_path=_WORK_RECORD_INDEX_NAME,
fixable=True,
_fix_context={"index_file": index_file, "content": content},
)
def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport:
"""Run all consistency checks for a registered repo."""
repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True)
@ -1042,6 +1153,9 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
# C-32: intake/decision work-record registration (CUST-WP-0061-T02)
_check_work_record_registration(repo_dir, report)
# C-33: generated per-repo work-record index (CUST-WP-0061-T04)
_check_work_record_index_freshness(repo_dir, repo_slug, report)
# C-01: workplans/ directory missing
if not workplans_dir.is_dir():
report.add(
@ -2786,6 +2900,12 @@ def fix_repo(
f"failed to write {id_field} back to {md_path.name}"
)
elif issue.check_id == "C-33":
ctx["index_file"].write_text(ctx["content"], encoding="utf-8")
report.fixes_applied.append(
f"C-33 fixed: regenerated {ctx['index_file'].name}"
)
elif issue.check_id == "C-09":
ws_id = ctx["ws_id"]
correct_repo_id = ctx["correct_repo_id"]