generated from coulomb/repo-seed
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:
@@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user