refactor(sdm): Global pass with refined 'External Only' SDM tags. Pruned redundant internal references and fixed indentation logic in injector. Verified full project compilation.

This commit is contained in:
2026-05-09 14:32:44 -04:00
parent 696c08692e
commit 8c06c1767b
142 changed files with 2352 additions and 990 deletions
+4 -4
View File
@@ -1,6 +1,6 @@
[ai]
provider = "minimax"
model = "MiniMax-M2.5"
provider = "gemini"
model = "gemini-2.5-flash-lite"
temperature = 0.0
top_p = 1.0
max_tokens = 32000
@@ -24,7 +24,7 @@ bg_shader_enabled = false
crt_filter_enabled = false
separate_task_dag = false
separate_usage_analytics = false
separate_tier1 = false
separate_tier1 = true
separate_tier2 = false
separate_tier3 = false
separate_tier4 = false
@@ -36,7 +36,7 @@ separate_external_tools = false
"AI Settings" = true
"MMA Dashboard" = true
"Task DAG" = false
"Usage Analytics" = false
"Usage Analytics" = true
"Tier 1" = false
"Tier 2" = false
"Tier 3" = false
+1 -1
View File
@@ -9,5 +9,5 @@ active = "main"
[discussions.main]
git_commit = ""
last_updated = "2026-05-09T12:24:27"
last_updated = "2026-05-09T14:29:48"
history = []
Binary file not shown.
+3
View File
@@ -3,6 +3,9 @@ from simulation.sim_base import BaseSimulation, run_sim
class AISettingsSimulation(BaseSimulation):
def run(self) -> None:
"""
[C: simulation/sim_base.py:run_sim, 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]
"""
print("\n--- Running AI Settings Simulation (Gemini Only) ---")
# 1. Verify initial model
provider = self.client.get_value("current_provider")
+16 -1
View File
@@ -62,6 +62,9 @@ class BaseSimulation:
self.project_path = None
def setup(self, project_name: str = "SimProject") -> None:
"""
[C: simulation/sim_execution.py:ExecutionSimulation.setup, 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_sim_base.py:test_base_simulation_setup]
"""
print("\n[BaseSim] Connecting to GUI...")
if not self.client.wait_for_server(timeout=5):
raise RuntimeError("Could not connect to GUI. Ensure it is running with --enable-test-hooks")
@@ -88,6 +91,9 @@ class BaseSimulation:
time.sleep(1.5)
def teardown(self) -> None:
"""
[C: 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]
"""
if self.project_path and os.path.exists(self.project_path):
# We keep it for debugging if it failed, but usually we'd clean up
# os.remove(self.project_path)
@@ -95,9 +101,15 @@ class BaseSimulation:
print("[BaseSim] Teardown complete.")
def get_value(self, tag: str) -> Any:
"""
[C: 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_visual_mma.py:test_visual_mma_components, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration]
"""
return self.client.get_value(tag)
def wait_for_event(self, event_type: str, timeout: int = 5) -> Optional[dict]:
"""
[C: 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]
"""
return self.client.wait_for_event(event_type, timeout)
def assert_panel_visible(self, panel_tag: str, msg: str = None) -> None:
@@ -115,7 +127,10 @@ class BaseSimulation:
return False
def run_sim(sim_class: type) -> None:
"""Helper to run a simulation class standalone."""
"""
Helper to run a simulation class standalone.
[C: simulation/sim_context.py:module, simulation/sim_execution.py:module, simulation/sim_tools.py:module]
"""
sim = sim_class()
try:
sim.setup()
+3
View File
@@ -4,6 +4,9 @@ from simulation.sim_base import BaseSimulation, run_sim
class ContextSimulation(BaseSimulation):
def run(self) -> None:
"""
[C: 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]
"""
print("\n--- Running Context & Chat Simulation ---")
# 1. Skip Discussion Creation, use 'main'
print("[Sim] Using existing 'main' discussion")
+6
View File
@@ -4,11 +4,17 @@ from simulation.sim_base import BaseSimulation, run_sim
class ExecutionSimulation(BaseSimulation):
def setup(self, project_name: str = "SimProject") -> None:
"""
[C: 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_sim_base.py:test_base_simulation_setup]
"""
super().setup(project_name)
if os.path.exists("hello.ps1"):
os.remove("hello.ps1")
def run(self) -> None:
"""
[C: 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]
"""
print("\n--- Running Execution & Modals Simulation ---")
# 1. Trigger script generation (Async so we don't block on the wait loop)
msg = "Create a hello.ps1 script that prints 'Simulation Test' and execute it."
+3
View File
@@ -3,6 +3,9 @@ from simulation.sim_base import BaseSimulation, run_sim
class ToolsSimulation(BaseSimulation):
def run(self) -> None:
"""
[C: 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]
"""
print("\n--- Running Tools Simulation ---")
# 1. Trigger list_directory tool
msg = "List the files in the current directory."
+13
View File
@@ -22,16 +22,25 @@ class UserSimAgent:
return max(0.5, min(5.0, delay))
def wait_to_read(self, text: str) -> None:
"""
[C: simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response]
"""
if self.enable_delays:
delay = self.calculate_reading_delay(text)
time.sleep(delay)
def wait_to_think(self, probability: float = 0.2, min_delay: float = 2.0, max_delay: float = 5.0) -> None:
"""
[C: simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response]
"""
if self.enable_delays and random.random() < probability:
delay = random.uniform(min_delay, max_delay)
time.sleep(delay)
def simulate_typing(self, text: str, jitter_range: tuple[float, float] = (0.01, 0.05), batch_typing: bool = False) -> None:
"""
[C: simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async]
"""
if not self.enable_delays:
return
if batch_typing or self.batch_typing:
@@ -50,8 +59,10 @@ class UserSimAgent:
def generate_response(self, conversation_history: list[dict]) -> str:
"""
Generates a human-like response based on the conversation history.
conversation_history: list of dicts with 'role' and 'content'
[C: simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async]
"""
last_ai_msg = ""
for entry in reversed(conversation_history):
@@ -67,7 +78,9 @@ class UserSimAgent:
def perform_action_with_delay(self, action_func: Callable, *args: Any, **kwargs: Any) -> Any:
"""
Executes an action with a human-like delay if enabled.
[C: tests/test_user_agent.py:test_perform_action_with_delay]
"""
if self.enable_delays:
delay = random.uniform(0.5, 2.0)
+12
View File
@@ -49,6 +49,9 @@ class WorkflowSimulator:
self.user_agent = UserSimAgent(hook_client)
def setup_new_project(self, name: str, git_dir: str, project_path: str = None) -> None:
"""
[C: tests/test_workflow_sim.py:test_setup_new_project]
"""
print(f"Setting up new project: {name}")
if project_path:
self.client.click("btn_project_new_automated", user_data=project_path)
@@ -62,6 +65,9 @@ class WorkflowSimulator:
self.client.set_value("auto_add_history", True)
def create_discussion(self, name: str) -> None:
"""
[C: tests/test_workflow_sim.py:test_discussion_switching]
"""
print(f"Creating discussion: {name}")
self.client.set_value("disc_new_name_input", name)
self.client.click("btn_disc_create")
@@ -69,6 +75,9 @@ class WorkflowSimulator:
time.sleep(2)
def switch_discussion(self, name: str) -> None:
"""
[C: tests/test_workflow_sim.py:test_discussion_switching]
"""
print(f"Switching to discussion: {name}")
self.client.select_list_item("disc_listbox", name)
time.sleep(1)
@@ -81,6 +90,9 @@ class WorkflowSimulator:
time.sleep(1)
def truncate_history(self, pairs: int) -> None:
"""
[C: tests/test_workflow_sim.py:test_history_truncation]
"""
print(f"Truncating history to {pairs} pairs")
self.client.set_value("disc_truncate_pairs", pairs)
self.client.click("btn_disc_truncate")
+9
View File
@@ -62,6 +62,7 @@ def resolve_paths(base_dir: Path, entry: str) -> list[Path]:
def build_discussion_section(history: list[Any]) -> str:
"""
Builds a markdown section for discussion history.
Handles both legacy list[str] and new list[dict].
"""
@@ -125,6 +126,7 @@ def build_screenshots_section(base_dir: Path, screenshots: list[str]) -> str:
def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[dict[str, Any]]:
"""
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.
@@ -182,6 +184,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
def build_summary_section(base_dir: Path, files: list[str | dict[str, Any]]) -> str:
"""
Build a compact summary section using summarize.py — one short block per file.
Used as the initial <context> block instead of full file contents.
"""
@@ -191,6 +194,7 @@ def build_summary_section(base_dir: Path, files: list[str | dict[str, Any]]) ->
def _build_files_section_from_items(file_items: list[dict[str, Any]]) -> 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_ui_summary_only_removal.py:test_aggregate_from_items_respects_auto_aggregate]
"""
@@ -261,6 +265,7 @@ def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_
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:
"""
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]
"""
@@ -268,6 +273,7 @@ def build_markdown_no_history(file_items: list[dict[str, Any]], screenshot_base_
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]
"""
@@ -278,6 +284,7 @@ def build_discussion_text(history: list[str]) -> str:
def build_tier1_context(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str]) -> str:
"""
Tier 1 Context: Strategic/Orchestration.
Full content for core conductor files and files with tier=1, summaries for others.
[C: tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_aggregate_flags.py:test_force_full, tests/test_tiered_context.py:test_build_tier1_context_exists, tests/test_tiered_context.py:test_tiered_context_by_tier_field]
@@ -311,6 +318,7 @@ def build_tier1_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
def build_tier2_context(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str]) -> str:
"""
Tier 2 Context: Architectural/Tech Lead.
Full content for all files (standard behavior).
[C: tests/test_tiered_context.py:test_build_tier2_context_exists]
@@ -320,6 +328,7 @@ def build_tier2_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
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:
"""
Tier 3 Context: Execution/Worker.
Full content for focus_files and files with tier=3, summaries/skeletons for others.
[C: tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_aggregate_flags.py:test_force_full, tests/test_perf_aggregate.py:test_build_tier3_context_scaling, tests/test_tiered_context.py:test_build_tier3_context_ast_skeleton, tests/test_tiered_context.py:test_build_tier3_context_exists, tests/test_tiered_context.py:test_tiered_context_by_tier_field]
+142 -15
View File
@@ -51,7 +51,10 @@ _history_trunc_limit: int = 8000
events: EventEmitter = EventEmitter()
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: float = 1.0) -> None:
"""Sets global generation parameters like temperature and max tokens."""
"""
Sets global generation parameters like temperature and max tokens.
[C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, tests/test_history_management.py:test_get_history_bleed_stats_basic]
"""
global _temperature, _max_tokens, _history_trunc_limit, _top_p
_temperature = temp
_max_tokens = max_tok
@@ -111,21 +114,33 @@ _local_storage = threading.local()
_tool_approval_modes: dict[str, str] = {}
def get_current_tier() -> Optional[str]:
"""Returns the current tier from thread-local storage."""
"""
Returns the current tier from thread-local storage.
[C: src/app_controller.py:AppController._on_tool_log, tests/test_ai_client_concurrency.py:intercepted_append]
"""
return getattr(_local_storage, "current_tier", None)
def set_current_tier(tier: Optional[str]) -> None:
"""Sets the current tier in thread-local storage."""
"""
Sets the current tier in thread-local storage.
[C: src/app_controller.py:AppController._handle_request_event, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:reset_tier, 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]
"""
_local_storage.current_tier = tier
def get_comms_log_callback() -> Optional[Callable[[dict[str, Any]], None]]:
"""Returns the comms log callback (thread-local with global fallback)."""
"""
Returns the comms log callback (thread-local with global fallback).
[C: src/multi_agent_conductor.py:run_worker_lifecycle]
"""
tl_cb = getattr(_local_storage, "comms_log_callback", None)
if tl_cb: return tl_cb
return comms_log_callback
def set_comms_log_callback(cb: Optional[Callable[[dict[str, Any]], None]]) -> None:
"""Sets the comms log callback (both global and thread-local)."""
"""
Sets the comms log callback (both global and thread-local).
[C: src/app_controller.py:AppController._init_ai_and_hooks, src/multi_agent_conductor.py:run_worker_lifecycle]
"""
global comms_log_callback
comms_log_callback = cb
_local_storage.comms_log_callback = cb
@@ -161,19 +176,31 @@ _use_default_base_system_prompt: bool = True
_project_context_marker: str = ""
def set_custom_system_prompt(prompt: str) -> None:
"""Sets a custom system prompt to be combined with the default instructions."""
"""
Sets a custom system prompt to be combined with the default instructions.
[C: simulation/user_agent.py:UserSimAgent.generate_response, src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp]
"""
global _custom_system_prompt
_custom_system_prompt = prompt
def set_base_system_prompt(prompt: str) -> None:
"""
[C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false]
"""
global _base_system_prompt_override
_base_system_prompt_override = prompt
def set_use_default_base_prompt(use_default: bool) -> None:
"""
[C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false]
"""
global _use_default_base_system_prompt
_use_default_base_system_prompt = use_default
def set_project_context_marker(marker: str) -> None:
"""
[C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate]
"""
global _project_context_marker
_project_context_marker = marker
@@ -181,6 +208,9 @@ def _get_context_marker() -> str:
return _project_context_marker if _project_context_marker.strip() else "[SYSTEM: FILES UPDATED]"
def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str:
"""
[C: tests/test_bias_efficacy.py:test_bias_efficacy_prompt_generation, tests/test_bias_integration.py:test_system_prompt_biasing, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false]
"""
if preset is None: preset = _active_tool_preset
if bias is None: bias = _active_bias_profile
if _use_default_base_system_prompt:
@@ -196,6 +226,9 @@ def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optio
return base
def get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str:
"""
[C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event]
"""
return _get_combined_system_prompt(preset, bias)
from collections import deque
@@ -205,6 +238,9 @@ _comms_log: deque[dict[str, Any]] = deque(maxlen=1000)
COMMS_CLAMP_CHARS: int = 300
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
"""
[C: tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, 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]
"""
entry: dict[str, Any] = {
"ts": datetime.datetime.now().strftime("%H:%M:%S"),
"direction": direction,
@@ -221,15 +257,27 @@ def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
_cb(entry)
def get_comms_log() -> list[dict[str, Any]]:
"""
[C: src/app_controller.py:AppController._bg_task, src/app_controller.py:AppController._recalculate_session_usage, src/app_controller.py:AppController._start_track_logic, src/multi_agent_conductor.py:run_worker_lifecycle, 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_token_usage.py:test_token_usage_tracking]
"""
return list(_comms_log)
def clear_comms_log() -> None:
"""
[C: src/app_controller.py:AppController._handle_reset_session, src/gui_2.py:App._render_comms_history_panel, src/gui_2.py:App._show_menus, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_token_usage.py:test_token_usage_tracking]
"""
_comms_log.clear()
def get_credentials_path() -> Path:
"""
[C: src/mcp_client.py:_is_allowed]
"""
return Path(os.environ.get("SLOP_CREDENTIALS", str(Path(__file__).parent.parent / "credentials.toml")))
def _load_credentials() -> dict[str, Any]:
"""
[C: tests/test_deepseek_infra.py:test_credentials_error_mentions_deepseek, tests/test_minimax_provider.py:test_minimax_credentials_template]
"""
cred_path = get_credentials_path()
try:
with open(cred_path, "rb") as f:
@@ -247,12 +295,18 @@ def _load_credentials() -> dict[str, Any]:
class ProviderError(Exception):
def __init__(self, kind: str, provider: str, original: Exception) -> None:
"""
[C: src/api_hooks.py:HookServerInstance.__init__, src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.kind = kind
self.provider = provider
self.original = original
super().__init__(str(original))
def ui_message(self) -> str:
"""
[C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate]
"""
labels = {
"quota": "QUOTA EXHAUSTED",
"rate_limit": "RATE LIMITED",
@@ -382,7 +436,10 @@ def _classify_minimax_error(exc: Exception) -> ProviderError:
return ProviderError("unknown", "minimax", Exception(body))
def set_provider(provider: str, model: str) -> None:
"""Updates the active LLM provider and model name."""
"""
Updates the active LLM provider and model name.
[C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.do_fetch, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, 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_completion_logic, tests/test_deepseek_provider.py:test_deepseek_model_selection, 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_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_get_history_bleed_stats, 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_history_management.py:test_get_history_bleed_stats_basic, tests/test_minimax_provider.py:test_minimax_default_model, tests/test_minimax_provider.py:test_minimax_history_bleed_stats, tests/test_minimax_provider.py:test_minimax_model_selection, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_rag_integration.py:test_rag_integration, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script, tests/test_token_usage.py:test_token_usage_tracking, tests/test_token_viz.py:test_get_history_bleed_stats_returns_all_keys_unknown_provider]
"""
global _provider, _model
_provider = provider
if provider == "gemini_cli":
@@ -401,11 +458,17 @@ def set_provider(provider: str, model: str) -> None:
_model = model
def get_provider() -> str:
"""Returns the current active provider name."""
"""
Returns the current active provider name.
[C: src/multi_agent_conductor.py:run_worker_lifecycle]
"""
return _provider
def cleanup() -> None:
"""Performs cleanup operations like deleting server-side Gemini caches."""
"""
Performs cleanup operations like deleting server-side Gemini caches.
[C: src/app_controller.py:AppController.clear_cache, src/app_controller.py:AppController.shutdown, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking_cleanup, tests/test_log_registry.py:TestLogRegistry.tearDown, tests/test_project_serialization.py:TestProjectSerialization.tearDown]
"""
global _gemini_client, _gemini_cache, _gemini_cached_file_paths
if _gemini_client and _gemini_cache:
try:
@@ -415,7 +478,10 @@ def cleanup() -> None:
_gemini_cached_file_paths = []
def reset_session() -> None:
"""Clears conversation history and resets provider-specific session state."""
"""
Clears conversation history and resets provider-specific session state.
[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_minimax_provider.py:test_minimax_history_bleed_stats, 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]
"""
global _gemini_client, _gemini_chat, _gemini_cache
global _gemini_cache_md_hash, _gemini_cache_created_at, _gemini_cached_file_paths
global _anthropic_client, _anthropic_history
@@ -454,6 +520,9 @@ def reset_session() -> None:
file_cache.reset_client()
def get_gemini_cache_stats() -> dict[str, Any]:
"""
[C: src/app_controller.py:AppController._recalculate_session_usage, src/app_controller.py:AppController._update_cached_stats, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_gemini_metrics.py:test_get_gemini_cache_stats_with_mock_client]
"""
_ensure_gemini_client()
if not _gemini_client:
return {"cache_count": 0, "total_size_bytes": 0, "cached_files": []}
@@ -467,6 +536,9 @@ def get_gemini_cache_stats() -> dict[str, Any]:
}
def list_models(provider: str) -> list[str]:
"""
[C: src/app_controller.py:AppController.do_fetch, tests/test_agent_capabilities.py:test_agent_capabilities_listing, tests/test_ai_client_list_models.py:test_list_models_gemini_cli, tests/test_deepseek_infra.py:test_deepseek_model_listing, tests/test_minimax_provider.py:test_minimax_list_models]
"""
creds = _load_credentials()
if provider == "gemini":
return _list_gemini_models(creds["gemini"]["api_key"])
@@ -526,14 +598,20 @@ TOOL_NAME: str = "run_powershell"
_agent_tools: dict[str, bool] = {}
def set_agent_tools(tools: dict[str, bool]) -> None:
"""Configures which tools are enabled for the AI agent."""
"""
Configures which tools are enabled for the AI agent.
[C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, tests/test_agent_tools_wiring.py:test_build_anthropic_tools_conversion, tests/test_agent_tools_wiring.py:test_set_agent_tools, tests/test_tool_access_exclusion.py:test_build_anthropic_tools_excludes_disabled, tests/test_tool_access_exclusion.py:test_build_deepseek_tools_excludes_disabled, tests/test_tool_access_exclusion.py:test_gemini_tool_declaration_excludes_disabled, tests/test_tool_access_exclusion.py:test_set_agent_tools_clears_caches]
"""
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS
_agent_tools = tools
_CACHED_ANTHROPIC_TOOLS = None
_CACHED_DEEPSEEK_TOOLS = None
def set_tool_preset(preset_name: Optional[str]) -> None:
"""Loads a tool preset and applies it via set_agent_tools."""
"""
Loads a tool preset and applies it via set_agent_tools.
[C: src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_persona_selector_panel, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_bias_integration.py:test_set_tool_preset_with_objects, tests/test_tool_preset_env.py:test_tool_preset_env_loading, tests/test_tool_preset_env.py:test_tool_preset_env_no_var, tests/test_tool_presets_execution.py:test_tool_ask_approval, tests/test_tool_presets_execution.py:test_tool_auto_approval, tests/test_tool_presets_execution.py:test_tool_rejection]
"""
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS, _tool_approval_modes, _active_tool_preset
_tool_approval_modes = {}
if not preset_name or preset_name == "None":
@@ -564,7 +642,10 @@ def set_tool_preset(preset_name: Optional[str]) -> None:
_CACHED_DEEPSEEK_TOOLS = None
def set_bias_profile(profile_name: Optional[str]) -> None:
"""Sets the active tool bias profile for tuning model behavior."""
"""
Sets the active tool bias profile for tuning model behavior.
[C: src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_agent_tools_panel, src/gui_2.py:App._render_persona_selector_panel, src/multi_agent_conductor.py:run_worker_lifecycle]
"""
global _active_bias_profile
if not profile_name or profile_name == "None":
_active_bias_profile = None
@@ -584,6 +665,9 @@ def get_bias_profile() -> Optional[str]:
return _active_bias_profile.name if _active_bias_profile else None
def _build_anthropic_tools() -> list[dict[str, Any]]:
"""
[C: tests/test_agent_tools_wiring.py:test_build_anthropic_tools_conversion, tests/test_tool_access_exclusion.py:test_build_anthropic_tools_excludes_disabled]
"""
raw_tools: list[dict[str, Any]] = []
for spec in mcp_client.get_tool_schemas():
if _agent_tools.get(spec["name"], True):
@@ -622,12 +706,18 @@ def _build_anthropic_tools() -> list[dict[str, Any]]:
_CACHED_ANTHROPIC_TOOLS: Optional[list[dict[str, Any]]] = None
def _get_anthropic_tools() -> list[dict[str, Any]]:
"""
[C: tests/test_bias_efficacy.py:test_bias_efficacy_prompt_generation, tests/test_bias_efficacy.py:test_bias_parameter_nudging, tests/test_bias_integration.py:test_tool_declaration_biasing_anthropic]
"""
global _CACHED_ANTHROPIC_TOOLS
if _CACHED_ANTHROPIC_TOOLS is None:
_CACHED_ANTHROPIC_TOOLS = _build_anthropic_tools()
return _CACHED_ANTHROPIC_TOOLS
def _gemini_tool_declaration() -> Optional[types.Tool]:
"""
[C: tests/test_tool_access_exclusion.py:test_gemini_tool_declaration_excludes_disabled]
"""
raw_tools: list[dict[str, Any]] = []
for spec in mcp_client.get_tool_schemas():
if _agent_tools.get(spec["name"], True):
@@ -691,8 +781,10 @@ async def _execute_tool_calls_concurrently(
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None
) -> list[tuple[str, str, str, str]]: # tool_name, call_id, output, original_name
"""
Executes multiple tool calls concurrently using asyncio.gather.
Returns a list of (tool_name, call_id, output, original_name).
[C: tests/test_async_tools.py:test_execute_tool_calls_concurrently_exception_handling, tests/test_async_tools.py:test_execute_tool_calls_concurrently_timing]
"""
monitor = performance_monitor.get_monitor()
if monitor.enabled: monitor.start_component("ai_client._execute_tool_calls_concurrently")
@@ -739,6 +831,9 @@ async def _execute_single_tool_call_async(
tier: str | None = None,
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None
) -> tuple[str, str, str, str]:
"""
[C: tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call, tests/test_external_mcp_hitl.py:test_external_mcp_hitl_approval, tests/test_external_mcp_hitl.py:test_external_mcp_hitl_rejection, tests/test_tool_presets_execution.py:test_tool_ask_approval, tests/test_tool_presets_execution.py:test_tool_auto_approval, tests/test_tool_presets_execution.py:test_tool_rejection]
"""
if tier:
set_current_tier(tier)
out = ""
@@ -870,6 +965,9 @@ def _build_file_diff_text(changed_items: list[dict[str, Any]]) -> str:
return "\n\n---\n\n".join(parts)
def _build_deepseek_tools() -> list[dict[str, Any]]:
"""
[C: tests/test_tool_access_exclusion.py:test_build_deepseek_tools_excludes_disabled]
"""
raw_tools: list[dict[str, Any]] = []
for spec in mcp_client.get_tool_schemas():
if _agent_tools.get(spec["name"], True):
@@ -936,6 +1034,9 @@ def _content_block_to_dict(block: Any) -> dict[str, Any]:
return {"type": "text", "text": str(block)}
def _ensure_gemini_client() -> None:
"""
[C: src/rag_engine.py:GeminiEmbeddingProvider.embed]
"""
global _gemini_client
if _gemini_client is None:
creds = _load_credentials()
@@ -959,6 +1060,9 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str,
enable_tools: bool = True,
stream_callback: Optional[Callable[[str], None]] = None,
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
"""
[C: tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script]
"""
global _gemini_chat, _gemini_cache, _gemini_cache_md_hash, _gemini_cache_created_at, _gemini_cached_file_paths
monitor = performance_monitor.get_monitor()
if monitor.enabled: monitor.start_component("ai_client._send_gemini")
@@ -1399,6 +1503,9 @@ def _ensure_anthropic_client() -> None:
)
def _chunk_text(text: str, chunk_size: int) -> list[str]:
"""
[C: src/rag_engine.py:RAGEngine._chunk_code, src/rag_engine.py:RAGEngine.index_file]
"""
return [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]
def _build_chunked_context_blocks(md_content: str) -> list[dict[str, Any]]:
@@ -2144,6 +2251,9 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
def run_tier4_analysis(stderr: str) -> str:
"""
[C: src/native_orchestrator.py:NativeOrchestrator.analyze_error]
"""
if not stderr or not stderr.strip():
return ""
try:
@@ -2189,6 +2299,9 @@ def run_tier4_patch_callback(stderr: str, base_dir: str) -> Optional[str]:
def run_tier4_patch_generation(error: str, file_context: str) -> str:
"""
[C: src/gui_2.py:App.request_patch_from_tier4, src/native_orchestrator.py:NativeOrchestrator.run_tier4_patch, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_calls_ai, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_empty_error, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_returns_diff]
"""
if not error or not error.strip():
return ""
try:
@@ -2215,6 +2328,9 @@ def run_tier4_patch_generation(error: str, file_context: str) -> str:
return f"[PATCH GENERATION FAILED] {e}"
def get_token_stats(md_content: str) -> dict[str, Any]:
"""
[C: src/app_controller.py:AppController._refresh_api_metrics]
"""
global _provider, _gemini_client, _model, _CHARS_PER_TOKEN
total_tokens = 0
if _provider == "gemini":
@@ -2261,6 +2377,9 @@ def send(
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None,
rag_engine: Optional[Any] = None,
) -> str:
"""
[C: simulation/user_agent.py:UserSimAgent.generate_response, src/api_hooks.py:WebSocketServer._handler, src/api_hooks.py:WebSocketServer.broadcast, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:AppController.generate, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, src/native_orchestrator.py:NativeOrchestrator.execute_ticket, 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_get_history_bleed_stats, 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]
"""
monitor = performance_monitor.get_monitor()
if monitor.enabled: monitor.start_component("ai_client.send")
@@ -2307,6 +2426,9 @@ def send(
return res
def _add_bleed_derived(d: dict[str, Any], sys_tok: int = 0, tool_tok: int = 0) -> dict[str, Any]:
"""
[C: tests/test_token_viz.py:test_add_bleed_derived_aliases, tests/test_token_viz.py:test_add_bleed_derived_breakdown, tests/test_token_viz.py:test_add_bleed_derived_headroom, tests/test_token_viz.py:test_add_bleed_derived_headroom_clamped_to_zero, tests/test_token_viz.py:test_add_bleed_derived_history_clamped_to_zero, tests/test_token_viz.py:test_add_bleed_derived_would_trim_false, tests/test_token_viz.py:test_add_bleed_derived_would_trim_true, tests/test_token_viz.py:test_would_trim_boundary_exact, tests/test_token_viz.py:test_would_trim_just_above_threshold, tests/test_token_viz.py:test_would_trim_just_below_threshold]
"""
cur = d.get("current", 0)
lim = d.get("limit", 0)
d["estimated_prompt_tokens"] = cur
@@ -2326,6 +2448,9 @@ if os.environ.get("SLOP_TOOL_PRESET"):
pass
def get_history_bleed_stats(md_content: Optional[str] = None) -> dict[str, Any]:
"""
[C: tests/test_gemini_cli_parity_regression.py:test_get_history_bleed_stats, tests/test_history_management.py:test_get_history_bleed_stats_basic, tests/test_minimax_provider.py:test_minimax_history_bleed_stats, tests/test_token_viz.py:test_get_history_bleed_stats_returns_all_keys_unknown_provider]
"""
if _provider == "anthropic":
with _anthropic_history_lock:
history_snapshot = list(_anthropic_history)
@@ -2475,7 +2600,10 @@ def get_history_bleed_stats(md_content: Optional[str] = None) -> dict[str, Any]:
})
def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str:
"""Performs a stateless summarization request using a sub-agent prompt."""
"""
Performs a stateless summarization request using a sub-agent prompt.
[C: src/summarize.py:summarise_file, tests/test_subagent_summarization.py:test_run_subagent_summarization_anthropic, tests/test_subagent_summarization.py:test_run_subagent_summarization_gemini]
"""
prompt_tmpl = mma_prompts.TIER4_SUMMARIZE_CODE_PROMPT if is_code else mma_prompts.TIER4_SUMMARIZE_TEXT_PROMPT
prompt = prompt_tmpl.format(file_path=file_path, outline=outline, content=content)
if _provider == "gemini":
@@ -2522,4 +2650,3 @@ def run_subagent_summarization(file_path: str, content: str, is_code: bool, outl
resp_data = adapter.send(prompt, model=_model)
return resp_data.get("text", "")
return "ERROR: Unsupported provider for sub-agent summarization"
+136 -33
View File
@@ -38,11 +38,17 @@ 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) -> dict[str, Any] | None:
"""Helper to make HTTP requests to the hook server."""
"""
Helper to make HTTP requests to the hook server.
[C: tests/test_api_hook_client.py:test_unsupported_method_error]
"""
url = f"{self.base_url}{path}"
headers = {}
if self.api_key:
@@ -64,7 +70,10 @@ class ApiHookClient:
return None
def wait_for_server(self, timeout: int = 15) -> bool:
"""Polls the health endpoint until the server responds or timeout occurs."""
"""
Polls the health endpoint until the server responds or timeout occurs.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_conductor_api_hook_integration.py:test_conductor_integrates_api_hook_client_for_verification, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_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_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
start = time.time()
while time.time() - start < timeout:
status = self.get_status()
@@ -74,7 +83,10 @@ class ApiHookClient:
return False
def get_status(self) -> dict[str, Any]:
"""Checks the health of the hook server."""
"""
Checks the health of the hook server.
[C: tests/test_api_hook_client.py:test_get_status_success, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_preset_windows_layout.py:make_request, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls]
"""
res = self._make_request('GET', '/status')
if res is None:
# For backward compatibility with tests expecting ConnectionError
@@ -83,19 +95,31 @@ class ApiHookClient:
return res
def post_project(self, project_data: dict) -> dict[str, Any]:
"""Updates the current project configuration."""
"""
Updates the current project configuration.
[C: simulation/sim_context.py:ContextSimulation.run]
"""
return self._make_request('POST', '/api/project', data=project_data) or {}
def get_project(self) -> dict[str, Any]:
"""Retrieves the current project state."""
"""
Retrieves the current project state.
[C: simulation/sim_context.py:ContextSimulation.run, tests/test_api_hook_client.py:test_get_project_success, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self._make_request('GET', '/api/project') or {}
def get_session(self) -> dict[str, Any]:
"""Retrieves the current discussion session history."""
"""
Retrieves the current discussion session history.
[C: simulation/ping_pong.py:main, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/sim_tools.py:ToolsSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_api_hook_client.py:test_get_session_success, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim]
"""
return self._make_request('GET', '/api/session') or {}
def post_session(self, session_entries: list[dict]) -> dict[str, Any]:
"""Updates the session history."""
"""
Updates the session history.
[C: tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {}
def get_events(self) -> list[dict[str, Any]]:
@@ -104,11 +128,17 @@ class ApiHookClient:
return res.get("events", []) if res else []
def clear_events(self) -> list[dict[str, Any]]:
"""Retrieves and clears the event queue."""
"""
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:
"""
[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()
@@ -119,35 +149,59 @@ class ApiHookClient:
return None
def post_gui(self, payload: dict) -> dict[str, Any]:
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
"""
Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint.
[C: tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_api_hook_client.py:test_post_gui_success, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_visual_mma.py:test_visual_mma_components]
"""
return self._make_request('POST', '/api/gui', data=payload) or {}
def push_event(self, action: str, payload: dict) -> dict[str, Any]:
"""Convenience to push a GUI task."""
"""
Convenience to push a GUI task.
[C: tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_auto_switch_sim.py:trigger_tier, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
return self.post_gui({"action": action, **payload})
def click(self, item: str, user_data: Any = None) -> dict[str, Any]:
"""Simulates a button click."""
"""
Simulates a button click.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.load_prior_log, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_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_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_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, 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 set_value(self, item: str, value: Any) -> dict[str, Any]:
"""Sets the value of a GUI widget."""
"""
Sets the value of a GUI widget.
[C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_ai_settings.py:AISettingsSimulation.run, simulation/sim_base.py:BaseSimulation.setup, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
"""
return self.post_gui({"action": "set_value", "item": item, "value": value})
def select_tab(self, item: str, value: str) -> dict[str, Any]:
"""Selects a specific tab in a tab bar."""
"""
Selects a specific tab in a tab bar.
[C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_select_tab_integration]
"""
return self.set_value(item, value)
def select_list_item(self, item: str, value: str) -> dict[str, Any]:
"""Selects an item in a listbox or combo."""
"""
Selects an item in a listbox or combo.
[C: simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.switch_discussion, tests/test_api_hook_extensions.py:test_select_list_item_integration, tests/test_live_workflow.py:test_full_live_workflow]
"""
return self.set_value(item, value)
def get_gui_state(self) -> dict[str, Any]:
"""Returns the full GUI state available via the hook API."""
"""
Returns the full GUI state available via the hook API.
[C: tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_conductor_api_hook_integration.py:simulate_conductor_phase_completion, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_live_workflow.py:wait_for_value, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_management_layout.py:test_tool_management_gettable_fields, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout]
"""
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."""
"""
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_visual_mma.py:test_visual_mma_components, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration]
"""
# Try state endpoint first (new preferred way)
state = self.get_gui_state()
if item in state:
@@ -168,33 +222,54 @@ class ApiHookClient:
return None
def get_text_value(self, item_tag: str) -> str | None:
"""Wraps get_value and returns its string representation, or None."""
"""
Wraps get_value and returns its string representation, or None.
[C: tests/test_api_hook_client.py:test_get_text_value]
"""
val = self.get_value(item_tag)
return str(val) if val is not None else None
def get_indicator_state(self, item_tag: str) -> dict[str, bool]:
"""Returns the visibility/active state of a status indicator."""
"""
Returns the visibility/active state of a status indicator.
[C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_get_indicator_state_integration, tests/test_live_workflow.py:test_full_live_workflow]
"""
val = self.get_value(item_tag)
return {"shown": bool(val)}
def get_gui_diagnostics(self) -> dict[str, Any]:
"""Retrieves performance and diagnostic metrics."""
"""
Retrieves performance and diagnostic metrics.
[C: tests/test_api_hook_client.py:test_get_performance_success, tests/test_hooks.py:test_live_hook_server_responses, tests/test_selectable_ui.py:test_selectable_label_stability]
"""
return self._make_request('GET', '/api/gui/diagnostics') or {}
def get_performance(self) -> dict[str, Any]:
"""Retrieves performance metrics from the dedicated endpoint."""
"""
Retrieves performance metrics from the dedicated endpoint.
[C: tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_gui_performance_requirements.py:test_idle_performance_requirements, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing]
"""
return self._make_request('GET', '/api/performance') or {}
def get_mma_status(self) -> dict[str, Any]:
"""Retrieves the dedicated MMA engine status."""
"""
Retrieves the dedicated MMA engine status.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:_poll_mma_status, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:_poll_mma_status, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_visual_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]:
"""Retrieves status for all active MMA workers."""
"""
Retrieves status for all active MMA workers.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:_poll_mma_workers]
"""
return self._make_request('GET', '/api/mma/workers') or {}
def get_context_state(self) -> dict[str, Any]:
"""Retrieves the current file and screenshot context state."""
"""
Retrieves the current file and screenshot context state.
[C: tests/test_gui_context_presets.py:test_gui_context_preset_save_load]
"""
return self._make_request('GET', '/api/context/state') or {}
def get_financial_metrics(self) -> dict[str, Any]:
@@ -206,13 +281,18 @@ class ApiHookClient:
return self._make_request('GET', '/api/system/telemetry') or {}
def get_node_status(self, node_id: str) -> dict[str, Any]:
"""Retrieves status for a specific node in the MMA DAG."""
"""
Retrieves status for a specific node in the MMA DAG.
[C: tests/test_api_hook_client.py:test_get_node_status]
"""
return self._make_request('GET', f'/api/mma/node/{node_id}') or {}
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',
@@ -221,7 +301,10 @@ class ApiHookClient:
return res.get('response') if res else None
def reset_session(self) -> None:
"""Resets the current session via button click."""
"""
Resets the current session via button click.
[C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_provider_panel, src/gui_2.py:App._show_menus, src/multi_agent_conductor.py:run_worker_lifecycle, tests/conftest.py:live_gui, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_metrics.py:test_get_gemini_cache_stats_with_mock_client, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_minimax_provider.py:test_minimax_history_bleed_stats, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_session_logger_reset.py:test_reset_session, tests/test_token_usage.py:test_token_usage_tracking]
"""
self.click("btn_reset")
def trigger_patch(self, patch_text: str, file_paths: list[str]) -> dict[str, Any]:
@@ -232,11 +315,17 @@ class ApiHookClient:
}) or {}
def apply_patch(self) -> dict[str, Any]:
"""Applies the pending patch."""
"""
Applies the pending patch.
[C: tests/test_patch_modal.py:test_apply_callback]
"""
return self._make_request('POST', '/api/patch/apply') or {}
def reject_patch(self) -> dict[str, Any]:
"""Rejects the pending patch."""
"""
Rejects the pending patch.
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
"""
return self._make_request('POST', '/api/patch/reject') or {}
def get_patch_status(self) -> dict[str, Any]:
@@ -244,7 +333,10 @@ class ApiHookClient:
return self._make_request('GET', '/api/patch/status') or {}
def spawn_mma_worker(self, data: dict) -> dict:
"""Spawns a new MMA worker with the provided configuration."""
"""
Spawns a new MMA worker with the provided configuration.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/mma/workers/spawn', data=data) or {}
def kill_mma_worker(self, worker_id: str) -> dict:
@@ -252,7 +344,10 @@ 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."""
"""
Pauses the MMA execution pipeline.
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
"""
return self._make_request('POST', '/api/mma/pipeline/pause') or {}
def resume_mma_pipeline(self) -> dict:
@@ -260,14 +355,22 @@ class ApiHookClient:
return self._make_request('POST', '/api/mma/pipeline/resume') or {}
def inject_context(self, data: dict) -> dict:
"""Injects custom file context into the application."""
"""
Injects custom file context into the application.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/context/inject', data=data) or {}
def mutate_mma_dag(self, data: dict) -> dict:
"""Mutates the MMA DAG (Directed Acyclic Graph) structure."""
"""
Mutates the MMA DAG (Directed Acyclic Graph) structure.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
return self._make_request('POST', '/api/mma/dag/mutate', data=data) or {}
def approve_mma_ticket(self, ticket_id: str) -> dict:
"""Manually approves a specific ticket for execution in Step Mode."""
"""
Manually approves a specific ticket for execution in Step Mode.
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
"""
return self._make_request('POST', '/api/mma/ticket/approve', data={"ticket_id": ticket_id}) or {}
+40 -2
View File
@@ -69,7 +69,10 @@ def _set_app_attr(app: Any, name: str, value: Any) -> None:
class HookServerInstance(ThreadingHTTPServer):
"""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."""
"""
Initializes the server instance with an app reference.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
super().__init__(server_address, RequestHandlerClass)
self.app = app
@@ -410,6 +413,9 @@ class HookHandler(BaseHTTPRequestHandler):
event = threading.Event()
result = {"status": "done"}
def apply_patch():
"""
[C: tests/test_patch_modal.py:test_apply_callback]
"""
try:
if hasattr(app, "_apply_pending_patch"):
app._apply_pending_patch()
@@ -436,6 +442,9 @@ class HookHandler(BaseHTTPRequestHandler):
event = threading.Event()
result = {"status": "done"}
def reject_patch():
"""
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
"""
try:
app._show_patch_modal = False
app._pending_patch_text = None
@@ -552,6 +561,9 @@ class HookHandler(BaseHTTPRequestHandler):
self.wfile.write(json.dumps({"status": "queued"}).encode("utf-8"))
elif self.path == "/api/mma/workers/kill":
def kill_worker():
"""
[C: src/app_controller.py:AppController.kill_worker, src/gui_2.py:App._cb_kill_ticket, tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread]
"""
try:
worker_id = data.get("worker_id")
func = _get_app_attr(app, "_kill_worker")
@@ -591,6 +603,9 @@ class HookHandler(BaseHTTPRequestHandler):
self.wfile.write(json.dumps({"status": "queued"}).encode("utf-8"))
elif self.path == "/api/context/inject":
def inject_context():
"""
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
files = _get_app_attr(app, "files")
if isinstance(files, list):
files.extend(data.get("files", []))
@@ -650,6 +665,9 @@ class HookHandler(BaseHTTPRequestHandler):
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
@@ -657,6 +675,9 @@ 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.start_services, src/gui_2.py:App._render_discussion_panel, 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'
@@ -682,6 +703,9 @@ 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:
@@ -694,6 +718,9 @@ 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()}
@@ -726,27 +753,38 @@ class WebSocketServer:
asyncio.set_event_loop(self.loop)
self._stop_event = asyncio.Event()
async def main():
"""
[C: simulation/live_walkthrough.py:module, simulation/ping_pong.py:module, src/gui_2.py:module, tests/mock_concurrent_mma.py:module, tests/mock_gemini_cli.py:module, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_allow_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_deny_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_unreachable_hook_server, tests/test_cli_tool_bridge.py:module, tests/test_cli_tool_bridge_mapping.py:TestCliToolBridgeMapping.test_mapping_from_api_format, tests/test_cli_tool_bridge_mapping.py:module, tests/test_discussion_takes.py:module, tests/test_external_editor_gui.py:module, tests/test_headless_service.py:TestHeadlessStartup.test_headless_flag_triggers_run, tests/test_headless_service.py:TestHeadlessStartup.test_normal_startup_calls_app_run, tests/test_mma_skeleton.py:module, tests/test_orchestrator_pm.py:module, tests/test_orchestrator_pm_history.py:module, tests/test_post_process.py:module, tests/test_presets.py:module, tests/test_project_serialization.py:module, tests/test_run_worker_lifecycle_abort.py:module, tests/test_symbol_lookup.py:module, tests/test_system_prompt_exposure.py:module, tests/test_theme_nerv_fx.py:module]
"""
async with serve(self._handler, "127.0.0.1", self.port) as server:
self.server = server
await self._stop_event.wait()
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.start_services, src/gui_2.py:App._render_discussion_panel, 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, channel: str, payload: dict[str, Any]) -> 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 channel not in self.clients:
return
message = json.dumps({"channel": channel, "payload": payload})
for ws in list(self.clients[channel]):
asyncio.run_coroutine_threadsafe(ws.send(message), self.loop)
+23 -1
View File
@@ -53,6 +53,7 @@ def hide_tk_root() -> Tk:
def parse_symbols(text: str) -> list[str]:
"""
Finds all occurrences of '@SymbolName' in text and returns SymbolName.
SymbolName can be a function, class, or method (e.g. @MyClass, @my_func, @MyClass.my_method).
[C: tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_basic, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_edge_cases, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_methods, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_mixed, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_no_symbols]
@@ -157,7 +158,6 @@ class MMASpawnApprovalDialog:
class AppController:
"""
The headless controller for the Manual Slop application.
Owns the application state and manages background services.
"""
@@ -517,6 +517,7 @@ class AppController:
def _update_inject_preview(self) -> None:
"""
Updates the preview content based on the selected file and injection mode.
[C: src/gui_2.py:App._gui_func, tests/test_skeleton_injection.py:test_update_inject_preview_full, tests/test_skeleton_injection.py:test_update_inject_preview_skeleton, tests/test_skeleton_injection.py:test_update_inject_preview_truncation]
"""
@@ -1027,6 +1028,7 @@ class AppController:
def _process_pending_history_adds(self) -> None:
"""
Synchronizes pending history entries to the active discussion and project state.
[C: src/gui_2.py:App._gui_func]
"""
@@ -1055,6 +1057,7 @@ class AppController:
def _process_pending_tool_calls(self) -> bool:
"""
Drains pending tool calls into the tool log. Returns True if any were processed.
[C: src/gui_2.py:App._gui_func]
"""
@@ -1102,6 +1105,7 @@ class AppController:
def init_state(self):
"""
Initializes the application state from configurations.
[C: src/gui_2.py:App.__init__, src/gui_2.py:App._render_paths_panel, src/gui_2.py:App._save_paths, tests/test_app_controller_mcp.py:test_app_controller_mcp_loading, tests/test_app_controller_mcp.py:test_app_controller_mcp_project_override, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_app_controller_init_state_loads_prompts]
"""
@@ -1559,6 +1563,7 @@ class AppController:
def start_services(self, app: Any = None):
"""
Starts background threads.
[C: src/gui_2.py:App.__init__]
"""
@@ -1569,6 +1574,7 @@ class AppController:
def shutdown(self) -> None:
"""
Stops background threads and cleans up resources.
[C: src/gui_2.py:App.run, src/gui_2.py:App.shutdown, tests/conftest.py:app_instance, tests/conftest.py:mock_app]
"""
@@ -1671,6 +1677,7 @@ class AppController:
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
"""
Processes a UserRequestEvent by calling the AI client.
[C: tests/test_live_gui_integration_v2.py:test_user_request_error_handling, tests/test_live_gui_integration_v2.py:test_user_request_integration_flow, tests/test_rag_integration.py:test_rag_integration]
"""
@@ -1995,6 +2002,7 @@ class AppController:
def create_api(self) -> FastAPI:
"""
Creates and configures the FastAPI application for headless mode.
[C: src/gui_2.py:App.run, tests/test_headless_service.py:TestHeadlessAPI.setUp]
"""
@@ -2022,6 +2030,7 @@ class AppController:
@api.get("/api/gui/state", dependencies=[Depends(get_api_key)])
def get_gui_state() -> dict[str, Any]:
"""
Returns the current GUI state for specific fields.
[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]
"""
@@ -2039,6 +2048,7 @@ class AppController:
@api.get("/api/gui/mma_status", dependencies=[Depends(get_api_key)])
def get_mma_status() -> dict[str, Any]:
"""
Dedicated endpoint for MMA-related 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_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]
"""
@@ -2057,6 +2067,7 @@ class AppController:
@api.post("/api/gui", dependencies=[Depends(get_api_key)])
def post_gui(req: dict) -> dict[str, str]:
"""
Pushes a GUI task to the event queue.
[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, tests/test_visual_mma.py:test_visual_mma_components]
"""
@@ -2085,6 +2096,7 @@ class AppController:
@api.get("/api/performance", dependencies=[Depends(get_api_key)])
def get_performance() -> dict[str, Any]:
"""
Returns performance monitor metrics.
[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, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing]
"""
@@ -2207,6 +2219,7 @@ class AppController:
@api.get("/api/v1/sessions/{session_id}", dependencies=[Depends(get_api_key)])
def get_session(session_id: str) -> dict[str, Any]:
"""
Returns the content of the comms.log for a specific session.
[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]
"""
@@ -2685,6 +2698,7 @@ class AppController:
def _handle_approve_ask(self) -> None:
"""
Responds with approval for a pending /api/ask request.
[C: src/gui_2.py:App.__init__, src/gui_2.py:App._gui_func]
"""
@@ -2706,6 +2720,7 @@ class AppController:
def _handle_reject_ask(self) -> None:
"""
Responds with rejection for a pending /api/ask request.
[C: src/gui_2.py:App._gui_func]
"""
@@ -2727,6 +2742,7 @@ class AppController:
def _handle_reset_session(self) -> None:
"""
Logic for resetting the AI session and GUI state.
[C: src/gui_2.py:App._render_message_panel]
"""
@@ -2758,6 +2774,7 @@ class AppController:
def _handle_md_only(self) -> None:
"""
Logic for the 'MD Only' action.
[C: src/gui_2.py:App._render_message_panel]
"""
@@ -2779,6 +2796,7 @@ class AppController:
def _handle_generate_send(self) -> None:
"""
Logic for the 'Gen + Send' action.
[C: src/gui_2.py:App._render_message_panel, src/gui_2.py:App._render_synthesis_panel, src/gui_2.py:App._render_takes_panel, tests/test_gui_events_v2.py:test_handle_generate_send_pushes_event, tests/test_symbol_parsing.py:test_handle_generate_send_appends_definitions, tests/test_symbol_parsing.py:test_handle_generate_send_no_symbols]
"""
@@ -2995,6 +3013,7 @@ class AppController:
def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]:
"""
Returns (full_md, output_path, file_items, stable_md, discussion_text).
[C: src/gui_2.py:App._show_menus, tests/test_tiered_aggregation.py:test_app_controller_do_generate_uses_persona_strategy]
"""
@@ -3266,6 +3285,7 @@ class AppController:
def kill_worker(self, worker_id: str) -> None:
"""
Aborts a running worker.
[C: src/gui_2.py:App._cb_kill_ticket, tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread]
"""
@@ -3289,6 +3309,7 @@ class AppController:
def inject_context(self, data: dict) -> None:
"""
Programmatic context injection.
[C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
"""
@@ -3420,6 +3441,7 @@ class AppController:
def _load_active_tickets(self) -> None:
"""
Populates self.active_tickets based on the current execution mode.
[C: tests/test_gui_dag_beads.py:test_load_active_tickets_from_beads]
"""
+23 -5
View File
@@ -12,22 +12,34 @@ class Bead:
class BeadsClient:
def __init__(self, working_dir: Path):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.working_dir = Path(working_dir)
self.repo_dir = self.working_dir / ".beads_mock"
self.beads_file = self.repo_dir / "beads.json"
def init_repo(self) -> None:
"""Initialize the mock repository."""
"""
Initialize the mock repository.
[C: tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query, tests/test_gui_dag_beads.py:test_load_active_tickets_from_beads, tests/test_mcp_client_beads.py:test_bd_mcp_tools]
"""
self.repo_dir.mkdir(parents=True, exist_ok=True)
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."""
"""
Check if the repository is initialized.
[C: src/mcp_client.py:dispatch, tests/test_beads_client.py:test_beads_init_and_query]
"""
return self.beads_file.exists()
def create_bead(self, title: str, description: str) -> str:
"""Create a new bead and return its ID."""
"""
Create a new bead and return its ID.
[C: src/mcp_client.py:dispatch, tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query, tests/test_gui_dag_beads.py:test_load_active_tickets_from_beads]
"""
beads = self._read_beads()
bead_id = f"bead-{len(beads) + 1}"
bead = {"id": bead_id, "title": title, "description": description, "status": "active"}
@@ -36,7 +48,10 @@ class BeadsClient:
return bead_id
def update_bead(self, bead_id: str, status: str) -> bool:
"""Update the status of an existing bead."""
"""
Update the status of an existing bead.
[C: src/mcp_client.py:dispatch, tests/test_aggregate_beads.py:test_build_beads_compaction, tests/test_beads_client.py:test_beads_init_and_query]
"""
beads = self._read_beads()
for bead in beads:
if bead["id"] == bead_id:
@@ -46,7 +61,10 @@ class BeadsClient:
return False
def list_beads(self) -> List[Bead]:
"""List all beads."""
"""
List all beads.
[C: src/gui_2.py:App._render_beads_tab, src/mcp_client.py:dispatch, tests/test_beads_client.py:test_beads_init_and_query]
"""
return [Bead(**b) for b in self._read_beads()]
def _read_beads(self) -> List[dict]:
+9 -1
View File
@@ -7,11 +7,17 @@ from imgui_bundle import imgui, nanovg as nvg, hello_imgui
class BackgroundShader:
def __init__(self):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.enabled = False
self.start_time = time.time()
self.ctx: Optional[nvg.Context] = None
def render(self, width: float, height: float):
"""
[C: src/gui_2.py:App._gui_func, src/gui_2.py:App._render_discussion_panel, src/gui_2.py:App._render_heavy_text, src/gui_2.py:App._render_markdown_test, src/gui_2.py:App._render_response_panel, src/gui_2.py:App._render_snapshot_tab, src/markdown_helper.py:MarkdownRenderer._render_code_block, src/markdown_helper.py:MarkdownRenderer.render, src/markdown_helper.py:render, 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]
"""
if not self.enabled or width <= 0 or height <= 0:
return
@@ -59,8 +65,10 @@ class BackgroundShader:
_bg: Optional[BackgroundShader] = None
def get_bg():
"""
[C: src/gui_2.py:App._gui_func, src/gui_2.py:App._render_theme_panel]
"""
global _bg
if _bg is None:
_bg = BackgroundShader()
return _bg
+4 -1
View File
@@ -41,8 +41,10 @@ from typing import Any
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]:
"""
Tier 2 (Tech Lead) call.
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
[C: src/native_orchestrator.py:NativeOrchestrator.generate_tickets, 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
@@ -95,8 +97,10 @@ from src.models import Ticket
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""
Sorts a list of tickets 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]
"""
# 1. Convert to Ticket objects for TrackDAG
ticket_objs = []
@@ -118,4 +122,3 @@ if __name__ == "__main__":
test_skeletons = "class NewFeature: pass"
tickets = generate_tickets(test_brief, test_skeletons)
print(json.dumps(tickets, indent=2))
+2 -1
View File
@@ -46,8 +46,10 @@ MODEL_PRICING = [
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_dashboard, src/gui_2.py:App._render_token_budget_panel, tests/test_cost_tracker.py:test_estimate_cost]
"""
if not model:
return 0.0
@@ -59,4 +61,3 @@ def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
return input_cost + output_cost
return 0.0
+20
View File
@@ -32,23 +32,28 @@ from src.performance_monitor import get_monitor
class TrackDAG:
"""
Manages a Directed Acyclic Graph of implementation tickets.
Provides methods for dependency resolution, cycle detection, and topological sorting.
"""
def __init__(self, tickets: List[Ticket]) -> None:
"""
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}
def cascade_blocks(self) -> None:
"""
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
@@ -82,9 +87,11 @@ class TrackDAG:
def get_ready_tasks(self) -> List[Ticket]:
"""
Returns a list of tickets that are in 'todo' status and whose dependencies are all 'completed'.
Returns:
A list of Ticket objects ready for execution.
[C: src/models.py:Track.get_executable_tickets, tests/test_dag_engine.py:test_get_ready_tasks_branching, tests/test_dag_engine.py:test_get_ready_tasks_linear, tests/test_dag_engine.py:test_get_ready_tasks_multiple_deps, tests/test_orchestration_logic.py:test_track_executable_tickets]
"""
ready = []
for ticket in self.tickets:
@@ -94,9 +101,11 @@ class TrackDAG:
def has_cycle(self) -> bool:
"""
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()
@@ -125,12 +134,14 @@ class TrackDAG:
def topological_sort(self) -> List[str]:
"""
Returns a list of ticket IDs in topological order (dependencies before dependents).
Uses Kahn's algorithm for efficient O(V+E) sorting and cycle detection.
Returns:
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}
@@ -159,26 +170,31 @@ class TrackDAG:
class ExecutionEngine:
"""
A state machine that governs the progression of tasks within a TrackDAG.
Handles automatic queueing and manual task approval.
"""
def __init__(self, dag: TrackDAG, auto_queue: bool = False) -> None:
"""
Initializes the 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
def tick(self) -> List[Ticket]:
"""
Evaluates the DAG and returns a list of tasks that are currently 'ready' for execution.
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()
@@ -187,9 +203,11 @@ class ExecutionEngine:
def approve_task(self, task_id: str) -> None:
"""
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):
@@ -197,10 +215,12 @@ class ExecutionEngine:
def update_task_status(self, task_id: str, status: str) -> None:
"""
Force-updates the status of a specific task.
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:
+24
View File
@@ -36,6 +36,9 @@ def parse_diff_header(line: str) -> tuple[Optional[str], Optional[str], Optional
return None, None, None
def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]:
"""
[C: tests/test_diff_viewer.py:test_parse_hunk_header]
"""
if not line.startswith("@@"):
return None
@@ -57,6 +60,9 @@ def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]:
return (old_start, old_count, new_start, new_count)
def parse_diff(diff_text: str) -> List[DiffFile]:
"""
[C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff, tests/test_diff_viewer.py:test_render_diff_text_immediate]
"""
if not diff_text or not diff_text.strip():
return []
@@ -132,6 +138,9 @@ def format_diff_for_display(diff_files: List[DiffFile]) -> str:
return "\n".join(output)
def get_line_color(line: str) -> Optional[str]:
"""
[C: tests/test_diff_viewer.py:test_get_line_color]
"""
if line.startswith("+"):
return "green"
elif line.startswith("-"):
@@ -141,6 +150,9 @@ def get_line_color(line: str) -> Optional[str]:
return None
def render_diff_text_immediate(diff_files: List[DiffFile]) -> List[tuple[str, Optional[str]]]:
"""
[C: tests/test_diff_viewer.py:test_render_diff_text_immediate]
"""
output: List[tuple[str, Optional[str]]] = []
for df in diff_files:
output.append((f"File: {df.old_path}", "white"))
@@ -152,6 +164,9 @@ def render_diff_text_immediate(diff_files: List[DiffFile]) -> List[tuple[str, Op
return output
def create_backup(file_path: str) -> Optional[str]:
"""
[C: tests/test_diff_viewer.py:test_create_backup, tests/test_diff_viewer.py:test_create_backup_nonexistent]
"""
path = Path(file_path)
if not path.exists():
return None
@@ -160,6 +175,9 @@ def create_backup(file_path: str) -> Optional[str]:
return str(backup_path)
def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str]:
"""
[C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context]
"""
import difflib
diff_files = parse_diff(patch_text)
@@ -207,6 +225,9 @@ def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str
return True, "\n".join(results)
def restore_from_backup(file_path: str) -> bool:
"""
[C: tests/test_diff_viewer.py:test_restore_from_backup]
"""
backup_path = Path(str(file_path) + ".backup")
if not backup_path.exists():
return False
@@ -215,6 +236,9 @@ def restore_from_backup(file_path: str) -> bool:
return True
def cleanup_backup(file_path: str) -> None:
"""
[C: tests/test_diff_viewer.py:test_cleanup_backup]
"""
backup_path = Path(str(file_path) + ".backup")
if backup_path.exists():
backup_path.unlink()
+35 -4
View File
File diff suppressed because one or more lines are too long
+18
View File
@@ -12,9 +12,15 @@ from src.models import ExternalEditorConfig, 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) -> Optional[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)
return self.config.get_default()
@@ -22,12 +28,18 @@ class ExternalEditorLauncher:
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(
self, editor_name: Optional[str], original_path: str, modified_path: str
) -> Optional[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:
return None
@@ -104,6 +116,9 @@ def auto_detect_vscode() -> Optional[TextEditorConfig]:
def get_default_launcher() -> ExternalEditorLauncher:
"""
[C: src/gui_2.py:App._open_patch_in_external_editor, src/gui_2.py:App._render_external_editor_panel]
"""
from src import models
config = models.load_config()
editors_config = config.get("tools", {}).get("text_editors", {})
@@ -137,6 +152,9 @@ def resolve_project_editor_override(project_path: Optional[str]) -> Optional[str
def create_temp_modified_file(content: str) -> str:
"""
[C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestHelperFunctions.test_create_temp_modified_file]
"""
with tempfile.NamedTemporaryFile(mode="w", suffix="_modified", delete=False, encoding="utf-8") as f:
f.write(content)
return f.name
+40 -2
View File
@@ -46,11 +46,15 @@ _ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
class ASTParser:
"""
Parser for extracting AST-based views of source code.
Currently supports Python.
"""
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
@@ -64,7 +68,10 @@ class ASTParser:
self.parser = tree_sitter.Parser(self.language)
def parse(self, code: str) -> tree_sitter.Tree:
"""Parse the given code and return the tree-sitter Tree."""
"""
Parse the given code and return the tree-sitter Tree.
[C: src/mcp_client.py:_search_file, src/mcp_client.py:derive_code_path, src/mcp_client.py:py_check_syntax, src/mcp_client.py:py_get_class_summary, src/mcp_client.py:py_get_definition, src/mcp_client.py:py_get_docstring, src/mcp_client.py:py_get_imports, src/mcp_client.py:py_get_signature, src/mcp_client.py:py_get_symbol_info, src/mcp_client.py:py_get_var_declaration, src/mcp_client.py:py_set_signature, src/mcp_client.py:py_set_var_declaration, src/mcp_client.py:py_update_definition, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/rag_engine.py:RAGEngine._chunk_code, src/summarize.py:_summarise_python, tests/test_ast_parser.py:test_ast_parser_parse, tests/test_tree_sitter_setup.py:test_tree_sitter_python_setup]
"""
return self.parser.parse(bytes(code, "utf8"))
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
@@ -129,7 +136,9 @@ class ASTParser:
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]
"""
code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code)
@@ -142,6 +151,9 @@ 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"):
@@ -178,9 +190,11 @@ class ASTParser:
def get_curated_view(self, code: str, path: Optional[str] = None) -> str:
"""
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)
@@ -217,6 +231,9 @@ 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"):
@@ -250,8 +267,10 @@ class ASTParser:
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]
"""
code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code)
@@ -412,8 +431,10 @@ class ASTParser:
def get_definition(self, code: str, name: str, path: Optional[str] = None) -> str:
"""
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)
@@ -421,6 +442,9 @@ class ASTParser:
parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[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 None
target = target_parts[0]
@@ -480,8 +504,10 @@ class ASTParser:
def get_signature(self, code: str, name: str, path: Optional[str] = None) -> str:
"""
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)
@@ -489,6 +515,9 @@ class ASTParser:
parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[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 None
target = target_parts[0]
@@ -559,13 +588,18 @@ class ASTParser:
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]
"""
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"):
@@ -595,7 +629,9 @@ class ASTParser:
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]
"""
code_bytes = code.encode("utf8")
tree = self.get_cached_tree(path, code)
@@ -603,6 +639,9 @@ class ASTParser:
parts = re.split(r'::|\.', name)
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[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 None
target = target_parts[0]
@@ -677,4 +716,3 @@ def evict(path: Path) -> None:
def list_cached() -> List[Dict[str, Any]]:
return []
+9 -2
View File
@@ -44,10 +44,14 @@ from typing import Optional, Callable, Any
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."""
"""
Initializes the adapter with the path to the gemini CLI executable.
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.binary_path = binary_path
self.session_id: Optional[str] = None
self.last_usage: Optional[dict[str, Any]] = None
@@ -56,8 +60,10 @@ class GeminiCliAdapter:
def send(self, message: str, safety_settings: list[Any] | None = None, system_instruction: str | None = None,
model: str | None = None, stream_callback: Optional[Callable[[str], None]] = None) -> dict[str, Any]:
"""
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/native_orchestrator.py:NativeOrchestrator.execute_ticket, 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_get_history_bleed_stats, 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]
@@ -185,9 +191,10 @@ class GeminiCliAdapter:
def count_tokens(self, contents: list[str]) -> int:
"""
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
+60 -4
View File
@@ -103,6 +103,9 @@ class App:
def __init__(self) -> None:
# Initialize controller and delegate state
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
self.controller = app_controller.AppController()
self.controller._app = self
from src import history
@@ -429,7 +432,10 @@ class App:
self._apply_snapshot(entry.state)
def shutdown(self) -> None:
"""Cleanly shuts down the app's background tasks and saves state."""
"""
Cleanly shuts down the app's background tasks and saves state.
[C: tests/conftest.py:app_instance, tests/conftest.py:mock_app]
"""
try:
if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
imgui.save_ini_settings_to_disk(self.runner_params.ini_filename)
@@ -438,6 +444,9 @@ class App:
self.controller.shutdown()
def save_context_preset(self, name: str) -> None:
"""
[C: tests/test_context_presets.py:test_save_context_preset]
"""
sys.stderr.write(f"[DEBUG] save_context_preset called with: {name}\n")
sys.stderr.flush()
if 'context_presets' not in self.controller.project:
@@ -451,6 +460,9 @@ class App:
sys.stderr.flush()
def load_context_preset(self, name: str) -> None:
"""
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
"""
presets = self.controller.project.get('context_presets', {})
if name in presets:
preset = presets[name]
@@ -458,6 +470,9 @@ class App:
self.screenshots = list(preset.get('screenshots', []))
def delete_context_preset(self, name: str) -> None:
"""
[C: tests/test_context_presets.py:test_delete_context_preset, tests/test_context_presets.py:test_delete_nonexistent_preset_no_error]
"""
if 'context_presets' in self.controller.project:
self.controller.project['context_presets'].pop(name, None)
self.controller._save_active_project()
@@ -597,6 +612,9 @@ class App:
imgui.pop_id()
def _show_menus(self) -> None:
"""
[C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close]
"""
if imgui.begin_menu("manual slop"):
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
self.runner_params.app_shall_exit = True
@@ -719,6 +737,9 @@ class App:
pass
def _render_shader_live_editor(self) -> None:
"""
[C: tests/test_shader_live_editor.py:test_shader_live_editor_renders]
"""
if self.show_windows.get('Shader Editor', False):
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
self.show_windows['Shader Editor'] = bool(opened)
@@ -2044,6 +2065,9 @@ class App:
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")
def _save_paths(self):
"""
[C: tests/test_gui_paths.py:test_save_paths]
"""
self.config["paths"] = {
"logs_dir": self.ui_logs_dir,
"scripts_dir": self.ui_scripts_dir
@@ -2295,6 +2319,9 @@ class App:
self._patch_error_message = str(e)
def _render_log_management(self) -> None:
"""
[C: tests/test_log_management_ui.py:test_render_log_management_logic]
"""
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
exp, opened = imgui.begin("Log Management", self.show_windows["Log Management"])
self.show_windows["Log Management"] = bool(opened)
@@ -2797,6 +2824,9 @@ def hello():
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
def _render_discussion_panel(self) -> None:
"""
[C: tests/test_discussion_takes_gui.py:test_render_discussion_tabs, tests/test_discussion_takes_gui.py:test_switching_discussion_via_tabs, tests/test_gui_discussion_tabs.py:test_discussion_tabs_rendered, tests/test_gui_phase4.py:test_track_discussion_toggle, tests/test_gui_symbol_navigation.py:test_render_discussion_panel_symbol_lookup]
"""
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
# THINKING indicator
is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...']
@@ -3175,7 +3205,10 @@ def hello():
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
def _render_synthesis_panel(self) -> None:
"""Renders a panel for synthesizing multiple discussion takes."""
"""
Renders a panel for synthesizing multiple discussion takes.
[C: tests/test_gui_synthesis.py:test_render_synthesis_panel]
"""
imgui.text("Select takes to synthesize:")
discussions = self.project.get('discussion', {}).get('discussions', {})
if not hasattr(self, 'ui_synthesis_selected_takes'):
@@ -3917,18 +3950,27 @@ def hello():
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_calls_panel")
def bulk_execute(self) -> None:
"""
[C: tests/test_ticket_queue.py:TestBulkOperations.test_bulk_execute]
"""
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'in_progress'
self._push_mma_state_update()
def bulk_skip(self) -> None:
"""
[C: tests/test_ticket_queue.py:TestBulkOperations.test_bulk_skip]
"""
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'completed'
self._push_mma_state_update()
def bulk_block(self) -> None:
"""
[C: tests/test_ticket_queue.py:TestBulkOperations.test_bulk_block]
"""
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'blocked'
@@ -3980,6 +4022,9 @@ def hello():
self._push_mma_state_update()
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
"""
[C: tests/test_ticket_queue.py:TestReorder.test_reorder_ticket_invalid, tests/test_ticket_queue.py:TestReorder.test_reorder_ticket_valid]
"""
if src_idx == dst_idx: return
new_tickets = list(self.active_tickets)
ticket = new_tickets.pop(src_idx)
@@ -3999,6 +4044,9 @@ def hello():
self._push_mma_state_update()
def _render_ticket_queue(self) -> None:
"""
[C: tests/test_gui_kill_button.py:test_render_ticket_queue_table_columns]
"""
imgui.text("Ticket Queue Management")
if not self.active_track:
imgui.text_disabled("No active track.")
@@ -4126,6 +4174,9 @@ def hello():
imgui.end_table()
def _render_mma_dashboard(self) -> None:
"""
[C: tests/test_gui_progress.py:test_render_mma_dashboard_progress, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_ask_dialog_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_mma_approval_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_spawn_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_no_approval_badge_when_idle]
"""
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
# Focus Agent dropdown
@@ -4734,6 +4785,9 @@ def hello():
imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"Error loading beads: {e}")
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
"""
[C: tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier1_renders_stream_content, tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier3_renders_worker_subheaders]
"""
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel")
if self.is_viewing_prior_session:
imgui.text_colored(vec4(255, 200, 100), "HISTORICAL VIEW - READ ONLY")
@@ -5135,7 +5189,10 @@ def hello():
theme.apply_current()
def run(self) -> None:
"""Initializes the ImGui runner and starts the main application loop."""
"""
Initializes the ImGui runner and starts the main application loop.
[C: simulation/sim_base.py:run_sim, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/project_manager.py:get_git_log, 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]
"""
if "--headless" in sys.argv:
print("Headless mode active")
self._fetch_models(self.current_provider)
@@ -5202,4 +5259,3 @@ def main() -> None:
if __name__ == "__main__":
main()
+21 -1
View File
@@ -19,6 +19,9 @@ class UISnapshot:
screenshots: list[str]
def to_dict(self) -> dict:
"""
[C: 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_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_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_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,
@@ -36,6 +39,9 @@ class UISnapshot:
@classmethod
def from_dict(cls, data: dict) -> "UISnapshot":
"""
[C: 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_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_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_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", ""),
@@ -59,14 +65,19 @@ 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] = []
def push(self, state: typing.Any, description: str) -> None:
"""
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]
"""
entry = HistoryEntry(state=state, description=description)
self._undo_stack.append(entry)
@@ -76,8 +87,10 @@ class HistoryManager:
def undo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]:
"""
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]
"""
if not self._undo_stack:
return None
@@ -88,8 +101,10 @@ class HistoryManager:
def redo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]:
"""
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]
"""
if not self._redo_stack:
return None
@@ -107,7 +122,10 @@ class HistoryManager:
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."""
"""
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]
"""
return [
{"description": e.description, "timestamp": e.timestamp}
for e in self._undo_stack
@@ -115,8 +133,10 @@ class HistoryManager:
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]
"""
if index < 0 or index >= len(self._undo_stack):
return None
+5 -1
View File
@@ -7,6 +7,7 @@ from src.log_registry import LogRegistry
class LogPruner:
"""
Handles the automated deletion of old and insignificant session logs.
Ensures that only whitelisted or significant sessions (based on size/content)
are preserved long-term.
@@ -14,23 +15,27 @@ class LogPruner:
def __init__(self, log_registry: LogRegistry, logs_dir: str) -> None:
"""
Initializes the 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
def prune(self, max_age_days: int = 1, min_size_kb: int = 2) -> None:
"""
Prunes old and small session directories from the logs directory.
Deletes session directories that meet the following criteria:
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)
@@ -115,4 +120,3 @@ class LogPruner:
sys.stderr.write(f"[LogPruner] Error removing {resolved_path}: {e}\n")
self.log_registry.save_registry()
+16 -1
View File
@@ -47,16 +47,19 @@ from typing import Any
class LogRegistry:
"""
Manages a persistent registry of session logs using a TOML file.
Tracks session paths, start times, whitelisting status, and metadata.
"""
def __init__(self, registry_path: str) -> None:
"""
Initializes the LogRegistry with a path to the registry file.
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, dict[str, Any]] = {}
@@ -69,6 +72,7 @@ class LogRegistry:
def load_registry(self) -> None:
"""
Loads the registry data from the TOML file into memory.
Handles date/time conversions from TOML-native formats to strings for consistency.
"""
@@ -97,8 +101,10 @@ class LogRegistry:
def save_registry(self) -> None:
"""
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
@@ -130,12 +136,14 @@ class LogRegistry:
def register_session(self, session_id: str, path: str, start_time: datetime | str) -> None:
"""
Registers a new session in the registry.
Args:
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.")
@@ -154,6 +162,7 @@ class LogRegistry:
def update_session_metadata(self, session_id: str, message_count: int, errors: int, size_kb: int, whitelisted: bool, reason: str) -> None:
"""
Updates metadata fields for an existing session.
Args:
@@ -163,6 +172,7 @@ 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.")
@@ -186,6 +196,7 @@ class LogRegistry:
def is_session_whitelisted(self, session_id: str) -> bool:
"""
Checks if a specific session is marked as whitelisted.
Args:
@@ -193,6 +204,7 @@ 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_auto_whitelist.py:test_no_auto_whitelist_insignificant, tests/test_log_registry.py:TestLogRegistry.test_is_session_whitelisted, tests/test_logging_e2e.py:test_logging_e2e]
"""
session_data = self.data.get(session_id)
if session_data is None:
@@ -202,12 +214,14 @@ class LogRegistry:
def update_auto_whitelist_status(self, session_id: str) -> None:
"""
Analyzes session logs and updates whitelisting status based on heuristics.
Sessions are automatically whitelisted if they contain error keywords,
have a high message count, or exceed a size threshold.
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
@@ -260,6 +274,7 @@ class LogRegistry:
def get_old_non_whitelisted_sessions(self, cutoff_datetime: datetime) -> list[dict[str, Any]]:
"""
Retrieves a list of sessions that are older than a specific cutoff time
and are not marked as whitelisted.
Also includes non-whitelisted sessions that are empty (message_count=0 or size_kb=0).
@@ -269,6 +284,7 @@ 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_data in self.data.items():
@@ -301,4 +317,3 @@ class LogRegistry:
})
return old_sessions
+11 -2
View File
@@ -9,10 +9,14 @@ from typing import Optional, Dict, Callable
class MarkdownRenderer:
"""
Hybrid Markdown renderer that uses imgui_md for text/headers
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"
@@ -62,7 +66,10 @@ 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."""
"""
Render Markdown text with code block interception.
[C: 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]
"""
if not text:
return
@@ -159,6 +166,9 @@ def get_renderer() -> MarkdownRenderer:
return _renderer
def render(text: str, context_id: str = "default") -> None:
"""
[C: 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:
@@ -166,4 +176,3 @@ def render_unindented(text: str) -> None:
def render_code(code: str, lang: str = "", context_id: str = "default", block_idx: int = 0) -> None:
get_renderer().render_code(code, lang, context_id, block_idx)
+75 -13
View File
@@ -96,11 +96,13 @@ perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None
def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | None = None) -> None:
"""
Build the allowlist from aggregate file_items.
Called by ai_client before each send so the list reflects the current project.
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]
"""
global _allowed_paths, _base_dirs, _primary_base_dir
_allowed_paths = set()
@@ -123,6 +125,7 @@ def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | Non
def _is_allowed(path: Path) -> bool:
"""
Return True if `path` is within the allowlist.
A path is allowed if:
- it is explicitly in _allowed_paths, OR
@@ -131,6 +134,7 @@ 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
from src.ai_client import get_credentials_path
@@ -170,6 +174,7 @@ def _is_allowed(path: Path) -> bool:
def _resolve_and_check(raw_path: str) -> tuple[Path | None, str]:
"""
Resolve raw_path and verify it passes the allowlist check.
Returns (resolved_path, error_string). error_string is empty on success.
"""
@@ -232,6 +237,7 @@ def list_directory(path: str) -> str:
def search_files(path: str, pattern: str) -> str:
"""
Search for files matching a glob pattern within path.
pattern examples: '*.py', '**/*.toml', 'src/**/*.rs'
"""
@@ -262,6 +268,7 @@ def search_files(path: str, pattern: str) -> str:
def get_file_summary(path: str) -> str:
"""
Return the heuristic summary for a file (same as the initial context block).
For .py files: imports, classes, methods, functions, constants.
For .toml: table keys. For .md: headings. Others: line count + preview.
@@ -281,6 +288,7 @@ def get_file_summary(path: str) -> str:
def py_get_skeleton(path: str) -> str:
"""
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
"""
p, err = _resolve_and_check(path)
@@ -300,7 +308,10 @@ def py_get_skeleton(path: str) -> str:
return f"ERROR generating skeleton for '{path}': {e}"
def ts_c_get_skeleton(path: str) -> str:
"""Returns a skeleton of a C file."""
"""
Returns a skeleton of a C file.
[C: tests/test_ts_c_tools.py:test_ts_c_get_skeleton]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -314,7 +325,10 @@ def ts_c_get_skeleton(path: str) -> str:
return f"ERROR generating skeleton for '{path}': {e}"
def ts_cpp_get_skeleton(path: str) -> str:
"""Returns a skeleton of a C++ file."""
"""
Returns a skeleton of a C++ file.
[C: tests/test_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]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -329,6 +343,7 @@ def ts_cpp_get_skeleton(path: str) -> str:
def py_get_code_outline(path: str) -> str:
"""
Returns a hierarchical outline of a code file (classes, functions, methods with line ranges).
"""
p, err = _resolve_and_check(path)
@@ -346,7 +361,10 @@ def py_get_code_outline(path: str) -> str:
return f"ERROR generating outline for '{path}': {e}"
def ts_c_get_code_outline(path: str) -> str:
"""Returns a hierarchical outline of a C file."""
"""
Returns a hierarchical outline of a C file.
[C: tests/test_ts_c_tools.py:test_ts_c_get_code_outline]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -360,7 +378,10 @@ def ts_c_get_code_outline(path: str) -> str:
return f"ERROR generating outline for '{path}': {e}"
def ts_cpp_get_code_outline(path: str) -> str:
"""Returns a hierarchical outline of a C++ file."""
"""
Returns a hierarchical outline of a C++ file.
[C: tests/test_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]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -388,7 +409,10 @@ def ts_c_get_definition(path: str, name: str) -> str:
return f"ERROR retrieving definition '{name}' from '{path}': {e}"
def ts_cpp_get_definition(path: str, name: str) -> str:
"""Returns the source code for a specific definition in a C++ file."""
"""
Returns the source code for a specific definition in a C++ file.
[C: 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_update_definition, tests/test_ts_cpp_tools.py:test_ts_cpp_update_definition_gencpp]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -448,7 +472,10 @@ def ts_c_update_definition(path: str, name: str, new_content: str) -> str:
return f"ERROR updating definition '{name}' in '{path}': {e}"
def ts_cpp_update_definition(path: str, name: str, new_content: str) -> str:
"""Surgically replace the definition of a class or function in a C++ file."""
"""
Surgically replace the definition of a class or function in a C++ file.
[C: tests/test_ts_cpp_tools.py:test_ts_cpp_update_definition, tests/test_ts_cpp_tools.py:test_ts_cpp_update_definition_gencpp]
"""
p, err = _resolve_and_check(path)
if err: return err
assert p is not None
@@ -504,6 +531,7 @@ def set_file_slice(path: str, start_line: int, end_line: int, new_content: str)
def edit_file(path: str, old_string: str, new_string: str, replace_all: bool = False) -> str:
"""
Replace exact string match in a file. Preserves indentation and line endings.
Drop-in replacement for native edit tool that destroys 1-space indentation.
"""
@@ -561,6 +589,7 @@ def _get_symbol_node(tree: ast.AST, name: str) -> Optional[ast.AST]:
def py_get_symbol_info(path: str, name: str) -> tuple[str, int] | str:
"""
Returns (source_code, line_number) for a specific class, function, or method definition.
If not found, returns an error string.
"""
@@ -587,6 +616,7 @@ def py_get_symbol_info(path: str, name: str) -> tuple[str, int] | str:
def py_get_definition(path: str, name: str) -> str:
"""
Returns the source code for a specific class, function, or method definition.
path: Path to the code file.
name: Name of the definition to retrieve (e.g., 'MyClass', 'my_function', 'MyClass.my_method').
@@ -761,6 +791,7 @@ def py_set_var_declaration(path: str, name: str, new_declaration: str) -> str:
def get_git_diff(path: str, base_rev: str = "HEAD", head_rev: str = "") -> str:
"""
Returns the git diff for a file or directory.
base_rev: The base revision (default: HEAD)
head_rev: The head revision (optional)
@@ -1114,7 +1145,10 @@ def fetch_url(url: str) -> str:
return f"ERROR fetching URL '{url}': {e}"
def get_ui_performance() -> str:
"""Returns current UI performance metrics (FPS, Frame Time, CPU, Input Lag)."""
"""
Returns current UI performance metrics (FPS, Frame Time, CPU, Input Lag).
[C: tests/test_mcp_perf_tool.py:test_mcp_perf_tool_retrieval]
"""
if perf_monitor_callback is None:
return "INFO: UI Performance monitor is not available (headless/CLI mode). This tool is only functional when the Manual Slop GUI is running."
try:
@@ -1143,6 +1177,9 @@ 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,
@@ -1156,6 +1193,9 @@ class StdioMCPServer:
self.status = 'running'
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]
"""
if self.proc:
try:
if self.proc.stdin:
@@ -1216,7 +1256,10 @@ class ExternalMCPManager:
self.servers = {}
async def add_server(self, config: models.MCPServerConfig):
"""Add and start a new MCP server from a configuration object."""
"""
Add and start a new MCP server from a configuration object.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp.py:test_get_tool_schemas_includes_external]
"""
if config.url:
# RemoteMCPServer placeholder
return
@@ -1225,13 +1268,19 @@ class ExternalMCPManager:
self.servers[config.name] = server
async def stop_all(self):
"""Stop all managed MCP servers and clear the registry."""
"""
Stop all managed MCP servers and clear the registry.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
for server in self.servers.values():
await server.stop()
self.servers = {}
def get_all_tools(self) -> dict:
"""Retrieve a dictionary of all tools available across all managed servers."""
"""
Retrieve a dictionary of all tools available across all managed servers.
[C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
all_tools = {}
for sname, server in self.servers.items():
for tname, tool in server.tools.items():
@@ -1243,7 +1292,10 @@ 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."""
"""
Dispatch a tool call to the appropriate external MCP server asynchronously.
[C: src/rag_engine.py:RAGEngine._async_search_mcp, tests/test_external_mcp.py:test_external_mcp_real_process]
"""
for server in self.servers.values():
if tool_name in server.tools:
return await server.call_tool(tool_name, tool_input)
@@ -1252,13 +1304,18 @@ class ExternalMCPManager:
_external_mcp_manager = ExternalMCPManager()
def get_external_mcp_manager() -> ExternalMCPManager:
"""Retrieve the global ExternalMCPManager instance."""
"""
Retrieve the global ExternalMCPManager instance.
[C: tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_external_mcp_e2e.py:test_external_mcp_e2e_refresh_and_call]
"""
global _external_mcp_manager
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]
"""
# Handle aliases
path = str(tool_input.get("path", tool_input.get("file_path", tool_input.get("dir_path", ""))))
@@ -1377,6 +1434,9 @@ 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 = {t['name'] for t in MCP_TOOL_SPECS}
if tool_name in native_names:
return await asyncio.to_thread(dispatch, tool_name, tool_input)
@@ -1390,6 +1450,9 @@ async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
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 = list(MCP_TOOL_SPECS)
manager = get_external_mcp_manager()
for tname, tinfo in manager.get_all_tools().items():
@@ -2126,4 +2189,3 @@ TOOL_NAMES: set[str] = {t['name'] for t in MCP_TOOL_SPECS}
+150 -1
View File
File diff suppressed because one or more lines are too long
+42 -6
View File
@@ -47,6 +47,7 @@ from src.dag_engine import TrackDAG, ExecutionEngine
class WorkerPool:
"""
Manages a pool of worker threads with a concurrency limit.
"""
def __init__(self, max_workers: int = 4):
@@ -57,8 +58,10 @@ class WorkerPool:
def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]:
"""
Spawns a new worker thread if the pool is not full.
Returns the thread object or None if full.
[C: tests/test_parallel_execution.py:test_worker_pool_completion_cleanup, tests/test_parallel_execution.py:test_worker_pool_limit, tests/test_parallel_execution.py:test_worker_pool_tracking]
"""
with self._lock:
if len(self._active) >= self.max_workers:
@@ -79,6 +82,9 @@ class WorkerPool:
return t
def join_all(self, timeout: float = None) -> None:
"""
[C: tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_parallel_execution.py:test_worker_pool_limit, tests/test_parallel_execution.py:test_worker_pool_tracking]
"""
with self._lock:
threads = list(self._active.values())
for t in threads:
@@ -87,14 +93,21 @@ class WorkerPool:
self._active.clear()
def get_active_count(self) -> int:
"""
[C: tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_parallel_execution.py:test_worker_pool_completion_cleanup, tests/test_parallel_execution.py:test_worker_pool_limit]
"""
with self._lock:
return len(self._active)
def is_full(self) -> bool:
"""
[C: tests/test_parallel_execution.py:test_worker_pool_limit]
"""
return self.get_active_count() >= self.max_workers
class ConductorEngine:
"""
Orchestrates the execution of tickets within a track.
"""
@@ -134,25 +147,40 @@ class ConductorEngine:
self.tier_usage[tier]["output"] += output_tokens
def pause(self) -> None:
"""Pauses the pipeline execution."""
"""
Pauses the pipeline execution.
[C: tests/test_pipeline_pause.py:test_pause_method, tests/test_pipeline_pause.py:test_resume_method]
"""
self._pause_event.set()
def resume(self) -> None:
"""Resumes the pipeline execution."""
"""
Resumes the pipeline execution.
[C: tests/test_pipeline_pause.py:test_resume_method]
"""
self._pause_event.clear()
def approve_task(self, task_id: str) -> None:
"""Manually transition todo to in_progress and mark engine dirty."""
"""
Manually transition todo to in_progress and mark engine dirty.
[C: tests/test_execution_engine.py:test_execution_engine_approve_task, tests/test_execution_engine.py:test_execution_engine_step_mode]
"""
self.engine.approve_task(task_id)
self._dirty = True
def update_task_status(self, task_id: str, status: str) -> None:
"""Force-update ticket status and mark engine dirty."""
"""
Force-update ticket status and mark engine dirty.
[C: 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]
"""
self.engine.update_task_status(task_id, status)
self._dirty = True
def kill_worker(self, ticket_id: str) -> None:
"""Sets the abort event for a worker and attempts to join its thread."""
"""
Sets the abort event for a worker and attempts to join its thread.
[C: tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread]
"""
if ticket_id in self._abort_events:
print(f"[MMA] Setting abort event for {ticket_id}")
self._abort_events[ticket_id].set()
@@ -184,8 +212,10 @@ class ConductorEngine:
def parse_json_tickets(self, json_str: str) -> None:
"""
Parses a JSON string of ticket definitions (Godot ECS Flat List format)
and populates the Track's ticket list.
[C: tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_orchestration_logic.py:test_conductor_engine_parse_json_tickets]
"""
try:
data = json.loads(json_str)
@@ -213,10 +243,12 @@ class ConductorEngine:
def run(self, md_content: str = "", max_ticks: Optional[int] = None) -> None:
"""
Main execution loop using the DAG engine.
Args:
md_content: The full markdown context (history + files) for AI workers.
max_ticks: Optional limit on number of iterations (for testing).
[C: simulation/sim_base.py:run_sim, src/project_manager.py:get_git_commit, src/project_manager.py:get_git_log, 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]
"""
tick_count = 0
while True:
@@ -335,6 +367,7 @@ def _queue_put(event_queue: events.AsyncEventQueue, event_name: str, payload) ->
def confirm_execution(payload: str, event_queue: events.AsyncEventQueue, ticket_id: str) -> bool:
"""
Pushes an approval request to the GUI and waits for response.
"""
dialog_container = [None]
@@ -357,8 +390,10 @@ def confirm_execution(payload: str, event_queue: events.AsyncEventQueue, ticket_
def confirm_spawn(role: str, prompt: str, context_md: str, event_queue: events.AsyncEventQueue, ticket_id: str) -> Tuple[bool, str, str]:
"""
Pushes a spawn approval request to the GUI and waits for response.
Returns (approved, modified_prompt, modified_context)
[C: tests/test_spawn_interception_v2.py:run_confirm]
"""
dialog_container = [None]
task = {
@@ -396,6 +431,7 @@ def confirm_spawn(role: str, prompt: str, context_md: str, event_queue: events.A
def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files: List[str] | None = None, event_queue: events.AsyncEventQueue | None = None, engine: Optional['ConductorEngine'] = None, md_content: str = "") -> None:
"""
Simulates the lifecycle of a single agent working on a ticket.
Calls the AI client and updates the ticket status based on the response.
Args:
@@ -405,6 +441,7 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
event_queue: Queue for pushing state updates and receiving approvals.
engine: The conductor engine.
md_content: The markdown context (history + files) for AI workers.
[C: tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_calls_ai_client_send, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_context_injection, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_handles_blocked_response, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_pushes_response_via_queue, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_step_mode_confirmation, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_step_mode_rejection, tests/test_conductor_engine_v2.py:test_run_worker_lifecycle_token_usage_from_comms_log, tests/test_context_pruner.py:test_token_reduction_logging, tests/test_orchestration_logic.py:test_run_worker_lifecycle_blocked, tests/test_phase6_engine.py:test_worker_streaming_intermediate, tests/test_run_worker_lifecycle_abort.py:TestRunWorkerLifecycleAbort.test_run_worker_lifecycle_returns_early_on_abort, tests/test_spawn_interception_v2.py:test_run_worker_lifecycle_approved, tests/test_spawn_interception_v2.py:test_run_worker_lifecycle_rejected, tests/test_tiered_aggregation.py:test_run_worker_lifecycle_uses_strategy]
"""
# Enforce Context Amnesia: each ticket starts with a clean slate.
ai_client.reset_session()
@@ -609,4 +646,3 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
if event_queue:
_queue_put(event_queue, "ticket_completed", {"ticket_id": ticket.id, "timestamp": time.time()})
return response
+52 -14
View File
@@ -9,20 +9,29 @@ import re
from src import paths
def read_plan(track_id: str, base_dir: str = ".") -> str:
"""Reads the implementation plan (plan.md) for a track."""
"""
Reads the implementation plan (plan.md) for a track.
[C: tests/test_native_orchestrator.py:test_read_plan_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_plan]
"""
plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md"
if not plan_path.exists():
return ""
return plan_path.read_text(encoding="utf-8")
def write_plan(track_id: str, content: str, base_dir: str = ".") -> None:
"""Writes the implementation plan (plan.md) for a track."""
"""
Writes the implementation plan (plan.md) for a track.
[C: tests/test_native_orchestrator.py:test_write_and_read_plan]
"""
plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md"
plan_path.parent.mkdir(parents=True, exist_ok=True)
plan_path.write_text(content, encoding="utf-8")
def parse_plan_tasks(content: str) -> list[dict[str, str]]:
"""Parses the tasks from a plan.md file."""
"""
Parses the tasks from a plan.md file.
[C: tests/test_native_orchestrator.py:test_parse_plan_tasks]
"""
tasks = []
for line in content.split("\n"):
stripped = line.strip()
@@ -33,24 +42,36 @@ def parse_plan_tasks(content: str) -> list[dict[str, str]]:
return tasks
def read_metadata(track_id: str, base_dir: str = ".") -> dict:
"""Reads the metadata (metadata.json) for a track."""
"""
Reads the metadata (metadata.json) for a track.
[C: tests/test_native_orchestrator.py:test_read_metadata_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_metadata]
"""
meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json"
if not meta_path.exists():
return {}
return json.loads(meta_path.read_text(encoding="utf-8"))
def write_metadata(track_id: str, data: dict, base_dir: str = ".") -> None:
"""Writes the metadata (metadata.json) for a track."""
"""
Writes the metadata (metadata.json) for a track.
[C: tests/test_native_orchestrator.py:test_write_and_read_metadata]
"""
meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json"
meta_path.parent.mkdir(parents=True, exist_ok=True)
meta_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
def get_track_dir(track_id: str, base_dir: str = ".") -> Path:
"""Returns the state directory for a specific track."""
"""
Returns the state directory for a specific track.
[C: tests/test_native_orchestrator.py:test_get_track_dir]
"""
return paths.get_track_state_dir(track_id, base_dir)
def get_archive_dir(base_dir: str = ".") -> Path:
"""Returns the central archive directory for completed tracks."""
"""
Returns the central archive directory for completed tracks.
[C: src/orchestrator_pm.py:get_track_history_summary, tests/test_native_orchestrator.py:test_get_archive_dir, tests/test_paths.py:test_conductor_dir_project_relative]
"""
return paths.get_archive_dir(base_dir)
class NativeOrchestrator:
@@ -59,28 +80,46 @@ class NativeOrchestrator:
self._conductor = None
def load_track(self, track_id: str) -> dict:
"""Load track from metadata.json"""
"""
Load track from metadata.json
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
"""
return read_metadata(track_id, str(self.base_dir))
def save_track(self, track_id: str, data: dict) -> None:
"""Persist track metadata"""
"""
Persist track metadata
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
"""
write_metadata(track_id, data, str(self.base_dir))
def load_plan(self, track_id: str) -> str:
"""Load plan.md content"""
"""
Load plan.md content
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
"""
return read_plan(track_id, str(self.base_dir))
def save_plan(self, track_id: str, content: str) -> None:
"""Persist plan.md content"""
"""
Persist plan.md content
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
"""
write_plan(track_id, content, str(self.base_dir))
def get_track_tasks(self, track_id: str) -> list[dict]:
"""Get parsed task list from plan.md"""
"""
Get parsed task list from plan.md
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
"""
content = self.load_plan(track_id)
return parse_plan_tasks(content)
def generate_tickets(self, brief: str, module_skeletons: str = "") -> list[dict]:
"""Tier 2: Generate tickets from brief"""
"""
Tier 2: Generate tickets from brief
[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]
"""
from src import conductor_tech_lead
return conductor_tech_lead.generate_tickets(brief, module_skeletons)
@@ -98,4 +137,3 @@ class NativeOrchestrator:
"""Tier 4: Generate patch for error"""
from src import ai_client
return ai_client.run_tier4_patch_generation(error, file_context)
+4 -1
View File
@@ -11,7 +11,9 @@ from src import paths
def get_track_history_summary() -> str:
"""
Scans conductor/archive/ and conductor/tracks/ to build a summary of past work.
[C: tests/test_orchestrator_pm_history.py:TestOrchestratorPMHistory.test_get_track_history_summary, tests/test_orchestrator_pm_history.py:TestOrchestratorPMHistory.test_get_track_history_summary_missing_files]
"""
summary_parts = []
archive_path = paths.get_archive_dir()
@@ -56,8 +58,10 @@ def get_track_history_summary() -> str:
def generate_tracks(user_request: str, project_config: dict[str, Any], file_items: list[dict[str, Any]], history_summary: Optional[str] = None) -> list[dict[str, Any]]:
"""
Tier 1 (Strategic PM) call.
Analyzes the project state and user request to generate a list of Tracks.
[C: tests/test_orchestration_logic.py:test_generate_tracks, tests/test_orchestrator_pm.py:TestOrchestratorPM.test_generate_tracks_malformed_json, tests/test_orchestrator_pm.py:TestOrchestratorPM.test_generate_tracks_markdown_wrapped, tests/test_orchestrator_pm.py:TestOrchestratorPM.test_generate_tracks_success, tests/test_orchestrator_pm_history.py:TestOrchestratorPMHistory.test_generate_tracks_with_history]
"""
# 1. Build Repository Map (Summary View)
repo_map = summarize.build_summary_markdown(file_items)
@@ -125,4 +129,3 @@ if __name__ == "__main__":
history = get_track_history_summary()
tracks = generate_tracks("Implement a basic unit test for the ai_client.py module.", flat, file_items, history_summary=history)
print(json.dumps(tracks, indent=2))
+3 -1
View File
@@ -54,6 +54,9 @@ class CodeOutliner:
return None
def walk(node: ast.AST, indent: int = 0) -> None:
"""
[C: src/summarize.py:_summarise_python]
"""
if isinstance(node, ast.ClassDef):
start_line = node.lineno
end_line = getattr(node, "end_lineno", start_line)
@@ -87,4 +90,3 @@ def get_outline(path: Path, code: str) -> str:
return outliner.outline(code)
else:
return f"Outlining not supported for {suffix} files yet."
+33
View File
@@ -16,6 +16,9 @@ class PatchModalManager:
self._on_reject_callback: Optional[Callable[[], None]] = None
def request_patch_approval(self, patch_text: str, file_paths: List[str], generated_by: str = "Tier 4 QA") -> bool:
"""
[C: tests/test_patch_modal.py:test_close_modal, tests/test_patch_modal.py:test_reject_patch, tests/test_patch_modal.py:test_request_patch_approval, tests/test_patch_modal.py:test_reset]
"""
from time import time
self._pending_patch = PendingPatch(
patch_text=patch_text,
@@ -27,32 +30,56 @@ class PatchModalManager:
return True
def get_pending_patch(self) -> Optional[PendingPatch]:
"""
[C: tests/test_patch_modal.py:test_patch_modal_manager_init, tests/test_patch_modal.py:test_reject_patch, tests/test_patch_modal.py:test_request_patch_approval, tests/test_patch_modal.py:test_reset]
"""
return self._pending_patch
def is_modal_shown(self) -> bool:
"""
[C: tests/test_patch_modal.py:test_close_modal, tests/test_patch_modal.py:test_patch_modal_manager_init, tests/test_patch_modal.py:test_reject_patch, tests/test_patch_modal.py:test_request_patch_approval, tests/test_patch_modal.py:test_reset]
"""
return self._show_modal
def set_apply_callback(self, callback: Callable[[str], bool]) -> None:
"""
[C: tests/test_patch_modal.py:test_apply_callback, tests/test_patch_modal.py:test_reset]
"""
self._on_apply_callback = callback
def set_reject_callback(self, callback: Callable[[], None]) -> None:
"""
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reset]
"""
self._on_reject_callback = callback
def apply_patch(self, patch_text: str) -> bool:
"""
[C: tests/test_patch_modal.py:test_apply_callback]
"""
if self._on_apply_callback:
return self._on_apply_callback(patch_text)
return False
def reject_patch(self) -> None:
"""
[C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
"""
self._pending_patch = None
self._show_modal = False
if self._on_reject_callback:
self._on_reject_callback()
def close_modal(self) -> None:
"""
[C: tests/test_patch_modal.py:test_close_modal]
"""
self._show_modal = False
def reset(self) -> None:
"""
[C: tests/test_patch_modal.py:test_reset]
"""
self._pending_patch = None
self._show_modal = False
self._on_apply_callback = None
@@ -61,12 +88,18 @@ class PatchModalManager:
_patch_modal_manager: Optional[PatchModalManager] = None
def get_patch_modal_manager() -> PatchModalManager:
"""
[C: tests/test_patch_modal.py:test_get_patch_modal_manager_singleton]
"""
global _patch_modal_manager
if _patch_modal_manager is None:
_patch_modal_manager = PatchModalManager()
return _patch_modal_manager
def reset_patch_modal_manager() -> None:
"""
[C: tests/test_patch_modal.py:test_get_patch_modal_manager_singleton]
"""
global _patch_modal_manager
if _patch_modal_manager:
_patch_modal_manager.reset()
+46 -1
View File
@@ -48,35 +48,62 @@ from typing import Optional, Any
_RESOLVED: dict[str, Path] = {}
def get_config_path() -> Path:
"""
[C: tests/test_paths.py:test_default_paths]
"""
root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_CONFIG", root_dir / "config.toml"))
def get_global_presets_path() -> Path:
"""
[C: src/presets.py:PresetManager.__init__, src/presets.py:PresetManager.delete_preset, src/presets.py:PresetManager.get_preset_scope]
"""
root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_PRESETS", root_dir / "presets.toml"))
def get_project_presets_path(project_root: Path) -> Path:
"""
[C: src/presets.py:PresetManager.delete_preset, src/presets.py:PresetManager.get_preset_scope, src/presets.py:PresetManager.project_path]
"""
return project_root / "project_presets.toml"
def get_global_tool_presets_path() -> Path:
"""
[C: src/tool_presets.py:ToolPresetManager._get_path, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets]
"""
root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_TOOL_PRESETS", root_dir / "tool_presets.toml"))
def get_project_tool_presets_path(project_root: Path) -> Path:
"""
[C: src/tool_presets.py:ToolPresetManager._get_path, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets]
"""
return project_root / "project_tool_presets.toml"
def get_global_personas_path() -> Path:
"""
[C: src/personas.py:PersonaManager._get_path, src/personas.py:PersonaManager.get_persona_scope, src/personas.py:PersonaManager.load_all]
"""
root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_PERSONAS", root_dir / "personas.toml"))
def get_project_personas_path(project_root: Path) -> Path:
"""
[C: src/personas.py:PersonaManager._get_path, src/personas.py:PersonaManager.get_persona_scope, src/personas.py:PersonaManager.load_all]
"""
return project_root / "project_personas.toml"
def get_global_workspace_profiles_path() -> Path:
"""
[C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles]
"""
root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_WORKSPACE_PROFILES", root_dir / "workspace_profiles.toml"))
def get_project_workspace_profiles_path(project_root: Path) -> Path:
"""
[C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles]
"""
return project_root / ".ai" / "workspace_profiles.toml"
def _resolve_path(env_var: str, config_key: str, default: str) -> Path:
@@ -115,6 +142,9 @@ def _get_project_conductor_dir_from_toml(project_root: Path) -> Optional[Path]:
return None
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
"""
[C: tests/test_paths.py:test_conductor_dir_project_relative, tests/test_project_paths.py:test_get_conductor_dir_default, tests/test_project_paths.py:test_get_conductor_dir_project_specific_with_toml]
"""
if not project_path:
# Fallback for legacy/tests, but we should avoid this
return Path('conductor').resolve()
@@ -126,19 +156,31 @@ def get_conductor_dir(project_path: Optional[str] = None) -> Path:
return (project_root / "conductor").resolve()
def get_logs_dir() -> Path:
"""
[C: src/session_logger.py:close_session, src/session_logger.py:open_session, tests/test_paths.py:test_config_overrides, tests/test_paths.py:test_default_paths, tests/test_paths.py:test_env_var_overrides, tests/test_paths.py:test_precedence]
"""
if "logs_dir" not in _RESOLVED:
_RESOLVED["logs_dir"] = _resolve_path("SLOP_LOGS_DIR", "logs_dir", "logs/sessions")
return _RESOLVED["logs_dir"]
def get_scripts_dir() -> Path:
"""
[C: src/session_logger.py:log_tool_call, src/session_logger.py:open_session, tests/test_paths.py:test_config_overrides, tests/test_paths.py:test_default_paths]
"""
if "scripts_dir" not in _RESOLVED:
_RESOLVED["scripts_dir"] = _resolve_path("SLOP_SCRIPTS_DIR", "scripts_dir", "scripts/generated")
return _RESOLVED["scripts_dir"]
def get_tracks_dir(project_path: Optional[str] = None) -> Path:
"""
[C: src/project_manager.py:get_all_tracks, tests/test_paths.py:test_conductor_dir_project_relative]
"""
return get_conductor_dir(project_path) / "tracks"
def get_track_state_dir(track_id: str, project_path: Optional[str] = None) -> Path:
"""
[C: src/project_manager.py:load_track_state, src/project_manager.py:save_track_state, tests/test_paths.py:test_conductor_dir_project_relative]
"""
return get_tracks_dir(project_path) / track_id
def get_archive_dir(project_path: Optional[str] = None) -> Path:
@@ -167,5 +209,8 @@ def get_full_path_info() -> dict[str, dict[str, Any]]:
}
def reset_resolved() -> None:
"""For testing only - clear cached resolutions."""
"""
For testing only - clear cached resolutions.
[C: tests/conftest.py:reset_paths, tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_gui_phase3.py:test_conductor_setup_scan, tests/test_paths.py:reset_paths, tests/test_project_paths.py:test_get_all_tracks_project_specific, tests/test_project_paths.py:test_get_conductor_dir_default, tests/test_project_paths.py:test_get_conductor_dir_project_specific_with_toml]
"""
_RESOLVED.clear()
+31 -4
View File
@@ -75,6 +75,9 @@ class PerformanceScope:
def get_monitor() -> PerformanceMonitor:
"""
[C: tests/test_perf_aggregate.py:test_build_tier3_context_scaling, tests/test_perf_dag.py:test_dag_performance]
"""
global _instance
if _instance is None:
_instance = PerformanceMonitor()
@@ -82,6 +85,7 @@ def get_monitor() -> PerformanceMonitor:
class PerformanceMonitor:
"""
Tracks application performance metrics like FPS, frame time, and CPU usage.
Supports thread-safe tracking for individual components with efficient moving averages.
"""
@@ -154,6 +158,9 @@ class PerformanceMonitor:
return self._history_sums[key] / len(h)
def start_frame(self) -> None:
"""
[C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing]
"""
now = time.perf_counter()
with self._lock:
if self._last_frame_start_time > 0:
@@ -165,6 +172,9 @@ class PerformanceMonitor:
self._frame_count += 1
def end_frame(self) -> None:
"""
[C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing]
"""
if self._start_time is None:
return
now = time.perf_counter()
@@ -193,12 +203,18 @@ class PerformanceMonitor:
self._fps_timer = 0.0
def start_component(self, name: str) -> None:
"""
[C: tests/test_performance_monitor.py:test_perf_monitor_component_timing, tests/test_performance_monitor.py:test_perf_monitor_extended_metrics]
"""
if not self.enabled: return
now = time.perf_counter()
with self._lock:
self._component_starts[name] = now
def end_component(self, name: str) -> None:
"""
[C: tests/test_performance_monitor.py:test_perf_monitor_component_timing, tests/test_performance_monitor.py:test_perf_monitor_extended_metrics]
"""
if not self.enabled: return
now = time.perf_counter()
with self._lock:
@@ -215,7 +231,10 @@ class PerformanceMonitor:
self._add_to_history(f'comp_{name}', elapsed)
def get_metrics(self) -> dict[str, float]:
"""Returns current metrics and their moving averages. Thread-safe."""
"""
Returns current metrics and their moving averages. Thread-safe.
[C: tests/test_perf_aggregate.py:test_build_tier3_context_scaling, tests/test_perf_dag.py:test_dag_performance, 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]
"""
with self._lock:
fps = self._fps
last_ft = self._last_frame_time
@@ -246,7 +265,10 @@ class PerformanceMonitor:
return metrics
def get_history(self, key: str) -> List[float]:
"""Returns a snapshot of the full history buffer for a specific metric key."""
"""
Returns a snapshot of the full history buffer for a specific metric key.
[C: tests/test_history.py:test_initial_state, tests/test_history.py:test_push_state]
"""
with self._lock:
if key in self._history:
return list(self._history[key])
@@ -255,11 +277,16 @@ class PerformanceMonitor:
return []
def scope(self, name: str) -> PerformanceScope:
"""Returns a context manager for timing a component."""
"""
Returns a context manager for timing a component.
[C: tests/test_perf_aggregate.py:test_build_tier3_context_scaling, tests/test_performance_monitor.py:test_perf_monitor_scope_context_manager]
"""
return PerformanceScope(self, name)
def stop(self) -> None:
"""
[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]
"""
self._stop_event.set()
if self._cpu_thread.is_alive():
self._cpu_thread.join(timeout=2.0)
+19 -2
View File
@@ -12,6 +12,9 @@ class PersonaManager:
self.project_root = project_root
def _get_path(self, scope: str) -> Path:
"""
[C: src/tool_presets.py:ToolPresetManager.delete_bias_profile, src/tool_presets.py:ToolPresetManager.delete_preset, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
if scope == "global":
return paths.get_global_personas_path()
elif scope == "project":
@@ -22,7 +25,10 @@ class PersonaManager:
raise ValueError("Invalid scope, must be 'global' or 'project'")
def load_all(self) -> Dict[str, Persona]:
"""Merges global and project personas into a single dictionary."""
"""
Merges global and project personas into a single dictionary.
[C: tests/test_persona_manager.py:test_delete_persona, tests/test_persona_manager.py:test_load_all_merged, tests/test_persona_manager.py:test_save_persona, tests/test_preset_manager.py:test_delete_preset, tests/test_preset_manager.py:test_load_all_merged, tests/test_preset_manager.py:test_save_preset_global, tests/test_preset_manager.py:test_save_preset_project, tests/test_presets.py:TestPresetManager.test_delete_preset, tests/test_presets.py:TestPresetManager.test_project_overwrites_global, tests/test_presets.py:TestPresetManager.test_save_and_load_global, tests/test_presets.py:TestPresetManager.test_save_and_load_project]
"""
personas = {}
global_path = paths.get_global_personas_path()
@@ -39,6 +45,9 @@ class PersonaManager:
return personas
def save_persona(self, persona: Persona, scope: str = "project") -> None:
"""
[C: tests/test_persona_manager.py:test_save_persona]
"""
path = self._get_path(scope)
data = self._load_file(path)
if "personas" not in data:
@@ -63,6 +72,9 @@ class PersonaManager:
return "project"
def delete_persona(self, name: str, scope: str = "project") -> None:
"""
[C: tests/test_persona_manager.py:test_delete_persona]
"""
path = self._get_path(scope)
data = self._load_file(path)
if "personas" in data and name in data["personas"]:
@@ -70,6 +82,9 @@ class PersonaManager:
self._save_file(path, data)
def _load_file(self, path: Path) -> Dict[str, Any]:
"""
[C: src/presets.py:PresetManager.delete_preset, src/presets.py:PresetManager.get_preset_scope, src/presets.py:PresetManager.load_all, src/presets.py:PresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.load_all_profiles, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
if not path.exists():
return {}
try:
@@ -79,7 +94,9 @@ class PersonaManager:
return {}
def _save_file(self, path: Path, data: Dict[str, Any]) -> None:
"""
[C: src/presets.py:PresetManager.delete_preset, src/presets.py:PresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "wb") as f:
tomli_w.dump(data, f)
+17 -2
View File
@@ -18,7 +18,10 @@ class PresetManager:
return get_project_presets_path(self.project_root) if self.project_root else None
def load_all(self) -> Dict[str, Preset]:
"""Merges global and project presets into a single dictionary."""
"""
Merges global and project presets into a single dictionary.
[C: tests/test_persona_manager.py:test_delete_persona, tests/test_persona_manager.py:test_load_all_merged, tests/test_persona_manager.py:test_save_persona, tests/test_preset_manager.py:test_delete_preset, tests/test_preset_manager.py:test_load_all_merged, tests/test_preset_manager.py:test_save_preset_global, tests/test_preset_manager.py:test_save_preset_project, tests/test_presets.py:TestPresetManager.test_delete_preset, tests/test_presets.py:TestPresetManager.test_project_overwrites_global, tests/test_presets.py:TestPresetManager.test_save_and_load_global, tests/test_presets.py:TestPresetManager.test_save_and_load_project]
"""
presets: Dict[str, Preset] = {}
# Load global presets
@@ -41,7 +44,10 @@ class PresetManager:
return presets
def save_preset(self, preset: Preset, scope: str = "project") -> None:
"""Saves a preset to either the global or project-specific TOML file."""
"""
Saves a preset to either the global or project-specific TOML file.
[C: tests/test_preset_manager.py:test_save_preset_global, tests/test_preset_manager.py:test_save_preset_project, tests/test_preset_manager.py:test_save_preset_project_no_root, tests/test_presets.py:TestPresetManager.test_delete_preset, tests/test_presets.py:TestPresetManager.test_project_overwrites_global, tests/test_presets.py:TestPresetManager.test_save_and_load_global, tests/test_presets.py:TestPresetManager.test_save_and_load_project]
"""
path = self.global_path if scope == "global" else self.project_path
if not path:
if scope == "project":
@@ -56,6 +62,9 @@ class PresetManager:
self._save_file(path, data)
def delete_preset(self, name: str, scope: str) -> None:
"""
[C: tests/test_preset_manager.py:test_delete_preset, tests/test_presets.py:TestPresetManager.test_delete_preset]
"""
if scope == "project" and self.project_root:
path = get_project_presets_path(self.project_root)
else:
@@ -82,6 +91,9 @@ class PresetManager:
return "project"
def _load_file(self, path: Path) -> Dict[str, Any]:
"""
[C: src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.load_all_profiles, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
if not path.exists():
return {"presets": {}}
try:
@@ -97,6 +109,9 @@ class PresetManager:
return {"presets": {}}
def _save_file(self, path: Path, data: Dict[str, Any]) -> None:
"""
[C: src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
if path.parent.exists() and path.parent.is_file():
raise ValueError(f"Cannot save to {path}: Parent directory {path.parent} is a file.")
path.parent.mkdir(parents=True, exist_ok=True)
+58 -9
View File
@@ -29,7 +29,10 @@ def parse_ts(s: str) -> Optional[datetime.datetime]:
# ── entry serialisation ──────────────────────────────────────────────────────
def entry_to_str(entry: dict[str, Any]) -> str:
"""Serialise a disc entry dict -> stored string."""
"""
Serialise a disc entry dict -> stored string.
[C: tests/test_thinking_persistence.py:test_entry_to_str_with_thinking]
"""
ts = entry.get("ts", "")
role = entry.get("role", "User")
content = entry.get("content", "")
@@ -46,7 +49,10 @@ def entry_to_str(entry: dict[str, Any]) -> str:
return f"{role}:\n{content}"
def str_to_entry(raw: str, roles: list[str]) -> dict[str, Any]:
"""Parse a stored string back to a disc entry dict."""
"""
Parse a stored string back to a disc entry dict.
[C: tests/test_thinking_persistence.py:test_str_to_entry_with_thinking]
"""
ts = ""
rest = raw
if rest.startswith("@"):
@@ -93,9 +99,15 @@ def get_git_log(git_dir: str, n: int = 5) -> str:
# ── default structures ───────────────────────────────────────────────────────
def default_discussion() -> dict[str, Any]:
"""
[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]:
"""
[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]
"""
return {
"project": {"name": name, "git_dir": "", "system_prompt": "", "main_context": "", "execution_mode": "native"},
"output": {"output_dir": "./md_gen"},
@@ -148,14 +160,19 @@ def default_project(name: str = "unnamed") -> dict[str, Any]:
# ── load / save ──────────────────────────────────────────────────────────────
def get_history_path(project_path: Union[str, Path]) -> Path:
"""Return the Path to the sibling history TOML file for a given project."""
"""
Return the Path to the sibling history TOML file for a given project.
[C: tests/test_history_management.py:test_save_separation]
"""
p = Path(project_path)
return p.parent / f"{p.stem}_history.toml"
def load_project(path: Union[str, Path]) -> dict[str, Any]:
"""
Load a project TOML file.
Automatically migrates legacy 'discussion' keys to a sibling history file.
[C: tests/test_history_management.py:test_history_persistence_across_turns, tests/test_history_management.py:test_migration_on_load, tests/test_project_manager_modes.py:test_load_save_execution_mode, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_project_serialization.py:TestProjectSerialization.test_fileitem_roundtrip]
"""
with open(path, "rb") as f:
proj = tomllib.load(f)
@@ -176,7 +193,10 @@ def load_project(path: Union[str, Path]) -> dict[str, Any]:
return proj
def load_history(project_path: Union[str, Path]) -> dict[str, Any]:
"""Load the segregated discussion history from its dedicated TOML file."""
"""
Load the segregated discussion history from its dedicated TOML file.
[C: tests/test_thinking_persistence.py:test_save_and_load_history_with_thinking_segments]
"""
hist_path = get_history_path(project_path)
if hist_path.exists():
with open(hist_path, "rb") as f:
@@ -184,7 +204,10 @@ def load_history(project_path: Union[str, Path]) -> dict[str, Any]:
return {}
def clean_nones(data: Any) -> Any:
"""Recursively remove None values from a dictionary/list."""
"""
Recursively remove None values from a dictionary/list.
[C: tests/test_thinking_persistence.py:test_clean_nones_removes_thinking]
"""
if isinstance(data, dict):
return {k: clean_nones(v) for k, v in data.items() if v is not None}
elif isinstance(data, list):
@@ -193,8 +216,10 @@ def clean_nones(data: Any) -> Any:
def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Optional[dict[str, Any]] = None) -> None:
"""
Save the project TOML.
If 'discussion' is present in proj, it is moved to the sibling history file.
[C: 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_load_save_execution_mode, tests/test_project_serialization.py:TestProjectSerialization.test_fileitem_roundtrip, tests/test_thinking_persistence.py:test_save_and_load_history_with_thinking_segments]
"""
proj = clean_nones(proj)
# Serialise FileItems
@@ -253,7 +278,10 @@ def flat_config(proj: dict[str, Any], disc_name: Optional[str] = None, track_id:
# ── context presets ──────────────────────────────────────────────────────────
def save_context_preset(project_dict: dict, preset_name: str, files: list[str], screenshots: list[str]) -> None:
"""Save a named context preset (files + screenshots) into the project dict."""
"""
Save a named context preset (files + screenshots) into the project dict.
[C: tests/test_context_presets.py:test_save_context_preset]
"""
if "context_presets" not in project_dict:
project_dict["context_presets"] = {}
project_dict["context_presets"][preset_name] = {
@@ -262,20 +290,28 @@ def save_context_preset(project_dict: dict, preset_name: str, files: list[str],
}
def load_context_preset(project_dict: dict, preset_name: str) -> dict:
"""Return the files and screenshots for a named preset."""
"""
Return the files and screenshots for a named preset.
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
"""
if "context_presets" not in project_dict or preset_name not in project_dict["context_presets"]:
raise KeyError(f"Preset '{preset_name}' not found in project context_presets.")
return project_dict["context_presets"][preset_name]
def delete_context_preset(project_dict: dict, preset_name: str) -> None:
"""Remove a named preset if it exists."""
"""
Remove a named preset if it exists.
[C: tests/test_context_presets.py:test_delete_context_preset, tests/test_context_presets.py:test_delete_nonexistent_preset_no_error]
"""
if "context_presets" in project_dict:
project_dict["context_presets"].pop(preset_name, None)
# ── track state persistence ─────────────────────────────────────────────────
def save_track_state(track_id: str, state: 'TrackState', base_dir: Union[str, Path] = ".") -> None:
"""
Saves a TrackState object to conductor/tracks/<track_id>/state.toml.
[C: tests/test_project_manager_tracks.py:test_get_all_tracks_with_state, tests/test_track_state_persistence.py:test_track_state_persistence]
"""
track_dir = paths.get_track_state_dir(track_id, project_path=str(base_dir))
track_dir.mkdir(parents=True, exist_ok=True)
@@ -286,7 +322,9 @@ def save_track_state(track_id: str, state: 'TrackState', base_dir: Union[str, Pa
def load_track_state(track_id: str, base_dir: Union[str, Path] = ".") -> Optional['TrackState']:
"""
Loads a TrackState object from conductor/tracks/<track_id>/state.toml.
[C: tests/test_track_state_persistence.py:test_track_state_persistence]
"""
from src.models import TrackState
state_file = paths.get_track_state_dir(track_id, project_path=str(base_dir)) / 'state.toml'
@@ -298,6 +336,7 @@ def load_track_state(track_id: str, base_dir: Union[str, Path] = ".") -> Optiona
def load_track_history(track_id: str, base_dir: Union[str, Path] = ".") -> list[str]:
"""
Loads the discussion history for a specific track from its state.toml.
Returns a list of entry strings formatted with @timestamp.
"""
@@ -315,6 +354,7 @@ def load_track_history(track_id: str, base_dir: Union[str, Path] = ".") -> list[
def save_track_history(track_id: str, history: list[str], base_dir: Union[str, Path] = ".") -> None:
"""
Saves the discussion history for a specific track to its state.toml.
'history' is expected to be a list of formatted strings.
"""
@@ -328,11 +368,13 @@ def save_track_history(track_id: str, history: list[str], base_dir: Union[str, P
def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
"""
Scans the conductor/tracks/ directory and returns a list of dictionaries
containing track metadata: 'id', 'title', 'status', 'complete', 'total',
and 'progress' (0.0 to 1.0).
Handles missing or malformed metadata.json or state.toml by falling back
to available info or defaults.
[C: tests/test_project_manager_tracks.py:test_get_all_tracks_empty, tests/test_project_manager_tracks.py:test_get_all_tracks_malformed, tests/test_project_manager_tracks.py:test_get_all_tracks_with_metadata_json, tests/test_project_manager_tracks.py:test_get_all_tracks_with_state, tests/test_project_paths.py:test_get_all_tracks_project_specific]
"""
tracks_dir = paths.get_tracks_dir(project_path=str(base_dir))
if not tracks_dir.exists():
@@ -394,8 +436,10 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
def calculate_track_progress(tickets: list) -> dict:
"""
Calculates track progress based on ticket statuses.
percentage (float), completed (int), total (int), in_progress (int), blocked (int), todo (int)
[C: tests/test_progress_viz.py:test_calculate_track_progress_all_completed, tests/test_progress_viz.py:test_calculate_track_progress_all_todo, tests/test_progress_viz.py:test_calculate_track_progress_empty, tests/test_progress_viz.py:test_calculate_track_progress_mixed]
"""
total = len(tickets)
if total == 0:
@@ -427,8 +471,10 @@ def calculate_track_progress(tickets: list) -> dict:
def branch_discussion(project_dict: dict, source_id: str, new_id: str, message_index: int) -> None:
"""
Creates a new discussion in project_dict['discussion']['discussions'] by copying
the history from source_id up to (and including) message_index, and sets active to new_id.
[C: tests/test_discussion_takes.py:TestDiscussionTakes.test_branch_discussion_creates_new_take]
"""
if "discussion" not in project_dict or "discussions" not in project_dict["discussion"]:
return
@@ -445,7 +491,10 @@ def branch_discussion(project_dict: dict, source_id: str, new_id: str, message_i
project_dict["discussion"]["active"] = new_id
def promote_take(project_dict: dict, take_id: str, new_id: str) -> None:
"""Renames a take_id to new_id in the discussions dict."""
"""
Renames a take_id to new_id in the discussions dict.
[C: tests/test_discussion_takes.py:TestDiscussionTakes.test_promote_take_renames_discussion]
"""
if "discussion" not in project_dict or "discussions" not in project_dict["discussion"]:
return
if take_id not in project_dict["discussion"]["discussions"]:
+9
View File
@@ -94,6 +94,9 @@ class RAGEngine:
return self.collection.count() == 0
def add_documents(self, ids: List[str], texts: List[str], metadatas: Optional[List[Dict[str, Any]]] = None):
"""
[C: tests/test_rag_engine.py:test_rag_engine_chroma]
"""
if not self.config.enabled or self.collection == "mock":
return
embeddings = self.embedding_provider.embed(texts)
@@ -202,6 +205,9 @@ class RAGEngine:
return asyncio.run(_async_search_mcp())
def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
"""
[C: tests/mock_concurrent_mma.py:main, tests/test_rag_engine.py:test_rag_engine_chroma]
"""
if not self.config.enabled:
return []
if self.config.vector_store.provider == 'mcp':
@@ -227,6 +233,9 @@ class RAGEngine:
return ret
def delete_documents(self, ids: List[str]):
"""
[C: tests/test_rag_engine.py:test_rag_engine_chroma]
"""
if not self.config.enabled or self.collection == "mock":
return
self.collection.delete(ids=ids)
+12 -2
View File
@@ -43,8 +43,10 @@ def _now_ts() -> str:
def open_session(label: Optional[str] = None) -> None:
"""
Called once at GUI startup. Creates the log directories if needed and
opens the log files for this session within a sub-directory.
[C: tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_logging_e2e.py:test_logging_e2e, tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs, tests/test_session_logger_optimization.py:test_session_directory_and_subdirectories_creation, tests/test_session_logger_reset.py:test_reset_session, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry]
"""
global _ts, _session_id, _session_dir, _comms_fh, _tool_fh, _api_fh, _cli_fh, _seq, _output_seq
if _comms_fh is not None:
@@ -86,7 +88,10 @@ def open_session(label: Optional[str] = None) -> None:
atexit.register(close_session)
def close_session() -> None:
"""Flush and close all log files. Called on clean exit."""
"""
Flush and close all log files. Called on clean exit.
[C: tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_logging_e2e.py:e2e_setup, tests/test_logging_e2e.py:test_logging_e2e, tests/test_session_logger_optimization.py:temp_session_setup, tests/test_session_logger_reset.py:temp_logs, tests/test_session_logging.py:temp_logs]
"""
global _comms_fh, _tool_fh, _api_fh, _cli_fh, _session_id
if _comms_fh is None:
return
@@ -130,8 +135,10 @@ def log_api_hook(method: str, path: str, payload: str) -> None:
def log_comms(entry: dict[str, Any]) -> None:
"""
Append one comms entry to the comms log file as a JSON-L line.
Thread-safe (GIL + line-buffered file).
[C: tests/test_logging_e2e.py:test_logging_e2e]
"""
if _comms_fh is None:
return
@@ -142,8 +149,10 @@ def log_comms(entry: dict[str, Any]) -> None:
def log_tool_call(script: str, result: str, script_path: Optional[str]) -> Optional[str]:
"""
Append a tool-call record to the toolcalls log and write the PS1 script to
the session's scripts directory. Returns the path of the written script file.
[C: tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts]
"""
global _seq
if _tool_fh is None:
@@ -184,8 +193,10 @@ def log_tool_call(script: str, result: str, script_path: Optional[str]) -> Optio
def log_tool_output(content: str) -> Optional[str]:
"""
Save tool output content to a unique file in the session's outputs directory.
Returns the path of the written file.
[C: tests/test_session_logger_optimization.py:test_log_tool_output_returns_none_if_no_session, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs]
"""
global _output_seq
if _session_dir is None:
@@ -222,4 +233,3 @@ def log_cli_call(command: str, stdin_content: Optional[str], stdout_content: Opt
_cli_fh.flush()
except Exception:
pass
+18
View File
@@ -7,6 +7,9 @@ class ShaderManager:
self.pp_program = None
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
"""
[C: tests/test_shader_manager.py:test_shader_manager_initialization_and_compilation]
"""
program = gl.glCreateProgram()
def _compile(src, shader_type):
@@ -41,6 +44,9 @@ class ShaderManager:
return program
def update_uniforms(self, uniforms: dict):
"""
[C: tests/test_shader_manager.py:test_shader_manager_uniform_update]
"""
if self.program is None:
return
@@ -62,6 +68,9 @@ class ShaderManager:
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
def setup_background_shader(self):
"""
[C: tests/test_dynamic_background.py:test_dynamic_background_rendering]
"""
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
@@ -88,6 +97,9 @@ void main() {
self.bg_program = self.compile_shader(vertex_src, fragment_src)
def render_background(self, width, height, time):
"""
[C: tests/test_dynamic_background.py:test_dynamic_background_rendering]
"""
if not self.bg_program:
return
gl.glUseProgram(self.bg_program)
@@ -101,6 +113,9 @@ void main() {
gl.glUseProgram(0)
def setup_post_process_shader(self):
"""
[C: tests/test_post_process.py:TestPostProcess.test_setup_post_process_shader]
"""
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
@@ -137,6 +152,9 @@ void main() {
self.pp_program = self.compile_shader(vertex_src, fragment_src)
def render_post_process(self, texture_id, width, height, time):
"""
[C: tests/test_post_process.py:TestPostProcess.test_render_post_process]
"""
if not self.pp_program:
return
gl.glUseProgram(self.pp_program)
+2 -1
View File
@@ -2,6 +2,7 @@ from imgui_bundle import imgui
def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None:
"""
Simulates a soft shadow effect by drawing multiple concentric rounded rectangles
with decreasing alpha values. This is a faux-shader effect using primitive batching.
"""
@@ -38,6 +39,7 @@ def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: im
def apply_faux_acrylic_glass(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, base_color: imgui.ImVec4, rounding: float = 0.0) -> None:
"""
Simulates a faux acrylic/glass effect by drawing a semi-transparent base,
a gradient overlay for 'shine', and a subtle inner border for 'edge glow'.
"""
@@ -70,4 +72,3 @@ def apply_faux_acrylic_glass(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none,
thickness=1.0
)
+2 -1
View File
@@ -53,12 +53,14 @@ def _build_subprocess_env() -> dict[str, str]:
def run_powershell(script: str, base_dir: str, qa_callback: Optional[Callable[[str], str]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
"""
Run a PowerShell script with working directory set to base_dir.
Returns a string combining stdout, stderr, and exit code.
Environment is configured via mcp_env.toml (project root).
If qa_callback is provided and the command fails or has stderr,
the callback is called with the stderr content and its result is appended.
If patch_callback is provided, it receives (error, file_context) and returns patch text.
[C: tests/test_tier4_interceptor.py:test_run_powershell_no_qa_callback_on_success, tests/test_tier4_interceptor.py:test_run_powershell_optional_qa_callback, tests/test_tier4_interceptor.py:test_run_powershell_qa_callback_on_failure, tests/test_tier4_interceptor.py:test_run_powershell_qa_callback_on_stderr_only]
"""
safe_dir: str = str(base_dir).replace("'", "''")
full_script: str = f"Set-Location -LiteralPath '{safe_dir}'\n{script}"
@@ -97,4 +99,3 @@ def run_powershell(script: str, base_dir: str, qa_callback: Optional[Callable[[s
if 'process' in locals() and process:
subprocess.run(["taskkill", "/F", "/T", "/PID", str(process.pid)], capture_output=True)
return f"ERROR: {e}"
+4 -1
View File
@@ -153,8 +153,10 @@ _SUMMARISERS: dict[str, Callable[[Path, str], str]] = {
def summarise_file(path: Path, content: str) -> str:
"""
Return a compact markdown summary string for a single file.
`content` is the already-read file text (or an error string).
[C: tests/test_subagent_summarization.py:test_summarise_file_integration]
"""
content_hash = get_file_hash(content)
cached = _summary_cache.get_summary(str(path), content_hash)
@@ -190,6 +192,7 @@ def summarise_file(path: Path, content: str) -> str:
def summarise_items(file_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""
Given a list of file_item dicts (as returned by aggregate.build_file_items),
return a parallel list of dicts with an added `summary` key.
"""
@@ -208,6 +211,7 @@ def summarise_items(file_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
def build_summary_markdown(file_items: list[dict[str, Any]]) -> str:
"""
Build a compact markdown string of file summaries, suitable for the
initial <context> block instead of full file contents.
"""
@@ -218,4 +222,3 @@ def build_summary_markdown(file_items: list[dict[str, Any]]) -> str:
summary = item.get("summary", "")
parts.append(f"### `{path}`\n\n{summary}")
return "\n\n---\n\n".join(parts)
+21 -5
View File
@@ -4,11 +4,15 @@ from pathlib import Path
from typing import Optional, Dict
def get_file_hash(content: str) -> str:
"""Returns SHA256 hash of the content."""
"""
Returns SHA256 hash of the content.
[C: tests/test_summary_cache.py:test_get_file_hash, tests/test_summary_cache.py:test_summary_cache]
"""
return hashlib.sha256(content.encode("utf-8")).hexdigest()
class SummaryCache:
"""
A hash-based cache for file summaries to avoid redundant processing.
Invalidates when content hash changes.
"""
@@ -23,7 +27,10 @@ class SummaryCache:
self.load()
def load(self) -> None:
"""Loads cache from disk."""
"""
Loads cache from disk.
[C: src/tool_presets.py:ToolPresetManager._read_raw, src/workspace_manager.py:WorkspaceManager._load_file, tests/test_gui_phase3.py:test_create_track, tests/test_history_management.py:test_save_separation, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry, tests/test_visual_sim_gui_ux.py:test_gui_track_creation]
"""
if self.cache_file.exists():
try:
with open(self.cache_file, "r", encoding="utf-8") as f:
@@ -41,7 +48,10 @@ class SummaryCache:
pass
def get_summary(self, file_path: str, content_hash: str) -> Optional[str]:
"""Returns cached summary if hash matches, otherwise None."""
"""
Returns cached summary if hash matches, otherwise None.
[C: tests/test_summary_cache.py:test_summary_cache, tests/test_summary_cache.py:test_summary_cache_lru]
"""
entry = self.cache.get(file_path)
if entry and entry.get("hash") == content_hash:
# LRU: move to end
@@ -51,7 +61,10 @@ class SummaryCache:
return None
def set_summary(self, file_path: str, content_hash: str, summary: str) -> None:
"""Stores summary in cache and saves to disk."""
"""
Stores summary in cache and saves to disk.
[C: tests/test_summary_cache.py:test_summary_cache, tests/test_summary_cache.py:test_summary_cache_lru]
"""
if file_path in self.cache:
self.cache.pop(file_path)
self.cache[file_path] = {
@@ -66,7 +79,10 @@ class SummaryCache:
self.save()
def clear(self) -> None:
"""Clears the cache both in-memory and on disk."""
"""
Clears the cache both in-memory and on disk.
[C: tests/conftest.py:reset_ai_client]
"""
self.cache.clear()
if self.cache_file.exists():
try:
+3
View File
@@ -1,4 +1,7 @@
def format_takes_diff(takes: dict[str, list[dict]]) -> str:
"""
[C: tests/test_synthesis_formatter.py:test_format_takes_diff_common_prefix, tests/test_synthesis_formatter.py:test_format_takes_diff_empty, tests/test_synthesis_formatter.py:test_format_takes_diff_no_common_prefix, tests/test_synthesis_formatter.py:test_format_takes_diff_single_take]
"""
if not takes:
return ""
+19 -5
View File
@@ -293,11 +293,17 @@ def get_current_scale() -> float:
return _current_scale
def get_shader_config(key: str) -> Any:
"""Get a specific shader configuration value."""
"""
Get a specific shader configuration value.
[C: tests/test_shader_config.py:test_shader_config_parsing]
"""
return _shader_config.get(key)
def get_window_frame_config() -> bool:
"""Get the window frame configuration."""
"""
Get the window frame configuration.
[C: tests/test_shader_config.py:test_shader_config_parsing]
"""
return _shader_config.get("custom_window_frame", False)
def get_palette_colours(name: str) -> dict[str, Any]:
@@ -306,10 +312,12 @@ def get_palette_colours(name: str) -> dict[str, Any]:
def apply(palette_name: str, overrides: dict[str, Any] | None = None) -> None:
"""
Build a global DPG theme from the named palette plus optional per-colour
overrides, and bind it as the default theme.
overrides: {colour_key: (R,G,B) or (R,G,B,A)} merged on top of palette.
[C: src/theme_2.py:apply_current, src/theme_2.py:set_child_transparency, src/theme_2.py:set_transparency, tests/test_theme.py:test_theme_apply_sets_rounding_and_padding]
"""
global _current_theme_tag, _current_palette
_current_palette = palette_name
@@ -349,6 +357,7 @@ def apply(palette_name: str, overrides: dict[str, Any] | None = None) -> None:
def apply_font(font_path: str, size: float = 14.0) -> None:
"""
Load the TTF at font_path at the given point size and bind it globally.
Safe to call multiple times. Uses a single persistent font_registry; only
the font *item* tag is tracked. Passing an empty path or a missing file
@@ -378,7 +387,10 @@ def apply_font(font_path: str, size: float = 14.0) -> None:
dpg.bind_font(font)
def set_scale(factor: float) -> None:
"""Set the global Dear PyGui font/UI scale factor."""
"""
Set the global Dear PyGui font/UI scale factor.
[C: src/theme_2.py:apply_current]
"""
global _current_scale
_current_scale = factor
dpg.set_global_font_scale(factor)
@@ -392,7 +404,10 @@ def save_to_config(config: dict[str, Any]) -> None:
config["theme"]["scale"] = _current_scale
def load_from_config(config: dict[str, Any]) -> None:
"""Read [theme] from config and apply everything."""
"""
Read [theme] from config and apply everything.
[C: tests/test_shader_config.py:test_shader_config_parsing]
"""
t = config.get("theme", {})
palette = t.get("palette", "DPG Default")
font_path = t.get("font_path", "")
@@ -407,4 +422,3 @@ def load_from_config(config: dict[str, Any]) -> None:
_shader_config["bloom"] = t.get("shader_bloom", False)
_shader_config["bg"] = t.get("shader_bg", "none")
_shader_config["custom_window_frame"] = t.get("custom_window_frame", False)
+5 -2
View File
@@ -18,7 +18,10 @@ import src.theme_nerv
# Only keys that differ from the ImGui dark defaults need to be listed.
def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]:
"""Convert 0-255 RGBA to 0.0-1.0 floats."""
"""
Convert 0-255 RGBA to 0.0-1.0 floats.
[C: src/theme_nerv.py:module]
"""
return (r / 255.0, g / 255.0, b / 255.0, a / 255.0)
_PALETTES: dict[str, dict[int, tuple]] = {
@@ -271,6 +274,7 @@ def set_child_transparency(val: float) -> None:
def apply(palette_name: str) -> None:
"""
Apply a named palette by setting all ImGui style colors and applying global professional styling.
"""
global _current_palette
@@ -392,4 +396,3 @@ def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme:
# Sync tweaks
tt.tweaks.rounding = 6.0
return tt
+4 -2
View File
@@ -62,7 +62,10 @@ NERV_PALETTE = {
}
def apply_nerv() -> None:
"""Apply NERV theme with hard edges and specific palette."""
"""
Apply NERV theme with hard edges and specific palette.
[C: tests/test_theme_nerv.py:test_apply_nerv_sets_rounding_and_colors]
"""
style = imgui.get_style()
for col_enum, rgba in NERV_PALETTE.items():
style.set_color_(col_enum, imgui.ImVec4(*rgba))
@@ -82,4 +85,3 @@ def apply_nerv() -> None:
style.popup_border_size = 1.0
style.child_border_size = 1.0
style.tab_border_size = 1.0
+12 -1
View File
@@ -8,6 +8,9 @@ class CRTFilter:
self.enabled = True
def render(self, width: float, height: float):
"""
[C: 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]
"""
if not self.enabled:
return
draw_list = imgui.get_foreground_draw_list()
@@ -64,6 +67,9 @@ class CRTFilter:
class StatusFlicker:
def get_alpha(self) -> float:
# Modulate between 0.7 and 1.0 using sin wave
"""
[C: tests/test_theme_nerv_fx.py:TestThemeNervFx.test_status_flicker_get_alpha]
"""
return 0.85 + 0.15 * math.sin(time.time() * 20.0)
class AlertPulsing:
@@ -71,9 +77,15 @@ class AlertPulsing:
self.active = False
def update(self, status: str):
"""
[C: tests/test_spawn_interception_v2.py:MockDialog.wait, tests/test_theme_nerv_alert.py:test_alert_pulsing_update, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_update]
"""
self.active = status.lower().startswith("error")
def render(self, width: float, height: float):
"""
[C: 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]
"""
if not self.active:
return
draw_list = imgui.get_foreground_draw_list()
@@ -83,4 +95,3 @@ class AlertPulsing:
alpha = 0.05 + 0.15 * ((math.sin(time.time() * 4.0) + 1.0) / 2.0)
color = imgui.get_color_u32((1.0, 0.0, 0.0, alpha))
draw_list.add_rect((0.0, 0.0), (width, height), color, 0.0, 0, 10.0)
+2
View File
@@ -4,9 +4,11 @@ from src.models import ThinkingSegment
def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]:
"""
Parses thinking segments from text and returns (segments, response_content).
Support extraction of thinking traces from <thinking>...</thinking>, <thought>...</thought>,
and blocks prefixed with Thinking:.
[C: tests/test_thinking_trace.py:test_parse_empty_response, tests/test_thinking_trace.py:test_parse_multiple_markers, tests/test_thinking_trace.py:test_parse_no_thinking, tests/test_thinking_trace.py:test_parse_text_thinking_prefix, tests/test_thinking_trace.py:test_parse_thinking_with_empty_response, tests/test_thinking_trace.py:test_parse_xml_thinking_tag, tests/test_thinking_trace.py:test_parse_xml_thought_tag]
"""
segments = []
+6 -1
View File
@@ -3,6 +3,9 @@ from src.models import Tool, ToolPreset, BiasProfile
class ToolBiasEngine:
def apply_semantic_nudges(self, tool_definitions: List[Dict[str, Any]], preset: ToolPreset) -> List[Dict[str, Any]]:
"""
[C: tests/test_tool_bias.py:test_apply_semantic_nudges, tests/test_tool_bias.py:test_parameter_bias_nudging]
"""
weight_map = {
5: "[HIGH PRIORITY] ",
4: "[PREFERRED] ",
@@ -36,6 +39,9 @@ class ToolBiasEngine:
return tool_definitions
def generate_tooling_strategy(self, preset: ToolPreset, global_bias: BiasProfile) -> str:
"""
[C: tests/test_tool_bias.py:test_generate_tooling_strategy]
"""
lines = ["### Tooling Strategy"]
preferred = []
@@ -63,4 +69,3 @@ class ToolBiasEngine:
lines.append(f"- {cat}: {mult}x")
return "\n\n".join(lines)
+25 -2
View File
@@ -10,6 +10,9 @@ class ToolPresetManager:
self.project_root = Path(project_root) if project_root else None
def _get_path(self, scope: str) -> Path:
"""
[C: src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.save_profile]
"""
if scope == "global":
return paths.get_global_tool_presets_path()
elif scope == "project":
@@ -34,6 +37,9 @@ class ToolPresetManager:
tomli_w.dump(data, f)
def load_all_presets(self) -> Dict[str, ToolPreset]:
"""
[C: tests/test_tool_preset_manager.py:test_load_all_presets_merged]
"""
global_path = paths.get_global_tool_presets_path()
global_data = self._read_raw(global_path).get("presets", {})
@@ -52,10 +58,16 @@ class ToolPresetManager:
return presets
def load_all(self) -> Dict[str, ToolPreset]:
"""Backward compatibility for load_all()."""
"""
Backward compatibility for load_all().
[C: tests/test_persona_manager.py:test_delete_persona, tests/test_persona_manager.py:test_load_all_merged, tests/test_persona_manager.py:test_save_persona, tests/test_preset_manager.py:test_delete_preset, tests/test_preset_manager.py:test_load_all_merged, tests/test_preset_manager.py:test_save_preset_global, tests/test_preset_manager.py:test_save_preset_project, tests/test_presets.py:TestPresetManager.test_delete_preset, tests/test_presets.py:TestPresetManager.test_project_overwrites_global, tests/test_presets.py:TestPresetManager.test_save_and_load_global, tests/test_presets.py:TestPresetManager.test_save_and_load_project]
"""
return self.load_all_presets()
def save_preset(self, preset: ToolPreset, scope: str = "project") -> None:
"""
[C: tests/test_preset_manager.py:test_save_preset_global, tests/test_preset_manager.py:test_save_preset_project, tests/test_preset_manager.py:test_save_preset_project_no_root, tests/test_presets.py:TestPresetManager.test_delete_preset, tests/test_presets.py:TestPresetManager.test_project_overwrites_global, tests/test_presets.py:TestPresetManager.test_save_and_load_global, tests/test_presets.py:TestPresetManager.test_save_and_load_project]
"""
path = self._get_path(scope)
data = self._read_raw(path)
if "presets" not in data:
@@ -64,6 +76,9 @@ class ToolPresetManager:
self._write_raw(path, data)
def delete_preset(self, name: str, scope: str = "project") -> None:
"""
[C: tests/test_preset_manager.py:test_delete_preset, tests/test_presets.py:TestPresetManager.test_delete_preset]
"""
path = self._get_path(scope)
data = self._read_raw(path)
if "presets" in data and name in data["presets"]:
@@ -71,6 +86,9 @@ class ToolPresetManager:
self._write_raw(path, data)
def load_all_bias_profiles(self) -> Dict[str, BiasProfile]:
"""
[C: tests/test_tool_preset_manager.py:test_bias_profiles_merged, tests/test_tool_preset_manager.py:test_delete_bias_profile, tests/test_tool_preset_manager.py:test_save_bias_profile]
"""
global_path = paths.get_global_tool_presets_path()
global_data = self._read_raw(global_path).get("bias_profiles", {})
@@ -95,6 +113,9 @@ class ToolPresetManager:
return profiles
def save_bias_profile(self, profile: BiasProfile, scope: str = "project") -> None:
"""
[C: tests/test_tool_preset_manager.py:test_save_bias_profile]
"""
path = self._get_path(scope)
data = self._read_raw(path)
if "bias_profiles" not in data:
@@ -103,9 +124,11 @@ class ToolPresetManager:
self._write_raw(path, data)
def delete_bias_profile(self, name: str, scope: str = "project") -> None:
"""
[C: tests/test_tool_preset_manager.py:test_delete_bias_profile]
"""
path = self._get_path(scope)
data = self._read_raw(path)
if "bias_profiles" in data and name in data["bias_profiles"]:
del data["bias_profiles"][name]
self._write_raw(path, data)
+10 -1
View File
@@ -25,7 +25,10 @@ class WorkspaceManager:
raise ValueError("Invalid scope, must be 'global' or 'project'")
def load_all_profiles(self) -> Dict[str, WorkspaceProfile]:
"""Merges global and project profiles into a single dictionary."""
"""
Merges global and project profiles into a single dictionary.
[C: tests/test_workspace_manager.py:test_delete_profile, tests/test_workspace_manager.py:test_load_all_profiles_merged, tests/test_workspace_manager.py:test_save_profile_global_and_project]
"""
profiles = {}
global_path = paths.get_global_workspace_profiles_path()
@@ -42,6 +45,9 @@ class WorkspaceManager:
return profiles
def save_profile(self, profile: WorkspaceProfile, scope: str = "project") -> None:
"""
[C: tests/test_workspace_manager.py:test_delete_profile, tests/test_workspace_manager.py:test_save_profile_global_and_project]
"""
path = self._get_path(scope)
data = self._load_file(path)
if "profiles" not in data:
@@ -51,6 +57,9 @@ class WorkspaceManager:
self._save_file(path, data)
def delete_profile(self, name: str, scope: str = "project") -> None:
"""
[C: tests/test_workspace_manager.py:test_delete_profile]
"""
path = self._get_path(scope)
data = self._load_file(path)
if "profiles" in data and name in data["profiles"]:
+12
View File
@@ -28,6 +28,9 @@ class VerificationLogger:
self.logs_dir.mkdir(parents=True, exist_ok=True)
def log_state(self, field: str, before: Any, after: Any) -> None:
"""
[C: tests/test_ai_style_formatter.py:test_multiple_top_level_definitions, 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_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_tier4_interceptor.py:test_run_powershell_qa_callback_on_failure, tests/test_vlogger_availability.py:test_vlogger_available]
"""
delta = ""
if isinstance(before, (int, float)) and isinstance(after, (int, float)):
diff = after - before
@@ -40,6 +43,9 @@ class VerificationLogger:
})
def finalize(self, title: str, status: str, result_msg: str) -> None:
"""
[C: tests/test_ai_style_formatter.py:test_multiple_top_level_definitions, 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_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_tier4_interceptor.py:test_end_to_end_tier4_integration, tests/test_tier4_interceptor.py:test_run_powershell_qa_callback_on_failure, tests/test_tier4_interceptor.py:test_run_powershell_qa_callback_on_stderr_only, tests/test_vlogger_availability.py:test_vlogger_available]
"""
round(time.time() - self.start_time, 2)
log_file = self.logs_dir / f"{self.script_name}.txt"
with open(log_file, "w", encoding="utf-8") as f:
@@ -57,6 +63,7 @@ class VerificationLogger:
@pytest.fixture(autouse=True)
def reset_paths() -> Generator[None, None, None]:
"""
Autouse fixture that resets the paths global state before each test.
"""
from src import paths
@@ -67,6 +74,7 @@ def reset_paths() -> Generator[None, None, None]:
@pytest.fixture(autouse=True)
def reset_ai_client() -> Generator[None, None, None]:
"""
Autouse fixture that resets the ai_client global state before each test.
This is critical for preventing state pollution between tests.
"""
@@ -115,6 +123,7 @@ def kill_process_tree(pid: int | None) -> None:
@pytest.fixture
def mock_app() -> Generator[App, None, None]:
"""
Mock version of the App for simple unit tests that don't need a loop.
"""
with (
@@ -146,8 +155,10 @@ def mock_app() -> Generator[App, None, None]:
@pytest.fixture
def app_instance() -> Generator[App, None, None]:
"""
Centralized App instance with all external side effects mocked.
Matches the pattern used in test_token_viz.py and test_gui_phase4.py.
[C: tests/test_gui2_events.py:test_app_subscribes_to_events]
"""
with (
patch('src.models.load_config', return_value={
@@ -180,6 +191,7 @@ def app_instance() -> Generator[App, None, None]:
@pytest.fixture(scope="session")
def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
"""
Session-scoped fixture that starts sloppy.py with --enable-test-hooks.
Includes high-signal environment telemetry and workspace isolation.
"""
+4 -1
View File
@@ -9,7 +9,10 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from src.api_hook_client import ApiHookClient
def wait_for_value(client, field, expected, timeout=5):
"""Polls the GUI state until a field matches the expected value."""
"""
Polls the GUI state until a field matches the expected value.
[C: tests/test_live_workflow.py:test_full_live_workflow]
"""
start = time.time()
while time.time() - start < timeout:
val = client.get_value(field)
+1
View File
@@ -2,6 +2,7 @@ from src import ai_client
def test_list_models_gemini_cli() -> None:
"""
Verifies that 'ai_client.list_models' correctly returns a list of models
for the 'gemini_cli' provider.
"""
+2
View File
@@ -36,6 +36,7 @@ def app_controller(tmp_session_dir):
def test_on_comms_entry_tool_result_offloading(app_controller, tmp_session_dir):
"""
Test that _on_comms_entry offloads tool_result output to a separate file.
"""
output_content = "This is a large tool output that should be offloaded."
@@ -81,6 +82,7 @@ def test_on_comms_entry_tool_result_offloading(app_controller, tmp_session_dir):
def test_on_tool_log_offloading(app_controller, tmp_session_dir):
"""
Test that _on_tool_log calls session_logger.log_tool_call and log_tool_output.
"""
script = "Get-Process"
-1
View File
@@ -324,4 +324,3 @@ public:
assert 'int y = 2;' in updated
assert 'int x = 1;' not in updated
assert 'class MyClass {' in updated
+2
View File
@@ -8,6 +8,7 @@ from src import mcp_client
@pytest.mark.asyncio
async def test_execute_tool_calls_concurrently_timing():
"""
Verifies that _execute_tool_calls_concurrently runs tools in parallel.
Total time should be approx 0.5s for 3 tools each taking 0.5s.
"""
@@ -65,6 +66,7 @@ async def test_execute_tool_calls_concurrently_timing():
@pytest.mark.asyncio
async def test_execute_tool_calls_concurrently_exception_handling():
"""
Verifies that if one tool call fails, it doesn't crash the whole group if caught,
but currently gather is used WITHOUT return_exceptions=True, so it should re-raise.
"""
+1
View File
@@ -21,6 +21,7 @@ class TestCliToolBridgeMapping(unittest.TestCase):
@patch('api_hook_client.ApiHookClient.request_confirmation')
def test_mapping_from_api_format(self, mock_request: MagicMock, mock_stdout: MagicMock, mock_stdin: MagicMock) -> None:
"""
Verify that bridge correctly maps 'id', 'name', 'input' (Gemini API format)
into tool_name and tool_input for the hook client.
"""
+1
View File
@@ -6,6 +6,7 @@ import threading
def test_conductor_abort_event_populated():
"""
Test that ConductorEngine populates _abort_events when spawning a worker.
"""
# 1. Mock WorkerPool.spawn to return a mock thread
+5 -2
View File
@@ -3,6 +3,7 @@ from src.api_hook_client import ApiHookClient
def simulate_conductor_phase_completion(client: ApiHookClient, track_id: str, phase_name: str) -> bool:
"""
Simulates the Conductor agent's logic for phase completion using ApiHookClient.
"""
try:
@@ -22,8 +23,10 @@ def simulate_conductor_phase_completion(client: ApiHookClient, track_id: str, ph
return False
def test_conductor_integrates_api_hook_client_for_verification(live_gui) -> None:
"""Verify that Conductor's simulated phase completion logic properly integrates
with the ApiHookClient and the live Hook Server."""
"""
Verify that Conductor's simulated phase completion logic properly integrates
with the ApiHookClient and the live Hook Server.
"""
client = ApiHookClient()
assert client.wait_for_server(timeout=10)
+5
View File
@@ -7,6 +7,7 @@ from src.models import Track
def test_conductor_engine_initializes_empty_worker_and_abort_dicts() -> None:
"""
Test that ConductorEngine correctly initializes _active_workers and _abort_events as empty dictionaries.
"""
# Mock the track object
@@ -22,6 +23,7 @@ def test_conductor_engine_initializes_empty_worker_and_abort_dicts() -> None:
def test_kill_worker_sets_abort_and_joins_thread() -> None:
"""
Test kill_worker: mock a running thread in _active_workers, call kill_worker,
assert abort_event is set and thread is joined.
"""
@@ -35,6 +37,9 @@ def test_kill_worker_sets_abort_and_joins_thread() -> None:
# Create a thread that waits for the abort event
def worker():
"""
[C: tests/test_symbol_parsing.py:test_handle_generate_send_appends_definitions, tests/test_symbol_parsing.py:test_handle_generate_send_no_symbols]
"""
abort_event.wait(timeout=2.0)
thread = threading.Thread(target=worker)
+10
View File
@@ -9,6 +9,7 @@ from src import ai_client
def test_conductor_engine_initialization() -> None:
"""
Test that ConductorEngine can be initialized with a Track.
"""
track = Track(id="test_track", description="Test Track")
@@ -18,6 +19,7 @@ def test_conductor_engine_initialization() -> None:
def test_conductor_engine_run_executes_tickets_in_order(monkeypatch: pytest.MonkeyPatch, vlogger) -> None:
"""
Test that run iterates through executable tickets and calls the worker lifecycle.
"""
ticket1 = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
@@ -64,6 +66,7 @@ def test_conductor_engine_run_executes_tickets_in_order(monkeypatch: pytest.Monk
def test_run_worker_lifecycle_calls_ai_client_send(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle triggers the AI client and updates ticket status on success.
"""
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
@@ -84,6 +87,7 @@ def test_run_worker_lifecycle_calls_ai_client_send(monkeypatch: pytest.MonkeyPat
def test_run_worker_lifecycle_context_injection(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle can take a context_files list and injects AST views into the prompt.
"""
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
@@ -129,6 +133,7 @@ def test_run_worker_lifecycle_context_injection(monkeypatch: pytest.MonkeyPatch)
def test_run_worker_lifecycle_handles_blocked_response(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle marks the ticket as blocked if the AI indicates it cannot proceed.
"""
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
@@ -145,6 +150,7 @@ def test_run_worker_lifecycle_handles_blocked_response(monkeypatch: pytest.Monke
def test_run_worker_lifecycle_step_mode_confirmation(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle passes confirm_execution to ai_client.send when step_mode is True.
Verify that if confirm_execution is called (simulated by mocking ai_client.send to call its callback),
the flow works as expected.
@@ -181,6 +187,7 @@ def test_run_worker_lifecycle_step_mode_confirmation(monkeypatch: pytest.MonkeyP
def test_run_worker_lifecycle_step_mode_rejection(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Verify that if confirm_execution returns False, the logic (in ai_client, which we simulate here)
would prevent execution. In run_worker_lifecycle, we just check if it's passed.
"""
@@ -205,6 +212,7 @@ def test_run_worker_lifecycle_step_mode_rejection(monkeypatch: pytest.MonkeyPatc
def test_conductor_engine_dynamic_parsing_and_execution(monkeypatch: pytest.MonkeyPatch, vlogger) -> None:
"""
Test that parse_json_tickets correctly populates the track and run executes them in dependency order.
"""
import json
@@ -272,6 +280,7 @@ def test_conductor_engine_dynamic_parsing_and_execution(monkeypatch: pytest.Monk
def test_run_worker_lifecycle_pushes_response_via_queue(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle pushes a 'response' event with the correct stream_id
via _queue_put when event_queue is provided.
"""
@@ -297,6 +306,7 @@ def test_run_worker_lifecycle_pushes_response_via_queue(monkeypatch: pytest.Monk
def test_run_worker_lifecycle_token_usage_from_comms_log(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Test that run_worker_lifecycle reads token usage from the comms log and
updates engine.tier_usage['Tier 3'] with real input/output token counts.
"""
+9
View File
@@ -9,6 +9,7 @@ from src.dag_engine import TrackDAG
def test_get_ready_tasks_linear():
"""
Verifies ready tasks detection in a simple linear dependency chain.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1")
@@ -20,6 +21,7 @@ def test_get_ready_tasks_linear():
def test_get_ready_tasks_branching():
"""
Verifies ready tasks detection in a branching dependency graph where multiple tasks
are unlocked simultaneously after a prerequisite is met.
"""
@@ -35,6 +37,7 @@ def test_get_ready_tasks_branching():
def test_has_cycle_no_cycle():
"""
Validates that an acyclic graph is correctly identified as not having cycles.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1")
@@ -44,6 +47,7 @@ def test_has_cycle_no_cycle():
def test_has_cycle_direct_cycle():
"""
Validates that a direct cycle (A depends on B, B depends on A) is correctly detected.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1", depends_on=["T2"])
@@ -53,6 +57,7 @@ def test_has_cycle_direct_cycle():
def test_has_cycle_indirect_cycle():
"""
Validates that an indirect cycle (A->B->C->A) is correctly detected.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1", depends_on=["T3"])
@@ -63,6 +68,7 @@ def test_has_cycle_indirect_cycle():
def test_has_cycle_complex_no_cycle():
"""
Validates cycle detection in a complex graph that merges branches but remains acyclic.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1")
@@ -74,6 +80,7 @@ def test_has_cycle_complex_no_cycle():
def test_get_ready_tasks_multiple_deps():
"""
Validates that a task is not marked ready until ALL of its dependencies are completed.
"""
t1 = Ticket(id="T1", description="desc", status="completed", assigned_to="worker1")
@@ -87,6 +94,7 @@ def test_get_ready_tasks_multiple_deps():
def test_topological_sort():
"""
Verifies that tasks are correctly ordered by dependencies regardless of input order.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1")
@@ -98,6 +106,7 @@ def test_topological_sort():
def test_topological_sort_cycle():
"""
Verifies that topological sorting safely aborts and raises ValueError when a cycle is present.
"""
t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1", depends_on=["T2"])
+5
View File
@@ -12,6 +12,7 @@ from src import project_manager
def test_credentials_error_mentions_deepseek(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Verify that the error message shown when credentials.toml is missing
includes deepseek instructions.
"""
@@ -25,6 +26,7 @@ def test_credentials_error_mentions_deepseek(monkeypatch: pytest.MonkeyPatch) ->
def test_default_project_includes_reasoning_role() -> None:
"""
Verify that 'Reasoning' is included in the default discussion roles
to support DeepSeek-R1 reasoning traces.
"""
@@ -34,6 +36,7 @@ def test_default_project_includes_reasoning_role() -> None:
def test_gui_providers_list() -> None:
"""
Check if 'deepseek' is in the GUI's provider list.
"""
from src.models import PROVIDERS
@@ -41,6 +44,7 @@ def test_gui_providers_list() -> None:
def test_deepseek_model_listing() -> None:
"""
Verify that list_models for deepseek returns expected models.
"""
models = ai_client.list_models("deepseek")
@@ -49,6 +53,7 @@ def test_deepseek_model_listing() -> None:
def test_gui_provider_list_via_hooks(live_gui: Any) -> None:
"""
Verify 'deepseek' is present in the GUI provider list using API hooks.
"""
from api_hook_client import ApiHookClient
+7
View File
@@ -4,6 +4,7 @@ from src import ai_client
def test_deepseek_model_selection() -> None:
"""
Verifies that ai_client.set_provider('deepseek', 'deepseek-chat') correctly updates the internal state.
"""
ai_client.set_provider("deepseek", "deepseek-chat")
@@ -13,6 +14,7 @@ def test_deepseek_model_selection() -> None:
@patch("requests.post")
def test_deepseek_completion_logic(mock_post: MagicMock) -> None:
"""
Verifies that ai_client.send() correctly calls the DeepSeek API and returns content.
"""
ai_client.set_provider("deepseek", "deepseek-chat")
@@ -31,6 +33,7 @@ def test_deepseek_completion_logic(mock_post: MagicMock) -> None:
@patch("requests.post")
def test_deepseek_reasoning_logic(mock_post: MagicMock) -> None:
"""
Verifies that reasoning_content is captured and wrapped in <thinking> tags.
"""
ai_client.set_provider("deepseek", "deepseek-reasoner")
@@ -52,6 +55,7 @@ def test_deepseek_reasoning_logic(mock_post: MagicMock) -> None:
@patch("requests.post")
def test_deepseek_tool_calling(mock_post: MagicMock) -> None:
"""
Verifies that DeepSeek provider correctly identifies and executes tool calls.
"""
ai_client.set_provider("deepseek", "deepseek-chat")
@@ -93,6 +97,7 @@ def test_deepseek_tool_calling(mock_post: MagicMock) -> None:
@patch("requests.post")
def test_deepseek_streaming(mock_post: MagicMock) -> None:
"""
Verifies that DeepSeek provider correctly aggregates streaming chunks.
"""
ai_client.set_provider("deepseek", "deepseek-chat")
@@ -115,6 +120,7 @@ def test_deepseek_streaming(mock_post: MagicMock) -> None:
@patch("requests.post")
def test_deepseek_payload_verification(mock_post: MagicMock) -> None:
"""
Verifies that the correct JSON payload (tools, history, params) is sent to DeepSeek.
"""
ai_client.set_provider("deepseek", "deepseek-chat")
@@ -142,6 +148,7 @@ def test_deepseek_payload_verification(mock_post: MagicMock) -> None:
@patch("requests.post")
def test_deepseek_reasoner_payload_verification(mock_post: MagicMock) -> None:
"""
Verifies that deepseek-reasoner payload excludes tools and temperature.
"""
ai_client.set_provider("deepseek", "deepseek-reasoner")
-1
View File
@@ -70,4 +70,3 @@ def test_execution_sim_live(live_gui: Any) -> None:
sim.run()
time.sleep(2)
sim.teardown()
+1
View File
@@ -11,6 +11,7 @@ from src.ai_client import get_gemini_cache_stats, reset_session
def test_get_gemini_cache_stats_with_mock_client() -> None:
"""
Test that get_gemini_cache_stats correctly processes cache lists
from a mocked client instance.
"""
+1
View File
@@ -11,6 +11,7 @@ def app_instance(monkeypatch: pytest.MonkeyPatch) -> type[App]:
def test_app_subscribes_to_events(app_instance: type[App]) -> None:
"""
This test checks that the App's __init__ method subscribes the necessary
event handlers to the ai_client.events emitter.
"""
+2
View File
@@ -2,6 +2,7 @@ from src.gui_2 import App
def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
"""
Verifies that the new consolidated Hub windows are defined in the App's show_windows.
This ensures they will be available in the 'Windows' menu.
"""
@@ -18,6 +19,7 @@ def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
"""
Verifies that the old fragmented windows are removed from show_windows.
Note: Message, Response, and Tool Calls are kept as they are now optional standalone windows.
"""
+1
View File
@@ -5,6 +5,7 @@ from src import ai_client
def test_mcp_tool_call_is_dispatched(app_instance: App) -> None:
"""
This test verifies that when the AI returns a tool call for an MCP function,
the ai_client correctly dispatches it to mcp_client.
This will fail until mcp_client is properly integrated.
+3 -1
View File
@@ -25,6 +25,7 @@ def cleanup_callback_file() -> None:
def test_gui2_set_value_hook_works(live_gui: Any) -> None:
"""
Tests that the 'set_value' GUI hook is correctly implemented.
"""
client = ApiHookClient()
@@ -40,6 +41,7 @@ def test_gui2_set_value_hook_works(live_gui: Any) -> None:
def test_gui2_click_hook_works(live_gui: Any) -> None:
"""
Tests that the 'click' GUI hook for the 'Reset' button is implemented.
"""
client = ApiHookClient()
@@ -57,6 +59,7 @@ def test_gui2_click_hook_works(live_gui: Any) -> None:
def test_gui2_custom_callback_hook_works(live_gui: Any) -> None:
"""
Tests that the 'custom_callback' GUI hook is correctly implemented.
"""
client = ApiHookClient()
@@ -76,4 +79,3 @@ def test_gui2_custom_callback_hook_works(live_gui: Any) -> None:
with open(temp_workspace_file, "r") as f:
content = f.read()
assert content == test_data, "Callback executed, but file content is incorrect."
+2
View File
@@ -19,6 +19,7 @@ _shared_metrics = {}
def test_performance_benchmarking(live_gui: tuple) -> None:
"""
Collects performance metrics for the current GUI script over a 5-second window.
Ensures the application does not lock up and can report its internal state.
"""
@@ -65,6 +66,7 @@ def test_performance_benchmarking(live_gui: tuple) -> None:
def test_performance_baseline_check() -> None:
"""
Verifies that we have successfully collected performance metrics for sloppy.py
and that they meet the minimum 30 FPS baseline.
"""
+1
View File
@@ -14,6 +14,7 @@ def test_diagnostics_panel_initialization(app_instance: Any) -> None:
def test_diagnostics_history_updates(app_instance: Any) -> None:
"""
Verifies that the internal performance history is updated correctly.
This logic is inside the render loop in gui_2.py, but we can test
the data structure and initialization.
+3
View File
@@ -12,6 +12,9 @@ class MockApp:
self.ai_status = ""
def init_state(self):
"""
[C: tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_app_controller_init_state_loads_prompts]
"""
pass
from src.gui_2 import App
@@ -10,6 +10,7 @@ from api_hook_client import ApiHookClient
def test_idle_performance_requirements(live_gui) -> None:
"""
Requirement: GUI must maintain stable performance on idle.
"""
# Warmup to ensure GUI is ready
+3 -1
View File
@@ -11,6 +11,7 @@ from src import paths
def test_track_proposal_editing(app_instance):
"""
Verifies the structural integrity of track proposal items.
Ensures that track proposals can be edited and removed from the active list.
"""
@@ -33,6 +34,7 @@ def test_track_proposal_editing(app_instance):
def test_conductor_setup_scan(app_instance, tmp_path, monkeypatch):
"""
Verifies that the conductor setup scan properly iterates through the conductor directory,
counts files and lines, and identifies active tracks.
"""
@@ -60,6 +62,7 @@ def test_conductor_setup_scan(app_instance, tmp_path, monkeypatch):
def test_create_track(app_instance, tmp_path):
"""
Verifies that _cb_create_track properly creates the track folder
and populates the necessary boilerplate files (spec.md, plan.md, metadata.json).
"""
@@ -89,4 +92,3 @@ def test_create_track(app_instance, tmp_path):
assert data['id'] == track_dir.name
finally:
os.chdir(old_cwd)
+1
View File
@@ -2,6 +2,7 @@ import time
def test_gui_startup_smoke(live_gui):
"""
Smoke test to ensure the GUI starts and remains running.
"""
proc, _ = live_gui
+1
View File
@@ -4,6 +4,7 @@ from src.api_hook_client import ApiHookClient
def test_text_viewer_state_update(live_gui) -> None:
"""
Verifies that we can set text viewer state and it is reflected in GUI state.
"""
client = ApiHookClient()
+3
View File
@@ -16,6 +16,7 @@ from src.gui_2 import App
def test_telemetry_data_updates_correctly(app_instance: Any) -> None:
"""
Tests that the _refresh_api_metrics method correctly updates
the internal state for display by querying the ai_client.
Verifies the boundary between GUI state and API state.
@@ -41,6 +42,7 @@ def test_telemetry_data_updates_correctly(app_instance: Any) -> None:
def test_performance_history_updates(app_instance: Any) -> None:
"""
Verify the data structure that feeds the sparkline.
This ensures that the rolling buffer for performance telemetry maintains
the correct size and default initialization to prevent GUI rendering crashes.
@@ -51,6 +53,7 @@ def test_performance_history_updates(app_instance: Any) -> None:
def test_gui_updates_on_event(app_instance: App) -> None:
"""
Verifies that when an API event is received (e.g. from ai_client),
the _on_api_event handler correctly updates internal metrics and
queues the update to be processed by the GUI event loop.
+1
View File
@@ -5,6 +5,7 @@ from src.api_hook_client import ApiHookClient
@pytest.mark.asyncio
async def test_mma_track_lifecycle_simulation():
"""
This test simulates the sequence of API calls an external orchestrator
would make to manage an MMA track lifecycle via the Hook API.
It verifies that ApiHookClient correctly routes requests to the
+2
View File
@@ -9,6 +9,7 @@ from src import ai_client
@pytest.mark.asyncio
async def test_headless_verification_full_run(vlogger) -> None:
"""
1. Initialize a ConductorEngine with a Track containing multiple dependent Tickets.
2. Simulate a full execution run using engine.run().
3. Mock ai_client.send to simulate successful tool calls and final responses.
@@ -47,6 +48,7 @@ async def test_headless_verification_full_run(vlogger) -> None:
@pytest.mark.asyncio
async def test_headless_verification_error_and_qa_interceptor(vlogger) -> None:
"""
5. Simulate a shell error and verify that the Tier 4 QA interceptor is triggered
and its summary is injected into the worker's history for the next retry.
"""
+4
View File
@@ -11,6 +11,7 @@ from src.gui_2 import App
def test_new_hubs_defined_in_show_windows(mock_app: App) -> None:
"""
Verifies that the new consolidated Hub windows are defined in the App's show_windows.
This ensures they will be available in the 'Windows' menu.
"""
@@ -25,6 +26,7 @@ def test_new_hubs_defined_in_show_windows(mock_app: App) -> None:
def test_old_windows_removed_from_gui2(app_instance_simple: Any) -> None:
"""
Verifies that the old fragmented windows are removed or renamed.
"""
old_tags = [
@@ -51,6 +53,7 @@ def app_instance_simple() -> Any:
def test_hub_windows_exist_in_gui2(app_instance_simple: Any) -> None:
"""
Verifies that the new Hub windows are present in the show_windows dictionary.
"""
hubs = ["Project Settings", "AI Settings", "Discussion Hub", "Operations Hub"]
@@ -59,6 +62,7 @@ def test_hub_windows_exist_in_gui2(app_instance_simple: Any) -> None:
def test_indicators_logic_exists(app_instance_simple: Any) -> None:
"""
Verifies that the status indicators logic exists in the App.
"""
assert hasattr(app_instance_simple, 'ai_status')
+2
View File
@@ -13,6 +13,7 @@ from src.api_hook_client import ApiHookClient
@pytest.mark.timeout(10)
def test_user_request_integration_flow(mock_app: App) -> None:
"""
Verifies that pushing a UserRequestEvent to the event_queue:
1. Triggers ai_client.send
2. Results in a 'response' event back to the queue
@@ -74,6 +75,7 @@ def test_user_request_integration_flow(mock_app: App) -> None:
@pytest.mark.timeout(10)
def test_user_request_error_handling(mock_app: App) -> None:
"""
Verifies that if ai_client.send raises an exception, the UI is updated with the error state.
"""
app = mock_app
+2
View File
@@ -15,6 +15,7 @@ from src.api_hook_client import ApiHookClient
def wait_for_value(client, field, expected, timeout=10):
"""
Helper to poll the GUI state until a field matches the expected value.
"""
start = time.time()
@@ -29,6 +30,7 @@ def wait_for_value(client, field, expected, timeout=10):
@pytest.mark.integration
def test_full_live_workflow(live_gui) -> None:
"""
Integration test that drives the GUI through a full workflow.
ANTI-SIMPLIFICATION: Asserts exact AI behavior, thinking state tracking,
and response logging in discussion history.
+6
View File
@@ -5,6 +5,9 @@ from src.gui_2 import App
def _make_app(**kwargs):
"""
[C: tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier1_renders_stream_content, tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier3_renders_worker_subheaders]
"""
app = MagicMock()
app.mma_streams = kwargs.get("mma_streams", {})
app.mma_tier_usage = kwargs.get("mma_tier_usage", {
@@ -61,6 +64,9 @@ def _make_app(**kwargs):
return app
def _make_imgui_mock():
"""
[C: tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier1_renders_stream_content, tests/test_mma_dashboard_streams.py:TestMMADashboardStreams.test_tier3_renders_worker_subheaders]
"""
m = MagicMock()
m.begin_table.return_value = False
m.begin_child.return_value = False
+5 -1
View File
@@ -10,7 +10,10 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "s
from src import api_hook_client
def _poll_mma_status(client, timeout, condition, label):
"""Poll get_mma_status() until condition(status) is True or timeout."""
"""
Poll get_mma_status() until condition(status) is True or timeout.
[C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
"""
last_status = {}
for i in range(timeout):
status = client.get_mma_status() or {}
@@ -24,6 +27,7 @@ def _poll_mma_status(client, timeout, condition, label):
@pytest.mark.timeout(300)
def test_mma_concurrent_tracks_execution(live_gui) -> None:
"""
Stress test for concurrent MMA track execution.
Verifies that starting multiple tracks simultaneously doesn't cause crashes
and that workers from both tracks are processed.
@@ -24,6 +24,7 @@ def _poll_mma_workers(client: api_hook_client.ApiHookClient, timeout: int, condi
@pytest.mark.timeout(600)
def test_mma_concurrent_tracks_stress(live_gui) -> None:
"""
Stress test: Start two tracks concurrently and verify they both progress
without crashing the GUI or losing state.
"""
+9
View File
@@ -2,6 +2,7 @@ from src.models import Ticket, Track, WorkerContext
def test_ticket_instantiation() -> None:
"""
Verifies that a Ticket can be instantiated with its required fields:
id, description, status, assigned_to.
"""
@@ -23,6 +24,7 @@ def test_ticket_instantiation() -> None:
def test_ticket_with_dependencies() -> None:
"""
Verifies that a Ticket can store dependencies.
"""
ticket = Ticket(
@@ -36,6 +38,7 @@ def test_ticket_with_dependencies() -> None:
def test_track_instantiation() -> None:
"""
Verifies that a Track can be instantiated with its required fields:
id, description, and a list of Tickets.
"""
@@ -57,6 +60,7 @@ def test_track_instantiation() -> None:
def test_track_can_handle_empty_tickets() -> None:
"""
Verifies that a Track can be instantiated with an empty list of tickets.
"""
track = Track(id="TRACK-2", description="Empty Track", tickets=[])
@@ -64,6 +68,7 @@ def test_track_can_handle_empty_tickets() -> None:
def test_worker_context_instantiation() -> None:
"""
Verifies that a WorkerContext can be instantiated with ticket_id,
model_name, and messages.
"""
@@ -84,6 +89,7 @@ def test_worker_context_instantiation() -> None:
def test_ticket_mark_blocked() -> None:
"""
Verifies that ticket.mark_blocked(reason) sets the status to 'blocked'.
Note: The reason field might need to be added to the Ticket class.
"""
@@ -93,6 +99,7 @@ def test_ticket_mark_blocked() -> None:
def test_ticket_mark_complete() -> None:
"""
Verifies that ticket.mark_complete() sets the status to 'completed'.
"""
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="a")
@@ -101,6 +108,7 @@ def test_ticket_mark_complete() -> None:
def test_track_get_executable_tickets() -> None:
"""
Verifies that track.get_executable_tickets() returns only 'todo' tickets
whose dependencies are all 'completed'.
"""
@@ -125,6 +133,7 @@ def test_track_get_executable_tickets() -> None:
def test_track_get_executable_tickets_complex() -> None:
"""
Verifies executable tickets with complex dependency chains.
Chain: T1 (comp) -> T2 (todo) -> T3 (todo)
T4 (comp) -> T3
+1
View File
@@ -23,6 +23,7 @@ def _poll_mma_status(client: api_hook_client.ApiHookClient, timeout: int, condit
@pytest.mark.timeout(300)
def test_mma_step_mode_approval_flow(live_gui) -> None:
"""
Verify that we can manually approve a ticket in Step Mode and it proceeds.
"""
client = api_hook_client.ApiHookClient()
+2
View File
@@ -12,6 +12,7 @@ from src import api_hook_client
@pytest.mark.timeout(120)
def test_patch_modal_appears_on_trigger(live_gui) -> None:
"""
Test that triggering a patch shows the modal in the GUI.
Uses live_gui fixture to start the GUI with test hooks enabled.
"""
@@ -49,6 +50,7 @@ def test_patch_modal_appears_on_trigger(live_gui) -> None:
@pytest.mark.timeout(120)
def test_patch_apply_modal_workflow(live_gui) -> None:
"""
Test the full patch apply workflow: trigger -> apply -> verify modal closes.
"""
proc, _ = live_gui
+1
View File
@@ -24,6 +24,7 @@ def mock_project():
def test_rag_integration(mock_project):
"""
Integration test verifying the flow from AppController through RAGEngine to ai_client.
"""
# 1. Initializes a mock project and AppController.
+1
View File
@@ -9,6 +9,7 @@ from src.models import Ticket, WorkerContext
class TestRunWorkerLifecycleAbort(unittest.TestCase):
def test_run_worker_lifecycle_returns_early_on_abort(self):
"""
Test that run_worker_lifecycle returns early and marks ticket as 'killed'
if the abort event is set for the ticket.
"""
+1
View File
@@ -12,6 +12,7 @@ from src.gui_2 import App
@pytest.mark.integration
def test_selectable_label_stability(live_gui) -> None:
"""
Verifies that the application starts correctly with --enable-test-hooks
and that the selectable label infrastructure is present and stable.
"""

Some files were not shown because too many files have changed in this diff Show More