Private
Public Access
0
0

more organization

This commit is contained in:
2026-06-06 11:08:07 -04:00
parent 7d555361f9
commit 339b062913
9 changed files with 471 additions and 544 deletions
+8 -14
View File
@@ -114,8 +114,8 @@ def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | Non
[C: tests/conftest.py:reset_ai_client, tests/test_arch_boundary_phase1.py:TestArchBoundaryPhase1.test_mcp_client_whitelist_enforcement, tests/test_mcp_client_beads.py:test_bd_mcp_tools, tests/test_py_struct_tools.py:test_mcp_dispatch_errors, tests/test_py_struct_tools.py:test_mcp_dispatch_integration] [C: tests/conftest.py:reset_ai_client, tests/test_arch_boundary_phase1.py:TestArchBoundaryPhase1.test_mcp_client_whitelist_enforcement, tests/test_mcp_client_beads.py:test_bd_mcp_tools, tests/test_py_struct_tools.py:test_mcp_dispatch_errors, tests/test_py_struct_tools.py:test_mcp_dispatch_integration]
""" """
global _allowed_paths, _base_dirs, _primary_base_dir global _allowed_paths, _base_dirs, _primary_base_dir
_allowed_paths = set() _allowed_paths = set()
_base_dirs = set() _base_dirs = set()
_primary_base_dir = Path(extra_base_dirs[0]).resolve() if extra_base_dirs else Path.cwd() _primary_base_dir = Path(extra_base_dirs[0]).resolve() if extra_base_dirs else Path.cwd()
for item in file_items: for item in file_items:
p = item.get("path") p = item.get("path")
@@ -154,10 +154,8 @@ def _is_allowed(path: Path) -> bool:
rp = path.resolve() rp = path.resolve()
# Blacklist check by resolved path # Blacklist check by resolved path
if rp == get_config_path().resolve(): if rp == get_config_path().resolve(): return False
return False if rp == get_credentials_path().resolve(): return False
if rp == get_credentials_path().resolve():
return False
name = path.name.lower() name = path.name.lower()
if name == "history.toml" or name.endswith("_history.toml"): if name == "history.toml" or name.endswith("_history.toml"):
@@ -209,10 +207,8 @@ def read_file(path: str) -> str:
p, err = _resolve_and_check(path) p, err = _resolve_and_check(path)
if err or p is None: if err or p is None:
return err return err
if not p.exists(): if not p.exists(): return f"ERROR: file not found: {path}"
return f"ERROR: file not found: {path}" if not p.is_file(): return f"ERROR: not a file: {path}"
if not p.is_file():
return f"ERROR: not a file: {path}"
try: try:
return p.read_text(encoding="utf-8") return p.read_text(encoding="utf-8")
except Exception as e: except Exception as e:
@@ -223,10 +219,8 @@ def list_directory(path: str) -> str:
p, err = _resolve_and_check(path) p, err = _resolve_and_check(path)
if err or p is None: if err or p is None:
return err return err
if not p.exists(): if not p.exists(): return f"ERROR: path not found: {path}"
return f"ERROR: path not found: {path}" if not p.is_dir(): return f"ERROR: not a directory: {path}"
if not p.is_dir():
return f"ERROR: not a directory: {path}"
try: try:
entries = sorted(p.iterdir(), key=lambda e: (e.is_file(), e.name.lower())) entries = sorted(p.iterdir(), key=lambda e: (e.is_file(), e.name.lower()))
lines = [f"Directory: {p}", ""] lines = [f"Directory: {p}", ""]
+192 -197
View File
@@ -21,7 +21,7 @@ Status Machine (Ticket):
Serialization: Serialization:
All dataclasses provide to_dict() and from_dict() class methods for TOML/JSON All dataclasses provide to_dict() and from_dict() class methods for TOML/JSON
persistence via project_manager.py. persistence via project_manager.py.
tomli_w
Thread Safety: Thread Safety:
These dataclasses are NOT thread-safe. Callers must synchronize mutations These dataclasses are NOT thread-safe. Callers must synchronize mutations
if sharing instances across threads (e.g., during ConductorEngine execution). if sharing instances across threads (e.g., during ConductorEngine execution).
@@ -43,6 +43,7 @@ import json
import os import os
import sys import sys
import tomllib import tomllib
import tomli_w
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
@@ -179,12 +180,12 @@ def parse_history_entries(history_strings: list[str], roles: list[str]) -> list[
from src import thinking_parser from src import thinking_parser
entries = [] entries = []
for raw in history_strings: for raw in history_strings:
ts = "" ts = ""
rest = raw rest = raw
if rest.startswith("@"): if rest.startswith("@"):
nl = rest.find("\n") nl = rest.find("\n")
if nl != -1: if nl != -1:
ts = rest[1:nl] ts = rest[1:nl]
rest = rest[nl + 1:] rest = rest[nl + 1:]
known = roles or ["User", "AI", "Vendor API", "System"] known = roles or ["User", "AI", "Vendor API", "System"]
role_pat = re.compile(r"^(" + "|".join(re.escape(r) for r in known) + r"):", re.IGNORECASE) role_pat = re.compile(r"^(" + "|".join(re.escape(r) for r in known) + r"):", re.IGNORECASE)
@@ -206,15 +207,15 @@ def parse_history_entries(history_strings: list[str], roles: list[str]) -> list[
#region: Pydantic Models #region: Pydantic Models
class GenerateRequest(BaseModel): class GenerateRequest(BaseModel):
prompt: str prompt: str
auto_add_history: bool = True auto_add_history: bool = True
temperature: float | None = None temperature: float | None = None
top_p: float | None = None top_p: float | None = None
max_tokens: int | None = None max_tokens: int | None = None
class ConfirmRequest(BaseModel): class ConfirmRequest(BaseModel):
approved: bool approved: bool
script: Optional[str] = None script: Optional[str] = None
#region: MMA Core #region: MMA Core
@@ -238,45 +239,45 @@ class ThinkingSegment:
@dataclass @dataclass
class Ticket: class Ticket:
id: str id: str
description: str description: str
target_symbols: List[str] = field(default_factory=list) target_symbols: List[str] = field(default_factory=list)
context_requirements: List[str] = field(default_factory=list) context_requirements: List[str] = field(default_factory=list)
depends_on: List[str] = field(default_factory=list) depends_on: List[str] = field(default_factory=list)
status: str = "todo" status: str = "todo"
assigned_to: str = "unassigned" assigned_to: str = "unassigned"
priority: str = "medium" priority: str = "medium"
target_file: Optional[str] = None target_file: Optional[str] = None
blocked_reason: Optional[str] = None blocked_reason: Optional[str] = None
step_mode: bool = False step_mode: bool = False
retry_count: int = 0 retry_count: int = 0
manual_block: bool = False manual_block: bool = False
model_override: Optional[str] = None model_override: Optional[str] = None
persona_id: Optional[str] = None persona_id: Optional[str] = None
def mark_blocked(self, reason: str) -> None: def mark_blocked(self, reason: str) -> None:
""" """
[C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_mma_models.py:test_ticket_mark_blocked] [C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_mma_models.py:test_ticket_mark_blocked]
""" """
self.status = "blocked" self.status = "blocked"
self.blocked_reason = reason self.blocked_reason = reason
def mark_manual_block(self, reason: str) -> None: def mark_manual_block(self, reason: str) -> None:
""" """
[C: tests/test_manual_block.py:test_clear_manual_block_method, tests/test_manual_block.py:test_mark_manual_block_method] [C: tests/test_manual_block.py:test_clear_manual_block_method, tests/test_manual_block.py:test_mark_manual_block_method]
""" """
self.status = "blocked" self.status = "blocked"
self.blocked_reason = f"[MANUAL] {reason}" self.blocked_reason = f"[MANUAL] {reason}"
self.manual_block = True self.manual_block = True
def clear_manual_block(self) -> None: def clear_manual_block(self) -> None:
""" """
[C: tests/test_manual_block.py:test_clear_manual_block_method] [C: tests/test_manual_block.py:test_clear_manual_block_method]
""" """
if self.manual_block: if self.manual_block:
self.status = "todo" self.status = "todo"
self.blocked_reason = None self.blocked_reason = None
self.manual_block = False self.manual_block = False
def mark_complete(self) -> None: def mark_complete(self) -> None:
""" """
@@ -295,21 +296,21 @@ class Ticket:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"id": self.id, "id": self.id,
"description": self.description, "description": self.description,
"status": self.status, "status": self.status,
"assigned_to": self.assigned_to, "assigned_to": self.assigned_to,
"priority": self.priority, "priority": self.priority,
"target_file": self.target_file, "target_file": self.target_file,
"target_symbols": self.target_symbols, "target_symbols": self.target_symbols,
"context_requirements": self.context_requirements, "context_requirements": self.context_requirements,
"depends_on": self.depends_on, "depends_on": self.depends_on,
"blocked_reason": self.blocked_reason, "blocked_reason": self.blocked_reason,
"step_mode": self.step_mode, "step_mode": self.step_mode,
"retry_count": self.retry_count, "retry_count": self.retry_count,
"manual_block": self.manual_block, "manual_block": self.manual_block,
"model_override": self.model_override, "model_override": self.model_override,
"persona_id": self.persona_id, "persona_id": self.persona_id,
} }
@classmethod @classmethod
@@ -318,28 +319,28 @@ class Ticket:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
id=data["id"], id = data["id"],
description=data.get("description", ""), description = data.get("description", ""),
status=data.get("status", "todo"), status = data.get("status", "todo"),
assigned_to=data.get("assigned_to", "unassigned"), assigned_to = data.get("assigned_to", "unassigned"),
priority=data.get("priority", "medium"), priority = data.get("priority", "medium"),
target_file=data.get("target_file"), target_file = data.get("target_file"),
target_symbols=data.get("target_symbols", []), target_symbols = data.get("target_symbols", []),
context_requirements=data.get("context_requirements", []), context_requirements = data.get("context_requirements", []),
depends_on=data.get("depends_on", []), depends_on = data.get("depends_on", []),
blocked_reason=data.get("blocked_reason"), blocked_reason = data.get("blocked_reason"),
step_mode=data.get("step_mode", False), step_mode = data.get("step_mode", False),
retry_count=data.get("retry_count", 0), retry_count = data.get("retry_count", 0),
manual_block=data.get("manual_block", False), manual_block = data.get("manual_block", False),
model_override=data.get("model_override"), model_override = data.get("model_override"),
persona_id=data.get("persona_id"), persona_id = data.get("persona_id"),
) )
@dataclass @dataclass
class Track: class Track:
id: str id: str
description: str description: str
tickets: List[Ticket] = field(default_factory=list) tickets: List[Ticket] = field(default_factory=list)
def get_executable_tickets(self) -> List[Ticket]: def get_executable_tickets(self) -> List[Ticket]:
""" """
@@ -353,9 +354,9 @@ class Track:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"id": self.id, "id": self.id,
"description": self.description, "description": self.description,
"tickets": [t.to_dict() for t in self.tickets], "tickets": [t.to_dict() for t in self.tickets],
} }
@classmethod @classmethod
@@ -364,24 +365,24 @@ class Track:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
id=data["id"], id = data["id"],
description=data.get("description", ""), description = data.get("description", ""),
tickets=[Ticket.from_dict(t) for t in data.get("tickets", [])], tickets = [Ticket.from_dict(t) for t in data.get("tickets", [])],
) )
@dataclass @dataclass
class WorkerContext: class WorkerContext:
ticket_id: str ticket_id: str
model_name: str model_name: str
messages: List[Dict[str, Any]] = field(default_factory=list) messages: List[Dict[str, Any]] = field(default_factory=list)
tool_preset: Optional[str] = None tool_preset: Optional[str] = None
persona_id: Optional[str] = None persona_id: Optional[str] = None
@dataclass @dataclass
class Metadata: class Metadata:
id: str id: str
name: str name: str
status: Optional[str] = None status: Optional[str] = None
created_at: Optional[datetime.datetime] = None created_at: Optional[datetime.datetime] = None
updated_at: Optional[datetime.datetime] = None updated_at: Optional[datetime.datetime] = None
@@ -390,9 +391,9 @@ class Metadata:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"status": self.status, "status": self.status,
"created_at": self.created_at.isoformat() if self.created_at else None, "created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None,
} }
@@ -415,20 +416,20 @@ class Metadata:
except ValueError: except ValueError:
updated = None updated = None
return cls( return cls(
id=data["id"], id = data["id"],
name=data.get("name", ""), name = data.get("name", ""),
status=data.get("status"), status = data.get("status"),
created_at=created, created_at = created,
updated_at=updated, updated_at = updated,
) )
#region: State & Config #region: State & Config
@dataclass @dataclass
class TrackState: class TrackState:
metadata: Metadata metadata: Metadata
discussion: List[str] = field(default_factory=list) discussion: List[str] = field(default_factory=list)
tasks: List[Ticket] = field(default_factory=list) tasks: List[Ticket] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
""" """
@@ -444,9 +445,9 @@ class TrackState:
else: else:
serialized_discussion.append(item) serialized_discussion.append(item)
return { return {
"metadata": self.metadata.to_dict(), "metadata": self.metadata.to_dict(),
"discussion": serialized_discussion, "discussion": serialized_discussion,
"tasks": [t.to_dict() for t in self.tasks], "tasks": [t.to_dict() for t in self.tasks],
} }
@classmethod @classmethod
@@ -454,12 +455,12 @@ class TrackState:
""" """
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
discussion = data.get("discussion", []) discussion = data.get("discussion", [])
parsed_discussion = [] parsed_discussion = []
for item in discussion: for item in discussion:
if isinstance(item, dict): if isinstance(item, dict):
new_item = dict(item) new_item = dict(item)
ts = new_item.get("ts") ts = new_item.get("ts")
if isinstance(ts, str): if isinstance(ts, str):
try: try:
new_item["ts"] = datetime.datetime.fromisoformat(ts) new_item["ts"] = datetime.datetime.fromisoformat(ts)
@@ -469,23 +470,23 @@ class TrackState:
else: else:
parsed_discussion.append(item) parsed_discussion.append(item)
return cls( return cls(
metadata=Metadata.from_dict(data["metadata"]), metadata = Metadata.from_dict(data["metadata"]),
discussion=parsed_discussion, discussion = parsed_discussion,
tasks=[Ticket.from_dict(t) for t in data.get("tasks", [])], tasks = [Ticket.from_dict(t) for t in data.get("tasks", [])],
) )
@dataclass @dataclass
class FileItem: class FileItem:
path: str path: str
auto_aggregate: bool = True auto_aggregate: bool = True
force_full: bool = False force_full: bool = False
view_mode: str = 'full' view_mode: str = 'full'
selected: bool = False selected: bool = False
ast_signatures: bool = False ast_signatures: bool = False
ast_definitions: bool = False ast_definitions: bool = False
ast_mask: dict[str, str] = field(default_factory=dict) ast_mask: dict[str, str] = field(default_factory=dict)
custom_slices: list[dict] = field(default_factory=list) custom_slices: list[dict] = field(default_factory=list)
injected_at: Optional[float] = None injected_at: Optional[float] = None
def __post_init__(self): def __post_init__(self):
if self.custom_slices: if self.custom_slices:
@@ -493,7 +494,7 @@ class FileItem:
for slc in self.custom_slices: for slc in self.custom_slices:
if isinstance(slc, dict): if isinstance(slc, dict):
new_slc = slc.copy() new_slc = slc.copy()
if "tag" not in new_slc: new_slc["tag"] = None if "tag" not in new_slc: new_slc["tag"] = None
if "comment" not in new_slc: new_slc["comment"] = None if "comment" not in new_slc: new_slc["comment"] = None
normalized.append(new_slc) normalized.append(new_slc)
else: else:
@@ -505,15 +506,15 @@ class FileItem:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"path": self.path, "path": self.path,
"auto_aggregate": self.auto_aggregate, "auto_aggregate": self.auto_aggregate,
"force_full": self.force_full, "force_full": self.force_full,
"view_mode": self.view_mode, "view_mode": self.view_mode,
"ast_signatures": self.ast_signatures, "ast_signatures": self.ast_signatures,
"ast_definitions": self.ast_definitions, "ast_definitions": self.ast_definitions,
"ast_mask": self.ast_mask, "ast_mask": self.ast_mask,
"custom_slices": self.custom_slices, "custom_slices": self.custom_slices,
"injected_at": self.injected_at, "injected_at": self.injected_at,
} }
@classmethod @classmethod
@@ -522,20 +523,20 @@ class FileItem:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
path=data["path"], path = data["path"],
auto_aggregate=data.get("auto_aggregate", True), auto_aggregate = data.get("auto_aggregate", True),
force_full=data.get("force_full", False), force_full = data.get("force_full", False),
view_mode=data.get("view_mode", 'full'), view_mode = data.get("view_mode", 'full'),
ast_signatures=data.get("ast_signatures", False), ast_signatures = data.get("ast_signatures", False),
ast_definitions=data.get("ast_definitions", False), ast_definitions = data.get("ast_definitions", False),
ast_mask=data.get("ast_mask", {}), ast_mask = data.get("ast_mask", {}),
custom_slices=data.get("custom_slices", []), custom_slices = data.get("custom_slices", []),
injected_at=data.get("injected_at"), injected_at = data.get("injected_at"),
) )
@dataclass @dataclass
class Preset: class Preset:
name: str name: str
system_prompt: str system_prompt: str
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
@@ -555,9 +556,9 @@ class Preset:
@dataclass @dataclass
class Tool: class Tool:
name: str name: str
approval: str = 'auto' approval: str = 'auto'
weight: int = 3 weight: int = 3
parameter_bias: Dict[str, str] = field(default_factory=dict) parameter_bias: Dict[str, str] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
@@ -565,9 +566,9 @@ class Tool:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"name": self.name, "name": self.name,
"approval": self.approval, "approval": self.approval,
"weight": self.weight, "weight": self.weight,
"parameter_bias": self.parameter_bias, "parameter_bias": self.parameter_bias,
} }
@@ -585,7 +586,7 @@ class Tool:
@dataclass @dataclass
class ToolPreset: class ToolPreset:
name: str name: str
categories: Dict[str, List[Union[Tool, Any]]] categories: Dict[str, List[Union[Tool, Any]]]
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
@@ -602,7 +603,7 @@ class ToolPreset:
""" """
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
raw_categories = data.get("categories", {}) raw_categories = data.get("categories", {})
parsed_categories = {} parsed_categories = {}
for cat, tools in raw_categories.items(): for cat, tools in raw_categories.items():
parsed_categories[cat] = [Tool.from_dict(t) if isinstance(t, dict) else t for t in tools] parsed_categories[cat] = [Tool.from_dict(t) if isinstance(t, dict) else t for t in tools]
@@ -610,8 +611,8 @@ class ToolPreset:
@dataclass @dataclass
class BiasProfile: class BiasProfile:
name: str name: str
tool_weights: Dict[str, int] = field(default_factory=dict) tool_weights: Dict[str, int] = field(default_factory=dict)
category_multipliers: Dict[str, float] = field(default_factory=dict) category_multipliers: Dict[str, float] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
@@ -619,8 +620,8 @@ class BiasProfile:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"name": self.name, "name": self.name,
"tool_weights": self.tool_weights, "tool_weights": self.tool_weights,
"category_multipliers": self.category_multipliers, "category_multipliers": self.category_multipliers,
} }
@@ -630,17 +631,17 @@ class BiasProfile:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
name=data["name"], name = data["name"],
tool_weights=data.get("tool_weights", {}), tool_weights = data.get("tool_weights", {}),
category_multipliers=data.get("category_multipliers", {}), category_multipliers = data.get("category_multipliers", {}),
) )
#region: UI/Editor #region: UI/Editor
@dataclass @dataclass
class TextEditorConfig: class TextEditorConfig:
name: str name: str
path: str path: str
diff_args: List[str] = field(default_factory=list) diff_args: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
@@ -648,8 +649,8 @@ class TextEditorConfig:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"name": self.name, "name": self.name,
"path": self.path, "path": self.path,
"diff_args": self.diff_args, "diff_args": self.diff_args,
} }
@@ -659,14 +660,14 @@ class TextEditorConfig:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
name=data["name"], name = data["name"],
path=data["path"], path = data["path"],
diff_args=data.get("diff_args", []), diff_args = data.get("diff_args", []),
) )
@dataclass @dataclass
class ExternalEditorConfig: class ExternalEditorConfig:
editors: Dict[str, TextEditorConfig] = field(default_factory=dict) editors: Dict[str, TextEditorConfig] = field(default_factory=dict)
default_editor: Optional[str] = None default_editor: Optional[str] = None
def get_default(self) -> Optional[TextEditorConfig]: def get_default(self) -> Optional[TextEditorConfig]:
@@ -684,7 +685,7 @@ class ExternalEditorConfig:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"editors": {k: v.to_dict() for k, v in self.editors.items()}, "editors": {k: v.to_dict() for k, v in self.editors.items()},
"default_editor": self.default_editor, "default_editor": self.default_editor,
} }
@@ -695,10 +696,8 @@ class ExternalEditorConfig:
""" """
editors = {} editors = {}
for name, ed_data in data.get("editors", {}).items(): for name, ed_data in data.get("editors", {}).items():
if isinstance(ed_data, dict): if isinstance(ed_data, dict): editors[name] = TextEditorConfig.from_dict(ed_data)
editors[name] = TextEditorConfig.from_dict(ed_data) elif isinstance(ed_data, str): editors[name] = TextEditorConfig(name=name, path=ed_data)
elif isinstance(ed_data, str):
editors[name] = TextEditorConfig(name=name, path=ed_data)
return cls(editors=editors, default_editor=data.get("default_editor")) return cls(editors=editors, default_editor=data.get("default_editor"))
#region: Persona #region: Persona
@@ -751,14 +750,10 @@ class Persona:
else: else:
processed.append(m) processed.append(m)
res["preferred_models"] = processed res["preferred_models"] = processed
if self.tool_preset is not None: if self.tool_preset is not None: res["tool_preset"] = self.tool_preset
res["tool_preset"] = self.tool_preset if self.bias_profile is not None: res["bias_profile"] = self.bias_profile
if self.bias_profile is not None: if self.context_preset is not None: res["context_preset"] = self.context_preset
res["bias_profile"] = self.bias_profile if self.aggregation_strategy is not None: res["aggregation_strategy"] = self.aggregation_strategy
if self.context_preset is not None:
res["context_preset"] = self.context_preset
if self.aggregation_strategy is not None:
res["aggregation_strategy"] = self.aggregation_strategy
return res return res
@classmethod @classmethod
@@ -785,21 +780,21 @@ class Persona:
if k not in parsed_models[0] or parsed_models[0][k] is None: if k not in parsed_models[0] or parsed_models[0][k] is None:
parsed_models[0][k] = v parsed_models[0][k] = v
return cls( return cls(
name=name, name = name,
preferred_models=parsed_models, preferred_models = parsed_models,
system_prompt=data.get("system_prompt", ""), system_prompt = data.get("system_prompt", ""),
tool_preset=data.get("tool_preset"), tool_preset = data.get("tool_preset"),
bias_profile=data.get("bias_profile"), bias_profile = data.get("bias_profile"),
context_preset=data.get("context_preset"), context_preset = data.get("context_preset"),
aggregation_strategy=data.get("aggregation_strategy"), aggregation_strategy = data.get("aggregation_strategy"),
) )
#region: Workspace #region: Workspace
@dataclass @dataclass
class WorkspaceProfile: class WorkspaceProfile:
name: str name: str
ini_content: str ini_content: str
show_windows: Dict[str, bool] show_windows: Dict[str, bool]
panel_states: Dict[str, Any] panel_states: Dict[str, Any]
@@ -808,7 +803,7 @@ class WorkspaceProfile:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"ini_content": self.ini_content, "ini_content": self.ini_content,
"show_windows": self.show_windows, "show_windows": self.show_windows,
"panel_states": self.panel_states, "panel_states": self.panel_states,
} }
@@ -819,19 +814,19 @@ class WorkspaceProfile:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
name=name, name = name,
ini_content=data.get("ini_content", ""), ini_content = data.get("ini_content", ""),
show_windows=data.get("show_windows", {}), show_windows = data.get("show_windows", {}),
panel_states=data.get("panel_states", {}), panel_states = data.get("panel_states", {}),
) )
@dataclass @dataclass
class ContextFileEntry: class ContextFileEntry:
path: str path: str
view_mode: str = "summary" view_mode: str = "summary"
custom_slices: list = field(default_factory=list) custom_slices: list = field(default_factory=list)
ast_mask: dict = field(default_factory=dict) ast_mask: dict = field(default_factory=dict)
ast_signatures: bool = False ast_signatures: bool = False
ast_definitions: bool = False ast_definitions: bool = False
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
@@ -846,19 +841,19 @@ class ContextFileEntry:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
path=data.get("path", ""), path = data.get("path", ""),
view_mode=data.get("view_mode", "summary"), view_mode = data.get("view_mode", "summary"),
custom_slices=data.get("custom_slices", []), custom_slices = data.get("custom_slices", []),
ast_mask=data.get("ast_mask", {}), ast_mask = data.get("ast_mask", {}),
ast_signatures=data.get("ast_signatures", False), ast_signatures = data.get("ast_signatures", False),
ast_definitions=data.get("ast_definitions", False), ast_definitions = data.get("ast_definitions", False),
) )
@dataclass @dataclass
class NamedViewPreset: class NamedViewPreset:
name: str name: str
view_mode: str view_mode: str
ast_mask: dict = field(default_factory=dict) ast_mask: dict = field(default_factory=dict)
custom_slices: list = field(default_factory=list) custom_slices: list = field(default_factory=list)
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
@@ -873,16 +868,16 @@ class NamedViewPreset:
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return cls( return cls(
name=data.get("name", ""), name = data.get("name", ""),
view_mode=data.get("view_mode", "summary"), view_mode = data.get("view_mode", "summary"),
ast_mask=data.get("ast_mask", {}), ast_mask = data.get("ast_mask", {}),
custom_slices=data.get("custom_slices", []), custom_slices = data.get("custom_slices", []),
) )
@dataclass @dataclass
class ContextPreset: class ContextPreset:
name: str name: str
files: list[ContextFileEntry] = field(default_factory=list) files: list[ContextFileEntry] = field(default_factory=list)
screenshots: list[str] = field(default_factory=list) screenshots: list[str] = field(default_factory=list)
description: str = "" description: str = ""
@@ -891,7 +886,7 @@ class ContextPreset:
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
""" """
return { return {
"files": [f.to_dict() for f in self.files], "files": [f.to_dict() for f in self.files],
"screenshots": self.screenshots, "screenshots": self.screenshots,
"description": self.description, "description": self.description,
} }
@@ -903,21 +898,21 @@ class ContextPreset:
""" """
files_data = data.get("files", []) files_data = data.get("files", [])
return cls( return cls(
name=name, name = name,
files=[ContextFileEntry.from_dict(f) if isinstance(f, dict) else ContextFileEntry(path=str(f)) for f in files_data], files = [ContextFileEntry.from_dict(f) if isinstance(f, dict) else ContextFileEntry(path=str(f)) for f in files_data],
screenshots=data.get("screenshots", []), screenshots = data.get("screenshots", []),
description=data.get("description", ""), description = data.get("description", ""),
) )
#region: MCP Config #region: MCP Config
@dataclass @dataclass
class MCPServerConfig: class MCPServerConfig:
name: str name: str
command: Optional[str] = None command: Optional[str] = None
args: List[str] = field(default_factory=list) args: List[str] = field(default_factory=list)
url: Optional[str] = None url: Optional[str] = None
auto_start: bool = False auto_start: bool = False
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
""" """
+28 -37
View File
@@ -62,11 +62,9 @@ class WorkerPool:
def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]: 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.
Spawns a new worker thread if the pool is not 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]
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: with self._lock:
if len(self._active) >= self.max_workers: if len(self._active) >= self.max_workers:
@@ -88,7 +86,7 @@ class WorkerPool:
def join_all(self, timeout: float = None) -> None: 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] [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: with self._lock:
threads = list(self._active.values()) threads = list(self._active.values())
@@ -99,22 +97,20 @@ class WorkerPool:
def get_active_count(self) -> int: 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] [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: with self._lock:
return len(self._active) return len(self._active)
def is_full(self) -> bool: def is_full(self) -> bool:
""" """
[C: tests/test_parallel_execution.py:test_worker_pool_limit] [C: tests/test_parallel_execution.py:test_worker_pool_limit]
""" """
return self.get_active_count() >= self.max_workers return self.get_active_count() >= self.max_workers
class ConductorEngine: class ConductorEngine:
""" """
Orchestrates the execution of tickets within a track.
Orchestrates the execution of tickets within a track.
""" """
def __init__(self, track: Track, event_queue: Optional[events.AsyncEventQueue] = None, auto_queue: bool = False) -> None: def __init__(self, track: Track, event_queue: Optional[events.AsyncEventQueue] = None, auto_queue: bool = False) -> None:
@@ -123,74 +119,69 @@ class ConductorEngine:
self.tier_usage = { self.tier_usage = {
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview", "tool_preset": None, "persona": None}, "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview", "tool_preset": None, "persona": None},
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview", "tool_preset": None, "persona": None}, "Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview", "tool_preset": None, "persona": None},
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None}, "Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None},
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None}, "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None},
} }
self.dag = TrackDAG(self.track.tickets) self.dag = TrackDAG(self.track.tickets)
self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue) self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue)
# Load MMA config # Load MMA config
try: try:
config = models.load_config() config = models.load_config()
mma_cfg = config.get("mma", {}) mma_cfg = config.get("mma", {})
max_workers = mma_cfg.get("max_workers", 4) max_workers = mma_cfg.get("max_workers", 4)
except Exception: except Exception:
max_workers = 4 max_workers = 4
self.pool = WorkerPool(max_workers=max_workers) self.pool = WorkerPool(max_workers=max_workers)
self._workers_lock = threading.Lock() self._workers_lock = threading.Lock()
self._active_workers: dict[str, threading.Thread] = {} self._active_workers: dict[str, threading.Thread] = {}
self._abort_events: dict[str, threading.Event] = {} self._abort_events: dict[str, threading.Event] = {}
self._pause_event: threading.Event = threading.Event() self._pause_event: threading.Event = threading.Event()
self._tier_usage_lock = threading.Lock() self._tier_usage_lock = threading.Lock()
self._dirty: bool = True self._dirty: bool = True
def update_usage(self, tier: str, input_tokens: int, output_tokens: int) -> None: def update_usage(self, tier: str, input_tokens: int, output_tokens: int) -> None:
"""Updates token usage for a specific tier.""" """Updates token usage for a specific tier."""
with self._tier_usage_lock: with self._tier_usage_lock:
if tier in self.tier_usage: if tier in self.tier_usage:
self.tier_usage[tier]["input"] += input_tokens self.tier_usage[tier]["input"] += input_tokens
self.tier_usage[tier]["output"] += output_tokens self.tier_usage[tier]["output"] += output_tokens
def pause(self) -> None: 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]
[C: tests/test_pipeline_pause.py:test_pause_method, tests/test_pipeline_pause.py:test_resume_method]
""" """
self._pause_event.set() self._pause_event.set()
def resume(self) -> None: def resume(self) -> None:
""" """
Resumes the pipeline execution.
Resumes the pipeline execution. [C: tests/test_pipeline_pause.py:test_resume_method]
[C: tests/test_pipeline_pause.py:test_resume_method]
""" """
self._pause_event.clear() self._pause_event.clear()
def approve_task(self, task_id: str) -> None: 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]
[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.engine.approve_task(task_id)
self._dirty = True self._dirty = True
def update_task_status(self, task_id: str, status: str) -> None: 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]
[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.engine.update_task_status(task_id, status)
self._dirty = True self._dirty = True
def kill_worker(self, ticket_id: str) -> None: 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]
[C: tests/test_conductor_engine_abort.py:test_kill_worker_sets_abort_and_joins_thread]
""" """
if ticket_id in self._abort_events: if ticket_id in self._abort_events:
print(f"[MMA] Setting abort event for {ticket_id}") print(f"[MMA] Setting abort event for {ticket_id}")
+17 -24
View File
@@ -12,32 +12,27 @@ from src import summarize
def get_track_history_summary() -> str: 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]
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 = [] summary_parts = []
archive_path = paths.get_archive_dir() archive_path = paths.get_archive_dir()
tracks_path = paths.get_tracks_dir() tracks_path = paths.get_tracks_dir()
paths_to_scan = [] paths_to_scan = []
if archive_path.exists(): if archive_path.exists(): paths_to_scan.extend(list(archive_path.iterdir()))
paths_to_scan.extend(list(archive_path.iterdir())) if tracks_path.exists(): paths_to_scan.extend(list(tracks_path.iterdir()))
if tracks_path.exists():
paths_to_scan.extend(list(tracks_path.iterdir()))
for track_dir in paths_to_scan: for track_dir in paths_to_scan:
if not track_dir.is_dir(): if not track_dir.is_dir(): continue
continue
metadata_file = track_dir / "metadata.json" metadata_file = track_dir / "metadata.json"
spec_file = track_dir / "spec.md" spec_file = track_dir / "spec.md"
title = track_dir.name title = track_dir.name
status = "unknown" status = "unknown"
overview = "No overview available." overview = "No overview available."
if metadata_file.exists(): if metadata_file.exists():
try: try:
with open(metadata_file, "r", encoding="utf-8") as f: with open(metadata_file, "r", encoding="utf-8") as f:
meta = json.load(f) meta = json.load(f)
title = meta.get("title", title) title = meta.get("title", title)
status = meta.get("status", status) status = meta.get("status", status)
except Exception: except Exception:
pass pass
@@ -60,11 +55,9 @@ 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]]: 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.
Tier 1 (Strategic PM) call. [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]
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) # 1. Build Repository Map (Summary View)
repo_map = summarize.build_summary_markdown(file_items) repo_map = summarize.build_summary_markdown(file_items)
@@ -131,4 +124,4 @@ if __name__ == "__main__":
print("Testing Tier 1 Track Generation...") print("Testing Tier 1 Track Generation...")
history = get_track_history_summary() 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) 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)) print(json.dumps(tracks, indent=2))
+20 -22
View File
@@ -51,79 +51,79 @@ _RESOLVED: dict[str, Path] = {}
def get_config_path() -> Path: def get_config_path() -> Path:
""" """
[C: tests/test_paths.py:test_default_paths] [C: tests/test_paths.py:test_default_paths]
""" """
root_dir = Path(__file__).resolve().parent.parent root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_CONFIG", root_dir / "config.toml")) return Path(os.environ.get("SLOP_CONFIG", root_dir / "config.toml"))
def get_global_presets_path() -> Path: 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] [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 root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_PRESETS", root_dir / "presets.toml")) return Path(os.environ.get("SLOP_GLOBAL_PRESETS", root_dir / "presets.toml"))
def get_project_presets_path(project_root: Path) -> Path: 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] [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" return project_root / "project_presets.toml"
def get_global_tool_presets_path() -> Path: 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] [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 root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_TOOL_PRESETS", root_dir / "tool_presets.toml")) return Path(os.environ.get("SLOP_GLOBAL_TOOL_PRESETS", root_dir / "tool_presets.toml"))
def get_project_tool_presets_path(project_root: Path) -> Path: 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] [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" return project_root / "project_tool_presets.toml"
def get_global_personas_path() -> Path: 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] [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 root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_PERSONAS", root_dir / "personas.toml")) return Path(os.environ.get("SLOP_GLOBAL_PERSONAS", root_dir / "personas.toml"))
def get_project_personas_path(project_root: Path) -> Path: 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] [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" return project_root / "project_personas.toml"
def get_global_themes_path() -> Path: def get_global_themes_path() -> Path:
""" """
[C: src/theme_2.py:load_themes_from_disk] [C: src/theme_2.py:load_themes_from_disk]
""" """
root_dir = Path(__file__).resolve().parent.parent root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_THEMES", root_dir / "themes")) return Path(os.environ.get("SLOP_GLOBAL_THEMES", root_dir / "themes"))
def get_project_themes_path(project_root: Path) -> Path: def get_project_themes_path(project_root: Path) -> Path:
""" """
[C: src/theme_2.py:load_themes_from_disk] [C: src/theme_2.py:load_themes_from_disk]
""" """
return project_root / "project_themes.toml" return project_root / "project_themes.toml"
def get_global_workspace_profiles_path() -> Path: def get_global_workspace_profiles_path() -> Path:
""" """
[C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles] [C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles]
""" """
root_dir = Path(__file__).resolve().parent.parent root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_GLOBAL_WORKSPACE_PROFILES", root_dir / "workspace_profiles.toml")) return Path(os.environ.get("SLOP_GLOBAL_WORKSPACE_PROFILES", root_dir / "workspace_profiles.toml"))
def get_project_workspace_profiles_path(project_root: Path) -> Path: 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] [C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles]
""" """
return project_root / ".ai" / "workspace_profiles.toml" return project_root / ".ai" / "workspace_profiles.toml"
def get_credentials_path() -> Path: def get_credentials_path() -> Path:
""" """
[C: src/mcp_client.py:_is_allowed] [C: src/mcp_client.py:_is_allowed]
""" """
root_dir = Path(__file__).resolve().parent.parent root_dir = Path(__file__).resolve().parent.parent
return Path(os.environ.get("SLOP_CREDENTIALS", str(root_dir / "credentials.toml"))) return Path(os.environ.get("SLOP_CREDENTIALS", str(root_dir / "credentials.toml")))
@@ -165,21 +165,20 @@ def _get_project_conductor_dir_from_toml(project_root: Path) -> Optional[Path]:
def get_conductor_dir(project_path: Optional[str] = None) -> Path: 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] [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: if not project_path:
# Fallback for legacy/tests, but we should avoid this # Fallback for legacy/tests, but we should avoid this
return Path('conductor').resolve() return Path('conductor').resolve()
project_root = Path(project_path).resolve() project_root = Path(project_path).resolve()
p = _get_project_conductor_dir_from_toml(project_root) p = _get_project_conductor_dir_from_toml(project_root)
if p: return p if p: return p
return (project_root / "conductor").resolve() return (project_root / "conductor").resolve()
def get_logs_dir() -> Path: 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] [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: if "logs_dir" not in _RESOLVED:
_RESOLVED["logs_dir"] = _resolve_path("SLOP_LOGS_DIR", "logs_dir", "logs/sessions") _RESOLVED["logs_dir"] = _resolve_path("SLOP_LOGS_DIR", "logs_dir", "logs/sessions")
@@ -187,7 +186,7 @@ def get_logs_dir() -> Path:
def get_scripts_dir() -> Path: 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] [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: if "scripts_dir" not in _RESOLVED:
_RESOLVED["scripts_dir"] = _resolve_path("SLOP_SCRIPTS_DIR", "scripts_dir", "scripts/generated") _RESOLVED["scripts_dir"] = _resolve_path("SLOP_SCRIPTS_DIR", "scripts_dir", "scripts/generated")
@@ -195,13 +194,13 @@ def get_scripts_dir() -> Path:
def get_tracks_dir(project_path: Optional[str] = None) -> Path: 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] [C: src/project_manager.py:get_all_tracks, tests/test_paths.py:test_conductor_dir_project_relative]
""" """
return get_conductor_dir(project_path) / "tracks" return get_conductor_dir(project_path) / "tracks"
def get_track_state_dir(track_id: str, project_path: Optional[str] = None) -> Path: 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] [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 return get_tracks_dir(project_path) / track_id
@@ -235,9 +234,8 @@ def get_full_path_info() -> dict[str, dict[str, Any]]:
def reset_resolved() -> None: 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]
[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() _RESOLVED.clear()
+62 -70
View File
@@ -70,7 +70,7 @@ class PerformanceScope:
"""Helper class for PerformanceMonitor.scope() context manager.""" """Helper class for PerformanceMonitor.scope() context manager."""
def __init__(self, monitor: PerformanceMonitor, name: str) -> None: def __init__(self, monitor: PerformanceMonitor, name: str) -> None:
self.monitor = monitor self.monitor = monitor
self.name = name self.name = name
def __enter__(self) -> PerformanceScope: def __enter__(self) -> PerformanceScope:
self.monitor.start_component(self.name) self.monitor.start_component(self.name)
return self return self
@@ -89,35 +89,33 @@ def get_monitor() -> PerformanceMonitor:
class 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.
Tracks application performance metrics like FPS, frame time, and CPU usage.
Supports thread-safe tracking for individual components with efficient moving averages.
""" """
def __init__(self, history_size: int = 300) -> None: def __init__(self, history_size: int = 300) -> None:
self.enabled: bool = False self.enabled: bool = False
self.history_size = history_size self.history_size = history_size
self._lock = threading.Lock() self._lock = threading.Lock()
self._start_time: Optional[float] = None self._start_time: Optional[float] = None
self._last_frame_start_time: float = 0.0 self._last_frame_start_time: float = 0.0
self._last_frame_time: float = 0.0 self._last_frame_time: float = 0.0
self._fps: float = 0.0 self._fps: float = 0.0
self._last_calculated_fps: float = 0.0 self._last_calculated_fps: float = 0.0
self._frame_count: int = 0 self._frame_count: int = 0
self._fps_timer: float = 0.0 self._fps_timer: float = 0.0
self._cpu_percent: float = 0.0 self._cpu_percent: float = 0.0
self._input_lag_ms: float = 0.0 self._input_lag_ms: float = 0.0
self._component_starts: dict[str, float] = {} self._component_starts: dict[str, float] = {}
self._component_timings: dict[str, float] = {} self._component_timings: dict[str, float] = {}
self._component_counts: dict[str, int] = {} self._component_counts: dict[str, int] = {}
self._component_max: dict[str, float] = {} self._component_max: dict[str, float] = {}
self._component_min: dict[str, float] = {} self._component_min: dict[str, float] = {}
# Rolling history and running sums for O(1) average calculation # Rolling history and running sums for O(1) average calculation
# deques are thread-safe for appends and pops. # deques are thread-safe for appends and pops.
self._history: Dict[str, deque[float]] = {} self._history: Dict[str, deque[float]] = {}
self._history_sums: Dict[str, float] = {} self._history_sums: Dict[str, float] = {}
# For slowing down graph updates # For slowing down graph updates
@@ -143,7 +141,7 @@ class PerformanceMonitor:
"""Thread-safe O(1) history update.""" """Thread-safe O(1) history update."""
with self._lock: with self._lock:
if key not in self._history: if key not in self._history:
self._history[key] = deque(maxlen=self.history_size) self._history[key] = deque(maxlen=self.history_size)
self._history_sums[key] = 0.0 self._history_sums[key] = 0.0
h = self._history[key] h = self._history[key]
@@ -158,13 +156,12 @@ class PerformanceMonitor:
"""Thread-safe O(1) average retrieval.""" """Thread-safe O(1) average retrieval."""
with self._lock: with self._lock:
h = self._history.get(key) h = self._history.get(key)
if not h or len(h) == 0: if not h or len(h) == 0: return 0.0
return 0.0
return self._history_sums[key] / len(h) return self._history_sums[key] / len(h)
def start_frame(self) -> None: def start_frame(self) -> None:
""" """
[C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing] [C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing]
""" """
now = time.perf_counter() now = time.perf_counter()
with self._lock: with self._lock:
@@ -173,24 +170,24 @@ class PerformanceMonitor:
if dt > 0: if dt > 0:
self._fps = 1.0 / dt self._fps = 1.0 / dt
self._last_frame_start_time = now self._last_frame_start_time = now
self._start_time = now self._start_time = now
self._frame_count += 1 self._frame_count += 1
def end_frame(self) -> None: def end_frame(self) -> None:
""" """
[C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing] [C: tests/test_performance_monitor.py:test_perf_monitor_basic_timing]
""" """
if self._start_time is None: if self._start_time is None:
return return
now = time.perf_counter() now = time.perf_counter()
elapsed = now - self._start_time elapsed = now - self._start_time
frame_time_ms = elapsed * 1000 frame_time_ms = elapsed * 1000
with self._lock: with self._lock:
self._last_frame_time = frame_time_ms self._last_frame_time = frame_time_ms
cpu = self._cpu_percent cpu = self._cpu_percent
ilag = self._input_lag_ms ilag = self._input_lag_ms
fps = self._fps fps = self._fps
# Slow down history sampling for core metrics # Slow down history sampling for core metrics
if now - self._last_sample_time >= self._sample_interval: if now - self._last_sample_time >= self._sample_interval:
@@ -205,11 +202,11 @@ class PerformanceMonitor:
with self._lock: with self._lock:
self._last_calculated_fps = self._frame_count / self._fps_timer self._last_calculated_fps = self._frame_count / self._fps_timer
self._frame_count = 0 self._frame_count = 0
self._fps_timer = 0.0 self._fps_timer = 0.0
def start_component(self, name: str) -> None: 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] [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 if not self.enabled: return
now = time.perf_counter() now = time.perf_counter()
@@ -218,7 +215,7 @@ class PerformanceMonitor:
def end_component(self, name: str) -> None: 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] [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 if not self.enabled: return
now = time.perf_counter() now = time.perf_counter()
@@ -228,55 +225,51 @@ class PerformanceMonitor:
elapsed = (now - start) * 1000 elapsed = (now - start) * 1000
with self._lock: with self._lock:
self._component_timings[name] = elapsed self._component_timings[name] = elapsed
self._component_counts[name] = self._component_counts.get(name, 0) + 1 self._component_counts[name] = self._component_counts.get(name, 0) + 1
if name not in self._component_max or elapsed > self._component_max[name]: if name not in self._component_max or elapsed > self._component_max[name]: self._component_max[name] = elapsed
self._component_max[name] = elapsed if name not in self._component_min or elapsed < self._component_min[name]: self._component_min[name] = elapsed
if name not in self._component_min or elapsed < self._component_min[name]:
self._component_min[name] = elapsed
self._add_to_history(f'comp_{name}', elapsed) self._add_to_history(f'comp_{name}', elapsed)
def get_metrics(self) -> dict[str, float]: 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]
[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: with self._lock:
fps = self._fps fps = self._fps
last_ft = self._last_frame_time last_ft = self._last_frame_time
cpu = self._cpu_percent cpu = self._cpu_percent
ilag = self._input_lag_ms ilag = self._input_lag_ms
last_calc_fps = self._last_calculated_fps last_calc_fps = self._last_calculated_fps
total_frames = float(self._frame_count) total_frames = float(self._frame_count)
timings_snapshot = dict(self._component_timings) timings_snapshot = dict(self._component_timings)
counts_snapshot = dict(self._component_counts) counts_snapshot = dict(self._component_counts)
max_snapshot = dict(self._component_max) max_snapshot = dict(self._component_max)
min_snapshot = dict(self._component_min) min_snapshot = dict(self._component_min)
metrics = { metrics = {
'fps': fps, 'fps': fps,
'fps_avg': self._get_avg('fps'), 'fps_avg': self._get_avg('fps'),
'last_frame_time_ms': last_ft, 'last_frame_time_ms': last_ft,
'frame_time_ms_avg': self._get_avg('frame_time_ms'), 'frame_time_ms_avg': self._get_avg('frame_time_ms'),
'cpu_percent': cpu, 'cpu_percent': cpu,
'cpu_percent_avg': self._get_avg('cpu_percent'), 'cpu_percent_avg': self._get_avg('cpu_percent'),
'input_lag_ms': ilag, 'input_lag_ms': ilag,
'input_lag_ms_avg': self._get_avg('input_lag_ms'), 'input_lag_ms_avg': self._get_avg('input_lag_ms'),
'total_frames': total_frames 'total_frames': total_frames
} }
for name, elapsed in timings_snapshot.items(): for name, elapsed in timings_snapshot.items():
metrics[f'time_{name}_ms'] = elapsed metrics[f'time_{name}_ms'] = elapsed
metrics[f'time_{name}_ms_avg'] = self._get_avg(f'comp_{name}') metrics[f'time_{name}_ms_avg'] = self._get_avg(f'comp_{name}')
metrics[f'count_{name}'] = float(counts_snapshot.get(name, 0)) metrics[f'count_{name}'] = float(counts_snapshot.get(name, 0))
metrics[f'max_{name}_ms'] = max_snapshot.get(name, 0.0) metrics[f'max_{name}_ms'] = max_snapshot.get(name, 0.0)
metrics[f'min_{name}_ms'] = min_snapshot.get(name, 0.0) metrics[f'min_{name}_ms'] = min_snapshot.get(name, 0.0)
return metrics return metrics
def get_history(self, key: str) -> List[float]: 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, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions]
[C: tests/test_history.py:test_initial_state, tests/test_history.py:test_push_state, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions]
""" """
with self._lock: with self._lock:
if key in self._history: if key in self._history:
@@ -287,15 +280,14 @@ class PerformanceMonitor:
def scope(self, name: str) -> PerformanceScope: 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]
[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) return PerformanceScope(self, name)
def stop(self) -> None: 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] [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() self._stop_event.set()
if self._cpu_thread.is_alive(): if self._cpu_thread.is_alive():
+7 -8
View File
@@ -15,7 +15,7 @@ class PersonaManager:
def _get_path(self, scope: str) -> Path: 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] [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": if scope == "global":
return paths.get_global_personas_path() return paths.get_global_personas_path()
@@ -28,9 +28,8 @@ class PersonaManager:
def load_all(self) -> Dict[str, Persona]: 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]
[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 = {} personas = {}
@@ -49,7 +48,7 @@ class PersonaManager:
def save_persona(self, persona: Persona, scope: str = "project") -> None: def save_persona(self, persona: Persona, scope: str = "project") -> None:
""" """
[C: tests/test_persona_manager.py:test_save_persona] [C: tests/test_persona_manager.py:test_save_persona]
""" """
path = self._get_path(scope) path = self._get_path(scope)
data = self._load_file(path) data = self._load_file(path)
@@ -76,7 +75,7 @@ class PersonaManager:
def delete_persona(self, name: str, scope: str = "project") -> None: def delete_persona(self, name: str, scope: str = "project") -> None:
""" """
[C: tests/test_persona_manager.py:test_delete_persona] [C: tests/test_persona_manager.py:test_delete_persona]
""" """
path = self._get_path(scope) path = self._get_path(scope)
data = self._load_file(path) data = self._load_file(path)
@@ -86,7 +85,7 @@ class PersonaManager:
def _load_file(self, path: Path) -> Dict[str, Any]: 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] [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(): if not path.exists():
return {} return {}
@@ -98,7 +97,7 @@ class PersonaManager:
def _save_file(self, path: Path, data: Dict[str, Any]) -> None: 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] [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) path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "wb") as f: with open(path, "wb") as f:
+7 -9
View File
@@ -22,9 +22,8 @@ class PresetManager:
def load_all(self) -> Dict[str, Preset]: 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]
[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] = {} presets: Dict[str, Preset] = {}
@@ -49,9 +48,8 @@ class PresetManager:
def save_preset(self, preset: Preset, scope: str = "project") -> None: 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]
[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 path = self.global_path if scope == "global" else self.project_path
if not path: if not path:
@@ -68,7 +66,7 @@ class PresetManager:
def delete_preset(self, name: str, scope: str) -> None: 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] [C: tests/test_preset_manager.py:test_delete_preset, tests/test_presets.py:TestPresetManager.test_delete_preset]
""" """
if scope == "project" and self.project_root: if scope == "project" and self.project_root:
path = get_project_presets_path(self.project_root) path = get_project_presets_path(self.project_root)
@@ -97,7 +95,7 @@ class PresetManager:
def _load_file(self, path: Path) -> Dict[str, Any]: 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] [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(): if not path.exists():
return {"presets": {}} return {"presets": {}}
@@ -115,7 +113,7 @@ class PresetManager:
def _save_file(self, path: Path, data: Dict[str, Any]) -> None: 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] [C: src/workspace_manager.py:WorkspaceManager.delete_profile, src/workspace_manager.py:WorkspaceManager.save_profile]
""" """
if path.parent.exists() and path.parent.is_file(): if path.parent.exists() and path.parent.is_file():
raise ValueError(f"Cannot save to {path}: Parent directory {path.parent} is a file.") raise ValueError(f"Cannot save to {path}: Parent directory {path.parent} is a file.")
+130 -163
View File
@@ -38,16 +38,16 @@ 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] [C: tests/test_thinking_persistence.py:test_entry_to_str_with_thinking]
""" """
ts = entry.get("ts", "") ts = entry.get("ts", "")
role = entry.get("role", "User") role = entry.get("role", "User")
content = entry.get("content", "") content = entry.get("content", "")
segments = entry.get("thinking_segments") segments = entry.get("thinking_segments")
if segments: if segments:
for s in segments: for s in segments:
marker = s.get("marker", "thinking") marker = s.get("marker", "thinking")
s_content = s.get("content", "") s_content = s.get("content", "")
content = f"<{marker}>\n{s_content}\n</{marker}>\n{content}" content = f"<{marker}>\n{s_content}\n</{marker}>\n{content}"
if ts: if ts:
return f"@{ts}\n{role}:\n{content}" return f"@{ts}\n{role}:\n{content}"
@@ -64,27 +64,27 @@ 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] [C: tests/test_thinking_persistence.py:test_str_to_entry_with_thinking]
""" """
ts = "" ts = ""
rest = raw rest = raw
if rest.startswith("@"): if rest.startswith("@"):
nl = rest.find("\n") nl = rest.find("\n")
if nl != -1: if nl != -1:
ts = rest[1:nl] ts = rest[1:nl]
rest = rest[nl + 1:] rest = rest[nl + 1:]
known = roles or ["User", "AI", "Vendor API", "System"] known = roles or ["User", "AI", "Vendor API", "System"]
role_pat = re.compile( role_pat = re.compile(
r"^(?:\[)?(" + "|".join(re.escape(r) for r in known) + r")(?:\])?:?\s*$", r"^(?:\[)?(" + "|".join(re.escape(r) for r in known) + r")(?:\])?:?\s*$",
re.IGNORECASE, re.IGNORECASE,
) )
parts = rest.split("\n", 1) parts = rest.split("\n", 1)
matched_role = "User" matched_role = "User"
content = rest.strip() content = rest.strip()
if parts: if parts:
m = role_pat.match(parts[0].strip()) m = role_pat.match(parts[0].strip())
if m: if m:
raw_role = m.group(1) raw_role = m.group(1)
matched_role = next((r for r in known if r.lower() == raw_role.lower()), raw_role) matched_role = next((r for r in known if r.lower() == raw_role.lower()), raw_role)
content = parts[1].strip() if len(parts) > 1 else "" content = parts[1].strip() if len(parts) > 1 else ""
return {"role": matched_role, "content": content, "collapsed": False, "ts": ts} return {"role": matched_role, "content": content, "collapsed": False, "ts": ts}
# ── git helpers ────────────────────────────────────────────────────────────── # ── git helpers ──────────────────────────────────────────────────────────────
@@ -102,13 +102,13 @@ def get_git_commit(git_dir: str) -> str:
def default_discussion() -> dict[str, Any]: def default_discussion() -> dict[str, Any]:
""" """
[C: tests/test_discussion_takes.py:TestDiscussionTakes.test_promote_take_renames_discussion] [C: tests/test_discussion_takes.py:TestDiscussionTakes.test_promote_take_renames_discussion]
""" """
return {"git_commit": "", "last_updated": now_ts(), "history": []} return {"git_commit": "", "last_updated": now_ts(), "history": []}
def default_project(name: str = "unnamed") -> dict[str, Any]: 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] [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 { return {
"project": {"name": name, "git_dir": "", "system_prompt": "", "execution_mode": "native"}, "project": {"name": name, "git_dir": "", "system_prompt": "", "execution_mode": "native"},
@@ -120,31 +120,31 @@ def default_project(name: str = "unnamed") -> dict[str, Any]:
"deepseek": {"reasoning_effort": "medium"}, "deepseek": {"reasoning_effort": "medium"},
"agent": { "agent": {
"tools": { "tools": {
"run_powershell": True, "run_powershell": True,
"read_file": True, "read_file": True,
"list_directory": True, "list_directory": True,
"search_files": True, "search_files": True,
"get_file_summary": True, "get_file_summary": True,
"web_search": True, "web_search": True,
"fetch_url": True, "fetch_url": True,
"py_get_skeleton": True, "py_get_skeleton": True,
"py_get_code_outline": True, "py_get_code_outline": True,
"get_file_slice": True, "get_file_slice": True,
"py_get_definition": True, "py_get_definition": True,
"py_get_signature": True, "py_get_signature": True,
"py_get_class_summary": True, "py_get_class_summary": True,
"py_get_var_declaration": True, "py_get_var_declaration": True,
"get_git_diff": True, "get_git_diff": True,
"py_find_usages": True, "py_find_usages": True,
"py_get_imports": True, "py_get_imports": True,
"py_check_syntax": True, "py_check_syntax": True,
"py_get_hierarchy": True, "py_get_hierarchy": True,
"py_get_docstring": True, "py_get_docstring": True,
"get_tree": True, "get_tree": True,
"get_ui_performance": True, "get_ui_performance": True,
"set_file_slice": False, "set_file_slice": False,
"py_update_definition": False, "py_update_definition": False,
"py_set_signature": False, "py_set_signature": False,
"py_set_var_declaration": False, "py_set_var_declaration": False,
} }
}, },
@@ -163,23 +163,19 @@ def default_project(name: str = "unnamed") -> dict[str, Any]:
def get_history_path(project_path: Union[str, Path]) -> Path: 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]
[C: tests/test_history_management.py:test_save_separation]
""" """
p = Path(project_path) p = Path(project_path)
return p.parent / f"{p.stem}_history.toml" return p.parent / f"{p.stem}_history.toml"
def load_project(path: Union[str, Path]) -> dict[str, Any]: def load_project(path: Union[str, Path]) -> dict[str, Any]:
""" """
Load a project TOML file.
Automatically migrates legacy 'discussion' keys to a sibling history file.
Load a project TOML 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]
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: with open(path, "rb") as f: proj = tomllib.load(f)
proj = tomllib.load(f)
# Deserialise FileItems in files.paths # Deserialise FileItems in files.paths
if "files" in proj and "paths" in proj["files"]: if "files" in proj and "paths" in proj["files"]:
from src import models from src import models
@@ -198,9 +194,8 @@ def load_project(path: Union[str, Path]) -> dict[str, Any]:
def load_history(project_path: Union[str, Path]) -> dict[str, Any]: 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]
[C: tests/test_thinking_persistence.py:test_save_and_load_history_with_thinking_segments]
""" """
hist_path = get_history_path(project_path) hist_path = get_history_path(project_path)
if hist_path.exists(): if hist_path.exists():
@@ -210,31 +205,25 @@ def load_history(project_path: Union[str, Path]) -> dict[str, Any]:
def clean_nones(data: Any) -> Any: 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]
[C: tests/test_thinking_persistence.py:test_clean_nones_removes_thinking]
""" """
if isinstance(data, dict): if isinstance(data, dict): return {k: clean_nones(v) for k, v in data.items() if v is not None}
return {k: clean_nones(v) for k, v in data.items() if v is not None} elif isinstance(data, list): return [clean_nones(v) for v in data if v is not None]
elif isinstance(data, list):
return [clean_nones(v) for v in data if v is not None]
return data return data
def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Optional[dict[str, Any]] = None) -> None: def save_project(proj: 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.
Save the project TOML. [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]
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) proj = clean_nones(proj)
# Serialise FileItems # Serialise FileItems
if "files" in proj and "paths" in proj["files"]: if "files" in proj and "paths" in proj["files"]:
proj["files"]["paths"] = [p.to_dict() if hasattr(p, "to_dict") else p for p in proj["files"]["paths"]] proj["files"]["paths"] = [p.to_dict() if hasattr(p, "to_dict") else p for p in proj["files"]["paths"]]
if "discussion" in proj: if "discussion" in proj:
if disc_data is None: if disc_data is None: disc_data = proj["discussion"]
disc_data = proj["discussion"]
proj = dict(proj) proj = dict(proj)
del proj["discussion"] del proj["discussion"]
proj = clean_nones(proj) proj = clean_nones(proj)
@@ -252,13 +241,12 @@ def migrate_from_legacy_config(cfg: dict[str, Any]) -> dict[str, Any]:
name = cfg.get("output", {}).get("namespace", "project") name = cfg.get("output", {}).get("namespace", "project")
proj = default_project(name) proj = default_project(name)
for key in ("output", "files", "screenshots"): for key in ("output", "files", "screenshots"):
if key in cfg: if key in cfg: proj[key] = dict(cfg[key])
proj[key] = dict(cfg[key]) disc = cfg.get("discussion", {})
disc = cfg.get("discussion", {})
proj["discussion"]["roles"] = disc.get("roles", ["User", "AI", "Vendor API", "System", "Context"]) proj["discussion"]["roles"] = disc.get("roles", ["User", "AI", "Vendor API", "System", "Context"])
main_disc = proj["discussion"]["discussions"]["main"] main_disc = proj["discussion"]["discussions"]["main"]
main_disc["history"] = disc.get("history", []) main_disc["history"] = disc.get("history", [])
main_disc["last_updated"] = now_ts() main_disc["last_updated"] = now_ts()
return proj return proj
# ── flat config for aggregate.run() ───────────────────────────────────────── # ── flat config for aggregate.run() ─────────────────────────────────────────
@@ -268,9 +256,9 @@ def flat_config(proj: dict[str, Any], disc_name: Optional[str] = None, track_id:
if track_id: if track_id:
history = load_track_history(track_id, proj.get("files", {}).get("base_dir", ".")) history = load_track_history(track_id, proj.get("files", {}).get("base_dir", "."))
else: else:
name = disc_name or disc_sec.get("active", "main") name = disc_name or disc_sec.get("active", "main")
disc_data = disc_sec.get("discussions", {}).get(name, {}) disc_data = disc_sec.get("discussions", {}).get(name, {})
history = disc_data.get("history", []) history = disc_data.get("history", [])
return { return {
"project": proj.get("project", {}), "project": proj.get("project", {}),
"output": proj.get("output", {}), "output": proj.get("output", {}),
@@ -286,187 +274,169 @@ def flat_config(proj: dict[str, Any], disc_name: Optional[str] = None, track_id:
def save_track_state(track_id: str, state: 'TrackState', base_dir: Union[str, Path] = ".") -> None: 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]
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 = paths.get_track_state_dir(track_id, project_path=str(base_dir))
track_dir.mkdir(parents=True, exist_ok=True) track_dir.mkdir(parents=True, exist_ok=True)
state_file = track_dir / "state.toml" state_file = track_dir / "state.toml"
data = clean_nones(state.to_dict()) data = clean_nones(state.to_dict())
with open(state_file, "wb") as f: with open(state_file, "wb") as f: tomli_w.dump(data, f)
tomli_w.dump(data, f)
def load_track_state(track_id: str, base_dir: Union[str, Path] = ".") -> Optional['TrackState']: 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]
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 from src.models import TrackState
state_file = paths.get_track_state_dir(track_id, project_path=str(base_dir)) / 'state.toml' state_file = paths.get_track_state_dir(track_id, project_path=str(base_dir)) / 'state.toml'
if not state_file.exists(): if not state_file.exists(): return None
return None with open(state_file, "rb") as f: data = tomllib.load(f)
with open(state_file, "rb") as f:
data = tomllib.load(f)
return TrackState.from_dict(data) return TrackState.from_dict(data)
def load_track_history(track_id: str, base_dir: Union[str, Path] = ".") -> list[str]: 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.
Loads the discussion history for a specific track from its state.toml.
Returns a list of entry strings formatted with @timestamp.
""" """
state = load_track_state(track_id, base_dir) state = load_track_state(track_id, base_dir)
if not state: if not state: return []
return []
history: list[str] = [] history: list[str] = []
for entry in state.discussion: for entry in state.discussion:
e = dict(entry) e = dict(entry)
ts = e.get("ts") ts = e.get("ts")
if isinstance(ts, datetime.datetime): if isinstance(ts, datetime.datetime): e["ts"] = ts.strftime(TS_FMT)
e["ts"] = ts.strftime(TS_FMT)
history.append(entry_to_str(e)) history.append(entry_to_str(e))
return history return history
def save_track_history(track_id: str, history: list[str], base_dir: Union[str, Path] = ".") -> None: 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.
Saves the discussion history for a specific track to its state.toml.
'history' is expected to be a list of formatted strings.
""" """
state = load_track_state(track_id, base_dir) state = load_track_state(track_id, base_dir)
if not state: if not state:
return return
roles = ["User", "AI", "Vendor API", "System", "Reasoning"] roles = ["User", "AI", "Vendor API", "System", "Reasoning"]
entries = [str_to_entry(h, roles) for h in history] entries = [str_to_entry(h, roles) for h in history]
state.discussion = entries state.discussion = entries
save_track_state(track_id, state, base_dir) save_track_state(track_id, state, base_dir)
def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]: def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
""" """
Scans the conductor/tracks/ directory and returns a list of dictionaries
containing track metadata: 'id', 'title', 'status', 'complete', 'total',
Scans the conductor/tracks/ directory and returns a list of dictionaries and 'progress' (0.0 to 1.0).
containing track metadata: 'id', 'title', 'status', 'complete', 'total', Handles missing or malformed metadata.json or state.toml by falling back
and 'progress' (0.0 to 1.0). to available info or defaults.
Handles missing or malformed metadata.json or state.toml by falling back [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]
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)) tracks_dir = paths.get_tracks_dir(project_path=str(base_dir))
if not tracks_dir.exists(): if not tracks_dir.exists(): return []
return []
results: list[dict[str, Any]] = [] results: list[dict[str, Any]] = []
for entry in tracks_dir.iterdir(): for entry in tracks_dir.iterdir():
if not entry.is_dir(): if not entry.is_dir(): continue
continue
track_id = entry.name track_id = entry.name
track_info: dict[str, Any] = { track_info: dict[str, Any] = {
"id": track_id, "id": track_id,
"title": track_id, "title": track_id,
"status": "unknown", "status": "unknown",
"complete": 0, "complete": 0,
"total": 0, "total": 0,
"progress": 0.0 "progress": 0.0
} }
state_found = False state_found = False
try: try:
state = load_track_state(track_id, base_dir) state = load_track_state(track_id, base_dir)
if state: if state:
track_info["id"] = state.metadata.id or track_id track_info["id"] = state.metadata.id or track_id
track_info["title"] = state.metadata.name or track_id track_info["title"] = state.metadata.name or track_id
track_info["status"] = state.metadata.status or "unknown" track_info["status"] = state.metadata.status or "unknown"
progress = calculate_track_progress(state.tasks) progress = calculate_track_progress(state.tasks)
track_info["complete"] = progress["completed"] track_info["complete"] = progress["completed"]
track_info["total"] = progress["total"] track_info["total"] = progress["total"]
track_info["progress"] = progress["percentage"] / 100.0 track_info["progress"] = progress["percentage"] / 100.0
state_found = True state_found = True
except Exception: except Exception:
pass pass
if not state_found: if not state_found:
metadata_file = entry / "metadata.json" metadata_file = entry / "metadata.json"
if metadata_file.exists(): if metadata_file.exists():
try: try:
with open(metadata_file, "r") as f: with open(metadata_file, "r") as f:
data = json.load(f) data = json.load(f)
track_info["id"] = data.get("id", data.get("track_id", track_id)) track_info["id"] = data.get("id", data.get("track_id", track_id))
track_info["title"] = data.get("title", data.get("name", data.get("description", track_id))) track_info["title"] = data.get("title", data.get("name", data.get("description", track_id)))
track_info["status"] = data.get("status", "unknown") track_info["status"] = data.get("status", "unknown")
except Exception: except Exception:
pass pass
if track_info["total"] == 0: if track_info["total"] == 0:
plan_file = entry / "plan.md" plan_file = entry / "plan.md"
if plan_file.exists(): if plan_file.exists():
try: try:
with open(plan_file, "r", encoding="utf-8") as f: with open(plan_file, "r", encoding="utf-8") as f:
content = f.read() content = f.read()
tasks = re.findall(r"^[ \t]*- \[[ x~]\] .*", content, re.MULTILINE) tasks = re.findall(r"^[ \t]*- \[[ x~]\] .*", content, re.MULTILINE)
completed_tasks = re.findall(r"^[ \t]*- \[x\] .*", content, re.MULTILINE) completed_tasks = re.findall(r"^[ \t]*- \[x\] .*", content, re.MULTILINE)
track_info["total"] = len(tasks) track_info["total"] = len(tasks)
track_info["complete"] = len(completed_tasks) track_info["complete"] = len(completed_tasks)
if track_info["total"] > 0: if track_info["total"] > 0:
track_info["progress"] = float(track_info["complete"]) / track_info["total"] track_info["progress"] = float(track_info["complete"]) / track_info["total"]
except Exception: except Exception:
pass pass
results.append(track_info) results.append(track_info)
return results return results
def calculate_track_progress(tickets: list) -> dict: 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)
Calculates track progress based on ticket statuses. [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]
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) total = len(tickets)
if total == 0: if total == 0:
return { return {
"percentage": 0.0, "percentage": 0.0,
"completed": 0, "completed": 0,
"total": 0, "total": 0,
"in_progress": 0, "in_progress": 0,
"blocked": 0, "blocked": 0,
"todo": 0 "todo": 0
} }
completed = sum(1 for t in tickets if t.status == "completed") completed = sum(1 for t in tickets if t.status == "completed")
in_progress = sum(1 for t in tickets if t.status == "in_progress") in_progress = sum(1 for t in tickets if t.status == "in_progress")
blocked = sum(1 for t in tickets if t.status == "blocked") blocked = sum(1 for t in tickets if t.status == "blocked")
todo = sum(1 for t in tickets if t.status == "todo") todo = sum(1 for t in tickets if t.status == "todo")
percentage = (completed / total) * 100.0
percentage = (completed / total) * 100.0
return { return {
"percentage": float(percentage), "percentage": float(percentage),
"completed": completed, "completed": completed,
"total": total, "total": total,
"in_progress": in_progress, "in_progress": in_progress,
"blocked": blocked, "blocked": blocked,
"todo": todo "todo": todo
} }
def branch_discussion(project_dict: dict, source_id: str, new_id: str, message_index: int) -> None: 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.
Creates a new discussion in project_dict['discussion']['discussions'] by copying [C: tests/test_discussion_takes.py:TestDiscussionTakes.test_branch_discussion_creates_new_take]
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"]: if "discussion" not in project_dict or "discussions" not in project_dict["discussion"]: return
return if source_id not in project_dict["discussion"]["discussions"]: return
if source_id not in project_dict["discussion"]["discussions"]:
return
source_disc = project_dict["discussion"]["discussions"][source_id] source_disc = project_dict["discussion"]["discussions"][source_id]
new_disc = default_discussion() new_disc = default_discussion()
new_disc["git_commit"] = source_disc.get("git_commit", "") new_disc["git_commit"] = source_disc.get("git_commit", "")
# Copy history up to and including message_index # Copy history up to and including message_index
new_disc["history"] = source_disc["history"][:message_index + 1] new_disc["history"] = source_disc["history"][:message_index + 1]
@@ -476,14 +446,11 @@ def branch_discussion(project_dict: dict, source_id: str, new_id: str, message_i
def promote_take(project_dict: dict, take_id: str, new_id: str) -> None: 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]
[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"]: if "discussion" not in project_dict or "discussions" not in project_dict["discussion"]: return
return if take_id not in project_dict["discussion"]["discussions"]: return
if take_id not in project_dict["discussion"]["discussions"]:
return
disc = project_dict["discussion"]["discussions"].pop(take_id) disc = project_dict["discussion"]["discussions"].pop(take_id)
project_dict["discussion"]["discussions"][new_id] = disc project_dict["discussion"]["discussions"][new_id] = disc