diff --git a/src/dag_engine.py b/src/dag_engine.py index 0eabac1e..ddba8ad4 100644 --- a/src/dag_engine.py +++ b/src/dag_engine.py @@ -89,7 +89,7 @@ class TrackDAG: Returns a list of tickets that are in 'todo' status and whose dependencies are all 'completed'. Returns: A list of Ticket objects ready for execution. - [C: src/models.py:Track.get_executable_tickets, tests/test_dag_engine.py:test_get_ready_tasks_branching, tests/test_dag_engine.py:test_get_ready_tasks_linear, tests/test_dag_engine.py:test_get_ready_tasks_multiple_deps, tests/test_orchestration_logic.py:test_track_executable_tickets] + [C: src/dag_engine.py:get_executable_tickets, tests/test_dag_engine.py:test_get_ready_tasks_branching, tests/test_dag_engine.py:test_get_ready_tasks_linear, tests/test_dag_engine.py:test_get_ready_tasks_multiple_deps, tests/test_orchestration_logic.py:test_track_executable_tickets] """ ready = [] for ticket in self.tickets: @@ -162,6 +162,17 @@ class TrackDAG: raise ValueError("Dependency cycle detected") return result +def get_executable_tickets(track: "Track") -> List[Ticket]: + """ + Convenience: returns the ready-to-execute tickets of a Track. + Free function (instead of Track.get_executable_tickets) so that + src/models.py does not need to import TrackDAG at module level, + breaking the models<->dag_engine circular dependency. + [C: tests/test_mma_models.py:test_track_get_executable_tickets, tests/test_mma_models.py:test_track_get_executable_tickets_complex] + """ + return TrackDAG(track.tickets).get_ready_tasks() + + class ExecutionEngine: """ A state machine that governs the progression of tasks within a TrackDAG. diff --git a/src/models.py b/src/models.py index c113d907..17879415 100644 --- a/src/models.py +++ b/src/models.py @@ -341,16 +341,8 @@ class Track: description: str tickets: List[Ticket] = field(default_factory=list) - def get_executable_tickets(self) -> List[Ticket]: - """ - [C: tests/test_mma_models.py:test_track_get_executable_tickets, tests/test_mma_models.py:test_track_get_executable_tickets_complex] - """ - dag = TrackDAG(self.tickets) - return dag.get_ready_tasks() - def to_dict(self) -> Dict[str, Any]: """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] """ return { "id": self.id, @@ -361,7 +353,6 @@ class Track: @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Track": """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] """ return cls( id = data["id"], @@ -938,8 +929,6 @@ class MCPServerConfig: #endregion: MCP Config -from src.dag_engine import TrackDAG - @dataclass class MCPConfiguration: mcpServers: Dict[str, MCPServerConfig] = field(default_factory=dict) diff --git a/tests/test_conductor_engine_v2.py b/tests/test_conductor_engine_v2.py index 11bb801e..f07ef08c 100644 --- a/tests/test_conductor_engine_v2.py +++ b/tests/test_conductor_engine_v2.py @@ -55,7 +55,7 @@ def test_conductor_engine_run_executes_tickets_in_order(monkeypatch: pytest.Monk vlogger.log_state("T1 Status Final", "todo", ticket1.status) vlogger.log_state("T2 Status Final", "todo", ticket2.status) - # Track.get_executable_tickets() should be called repeatedly until all are done + # get_executable_tickets(track) should be called repeatedly until all are done # T1 should run first, then T2. assert mock_lifecycle.call_count == 2 assert ticket1.status == "completed" diff --git a/tests/test_mma_models.py b/tests/test_mma_models.py index 5fda5b1a..4f88a21d 100644 --- a/tests/test_mma_models.py +++ b/tests/test_mma_models.py @@ -1,4 +1,5 @@ from src.models import Ticket, Track, WorkerContext +from src.dag_engine import get_executable_tickets def test_ticket_instantiation() -> None: """ @@ -117,7 +118,7 @@ def test_track_get_executable_tickets() -> None: """ - Verifies that track.get_executable_tickets() returns only 'todo' tickets + Verifies that get_executable_tickets(track) returns only 'todo' tickets whose dependencies are all 'completed'. """ # T1: todo, no deps -> executable @@ -133,7 +134,7 @@ def test_track_get_executable_tickets() -> None: # T6: todo, deps [T5] -> executable (T5 is completed) t6 = Ticket(id="T6", description="T6", status="todo", assigned_to="a", depends_on=["T5"]) track = Track(id="TR1", description="Track 1", tickets=[t1, t2, t3, t4, t5, t6]) - executable = track.get_executable_tickets() + executable = get_executable_tickets(track) executable_ids = [t.id for t in executable] assert "T1" in executable_ids assert "T6" in executable_ids @@ -160,18 +161,18 @@ def test_track_get_executable_tickets_complex() -> None: # T2 is todo, depends on T1 (completed) -> Executable # T5 is todo, no deps -> Executable # T3 is todo, depends on T2 (todo), T4 (completed), T5 (todo) -> Not executable - executable = track.get_executable_tickets() + executable = get_executable_tickets(track) executable_ids = sorted([t.id for t in executable]) assert executable_ids == ["T2", "T5"] # Mark T2 complete t2.mark_complete() # T3 still depends on T5 - executable = track.get_executable_tickets() + executable = get_executable_tickets(track) executable_ids = sorted([t.id for t in executable]) assert executable_ids == ["T5"] # Mark T5 complete t5.mark_complete() # Now T3 should be executable - executable = track.get_executable_tickets() + executable = get_executable_tickets(track) executable_ids = sorted([t.id for t in executable]) assert executable_ids == ["T3"] \ No newline at end of file