Private
Public Access
more organization
This commit is contained in:
+6
-12
@@ -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}", ""]
|
||||||
|
|||||||
+74
-79
@@ -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
|
||||||
@@ -318,21 +319,21 @@ 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
|
||||||
@@ -364,9 +365,9 @@ 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
|
||||||
@@ -415,11 +416,11 @@ 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
|
||||||
@@ -469,9 +470,9 @@ 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
|
||||||
@@ -522,15 +523,15 @@ 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
|
||||||
@@ -630,9 +631,9 @@ 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
|
||||||
@@ -659,9 +660,9 @@ 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
|
||||||
@@ -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,13 +780,13 @@ 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
|
||||||
@@ -819,10 +814,10 @@ 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
|
||||||
@@ -846,12 +841,12 @@ 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
|
||||||
@@ -873,10 +868,10 @@ 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
|
||||||
@@ -903,10 +898,10 @@ 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
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ 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.
|
Spawns a new worker thread if the pool is not full.
|
||||||
Returns the thread object or None if full.
|
Returns the thread object or None if full.
|
||||||
[C: tests/test_parallel_execution.py:test_worker_pool_completion_cleanup, tests/test_parallel_execution.py:test_worker_pool_limit, tests/test_parallel_execution.py:test_worker_pool_tracking]
|
[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]
|
||||||
@@ -112,8 +110,6 @@ class WorkerPool:
|
|||||||
|
|
||||||
class ConductorEngine:
|
class ConductorEngine:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Orchestrates the execution of tickets within a track.
|
Orchestrates the execution of tickets within a track.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -154,7 +150,6 @@ class ConductorEngine:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -162,7 +157,6 @@ class ConductorEngine:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -170,7 +164,6 @@ class ConductorEngine:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -179,7 +172,6 @@ class ConductorEngine:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -188,7 +180,6 @@ class ConductorEngine:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
+3
-10
@@ -12,8 +12,6 @@ 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.
|
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]
|
[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]
|
||||||
"""
|
"""
|
||||||
@@ -21,13 +19,10 @@ def get_track_history_summary() -> str:
|
|||||||
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
|
||||||
@@ -60,8 +55,6 @@ 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.
|
Tier 1 (Strategic PM) call.
|
||||||
Analyzes the project state and user request to generate a list of Tracks.
|
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]
|
[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]
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
|||||||
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:
|
||||||
@@ -235,7 +234,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -89,8 +89,6 @@ def get_monitor() -> PerformanceMonitor:
|
|||||||
|
|
||||||
class PerformanceMonitor:
|
class PerformanceMonitor:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Tracks application performance metrics like FPS, frame time, and CPU usage.
|
Tracks application performance metrics like FPS, frame time, and CPU usage.
|
||||||
Supports thread-safe tracking for individual components with efficient moving averages.
|
Supports thread-safe tracking for individual components with efficient moving averages.
|
||||||
"""
|
"""
|
||||||
@@ -158,8 +156,7 @@ 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:
|
||||||
@@ -229,15 +226,12 @@ class PerformanceMonitor:
|
|||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -274,7 +268,6 @@ class PerformanceMonitor:
|
|||||||
|
|
||||||
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]
|
||||||
"""
|
"""
|
||||||
@@ -287,7 +280,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
@@ -49,7 +48,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
+22
-55
@@ -163,7 +163,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
@@ -172,14 +171,11 @@ def get_history_path(project_path: Union[str, Path]) -> Path:
|
|||||||
|
|
||||||
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.
|
Load a project TOML file.
|
||||||
Automatically migrates legacy 'discussion' keys to a sibling history file.
|
Automatically migrates legacy 'discussion' keys to a sibling history file.
|
||||||
[C: tests/test_history_management.py:test_history_persistence_across_turns, tests/test_history_management.py:test_migration_on_load, tests/test_project_manager_modes.py:test_load_save_execution_mode, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_project_serialization.py:TestProjectSerialization.test_fileitem_roundtrip]
|
[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,7 +194,6 @@ 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]
|
||||||
"""
|
"""
|
||||||
@@ -210,20 +205,15 @@ 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.
|
Save the project TOML.
|
||||||
If 'discussion' is present in proj, it is moved to the sibling history file.
|
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]
|
[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]
|
||||||
@@ -233,8 +223,7 @@ def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Option
|
|||||||
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,8 +241,7 @@ 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"]
|
||||||
@@ -286,8 +274,6 @@ 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.
|
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]
|
[C: tests/test_project_manager_tracks.py:test_get_all_tracks_with_state, tests/test_track_state_persistence.py:test_track_state_persistence]
|
||||||
"""
|
"""
|
||||||
@@ -295,47 +281,36 @@ def save_track_state(track_id: str, state: 'TrackState', base_dir: Union[str, Pa
|
|||||||
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.
|
Loads a TrackState object from conductor/tracks/<track_id>/state.toml.
|
||||||
[C: tests/test_track_state_persistence.py:test_track_state_persistence]
|
[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.
|
Loads the discussion history for a specific track from its state.toml.
|
||||||
Returns a list of entry strings formatted with @timestamp.
|
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.
|
Saves the discussion history for a specific track to its state.toml.
|
||||||
'history' is expected to be a list of formatted strings.
|
'history' is expected to be a list of formatted strings.
|
||||||
"""
|
"""
|
||||||
@@ -349,8 +324,6 @@ def save_track_history(track_id: str, history: list[str], base_dir: Union[str, P
|
|||||||
|
|
||||||
def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Scans the conductor/tracks/ directory and returns a list of dictionaries
|
Scans the conductor/tracks/ directory and returns a list of dictionaries
|
||||||
containing track metadata: 'id', 'title', 'status', 'complete', 'total',
|
containing track metadata: 'id', 'title', 'status', 'complete', 'total',
|
||||||
and 'progress' (0.0 to 1.0).
|
and 'progress' (0.0 to 1.0).
|
||||||
@@ -359,12 +332,12 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
[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]
|
[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,
|
||||||
@@ -375,6 +348,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
"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:
|
||||||
@@ -388,6 +362,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
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():
|
||||||
@@ -399,6 +374,7 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
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():
|
||||||
@@ -413,13 +389,12 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
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.
|
Calculates track progress based on ticket statuses.
|
||||||
percentage (float), completed (int), total (int), in_progress (int), blocked (int), todo (int)
|
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]
|
[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]
|
||||||
@@ -439,7 +414,6 @@ def calculate_track_progress(tickets: list) -> dict:
|
|||||||
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 {
|
||||||
@@ -454,16 +428,12 @@ def calculate_track_progress(tickets: list) -> dict:
|
|||||||
|
|
||||||
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
|
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.
|
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]
|
[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()
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user