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,
|
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
|
#region: Constants
|
||||||
|
|
||||||
@@ -278,255 +295,12 @@ def __getattr__(name: str) -> Any:
|
|||||||
return cls
|
return cls
|
||||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
||||||
#region: MMA Core
|
# MMA Core dataclasses (ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata)
|
||||||
|
# moved to src/mma.py in module_taxonomy_refactor_20260627 Phase 3.1. See
|
||||||
@dataclass
|
# the re-export block at the top of this module for backward-compat.
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
#region: State & Config
|
#region: State & Config
|
||||||
|
# TrackState moved to src/mma.py (re-exported at the top of this module for backward compat).
|
||||||
@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()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FileItem:
|
class FileItem:
|
||||||
|
|||||||
Reference in New Issue
Block a user