From 833e99f2ecc2e3c1961e9d881bfcd562f56191ce Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 12:39:17 -0400 Subject: [PATCH] refactor(project_manager,aggregate,api_hook_client): replace weak type sites with aliases --- src/aggregate.py | 28 +++++++++---- src/api_hook_client.py | 93 ++++++++++++++++++++++++------------------ src/project_manager.py | 41 ++++++++++++------- 3 files changed, 100 insertions(+), 62 deletions(-) diff --git a/src/aggregate.py b/src/aggregate.py index a2cbad6f..f50afd8c 100644 --- a/src/aggregate.py +++ b/src/aggregate.py @@ -33,6 +33,18 @@ from src.fuzzy_anchor import FuzzyAnchor from src.file_cache import ASTParser from src.paths import get_config_path from src.performance_monitor import get_monitor +from src.type_aliases import ( + CommsLog, + CommsLogCallback, + CommsLogEntry, + FileItem, + FileItems, + History, + HistoryMessage, + Metadata, + ToolCall, + ToolDefinition, +) def find_next_increment(output_dir: Path, namespace: str) -> int: @@ -143,7 +155,7 @@ def build_screenshots_section(base_dir: Path, screenshots: list[str]) -> str: sections.append(f"### `{original}`\n\n![{path.name}]({path.as_posix()})") return "\n\n---\n\n".join(sections) -def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[dict[str, Any]]: +def build_file_items(base_dir: Path, files: list[str | Metadata]) -> list[Metadata]: """ Return a list of dicts describing each file, for use by ai_client when it wants to upload individual files rather than inline everything as markdown. @@ -161,7 +173,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[ [C: src/app_controller.py:AppController._bg_task, src/orchestrator_pm.py:module, tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_aggregate_flags.py:test_force_full, tests/test_context_composition_phase6.py:test_view_mode_custom, tests/test_context_composition_phase6.py:test_view_mode_custom_empty_default_to_summary, tests/test_context_composition_phase6.py:test_view_mode_default_summary, tests/test_context_composition_phase6.py:test_view_mode_full, tests/test_context_composition_phase6.py:test_view_mode_none, tests/test_context_composition_phase6.py:test_view_mode_outline, tests/test_context_composition_phase6.py:test_view_mode_skeleton, tests/test_context_composition_phase6.py:test_view_mode_summary, tests/test_tiered_context.py:test_build_file_items_with_tiers, tests/test_tiered_context.py:test_build_files_section_with_dicts] """ with get_monitor().scope("build_file_items"): - items: list[dict[str, Any]] = [] + items: list[Metadata] = [] parser = None for entry_raw in files: if isinstance(entry_raw, dict): @@ -285,7 +297,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[ items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices}) return items -def _build_files_section_from_items(file_items: list[dict[str, Any]]) -> str: +def _build_files_section_from_items(file_items: list[Metadata]) -> str: """ Build the files markdown section from pre-read file items (avoids double I/O). [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] @@ -333,7 +345,7 @@ def build_beads_section(base_dir: Path) -> str: for b in active: parts.append(f"- **{b.title}** ({b.id}): {b.description}") return "\n\n".join(parts) -def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, aggregation_strategy: str = "auto", execution_mode: str = "standard", base_dir: Path | None = None) -> str: +def build_markdown_from_items(file_items: list[Metadata], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, aggregation_strategy: str = "auto", execution_mode: str = "standard", base_dir: Path | None = None) -> str: """Build markdown from pre-read file items instead of re-reading from disk.""" parts = [] # STATIC PREFIX: Files and Screenshots must go first to maximize Cache Hits @@ -351,7 +363,7 @@ def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_ if history: parts.append("## Discussion History\n\n" + build_discussion_section(history)) return "\n\n---\n\n".join(parts) -def build_markdown_no_history(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str: +def build_markdown_no_history(file_items: list[Metadata], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str: """ Build markdown with only files + screenshots (no history). Used for stable caching. [C: src/app_controller.py:AppController._do_generate, tests/test_history_management.py:test_aggregate_blacklist] @@ -367,7 +379,7 @@ def build_discussion_text(history: list[str]) -> str: return "" return "## Discussion History\n\n" + build_discussion_section(history) -def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], focus_files: list[str]) -> str: +def build_tier3_context(file_items: list[Metadata], screenshot_base_dir: Path, screenshots: list[str], history: list[str], focus_files: list[str]) -> str: """ Tier 3 Context: Execution/Worker. Full content for focus_files and files with tier=3, summaries/skeletons for others. @@ -460,11 +472,11 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P if history: parts.append("## Discussion History\n\n" + build_discussion_section(history)) return "\n\n---\n\n".join(parts) -def build_markdown(base_dir: Path, files: list[str | dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, execution_mode: str = "standard") -> str: +def build_markdown(base_dir: Path, files: list[str | Metadata], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, execution_mode: str = "standard") -> str: file_items = build_file_items(base_dir, files) return build_markdown_from_items(file_items, screenshot_base_dir, screenshots, history, summary_only=summary_only, aggregation_strategy='auto', execution_mode=execution_mode, base_dir=base_dir) -def run(config: dict[str, Any], aggregation_strategy: str = "auto") -> tuple[str, Path, list[dict[str, Any]]]: +def run(config: Metadata, aggregation_strategy: str = "auto") -> tuple[str, Path, list[Metadata]]: """ [C: simulation/sim_base.py:run_sim, src/ai_client.py:_send_anthropic, src/ai_client.py:_send_deepseek, src/ai_client.py:_send_gemini, src/ai_client.py:_send_gemini_cli, src/ai_client.py:_send_minimax, src/app_controller.py:AppController._cb_start_track, src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._process_event_queue, src/app_controller.py:AppController._start_track_logic, src/external_editor.py:_find_vscode_in_registry, src/gui_2.py:App._render_snapshot_tab, src/gui_2.py:App.run, src/gui_2.py:main, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/rag_engine.py:RAGEngine._search_mcp, src/shell_runner.py:run_powershell, tests/conftest.py:kill_process_tree, tests/conftest.py:live_gui, tests/test_conductor_abort_event.py:test_conductor_abort_event_populated, tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_conductor_engine_v2.py:test_conductor_engine_run_executes_tickets_in_order, 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:get_vscode_processes, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_custom_window.py:test_app_window_is_borderless, tests/test_headless_simulation.py:module, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_mock_gemini_cli.py:run_mock, tests/test_orchestration_logic.py:test_conductor_engine_run, tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_sim_ai_settings.py:test_ai_settings_simulation_run, tests/test_sim_context.py:test_context_simulation_run, tests/test_sim_execution.py:test_execution_simulation_run, tests/test_sim_tools.py:test_tools_simulation_run] """ diff --git a/src/api_hook_client.py b/src/api_hook_client.py index 51e82d3f..68417e73 100644 --- a/src/api_hook_client.py +++ b/src/api_hook_client.py @@ -9,7 +9,7 @@ This module provides a Python client for interacting with the Hook API exposed b Architecture: - Uses requests library for HTTP communication - - All methods return dict[str, Any] or None + - All methods return Metadata or None - Handles connection errors gracefully (returns None on failure) Key Method Categories: @@ -37,6 +37,19 @@ import requests # type: ignore[import-untyped] import sys import time +from src.type_aliases import ( + CommsLog, + CommsLogCallback, + CommsLogEntry, + FileItem, + FileItems, + History, + HistoryMessage, + Metadata, + ToolCall, + ToolDefinition, +) + from typing import Any @@ -49,7 +62,7 @@ class ApiHookClient: 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) -> dict[str, Any] | None: + def _make_request(self, method: str, path: str, data: dict | None = None, timeout: float = 5.0) -> Metadata | None: """ Helper to make HTTP requests to the hook server. [C: tests/test_api_hook_client.py:test_unsupported_method_error] @@ -89,7 +102,7 @@ class ApiHookClient: time.sleep(0.5) return False - def get_status(self) -> dict[str, Any]: + 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] @@ -101,26 +114,26 @@ class ApiHookClient: return {} return res - def post_session(self, session_entries: list[dict]) -> dict[str, Any]: + 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] """ return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {} - def get_events(self) -> list[dict[str, Any]]: + def get_events(self) -> list[Metadata]: """Retrieves any pending events from the API event queue.""" res = self._make_request('GET', '/api/events') return res.get("events", []) if res else [] - def clear_events(self) -> list[dict[str, Any]]: + def clear_events(self) -> list[Metadata]: """ Retrieves and clears the event queue. [C: simulation/sim_base.py:BaseSimulation.setup] """ return self.get_events() - def wait_for_event(self, event_type: str, timeout: int = 5) -> dict[str, Any] | None: + def wait_for_event(self, event_type: str, timeout: int = 5) -> Metadata | None: """ [C: simulation/sim_base.py:BaseSimulation.wait_for_event, simulation/sim_execution.py:ExecutionSimulation.run, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout] """ @@ -133,14 +146,14 @@ class ApiHookClient: time.sleep(0.2) return None - def post_gui(self, payload: dict) -> dict[str, Any]: + 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] """ return self._make_request('POST', '/api/gui', data=payload) or {} - def push_event(self, action: str, payload: dict) -> dict[str, Any]: + 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] @@ -149,7 +162,7 @@ class ApiHookClient: #region: Data - def get_gui_state(self) -> dict[str, Any]: + 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] @@ -196,7 +209,7 @@ class ApiHookClient: val = self.get_value(item_tag) return str(val) if val is not None else None - def set_value(self, item: str, value: Any) -> dict[str, Any]: + 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] @@ -207,21 +220,21 @@ class ApiHookClient: #region: Input - def click(self, item: str, user_data: Any = None) -> dict[str, Any]: + 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] """ return self.post_gui({"action": "click", "item": item, "user_data": user_data}) - def drag(self, src_item: str, dst_item: str) -> dict[str, Any]: + 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] """ return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item}) - def right_click(self, item: str) -> dict[str, Any]: + 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] @@ -240,14 +253,14 @@ class ApiHookClient: timeout=60.0) return res.get('response') if res else None - def select_list_item(self, item: str, value: str) -> dict[str, Any]: + 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] """ return self.set_value(item, value) - def select_tab(self, item: str, value: str) -> dict[str, Any]: + 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] @@ -258,28 +271,28 @@ class ApiHookClient: #region: Patching - def trigger_patch(self, patch_text: str, file_paths: list[str]) -> dict[str, Any]: + def trigger_patch(self, patch_text: str, file_paths: list[str]) -> Metadata: """Triggers the patch modal to show in the GUI.""" return self._make_request('POST', '/api/patch/trigger', data={ "patch_text": patch_text, "file_paths": file_paths }) or {} - def apply_patch(self) -> dict[str, Any]: + def apply_patch(self) -> Metadata: """ Applies the pending patch. [C: tests/test_patch_modal.py:test_apply_callback] """ return self._make_request('POST', '/api/patch/apply') or {} - def reject_patch(self) -> dict[str, Any]: + 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] """ return self._make_request('POST', '/api/patch/reject') or {} - def get_patch_status(self) -> dict[str, Any]: + def get_patch_status(self) -> Metadata: """Gets the current patch modal status.""" return self._make_request('GET', '/api/patch/status') or {} @@ -295,28 +308,28 @@ class ApiHookClient: val = self.get_value(item_tag) return {"shown": bool(val)} - def get_gui_diagnostics(self) -> dict[str, Any]: + 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] """ return self._make_request('GET', '/api/gui/diagnostics') or {} - def get_performance(self) -> dict[str, Any]: + 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] """ return self._make_request('GET', '/api/performance') or {} - def get_warmup_status(self) -> dict[str, Any]: + 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] """ return self._make_request('GET', '/api/warmup_status') or {} - def get_warmup_wait(self, timeout: float = 30.0) -> dict[str, Any]: + def get_warmup_wait(self, timeout: float = 30.0) -> Metadata: """ Blocks server-side up to `timeout` seconds waiting for the warmup to complete, then returns the final status. Useful for external clients @@ -326,7 +339,7 @@ class ApiHookClient: """ return self._make_request('GET', f'/api/warmup_wait?timeout={timeout}') or {} - def get_warmup_canaries(self) -> list[dict[str, Any]]: + def get_warmup_canaries(self) -> list[Metadata]: """ Returns per-module import canary records: list of dicts with canary_id, module, thread_name, thread_id, submit_ts, start_ts, @@ -337,7 +350,7 @@ class ApiHookClient: result = self._make_request('GET', '/api/warmup_canaries') or {} return result.get("canaries", []) if isinstance(result, dict) else [] - def get_startup_timeline(self) -> dict[str, Any]: + def get_startup_timeline(self) -> Metadata: """ Returns the startup timeline: dict with init_start_ts, warmup_done_ts, first_frame_ts, warmup_ms, first_frame_after_init_ms, @@ -351,14 +364,14 @@ class ApiHookClient: #region: Project - def get_project(self) -> dict[str, Any]: + 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] """ return self._make_request('GET', '/api/project') or {} - def get_project_switch_status(self) -> dict[str, Any]: + def get_project_switch_status(self) -> Metadata: """ Returns the project switch status: {in_progress, path, error}. - in_progress: True when a project switch is currently scheduled or running @@ -373,7 +386,7 @@ class ApiHookClient: return {"in_progress": False, "path": None, "error": None} return result - def wait_for_project_switch(self, expected_path: str = None, timeout: float = 30.0, poll_interval: float = 0.2) -> dict[str, Any]: + def wait_for_project_switch(self, expected_path: str = None, timeout: float = 30.0, poll_interval: float = 0.2) -> Metadata: """ Blocks until the project switch completes (or fails). Returns the final status dict. If expected_path is provided, also waits until the @@ -404,7 +417,7 @@ class ApiHookClient: last_status["timeout"] = True return last_status - def get_io_pool_status(self) -> dict[str, Any]: + def get_io_pool_status(self) -> Metadata: """ Returns the controller's io_pool status: {idle, inflight}. - idle: True if no jobs are currently in-flight (running or queued) @@ -418,7 +431,7 @@ class ApiHookClient: return {"idle": True, "inflight": 0} return result - def get_gui_health(self) -> dict[str, Any]: + def get_gui_health(self) -> Metadata: """ Returns the controller's GUI health: {healthy, degraded_reason, last_assert, io_pool_alive}. Tests should call this before starting @@ -454,10 +467,10 @@ class ApiHookClient: time.sleep(poll_interval) return False - def post_project(self, project_data: dict) -> dict[str, Any]: + def post_project(self, project_data: dict) -> Metadata: return last_status - def post_project(self, project_data: dict) -> dict[str, Any]: + def post_project(self, project_data: dict) -> Metadata: """ Updates the current project configuration. [C: simulation/sim_context.py:ContextSimulation.run] @@ -475,7 +488,7 @@ class ApiHookClient: """ return self._make_request('POST', '/api/context/inject', data=data) or {} - def get_context_state(self) -> dict[str, Any]: + 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] @@ -486,7 +499,7 @@ class ApiHookClient: #region: Discussion - def get_session(self) -> dict[str, Any]: + 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] @@ -504,11 +517,11 @@ class ApiHookClient: #region: Analytics - def get_financial_metrics(self) -> dict[str, Any]: + def get_financial_metrics(self) -> Metadata: """Retrieves token usage and estimated financial cost metrics.""" return self._make_request('GET', '/api/metrics/financial') or {} - def get_system_telemetry(self) -> dict[str, Any]: + def get_system_telemetry(self) -> Metadata: """Retrieves system-level telemetry including thread status and event queue size.""" return self._make_request('GET', '/api/system/telemetry') or {} @@ -516,21 +529,21 @@ class ApiHookClient: #region: MMA - def get_node_status(self, node_id: str) -> dict[str, Any]: + 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] """ return self._make_request('GET', f'/api/mma/node/{node_id}') or {} - def get_mma_status(self) -> dict[str, Any]: + 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] """ return self._make_request('GET', '/api/gui/mma_status') or {} - def get_mma_workers(self) -> dict[str, Any]: + 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] diff --git a/src/project_manager.py b/src/project_manager.py index 2ff28855..196fddf1 100644 --- a/src/project_manager.py +++ b/src/project_manager.py @@ -17,6 +17,19 @@ from typing import Any, Optional, TYPE_CHECKING, Union from src import paths +from src.type_aliases import ( + CommsLog, + CommsLogCallback, + CommsLogEntry, + FileItem, + FileItems, + History, + HistoryMessage, + Metadata, + ToolCall, + ToolDefinition, +) + if TYPE_CHECKING: from src.models import TrackState @@ -33,7 +46,7 @@ def parse_ts(s: str) -> Optional[datetime.datetime]: return None # ── entry serialisation ────────────────────────────────────────────────────── -def entry_to_str(entry: dict[str, Any]) -> str: +def entry_to_str(entry: Metadata) -> str: """ Serialise a disc entry dict -> stored string. [C: tests/test_thinking_persistence.py:test_entry_to_str_with_thinking] @@ -53,13 +66,13 @@ def entry_to_str(entry: dict[str, Any]) -> str: return f"@{ts}\n{role}:\n{content}" return f"{role}:\n{content}" -def format_discussion(entries: list[dict[str, Any]]) -> str: +def format_discussion(entries: list[Metadata]) -> str: """ Convert a list of discussion entry dicts into a single formatted string. """ return "\n\n".join([entry_to_str(e) for e in entries]) -def str_to_entry(raw: str, roles: list[str]) -> dict[str, Any]: +def str_to_entry(raw: str, roles: list[str]) -> Metadata: """ Parse a stored string back to a disc entry dict. [C: tests/test_thinking_persistence.py:test_str_to_entry_with_thinking] @@ -101,13 +114,13 @@ def get_git_commit(git_dir: str) -> str: # ── default structures ─────────────────────────────────────────────────────── -def default_discussion() -> dict[str, Any]: +def default_discussion() -> Metadata: """ [C: tests/test_discussion_takes.py:TestDiscussionTakes.test_promote_take_renames_discussion] """ return {"git_commit": "", "last_updated": now_ts(), "history": []} -def default_project(name: str = "unnamed") -> dict[str, Any]: +def default_project(name: str = "unnamed") -> Metadata: """ [C: tests/test_deepseek_infra.py:test_default_project_includes_reasoning_role, tests/test_discussion_takes.py:TestDiscussionTakes.setUp, tests/test_history_management.py:test_history_persistence_across_turns, tests/test_history_management.py:test_save_separation, tests/test_project_manager_modes.py:test_default_project_execution_mode, tests/test_project_manager_modes.py:test_load_save_execution_mode, tests/test_project_serialization.py:TestProjectSerialization.test_default_roles_include_context, tests/test_project_serialization.py:TestProjectSerialization.test_fileitem_roundtrip] """ @@ -170,7 +183,7 @@ def get_history_path(project_path: Union[str, Path]) -> Path: p = Path(project_path) return p.parent / f"{p.stem}_history.toml" -def load_project(path: Union[str, Path]) -> dict[str, Any]: +def load_project(path: Union[str, Path]) -> Metadata: """ Load a project TOML file. Automatically migrates legacy 'discussion' keys to a sibling history file. @@ -193,7 +206,7 @@ def load_project(path: Union[str, Path]) -> dict[str, Any]: proj["discussion"] = load_history(path) return proj -def load_history(project_path: Union[str, Path]) -> dict[str, Any]: +def load_history(project_path: Union[str, Path]) -> Metadata: """ Load the segregated discussion history from its dedicated TOML file. [C: tests/test_thinking_persistence.py:test_save_and_load_history_with_thinking_segments] @@ -213,7 +226,7 @@ def clean_nones(data: Any) -> Any: elif isinstance(data, list): return [clean_nones(v) for v in data if v is not None] return data -def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Optional[dict[str, Any]] = None) -> None: +def save_project(proj: Metadata, path: Union[str, Path], disc_data: Optional[Metadata] = None) -> None: """ Save the project TOML. If 'discussion' is present in proj, it is moved to the sibling history file. @@ -237,7 +250,7 @@ def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Option tomli_w.dump(disc_data, f) # ── migration helper ───────────────────────────────────────────────────────── -def migrate_from_legacy_config(cfg: dict[str, Any]) -> dict[str, Any]: +def migrate_from_legacy_config(cfg: Metadata) -> Metadata: """Build a fresh project dict from a legacy flat config.toml. Does NOT save.""" name = cfg.get("output", {}).get("namespace", "project") proj = default_project(name) @@ -251,7 +264,7 @@ def migrate_from_legacy_config(cfg: dict[str, Any]) -> dict[str, Any]: return proj # ── flat config for aggregate.run() ───────────────────────────────────────── -def flat_config(proj: dict[str, Any], disc_name: Optional[str] = None, track_id: Optional[str] = None) -> dict[str, Any]: +def flat_config(proj: Metadata, disc_name: Optional[str] = None, track_id: Optional[str] = None) -> Metadata: """Return a flat config dict compatible with aggregate.run().""" disc_sec = proj.get("discussion", {}) if track_id: @@ -326,7 +339,7 @@ def save_track_history(track_id: str, history: list[str], base_dir: Union[str, P state.discussion = entries save_track_state(track_id, state, base_dir) -def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: +def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[Metadata]: """ Scans the conductor/tracks/ directory and returns a list of dictionaries containing track metadata: 'id', 'title', 'status', 'complete', 'total', @@ -343,13 +356,13 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: if not tracks_dir.exists(): return [] from src.result_types import ErrorInfo, ErrorKind - results: list[dict[str, Any]] = [] + results: list[Metadata] = [] for entry in tracks_dir.iterdir(): if not entry.is_dir(): continue track_id = entry.name - track_errors: list[dict[str, Any]] = [] - track_info: dict[str, Any] = { + track_errors: list[Metadata] = [] + track_info: Metadata = { "id": track_id, "title": track_id, "status": "unknown",