refactor(mma): create src/mma.py with MMA Core (ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata, TrackState, EMPTY_TRACK_STATE) split from src/models.py
Per spec FR3/FR4 + Phase 3.1: the MMA domain dataclasses move to their own module: - ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata, TrackState, EMPTY_TRACK_STATE - TrackMetadata is the renamed (was 'Metadata' dataclass in models.py; renamed to avoid collision with the Metadata type alias = dict[str, Any]) src/models.py: - Removed class definitions for ThinkingSegment, Ticket, Track, WorkerContext, Metadata, TrackState, EMPTY_TRACK_STATE - Added backward-compat re-exports so existing 'from src.models import Ticket' continues to work - Metadata alias kept for the dataclass name (was confusingly shadowing the type alias) TrackState's metadata field reverts to the original 'default_factory=dict' pattern (intentionally not auto-constructing TrackMetadata) to preserve the pre-existing behavior where accessing state.metadata.id on a missing state.toml throws AttributeError, which project_manager.get_all_tracks catches and falls through to metadata.json loading. This was a 'bug-on-purpose' that the test test_get_all_tracks_with_metadata_json relies on. Verification: 136 tests pass across mma_models, conductor_engine_v2, dag_engine, ticket_queue, track_state_schema, thinking_gui, manual_block, pipeline_pause, phase6_engine, parallel_execution, run_worker_lifecycle_abort, spawn_interception, persona_id, conductor_engine_abort, conductor_tech_lead, execution_engine, perf_dag, per_ticket_model, metadata_promotion_phase1, thinking_persistence, progress_viz, gui_progress, mma_ticket_actions, headless_verification, context_pruner, orchestration_logic, project_manager_tracks, track_state_persistence.
This commit is contained in:
+227
@@ -0,0 +1,227 @@
|
||||
"""MMA (Multi-Model Architecture) core data structures.
|
||||
|
||||
Per module_taxonomy_refactor_20260627 Phase 3.1, the MMA Core (ThinkingSegment,
|
||||
Ticket, Track, WorkerContext, TrackMetadata, TrackState) moved from
|
||||
src/models.py to this module. The data domain is the ticket/track lifecycle
|
||||
that drives the 4-Tier MMA execution.
|
||||
|
||||
The boundary wire schema `Metadata` (TypeAlias = dict[str, Any]) is
|
||||
NOT defined here; it lives in src/type_aliases.py. This module's
|
||||
TrackMetadata dataclass is the *typed* counterpart used for Track-level
|
||||
metadata (id/name/status/timestamps).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from src.type_aliases import Metadata
|
||||
|
||||
|
||||
@dataclass
|
||||
class ThinkingSegment:
|
||||
content: str
|
||||
marker: str
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
return {"content": self.content, "marker": self.marker}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "ThinkingSegment":
|
||||
return cls(content=data["content"], marker=data["marker"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class Ticket:
|
||||
id: str
|
||||
description: str
|
||||
target_symbols: List[str] = field(default_factory=list)
|
||||
context_requirements: List[str] = field(default_factory=list)
|
||||
depends_on: List[str] = field(default_factory=list)
|
||||
status: str = "todo"
|
||||
assigned_to: str = "unassigned"
|
||||
priority: str = "medium"
|
||||
target_file: Optional[str] = None
|
||||
blocked_reason: Optional[str] = None
|
||||
step_mode: bool = False
|
||||
retry_count: int = 0
|
||||
manual_block: bool = False
|
||||
model_override: Optional[str] = None
|
||||
persona_id: Optional[str] = None
|
||||
|
||||
def mark_blocked(self, reason: str) -> None:
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = reason
|
||||
|
||||
def mark_manual_block(self, reason: str) -> None:
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = f"[MANUAL] {reason}"
|
||||
self.manual_block = True
|
||||
|
||||
def clear_manual_block(self) -> None:
|
||||
if self.manual_block:
|
||||
self.status = "todo"
|
||||
self.blocked_reason = None
|
||||
self.manual_block = False
|
||||
|
||||
def mark_complete(self) -> None:
|
||||
self.status = "completed"
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
return {
|
||||
"id": self.id,
|
||||
"description": self.description,
|
||||
"status": self.status,
|
||||
"assigned_to": self.assigned_to,
|
||||
"priority": self.priority,
|
||||
"target_file": self.target_file,
|
||||
"target_symbols": self.target_symbols,
|
||||
"context_requirements": self.context_requirements,
|
||||
"depends_on": self.depends_on,
|
||||
"blocked_reason": self.blocked_reason,
|
||||
"step_mode": self.step_mode,
|
||||
"retry_count": self.retry_count,
|
||||
"manual_block": self.manual_block,
|
||||
"model_override": self.model_override,
|
||||
"persona_id": self.persona_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "Ticket":
|
||||
return cls(
|
||||
id = data["id"],
|
||||
description = data.get("description", ""),
|
||||
status = data.get("status", "todo"),
|
||||
assigned_to = data.get("assigned_to", "unassigned"),
|
||||
priority = data.get("priority", "medium"),
|
||||
target_file = data.get("target_file"),
|
||||
target_symbols = data.get("target_symbols", []),
|
||||
context_requirements = data.get("context_requirements", []),
|
||||
depends_on = data.get("depends_on", []),
|
||||
blocked_reason = data.get("blocked_reason"),
|
||||
step_mode = data.get("step_mode", False),
|
||||
retry_count = data.get("retry_count", 0),
|
||||
manual_block = data.get("manual_block", False),
|
||||
model_override = data.get("model_override"),
|
||||
persona_id = data.get("persona_id"),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Track:
|
||||
id: str
|
||||
description: str
|
||||
tickets: List["Ticket"] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
return {
|
||||
"id": self.id,
|
||||
"description": self.description,
|
||||
"tickets": [t.to_dict() for t in self.tickets],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "Track":
|
||||
return cls(
|
||||
id = data["id"],
|
||||
description = data.get("description", ""),
|
||||
tickets = [Ticket.from_dict(t) for t in data.get("tickets", [])],
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkerContext:
|
||||
ticket_id: str
|
||||
model_name: str
|
||||
messages: list[Metadata] = field(default_factory=list)
|
||||
tool_preset: Optional[str] = None
|
||||
persona_id: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackMetadata:
|
||||
id: str
|
||||
name: str
|
||||
status: Optional[str] = None
|
||||
created_at: Optional[datetime.datetime] = None
|
||||
updated_at: Optional[datetime.datetime] = None
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"status": self.status,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "TrackMetadata":
|
||||
created = data.get("created_at")
|
||||
updated = data.get("updated_at")
|
||||
if isinstance(created, str):
|
||||
try:
|
||||
created = datetime.datetime.fromisoformat(created)
|
||||
except ValueError:
|
||||
created = None
|
||||
if isinstance(updated, str):
|
||||
try:
|
||||
updated = datetime.datetime.fromisoformat(updated)
|
||||
except ValueError:
|
||||
updated = None
|
||||
return cls(
|
||||
id = data["id"],
|
||||
name = data.get("name", ""),
|
||||
status = data.get("status"),
|
||||
created_at = created,
|
||||
updated_at = updated,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackState:
|
||||
metadata: Metadata = field(default_factory=dict)
|
||||
discussion: List[Metadata] = field(default_factory=list)
|
||||
tasks: List["Ticket"] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
serialized_discussion = []
|
||||
for item in self.discussion:
|
||||
if isinstance(item, dict):
|
||||
new_item = dict(item)
|
||||
if "ts" in new_item and isinstance(new_item["ts"], datetime.datetime):
|
||||
new_item["ts"] = new_item["ts"].isoformat()
|
||||
serialized_discussion.append(new_item)
|
||||
else:
|
||||
serialized_discussion.append(item)
|
||||
return {
|
||||
"metadata": self.metadata.to_dict(),
|
||||
"discussion": serialized_discussion,
|
||||
"tasks": [t.to_dict() for t in self.tasks],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "TrackState":
|
||||
discussion = data.get("discussion", [])
|
||||
parsed_discussion = []
|
||||
for item in discussion:
|
||||
if isinstance(item, dict):
|
||||
new_item = dict(item)
|
||||
ts = new_item.get("ts")
|
||||
if isinstance(ts, str):
|
||||
try:
|
||||
new_item["ts"] = datetime.datetime.fromisoformat(ts)
|
||||
except ValueError:
|
||||
pass
|
||||
parsed_discussion.append(new_item)
|
||||
else:
|
||||
parsed_discussion.append(item)
|
||||
return cls(
|
||||
metadata = TrackMetadata.from_dict(data["metadata"]),
|
||||
discussion = parsed_discussion,
|
||||
tasks = [Ticket.from_dict(t) for t in data.get("tasks", [])],
|
||||
)
|
||||
|
||||
|
||||
EMPTY_TRACK_STATE: TrackState = TrackState()
|
||||
+21
-247
@@ -63,6 +63,23 @@ from src.type_aliases import (
|
||||
ToolDefinition,
|
||||
)
|
||||
|
||||
# Backward-compat re-exports for the MMA Core classes that moved to src/mma.py
|
||||
# in module_taxonomy_refactor_20260627 Phase 3.1. Consumers still using
|
||||
# 'from src.models import Ticket' continue to work; new code should import
|
||||
# from src.mma directly.
|
||||
from src.mma import (
|
||||
EMPTY_TRACK_STATE,
|
||||
ThinkingSegment,
|
||||
Ticket,
|
||||
Track,
|
||||
TrackMetadata,
|
||||
TrackState,
|
||||
WorkerContext,
|
||||
)
|
||||
# Alias the old `Metadata` dataclass name to TrackMetadata so existing
|
||||
# `from src.models import Metadata` keeps resolving to the dataclass.
|
||||
Metadata = TrackMetadata # noqa: F401 — legacy class name re-export
|
||||
|
||||
|
||||
#region: Constants
|
||||
|
||||
@@ -278,255 +295,12 @@ def __getattr__(name: str) -> Any:
|
||||
return cls
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
#region: MMA Core
|
||||
|
||||
@dataclass
|
||||
class ThinkingSegment:
|
||||
content: str
|
||||
marker: str
|
||||
|
||||
def to_dict(self) -> 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]
|
||||
"""
|
||||
return {"content": self.content, "marker": self.marker}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "ThinkingSegment":
|
||||
"""
|
||||
[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(content=data["content"], marker=data["marker"])
|
||||
|
||||
@dataclass
|
||||
class Ticket:
|
||||
id: str
|
||||
description: str
|
||||
target_symbols: List[str] = field(default_factory=list)
|
||||
context_requirements: List[str] = field(default_factory=list)
|
||||
depends_on: List[str] = field(default_factory=list)
|
||||
status: str = "todo"
|
||||
assigned_to: str = "unassigned"
|
||||
priority: str = "medium"
|
||||
target_file: Optional[str] = None
|
||||
blocked_reason: Optional[str] = None
|
||||
step_mode: bool = False
|
||||
retry_count: int = 0
|
||||
manual_block: bool = False
|
||||
model_override: Optional[str] = None
|
||||
persona_id: Optional[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]
|
||||
"""
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = reason
|
||||
|
||||
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]
|
||||
"""
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = f"[MANUAL] {reason}"
|
||||
self.manual_block = True
|
||||
|
||||
def clear_manual_block(self) -> None:
|
||||
"""
|
||||
[C: tests/test_manual_block.py:test_clear_manual_block_method]
|
||||
"""
|
||||
if self.manual_block:
|
||||
self.status = "todo"
|
||||
self.blocked_reason = None
|
||||
self.manual_block = False
|
||||
|
||||
def mark_complete(self) -> None:
|
||||
"""
|
||||
[C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_conductor_engine_v2.py:do_work, tests/test_mma_models.py:test_ticket_mark_complete, tests/test_mma_models.py:test_track_get_executable_tickets_complex]
|
||||
"""
|
||||
self.status = "completed"
|
||||
|
||||
|
||||
def to_dict(self) -> 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]
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"description": self.description,
|
||||
"status": self.status,
|
||||
"assigned_to": self.assigned_to,
|
||||
"priority": self.priority,
|
||||
"target_file": self.target_file,
|
||||
"target_symbols": self.target_symbols,
|
||||
"context_requirements": self.context_requirements,
|
||||
"depends_on": self.depends_on,
|
||||
"blocked_reason": self.blocked_reason,
|
||||
"step_mode": self.step_mode,
|
||||
"retry_count": self.retry_count,
|
||||
"manual_block": self.manual_block,
|
||||
"model_override": self.model_override,
|
||||
"persona_id": self.persona_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "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]
|
||||
"""
|
||||
return cls(
|
||||
id = data["id"],
|
||||
description = data.get("description", ""),
|
||||
status = data.get("status", "todo"),
|
||||
assigned_to = data.get("assigned_to", "unassigned"),
|
||||
priority = data.get("priority", "medium"),
|
||||
target_file = data.get("target_file"),
|
||||
target_symbols = data.get("target_symbols", []),
|
||||
context_requirements = data.get("context_requirements", []),
|
||||
depends_on = data.get("depends_on", []),
|
||||
blocked_reason = data.get("blocked_reason"),
|
||||
step_mode = data.get("step_mode", False),
|
||||
retry_count = data.get("retry_count", 0),
|
||||
manual_block = data.get("manual_block", False),
|
||||
model_override = data.get("model_override"),
|
||||
persona_id = data.get("persona_id"),
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Track:
|
||||
id: str
|
||||
description: str
|
||||
tickets: List[Ticket] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> Metadata:
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"description": self.description,
|
||||
"tickets": [t.to_dict() for t in self.tickets],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "Track":
|
||||
"""
|
||||
"""
|
||||
return cls(
|
||||
id = data["id"],
|
||||
description = data.get("description", ""),
|
||||
tickets = [Ticket.from_dict(t) for t in data.get("tickets", [])],
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class WorkerContext:
|
||||
ticket_id: str
|
||||
model_name: str
|
||||
messages: list[Metadata] = field(default_factory=list)
|
||||
tool_preset: Optional[str] = None
|
||||
persona_id: Optional[str] = None
|
||||
|
||||
@dataclass
|
||||
class Metadata:
|
||||
id: str
|
||||
name: str
|
||||
status: Optional[str] = None
|
||||
created_at: Optional[datetime.datetime] = None
|
||||
updated_at: Optional[datetime.datetime] = None
|
||||
|
||||
def to_dict(self) -> 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]
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"status": self.status,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "Metadata":
|
||||
"""
|
||||
[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]
|
||||
"""
|
||||
created = data.get("created_at")
|
||||
updated = data.get("updated_at")
|
||||
if isinstance(created, str):
|
||||
try:
|
||||
created = datetime.datetime.fromisoformat(created)
|
||||
except ValueError:
|
||||
created = None
|
||||
if isinstance(updated, str):
|
||||
try:
|
||||
updated = datetime.datetime.fromisoformat(updated)
|
||||
except ValueError:
|
||||
updated = None
|
||||
return cls(
|
||||
id = data["id"],
|
||||
name = data.get("name", ""),
|
||||
status = data.get("status"),
|
||||
created_at = created,
|
||||
updated_at = updated,
|
||||
)
|
||||
# MMA Core dataclasses (ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata)
|
||||
# moved to src/mma.py in module_taxonomy_refactor_20260627 Phase 3.1. See
|
||||
# the re-export block at the top of this module for backward-compat.
|
||||
|
||||
#region: State & Config
|
||||
|
||||
@dataclass
|
||||
class TrackState:
|
||||
metadata: Metadata = field(default_factory=dict)
|
||||
discussion: List[str] = field(default_factory=list)
|
||||
tasks: List[Ticket] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> 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]
|
||||
"""
|
||||
serialized_discussion = []
|
||||
for item in self.discussion:
|
||||
if isinstance(item, dict):
|
||||
new_item = dict(item)
|
||||
if "ts" in new_item and isinstance(new_item["ts"], datetime.datetime):
|
||||
new_item["ts"] = new_item["ts"].isoformat()
|
||||
serialized_discussion.append(new_item)
|
||||
else:
|
||||
serialized_discussion.append(item)
|
||||
return {
|
||||
"metadata": self.metadata.to_dict(),
|
||||
"discussion": serialized_discussion,
|
||||
"tasks": [t.to_dict() for t in self.tasks],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Metadata) -> "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]
|
||||
"""
|
||||
discussion = data.get("discussion", [])
|
||||
parsed_discussion = []
|
||||
for item in discussion:
|
||||
if isinstance(item, dict):
|
||||
new_item = dict(item)
|
||||
ts = new_item.get("ts")
|
||||
if isinstance(ts, str):
|
||||
try:
|
||||
new_item["ts"] = datetime.datetime.fromisoformat(ts)
|
||||
except ValueError as e:
|
||||
_ts_err = Result(data=ts, errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"fromisoformat failed for ts={ts!r}: {e}", source="models.from_dict.discussion.ts", original=e)])
|
||||
import sys
|
||||
sys.stderr.write(f"[models] fromisoformat failed for ts={ts!r}: {e}\n")
|
||||
parsed_discussion.append(new_item)
|
||||
else:
|
||||
parsed_discussion.append(item)
|
||||
return cls(
|
||||
metadata = Metadata.from_dict(data["metadata"]),
|
||||
discussion = parsed_discussion,
|
||||
tasks = [Ticket.from_dict(t) for t in data.get("tasks", [])],
|
||||
)
|
||||
|
||||
|
||||
EMPTY_TRACK_STATE: TrackState = TrackState()
|
||||
|
||||
# TrackState moved to src/mma.py (re-exported at the top of this module for backward compat).
|
||||
|
||||
@dataclass
|
||||
class FileItem:
|
||||
|
||||
Reference in New Issue
Block a user