From a41f7aa06c06f13e33a59d24ccfbf0e88be4d0a1 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 21 Apr 2026 17:35:23 +0200 Subject: [PATCH] feat(tooling): add `decision show ` with implementing tickets + refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the decision-to-ticket coverage gap (#723). The `decisions-coverage` Makefile target already reported per-decision counts; this adds the single-decision drill-down via `tooling/db/decision show D-159`, returning linked tickets, outbound refs, and inbound refs in one shot. Schema unchanged — reverse link is a SELECT on tickets.decision_ref. Refs: #723 Co-Authored-By: Claude Opus 4.6 --- tooling/db/decisions_sync.py | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tooling/db/decisions_sync.py b/tooling/db/decisions_sync.py index 5eca7e6ec..8a0e23aa0 100644 --- a/tooling/db/decisions_sync.py +++ b/tooling/db/decisions_sync.py @@ -414,6 +414,49 @@ def claim_id(cfg, prefix, domain, title): conn.close() +def show_decision(cfg, decision_id): + """Show a single decision with full details including linked tickets and cross-refs.""" + conn = get_connection(cfg) + try: + row = conn.execute( + "SELECT * FROM decisions WHERE id = ?", + (decision_id,), + ).fetchone() + + if not row: + return {"ok": False, "error": f"Decision not found: {decision_id}"} + + decision = dict(row) + + # Implementing tickets: tickets where decision_ref = this ID + ticket_rows = conn.execute( + "SELECT id, title, status, type FROM tickets" + " WHERE decision_ref = ? ORDER BY id", + (decision_id,), + ).fetchall() + decision["implementing_tickets"] = [dict(t) for t in ticket_rows] + + # Cross-refs outbound: references from this decision to others + refs_out = conn.execute( + "SELECT target_id, ref_type, note FROM decision_refs" + " WHERE source_id = ? ORDER BY target_id", + (decision_id,), + ).fetchall() + decision["refs_out"] = [dict(r) for r in refs_out] + + # Cross-refs inbound: other decisions referencing this one + refs_in = conn.execute( + "SELECT source_id, ref_type, note FROM decision_refs" + " WHERE target_id = ? ORDER BY source_id", + (decision_id,), + ).fetchall() + decision["refs_in"] = [dict(r) for r in refs_in] + + return {"ok": True, "decision": decision} + 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). @@ -461,6 +504,7 @@ Settled Reach Decisions Sync & ID Management Usage: decisions_sync.py sync Parse decisions/*.md and upsert into SQLite + decisions_sync.py show Show a decision with linked tickets + refs decisions_sync.py next [D|Q|R] Show next available ID (all prefixes or one) decisions_sync.py claim [title] Claim next ID and insert placeholder decisions_sync.py check-dupes Check for duplicate IDs across markdown files @@ -493,6 +537,11 @@ def main(): if cmd == "sync": result = sync(cfg) + elif cmd == "show": + if len(sys.argv) < 3: + result = {"ok": False, "error": "Usage: show e.g. show D-159"} + else: + result = show_decision(cfg, sys.argv[2]) elif cmd == "next": result = next_id(cfg, sys.argv[2] if len(sys.argv) > 2 else None) elif cmd == "claim":