Skip to content

Commit 66a8d14

Browse files
feat(8a5b-e07b): GREEN — consolidate ticket-graph.py to single reduce_all_tickets call
Story bd09-9857: _find_direct_blockers and _compute_dep_graph now use a single pre-loaded ticket_states dict from reduce_all_tickets instead of per-ticket _reduce_ticket calls. Net reduction of 18 lines. _get_all_blocked_by and compute_archive_eligible unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fe5065a commit 66a8d14

File tree

2 files changed

+60
-78
lines changed

2 files changed

+60
-78
lines changed

.test-index

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ plugins/dso/scripts/issue-quality-check.sh:tests/scripts/test_issue_quality_chec
6060
plugins/dso/scripts/pre-commit-format-fix.sh:tests/scripts/test-precommit-format-fix-config-paths.sh
6161
plugins/dso/scripts/project-detect.sh:tests/scripts/test-ci-generator-integration.sh,tests/scripts/test-project-detect.sh
6262
plugins/dso/scripts/ticket-bridge-status.sh:tests/scripts/test_bridge_status.py
63-
plugins/dso/scripts/ticket-graph.py:tests/scripts/test_ticket_graph.py [test_build_dep_graph_single_batch_scan]
63+
plugins/dso/scripts/ticket-graph.py:tests/scripts/test_ticket_graph.py
6464
plugins/dso/scripts/ticket-lib.sh:tests/scripts/test_revert_event.py,tests/scripts/test-ticket-lib.sh,tests/scripts/test-ticket-health-guards.sh
6565
plugins/dso/scripts/ticket-list.sh:tests/scripts/test-ticket-list.sh,tests/scripts/test_bridge_alert_display.py
6666
plugins/dso/scripts/ticket-revert.sh:tests/scripts/test_revert_event.py

plugins/dso/scripts/ticket-graph.py

Lines changed: 59 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ def _get_ticket_status(ticket_id: str, tracker_dir: str) -> str:
117117

118118

119119
def _find_direct_blockers(
120-
ticket_id: str, tracker_dir: str, exclude_archived: bool = True
120+
ticket_id: str,
121+
tracker_dir: str,
122+
exclude_archived: bool = True,
123+
ticket_states: dict[str, Any] | None = None,
121124
) -> list[str]:
122125
"""Return a list of ticket IDs that directly block ticket_id.
123126
@@ -130,69 +133,56 @@ def _find_direct_blockers(
130133
Args:
131134
exclude_archived: When True (default), skip blockers whose compiled state
132135
has state.get('archived') == True.
136+
ticket_states: Optional pre-loaded dict keyed by ticket_id with compiled
137+
state dicts. When provided, avoids per-ticket _reduce_ticket calls.
138+
When None, loads all ticket states via reduce_all_tickets.
133139
"""
140+
# Build ticket_states from reduce_all_tickets if not provided
141+
if ticket_states is None:
142+
all_states = _reducer.reduce_all_tickets(tracker_dir, exclude_archived=False)
143+
ticket_states = {}
144+
for t in all_states:
145+
tid = t.get("ticket_id", "")
146+
if tid and t.get("status") not in ("error", "fsck_needed"):
147+
ticket_states[tid] = t
148+
134149
blockers: list[str] = []
135150

136151
# Source 1: ticket_id's own compiled deps for 'depends_on'
137-
ticket_dir = os.path.join(tracker_dir, ticket_id)
138-
if os.path.isdir(ticket_dir):
139-
try:
140-
state = _reduce_ticket(ticket_dir)
141-
except Exception:
142-
state = None
143-
144-
if state is not None and isinstance(state, dict):
145-
for dep in state.get("deps", []):
146-
if dep.get("relation") in _BLOCKING_RELATIONS:
147-
# For depends_on: target is what blocks this ticket
148-
# We only want depends_on here (blocks stored in blocker's dir)
149-
if dep.get("relation") == "depends_on":
150-
target = dep.get("target_id", "")
151-
if target and target not in blockers:
152-
# Check if target is archived when filtering
153-
if exclude_archived:
154-
target_dir = os.path.join(tracker_dir, target)
155-
if os.path.isdir(target_dir):
156-
try:
157-
target_state = _reduce_ticket(target_dir)
158-
except Exception:
159-
target_state = None
160-
if (
161-
target_state is not None
162-
and isinstance(target_state, dict)
163-
and target_state.get("archived") is True
164-
):
165-
continue
166-
blockers.append(target)
167-
168-
# Source 2: scan all ticket dirs for LINK events with relation=='blocks'
152+
state = ticket_states.get(ticket_id)
153+
if state is not None and isinstance(state, dict):
154+
for dep in state.get("deps", []):
155+
if dep.get("relation") in _BLOCKING_RELATIONS:
156+
# For depends_on: target is what blocks this ticket
157+
# We only want depends_on here (blocks stored in blocker's dir)
158+
if dep.get("relation") == "depends_on":
159+
target = dep.get("target_id", "")
160+
if target and target not in blockers:
161+
# Check if target is archived when filtering
162+
if exclude_archived:
163+
target_state = ticket_states.get(target)
164+
if (
165+
target_state is not None
166+
and isinstance(target_state, dict)
167+
and target_state.get("archived") is True
168+
):
169+
continue
170+
blockers.append(target)
171+
172+
# Source 2: scan all ticket states for deps with relation=='blocks'
169173
# targeting ticket_id
170-
try:
171-
entries = os.listdir(tracker_dir)
172-
except OSError:
173-
entries = []
174-
175-
for entry in entries:
174+
for entry, entry_state in ticket_states.items():
176175
if entry == ticket_id:
177176
continue
178-
# Skip non-directory entries (like .graph-cache.json, .env-id)
179-
entry_path = os.path.join(tracker_dir, entry)
180-
if not os.path.isdir(entry_path):
181-
continue
182177

183-
try:
184-
state = _reduce_ticket(entry_path)
185-
except Exception:
186-
state = None
187-
188-
if state is None or not isinstance(state, dict):
178+
if entry_state is None or not isinstance(entry_state, dict):
189179
continue
190180

191181
# Skip archived entries when exclude_archived is True
192-
if exclude_archived and state.get("archived") is True:
182+
if exclude_archived and entry_state.get("archived") is True:
193183
continue
194184

195-
for dep in state.get("deps", []):
185+
for dep in entry_state.get("deps", []):
196186
if dep.get("relation") == "blocks" and dep.get("target_id") == ticket_id:
197187
if entry not in blockers:
198188
blockers.append(entry)
@@ -328,41 +318,33 @@ def _compute_dep_graph(
328318
exclude_archived: When True (default), archived tickets are excluded from
329319
children and blockers lists.
330320
"""
331-
# Get the ticket's compiled deps list
332-
ticket_dir = os.path.join(tracker_dir, ticket_id)
333-
deps: list[dict[str, Any]] = []
334-
335-
if os.path.isdir(ticket_dir):
336-
try:
337-
state = _reduce_ticket(ticket_dir)
338-
except Exception:
339-
state = None
321+
# Pre-load all ticket states once to avoid per-ticket _reduce_ticket calls
322+
all_states_list = _reducer.reduce_all_tickets(tracker_dir, exclude_archived=False)
323+
ticket_states: dict[str, Any] = {}
324+
for t in all_states_list:
325+
tid = t.get("ticket_id", "")
326+
if tid and t.get("status") not in ("error", "fsck_needed"):
327+
ticket_states[tid] = t
340328

341-
if state is not None and isinstance(state, dict):
342-
deps = list(state.get("deps", []))
329+
# Get the ticket's compiled deps list from pre-loaded state
330+
deps: list[dict[str, Any]] = []
331+
state = ticket_states.get(ticket_id)
332+
if state is not None and isinstance(state, dict):
333+
deps = list(state.get("deps", []))
343334

344-
# Find direct blockers
335+
# Find direct blockers using pre-loaded ticket_states
345336
direct_blockers = _find_direct_blockers(
346-
ticket_id, tracker_dir, exclude_archived=exclude_archived
337+
ticket_id,
338+
tracker_dir,
339+
exclude_archived=exclude_archived,
340+
ticket_states=ticket_states,
347341
)
348342

349343
# Find children: tickets whose parent_id matches this ticket (8cbf-e13b)
350344
children: list[str] = []
351-
try:
352-
entries = os.listdir(tracker_dir)
353-
except OSError:
354-
entries = []
355-
356-
for entry in entries:
345+
for entry, child_state in ticket_states.items():
357346
if entry == ticket_id:
358347
continue
359-
entry_path = os.path.join(tracker_dir, entry)
360-
if not os.path.isdir(entry_path):
361-
continue
362-
try:
363-
child_state = _reduce_ticket(entry_path)
364-
except Exception:
365-
continue
366348
if child_state is not None and isinstance(child_state, dict):
367349
if child_state.get("parent_id") == ticket_id:
368350
# Skip archived children when exclude_archived is True

0 commit comments

Comments
 (0)