fix(consistency): C-14 ghost-duplicate check + CLAUDE.md sync rule

Root cause analysis: calling create_workstream() before writing the workplan
file creates a ghost workstream with repo_id=null. When fix-consistency later
runs on the file, it creates a second workstream and writes its ID into the
file — leaving the ghost permanently active and showing false partial progress
in the dashboard.

C-14: after checking file-backed workstreams, query active workstreams on the
same topic with repo_id=null. Flag any whose title matches a file-backed
workstream as a probable ghost duplicate.

CLAUDE.md: add explicit "workplan ↔ DB sync rule" prohibiting create_workstream()
for file-backed work. Write file first, then make fix-consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 23:23:24 +01:00
parent d86b3cec14
commit fcf0515874

View File

@@ -18,6 +18,7 @@ Checks:
C-11 task-unlinked WARN Yes Task block has no state_hub_task_id
C-12 orphan-db-task WARN No DB task in workstream has no file backing
C-13 workstream-auto-complete WARN Yes All DB tasks done but workstream still active
C-14 ghost-duplicate WARN No Active topic workstream with no repo_id matches a file-backed title — probable ghost from premature create_workstream() call
Usage:
python scripts/consistency_check.py --repo SLUG [--fix] [--json] [--api-base URL]
@@ -581,6 +582,12 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
# C-07 / C-08: orphan DB workstreams (have repo_id=this_repo but no backing file)
_check_orphan_db(api_base, repo_id, set(file_ws_ids.keys()), report)
# C-14: ghost duplicate — active workstream on same topic with no repo_id whose
# title matches a file-backed workstream. Root cause: create_workstream() called
# before the workplan file was written; fix-consistency then created a second
# workstream from the file, leaving the first as an invisible orphan.
_check_ghost_duplicates(api_base, workplan_infos, file_ws_ids, report)
return report
@@ -619,6 +626,62 @@ def _check_orphan_db(
)
def _check_ghost_duplicates(
api_base: str,
workplan_infos: list[tuple],
file_ws_ids: dict[str, tuple],
report: ConsistencyReport,
) -> None:
"""C-14: detect active workstreams with repo_id=null whose title matches a
file-backed workstream — these are ghosts created by premature create_workstream()
calls before the workplan file existed.
"""
# Build lookup: normalised title → file-backed workstream id
file_titles: dict[str, str] = {}
for _, meta, _ in workplan_infos:
ws_id = str(meta.get("state_hub_workstream_id", "")).strip().strip('"')
title = str(meta.get("title", "")).strip().lower()
if title and ws_id:
file_titles[title] = ws_id
if not file_titles:
return
# Gather topic_ids from all file-backed workstreams so we can query by topic
topic_ids: set[str] = set()
for ws_id in file_ws_ids:
ws = _api_get(api_base, f"/workstreams/{ws_id}")
if ws and ws.get("topic_id"):
topic_ids.add(ws["topic_id"])
for topic_id in topic_ids:
topic_ws = _api_get(api_base, "/workstreams", {"topic_id": topic_id, "status": "active"})
if not isinstance(topic_ws, list):
continue
for ws in topic_ws:
ws_id = ws["id"]
if ws_id in file_ws_ids:
continue # legitimately linked
if ws.get("repo_id"):
continue # C-07 covers repo-scoped orphans
ws_title = ws.get("title", "").strip().lower()
if ws_title in file_titles:
file_backed_id = file_titles[ws_title]
report.add(
severity="WARN",
check_id="C-14",
message=(
f"Ghost duplicate: active workstream '{ws.get('slug')}' "
f"(id={ws_id[:8]}…, repo_id=null) has same title as "
f"file-backed workstream {file_backed_id[:8]}… — "
f"likely created via create_workstream() before workplan file existed; "
f"archive it"
),
db_id=ws_id,
fixable=False,
)
# ---------------------------------------------------------------------------
# Fix engine
# ---------------------------------------------------------------------------