chore(db): add decisions-orphan-tickets CLI (#887)
New `tooling/db/decision orphan-tickets` subcommand scans tickets with a decision_ref that doesn't match any row in the decisions table. Surfaces silently orphaned tickets from typo'd or renumbered D-IDs. Makefile target: `make decisions-orphan-tickets`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
GODOT := $(shell command -v godot4 2>/dev/null || command -v godot 2>/dev/null)
|
||||
|
||||
.PHONY: help setup build check-protocol client server game stop test lint lint-python setup-venv ci ci-client ci-server clean \
|
||||
decisions-sync decisions-coverage decisions-active decisions-orphan \
|
||||
decisions-sync decisions-coverage decisions-active decisions-orphan decisions-orphan-tickets \
|
||||
db-backup db-install validate-content check-fact-ids setup-hooks install-hooks \
|
||||
audit deny atlas-verify economy-db atlas-generate regen-db check-systems-db \
|
||||
pre-pr pre-pr-lint pre-pr-build pre-pr-test pre-pr-validate pre-pr-fixtures \
|
||||
@@ -49,6 +49,7 @@ help:
|
||||
@echo " make decisions-coverage Each decision with its implementing ticket(s)"
|
||||
@echo " make decisions-active List active decisions"
|
||||
@echo " make decisions-orphan Decisions without implementing tickets"
|
||||
@echo " make decisions-orphan-tickets Tickets with invalid or missing decision_ref"
|
||||
@echo " make audit Run cargo audit (security advisory check)"
|
||||
@echo " make deny Run cargo deny check (license/ban policy)"
|
||||
@echo " make validate-content Validate content YAML against schemas"
|
||||
@@ -404,6 +405,9 @@ decisions-active:
|
||||
decisions-orphan:
|
||||
@tooling/db/sqlite-query "SELECT id, title FROM decisions WHERE type='confirmed' AND status='active' AND id NOT IN (SELECT DISTINCT decision_ref FROM tickets WHERE decision_ref IS NOT NULL)"
|
||||
|
||||
decisions-orphan-tickets:
|
||||
@tooling/db/decision orphan-tickets
|
||||
|
||||
# --- Content Validation ---
|
||||
|
||||
validate-content:
|
||||
|
||||
@@ -457,6 +457,35 @@ def show_decision(cfg, decision_id):
|
||||
conn.close()
|
||||
|
||||
|
||||
def orphan_tickets(cfg):
|
||||
"""List tickets whose decision_ref is set but does not match any decision in the DB."""
|
||||
conn = get_connection(cfg)
|
||||
try:
|
||||
rows = conn.execute(
|
||||
"""SELECT t.id, t.title, t.decision_ref, t.status, t.team
|
||||
FROM tickets t
|
||||
WHERE t.decision_ref IS NOT NULL
|
||||
AND t.decision_ref != ''
|
||||
AND t.decision_ref NOT IN (SELECT id FROM decisions)
|
||||
ORDER BY t.decision_ref, t.id""",
|
||||
).fetchall()
|
||||
|
||||
orphans = [dict(r) for r in rows]
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"count": len(orphans),
|
||||
"orphans": orphans,
|
||||
"summary": (
|
||||
f"{len(orphans)} orphan ticket(s) found"
|
||||
if orphans
|
||||
else "No orphan tickets — all decision_ref values are valid"
|
||||
),
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def check_dupes(cfg):
|
||||
"""Check for duplicate decision IDs across all markdown files."""
|
||||
# Pre-existing collisions too deeply embedded to renumber (139+ references).
|
||||
@@ -508,6 +537,7 @@ Usage:
|
||||
decisions_sync.py next [D|Q|R] Show next available ID (all prefixes or one)
|
||||
decisions_sync.py claim <D|Q|R> <domain> [title] Claim next ID and insert placeholder
|
||||
decisions_sync.py check-dupes Check for duplicate IDs across markdown files
|
||||
decisions_sync.py orphan-tickets List tickets with invalid/missing decision_ref
|
||||
decisions_sync.py --help Show this help message
|
||||
|
||||
ID claiming workflow:
|
||||
@@ -554,6 +584,8 @@ def main():
|
||||
result = claim_id(cfg, prefix, domain, title)
|
||||
elif cmd == "check-dupes":
|
||||
result = check_dupes(cfg)
|
||||
elif cmd == "orphan-tickets":
|
||||
result = orphan_tickets(cfg)
|
||||
else:
|
||||
result = {"ok": False, "error": f"Unknown command: {cmd}. Use --help for usage."}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user