@@ -117,7 +117,10 @@ def _get_ticket_status(ticket_id: str, tracker_dir: str) -> str:
117117
118118
119119def _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