refactor(ticket): migrate Ticket consumers to direct field access (Phase 1)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md,
conductor/tier2/githooks/forbidden-files.txt,
conductor/tracks/tier2_leak_prevention_20260620/spec.md,
conductor/code_styleguides/data_oriented_design.md,
conductor/code_styleguides/error_handling.md,
conductor/code_styleguides/type_aliases.md before Phase 1.
Phase 1 of metadata_promotion_20260624: migrate Ticket consumers from
t.get('key', default) / t['key'] to direct field access (t.id, t.status, etc.).
Changes:
- self.active_tickets: list[Metadata] -> list[models.Ticket]
- _deserialize_active_track_result populates self.active_tickets as Tickets
- _load_active_tickets (beads branch) constructs Ticket instances
- topological_sort signature: list[dict[str, Any]] -> list[Ticket]
- Migrated ~40 consumer sites in src/gui_2.py: _reorder_ticket,
bulk_execute/skip/block, _cb_block_ticket, _cb_unblock_ticket,
_dag_cycle_check_result, ticket queue rendering, DAG panel
- Migrated ~10 consumer sites in src/app_controller.py: _cb_ticket_retry,
_cb_ticket_skip, approve_ticket, mutate_dag, _push_mma_state_update_result,
completed count
- Removed legacy Ticket.get() compat method (Task 1.5)
- Added tests/test_metadata_promotion_phase1.py with 15 regression-guard tests
- Updated existing tests to construct Ticket instances instead of dicts
Verified: 1885 of 1910 unit tests pass (25 pre-existing failures unrelated
to Ticket migration; many are live_gui/sim tests that need a running GUI).
This commit is contained in:
+26
-24
@@ -1107,7 +1107,7 @@ class AppController:
|
||||
# --- Defaults set here so tests that construct AppController without
|
||||
# calling init_state() still see the attributes ---
|
||||
self.ui_global_preset_name: Optional[str] = None
|
||||
self.active_tickets: list[Metadata] = []
|
||||
self.active_tickets: list[models.Ticket] = []
|
||||
self.ui_selected_tickets: Set[str] = set()
|
||||
|
||||
#region: --- Configuration Maps ---
|
||||
@@ -2145,6 +2145,7 @@ class AppController:
|
||||
description=at_data.get("description"),
|
||||
tickets=tickets
|
||||
)
|
||||
self.active_tickets = tickets
|
||||
return Result(data=track)
|
||||
except (TypeError, ValueError, KeyError, AttributeError) as e:
|
||||
return Result(data=None, errors=[ErrorInfo(
|
||||
@@ -3052,7 +3053,7 @@ class AppController:
|
||||
elapsed_min = (time.time() - self._session_start_time) / 60.0 if self._token_history else 0
|
||||
burn_rate = total_tokens / elapsed_min if elapsed_min > 0 else 0
|
||||
session_cost = cost_tracker.estimate_cost("gemini-2.5-flash", total_input, total_output)
|
||||
completed = sum(1 for t in self.active_tickets if t.get("status") == "complete")
|
||||
completed = sum(1 for t in self.active_tickets if t.status == "complete")
|
||||
efficiency = total_tokens / completed if completed > 0 else 0
|
||||
return {
|
||||
"total_tokens": total_tokens,
|
||||
@@ -3273,7 +3274,8 @@ class AppController:
|
||||
result = self._deserialize_active_track_result(at_data)
|
||||
if result.ok:
|
||||
self.active_track = result.data
|
||||
self.active_tickets = at_data.get("tickets", []) # Keep dicts for UI table
|
||||
raw_tickets = at_data.get("tickets", [])
|
||||
self.active_tickets = [models.Ticket.from_dict(t) if isinstance(t, dict) else t for t in raw_tickets]
|
||||
else:
|
||||
err = result.errors[0]
|
||||
self._last_request_errors.append(("active_track_deserialize", err))
|
||||
@@ -4704,7 +4706,8 @@ class AppController:
|
||||
"""Phase 6 Group 6.7: topological sort with Result propagation.
|
||||
On ValueError: fall back to raw_tickets (preserves existing behavior)."""
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
normalized = [models.Ticket.from_dict(t) if isinstance(t, dict) else t for t in raw_tickets]
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(normalized)
|
||||
return Result(data=sorted_tickets_data)
|
||||
except ValueError as e:
|
||||
err = ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=str(e),
|
||||
@@ -4806,8 +4809,8 @@ class AppController:
|
||||
[C: tests/test_mma_ticket_actions.py:test_cb_ticket_retry]
|
||||
"""
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'todo'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'todo'
|
||||
break
|
||||
self.event_queue.put("mma_retry", {"ticket_id": ticket_id})
|
||||
|
||||
@@ -4816,8 +4819,8 @@ class AppController:
|
||||
[C: tests/test_mma_ticket_actions.py:test_cb_ticket_skip]
|
||||
"""
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'skipped'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'skipped'
|
||||
break
|
||||
self.event_queue.put("mma_skip", {"ticket_id": ticket_id})
|
||||
|
||||
@@ -4864,8 +4867,8 @@ class AppController:
|
||||
else:
|
||||
# Fallback if engine not running
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'in_progress'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'in_progress'
|
||||
break
|
||||
self._push_mma_state_update()
|
||||
|
||||
@@ -4875,8 +4878,8 @@ class AppController:
|
||||
depends_on = data.get("depends_on")
|
||||
if ticket_id and depends_on is not None:
|
||||
for t in self.active_tickets:
|
||||
if t.get("id") == ticket_id:
|
||||
t["depends_on"] = depends_on
|
||||
if t.id == ticket_id:
|
||||
t.depends_on = depends_on
|
||||
break
|
||||
if self.active_track:
|
||||
for t in self.active_track.tickets:
|
||||
@@ -5068,11 +5071,11 @@ class AppController:
|
||||
if track is None: return OK
|
||||
new_tickets = [
|
||||
models.Ticket(
|
||||
id=t.get("id", ""),
|
||||
description=t.get("description", ""),
|
||||
status=t.get("status", "todo"),
|
||||
assigned_to=t.get("assigned_to", ""),
|
||||
depends_on=t.get("depends_on", []),
|
||||
id=t.id,
|
||||
description=t.description,
|
||||
status=t.status,
|
||||
assigned_to=t.assigned_to,
|
||||
depends_on=list(t.depends_on),
|
||||
)
|
||||
for t in self.active_tickets
|
||||
]
|
||||
@@ -5104,13 +5107,12 @@ class AppController:
|
||||
beads_result = self._load_beads_from_path_result(Path(base))
|
||||
if beads_result.ok:
|
||||
for bead in beads_result.data:
|
||||
self.active_tickets.append({
|
||||
"id": bead.id,
|
||||
"title": bead.title,
|
||||
"description": bead.description,
|
||||
"status": bead.status,
|
||||
"depends_on": [],
|
||||
})
|
||||
self.active_tickets.append(models.Ticket(
|
||||
id=bead.id,
|
||||
description=bead.description or "",
|
||||
status=bead.status,
|
||||
depends_on=[],
|
||||
))
|
||||
elif not beads_result.ok:
|
||||
self._report_worker_error("load_beads", beads_result)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user