cleaning cruft part 2

This commit is contained in:
ed
2026-07-05 14:08:03 -04:00
parent 508baa69b6
commit 06898ca5a6
20 changed files with 74 additions and 469 deletions
+3 -15
View File
@@ -301,10 +301,7 @@ def build_file_items(base_dir: Path, files: list[str | Metadata]) -> list[Metada
return items return items
def _build_files_section_from_items(file_items: list[Metadata]) -> str: def _build_files_section_from_items(file_items: list[Metadata]) -> str:
""" """Build the files markdown section from pre-read file items (avoids double I/O)."""
Build the files markdown section from pre-read file items (avoids double I/O).
[C: tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_context_composition_phase6.py:test_files_section_rendering, tests/test_tiered_context.py:test_build_files_section_with_dicts, tests/test_ui_summary_only_removal.py:test_aggregate_from_items_respects_auto_aggregate]
"""
sections = [] sections = []
file_items = file_items or [] file_items = file_items or []
for item in file_items: for item in file_items:
@@ -330,9 +327,6 @@ def _build_files_section_from_items(file_items: list[Metadata]) -> str:
return "\n\n---\n\n".join(sections) return "\n\n---\n\n".join(sections)
def build_beads_section(base_dir: Path) -> str: def build_beads_section(base_dir: Path) -> str:
"""
[C: tests/test_aggregate_beads.py:test_build_beads_compaction]
"""
client = beads_client.BeadsClient(base_dir) client = beads_client.BeadsClient(base_dir)
if not client.is_initialized(): return "" if not client.is_initialized(): return ""
beads = client.list_beads() beads = client.list_beads()
@@ -369,17 +363,11 @@ def build_markdown_from_items(file_items: list[Metadata], screenshot_base_dir: P
return "\n\n---\n\n".join(parts) return "\n\n---\n\n".join(parts)
def build_markdown_no_history(file_items: list[Metadata], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str: def build_markdown_no_history(file_items: list[Metadata], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str:
""" """Build markdown with only files + screenshots (no history). Used for stable caching."""
Build markdown with only files + screenshots (no history). Used for stable caching.
[C: src/app_controller.py:AppController._do_generate, tests/test_history_management.py:test_aggregate_blacklist]
"""
return build_markdown_from_items(file_items, screenshot_base_dir, screenshots, history=[], summary_only=summary_only, aggregation_strategy=aggregation_strategy) return build_markdown_from_items(file_items, screenshot_base_dir, screenshots, history=[], summary_only=summary_only, aggregation_strategy=aggregation_strategy)
def build_discussion_text(history: list[str]) -> str: def build_discussion_text(history: list[str]) -> str:
""" """Build just the discussion history section text. Returns empty string if no history."""
Build just the discussion history section text. Returns empty string if no history.
[C: src/app_controller.py:AppController._do_generate, tests/test_history_management.py:test_aggregate_includes_segregated_history]
"""
if not history: if not history:
return "" return ""
return "## Discussion History\n\n" + build_discussion_section(history) return "## Discussion History\n\n" + build_discussion_section(history)
+35 -156
View File
@@ -56,17 +56,11 @@ from typing import Any
class ApiHookClient: class ApiHookClient:
def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None): def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.base_url = base_url.rstrip('/') self.base_url = base_url.rstrip('/')
self.api_key = api_key self.api_key = api_key
def _make_request(self, method: str, path: str, data: dict | None = None, timeout: float = 5.0) -> Metadata | None: def _make_request(self, method: str, path: str, data: dict | None = None, timeout: float = 5.0) -> Metadata | None:
""" """Helper to make HTTP requests to the hook server."""
Helper to make HTTP requests to the hook server.
[C: tests/test_api_hook_client.py:test_unsupported_method_error]
"""
url = f"{self.base_url}{path}" url = f"{self.base_url}{path}"
headers = {} headers = {}
if self.api_key: if self.api_key:
@@ -90,10 +84,7 @@ class ApiHookClient:
return None return None
def wait_for_server(self, timeout: int = 15) -> bool: def wait_for_server(self, timeout: int = 15) -> bool:
""" """Polls the health endpoint until the server responds or timeout occurs."""
Polls the health endpoint until the server responds or timeout occurs.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_conductor_api_hook_integration.py:test_conductor_integrates_api_hook_client_for_verification, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_phase6_simulation.py:test_ast_inspector_modal_opens, tests/test_phase6_simulation.py:test_batch_operations_shift_click, tests/test_phase6_simulation.py:test_slice_editor_add_remove, tests/test_preset_windows_layout.py:test_api_hook_under_load, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_tool_management_layout.py:test_tool_management_gettable_fields, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
start = time.time() start = time.time()
while time.time() - start < timeout: while time.time() - start < timeout:
status = self.get_status() status = self.get_status()
@@ -103,10 +94,7 @@ class ApiHookClient:
return False return False
def get_status(self) -> Metadata: def get_status(self) -> Metadata:
""" """Checks the health of the hook server."""
Checks the health of the hook server.
[C: tests/test_api_hook_client.py:test_get_status_success, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_phase6_simulation.py:test_ast_inspector_modal_opens, tests/test_phase6_simulation.py:test_batch_operations_shift_click, tests/test_phase6_simulation.py:test_slice_editor_add_remove, tests/test_preset_windows_layout.py:make_request, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls]
"""
res = self._make_request('GET', '/status') res = self._make_request('GET', '/status')
if res is None: if res is None:
# For backward compatibility with tests expecting ConnectionError # For backward compatibility with tests expecting ConnectionError
@@ -115,10 +103,7 @@ class ApiHookClient:
return res return res
def post_session(self, session_entries: list[dict]) -> Metadata: def post_session(self, session_entries: list[dict]) -> Metadata:
""" """Updates the session history."""
Updates the session history.
[C: tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {} return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {}
def get_events(self) -> list[Metadata]: def get_events(self) -> list[Metadata]:
@@ -127,16 +112,10 @@ class ApiHookClient:
return res.get("events", []) if res else [] return res.get("events", []) if res else []
def clear_events(self) -> list[Metadata]: def clear_events(self) -> list[Metadata]:
""" """Retrieves and clears the event queue."""
Retrieves and clears the event queue.
[C: simulation/sim_base.py:BaseSimulation.setup]
"""
return self.get_events() return self.get_events()
def wait_for_event(self, event_type: str, timeout: int = 5) -> Metadata | None: def wait_for_event(self, event_type: str, timeout: int = 5) -> Metadata | None:
"""
[C: simulation/sim_base.py:BaseSimulation.wait_for_event, simulation/sim_execution.py:ExecutionSimulation.run, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
start = time.time() start = time.time()
while time.time() - start < timeout: while time.time() - start < timeout:
events = self.get_events() events = self.get_events()
@@ -147,33 +126,21 @@ class ApiHookClient:
return None return None
def post_gui(self, payload: dict) -> Metadata: def post_gui(self, payload: dict) -> Metadata:
""" """Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint.
[C: tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_api_hook_client.py:test_post_gui_success, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works]
"""
return self._make_request('POST', '/api/gui', data=payload) or {} return self._make_request('POST', '/api/gui', data=payload) or {}
def push_event(self, action: str, payload: dict) -> Metadata: def push_event(self, action: str, payload: dict) -> Metadata:
""" """Convenience to push a GUI task."""
Convenience to push a GUI task.
[C: tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_auto_switch_sim.py:trigger_tier, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
return self.post_gui({"action": action, **payload}) return self.post_gui({"action": action, **payload})
#region: Data #region: Data
def get_gui_state(self) -> Metadata: def get_gui_state(self) -> Metadata:
""" """Returns the full GUI state available via the hook API."""
Returns the full GUI state available via the hook API.
[C: tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_conductor_api_hook_integration.py:simulate_conductor_phase_completion, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_live_workflow.py:wait_for_value, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_management_layout.py:test_tool_management_gettable_fields, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout, tests/test_visual_mma.py:test_visual_mma_components]
"""
return self._make_request('GET', '/api/gui/state') or {} return self._make_request('GET', '/api/gui/state') or {}
def get_value(self, item: str) -> Any: def get_value(self, item: str) -> Any:
""" """Gets the value of a GUI item via its mapped field."""
Gets the value of a GUI item via its mapped field.
[C: simulation/sim_ai_settings.py:AISettingsSimulation.run, simulation/sim_base.py:BaseSimulation.get_value, simulation/sim_base.py:BaseSimulation.setup, simulation/sim_base.py:BaseSimulation.wait_for_element, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/smoke_status_hook.py:test_status_hook, tests/smoke_status_hook.py:wait_for_value, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration]
"""
# Dict-key bracket notation: e.g. 'show_windows["Project Settings"]' # Dict-key bracket notation: e.g. 'show_windows["Project Settings"]'
if "[" in item and item.endswith("]"): if "[" in item and item.endswith("]"):
dict_name, _, key_part = item.partition("[") dict_name, _, key_part = item.partition("[")
@@ -202,18 +169,12 @@ class ApiHookClient:
return None return None
def get_text_value(self, item_tag: str) -> str | None: def get_text_value(self, item_tag: str) -> str | None:
""" """Wraps get_value and returns its string representation, or None."""
Wraps get_value and returns its string representation, or None.
[C: tests/test_api_hook_client.py:test_get_text_value]
"""
val = self.get_value(item_tag) val = self.get_value(item_tag)
return str(val) if val is not None else None return str(val) if val is not None else None
def set_value(self, item: str, value: Any) -> Metadata: def set_value(self, item: str, value: Any) -> Metadata:
""" """Sets the value of a GUI widget."""
Sets the value of a GUI widget.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_ai_settings.py:AISettingsSimulation.run, simulation/sim_base.py:BaseSimulation.setup, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
return self.post_gui({"action": "set_value", "item": item, "value": value}) return self.post_gui({"action": "set_value", "item": item, "value": value})
#endregion: Data #endregion: Data
@@ -221,31 +182,21 @@ class ApiHookClient:
#region: Input #region: Input
def click(self, item: str, user_data: Any = None) -> Metadata: def click(self, item: str, user_data: Any = None) -> Metadata:
""" """Simulates a button click."""
Simulates a button click.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.load_prior_log, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:_drain_approvals, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
return self.post_gui({"action": "click", "item": item, "user_data": user_data}) return self.post_gui({"action": "click", "item": item, "user_data": user_data})
def drag(self, src_item: str, dst_item: str) -> Metadata: def drag(self, src_item: str, dst_item: str) -> Metadata:
""" """Simulates a drag and drop operation."""
Simulates a drag and drop operation.
[C: tests/test_api_hook_client.py:test_drag_success]
"""
return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item}) return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item})
def right_click(self, item: str) -> Metadata: def right_click(self, item: str) -> Metadata:
""" """Simulates a right-click on an item."""
Simulates a right-click on an item.
[C: tests/test_api_hook_client.py:test_right_click_success]
"""
return self.push_event("right_click", {"item": item}) return self.push_event("right_click", {"item": item})
def request_confirmation(self, tool_name: str, args: dict) -> bool | None: def request_confirmation(self, tool_name: str, args: dict) -> bool | None:
""" """
Pushes a manual confirmation request and waits for response. Pushes a manual confirmation request and waits for response.
Blocks for up to 60 seconds. Blocks for up to 60 seconds.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_sync_hooks.py:test_api_ask_client_error, tests/test_sync_hooks.py:test_api_ask_client_method, tests/test_sync_hooks.py:test_api_ask_client_rejection]
""" """
# Long timeout as this waits for human input (60 seconds) # Long timeout as this waits for human input (60 seconds)
res = self._make_request('POST', '/api/ask', res = self._make_request('POST', '/api/ask',
@@ -254,17 +205,11 @@ class ApiHookClient:
return res.get('response') if res else None return res.get('response') if res else None
def select_list_item(self, item: str, value: str) -> Metadata: def select_list_item(self, item: str, value: str) -> Metadata:
""" """Selects an item in a listbox or combo."""
Selects an item in a listbox or combo.
[C: simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.switch_discussion, tests/test_api_hook_extensions.py:test_select_list_item_integration, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self.set_value(item, value) return self.set_value(item, value)
def select_tab(self, item: str, value: str) -> Metadata: def select_tab(self, item: str, value: str) -> Metadata:
""" """Selects a specific tab in a tab bar."""
Selects a specific tab in a tab bar.
[C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_select_tab_integration]
"""
return self.set_value(item, value) return self.set_value(item, value)
#endregion: Input #endregion: Input
@@ -279,17 +224,11 @@ class ApiHookClient:
}) or {} }) or {}
def apply_patch(self) -> Metadata: def apply_patch(self) -> Metadata:
""" """Applies the pending patch."""
Applies the pending patch.
[C: tests/test_patch_modal.py:test_apply_callback]
"""
return self._make_request('POST', '/api/patch/apply') or {} return self._make_request('POST', '/api/patch/apply') or {}
def reject_patch(self) -> Metadata: def reject_patch(self) -> Metadata:
""" """Rejects the pending patch."""
Rejects the pending patch.
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
"""
return self._make_request('POST', '/api/patch/reject') or {} return self._make_request('POST', '/api/patch/reject') or {}
def get_patch_status(self) -> Metadata: def get_patch_status(self) -> Metadata:
@@ -301,32 +240,20 @@ class ApiHookClient:
#region: Diagnostics #region: Diagnostics
def get_indicator_state(self, item_tag: str) -> dict[str, bool]: def get_indicator_state(self, item_tag: str) -> dict[str, bool]:
""" """Returns the visibility/active state of a status indicator."""
Returns the visibility/active state of a status indicator.
[C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_get_indicator_state_integration, tests/test_live_workflow.py:test_full_live_workflow]
"""
val = self.get_value(item_tag) val = self.get_value(item_tag)
return {"shown": bool(val)} return {"shown": bool(val)}
def get_gui_diagnostics(self) -> Metadata: def get_gui_diagnostics(self) -> Metadata:
""" """Retrieves performance and diagnostic metrics."""
Retrieves performance and diagnostic metrics.
[C: tests/test_api_hook_client.py:test_get_performance_success, tests/test_hooks.py:test_live_hook_server_responses, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing]
"""
return self._make_request('GET', '/api/gui/diagnostics') or {} return self._make_request('GET', '/api/gui/diagnostics') or {}
def get_performance(self) -> Metadata: def get_performance(self) -> Metadata:
""" """Retrieves performance metrics from the dedicated endpoint."""
Retrieves performance metrics from the dedicated endpoint.
[C: tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_gui_performance_requirements.py:test_idle_performance_requirements, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_selectable_ui.py:test_selectable_label_stability]
"""
return self._make_request('GET', '/api/performance') or {} return self._make_request('GET', '/api/performance') or {}
def get_warmup_status(self) -> Metadata: def get_warmup_status(self) -> Metadata:
""" """Returns the current warmup status: {pending, completed, failed}."""
Returns the current warmup status: {pending, completed, failed}.
[C: tests/test_api_hooks_warmup.py:test_get_warmup_status_calls_correct_endpoint, tests/test_api_hooks_warmup.py:test_get_warmup_status_handles_empty_response, tests/test_api_hooks_warmup.py:test_live_warmup_status_endpoint]
"""
return self._make_request('GET', '/api/warmup_status') or {} return self._make_request('GET', '/api/warmup_status') or {}
def get_warmup_wait(self, timeout: float = 30.0) -> Metadata: def get_warmup_wait(self, timeout: float = 30.0) -> Metadata:
@@ -335,7 +262,6 @@ class ApiHookClient:
complete, then returns the final status. Useful for external clients complete, then returns the final status. Useful for external clients
that need to wait until the system is fully ready before issuing AI that need to wait until the system is fully ready before issuing AI
requests. requests.
[C: tests/test_api_hooks_warmup.py:test_get_warmup_wait_passes_timeout_as_query_string, tests/test_api_hooks_warmup.py:test_get_warmup_wait_uses_default_timeout_when_unspecified, tests/test_api_hooks_warmup.py:test_get_warmup_wait_handles_empty_response, tests/test_api_hooks_warmup.py:test_live_warmup_wait_endpoint_completes]
""" """
return self._make_request('GET', f'/api/warmup_wait?timeout={timeout}') or {} return self._make_request('GET', f'/api/warmup_wait?timeout={timeout}') or {}
@@ -345,7 +271,6 @@ class ApiHookClient:
canary_id, module, thread_name, thread_id, submit_ts, start_ts, canary_id, module, thread_name, thread_id, submit_ts, start_ts,
end_ts, elapsed_ms, status, error. Used for debugging which end_ts, elapsed_ms, status, error. Used for debugging which
worker thread loaded which module and how long it took. worker thread loaded which module and how long it took.
[C: tests/test_api_hooks_warmup.py:test_get_warmup_canaries_in_live_gui]
""" """
result = self._make_request('GET', '/api/warmup_canaries') or {} result = self._make_request('GET', '/api/warmup_canaries') or {}
return result.get("canaries", []) if isinstance(result, dict) else [] return result.get("canaries", []) if isinstance(result, dict) else []
@@ -356,7 +281,6 @@ class ApiHookClient:
first_frame_ts, warmup_ms, first_frame_after_init_ms, first_frame_ts, warmup_ms, first_frame_after_init_ms,
first_frame_after_warmup_ms. Lets external clients answer first_frame_after_warmup_ms. Lets external clients answer
'did the warmup block the first frame?'. 'did the warmup block the first frame?'.
[C: tests/test_api_hooks_warmup.py:test_live_startup_timeline_endpoint]
""" """
return self._make_request('GET', '/api/startup_timeline') or {} return self._make_request('GET', '/api/startup_timeline') or {}
@@ -365,10 +289,7 @@ class ApiHookClient:
#region: Project #region: Project
def get_project(self) -> Metadata: def get_project(self) -> Metadata:
""" """Retrieves the current project state."""
Retrieves the current project state.
[C: simulation/sim_context.py:ContextSimulation.run, tests/test_api_hook_client.py:test_get_project_success, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self._make_request('GET', '/api/project') or {} return self._make_request('GET', '/api/project') or {}
def get_project_switch_status(self) -> Metadata: def get_project_switch_status(self) -> Metadata:
@@ -379,7 +300,6 @@ class ApiHookClient:
- error: string error message if the last switch failed; None on success - error: string error message if the last switch failed; None on success
Used by tests to wait deterministically for a project switch to complete Used by tests to wait deterministically for a project switch to complete
instead of blind-polling the project state. instead of blind-polling the project state.
[C: tests/test_api_hooks_project_switch.py]
""" """
result = self._make_request('GET', '/api/project_switch_status') result = self._make_request('GET', '/api/project_switch_status')
if not result or not isinstance(result, dict): if not result or not isinstance(result, dict):
@@ -395,7 +315,6 @@ class ApiHookClient:
- the controller reports an error during the switch - the controller reports an error during the switch
- the path doesn't reach expected_path within the timeout - the path doesn't reach expected_path within the timeout
- the controller is hung (in_progress stays True) - the controller is hung (in_progress stays True)
[C: tests/test_live_workflow.py:test_full_live_workflow]
""" """
import os import os
start = time.time() start = time.time()
@@ -424,7 +343,6 @@ class ApiHookClient:
- inflight: integer count of jobs submitted via submit_io - inflight: integer count of jobs submitted via submit_io
Used by tests to wait for the pool to drain before submitting work Used by tests to wait for the pool to drain before submitting work
when sharing a live_gui session with prior tests. when sharing a live_gui session with prior tests.
[C: tests/test_api_hook_client.py:test_get_io_pool_status_*]
""" """
result = self._make_request('GET', '/api/io_pool_status') result = self._make_request('GET', '/api/io_pool_status')
if not result or not isinstance(result, dict): if not result or not isinstance(result, dict):
@@ -440,7 +358,6 @@ class ApiHookClient:
- degraded_reason: human-readable description of the failure (if any) - degraded_reason: human-readable description of the failure (if any)
- last_assert: full traceback of the last ImGui scope mismatch - last_assert: full traceback of the last ImGui scope mismatch
- io_pool_alive: True if submit_io is currently functional - io_pool_alive: True if submit_io is currently functional
[C: tests/test_api_hook_client_gui_health.py:test_get_gui_health_*]
""" """
result = self._make_request('GET', '/api/gui_health') result = self._make_request('GET', '/api/gui_health')
if not result or not isinstance(result, dict): if not result or not isinstance(result, dict):
@@ -457,7 +374,6 @@ class ApiHookClient:
Blocks until the controller's io_pool reports idle=True or timeout. Blocks until the controller's io_pool reports idle=True or timeout.
Returns True on idle, False on timeout. Use this to ensure prior Returns True on idle, False on timeout. Use this to ensure prior
tests' background work has completed before submitting new work. tests' background work has completed before submitting new work.
[C: tests/test_live_workflow.py:test_full_live_workflow]
""" """
start = time.time() start = time.time()
while time.time() - start < timeout: while time.time() - start < timeout:
@@ -471,10 +387,7 @@ class ApiHookClient:
return last_status return last_status
def post_project(self, project_data: dict) -> Metadata: def post_project(self, project_data: dict) -> Metadata:
""" """Updates the current project configuration."""
Updates the current project configuration.
[C: simulation/sim_context.py:ContextSimulation.run]
"""
return self._make_request('POST', '/api/project', data=project_data) or {} return self._make_request('POST', '/api/project', data=project_data) or {}
#endregion: Project #endregion: Project
@@ -482,17 +395,11 @@ class ApiHookClient:
#region: Context #region: Context
def inject_context(self, data: dict) -> dict: def inject_context(self, data: dict) -> dict:
""" """Injects custom file context into the application."""
Injects custom file context into the application.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/context/inject', data=data) or {} return self._make_request('POST', '/api/context/inject', data=data) or {}
def get_context_state(self) -> Metadata: def get_context_state(self) -> Metadata:
""" """Retrieves the current file and screenshot context state."""
Retrieves the current file and screenshot context state.
[C: tests/test_gui_context_presets.py:test_gui_context_preset_save_load]
"""
return self._make_request('GET', '/api/context/state') or {} return self._make_request('GET', '/api/context/state') or {}
#endregion: Context #endregion: Context
@@ -500,17 +407,11 @@ class ApiHookClient:
#region: Discussion #region: Discussion
def get_session(self) -> Metadata: def get_session(self) -> Metadata:
""" """Retrieves the current discussion session history."""
Retrieves the current discussion session history.
[C: simulation/ping_pong.py:main, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/sim_tools.py:ToolsSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_api_hook_client.py:test_get_session_success, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim]
"""
return self._make_request('GET', '/api/session') or {} return self._make_request('GET', '/api/session') or {}
def reset_session(self) -> None: def reset_session(self) -> None:
""" """Resets the current session via button click."""
Resets the current session via button click.
[C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_provider_panel, src/gui_2.py:App._show_menus, src/multi_agent_conductor.py:run_worker_lifecycle, tests/conftest.py:live_gui, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_metrics.py:test_get_gemini_cache_stats_with_mock_client, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_session_logger_reset.py:test_reset_session, tests/test_token_usage.py:test_token_usage_tracking]
"""
self.click("btn_reset") self.click("btn_reset")
#endregion: Discussion #endregion: Discussion
@@ -530,32 +431,19 @@ class ApiHookClient:
#region: MMA #region: MMA
def get_node_status(self, node_id: str) -> Metadata: def get_node_status(self, node_id: str) -> Metadata:
""" """Retrieves status for a specific node in the MMA DAG."""
Retrieves status for a specific node in the MMA DAG.
[C: tests/test_api_hook_client.py:test_get_node_status]
"""
return self._make_request('GET', f'/api/mma/node/{node_id}') or {} return self._make_request('GET', f'/api/mma/node/{node_id}') or {}
def get_mma_status(self) -> Metadata: def get_mma_status(self) -> Metadata:
""" """Retrieves the dedicated MMA engine status."""
Retrieves the dedicated MMA engine status.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:_poll_mma_status, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:_poll_mma_status, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:_poll]
"""
return self._make_request('GET', '/api/gui/mma_status') or {} return self._make_request('GET', '/api/gui/mma_status') or {}
def get_mma_workers(self) -> Metadata: def get_mma_workers(self) -> Metadata:
""" """Retrieves status for all active MMA workers."""
Retrieves status for all active MMA workers.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:_poll_mma_workers]
"""
return self._make_request('GET', '/api/mma/workers') or {} return self._make_request('GET', '/api/mma/workers') or {}
def spawn_mma_worker(self, data: dict) -> dict: def spawn_mma_worker(self, data: dict) -> dict:
""" """Spawns a new MMA worker with the provided configuration."""
Spawns a new MMA worker with the provided configuration.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/mma/workers/spawn', data=data) or {} return self._make_request('POST', '/api/mma/workers/spawn', data=data) or {}
def kill_mma_worker(self, worker_id: str) -> dict: def kill_mma_worker(self, worker_id: str) -> dict:
@@ -563,10 +451,7 @@ class ApiHookClient:
return self._make_request('POST', '/api/mma/workers/kill', data={"worker_id": worker_id}) or {} return self._make_request('POST', '/api/mma/workers/kill', data={"worker_id": worker_id}) or {}
def pause_mma_pipeline(self) -> dict: def pause_mma_pipeline(self) -> dict:
""" """Pauses the MMA execution pipeline."""
Pauses the MMA execution pipeline.
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
"""
return self._make_request('POST', '/api/mma/pipeline/pause') or {} return self._make_request('POST', '/api/mma/pipeline/pause') or {}
def resume_mma_pipeline(self) -> dict: def resume_mma_pipeline(self) -> dict:
@@ -574,17 +459,11 @@ class ApiHookClient:
return self._make_request('POST', '/api/mma/pipeline/resume') or {} return self._make_request('POST', '/api/mma/pipeline/resume') or {}
def mutate_mma_dag(self, data: dict) -> dict: def mutate_mma_dag(self, data: dict) -> dict:
""" """Mutates the MMA DAG (Directed Acyclic Graph) structure."""
Mutates the MMA DAG (Directed Acyclic Graph) structure.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/mma/dag/mutate', data=data) or {} return self._make_request('POST', '/api/mma/dag/mutate', data=data) or {}
def approve_mma_ticket(self, ticket_id: str) -> dict: def approve_mma_ticket(self, ticket_id: str) -> dict:
""" """Manually approves a specific ticket for execution in Step Mode."""
Manually approves a specific ticket for execution in Step Mode.
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
"""
return self._make_request('POST', '/api/mma/ticket/approve', data={"ticket_id": ticket_id}) or {} return self._make_request('POST', '/api/mma/ticket/approve', data={"ticket_id": ticket_id}) or {}
#endregion: MMA #endregion: MMA
+3 -35
View File
@@ -33,7 +33,6 @@ def _create_generate_request() -> type:
max_tokens=(int | None, None), max_tokens=(int | None, None),
) )
def _create_confirm_request() -> type: def _create_confirm_request() -> type:
from src.module_loader import _require_warmed from src.module_loader import _require_warmed
pydantic = _require_warmed("pydantic") pydantic = _require_warmed("pydantic")
@@ -49,7 +48,6 @@ _PYDANTIC_CLASS_FACTORIES: dict[str, callable] = {
"ConfirmRequest": _create_confirm_request, "ConfirmRequest": _create_confirm_request,
} }
def __getattr__(name: str) -> Any: def __getattr__(name: str) -> Any:
if name in _PYDANTIC_CLASS_FACTORIES: if name in _PYDANTIC_CLASS_FACTORIES:
cls = _PYDANTIC_CLASS_FACTORIES[name]() cls = _PYDANTIC_CLASS_FACTORIES[name]()
@@ -126,8 +124,6 @@ def _safe_controller_result(controller: Any, method_name: str, fallback: dict) -
drain point. This helper internally does the try/except and returns Result[dict] drain point. This helper internally does the try/except and returns Result[dict]
(matching Heuristic A: Result-returning recovery = INTERNAL_COMPLIANT). The HTTP (matching Heuristic A: Result-returning recovery = INTERNAL_COMPLIANT). The HTTP
response (the drain point) terminates the propagation. response (the drain point) terminates the propagation.
[C: src/api_hooks.py:HookHandler.do_GET, src/api_hooks.py:HookHandler.do_POST]
""" """
if controller is None or not hasattr(controller, method_name): if controller is None or not hasattr(controller, method_name):
return Result(data=fallback, errors=[ErrorInfo(kind=ErrorKind.NOT_READY, message=f"controller missing or has no {method_name}", source=f"api_hooks._safe_controller_result.{method_name}")]) return Result(data=fallback, errors=[ErrorInfo(kind=ErrorKind.NOT_READY, message=f"controller missing or has no {method_name}", source=f"api_hooks._safe_controller_result.{method_name}")])
@@ -143,8 +139,6 @@ def _parse_float_result(value: Any, default: float) -> Result[float]:
Per error_handling.md: narrow-except fallback sites must propagate Result[T]. This Per error_handling.md: narrow-except fallback sites must propagate Result[T]. This
helper does the parse + try/except + Result conversion internally (Heuristic A). helper does the parse + try/except + Result conversion internally (Heuristic A).
[C: src/api_hooks.py:HookHandler.do_GET]
""" """
try: try:
return Result(data=float(value)) return Result(data=float(value))
@@ -158,8 +152,6 @@ def _run_callback_result(callback) -> Result[bool]:
Per error_handling.md: log/silent-fallback sites must propagate Result[T] to a true Per error_handling.md: log/silent-fallback sites must propagate Result[T] to a true
drain point. This helper internally does the try/except and returns Result[bool] drain point. This helper internally does the try/except and returns Result[bool]
(matching Heuristic A). The drain point is the HTTP response (self.send_response). (matching Heuristic A). The drain point is the HTTP response (self.send_response).
[C: src/api_hooks.py:HookHandler.do_POST, src/api_hooks.py:HookHandler.do_GET]
""" """
try: try:
callback() callback()
@@ -171,12 +163,9 @@ def _run_callback_result(callback) -> Result[bool]:
class HookServerInstance(ThreadingHTTPServer): class HookServerInstance(ThreadingHTTPServer):
allow_reuse_address = True allow_reuse_address = True
"""Custom HTTPServer that carries a reference to the main App instance.""" """Custom HTTPServer that carries a reference to the main App instance."""
def __init__(self, server_address: tuple[str, int], RequestHandlerClass: type, app: Any) -> None:
"""
Initializes the server instance with an app reference. def __init__(self, server_address: tuple[str, int], RequestHandlerClass: type, app: Any) -> None:
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__] """Initializes the server instance with an app reference."""
"""
super().__init__(server_address, RequestHandlerClass) super().__init__(server_address, RequestHandlerClass)
self.app = app self.app = app
@@ -207,6 +196,7 @@ def _serialize_for_api(obj: Any) -> JsonValue:
class HookHandler(BaseHTTPRequestHandler): class HookHandler(BaseHTTPRequestHandler):
"""Handles incoming HTTP requests for the API hooks.""" """Handles incoming HTTP requests for the API hooks."""
def do_GET(self) -> None: def do_GET(self) -> None:
"""Handles GET requests by routing to the appropriate state provider.""" """Handles GET requests by routing to the appropriate state provider."""
try: try:
@@ -934,15 +924,11 @@ class HookHandler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode("utf-8")) self.wfile.write(json.dumps({"error": str(e)}).encode("utf-8"))
def log_message(self, format: str, *args: Any) -> None: def log_message(self, format: str, *args: Any) -> None:
logging.info("Hook API: " + format % args) logging.info("Hook API: " + format % args)
class HookServer: class HookServer:
def __init__(self, app: Any, port: int = 8999) -> None: def __init__(self, app: Any, port: int = 8999) -> None:
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.app = app self.app = app
self.port = port self.port = port
self.server = None self.server = None
@@ -950,9 +936,6 @@ class HookServer:
self.websocket_server: WebSocketServer | None = None self.websocket_server: WebSocketServer | None = None
def start(self) -> None: def start(self) -> None:
"""
[C: src/app_controller.py:AppController._cb_accept_tracks, src/app_controller.py:AppController._cb_plan_epic, src/app_controller.py:AppController._cb_start_track, src/app_controller.py:AppController._fetch_models, src/app_controller.py:AppController._handle_approve_ask, src/app_controller.py:AppController._handle_generate_send, src/app_controller.py:AppController._handle_md_only, src/app_controller.py:AppController._handle_reject_ask, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController._process_event_queue, src/app_controller.py:AppController._prune_old_logs, src/app_controller.py:AppController._rebuild_rag_index, src/app_controller.py:AppController._run_event_loop, src/app_controller.py:AppController._start_track_logic, src/app_controller.py:AppController.cb_prune_logs, src/app_controller.py:AppController.init_state, src/app_controller.py:AppController.start_services, src/gui_2.py:App._render_discussion_entry_read_mode, src/gui_2.py:App._update_context_file_stats, src/mcp_client.py:ExternalMCPManager.add_server, src/multi_agent_conductor.py:WorkerPool.spawn, src/performance_monitor.py:PerformanceMonitor.__init__, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread, tests/test_conductor_engine_v2.py:side_effect, tests/test_spawn_interception_v2.py:test_confirm_spawn_pushed_to_queue, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
if self.thread and self.thread.is_alive(): if self.thread and self.thread.is_alive():
return return
is_gemini_cli = _get_app_attr(self.app, 'current_provider', '') == 'gemini_cli' is_gemini_cli = _get_app_attr(self.app, 'current_provider', '') == 'gemini_cli'
@@ -978,9 +961,6 @@ class HookServer:
logging.info(f"Hook server started on port {self.port}") logging.info(f"Hook server started on port {self.port}")
def stop(self) -> None: def stop(self) -> None:
"""
[C: src/app_controller.py:AppController.shutdown, src/mcp_client.py:ExternalMCPManager.stop_all, tests/test_performance_monitor.py:test_perf_monitor_basic_timing, tests/test_performance_monitor.py:test_perf_monitor_component_timing, tests/test_performance_monitor.py:test_perf_monitor_extended_metrics, tests/test_performance_monitor.py:test_perf_monitor_scope_context_manager, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
if self.websocket_server: if self.websocket_server:
self.websocket_server.stop() self.websocket_server.stop()
if self.server: if self.server:
@@ -993,9 +973,6 @@ class HookServer:
class WebSocketServer: class WebSocketServer:
"""WebSocket gateway for real-time event streaming.""" """WebSocket gateway for real-time event streaming."""
def __init__(self, app: Any, port: int = 9000) -> None: def __init__(self, app: Any, port: int = 9000) -> None:
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.app = app self.app = app
self.port = port self.port = port
self.clients: dict[str, set] = {"events": set(), "telemetry": set()} self.clients: dict[str, set] = {"events": set(), "telemetry": set()}
@@ -1048,27 +1025,18 @@ class WebSocketServer:
self.loop.run_until_complete(main()) self.loop.run_until_complete(main())
def start(self) -> None: def start(self) -> None:
"""
[C: src/app_controller.py:AppController._cb_accept_tracks, src/app_controller.py:AppController._cb_plan_epic, src/app_controller.py:AppController._cb_start_track, src/app_controller.py:AppController._fetch_models, src/app_controller.py:AppController._handle_approve_ask, src/app_controller.py:AppController._handle_generate_send, src/app_controller.py:AppController._handle_md_only, src/app_controller.py:AppController._handle_reject_ask, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController._process_event_queue, src/app_controller.py:AppController._prune_old_logs, src/app_controller.py:AppController._rebuild_rag_index, src/app_controller.py:AppController._run_event_loop, src/app_controller.py:AppController._start_track_logic, src/app_controller.py:AppController.cb_prune_logs, src/app_controller.py:AppController.init_state, src/app_controller.py:AppController.start_services, src/gui_2.py:App._render_discussion_entry_read_mode, src/gui_2.py:App._update_context_file_stats, src/mcp_client.py:ExternalMCPManager.add_server, src/multi_agent_conductor.py:WorkerPool.spawn, src/performance_monitor.py:PerformanceMonitor.__init__, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread, tests/test_conductor_engine_v2.py:side_effect, tests/test_spawn_interception_v2.py:test_confirm_spawn_pushed_to_queue, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
if self.thread and self.thread.is_alive(): if self.thread and self.thread.is_alive():
return return
self.thread = threading.Thread(target=self._run_loop, daemon=True) self.thread = threading.Thread(target=self._run_loop, daemon=True)
self.thread.start() self.thread.start()
def stop(self) -> None: def stop(self) -> None:
"""
[C: src/app_controller.py:AppController.shutdown, src/mcp_client.py:ExternalMCPManager.stop_all, tests/test_performance_monitor.py:test_perf_monitor_basic_timing, tests/test_performance_monitor.py:test_perf_monitor_component_timing, tests/test_performance_monitor.py:test_perf_monitor_extended_metrics, tests/test_performance_monitor.py:test_perf_monitor_scope_context_manager, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
if self.loop and self._stop_event: if self.loop and self._stop_event:
self.loop.call_soon_threadsafe(self._stop_event.set) self.loop.call_soon_threadsafe(self._stop_event.set)
if self.thread: if self.thread:
self.thread.join(timeout=2.0) self.thread.join(timeout=2.0)
def broadcast(self, message: WebSocketMessage) -> None: def broadcast(self, message: WebSocketMessage) -> None:
"""
[C: src/app_controller.py:AppController._process_pending_gui_tasks, src/events.py:AsyncEventQueue.put, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
if not self.loop or message.channel not in self.clients: if not self.loop or message.channel not in self.clients:
return return
wire = json.dumps({"channel": message.channel, "payload": message.payload}) wire = json.dumps({"channel": message.channel, "payload": message.payload})
+5 -25
View File
@@ -22,29 +22,17 @@ class BeadsClient:
self.beads_file = self.repo_dir / "beads.json" self.beads_file = self.repo_dir / "beads.json"
def init_repo(self) -> None: def init_repo(self) -> None:
""" """Initialize the mock repository."""
Initialize the mock repository.
[C: tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query, tests/test_gui_dag_beads.py:test_load_active_tickets_from_beads, tests/test_mcp_client_beads.py:test_bd_mcp_tools]
"""
self.repo_dir.mkdir(parents=True, exist_ok=True) self.repo_dir.mkdir(parents=True, exist_ok=True)
if not self.beads_file.exists(): if not self.beads_file.exists():
self.beads_file.write_text("[]", encoding="utf-8") self.beads_file.write_text("[]", encoding="utf-8")
def is_initialized(self) -> bool: def is_initialized(self) -> bool:
""" """Check if the repository is initialized."""
Check if the repository is initialized.
[C: src/mcp_client.py:dispatch, tests/test_beads_client.py:test_beads_init_and_query]
"""
return self.beads_file.exists() return self.beads_file.exists()
def create_bead(self, title: str, description: str) -> str: def create_bead(self, title: str, description: str) -> str:
""" """Create a new bead and return its ID."""
Create a new bead and return its ID.
[C: src/mcp_client.py:dispatch, tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query, tests/test_gui_dag_beads.py:test_load_active_tickets_from_beads]
"""
beads = self._read_beads() beads = self._read_beads()
bead_id = f"bead-{len(beads) + 1}" bead_id = f"bead-{len(beads) + 1}"
bead = {"id": bead_id, "title": title, "description": description, "status": "active"} bead = {"id": bead_id, "title": title, "description": description, "status": "active"}
@@ -53,11 +41,7 @@ class BeadsClient:
return bead_id return bead_id
def update_bead(self, bead_id: str, status: str) -> bool: def update_bead(self, bead_id: str, status: str) -> bool:
""" """Update the status of an existing bead."""
Update the status of an existing bead.
[C: src/mcp_client.py:dispatch, tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query]
"""
beads = self._read_beads() beads = self._read_beads()
for bead in beads: for bead in beads:
if bead["id"] == bead_id: if bead["id"] == bead_id:
@@ -67,11 +51,7 @@ class BeadsClient:
return False return False
def list_beads(self) -> List[Bead]: def list_beads(self) -> List[Bead]:
""" """List all beads."""
List all beads.
[C: src/gui_2.py:App._render_beads_tab, src/mcp_client.py:dispatch, tests/test_beads_client.py:test_beads_init_and_query]
"""
return [Bead(**b) for b in self._read_beads()] return [Bead(**b) for b in self._read_beads()]
def _read_beads(self) -> List[dict]: def _read_beads(self) -> List[dict]:
-2
View File
@@ -46,7 +46,6 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str,
""" """
Tier 2 (Tech Lead) call. Tier 2 (Tech Lead) call.
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets. Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
[C: tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_failure, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_success, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_success, tests/test_orchestration_logic.py:test_generate_tickets]
""" """
# 1. Set Tier 2 Model (Tech Lead - Flash) # 1. Set Tier 2 Model (Tech Lead - Flash)
# 2. Construct Prompt # 2. Construct Prompt
@@ -108,7 +107,6 @@ def topological_sort(tickets: list[Ticket]) -> list[Ticket]:
""" """
Sorts a list of Ticket objects based on their depends_on field. Sorts a list of Ticket objects based on their depends_on field.
Raises ValueError if a circular dependency or missing internal dependency is detected. Raises ValueError if a circular dependency or missing internal dependency is detected.
[C: tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_complex, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_cycle, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_empty, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_linear, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_missing_dependency, tests/test_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_dag_engine.py:test_topological_sort, tests/test_dag_engine.py:test_topological_sort_cycle, tests/test_orchestration_logic.py:test_topological_sort, tests/test_orchestration_logic.py:test_topological_sort_circular, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
""" """
dag = TrackDAG(tickets) dag = TrackDAG(tickets)
try: try:
-2
View File
@@ -67,8 +67,6 @@ def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
""" """
Estimate the cost of a model call based on input and output tokens. Estimate the cost of a model call based on input and output tokens.
Returns the total cost in USD. Returns the total cost in USD.
[C: src/gui_2.py:App._render_mma_track_summary, src/gui_2.py:App._render_mma_usage_section, src/gui_2.py:App._render_token_budget_panel, tests/test_cost_tracker.py:test_estimate_cost]
""" """
if not model: if not model:
return 0.0 return 0.0
-10
View File
@@ -43,7 +43,6 @@ class TrackDAG:
Initializes the TrackDAG with a list of Ticket objects. Initializes the TrackDAG with a list of Ticket objects.
Args: Args:
tickets: A list of Ticket instances defining the graph nodes and edges. tickets: A list of Ticket instances defining the graph nodes and edges.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
""" """
self.tickets = tickets self.tickets = tickets
self.ticket_map = {t.id: t for t in tickets} self.ticket_map = {t.id: t for t in tickets}
@@ -52,7 +51,6 @@ class TrackDAG:
""" """
Transitively marks `todo` tickets as `blocked` if any dependency is `blocked`. Transitively marks `todo` tickets as `blocked` if any dependency is `blocked`.
Propagates 'blocked' status from initially blocked nodes to their dependents. Propagates 'blocked' status from initially blocked nodes to their dependents.
[C: tests/test_perf_dag.py:test_dag_performance]
""" """
with get_monitor().scope("dag_cascade_blocks"): with get_monitor().scope("dag_cascade_blocks"):
# Build adjacency list of dependents using object references to avoid lookups # Build adjacency list of dependents using object references to avoid lookups
@@ -89,7 +87,6 @@ class TrackDAG:
Returns a list of tickets that are in 'todo' status and whose dependencies are all 'completed'. Returns a list of tickets that are in 'todo' status and whose dependencies are all 'completed'.
Returns: Returns:
A list of Ticket objects ready for execution. A list of Ticket objects ready for execution.
[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 = [] ready = []
for ticket in self.tickets: for ticket in self.tickets:
@@ -102,7 +99,6 @@ class TrackDAG:
Performs an iterative Depth-First Search to detect cycles in the dependency graph. Performs an iterative Depth-First Search to detect cycles in the dependency graph.
Returns: Returns:
True if a cycle is detected, False otherwise. True if a cycle is detected, False otherwise.
[C: src/gui_2.py:App._render_task_dag_panel, tests/test_dag_engine.py:test_has_cycle_complex_no_cycle, tests/test_dag_engine.py:test_has_cycle_direct_cycle, tests/test_dag_engine.py:test_has_cycle_indirect_cycle, tests/test_dag_engine.py:test_has_cycle_no_cycle, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
""" """
with get_monitor().scope("dag_has_cycle"): with get_monitor().scope("dag_has_cycle"):
visited = set() visited = set()
@@ -135,7 +131,6 @@ class TrackDAG:
A list of ticket ID strings. A list of ticket ID strings.
Raises: Raises:
ValueError: If a dependency cycle is detected. ValueError: If a dependency cycle is detected.
[C: tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_complex, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_cycle, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_empty, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_linear, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_missing_dependency, tests/test_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_dag_engine.py:test_topological_sort, tests/test_dag_engine.py:test_topological_sort_cycle, tests/test_orchestration_logic.py:test_topological_sort, tests/test_orchestration_logic.py:test_topological_sort_circular, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
""" """
with get_monitor().scope("dag_topological_sort"): with get_monitor().scope("dag_topological_sort"):
in_degree = {t.id: len(t.depends_on) for t in self.tickets} in_degree = {t.id: len(t.depends_on) for t in self.tickets}
@@ -168,7 +163,6 @@ def get_executable_tickets(track: "Track") -> List[Ticket]:
Free function (instead of Track.get_executable_tickets) so that Free function (instead of Track.get_executable_tickets) so that
src/models.py does not need to import TrackDAG at module level, src/models.py does not need to import TrackDAG at module level,
breaking the models<->dag_engine circular dependency. 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() return TrackDAG(track.tickets).get_ready_tasks()
@@ -185,7 +179,6 @@ class ExecutionEngine:
Args: Args:
dag: The TrackDAG instance to manage. dag: The TrackDAG instance to manage.
auto_queue: If True, ready tasks will automatically move to 'in_progress'. auto_queue: If True, ready tasks will automatically move to 'in_progress'.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
""" """
self.dag = dag self.dag = dag
self.auto_queue = auto_queue self.auto_queue = auto_queue
@@ -196,7 +189,6 @@ class ExecutionEngine:
If auto_queue is enabled, tasks without 'step_mode' will be marked as 'in_progress'. If auto_queue is enabled, tasks without 'step_mode' will be marked as 'in_progress'.
Returns: Returns:
A list of ready Ticket objects. A list of ready Ticket objects.
[C: src/multi_agent_conductor.py:ConductorEngine.run, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_cascade_blocks_multi_hop, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_cascade_blocks_simple, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_execution_engine_tick_cascades_blocks, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_in_progress_not_blocked, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_manual_unblock_restores_todo, tests/test_execution_engine.py:test_execution_engine_auto_queue, tests/test_execution_engine.py:test_execution_engine_basic_flow, tests/test_execution_engine.py:test_execution_engine_step_mode]
""" """
with get_monitor().scope("dag_tick"): with get_monitor().scope("dag_tick"):
self.dag.cascade_blocks() self.dag.cascade_blocks()
@@ -208,7 +200,6 @@ class ExecutionEngine:
Manually transitions a task from 'todo' to 'in_progress' if its dependencies are met. Manually transitions a task from 'todo' to 'in_progress' if its dependencies are met.
Args: Args:
task_id: The ID of the task to approve. task_id: The ID of the task to approve.
[C: src/multi_agent_conductor.py:ConductorEngine.approve_task, tests/test_execution_engine.py:test_execution_engine_approve_task, tests/test_execution_engine.py:test_execution_engine_step_mode]
""" """
ticket = self.dag.ticket_map.get(task_id) ticket = self.dag.ticket_map.get(task_id)
if ticket and ticket.status == "todo" and self.dag.is_ticket_ready(ticket): if ticket and ticket.status == "todo" and self.dag.is_ticket_ready(ticket):
@@ -220,7 +211,6 @@ class ExecutionEngine:
Args: Args:
task_id: The ID of the task. task_id: The ID of the task.
status: The new status string (e.g., 'todo', 'in_progress', 'completed', 'blocked'). status: The new status string (e.g., 'todo', 'in_progress', 'completed', 'blocked').
[C: src/multi_agent_conductor.py:ConductorEngine.update_task_status, tests/test_arch_boundary_phase3.py:TestArchBoundaryPhase3.test_manual_unblock_restores_todo, tests/test_execution_engine.py:test_execution_engine_auto_queue, tests/test_execution_engine.py:test_execution_engine_basic_flow, tests/test_execution_engine.py:test_execution_engine_status_persistence, tests/test_execution_engine.py:test_execution_engine_update_nonexistent_task]
""" """
ticket = self.dag.ticket_map.get(task_id) ticket = self.dag.ticket_map.get(task_id)
if ticket: if ticket:
+1 -4
View File
@@ -41,10 +41,7 @@ class EventEmitter:
"""Simple event emitter for decoupled communication between modules.""" """Simple event emitter for decoupled communication between modules."""
def __init__(self) -> None: def __init__(self) -> None:
""" """Initializes the EventEmitter with an empty listener map."""
Initializes the EventEmitter with an empty listener map.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._listeners: Dict[str, List[Callable[..., Any]]] = {} self._listeners: Dict[str, List[Callable[..., Any]]] = {}
def on(self, event_name: str, callback: Callable[..., Any]) -> None: def on(self, event_name: str, callback: Callable[..., Any]) -> None:
+2 -18
View File
@@ -68,30 +68,18 @@ EMPTY_TEXT_EDITOR_CONFIG: TextEditorConfig = TextEditorConfig()
class ExternalEditorLauncher: class ExternalEditorLauncher:
def __init__(self, config: ExternalEditorConfig): def __init__(self, config: ExternalEditorConfig):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.config = config self.config = config
def get_editor(self, editor_name: Optional[str] = None) -> TextEditorConfig: def get_editor(self, editor_name: Optional[str] = None) -> TextEditorConfig:
"""
[C: tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_by_name, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_returns_default, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_unknown_name]
"""
if editor_name: if editor_name:
return self.config.editors.get(editor_name) or EMPTY_TEXT_EDITOR_CONFIG return self.config.editors.get(editor_name) or EMPTY_TEXT_EDITOR_CONFIG
return self.config.get_default() return self.config.get_default()
def build_diff_command(self, editor: TextEditorConfig, original_path: str, modified_path: str) -> List[str]: def build_diff_command(self, editor: TextEditorConfig, original_path: str, modified_path: str) -> List[str]:
"""
[C: tests/test_external_editor.py:TestExternalEditorLauncher.test_build_diff_command, tests/test_external_editor_gui.py:test_verify_command_format, tests/test_external_editor_gui.py:test_verify_vscode_command_format]
"""
cmd = [editor.path] + editor.diff_args + [original_path, modified_path] cmd = [editor.path] + editor.diff_args + [original_path, modified_path]
return cmd return cmd
def launch_diff_result(self, editor_name: Optional[str], original_path: str, modified_path: str) -> Result[subprocess.Popen]: def launch_diff_result(self, editor_name: Optional[str], original_path: str, modified_path: str) -> Result[subprocess.Popen]:
"""
[C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_file_not_found, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_missing_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_success]
"""
editor = self.get_editor(editor_name) editor = self.get_editor(editor_name)
if not editor.name or not editor.path: if not editor.name or not editor.path:
return Result(data=None, errors=[ErrorInfo(kind=ErrorKind.NOT_FOUND, message=f"No editor configured: {editor_name}", source="external_editor.launch_diff_result")]) return Result(data=None, errors=[ErrorInfo(kind=ErrorKind.NOT_FOUND, message=f"No editor configured: {editor_name}", source="external_editor.launch_diff_result")])
@@ -118,8 +106,8 @@ def _find_vscode_in_registry() -> Result[Optional[str]]:
for key in reg_keys: for key in reg_keys:
try: try:
result = subprocess.run( result = subprocess.run(
["powershell", "-Command", f"Get-ItemProperty -Path '{key}' -ErrorAction SilentlyContinue | Where-Object {{ $_.DisplayName -like '*Visual Studio Code*' }} | Select-Object -ExpandProperty InstallLocation"], ["powershell", "-Command", f"Get-ItemProperty -Path '{key}' -ErrorAction SilentlyContinue | Where-Object {{ $_.DisplayName -like '*Visual Studio Code*' }} | Select-Object -ExpandProperty InstallLocation"],
capture_output=True, text=True, timeout=5 capture_output=True, text=True, timeout=5
) )
for line in result.stdout.strip().split('\n'): for line in result.stdout.strip().split('\n'):
line = line.strip() line = line.strip()
@@ -171,7 +159,6 @@ def get_default_launcher(config: Optional[Dict[str, Any]] = None) -> ExternalEdi
AppController). Direct file I/O (the legacy public functions on src.models) is an AppController). Direct file I/O (the legacy public functions on src.models) is an
architectural smell (bypasses the controller state owner) and is architectural smell (bypasses the controller state owner) and is
forbidden by scripts/audit_no_models_config_io.py. forbidden by scripts/audit_no_models_config_io.py.
[C: src/gui_2.py:App._open_patch_in_external_editor, src/gui_2.py:App._render_external_editor_panel]
""" """
editors_config = config.get("tools", {}).get("text_editors", {}) if config else {} editors_config = config.get("tools", {}).get("text_editors", {}) if config else {}
default_editor = config.get("tools", {}).get("default_editor", {}).get("default_editor") if config else None default_editor = config.get("tools", {}).get("default_editor", {}).get("default_editor") if config else None
@@ -193,9 +180,6 @@ def get_default_launcher(config: Optional[Dict[str, Any]] = None) -> ExternalEdi
def create_temp_modified_file(content: str) -> str: def create_temp_modified_file(content: str) -> str:
"""
[C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestHelperFunctions.test_create_temp_modified_file]
"""
with tempfile.NamedTemporaryFile(mode="w", suffix="_modified", delete=False, encoding="utf-8") as f: with tempfile.NamedTemporaryFile(mode="w", suffix="_modified", delete=False, encoding="utf-8") as f:
f.write(content) f.write(content)
return f.name return f.name
+5 -45
View File
@@ -76,9 +76,6 @@ class ASTParser:
#region: Core Operations #region: Core Operations
def __init__(self, language: str) -> None: def __init__(self, language: str) -> None:
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
if language not in ("python", "cpp", "c"): if language not in ("python", "cpp", "c"):
raise ValueError(f"Language '{language}' not supported yet.") raise ValueError(f"Language '{language}' not supported yet.")
self.language_name = language self.language_name = language
@@ -91,10 +88,7 @@ class ASTParser:
self.parser = ts.Parser(self.language) self.parser = ts.Parser(self.language)
def parse(self, code: str) -> tree_sitter.Tree: def parse(self, code: str) -> tree_sitter.Tree:
""" """Parse the given code and return the tree-sitter Tree."""
Parse the given code and return the tree-sitter Tree.
[C: src/mcp_client.py:_search_file, src/mcp_client.py:derive_code_path, src/mcp_client.py:py_check_syntax, src/mcp_client.py:py_get_class_summary, src/mcp_client.py:py_get_definition, src/mcp_client.py:py_get_docstring, src/mcp_client.py:py_get_imports, src/mcp_client.py:py_get_signature, src/mcp_client.py:py_get_symbol_info, src/mcp_client.py:py_get_var_declaration, src/mcp_client.py:py_set_signature, src/mcp_client.py:py_set_var_declaration, src/mcp_client.py:py_update_definition, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/rag_engine.py:RAGEngine._chunk_code, src/summarize.py:_summarise_python, tests/test_ast_parser.py:test_ast_parser_parse, tests/test_tree_sitter_setup.py:test_tree_sitter_python_setup]
"""
return self.parser.parse(bytes(code, "utf8")) return self.parser.parse(bytes(code, "utf8"))
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree: def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
@@ -205,10 +199,7 @@ class ASTParser:
#region: Skeleton & Curated Views #region: Skeleton & Curated Views
def get_skeleton(self, code: str, path: Optional[str] = None) -> str: def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
""" """Returns a skeleton of a Python file (preserving docstrings, stripping function bodies)."""
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
[C: src/mcp_client.py:py_get_skeleton, src/mcp_client.py:ts_c_get_skeleton, src/mcp_client.py:ts_cpp_get_skeleton, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ast_parser.py:test_ast_parser_get_skeleton_c, tests/test_ast_parser.py:test_ast_parser_get_skeleton_cpp, tests/test_ast_parser.py:test_ast_parser_get_skeleton_python, tests/test_context_pruner.py:test_ast_caching, tests/test_context_pruner.py:test_performance_large_file]
"""
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
edits: List[Tuple[int, int, str]] = [] edits: List[Tuple[int, int, str]] = []
@@ -220,9 +211,6 @@ class ASTParser:
return False return False
def walk(node: tree_sitter.Node) -> None: def walk(node: tree_sitter.Node) -> None:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if node.type in ("function_definition", "method_definition"): if node.type in ("function_definition", "method_definition"):
body = node.child_by_field_name("body") body = node.child_by_field_name("body")
if not body: if not body:
@@ -293,7 +281,6 @@ class ASTParser:
Returns a curated skeleton of a Python file. Returns a curated skeleton of a Python file.
Preserves function bodies if they have @core_logic decorator or # [HOT] comment. Preserves function bodies if they have @core_logic decorator or # [HOT] comment.
Otherwise strips bodies but preserves docstrings. Otherwise strips bodies but preserves docstrings.
[C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ast_parser.py:test_ast_parser_get_curated_view]
""" """
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
@@ -330,9 +317,6 @@ class ASTParser:
return False return False
def walk(node: tree_sitter.Node) -> None: def walk(node: tree_sitter.Node) -> None:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if node.type == "function_definition": if node.type == "function_definition":
body = node.child_by_field_name("body") body = node.child_by_field_name("body")
if body and body.type in ("block", "compound_statement"): if body and body.type in ("block", "compound_statement"):
@@ -369,11 +353,7 @@ class ASTParser:
#region: Targeted Views #region: Targeted Views
def get_targeted_view(self, code: str, function_names: List[str], path: Optional[str] = None) -> str: def get_targeted_view(self, code: str, function_names: List[str], path: Optional[str] = None) -> str:
""" """Returns a targeted view of the code including only the specified functions and their dependencies up to depth 2."""
Returns a targeted view of the code including only the specified functions
and their dependencies up to depth 2.
[C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ast_parser.py:test_ast_parser_get_targeted_view, tests/test_context_pruner.py:test_class_targeted_extraction, tests/test_context_pruner.py:test_targeted_extraction]
"""
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
all_functions = {} all_functions = {}
@@ -539,7 +519,6 @@ class ASTParser:
""" """
Returns the full source code for a specific definition by name. Returns the full source code for a specific definition by name.
Supports 'ClassName::method' or 'method' for C++. Supports 'ClassName::method' or 'method' for C++.
[C: src/mcp_client.py:trace, src/mcp_client.py:ts_c_get_definition, src/mcp_client.py:ts_cpp_get_definition, tests/test_ast_parser.py:test_ast_parser_get_definition_c, tests/test_ast_parser.py:test_ast_parser_get_definition_cpp, tests/test_ast_parser.py:test_ast_parser_get_definition_cpp_template]
""" """
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
@@ -547,9 +526,6 @@ class ASTParser:
parts = re.split(r'::|\.', name) parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node: def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if not target_parts: if not target_parts:
return node return node
target = target_parts[0] target = target_parts[0]
@@ -637,16 +613,12 @@ class ASTParser:
""" """
Returns only the signature part of a function or method. Returns only the signature part of a function or method.
For C/C++, this is the code from the start of the definition until the block start '{'. For C/C++, this is the code from the start of the definition until the block start '{'.
[C: src/mcp_client.py:ts_c_get_signature, src/mcp_client.py:ts_cpp_get_signature, tests/test_ast_parser.py:test_ast_parser_get_signature_c, tests/test_ast_parser.py:test_ast_parser_get_signature_cpp]
""" """
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
parts = re.split(r'::|\.', name) parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node: def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if not target_parts: if not target_parts:
return node return node
target = target_parts[0] target = target_parts[0]
@@ -746,18 +718,12 @@ class ASTParser:
#region: Analysis & Updates #region: Analysis & Updates
def get_code_outline(self, code: str, path: Optional[str] = None) -> str: def get_code_outline(self, code: str, path: Optional[str] = None) -> str:
""" """Returns a hierarchical outline of the code (classes, structs, functions, methods)."""
Returns a hierarchical outline of the code (classes, structs, functions, methods).
[C: src/mcp_client.py:ts_c_get_code_outline, src/mcp_client.py:ts_cpp_get_code_outline, tests/test_ast_parser.py:test_ast_parser_get_code_outline_c, tests/test_ast_parser.py:test_ast_parser_get_code_outline_cpp]
"""
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
output = [] output = []
def walk(node: tree_sitter.Node, indent: int = 0) -> None: def walk(node: tree_sitter.Node, indent: int = 0) -> None:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
ntype = node.type ntype = node.type
label = "" label = ""
if ntype in ("class_definition", "class_specifier"): if ntype in ("class_definition", "class_specifier"):
@@ -788,18 +754,12 @@ class ASTParser:
return "\n".join(output) return "\n".join(output)
def update_definition(self, code: str, name: str, new_content: str, path: Optional[str] = None) -> str: def update_definition(self, code: str, name: str, new_content: str, path: Optional[str] = None) -> str:
""" """Surgically replace the definition of a class or function by name."""
Surgically replace the definition of a class or function by name.
[C: src/mcp_client.py:ts_c_update_definition, src/mcp_client.py:ts_cpp_update_definition, tests/test_ast_parser.py:test_ast_parser_update_definition_cpp]
"""
code_bytes = code.encode("utf8") code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code) tree = self.get_cached_tree(path, code)
parts = re.split(r'::|\.', name) parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node: def walk(node: tree_sitter.Node, target_parts: List[str]) -> tree_sitter.Node:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if not target_parts: if not target_parts:
return node return node
target = target_parts[0] target = target_parts[0]
+1 -4
View File
@@ -18,10 +18,7 @@ class FuzzyAnchor:
@classmethod @classmethod
def create_slice(cls, text: str, start_line: int, end_line: int) -> dict: def create_slice(cls, text: str, start_line: int, end_line: int) -> dict:
""" """start_line and end_line are 1-based."""
start_line and end_line are 1-based.
[C: src/gui_2.py:App._populate_auto_slices, src/gui_2.py:App._render_text_viewer_window, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_create_slice_basic, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_anchor_mismatch_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_exact_match, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_deleted_before_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_inserted_before, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_multiple_lines_changed, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations]
"""
lines = text.splitlines() lines = text.splitlines()
s_idx = max(0, start_line - 1) s_idx = max(0, start_line - 1)
e_idx = min(len(lines), end_line) e_idx = min(len(lines), end_line)
+1 -6
View File
@@ -49,10 +49,7 @@ class GeminiCliAdapter:
Adapter for the Gemini CLI that parses streaming JSON output. Adapter for the Gemini CLI that parses streaming JSON output.
""" """
def __init__(self, binary_path: str = "gemini"): def __init__(self, binary_path: str = "gemini"):
""" """Initializes the adapter with the path to the gemini CLI executable."""
Initializes the adapter with the path to the gemini CLI executable.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.binary_path = binary_path self.binary_path = binary_path
self.session_id: Optional[str] = None self.session_id: Optional[str] = None
self.last_usage: Optional[dict[str, Any]] = None self.last_usage: Optional[dict[str, Any]] = None
@@ -62,7 +59,6 @@ class GeminiCliAdapter:
""" """
Sends a message to the Gemini CLI and processes the streaming JSON output. Sends a message to the Gemini CLI and processes the streaming JSON output.
Uses non-blocking line-by-line reading to allow stream_callback. Uses non-blocking line-by-line reading to allow stream_callback.
[C: simulation/user_agent.py:UserSimAgent.generate_response, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_completion_logic, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoning_logic, tests/test_deepseek_provider.py:test_deepseek_streaming, tests/test_deepseek_provider.py:test_deepseek_tool_calling, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_full_flow_integration, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_captures_usage_metadata, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_handles_tool_use_events, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_parses_jsonl_output, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_starts_subprocess_with_correct_args, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_parses_tool_calls_from_streaming_json, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_starts_subprocess_with_model, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_context_bleed_prevention, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_loop_termination, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_cli_parity_regression.py:test_send_invokes_adapter_send, tests/test_gui2_mcp.py:test_mcp_tool_call_is_dispatched, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_token_usage.py:test_token_usage_tracking, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
""" """
start_time = time.time() start_time = time.time()
command_parts = [self.binary_path] command_parts = [self.binary_path]
@@ -192,7 +188,6 @@ class GeminiCliAdapter:
""" """
Provides a character-based token estimation for the Gemini CLI. Provides a character-based token estimation for the Gemini CLI.
Uses 4 chars/token as a conservative average. Uses 4 chars/token as a conservative average.
[C: tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_count_tokens_fallback]
""" """
total_chars = len("\n".join(contents)) total_chars = len("\n".join(contents))
return total_chars // 4 return total_chars // 4
+2 -22
View File
@@ -3,7 +3,6 @@ import typing
from dataclasses import dataclass, field from dataclasses import dataclass, field
@dataclass @dataclass
class UISnapshot: class UISnapshot:
"""Capture of restorable UI state.""" """Capture of restorable UI state."""
@@ -22,9 +21,6 @@ class UISnapshot:
screenshots: list[str] screenshots: list[str]
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""
[C: src/models.py:ContextPreset.to_dict, src/models.py:ExternalEditorConfig.to_dict, src/models.py:MCPConfiguration.to_dict, src/models.py:RAGConfig.to_dict, src/models.py:ToolPreset.to_dict, src/models.py:Track.to_dict, src/models.py:TrackState.to_dict, 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 { return {
"ai_input": self.ai_input, "ai_input": self.ai_input,
"project_system_prompt": self.project_system_prompt, "project_system_prompt": self.project_system_prompt,
@@ -43,9 +39,6 @@ class UISnapshot:
@classmethod @classmethod
def from_dict(cls, data: dict) -> "UISnapshot": def from_dict(cls, data: dict) -> "UISnapshot":
"""
[C: src/models.py:ContextPreset.from_dict, src/models.py:ExternalEditorConfig.from_dict, src/models.py:MCPConfiguration.from_dict, src/models.py:RAGConfig.from_dict, src/models.py:ToolPreset.from_dict, src/models.py:Track.from_dict, src/models.py:TrackState.from_dict, src/models.py:load_mcp_config, 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( return cls(
ai_input = data.get("ai_input", ""), ai_input = data.get("ai_input", ""),
project_system_prompt = data.get("project_system_prompt", ""), project_system_prompt = data.get("project_system_prompt", ""),
@@ -70,9 +63,6 @@ class HistoryEntry:
class HistoryManager: class HistoryManager:
def __init__(self, max_capacity: int = 100): def __init__(self, max_capacity: int = 100):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.max_capacity = max_capacity self.max_capacity = max_capacity
self._undo_stack: typing.List[HistoryEntry] = [] self._undo_stack: typing.List[HistoryEntry] = []
self._redo_stack: typing.List[HistoryEntry] = [] self._redo_stack: typing.List[HistoryEntry] = []
@@ -81,7 +71,6 @@ class HistoryManager:
""" """
Pushes a new state to the undo stack and clears the redo stack. Pushes a new state to the undo stack and clears the redo stack.
If the undo stack exceeds max_capacity, the oldest state is removed. If the undo stack exceeds max_capacity, the oldest state is removed.
[C: tests/test_history.py:test_jump_to_undo, tests/test_history.py:test_max_capacity, tests/test_history.py:test_push_state, tests/test_history.py:test_redo_cleared_on_push, tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions, tests/test_history_manager.py:TestHistoryManager.test_jump_to_undo, tests/test_history_manager.py:TestHistoryManager.test_push_and_undo, tests/test_history_manager.py:TestHistoryManager.test_push_clears_redo_stack, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo]
""" """
entry = HistoryEntry(state=state, description=description) entry = HistoryEntry(state=state, description=description)
self._undo_stack.append(entry) self._undo_stack.append(entry)
@@ -93,7 +82,6 @@ class HistoryManager:
""" """
Undoes the last action by moving the current_state to the redo stack Undoes the last action by moving the current_state to the redo stack
and returning the top of the undo stack. and returning the top of the undo stack.
[C: tests/test_history.py:test_redo_cleared_on_push, tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_push_and_undo, tests/test_history_manager.py:TestHistoryManager.test_push_clears_redo_stack, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo, tests/test_history_manager.py:TestHistoryManager.test_undo_no_history_returns_none]
""" """
if not self._undo_stack: return None if not self._undo_stack: return None
redo_entry = HistoryEntry(state=current_state, description=current_description) redo_entry = HistoryEntry(state=current_state, description=current_description)
@@ -104,7 +92,6 @@ class HistoryManager:
""" """
Redoes the last undone action by moving the current_state to the undo stack Redoes the last undone action by moving the current_state to the undo stack
and returning the top of the redo stack. and returning the top of the redo stack.
[C: tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_redo_no_history_returns_none, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo]
""" """
if not self._redo_stack: return None if not self._redo_stack: return None
undo_entry = HistoryEntry(state=current_state, description=current_description) undo_entry = HistoryEntry(state=current_state, description=current_description)
@@ -117,21 +104,14 @@ class HistoryManager:
def can_redo(self) -> bool: return len(self._redo_stack) > 0 def can_redo(self) -> bool: return len(self._redo_stack) > 0
def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]: def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]:
""" """Returns a list of descriptions and timestamps for the undo stack."""
Returns a list of descriptions and timestamps for the undo stack.
[C: tests/test_history.py:test_initial_state, tests/test_history.py:test_push_state, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions]
"""
return [ return [
{"description": e.description, "timestamp": e.timestamp} {"description": e.description, "timestamp": e.timestamp}
for e in self._undo_stack for e in self._undo_stack
] ]
def jump_to_undo(self, index: int, current_state: typing.Any, current_description: str = "Before Jump") -> typing.Optional[HistoryEntry]: def jump_to_undo(self, index: int, current_state: typing.Any, current_description: str = "Before Jump") -> typing.Optional[HistoryEntry]:
""" """Jumps to a specific state in the undo stack by moving subsequent states and the current_state to the redo stack."""
Jumps to a specific state in the undo stack by moving subsequent states
and the current_state to the redo stack.
[C: tests/test_history.py:test_jump_to_undo, tests/test_history_manager.py:TestHistoryManager.test_jump_to_undo]
"""
if index < 0 or index >= len(self._undo_stack): return None if index < 0 or index >= len(self._undo_stack): return None
# Move current state to redo # Move current state to redo
self._redo_stack.append(HistoryEntry(state=current_state, description=current_description)) self._redo_stack.append(HistoryEntry(state=current_state, description=current_description))
-45
View File
@@ -9,9 +9,6 @@ from imgui_bundle import imgui_node_editor
def child(id_str: str, size_x: float = 0, size_y: float = 0, flags: int = 0): return _ScopeChild(id_str, size_x, size_y, flags) def child(id_str: str, size_x: float = 0, size_y: float = 0, flags: int = 0): return _ScopeChild(id_str, size_x, size_y, flags)
class _ScopeChild: class _ScopeChild:
def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int): def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._id = id_str self._id = id_str
# Check if size_x is likely an ImVec2 without using isinstance (which breaks with mocks) # Check if size_x is likely an ImVec2 without using isinstance (which breaks with mocks)
if hasattr(size_x, 'x') and hasattr(size_x, 'y'): if hasattr(size_x, 'x') and hasattr(size_x, 'y'):
@@ -37,9 +34,6 @@ class _ScopeGroup:
def id(str_id: str): return _ScopeId(str_id) def id(str_id: str): return _ScopeId(str_id)
class _ScopeId: class _ScopeId:
def __init__(self, str_id: str): def __init__(self, str_id: str):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._id = str_id self._id = str_id
def __enter__(self): def __enter__(self):
# Use explicit conversion to avoid any possible nanobind ambiguity # Use explicit conversion to avoid any possible nanobind ambiguity
@@ -62,9 +56,6 @@ class _ScopeIndent:
def menu(label: str): return _ScopeMenu(label) def menu(label: str): return _ScopeMenu(label)
class _ScopeMenu: class _ScopeMenu:
def __init__(self, label: str): def __init__(self, label: str):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._label = label self._label = label
self._active = False self._active = False
def __enter__(self): def __enter__(self):
@@ -78,9 +69,6 @@ class _ScopeMenu:
def menu_bar(): return _ScopeMenuBar() def menu_bar(): return _ScopeMenuBar()
class _ScopeMenuBar: class _ScopeMenuBar:
def __init__(self): def __init__(self):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._active = False self._active = False
def __enter__(self): def __enter__(self):
self._active = imgui.begin_menu_bar() self._active = imgui.begin_menu_bar()
@@ -93,9 +81,6 @@ class _ScopeMenuBar:
def node(name: str): return _ScopeNode(name) def node(name: str): return _ScopeNode(name)
class _ScopeNode: class _ScopeNode:
def __init__(self, name: str): def __init__(self, name: str):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._name = name self._name = name
def __enter__(self): def __enter__(self):
return imgui_node_editor.begin(self._name) return imgui_node_editor.begin(self._name)
@@ -106,9 +91,6 @@ class _ScopeNode:
def popup(id_str: str): return _ScopePopup(id_str) def popup(id_str: str): return _ScopePopup(id_str)
class _ScopePopup: class _ScopePopup:
def __init__(self, id_str: str): def __init__(self, id_str: str):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._id = id_str self._id = id_str
self._active = False self._active = False
def __enter__(self): def __enter__(self):
@@ -122,9 +104,6 @@ class _ScopePopup:
def popup_modal(name: str, visible: bool = True, flags: int = 0): return _ScopePopupModal(name, visible, flags) def popup_modal(name: str, visible: bool = True, flags: int = 0): return _ScopePopupModal(name, visible, flags)
class _ScopePopupModal: class _ScopePopupModal:
def __init__(self, name: str, visible: bool, flags: int): def __init__(self, name: str, visible: bool, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._name = name self._name = name
self._visible = visible self._visible = visible
self._flags = flags self._flags = flags
@@ -141,9 +120,6 @@ class _ScopePopupModal:
def style_color(col: int, val: Any): return _ScopeStyleColor(col, val) def style_color(col: int, val: Any): return _ScopeStyleColor(col, val)
class _ScopeStyleColor: class _ScopeStyleColor:
def __init__(self, col: int, val: Any): def __init__(self, col: int, val: Any):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._col = col self._col = col
self._val = val self._val = val
def __enter__(self): def __enter__(self):
@@ -155,9 +131,6 @@ class _ScopeStyleColor:
def style_var(var: int, val: Any): return _ScopeStyleVar(var, val) def style_var(var: int, val: Any): return _ScopeStyleVar(var, val)
class _ScopeStyleVar: class _ScopeStyleVar:
def __init__(self, var: int, val: Any): def __init__(self, var: int, val: Any):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._var = var self._var = var
self._val = val self._val = val
def __enter__(self): def __enter__(self):
@@ -169,9 +142,6 @@ class _ScopeStyleVar:
def table(name: str, columns: int, flags: int = 0): return _ScopeTable(name, columns, flags) def table(name: str, columns: int, flags: int = 0): return _ScopeTable(name, columns, flags)
class _ScopeTable: class _ScopeTable:
def __init__(self, name: str, columns: int, flags: int): def __init__(self, name: str, columns: int, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._name = name self._name = name
self._columns = columns self._columns = columns
self._flags = flags self._flags = flags
@@ -187,9 +157,6 @@ class _ScopeTable:
def tab_bar(id_str: str, flags: int = 0): return _ScopeTabBar(id_str, flags) def tab_bar(id_str: str, flags: int = 0): return _ScopeTabBar(id_str, flags)
class _ScopeTabBar: class _ScopeTabBar:
def __init__(self, id_str: str, flags: int): def __init__(self, id_str: str, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._id = id_str self._id = id_str
self._flags = flags self._flags = flags
self._active = False self._active = False
@@ -204,9 +171,6 @@ class _ScopeTabBar:
def tab_item(label: str, flags: int = 0): return _ScopeTabItem(label, flags) def tab_item(label: str, flags: int = 0): return _ScopeTabItem(label, flags)
class _ScopeTabItem: class _ScopeTabItem:
def __init__(self, label: str, flags: int): def __init__(self, label: str, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._label = label self._label = label
self._flags = flags self._flags = flags
self._expanded = False self._expanded = False
@@ -222,9 +186,6 @@ class _ScopeTabItem:
def text_wrap(wrap_pos: float = 0.0): return _ScopeTextWrap(wrap_pos) def text_wrap(wrap_pos: float = 0.0): return _ScopeTextWrap(wrap_pos)
class _ScopeTextWrap: class _ScopeTextWrap:
def __init__(self, wrap_pos: float): def __init__(self, wrap_pos: float):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._wrap_pos = wrap_pos self._wrap_pos = wrap_pos
def __enter__(self): def __enter__(self):
imgui.push_text_wrap_pos(self._wrap_pos) imgui.push_text_wrap_pos(self._wrap_pos)
@@ -243,9 +204,6 @@ class _ScopeTooltip:
def tree_node_ex(label: str, flags: int = 0): return _ScopeTreeNodeEx(label, flags) def tree_node_ex(label: str, flags: int = 0): return _ScopeTreeNodeEx(label, flags)
class _ScopeTreeNodeEx: class _ScopeTreeNodeEx:
def __init__(self, label: str, flags: int): def __init__(self, label: str, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._label = label self._label = label
self._flags = flags self._flags = flags
self._opened = False self._opened = False
@@ -260,9 +218,6 @@ class _ScopeTreeNodeEx:
def window(name: str, visible: bool = True, flags: int = 0): return _ScopeWindow(name, visible, flags) def window(name: str, visible: bool = True, flags: int = 0): return _ScopeWindow(name, visible, flags)
class _ScopeWindow: class _ScopeWindow:
def __init__(self, name: str, visible: bool, flags: int): def __init__(self, name: str, visible: bool, flags: int):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self._name = name self._name = name
self._visible = visible self._visible = visible
self._flags = flags self._flags = flags
+4 -8
View File
@@ -15,8 +15,7 @@ class LayoutFile:
intended to be installed to cwd/manualslop_layout.ini by App._post_init intended to be installed to cwd/manualslop_layout.ini by App._post_init
when the user's INI is empty or missing. The raw text is opaque to when the user's INI is empty or missing. The raw text is opaque to
this module; structure parsing (ImGui sections, [Window] entries) this module; structure parsing (ImGui sections, [Window] entries)
is the consumer's responsibility. is the consumer's responsibility."""
[C: src/layouts.py:load_layouts_from_dir, src/gui_2.py:_install_default_layout_if_empty]"""
name: str name: str
raw_text: str raw_text: str
source_path: Path source_path: Path
@@ -25,8 +24,7 @@ class LayoutFile:
def load_layouts_from_file(path: Path, scope: str) -> dict[str, LayoutFile]: def load_layouts_from_file(path: Path, scope: str) -> dict[str, LayoutFile]:
"""Load ONE layout file and return a 1-entry dict. Public API matches """Load ONE layout file and return a 1-entry dict. Public API matches
themes' load_themes_from_toml shape (dict out). themes' load_themes_from_toml shape (dict out)."""
[C: src/layouts.py:load_layouts_from_dir]"""
out: dict[str, LayoutFile] = {} out: dict[str, LayoutFile] = {}
if not path.exists() or not path.is_file(): if not path.exists() or not path.is_file():
return out return out
@@ -45,8 +43,7 @@ def load_layouts_from_dir(path: Path, scope: str) -> dict[str, LayoutFile]:
or all-skip (per the 'delete to turn off' pattern in or all-skip (per the 'delete to turn off' pattern in
conductor/code_styleguides/feature_flags.md). Per-file errors are conductor/code_styleguides/feature_flags.md). Per-file errors are
drained via Result + stderr warn, mirroring drained via Result + stderr warn, mirroring
src/theme_models.py:load_themes_from_dir. src/theme_models.py:load_themes_from_dir."""
[C: src/layouts.py:load_layouts_from_disk]"""
out: dict[str, LayoutFile] = {} out: dict[str, LayoutFile] = {}
if not path.exists() or not path.is_dir(): if not path.exists() or not path.is_dir():
return out return out
@@ -73,8 +70,7 @@ def load_layouts_from_disk() -> dict[str, LayoutFile]:
"""Load all bundled layouts from the global layouts dir """Load all bundled layouts from the global layouts dir
(resolved via src/paths.py:get_layouts_dir()). Cached at the (resolved via src/paths.py:get_layouts_dir()). Cached at the
module level; callers iterate the returned dict. The 'delete to module level; callers iterate the returned dict. The 'delete to
turn off' guarantee: a missing or empty layouts/ dir returns {}. turn off' guarantee: a missing or empty layouts/ dir returns {}."""
[C: src/gui_2.py:_install_default_layout_if_empty]"""
global _LAYOUTS_CACHE global _LAYOUTS_CACHE
layouts_dir = get_layouts_dir() layouts_dir = get_layouts_dir()
_LAYOUTS_CACHE = load_layouts_from_dir(layouts_dir, scope="global") _LAYOUTS_CACHE = load_layouts_from_dir(layouts_dir, scope="global")
-2
View File
@@ -22,7 +22,6 @@ class LogPruner:
Args: Args:
log_registry: An instance of LogRegistry to check session data. log_registry: An instance of LogRegistry to check session data.
logs_dir: The path to the directory containing session sub-directories. logs_dir: The path to the directory containing session sub-directories.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
""" """
self.log_registry = log_registry self.log_registry = log_registry
self.logs_dir = logs_dir self.logs_dir = logs_dir
@@ -35,7 +34,6 @@ class LogPruner:
1. The session start time is older than max_age_days. 1. The session start time is older than max_age_days.
2. The session name is NOT in the whitelist provided by the LogRegistry. 2. The session name is NOT in the whitelist provided by the LogRegistry.
3. The total size of all files within the session directory is less than min_size_kb. 3. The total size of all files within the session directory is less than min_size_kb.
[C: tests/test_log_pruner.py:test_prune_old_insignificant_logs, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_handles_relative_paths_starting_with_logs, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_removes_empty_sessions_regardless_of_age, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_removes_sessions_without_metadata_regardless_of_age, tests/test_logging_e2e.py:test_logging_e2e]
""" """
now = datetime.now() now = datetime.now()
cutoff_time = now - timedelta(days=max_age_days) cutoff_time = now - timedelta(days=max_age_days)
-8
View File
@@ -145,7 +145,6 @@ class LogRegistry:
Args: Args:
registry_path (str): The file path to the TOML registry. registry_path (str): The file path to the TOML registry.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
""" """
self.registry_path = registry_path self.registry_path = registry_path
self.data: dict[str, Session] = {} self.data: dict[str, Session] = {}
@@ -188,7 +187,6 @@ class LogRegistry:
""" """
Serializes and saves the current registry data to the TOML file. Serializes and saves the current registry data to the TOML file.
Converts internal datetime objects to ISO format strings for compatibility. Converts internal datetime objects to ISO format strings for compatibility.
[C: tests/test_logging_e2e.py:test_logging_e2e]
""" """
try: try:
# Convert datetime objects to ISO format strings for TOML serialization # Convert datetime objects to ISO format strings for TOML serialization
@@ -228,7 +226,6 @@ class LogRegistry:
session_id (str): Unique identifier for the session. session_id (str): Unique identifier for the session.
path (str): File path to the session's log directory. path (str): File path to the session's log directory.
start_time (datetime|str): The timestamp when the session started. start_time (datetime|str): The timestamp when the session started.
[C: src/session_logger.py:open_session, tests/test_auto_whitelist.py:test_auto_whitelist_keywords, tests/test_auto_whitelist.py:test_auto_whitelist_large_size, tests/test_auto_whitelist.py:test_auto_whitelist_message_count, tests/test_auto_whitelist.py:test_no_auto_whitelist_insignificant, tests/test_log_pruner.py:test_prune_old_insignificant_logs, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_get_old_non_whitelisted_sessions_includes_empty_sessions, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_get_old_non_whitelisted_sessions_includes_sessions_without_metadata, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_handles_relative_paths_starting_with_logs, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_removes_empty_sessions_regardless_of_age, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_removes_sessions_without_metadata_regardless_of_age, tests/test_log_registry.py:TestLogRegistry.test_get_old_non_whitelisted_sessions, tests/test_log_registry.py:TestLogRegistry.test_is_session_whitelisted, tests/test_log_registry.py:TestLogRegistry.test_register_session, tests/test_log_registry.py:TestLogRegistry.test_update_session_metadata, tests/test_logging_e2e.py:test_logging_e2e]
""" """
if session_id in self.data: if session_id in self.data:
print(f"Warning: Session ID '{session_id}' already exists. Overwriting.") print(f"Warning: Session ID '{session_id}' already exists. Overwriting.")
@@ -257,7 +254,6 @@ class LogRegistry:
size_kb (int): Total size of the session logs in kilobytes. size_kb (int): Total size of the session logs in kilobytes.
whitelisted (bool): Whether the session should be protected from pruning. whitelisted (bool): Whether the session should be protected from pruning.
reason (str): Explanation for the current whitelisting status. reason (str): Explanation for the current whitelisting status.
[C: tests/test_auto_whitelist.py:test_auto_whitelist_large_size, tests/test_auto_whitelist.py:test_auto_whitelist_message_count, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_get_old_non_whitelisted_sessions_includes_empty_sessions, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_prune_removes_empty_sessions_regardless_of_age, tests/test_log_registry.py:TestLogRegistry.test_get_old_non_whitelisted_sessions, tests/test_log_registry.py:TestLogRegistry.test_is_session_whitelisted, tests/test_log_registry.py:TestLogRegistry.test_update_session_metadata]
""" """
if session_id not in self.data: if session_id not in self.data:
print(f"Error: Session ID '{session_id}' not found for metadata update.") print(f"Error: Session ID '{session_id}' not found for metadata update.")
@@ -291,7 +287,6 @@ class LogRegistry:
Args: Args:
session_id (str): Unique identifier for the session. session_id (str): Unique identifier for the session.
start_time (datetime|str): The new start timestamp. start_time (datetime|str): The new start timestamp.
[C: tests/test_logging_e2e.py:test_logging_e2e]
""" """
if session_id not in self.data: if session_id not in self.data:
print(f"Error: Session ID '{session_id}' not found for start_time update.") print(f"Error: Session ID '{session_id}' not found for start_time update.")
@@ -319,7 +314,6 @@ class LogRegistry:
Returns: Returns:
bool: True if whitelisted, False otherwise. bool: True if whitelisted, False otherwise.
[C: tests/test_auto_whitelist.py:test_auto_whitelist_keywords, tests/test_auto_whitelist.py:test_auto_whitelist_large_size, tests/test_auto_whitelist.py:test_auto_whitelist_message_count, tests/test_no_auto_whitelist_insignificant, tests/test_log_registry.py:TestLogRegistry.test_is_session_whitelisted, tests/test_logging_e2e.py:test_logging_e2e]
""" """
session = self.data.get(session_id) session = self.data.get(session_id)
if session is None: if session is None:
@@ -334,7 +328,6 @@ class LogRegistry:
Args: Args:
session_id (str): Unique identifier for the session to analyze. session_id (str): Unique identifier for the session to analyze.
[C: src/session_logger.py:close_session]
""" """
if session_id not in self.data: if session_id not in self.data:
return return
@@ -398,7 +391,6 @@ class LogRegistry:
Returns: Returns:
list: A list of dictionaries containing session details (id, path, start_time). list: A list of dictionaries containing session details (id, path, start_time).
[C: tests/test_log_pruner.py:test_prune_old_insignificant_logs, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_get_old_non_whitelisted_sessions_includes_empty_sessions, tests/test_log_pruning_heuristic.py:TestLogPruningHeuristic.test_get_old_non_whitelisted_sessions_includes_sessions_without_metadata, tests/test_log_registry.py:TestLogRegistry.test_get_old_non_whitelisted_sessions]
""" """
old_sessions = [] old_sessions = []
for session_id, session in self.data.items(): for session_id, session in self.data.items():
+1 -13
View File
@@ -64,9 +64,6 @@ class MarkdownRenderer:
and ImGuiColorTextEdit for syntax-highlighted code blocks. and ImGuiColorTextEdit for syntax-highlighted code blocks.
""" """
def __init__(self): def __init__(self):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.options = imgui_md.MarkdownOptions() self.options = imgui_md.MarkdownOptions()
# Base path for fonts (Inter family) # Base path for fonts (Inter family)
self.options.font_options.font_base_path = "fonts/Inter" self.options.font_options.font_base_path = "fonts/Inter"
@@ -126,10 +123,7 @@ class MarkdownRenderer:
print(f"Error opening link {url}: {e}") print(f"Error opening link {url}: {e}")
def render(self, text: str, context_id: str = "default") -> None: def render(self, text: str, context_id: str = "default") -> None:
""" """Render Markdown text with code block interception and GFM table substitution."""
Render Markdown text with code block interception and GFM table substitution.
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
"""
# Lazy lookup of src.markdown_table (Phase 5C). Warmup has already loaded # Lazy lookup of src.markdown_table (Phase 5C). Warmup has already loaded
# it on AppController's _io_pool, so this is an O(1) sys.modules get. # it on AppController's _io_pool, so this is an O(1) sys.modules get.
markdown_table = _require_warmed("src.markdown_table") markdown_table = _require_warmed("src.markdown_table")
@@ -221,7 +215,6 @@ class MarkdownRenderer:
next Y. The '-' delimiter uses Text+SameLine which works correctly. next Y. The '-' delimiter uses Text+SameLine which works correctly.
Converting '* ' to '- ' is the cheapest workaround available from Python Converting '* ' to '- ' is the cheapest workaround available from Python
(we cannot subclass the C++ imgui_md class). (we cannot subclass the C++ imgui_md class).
[C: src/markdown_helper.py:MarkdownRenderer.render]
""" """
return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text) return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text)
@@ -233,7 +226,6 @@ class MarkdownRenderer:
endings, no NewLine is emitted, so the next text starts at the same Y endings, no NewLine is emitted, so the next text starts at the same Y
as the last list item (overlap). Inserting a blank line forces a as the last list item (overlap). Inserting a blank line forces a
paragraph break. Cannot fix the upstream C++ from Python. paragraph break. Cannot fix the upstream C++ from Python.
[C: src/markdown_helper.py:MarkdownRenderer.render]
""" """
lines = text.split("\n") lines = text.split("\n")
out: list[str] = [] out: list[str] = []
@@ -267,7 +259,6 @@ class MarkdownRenderer:
continuation of the first paragraph (single BLOCK_P, proper line wrap, continuation of the first paragraph (single BLOCK_P, proper line wrap,
no overlap). Trade-off: the user cannot have separate paragraphs within no overlap). Trade-off: the user cannot have separate paragraphs within
a single list item. Acceptable for our use case. a single list item. Acceptable for our use case.
[C: src.markdown_helper:MarkdownRenderer.render]
""" """
lines = text.split("\n") lines = text.split("\n")
out: list[str] = [] out: list[str] = []
@@ -396,9 +387,6 @@ def get_renderer() -> MarkdownRenderer:
return _renderer return _renderer
def render(text: str, context_id: str = "default") -> None: def render(text: str, context_id: str = "default") -> None:
"""
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
"""
get_renderer().render(text, context_id) get_renderer().render(text, context_id)
def render_unindented(text: str) -> None: def render_unindented(text: str) -> None:
+2 -6
View File
@@ -7,9 +7,7 @@ from imgui_bundle import imgui, imgui_md
_TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$") _TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
def render_table(block: "TableBlock") -> None: def render_table(block: "TableBlock") -> None:
"""Render a GFM table block via imgui.begin_table. """Render a GFM table block via imgui.begin_table."""
[C: src/markdown_helper.py:MarkdownRenderer.render]
"""
n_cols = len(block.headers) n_cols = len(block.headers)
if n_cols == 0: return if n_cols == 0: return
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable
@@ -26,9 +24,7 @@ def render_table(block: "TableBlock") -> None:
@dataclass(frozen=True) @dataclass(frozen=True)
class TableBlock: class TableBlock:
"""Frozen GFM table block. """Frozen GFM table block.s"""
[C: src/markdown_helper.py:MarkdownRenderer.render]
"""
headers: list[str] headers: list[str]
rows: list[list[str]] rows: list[list[str]]
span: tuple[int, int] span: tuple[int, int]
+8 -42
View File
@@ -228,7 +228,6 @@ def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | Non
file_items : list of dicts from aggregate.build_file_items() file_items : list of dicts from aggregate.build_file_items()
extra_base_dirs : additional directory roots to allow traversal of extra_base_dirs : additional directory roots to allow traversal of
[C: tests/conftest.py:reset_ai_client, tests/test_arch_boundary_phase1.py:TestArchBoundaryPhase1.test_mcp_client_whitelist_enforcement, tests/test_mcp_client_beads.py:test_bd_mcp_tools, tests/test_py_struct_tools.py:test_mcp_dispatch_errors, tests/test_py_struct_tools.py:test_mcp_dispatch_integration]
""" """
global _allowed_paths, _base_dirs, _primary_base_dir global _allowed_paths, _base_dirs, _primary_base_dir
_allowed_paths = set() _allowed_paths = set()
@@ -259,7 +258,6 @@ def _is_allowed(path: Path) -> bool:
symlink-based path traversal. symlink-based path traversal.
CRITICAL: Blacklisted files (history) are NEVER allowed. CRITICAL: Blacklisted files (history) are NEVER allowed.
[C: tests/test_arch_boundary_phase1.py:TestArchBoundaryPhase1.test_mcp_client_whitelist_enforcement, tests/test_history_management.py:test_mcp_blacklist]
""" """
from src.paths import get_config_path, get_credentials_path from src.paths import get_config_path, get_credentials_path
@@ -1338,8 +1336,7 @@ def ts_c_update_definition(path: str, name: str, new_content: str) -> str:
def ts_cpp_get_skeleton(path: str) -> str: def ts_cpp_get_skeleton(path: str) -> str:
""" """
Returns a skeleton of a C++ file. Returns a skeleton of a C++ file.
[C: tests/test_gencpp_full_suite.py:test_gencpp_full_suite, tests/test_ts_cpp_tools.py:test_exhaustive_cpp_samples, tests/test_ts_cpp_tools.py:test_exhaustive_gencpp_samples, tests/test_ts_cpp_tools.py:test_ts_cpp_get_skeleton]
Thin wrapper over ts_cpp_get_skeleton_result; the legacy str shape is Thin wrapper over ts_cpp_get_skeleton_result; the legacy str shape is
preserved for backward compatibility, but the try/except Exception preserved for backward compatibility, but the try/except Exception
@@ -1352,8 +1349,7 @@ def ts_cpp_get_skeleton(path: str) -> str:
def ts_cpp_get_code_outline(path: str) -> str: def ts_cpp_get_code_outline(path: str) -> str:
""" """
Returns a hierarchical outline of a C++ file. Returns a hierarchical outline of a C++ file.
[C: tests/test_gencpp_full_suite.py:test_gencpp_full_suite, tests/test_ts_cpp_tools.py:test_exhaustive_cpp_samples, tests/test_ts_cpp_tools.py:test_exhaustive_gencpp_samples, tests/test_ts_cpp_tools.py:test_ts_cpp_get_code_outline]
Thin wrapper over ts_cpp_get_code_outline_result; the legacy str shape is Thin wrapper over ts_cpp_get_code_outline_result; the legacy str shape is
preserved for backward compatibility, but the try/except Exception preserved for backward compatibility, but the try/except Exception
@@ -1723,7 +1719,6 @@ def fetch_url(url: str) -> str:
def get_ui_performance() -> str: def get_ui_performance() -> str:
""" """
Returns current UI performance metrics (FPS, Frame Time, CPU, Input Lag). Returns current UI performance metrics (FPS, Frame Time, CPU, Input Lag).
[C: tests/test_mcp_perf_tool.py:test_mcp_perf_tool_retrieval]
Thin wrapper over get_ui_performance_result; the legacy str shape is Thin wrapper over get_ui_performance_result; the legacy str shape is
preserved for backward compatibility, but the try/except Exception preserved for backward compatibility, but the try/except Exception
@@ -1750,9 +1745,6 @@ class StdioMCPServer:
return self._id_counter return self._id_counter
async def start(self): async def start(self):
"""
[C: src/multi_agent_conductor.py:WorkerPool.spawn, src/performance_monitor.py:PerformanceMonitor.__init__, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread, tests/test_conductor_engine_v2.py:side_effect, tests/test_spawn_interception_v2.py:test_confirm_spawn_pushed_to_queue, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
"""
self.status = 'starting' self.status = 'starting'
self.proc = await asyncio.create_subprocess_exec( self.proc = await asyncio.create_subprocess_exec(
self.config.command, self.config.command,
@@ -1767,8 +1759,6 @@ class StdioMCPServer:
async def stop(self): async def stop(self):
""" """
[C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing, tests/test_performance_monitor.py:test_perf_monitor_component_timing, tests/test_performance_monitor.py:test_perf_monitor_extended_metrics, tests/test_performance_monitor.py:test_perf_monitor_scope_context_manager, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
Best-effort cleanup. Errors during cleanup are accumulated as ErrorInfo and Best-effort cleanup. Errors during cleanup are accumulated as ErrorInfo and
surfaced via the [MCP:<name>:stop-warning] drain (consistent with _read_stderr surfaced via the [MCP:<name>:stop-warning] drain (consistent with _read_stderr
which also uses print() as a stderr/stdout drain for visibility). which also uses print() as a stderr/stdout drain for visibility).
@@ -1836,10 +1826,7 @@ class ExternalMCPManager:
self.servers = {} self.servers = {}
async def add_server(self, config: MCPServerConfig): async def add_server(self, config: MCPServerConfig):
""" """Add and start a new MCP server from a configuration object."""
Add and start a new MCP server from a configuration object.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp.py:test_get_tool_schemas_includes_external]
"""
if config.url: if config.url:
# RemoteMCPServer placeholder # RemoteMCPServer placeholder
return return
@@ -1848,19 +1835,13 @@ class ExternalMCPManager:
self.servers[config.name] = server self.servers[config.name] = server
async def stop_all(self): async def stop_all(self):
""" """Stop all managed MCP servers and clear the registry."""
Stop all managed MCP servers and clear the registry.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
for server in self.servers.values(): for server in self.servers.values():
await server.stop() await server.stop()
self.servers = {} self.servers = {}
def get_all_tools(self) -> dict: def get_all_tools(self) -> dict:
""" """Retrieve a dictionary of all tools available across all managed servers."""
Retrieve a dictionary of all tools available across all managed servers.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
all_tools = {} all_tools = {}
for sname, server in self.servers.items(): for sname, server in self.servers.items():
for tname, tool in server.tools.items(): for tname, tool in server.tools.items():
@@ -1872,10 +1853,7 @@ class ExternalMCPManager:
return {name: server.status for name, server in self.servers.items()} return {name: server.status for name, server in self.servers.items()}
async def async_dispatch(self, tool_name: str, tool_input: dict) -> str: async def async_dispatch(self, tool_name: str, tool_input: dict) -> str:
""" """Dispatch a tool call to the appropriate external MCP server asynchronously."""
Dispatch a tool call to the appropriate external MCP server asynchronously.
[C: src/rag_engine.py:RAGEngine._async_search_mcp, tests/test_external_mcp.py:test_external_mcp_real_process]
"""
for server in self.servers.values(): for server in self.servers.values():
if tool_name in server.tools: if tool_name in server.tools:
return await server.call_tool(tool_name, tool_input) return await server.call_tool(tool_name, tool_input)
@@ -1884,18 +1862,12 @@ class ExternalMCPManager:
_external_mcp_manager = ExternalMCPManager() _external_mcp_manager = ExternalMCPManager()
def get_external_mcp_manager() -> ExternalMCPManager: def get_external_mcp_manager() -> ExternalMCPManager:
""" """Retrieve the global ExternalMCPManager instance."""
Retrieve the global ExternalMCPManager instance.
[C: tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
global _external_mcp_manager global _external_mcp_manager
return _external_mcp_manager return _external_mcp_manager
def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
""" """Dispatch an MCP tool call by name. Returns the result as a string."""
Dispatch an MCP tool call by name. Returns the result as a string.
[C: tests/test_gemini_cli_edge_cases.py:test_gemini_cli_parameter_resilience, tests/test_mcp_client_beads.py:test_bd_mcp_tools, tests/test_mcp_ts_integration.py:test_ts_c_get_code_outline_dispatch, tests/test_mcp_ts_integration.py:test_ts_c_get_definition_dispatch, tests/test_mcp_ts_integration.py:test_ts_c_get_signature_dispatch, tests/test_mcp_ts_integration.py:test_ts_c_get_skeleton_dispatch, tests/test_mcp_ts_integration.py:test_ts_c_update_definition_dispatch, tests/test_mcp_ts_integration.py:test_ts_cpp_get_code_outline_dispatch, tests/test_mcp_ts_integration.py:test_ts_cpp_get_definition_dispatch, tests/test_mcp_ts_integration.py:test_ts_cpp_get_signature_dispatch, tests/test_mcp_ts_integration.py:test_ts_cpp_get_skeleton_dispatch, tests/test_mcp_ts_integration.py:test_ts_cpp_update_definition_dispatch, tests/test_py_struct_tools.py:test_mcp_dispatch_errors, tests/test_py_struct_tools.py:test_mcp_dispatch_integration]
"""
# py_struct_tools is loaded on-demand to keep the main-thread import graph lean. # py_struct_tools is loaded on-demand to keep the main-thread import graph lean.
from scripts import py_struct_tools from scripts import py_struct_tools
# Handle aliases # Handle aliases
@@ -2062,9 +2034,6 @@ def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
# Check native tools # Check native tools
"""
[C: src/rag_engine.py:RAGEngine._async_search_mcp, tests/test_external_mcp.py:test_external_mcp_real_process]
"""
native_names = mcp_tool_specs.tool_names() native_names = mcp_tool_specs.tool_names()
if tool_name in native_names: if tool_name in native_names:
return await asyncio.to_thread(dispatch, tool_name, tool_input) return await asyncio.to_thread(dispatch, tool_name, tool_input)
@@ -2076,9 +2045,6 @@ async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
return f'ERROR: unknown MCP tool {tool_name}' return f'ERROR: unknown MCP tool {tool_name}'
def get_tool_schemas() -> list[dict[str, Any]]: def get_tool_schemas() -> list[dict[str, Any]]:
"""
[C: tests/test_arch_boundary_phase2.py:TestArchBoundaryPhase2.test_mcp_client_dispatch_completeness, tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_mcp_client_beads.py:test_bd_mcp_tools]
"""
res = [t.to_dict() for t in mcp_tool_specs.get_tool_schemas()] res = [t.to_dict() for t in mcp_tool_specs.get_tool_schemas()]
manager = get_external_mcp_manager() manager = get_external_mcp_manager()
for tname, tinfo in manager.get_all_tools().items(): for tname, tinfo in manager.get_all_tools().items():