cleaning cruft part 2
This commit is contained in:
+3
-15
@@ -301,10 +301,7 @@ def build_file_items(base_dir: Path, files: list[str | Metadata]) -> list[Metada
|
||||
return items
|
||||
|
||||
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).
|
||||
[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]
|
||||
"""
|
||||
"""Build the files markdown section from pre-read file items (avoids double I/O)."""
|
||||
sections = []
|
||||
file_items = file_items or []
|
||||
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)
|
||||
|
||||
def build_beads_section(base_dir: Path) -> str:
|
||||
"""
|
||||
[C: tests/test_aggregate_beads.py:test_build_beads_compaction]
|
||||
"""
|
||||
client = beads_client.BeadsClient(base_dir)
|
||||
if not client.is_initialized(): return ""
|
||||
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)
|
||||
|
||||
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.
|
||||
[C: src/app_controller.py:AppController._do_generate, tests/test_history_management.py:test_aggregate_blacklist]
|
||||
"""
|
||||
"""Build markdown with only files + screenshots (no history). Used for stable caching."""
|
||||
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:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Build just the discussion history section text. Returns empty string if no history."""
|
||||
if not history:
|
||||
return ""
|
||||
return "## Discussion History\n\n" + build_discussion_section(history)
|
||||
|
||||
+35
-156
@@ -56,17 +56,11 @@ from typing import Any
|
||||
class ApiHookClient:
|
||||
|
||||
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.api_key = api_key
|
||||
|
||||
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.
|
||||
[C: tests/test_api_hook_client.py:test_unsupported_method_error]
|
||||
"""
|
||||
"""Helper to make HTTP requests to the hook server."""
|
||||
url = f"{self.base_url}{path}"
|
||||
headers = {}
|
||||
if self.api_key:
|
||||
@@ -90,10 +84,7 @@ class ApiHookClient:
|
||||
return None
|
||||
|
||||
def wait_for_server(self, timeout: int = 15) -> bool:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Polls the health endpoint until the server responds or timeout occurs."""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
status = self.get_status()
|
||||
@@ -103,10 +94,7 @@ class ApiHookClient:
|
||||
return False
|
||||
|
||||
def get_status(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Checks the health of the hook server."""
|
||||
res = self._make_request('GET', '/status')
|
||||
if res is None:
|
||||
# For backward compatibility with tests expecting ConnectionError
|
||||
@@ -115,10 +103,7 @@ class ApiHookClient:
|
||||
return res
|
||||
|
||||
def post_session(self, session_entries: list[dict]) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Updates the session history."""
|
||||
return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {}
|
||||
|
||||
def get_events(self) -> list[Metadata]:
|
||||
@@ -127,16 +112,10 @@ class ApiHookClient:
|
||||
return res.get("events", []) if res else []
|
||||
|
||||
def clear_events(self) -> list[Metadata]:
|
||||
"""
|
||||
Retrieves and clears the event queue.
|
||||
[C: simulation/sim_base.py:BaseSimulation.setup]
|
||||
"""
|
||||
"""Retrieves and clears the event queue."""
|
||||
return self.get_events()
|
||||
|
||||
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()
|
||||
while time.time() - start < timeout:
|
||||
events = self.get_events()
|
||||
@@ -147,33 +126,21 @@ class ApiHookClient:
|
||||
return None
|
||||
|
||||
def post_gui(self, payload: dict) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
|
||||
return self._make_request('POST', '/api/gui', data=payload) or {}
|
||||
|
||||
def push_event(self, action: str, payload: dict) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Convenience to push a GUI task."""
|
||||
return self.post_gui({"action": action, **payload})
|
||||
|
||||
#region: Data
|
||||
|
||||
def get_gui_state(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns the full GUI state available via the hook API."""
|
||||
return self._make_request('GET', '/api/gui/state') or {}
|
||||
|
||||
def get_value(self, item: str) -> Any:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Gets the value of a GUI item via its mapped field."""
|
||||
# Dict-key bracket notation: e.g. 'show_windows["Project Settings"]'
|
||||
if "[" in item and item.endswith("]"):
|
||||
dict_name, _, key_part = item.partition("[")
|
||||
@@ -202,18 +169,12 @@ class ApiHookClient:
|
||||
return None
|
||||
|
||||
def get_text_value(self, item_tag: str) -> str | None:
|
||||
"""
|
||||
Wraps get_value and returns its string representation, or None.
|
||||
[C: tests/test_api_hook_client.py:test_get_text_value]
|
||||
"""
|
||||
"""Wraps get_value and returns its string representation, or None."""
|
||||
val = self.get_value(item_tag)
|
||||
return str(val) if val is not None else None
|
||||
|
||||
def set_value(self, item: str, value: Any) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Sets the value of a GUI widget."""
|
||||
return self.post_gui({"action": "set_value", "item": item, "value": value})
|
||||
|
||||
#endregion: Data
|
||||
@@ -221,31 +182,21 @@ class ApiHookClient:
|
||||
#region: Input
|
||||
|
||||
def click(self, item: str, user_data: Any = None) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Simulates a button click."""
|
||||
return self.post_gui({"action": "click", "item": item, "user_data": user_data})
|
||||
|
||||
def drag(self, src_item: str, dst_item: str) -> Metadata:
|
||||
"""
|
||||
Simulates a drag and drop operation.
|
||||
[C: tests/test_api_hook_client.py:test_drag_success]
|
||||
"""
|
||||
"""Simulates a drag and drop operation."""
|
||||
return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item})
|
||||
|
||||
def right_click(self, item: str) -> Metadata:
|
||||
"""
|
||||
Simulates a right-click on an item.
|
||||
[C: tests/test_api_hook_client.py:test_right_click_success]
|
||||
"""
|
||||
"""Simulates a right-click on an item."""
|
||||
return self.push_event("right_click", {"item": item})
|
||||
|
||||
def request_confirmation(self, tool_name: str, args: dict) -> bool | None:
|
||||
"""
|
||||
Pushes a manual confirmation request and waits for response.
|
||||
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)
|
||||
res = self._make_request('POST', '/api/ask',
|
||||
@@ -254,17 +205,11 @@ class ApiHookClient:
|
||||
return res.get('response') if res else None
|
||||
|
||||
def select_list_item(self, item: str, value: str) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Selects an item in a listbox or combo."""
|
||||
return self.set_value(item, value)
|
||||
|
||||
def select_tab(self, item: str, value: str) -> Metadata:
|
||||
"""
|
||||
Selects a specific tab in a tab bar.
|
||||
[C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_select_tab_integration]
|
||||
"""
|
||||
"""Selects a specific tab in a tab bar."""
|
||||
return self.set_value(item, value)
|
||||
|
||||
#endregion: Input
|
||||
@@ -279,17 +224,11 @@ class ApiHookClient:
|
||||
}) or {}
|
||||
|
||||
def apply_patch(self) -> Metadata:
|
||||
"""
|
||||
Applies the pending patch.
|
||||
[C: tests/test_patch_modal.py:test_apply_callback]
|
||||
"""
|
||||
"""Applies the pending patch."""
|
||||
return self._make_request('POST', '/api/patch/apply') or {}
|
||||
|
||||
def reject_patch(self) -> Metadata:
|
||||
"""
|
||||
Rejects the pending patch.
|
||||
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
|
||||
"""
|
||||
"""Rejects the pending patch."""
|
||||
return self._make_request('POST', '/api/patch/reject') or {}
|
||||
|
||||
def get_patch_status(self) -> Metadata:
|
||||
@@ -301,32 +240,20 @@ class ApiHookClient:
|
||||
#region: Diagnostics
|
||||
|
||||
def get_indicator_state(self, item_tag: str) -> dict[str, bool]:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns the visibility/active state of a status indicator."""
|
||||
val = self.get_value(item_tag)
|
||||
return {"shown": bool(val)}
|
||||
|
||||
def get_gui_diagnostics(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves performance and diagnostic metrics."""
|
||||
return self._make_request('GET', '/api/gui/diagnostics') or {}
|
||||
|
||||
def get_performance(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves performance metrics from the dedicated endpoint."""
|
||||
return self._make_request('GET', '/api/performance') or {}
|
||||
|
||||
def get_warmup_status(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns the current warmup status: {pending, completed, failed}."""
|
||||
return self._make_request('GET', '/api/warmup_status') or {}
|
||||
|
||||
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
|
||||
that need to wait until the system is fully ready before issuing AI
|
||||
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 {}
|
||||
|
||||
@@ -345,7 +271,6 @@ class ApiHookClient:
|
||||
canary_id, module, thread_name, thread_id, submit_ts, start_ts,
|
||||
end_ts, elapsed_ms, status, error. Used for debugging which
|
||||
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 {}
|
||||
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_after_warmup_ms. Lets external clients answer
|
||||
'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 {}
|
||||
|
||||
@@ -365,10 +289,7 @@ class ApiHookClient:
|
||||
#region: Project
|
||||
|
||||
def get_project(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves the current project state."""
|
||||
return self._make_request('GET', '/api/project') or {}
|
||||
|
||||
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
|
||||
Used by tests to wait deterministically for a project switch to complete
|
||||
instead of blind-polling the project state.
|
||||
[C: tests/test_api_hooks_project_switch.py]
|
||||
"""
|
||||
result = self._make_request('GET', '/api/project_switch_status')
|
||||
if not result or not isinstance(result, dict):
|
||||
@@ -395,7 +315,6 @@ class ApiHookClient:
|
||||
- the controller reports an error during the switch
|
||||
- the path doesn't reach expected_path within the timeout
|
||||
- the controller is hung (in_progress stays True)
|
||||
[C: tests/test_live_workflow.py:test_full_live_workflow]
|
||||
"""
|
||||
import os
|
||||
start = time.time()
|
||||
@@ -424,7 +343,6 @@ class ApiHookClient:
|
||||
- inflight: integer count of jobs submitted via submit_io
|
||||
Used by tests to wait for the pool to drain before submitting work
|
||||
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')
|
||||
if not result or not isinstance(result, dict):
|
||||
@@ -440,7 +358,6 @@ class ApiHookClient:
|
||||
- degraded_reason: human-readable description of the failure (if any)
|
||||
- last_assert: full traceback of the last ImGui scope mismatch
|
||||
- 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')
|
||||
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.
|
||||
Returns True on idle, False on timeout. Use this to ensure prior
|
||||
tests' background work has completed before submitting new work.
|
||||
[C: tests/test_live_workflow.py:test_full_live_workflow]
|
||||
"""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
@@ -471,10 +387,7 @@ class ApiHookClient:
|
||||
return last_status
|
||||
|
||||
def post_project(self, project_data: dict) -> Metadata:
|
||||
"""
|
||||
Updates the current project configuration.
|
||||
[C: simulation/sim_context.py:ContextSimulation.run]
|
||||
"""
|
||||
"""Updates the current project configuration."""
|
||||
return self._make_request('POST', '/api/project', data=project_data) or {}
|
||||
|
||||
#endregion: Project
|
||||
@@ -482,17 +395,11 @@ class ApiHookClient:
|
||||
#region: Context
|
||||
|
||||
def inject_context(self, data: dict) -> dict:
|
||||
"""
|
||||
Injects custom file context into the application.
|
||||
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
|
||||
"""
|
||||
"""Injects custom file context into the application."""
|
||||
return self._make_request('POST', '/api/context/inject', data=data) or {}
|
||||
|
||||
def get_context_state(self) -> Metadata:
|
||||
"""
|
||||
Retrieves the current file and screenshot context state.
|
||||
[C: tests/test_gui_context_presets.py:test_gui_context_preset_save_load]
|
||||
"""
|
||||
"""Retrieves the current file and screenshot context state."""
|
||||
return self._make_request('GET', '/api/context/state') or {}
|
||||
|
||||
#endregion: Context
|
||||
@@ -500,17 +407,11 @@ class ApiHookClient:
|
||||
#region: Discussion
|
||||
|
||||
def get_session(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves the current discussion session history."""
|
||||
return self._make_request('GET', '/api/session') or {}
|
||||
|
||||
def reset_session(self) -> None:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Resets the current session via button click."""
|
||||
self.click("btn_reset")
|
||||
|
||||
#endregion: Discussion
|
||||
@@ -530,32 +431,19 @@ class ApiHookClient:
|
||||
#region: MMA
|
||||
|
||||
def get_node_status(self, node_id: str) -> Metadata:
|
||||
"""
|
||||
Retrieves status for a specific node in the MMA DAG.
|
||||
[C: tests/test_api_hook_client.py:test_get_node_status]
|
||||
"""
|
||||
"""Retrieves status for a specific node in the MMA DAG."""
|
||||
return self._make_request('GET', f'/api/mma/node/{node_id}') or {}
|
||||
|
||||
def get_mma_status(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves the dedicated MMA engine status."""
|
||||
return self._make_request('GET', '/api/gui/mma_status') or {}
|
||||
|
||||
def get_mma_workers(self) -> Metadata:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieves status for all active MMA workers."""
|
||||
return self._make_request('GET', '/api/mma/workers') or {}
|
||||
|
||||
def spawn_mma_worker(self, data: dict) -> dict:
|
||||
"""
|
||||
|
||||
Spawns a new MMA worker with the provided configuration.
|
||||
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
|
||||
"""
|
||||
"""Spawns a new MMA worker with the provided configuration."""
|
||||
return self._make_request('POST', '/api/mma/workers/spawn', data=data) or {}
|
||||
|
||||
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 {}
|
||||
|
||||
def pause_mma_pipeline(self) -> dict:
|
||||
"""
|
||||
Pauses the MMA execution pipeline.
|
||||
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
|
||||
"""
|
||||
"""Pauses the MMA execution pipeline."""
|
||||
return self._make_request('POST', '/api/mma/pipeline/pause') or {}
|
||||
|
||||
def resume_mma_pipeline(self) -> dict:
|
||||
@@ -574,17 +459,11 @@ class ApiHookClient:
|
||||
return self._make_request('POST', '/api/mma/pipeline/resume') or {}
|
||||
|
||||
def mutate_mma_dag(self, data: dict) -> dict:
|
||||
"""
|
||||
Mutates the MMA DAG (Directed Acyclic Graph) structure.
|
||||
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
|
||||
"""
|
||||
"""Mutates the MMA DAG (Directed Acyclic Graph) structure."""
|
||||
return self._make_request('POST', '/api/mma/dag/mutate', data=data) or {}
|
||||
|
||||
def approve_mma_ticket(self, ticket_id: str) -> dict:
|
||||
"""
|
||||
Manually approves a specific ticket for execution in Step Mode.
|
||||
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
|
||||
"""
|
||||
"""Manually approves a specific ticket for execution in Step Mode."""
|
||||
return self._make_request('POST', '/api/mma/ticket/approve', data={"ticket_id": ticket_id}) or {}
|
||||
|
||||
#endregion: MMA
|
||||
|
||||
+3
-35
@@ -33,7 +33,6 @@ def _create_generate_request() -> type:
|
||||
max_tokens=(int | None, None),
|
||||
)
|
||||
|
||||
|
||||
def _create_confirm_request() -> type:
|
||||
from src.module_loader import _require_warmed
|
||||
pydantic = _require_warmed("pydantic")
|
||||
@@ -49,7 +48,6 @@ _PYDANTIC_CLASS_FACTORIES: dict[str, callable] = {
|
||||
"ConfirmRequest": _create_confirm_request,
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _PYDANTIC_CLASS_FACTORIES:
|
||||
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]
|
||||
(matching Heuristic A: Result-returning recovery = INTERNAL_COMPLIANT). The HTTP
|
||||
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):
|
||||
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
|
||||
helper does the parse + try/except + Result conversion internally (Heuristic A).
|
||||
|
||||
[C: src/api_hooks.py:HookHandler.do_GET]
|
||||
"""
|
||||
try:
|
||||
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
|
||||
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).
|
||||
|
||||
[C: src/api_hooks.py:HookHandler.do_POST, src/api_hooks.py:HookHandler.do_GET]
|
||||
"""
|
||||
try:
|
||||
callback()
|
||||
@@ -171,12 +163,9 @@ def _run_callback_result(callback) -> Result[bool]:
|
||||
class HookServerInstance(ThreadingHTTPServer):
|
||||
allow_reuse_address = True
|
||||
"""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.
|
||||
[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)
|
||||
self.app = app
|
||||
|
||||
@@ -207,6 +196,7 @@ def _serialize_for_api(obj: Any) -> JsonValue:
|
||||
|
||||
class HookHandler(BaseHTTPRequestHandler):
|
||||
"""Handles incoming HTTP requests for the API hooks."""
|
||||
|
||||
def do_GET(self) -> None:
|
||||
"""Handles GET requests by routing to the appropriate state provider."""
|
||||
try:
|
||||
@@ -934,15 +924,11 @@ class HookHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode("utf-8"))
|
||||
|
||||
|
||||
def log_message(self, format: str, *args: Any) -> None:
|
||||
logging.info("Hook API: " + format % args)
|
||||
|
||||
class HookServer:
|
||||
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.port = port
|
||||
self.server = None
|
||||
@@ -950,9 +936,6 @@ class HookServer:
|
||||
self.websocket_server: WebSocketServer | None = 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():
|
||||
return
|
||||
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}")
|
||||
|
||||
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:
|
||||
self.websocket_server.stop()
|
||||
if self.server:
|
||||
@@ -993,9 +973,6 @@ class HookServer:
|
||||
class WebSocketServer:
|
||||
"""WebSocket gateway for real-time event streaming."""
|
||||
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.port = port
|
||||
self.clients: dict[str, set] = {"events": set(), "telemetry": set()}
|
||||
@@ -1048,27 +1025,18 @@ class WebSocketServer:
|
||||
self.loop.run_until_complete(main())
|
||||
|
||||
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():
|
||||
return
|
||||
self.thread = threading.Thread(target=self._run_loop, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
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:
|
||||
self.loop.call_soon_threadsafe(self._stop_event.set)
|
||||
if self.thread:
|
||||
self.thread.join(timeout=2.0)
|
||||
|
||||
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:
|
||||
return
|
||||
wire = json.dumps({"channel": message.channel, "payload": message.payload})
|
||||
|
||||
+6
-26
@@ -22,29 +22,17 @@ class BeadsClient:
|
||||
self.beads_file = self.repo_dir / "beads.json"
|
||||
|
||||
def init_repo(self) -> None:
|
||||
"""
|
||||
|
||||
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]
|
||||
"""
|
||||
"""Initialize the mock repository."""
|
||||
self.repo_dir.mkdir(parents=True, exist_ok=True)
|
||||
if not self.beads_file.exists():
|
||||
self.beads_file.write_text("[]", encoding="utf-8")
|
||||
|
||||
def is_initialized(self) -> bool:
|
||||
"""
|
||||
|
||||
Check if the repository is initialized.
|
||||
[C: src/mcp_client.py:dispatch, tests/test_beads_client.py:test_beads_init_and_query]
|
||||
"""
|
||||
"""Check if the repository is initialized."""
|
||||
return self.beads_file.exists()
|
||||
|
||||
def create_bead(self, title: str, description: str) -> str:
|
||||
"""
|
||||
|
||||
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]
|
||||
"""
|
||||
"""Create a new bead and return its ID."""
|
||||
beads = self._read_beads()
|
||||
bead_id = f"bead-{len(beads) + 1}"
|
||||
bead = {"id": bead_id, "title": title, "description": description, "status": "active"}
|
||||
@@ -53,11 +41,7 @@ class BeadsClient:
|
||||
return bead_id
|
||||
|
||||
def update_bead(self, bead_id: str, status: str) -> bool:
|
||||
"""
|
||||
|
||||
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]
|
||||
"""
|
||||
"""Update the status of an existing bead."""
|
||||
beads = self._read_beads()
|
||||
for bead in beads:
|
||||
if bead["id"] == bead_id:
|
||||
@@ -67,11 +51,7 @@ class BeadsClient:
|
||||
return False
|
||||
|
||||
def list_beads(self) -> List[Bead]:
|
||||
"""
|
||||
|
||||
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]
|
||||
"""
|
||||
"""List all beads."""
|
||||
return [Bead(**b) for b in self._read_beads()]
|
||||
|
||||
def _read_beads(self) -> List[dict]:
|
||||
@@ -80,4 +60,4 @@ class BeadsClient:
|
||||
return json.loads(self.beads_file.read_text(encoding="utf-8"))
|
||||
|
||||
def _write_beads(self, beads: List[dict]) -> None:
|
||||
self.beads_file.write_text(json.dumps(beads, indent=1), encoding="utf-8")
|
||||
self.beads_file.write_text(json.dumps(beads, indent=1), encoding="utf-8")
|
||||
|
||||
@@ -46,7 +46,6 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str,
|
||||
"""
|
||||
Tier 2 (Tech Lead) call.
|
||||
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)
|
||||
# 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.
|
||||
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)
|
||||
try:
|
||||
|
||||
@@ -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.
|
||||
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:
|
||||
return 0.0
|
||||
|
||||
@@ -43,7 +43,6 @@ class TrackDAG:
|
||||
Initializes the TrackDAG with a list of Ticket objects.
|
||||
Args:
|
||||
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.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`.
|
||||
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"):
|
||||
# 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 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 = []
|
||||
for ticket in self.tickets:
|
||||
@@ -102,7 +99,6 @@ class TrackDAG:
|
||||
Performs an iterative Depth-First Search to detect cycles in the dependency graph.
|
||||
Returns:
|
||||
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"):
|
||||
visited = set()
|
||||
@@ -135,7 +131,6 @@ class TrackDAG:
|
||||
A list of ticket ID strings.
|
||||
Raises:
|
||||
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"):
|
||||
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
|
||||
src/models.py does not need to import TrackDAG at module level,
|
||||
breaking the models<->dag_engine circular dependency.
|
||||
[C: tests/test_mma_models.py:test_track_get_executable_tickets, tests/test_mma_models.py:test_track_get_executable_tickets_complex]
|
||||
"""
|
||||
return TrackDAG(track.tickets).get_ready_tasks()
|
||||
|
||||
@@ -185,7 +179,6 @@ class ExecutionEngine:
|
||||
Args:
|
||||
dag: The TrackDAG instance to manage.
|
||||
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.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'.
|
||||
Returns:
|
||||
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"):
|
||||
self.dag.cascade_blocks()
|
||||
@@ -208,7 +200,6 @@ class ExecutionEngine:
|
||||
Manually transitions a task from 'todo' to 'in_progress' if its dependencies are met.
|
||||
Args:
|
||||
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)
|
||||
if ticket and ticket.status == "todo" and self.dag.is_ticket_ready(ticket):
|
||||
@@ -220,7 +211,6 @@ class ExecutionEngine:
|
||||
Args:
|
||||
task_id: The ID of the task.
|
||||
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)
|
||||
if ticket:
|
||||
|
||||
+1
-4
@@ -41,10 +41,7 @@ class EventEmitter:
|
||||
"""Simple event emitter for decoupled communication between modules."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Initializes the EventEmitter with an empty listener map.
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
"""Initializes the EventEmitter with an empty listener map."""
|
||||
self._listeners: Dict[str, List[Callable[..., Any]]] = {}
|
||||
|
||||
def on(self, event_name: str, callback: Callable[..., Any]) -> None:
|
||||
|
||||
+2
-18
@@ -68,30 +68,18 @@ EMPTY_TEXT_EDITOR_CONFIG: TextEditorConfig = TextEditorConfig()
|
||||
|
||||
class ExternalEditorLauncher:
|
||||
def __init__(self, config: ExternalEditorConfig):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
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:
|
||||
return self.config.editors.get(editor_name) or EMPTY_TEXT_EDITOR_CONFIG
|
||||
return self.config.get_default()
|
||||
|
||||
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]
|
||||
return cmd
|
||||
|
||||
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)
|
||||
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")])
|
||||
@@ -118,8 +106,8 @@ def _find_vscode_in_registry() -> Result[Optional[str]]:
|
||||
for key in reg_keys:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["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
|
||||
["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
|
||||
)
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
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
|
||||
architectural smell (bypasses the controller state owner) and is
|
||||
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 {}
|
||||
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:
|
||||
"""
|
||||
[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:
|
||||
f.write(content)
|
||||
return f.name
|
||||
|
||||
+5
-45
@@ -76,9 +76,6 @@ class ASTParser:
|
||||
#region: Core Operations
|
||||
|
||||
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"):
|
||||
raise ValueError(f"Language '{language}' not supported yet.")
|
||||
self.language_name = language
|
||||
@@ -91,10 +88,7 @@ class ASTParser:
|
||||
self.parser = ts.Parser(self.language)
|
||||
|
||||
def parse(self, code: str) -> 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]
|
||||
"""
|
||||
"""Parse the given code and return the tree-sitter Tree."""
|
||||
return self.parser.parse(bytes(code, "utf8"))
|
||||
|
||||
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
|
||||
@@ -205,10 +199,7 @@ class ASTParser:
|
||||
#region: Skeleton & Curated Views
|
||||
|
||||
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns a skeleton of a Python file (preserving docstrings, stripping function bodies)."""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
edits: List[Tuple[int, int, str]] = []
|
||||
@@ -220,9 +211,6 @@ class ASTParser:
|
||||
return False
|
||||
|
||||
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"):
|
||||
body = node.child_by_field_name("body")
|
||||
if not body:
|
||||
@@ -293,7 +281,6 @@ class ASTParser:
|
||||
Returns a curated skeleton of a Python file.
|
||||
Preserves function bodies if they have @core_logic decorator or # [HOT] comment.
|
||||
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")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
@@ -330,9 +317,6 @@ class ASTParser:
|
||||
return False
|
||||
|
||||
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":
|
||||
body = node.child_by_field_name("body")
|
||||
if body and body.type in ("block", "compound_statement"):
|
||||
@@ -369,11 +353,7 @@ class ASTParser:
|
||||
#region: Targeted Views
|
||||
|
||||
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.
|
||||
[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]
|
||||
"""
|
||||
"""Returns a targeted view of the code including only the specified functions and their dependencies up to depth 2."""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
all_functions = {}
|
||||
@@ -539,7 +519,6 @@ class ASTParser:
|
||||
"""
|
||||
Returns the full source code for a specific definition by name.
|
||||
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")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
@@ -547,9 +526,6 @@ class ASTParser:
|
||||
parts = re.split(r'::|\.', name)
|
||||
|
||||
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:
|
||||
return node
|
||||
target = target_parts[0]
|
||||
@@ -637,16 +613,12 @@ class ASTParser:
|
||||
"""
|
||||
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 '{'.
|
||||
[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")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
parts = re.split(r'::|\.', name)
|
||||
|
||||
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:
|
||||
return node
|
||||
target = target_parts[0]
|
||||
@@ -746,18 +718,12 @@ class ASTParser:
|
||||
#region: Analysis & Updates
|
||||
|
||||
def get_code_outline(self, code: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns a hierarchical outline of the code (classes, structs, functions, methods)."""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
output = []
|
||||
|
||||
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
|
||||
label = ""
|
||||
if ntype in ("class_definition", "class_specifier"):
|
||||
@@ -788,18 +754,12 @@ class ASTParser:
|
||||
return "\n".join(output)
|
||||
|
||||
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.
|
||||
[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]
|
||||
"""
|
||||
"""Surgically replace the definition of a class or function by name."""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
parts = re.split(r'::|\.', name)
|
||||
|
||||
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:
|
||||
return node
|
||||
target = target_parts[0]
|
||||
|
||||
+1
-4
@@ -18,10 +18,7 @@ class FuzzyAnchor:
|
||||
|
||||
@classmethod
|
||||
def create_slice(cls, text: str, start_line: int, end_line: int) -> dict:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""start_line and end_line are 1-based."""
|
||||
lines = text.splitlines()
|
||||
s_idx = max(0, start_line - 1)
|
||||
e_idx = min(len(lines), end_line)
|
||||
|
||||
@@ -49,10 +49,7 @@ class GeminiCliAdapter:
|
||||
Adapter for the Gemini CLI that parses streaming JSON output.
|
||||
"""
|
||||
def __init__(self, binary_path: str = "gemini"):
|
||||
"""
|
||||
Initializes the adapter with the path to the gemini CLI executable.
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
"""Initializes the adapter with the path to the gemini CLI executable."""
|
||||
self.binary_path = binary_path
|
||||
self.session_id: Optional[str] = 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.
|
||||
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()
|
||||
command_parts = [self.binary_path]
|
||||
@@ -192,7 +188,6 @@ class GeminiCliAdapter:
|
||||
"""
|
||||
Provides a character-based token estimation for the Gemini CLI.
|
||||
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))
|
||||
return total_chars // 4
|
||||
+2
-22
@@ -3,7 +3,6 @@ import typing
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class UISnapshot:
|
||||
"""Capture of restorable UI state."""
|
||||
@@ -22,9 +21,6 @@ class UISnapshot:
|
||||
screenshots: list[str]
|
||||
|
||||
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 {
|
||||
"ai_input": self.ai_input,
|
||||
"project_system_prompt": self.project_system_prompt,
|
||||
@@ -43,9 +39,6 @@ class UISnapshot:
|
||||
|
||||
@classmethod
|
||||
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(
|
||||
ai_input = data.get("ai_input", ""),
|
||||
project_system_prompt = data.get("project_system_prompt", ""),
|
||||
@@ -70,9 +63,6 @@ class HistoryEntry:
|
||||
|
||||
class HistoryManager:
|
||||
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._undo_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.
|
||||
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)
|
||||
self._undo_stack.append(entry)
|
||||
@@ -93,7 +82,6 @@ class HistoryManager:
|
||||
"""
|
||||
Undoes the last action by moving the current_state to the redo 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
|
||||
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
|
||||
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
|
||||
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 get_history(self) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Returns a list of descriptions and timestamps for the undo stack."""
|
||||
return [
|
||||
{"description": e.description, "timestamp": e.timestamp}
|
||||
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]:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Jumps to a specific state in the undo stack by moving subsequent states and the current_state to the redo stack."""
|
||||
if index < 0 or index >= len(self._undo_stack): return None
|
||||
# Move current state to redo
|
||||
self._redo_stack.append(HistoryEntry(state=current_state, description=current_description))
|
||||
|
||||
@@ -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)
|
||||
class _ScopeChild:
|
||||
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
|
||||
# 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'):
|
||||
@@ -37,9 +34,6 @@ class _ScopeGroup:
|
||||
def id(str_id: str): return _ScopeId(str_id)
|
||||
class _ScopeId:
|
||||
def __init__(self, str_id: str):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._id = str_id
|
||||
def __enter__(self):
|
||||
# Use explicit conversion to avoid any possible nanobind ambiguity
|
||||
@@ -62,9 +56,6 @@ class _ScopeIndent:
|
||||
def menu(label: str): return _ScopeMenu(label)
|
||||
class _ScopeMenu:
|
||||
def __init__(self, label: str):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._label = label
|
||||
self._active = False
|
||||
def __enter__(self):
|
||||
@@ -78,9 +69,6 @@ class _ScopeMenu:
|
||||
def menu_bar(): return _ScopeMenuBar()
|
||||
class _ScopeMenuBar:
|
||||
def __init__(self):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._active = False
|
||||
def __enter__(self):
|
||||
self._active = imgui.begin_menu_bar()
|
||||
@@ -93,9 +81,6 @@ class _ScopeMenuBar:
|
||||
def node(name: str): return _ScopeNode(name)
|
||||
class _ScopeNode:
|
||||
def __init__(self, name: str):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._name = name
|
||||
def __enter__(self):
|
||||
return imgui_node_editor.begin(self._name)
|
||||
@@ -106,9 +91,6 @@ class _ScopeNode:
|
||||
def popup(id_str: str): return _ScopePopup(id_str)
|
||||
class _ScopePopup:
|
||||
def __init__(self, id_str: str):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._id = id_str
|
||||
self._active = False
|
||||
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)
|
||||
class _ScopePopupModal:
|
||||
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._visible = visible
|
||||
self._flags = flags
|
||||
@@ -141,9 +120,6 @@ class _ScopePopupModal:
|
||||
def style_color(col: int, val: Any): return _ScopeStyleColor(col, val)
|
||||
class _ScopeStyleColor:
|
||||
def __init__(self, col: int, val: Any):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._col = col
|
||||
self._val = val
|
||||
def __enter__(self):
|
||||
@@ -155,9 +131,6 @@ class _ScopeStyleColor:
|
||||
def style_var(var: int, val: Any): return _ScopeStyleVar(var, val)
|
||||
class _ScopeStyleVar:
|
||||
def __init__(self, var: int, val: Any):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._var = var
|
||||
self._val = val
|
||||
def __enter__(self):
|
||||
@@ -169,9 +142,6 @@ class _ScopeStyleVar:
|
||||
def table(name: str, columns: int, flags: int = 0): return _ScopeTable(name, columns, flags)
|
||||
class _ScopeTable:
|
||||
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._columns = columns
|
||||
self._flags = flags
|
||||
@@ -187,9 +157,6 @@ class _ScopeTable:
|
||||
def tab_bar(id_str: str, flags: int = 0): return _ScopeTabBar(id_str, flags)
|
||||
class _ScopeTabBar:
|
||||
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._flags = flags
|
||||
self._active = False
|
||||
@@ -204,9 +171,6 @@ class _ScopeTabBar:
|
||||
def tab_item(label: str, flags: int = 0): return _ScopeTabItem(label, flags)
|
||||
class _ScopeTabItem:
|
||||
def __init__(self, label: str, flags: int):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._label = label
|
||||
self._flags = flags
|
||||
self._expanded = False
|
||||
@@ -222,9 +186,6 @@ class _ScopeTabItem:
|
||||
def text_wrap(wrap_pos: float = 0.0): return _ScopeTextWrap(wrap_pos)
|
||||
class _ScopeTextWrap:
|
||||
def __init__(self, wrap_pos: float):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._wrap_pos = wrap_pos
|
||||
def __enter__(self):
|
||||
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)
|
||||
class _ScopeTreeNodeEx:
|
||||
def __init__(self, label: str, flags: int):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self._label = label
|
||||
self._flags = flags
|
||||
self._opened = False
|
||||
@@ -260,9 +218,6 @@ class _ScopeTreeNodeEx:
|
||||
def window(name: str, visible: bool = True, flags: int = 0): return _ScopeWindow(name, visible, flags)
|
||||
class _ScopeWindow:
|
||||
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._visible = visible
|
||||
self._flags = flags
|
||||
|
||||
+4
-8
@@ -15,8 +15,7 @@ class LayoutFile:
|
||||
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
|
||||
this module; structure parsing (ImGui sections, [Window] entries)
|
||||
is the consumer's responsibility.
|
||||
[C: src/layouts.py:load_layouts_from_dir, src/gui_2.py:_install_default_layout_if_empty]"""
|
||||
is the consumer's responsibility."""
|
||||
name: str
|
||||
raw_text: str
|
||||
source_path: Path
|
||||
@@ -25,8 +24,7 @@ class 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
|
||||
themes' load_themes_from_toml shape (dict out).
|
||||
[C: src/layouts.py:load_layouts_from_dir]"""
|
||||
themes' load_themes_from_toml shape (dict out)."""
|
||||
out: dict[str, LayoutFile] = {}
|
||||
if not path.exists() or not path.is_file():
|
||||
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
|
||||
conductor/code_styleguides/feature_flags.md). Per-file errors are
|
||||
drained via Result + stderr warn, mirroring
|
||||
src/theme_models.py:load_themes_from_dir.
|
||||
[C: src/layouts.py:load_layouts_from_disk]"""
|
||||
src/theme_models.py:load_themes_from_dir."""
|
||||
out: dict[str, LayoutFile] = {}
|
||||
if not path.exists() or not path.is_dir():
|
||||
return out
|
||||
@@ -73,8 +70,7 @@ def load_layouts_from_disk() -> dict[str, LayoutFile]:
|
||||
"""Load all bundled layouts from the global layouts dir
|
||||
(resolved via src/paths.py:get_layouts_dir()). Cached at the
|
||||
module level; callers iterate the returned dict. The 'delete to
|
||||
turn off' guarantee: a missing or empty layouts/ dir returns {}.
|
||||
[C: src/gui_2.py:_install_default_layout_if_empty]"""
|
||||
turn off' guarantee: a missing or empty layouts/ dir returns {}."""
|
||||
global _LAYOUTS_CACHE
|
||||
layouts_dir = get_layouts_dir()
|
||||
_LAYOUTS_CACHE = load_layouts_from_dir(layouts_dir, scope="global")
|
||||
|
||||
@@ -22,7 +22,6 @@ class LogPruner:
|
||||
Args:
|
||||
log_registry: An instance of LogRegistry to check session data.
|
||||
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.logs_dir = logs_dir
|
||||
@@ -35,7 +34,6 @@ class LogPruner:
|
||||
1. The session start time is older than max_age_days.
|
||||
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.
|
||||
[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()
|
||||
cutoff_time = now - timedelta(days=max_age_days)
|
||||
|
||||
@@ -145,7 +145,6 @@ class LogRegistry:
|
||||
|
||||
Args:
|
||||
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.data: dict[str, Session] = {}
|
||||
@@ -188,7 +187,6 @@ class LogRegistry:
|
||||
"""
|
||||
Serializes and saves the current registry data to the TOML file.
|
||||
Converts internal datetime objects to ISO format strings for compatibility.
|
||||
[C: tests/test_logging_e2e.py:test_logging_e2e]
|
||||
"""
|
||||
try:
|
||||
# Convert datetime objects to ISO format strings for TOML serialization
|
||||
@@ -228,7 +226,6 @@ class LogRegistry:
|
||||
session_id (str): Unique identifier for the session.
|
||||
path (str): File path to the session's log directory.
|
||||
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:
|
||||
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.
|
||||
whitelisted (bool): Whether the session should be protected from pruning.
|
||||
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:
|
||||
print(f"Error: Session ID '{session_id}' not found for metadata update.")
|
||||
@@ -291,7 +287,6 @@ class LogRegistry:
|
||||
Args:
|
||||
session_id (str): Unique identifier for the session.
|
||||
start_time (datetime|str): The new start timestamp.
|
||||
[C: tests/test_logging_e2e.py:test_logging_e2e]
|
||||
"""
|
||||
if session_id not in self.data:
|
||||
print(f"Error: Session ID '{session_id}' not found for start_time update.")
|
||||
@@ -319,7 +314,6 @@ class LogRegistry:
|
||||
|
||||
Returns:
|
||||
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)
|
||||
if session is None:
|
||||
@@ -334,7 +328,6 @@ class LogRegistry:
|
||||
|
||||
Args:
|
||||
session_id (str): Unique identifier for the session to analyze.
|
||||
[C: src/session_logger.py:close_session]
|
||||
"""
|
||||
if session_id not in self.data:
|
||||
return
|
||||
@@ -398,7 +391,6 @@ class LogRegistry:
|
||||
|
||||
Returns:
|
||||
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 = []
|
||||
for session_id, session in self.data.items():
|
||||
|
||||
+1
-13
@@ -64,9 +64,6 @@ class MarkdownRenderer:
|
||||
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self.options = imgui_md.MarkdownOptions()
|
||||
# Base path for fonts (Inter family)
|
||||
self.options.font_options.font_base_path = "fonts/Inter"
|
||||
@@ -126,10 +123,7 @@ class MarkdownRenderer:
|
||||
print(f"Error opening link {url}: {e}")
|
||||
|
||||
def render(self, text: str, context_id: str = "default") -> None:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Render Markdown text with code block interception and GFM table substitution."""
|
||||
# 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.
|
||||
markdown_table = _require_warmed("src.markdown_table")
|
||||
@@ -221,7 +215,6 @@ class MarkdownRenderer:
|
||||
next Y. The '-' delimiter uses Text+SameLine which works correctly.
|
||||
Converting '* ' to '- ' is the cheapest workaround available from Python
|
||||
(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)
|
||||
|
||||
@@ -233,7 +226,6 @@ class MarkdownRenderer:
|
||||
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
|
||||
paragraph break. Cannot fix the upstream C++ from Python.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
@@ -267,7 +259,6 @@ class MarkdownRenderer:
|
||||
continuation of the first paragraph (single BLOCK_P, proper line wrap,
|
||||
no overlap). Trade-off: the user cannot have separate paragraphs within
|
||||
a single list item. Acceptable for our use case.
|
||||
[C: src.markdown_helper:MarkdownRenderer.render]
|
||||
"""
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
@@ -396,9 +387,6 @@ def get_renderer() -> MarkdownRenderer:
|
||||
return _renderer
|
||||
|
||||
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)
|
||||
|
||||
def render_unindented(text: str) -> None:
|
||||
|
||||
@@ -7,9 +7,7 @@ from imgui_bundle import imgui, imgui_md
|
||||
_TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
|
||||
|
||||
def render_table(block: "TableBlock") -> None:
|
||||
"""Render a GFM table block via imgui.begin_table.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
"""Render a GFM table block via imgui.begin_table."""
|
||||
n_cols = len(block.headers)
|
||||
if n_cols == 0: return
|
||||
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable
|
||||
@@ -26,9 +24,7 @@ def render_table(block: "TableBlock") -> None:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TableBlock:
|
||||
"""Frozen GFM table block.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
"""Frozen GFM table block.s"""
|
||||
headers: list[str]
|
||||
rows: list[list[str]]
|
||||
span: tuple[int, int]
|
||||
|
||||
+8
-42
@@ -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()
|
||||
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
|
||||
_allowed_paths = set()
|
||||
@@ -259,7 +258,6 @@ def _is_allowed(path: Path) -> bool:
|
||||
symlink-based path traversal.
|
||||
|
||||
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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
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]
|
||||
Returns a skeleton of a C++ file.
|
||||
|
||||
Thin wrapper over ts_cpp_get_skeleton_result; the legacy str shape is
|
||||
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:
|
||||
"""
|
||||
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]
|
||||
Returns a hierarchical outline of a C++ file.
|
||||
|
||||
Thin wrapper over ts_cpp_get_code_outline_result; the legacy str shape is
|
||||
preserved for backward compatibility, but the try/except Exception
|
||||
@@ -1723,7 +1719,6 @@ def fetch_url(url: str) -> str:
|
||||
def get_ui_performance() -> str:
|
||||
"""
|
||||
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
|
||||
preserved for backward compatibility, but the try/except Exception
|
||||
@@ -1750,9 +1745,6 @@ class StdioMCPServer:
|
||||
return self._id_counter
|
||||
|
||||
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.proc = await asyncio.create_subprocess_exec(
|
||||
self.config.command,
|
||||
@@ -1767,8 +1759,6 @@ class StdioMCPServer:
|
||||
|
||||
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
|
||||
surfaced via the [MCP:<name>:stop-warning] drain (consistent with _read_stderr
|
||||
which also uses print() as a stderr/stdout drain for visibility).
|
||||
@@ -1836,10 +1826,7 @@ class ExternalMCPManager:
|
||||
self.servers = {}
|
||||
|
||||
async def add_server(self, config: MCPServerConfig):
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Add and start a new MCP server from a configuration object."""
|
||||
if config.url:
|
||||
# RemoteMCPServer placeholder
|
||||
return
|
||||
@@ -1848,19 +1835,13 @@ class ExternalMCPManager:
|
||||
self.servers[config.name] = server
|
||||
|
||||
async def stop_all(self):
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Stop all managed MCP servers and clear the registry."""
|
||||
for server in self.servers.values():
|
||||
await server.stop()
|
||||
self.servers = {}
|
||||
|
||||
def get_all_tools(self) -> dict:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieve a dictionary of all tools available across all managed servers."""
|
||||
all_tools = {}
|
||||
for sname, server in self.servers.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()}
|
||||
|
||||
async def async_dispatch(self, tool_name: str, tool_input: dict) -> str:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Dispatch a tool call to the appropriate external MCP server asynchronously."""
|
||||
for server in self.servers.values():
|
||||
if tool_name in server.tools:
|
||||
return await server.call_tool(tool_name, tool_input)
|
||||
@@ -1884,18 +1862,12 @@ class ExternalMCPManager:
|
||||
_external_mcp_manager = ExternalMCPManager()
|
||||
|
||||
def get_external_mcp_manager() -> ExternalMCPManager:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Retrieve the global ExternalMCPManager instance."""
|
||||
global _external_mcp_manager
|
||||
return _external_mcp_manager
|
||||
|
||||
def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
"""
|
||||
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]
|
||||
"""
|
||||
"""Dispatch an MCP tool call by name. Returns the result as a string."""
|
||||
# py_struct_tools is loaded on-demand to keep the main-thread import graph lean.
|
||||
from scripts import py_struct_tools
|
||||
# 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:
|
||||
# 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()
|
||||
if tool_name in native_names:
|
||||
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}'
|
||||
|
||||
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()]
|
||||
manager = get_external_mcp_manager()
|
||||
for tname, tinfo in manager.get_all_tools().items():
|
||||
|
||||
Reference in New Issue
Block a user