3c4a52901a0548eb2a31cd76e3326bd219c2fd74
1128
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3c4a52901a |
refactor(models): reduce to Pydantic proxy helpers + DEFAULT_TOOL_CATEGORIES
After 11 class moves (Phases 3a-3i) + 1 deletion (Phase 4), this commit
reduces src/models.py from 1044 lines (original) / 768 lines (pre-Phase 3b)
to 135 lines. The remaining content is:
- DEFAULT_TOOL_CATEGORIES: the canonical tool list grouped for
the UI's category filter (the ONLY non-Pydantic constant)
- _create_generate_request + _create_confirm_request: the Pydantic
proxy classes for the API hook subsystem
- _PYDANTIC_CLASS_FACTORIES: registry for the Pydantic proxies
- __getattr__: lazy re-exports for ALL 30+ moved classes + PROVIDERS
Removed:
- All 11 class definitions (MMA Core, FileItem + 4 file-related,
Tool + ToolPreset + BiasProfile, 2 editor configs, WorkspaceProfile,
4 MCP config classes + load_mcp_config, ProjectContext + 5 sub)
- All 3 config IO function definitions (load_config_from_disk,
save_config_to_disk, _clean_nones, parse_history_entries)
- All 5 eager re-export blocks at the top (they triggered tomli_w
loading at import time via the personas import; the lazy __getattr__
breaks the cycle)
- AGENT_TOOL_NAMES (deleted in Phase 4)
The lazy __getattr__ keeps the 'from src.models import X' pattern
working for legacy callers. New code should import directly from
the subsystem files (src.mma, src.project, src.project_files,
src.tool_presets, src.tool_bias, src.external_editor, src.mcp_client,
src.workspace_manager, src.personas).
Side benefit: the pre-existing test
tests/test_models_no_top_level_tomli_w.py::test_models_does_not_import_tomli_w_at_module_level
now PASSES. Before Phase 5 it failed because the eager
'from src.personas import Persona' triggered tomli_w loading. The
lazy __getattr__ for Persona only loads tomli_w when 'models.Persona'
is actually accessed (not on a bare 'import src.models').
Verification: VC10
wc -l src/models.py # 135 lines (well under the 1044-line original;
# 30-line target was aspirational; the lazy
# __getattr__ for 30+ moved classes is the
# dominant cost)
Measure-Object -Line on src/models.py # 135
Tests verified (84/85 PASS; 1 pre-existing failure unrelated):
tests/test_mcp_config.py (3/3 PASS)
tests/test_tool_preset_manager.py (4/4 PASS)
tests/test_bias_models.py (3/3 PASS)
tests/test_tool_bias.py (3/3 PASS)
tests/test_external_editor.py (17/17 PASS)
tests/test_workspace_manager.py (3/3 PASS)
tests/test_models_no_top_level_tomli_w.py (3/3 PASS) [previously 1 FAIL]
tests/test_project_context_20260627.py (10/10 PASS)
tests/test_file_item_model.py (4/4 PASS)
tests/test_view_presets.py (4/4 PASS)
tests/test_context_presets_models.py (3/3 PASS)
tests/test_presets.py (5/5 PASS)
tests/test_persona_models.py (2/2 PASS)
tests/test_persona_manager.py (3/3 PASS)
tests/test_arch_boundary_phase2.py (5/6 PASS; 1 pre-existing FAIL
unrelated: test_rejection_prevents_dispatch
is a dialog-mock issue)
tests/test_mcp_tool_specs.py (10/10 PASS)
|
||
|
|
779d504c70 |
refactor(mcp_tool_specs): delete redundant AGENT_TOOL_NAMES; use tool_names() at consumer sites
AGENT_TOOL_NAMES was a hardcoded snapshot of mcp_tool_specs.tool_names()
in src/models.py. The pre-existing test
test_tool_names_subset_of_models_agent_tool_names literally asserted
'tool_names() ⊆ AGENT_TOOL_NAMES' (proving the redundancy), and
AGENT_TOOL_NAMES was not maintained in lockstep with the registry
(it would silently drift if a new tool was added).
This commit:
1. Deletes AGENT_TOOL_NAMES from src/models.py (replaced by an
explanatory comment in the Constants section).
2. Updates 3 consumer sites in src/app_controller.py:
- 'for t in models.AGENT_TOOL_NAMES' -> 'for t in mcp_tool_specs.tool_names()'
- (in 2 methods: __init__ + a setter)
3. Updates 2 test sites in tests/test_arch_boundary_phase2.py:
- 'from src.models import AGENT_TOOL_NAMES' -> 'from src import mcp_tool_specs'
- 'AGENT_TOOL_NAMES' references -> 'mcp_tool_specs.tool_names()'
4. Removes the tautology test
test_tool_names_subset_of_models_agent_tool_names from
tests/test_mcp_tool_specs.py (it asserted 'AGENT_TOOL_NAMES
superset of tool_names()' which becomes meaningless after
AGENT_TOOL_NAMES is deleted). Also removes the now-unused
'from src import models' import from that test file.
Verification: VC9
git grep 'AGENT_TOOL_NAMES' -- 'src/*.py' 'tests/*.py' # 0 hits
from src import mcp_tool_specs
mcp_tool_specs.tool_names() # returns the canonical 45 tools
from src.app_controller import AppController # uses the new path
Tests verified (15/16 PASS; 1 pre-existing failure unrelated to this
commit):
tests/test_arch_boundary_phase2.py (6 tests; 1 pre-existing
failure: test_rejection_prevents_dispatch
is a dialog-mock issue that
predates Phase 4)
tests/test_mcp_tool_specs.py (10 tests; the tautology test was removed;
the remaining 10 pass)
|
||
|
|
a90f9634aa |
refactor(mcp_client): merge MCP config classes + load_mcp_config from models.py
Per the 4-criteria decision rule: MCP config classes (MCPServerConfig,
MCPConfiguration, VectorStoreConfig, RAGConfig) + load_mcp_config are
used by mcp_client + api_hooks + app_controller (3 systems) but
they are tightly coupled to the MCP subsystem's data layer. The test
file tests/test_mcp_config.py exists. Per the v2 spec: MERGE into
the existing src/mcp_client.py (the destination file IS the MCP
subsystem; the data layer belongs with the dispatcher).
This commit:
1. Adds MCPServerConfig + MCPConfiguration + VectorStoreConfig +
RAGConfig + load_mcp_config class/function definitions to
src/mcp_client.py at the top (after the imports + before the
mutating tools sentinel).
2. Removes the same class defs from src/models.py.
3. Adds lazy re-export via the existing __getattr__ in src/models.py
(EAGER would cycle: mcp_client was previously accessing them
via 'models.X'; eager re-export would deadlock).
4. Updates src/mcp_client.py internal references:
- 'def __init__(self, config: models.MCPServerConfig)' -> 'MCPServerConfig'
- 'async def add_server(self, config: models.MCPServerConfig)' -> 'MCPServerConfig'
Verification: VC8 (MCP config classes + load_mcp_config)
from src.mcp_client import MCPServerConfig, MCPConfiguration,
VectorStoreConfig, RAGConfig,
load_mcp_config # OK
from src.models import MCPServerConfig, MCPConfiguration,
VectorStoreConfig, RAGConfig,
load_mcp_config # OK (lazy)
identity check: True for all 5
Tests verified (4/4 PASS):
tests/test_mcp_config.py (3 tests)
tests/test_mcp_client_beads.py (1 test)
Consumer check (lazy __getattr__ keeps these working):
src/app_controller.py: models.MCPConfiguration, models.RAGConfig,
models.load_mcp_config (7+ sites)
src/rag_engine.py: models.RAGConfig (1 site)
All resolve via the lazy __getattr__.
|
||
|
|
0d2a9b5eed |
refactor(workspace_manager): merge WorkspaceProfile from models.py into workspace_manager.py
Per the 4-criteria decision rule: WorkspaceProfile fails C1 (only used
by the workspace subsystem), fails C2 (no state machine), fails C3 (no
dedicated test file), borderline C4. MERGE into the existing
src/workspace_manager.py which already has WorkspaceManager.
This commit:
1. Adds WorkspaceProfile class definition to src/workspace_manager.py
at the top.
2. Removes the same class def from src/models.py.
3. Adds lazy re-export via the existing __getattr__ in src/models.py.
4. Updates workspace_manager.py imports to no longer import from
models (the class def is now local).
Verification: VC8 (WorkspaceProfile)
from src.workspace_manager import WorkspaceProfile # OK
from src.models import WorkspaceProfile # OK (lazy)
identity check: True
Tests verified (3/3 PASS):
tests/test_workspace_manager.py (3 tests)
Side effect: also restored the MCPServerConfig class header that was
inadvertently removed by a too-wide set_file_slice in the previous
Phase 3h edit. Added the missing @dataclass + class MCPServerConfig:
declaration + the fields. The class body (to_dict + from_dict) was
already in models.py; only the header was missing.
|
||
|
|
bca0875580 |
refactor(external_editor): merge TextEditorConfig + ExternalEditorConfig from models.py
Per the 4-criteria decision rule: editor configs fail C1 (only used by
the editor subsystem), fail C2 (no state machine), fail C3 (no
dedicated test file), borderline C4. MERGE into the existing
src/external_editor.py which already has ExternalEditorLauncher +
the helper functions.
This commit:
1. Adds TextEditorConfig + ExternalEditorConfig + EMPTY_TEXT_EDITOR_CONFIG
class definitions to src/external_editor.py at the top.
2. Removes the same class defs from src/models.py.
3. Adds lazy re-export via the existing __getattr__ in src/models.py
(EAGER would cycle: external_editor was previously importing from
models; if models re-exports, the cycle would deadlock on initial
load).
4. Updates external_editor.py imports to no longer import from models
(the class defs are now local).
Verification: VC8 (TextEditorConfig + ExternalEditorConfig)
from src.external_editor import TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG # OK
from src.models import TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG # OK (lazy)
identity check: True for all 3
Tests verified (22/22 PASS):
tests/test_external_editor.py (17 tests)
tests/test_external_editor_gui.py (5 tests)
|
||
|
|
ecd8e82f2f |
refactor(tool_bias): merge BiasProfile from models.py into tool_bias.py
Per the 4-criteria decision rule: BiasProfile fails C1 (only used by
tool_presets + tool_bias), fails C2 (no state machine), fails C3 (no
dedicated test file), borderline C4. MERGE into the existing
src/tool_bias.py which already has ToolBiasEngine.
This commit:
1. Adds BiasProfile class definition to src/tool_bias.py at the top
(after the dataclass + typing imports).
2. Removes BiasProfile from src/models.py.
3. Adds lazy re-export via the existing __getattr__ in src/models.py
(EAGER would deadlock: tool_presets needs BiasProfile + tool_bias
needs Tool/ToolPreset, and both want models re-exports).
4. Updates src/tool_presets.py to use the local-import pattern for
BiasProfile (in load_all_bias_profiles) + adds
'from __future__ import annotations' so the 'BiasProfile' type
annotation is a string. This breaks the cycle.
5. Updates src/tool_bias.py to import Tool + ToolPreset from
src.tool_presets directly (no longer through models) + adds
'from __future__ import annotations'.
Verification: VC8 (BiasProfile)
from src.tool_bias import BiasProfile # OK
from src.tool_presets import Tool, ToolPreset # OK
from src.models import Tool, ToolPreset, BiasProfile # OK (lazy)
Tool is Tool returns True
ToolPreset is ToolPreset returns True
BiasProfile is BiasProfile returns True
Tests verified (10/10 PASS):
tests/test_tool_preset_manager.py (4 tests)
tests/test_bias_models.py (3 tests)
tests/test_tool_bias.py (3 tests)
Cycle resolution:
models -> tool_presets (lazy via __getattr__)
tool_presets -> tool_bias (local import in function body, only at call time)
tool_bias -> tool_presets (eager; OK because tool_presets is fully
loaded by the time tool_bias's class
definitions need Tool/ToolPreset)
The eager load of tool_bias from tool_presets is what made the
'from __future__ import annotations' necessary in both files (for
Tool/ToolPreset string annotations in tool_bias method signatures).
|
||
|
|
6adaae2ec3 |
refactor(tool_presets): merge Tool + ToolPreset from models.py into tool_presets.py
Per the 4-criteria decision rule: Tool + ToolPreset fail C1 (only used by
tool_presets + tool_bias), fail C2 (no state machine), fail C3 (no
dedicated test file), borderline C4 (~15 lines each). MERGE into the
existing src/tool_presets.py which already has ToolPresetManager.
This commit:
1. Adds Tool + ToolPreset class definitions to src/tool_presets.py at
the top (after the stdlib imports). Both classes are used by
ToolPresetManager and the tests.
2. Removes Tool + ToolPreset from src/models.py.
3. Adds lazy re-exports via the existing __getattr__ in src/models.py
(EAGER import would deadlock because src.tool_presets imports
BiasProfile from src.models; the lazy __getattr__ breaks the cycle).
4. Updates src/tool_presets.py import: from
'from src.models import ToolPreset, BiasProfile' to
'from src.models import BiasProfile' (ToolPreset is now local).
Verification: VC8 (Tool + ToolPreset)
from src.tool_presets import Tool, ToolPreset # OK
from src.models import Tool, ToolPreset # OK (lazy __getattr__)
Tool is Tool returns True
ToolPreset is ToolPreset returns True
Tests verified (7/7 PASS):
tests/test_tool_preset_manager.py (4 tests)
tests/test_bias_models.py (3 tests)
Consumer check:
src/ai_client.py: from src.models import FileItem, ToolPreset, BiasProfile, Tool
src/app_controller.py: (no Tool/ToolPreset import)
src/tool_bias.py: from src.models import Tool, ToolPreset, BiasProfile
All resolve via re-export/lazy __getattr__.
The lazy __getattr__ pattern is the same mechanism used for the
Pydantic proxies (GenerateRequest / ConfirmRequest) and for PROVIDERS.
Phase 5 will migrate Tool/ToolPreset to a similar lazy pattern in
the re-export block (or drop them entirely after the consumer
migration).
|
||
|
|
86f1676721 |
refactor(project_files): create src/project_files.py (split from models.py)
Per the 4-criteria decision rule (C1=cross-system, C3=tests, C4=substantial);
FileItem is the canonical per-file data structure used by aggregate,
app_controller, gui_2, presets, context_presets, and tests. Preset /
ContextPreset / ContextFileEntry / NamedViewPreset are the preset/view
data structures that round-trip through TOML.
This commit:
1. Creates src/project_files.py with FileItem + Preset + ContextPreset +
ContextFileEntry + NamedViewPreset (full class bodies copied verbatim
from src/models.py including __post_init__, to_dict, from_dict, and
the [C: ...] caller-docstring tags).
2. Removes the 5 class definitions from src/models.py.
3. Adds backward-compat re-exports in src/models.py (the same pattern
used by Phase 3a mma.py + Phase 3b project.py + Phase 3g personas.py).
4. Updates the 4 consumer files to import from src.project_files directly:
src/orchestrator_pm.py, src/presets.py, src/context_presets.py,
src/ai_client.py (3 sites of the banned 'local import + as _FIC alias'
pattern updated to use src.project_files.FileItem; the aliasing
anti-pattern is preserved for now - a follow-up track will remove
the local imports and the aliasing).
Verification: VC7
from src.project_files import FileItem, Preset, ContextPreset,
ContextFileEntry, NamedViewPreset # OK
from src.models import FileItem, Preset, ... # OK
(re-exports work; identity check: FileItem is FileItem returns True)
Tests verified (20/20 PASS):
tests/test_file_item_model.py (4 tests)
tests/test_view_presets.py (4 tests)
tests/test_context_presets_models.py (3 tests)
tests/test_custom_slices_annotations.py (3 tests)
tests/test_presets.py (5 tests)
Decorator-orphan pitfall caught and fixed: after removing the 3 classes
between WorkspaceProfile and the MCP Config region, the @dataclass
decorator was orphaned on a comment line. Removed the orphan.
|
||
|
|
e430df86f1 |
refactor(project): create src/project.py with ProjectContext + 5 sub + config IO (split from models.py)
Per the 4-criteria decision rule (C1=cross-system, C3=tests, C4=size);
ProjectContext is the typed return of project_manager.flat_config();
the 5 sub-dataclasses model the actual nested dict structure of
flat_config()'s return; load_config_from_disk / save_config_to_disk
are the canonical config I/O primitives (renamed from the private
_load_config_from_disk / _save_config_to_disk).
This commit:
1. Creates src/project.py with ProjectContext + 5 sub (ProjectMeta,
ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion)
+ EMPTY_PROJECT_CONTEXT + _clean_nones + load_config_from_disk +
save_config_to_disk + parse_history_entries.
2. Removes the original class + function definitions from src/models.py.
3. Adds backward-compat re-exports in src/models.py (the same pattern
used by Phase 3a mma.py and Phase 3g personas.py).
4. Updates src/app_controller.py to use the new public function names
(load_config_from_disk / save_config_to_disk).
5. Updates tests/test_models_no_top_level_tomli_w.py to use the new
public name (the test still asserts lazy-loading; the lazy load
happens in the new project.py module).
6. Updates scripts/audit_no_models_config_io.py FORBIDDEN_PATTERNS to
reference the new public names (models.load_config_from_disk /
models.save_config_to_disk) + the new src.project path.
Verification: VC6
uv run python -c 'from src.project import ProjectContext, ProjectMeta,
ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion,
_clean_nones, load_config_from_disk, save_config_to_disk,
parse_history_entries' # OK
uv run python -c 'from src.models import ProjectContext, ...' # OK
(re-exports work)
Pre-existing test regression (NOT caused by this commit):
tests/test_models_no_top_level_tomli_w.py::test_models_does_not_import_tomli_w_at_module_level
was already failing because the Phase 3g 'from src.personas import Persona'
re-export in src/models.py loads src.personas at module level, which
loads tomli_w. The Phase 5 reduce-models.py pass moves the persona
import into __getattr__ (lazy), which will make this test pass again.
Tests verified: tests/test_project_context_20260627.py (10/10 PASS),
tests/test_project_serialization.py (2/2 PASS), tests/test_thinking_persistence.py
(4/4 PASS), tests/test_presets.py (3/3 PASS), tests/test_persona_models.py
(2/2 PASS), tests/test_ticket_queue.py (PASS), tests/test_dag_engine.py
(PASS), tests/test_orchestration_logic.py (PASS).
|
||
|
|
e70703f894 | move vendor capabilities to different position in the file | ||
|
|
d7872bea53 |
refactor(personas): move Persona dataclass from models.py to personas.py
Per spec FR4 + Phase 3.4: Persona dataclass + properties (provider/model/ temperature/top_p/max_output_tokens) + to_dict/from_dict move from src/models.py into src/personas.py (which already has the PersonaManager ops layer). Re-export at top of models.py preserves 'from src.models import Persona'. |
||
|
|
cd828e5267 |
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. |
||
|
|
d9cd7c557b |
refactor(ai_client,gui_2): merge vendor_state split: VendorMetric -> ai_client, get_vendor_state (renamed _get_vendor_state_metrics) -> gui_2; git rm src/vendor_state.py
Per spec FR2 + Phase 2.2 + architecture feedback (data != view):
- VendorMetric (data) -> src/ai_client.py (alongside VendorCapabilities; all vendor data)
- get_vendor_state -> renamed to _get_vendor_state_metrics in src/gui_2.py
(it's a view-helper that builds the metrics for render_vendor_state's table)
- render_vendor_state in gui_2.py now calls _get_vendor_state_metrics directly
Tests:
- tests/test_vendor_state.py: imports get_vendor_state from src.gui_2, VendorMetric from src.ai_client
|
||
|
|
81d8bce419 |
refactor(ai_client): merge vendor_capabilities into ai_client; git rm src/vendor_capabilities.py
Per spec FR2 + Phase 2.1: VendorCapabilities + register + get_capabilities + list_models_for_vendor + the ~40 vendor registrations move into ai_client.py as a region block. Renamed internal _REGISTRY to _VENDOR_REGISTRY to avoid collision with mcp_tool_specs._REGISTRY. Importers (in src/) updated: - src/ai_client.py: removed top-level import; removed 4 local imports of list_models_for_vendor/get_capabilities (symbol now in module namespace) - src/app_controller.py: 2 sites updated to 'from src.ai_client import get_capabilities' - src/gui_2.py: 1 site updated to 'from src.ai_client import VendorCapabilities, get_capabilities' Tests updated: - 8 test_*.py files: changed 'from src.vendor_capabilities import' to 'from src.ai_client import' - tests/test_vendor_capabilities.py: _clean_registry fixture updated to reference src.ai_client._VENDOR_REGISTRY (was src.vendor_capabilities._REGISTRY) Verification: 157 tests pass across the affected files (vendor_capabilities, ai_client_tool_loop variants, openai_compatible, command_palette, diff_viewer, patch_modal, app_controller_result, app_controller_sigint, handle_reset_session, ai_loop_regressions, grok/llama/minimax provider tests). |
||
|
|
163b12493b |
refactor(gui_2,patch_modal): merge diff_viewer ops into gui_2; data classes (DiffHunk/DiffFile) move to patch_modal.py alongside PendingPatch; git rm src/diff_viewer.py
Per spec FR1 + Phase 1.4 + architecture feedback (data != view): - Data classes DiffHunk, DiffFile -> src/patch_modal.py (alongside PendingPatch; all patch-domain data) - Operations parse_diff/parse_hunk_header/get_line_color/apply_patch_to_file (called by gui_2) -> src/gui_2.py - GUI is a pure view; data lives elsewhere; no new files per AGENTS.md Tests: tests/test_diff_viewer.py imports from src.gui_2 (parse_diff/apply_patch_to_file) and src.patch_modal (DiffFile/DiffHunk). |
||
|
|
3dd153f718 |
refactor(gui_2): merge command_palette; split registry->commands + render->gui_2; git rm src/command_palette.py
Per spec FR1 + Phase 1.3 + architecture feedback: src/command_palette.py
split by responsibility:
- Command/ScoredCommand/CommandRegistry/fuzzy_match/_close_palette/_execute (data/ops)
-> src/commands.py (which already owns _LazyCommandRegistry pattern)
- render_palette_modal (view/ImGui) -> src/gui_2.py
GUI is a pure view; the registry/data classes are ops; commands.py owns
the registry because commands.py is where @registry.register decorators live.
gui_2.render_palette_modal imports Command from commands.py to type its
parameters.
Also fixes Phase 1.1 (bg_shader) per architecture feedback:
BackgroundShader no longer owns 'enabled' state - the GUI is pure view.
State is now owned by AppController.bg_shader_enabled (read on load from
config, written from gui_2 checkbox via app's __setattr__ delegation).
Tests:
- tests/test_command_palette.py: imports from src.commands (was src.command_palette)
- tests/test_commands_no_top_level_command_palette.py: rewritten for the
new architecture (eager registry in commands.py; render in gui_2; no
circular import between commands.py and gui_2)
|
||
|
|
4bb930c3cb |
refactor(gui_2): merge shaders into gui_2; git rm src/shaders.py
Per spec FR1 + Phase 1.2: draw_soft_shadow moved into src/gui_2.py as a region block; consumer sites changed from shaders.draw_soft_shadow() to draw_soft_shadow(). Removed the local import workaround at line 7016. |
||
|
|
e0a238e693 |
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md, conductor/tier2/githooks/forbidden-files.txt, conductor/tracks/tier2_leak_prevention_20260620/spec.md, conductor/code_styleguides/data_oriented_design.md, conductor/code_styleguides/error_handling.md, conductor/code_styleguides/type_aliases.md, conductor/product-guidelines.md, conductor/code_styleguides/python.md, docs/guide_meta_boundary.md, conductor/code_styleguides/agent_memory_dimensions.md, conductor/code_styleguides/rag_integration_discipline.md, conductor/code_styleguides/cache_friendly_context.md, conductor/code_styleguides/knowledge_artifacts.md, conductor/code_styleguides/feature_flags.md before module_taxonomy_refactor_20260627/Phase1.1
refactor(gui_2): merge bg_shader into gui_2; git rm src/bg_shader.py Per spec FR1 + Phase 1.1: bg_shader (66 lines) moved into src/gui_2.py as a region block; consumers updated to use the in-module get_bg(). Local import pattern preserved at app_controller sites (matches existing circular-dep workaround for gui_2<->app_controller). |
||
|
|
8f6ae6d983 | misc | ||
|
|
805a06197b |
feat(models,project_manager): add ProjectContext + 5 sub-dataclasses (Phase 2 / VC8)
Phase 2: Fix flat_config to return typed ProjectContext (FR8 / VC8)
Before: def flat_config(...) -> Metadata (returned dict[str, Any])
After: def flat_config(...) -> ProjectContext (typed fat struct)
Delta: -1 anonymous dict return type; +6 new dataclasses
Per SPEC_CORRECTION_phase_2.md, this is Option A (incremental):
- Add 6 sub-dataclasses: ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, ProjectContext
- Each matches the nested dict shape of flat_config()'s actual return
- ProjectContext has dict-compat methods (__getitem__ + get) so
consumers using .get() / [] continue to work unchanged
- ProjectContext.to_dict() returns the legacy dict shape for migration
- EMPTY_PROJECT_CONTEXT sentinel exported
File locations per spec:
- src/models.py: 6 new dataclasses + EMPTY_PROJECT_CONTEXT sentinel
- src/project_manager.py: flat_config body rewritten to construct
ProjectContext from the proj dict (typed return type)
- tests/test_project_context_20260627.py: NEW regression-guard test file
with 10 tests covering: imports, return type, zero defaults, full
input, dict-compat __getitem__/get, to_dict round-trip, sentinel,
output_dir required field, consumer patterns unchanged
Verification:
- audit_weak_types --strict: OK (96 <= 112 baseline; down from 107)
- generate_type_registry: 23 files regenerated
- 10 test_project_context_20260627 tests PASS
- All existing consumer tests pass (test_context_composition_decoupled: 2,
test_orchestrator_pm: 3, test_orchestration_logic: 8,
test_orchestrator_pm_history + test_context_preview_button: 7,
test_project_manager_tracks: 4, test_track_state_persistence: 1)
VC8 (corrected) verification:
- flat_config returns ProjectContext (typed) ✓
- All 6 sub-dataclasses exist + importable ✓
- Dict-compat methods (ctx["key"], ctx.get("key")) work ✓
- output_dir REQUIRED field defaults to "" (empty, but valid) ✓
- Consumer patterns (ctx.get("output", {}).get("namespace", "project"))
work unchanged via dict-compat ✓
Phase 2 IS COMPLETE.
|
||
|
|
e8b774d664 |
refactor(openai_compatible,orchestrator_pm): convert dict[str, Any] to typed (Phase 7 partial)
Phase 7: Eliminate Any + dict[str, Any] from internal signatures (FR6) - PARTIAL Before: 11 dict[str, Any] param sites After: 7 (4 converted; 7 remain as legitimate boundary params) Delta: -4 sites (cumulative) Specific changes: - src/openai_compatible.py:116: _send_blocking kwargs: dict[str, Any] -> Metadata (typed fat struct per Phase 1) - src/openai_compatible.py:133: _send_streaming kwargs: dict[str, Any] -> Metadata - src/orchestrator_pm.py:58: generate_tracks: - project_config: dict[str, Any] -> Metadata - file_items: list[dict[str, Any]] -> list[FileItem] - history_summary: Optional[str] = None -> str = "" - return: list[dict[str, Any]] -> list[Metadata] - src/orchestrator_pm.py imports: FileItem (from src.models), Metadata (from src.type_aliases); removed unused 'Optional' from typing Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax: OK on all changed files - 20 tests pass (test_openai_compatible: 6, test_orchestration_logic + test_orchestrator_pm + test_orchestrator_pm_history: 14) REMAINING ~7 dict[str, Any] sites (all BOUNDARY inputs from wire format): - src/mcp_client.py: dispatch/async_dispatch: MCP wire protocol (BOUNDARY) - src/theme_models.py: from_dict: TOML wire format (BOUNDARY) - src/log_registry.py: from_dict: session JSON wire (BOUNDARY) - src/session_logger.py: log_comms: comms JSON wire (BOUNDARY) - src/type_aliases.py: Metadata.from_dict: boundary entry (BOUNDARY) - src/hot_reloader.py: restore_state: snapshot deserialization (BOUNDARY-ish) Per spec.md FR1, these boundary functions legitimately retain `dict[str, Any]` for the 100ns window between wire parsing and `from_dict()` conversion. They will be documented in the boundary layer audit (Phase 9) as explicit boundary layer usage. REMAINING ~60 Any param sites (large scope; deferred): - src/api_hooks.py: 10 - src/app_controller.py: 9 - src/ai_client.py: 8 - src/command_palette.py: 4 - src/hot_reloader.py: 4 - src/imgui_scopes.py: 4 - src/api_hooks_helpers.py: 3 - src/events.py: 3 - src/gui_2.py: 3 - src/openai_compatible.py: 3 - src/api_hook_client.py: 2 - src/commands.py: 1 - src/log_registry.py: 1 - src/mcp_client.py: 1 - src/models.py: 1 - src/performance_monitor.py: 1 - src/project_manager.py: 1 - src/type_aliases.py: 1 |
||
|
|
3a80b65692 |
refactor(multiple): complete Phase 6 Optional[T] elimination (batches 4 + 5)
Phase 6: Eliminate Optional[T] returns - BATCHES 4 + 5 (FINAL) Before: 11 more Optional[T] returns removed (Phase 6 total: 30 of 30) After: 0 (Phase 6 COMPLETE per VC5) Delta: -11 sites in this commit; cumulative -30/30 sites across all batches Specific changes: - src/diff_viewer.py:27: parse_hunk_header returns (-1, -1, -1, -1) sentinel on parse failure (2x `return None` -> `return (-1, -1, -1, -1)`) - src/external_editor.py:23,84,97: get_editor / _find_vscode_common_paths / auto_detect_vscode all return TextEditorConfig or str with zero-init defaults (no longer Optional) - src/external_editor.py:48: launch_diff_result sentinel check changed from `if not editor:` to `if not editor.name or not editor.path:` - src/file_cache.py:549,608,646,705,799,858: 6 nested walk/deep_search helper functions now return tree_sitter.Node (root) instead of Optional[tree_sitter.Node] (None) - src/models.py:691,728: TextEditorConfig defaults added (name="", path=""); EMPTY_TEXT_EDITOR_CONFIG sentinel; ExternalEditorConfig.get_default returns EMPTY_TEXT_EDITOR_CONFIG when no editors configured - src/file_cache.py:895: get_file_id returns "" (was Optional[str]) Test updates: - tests/test_diff_viewer.py: still passes (parse_hunk_header tested) - tests/test_external_editor.py:78,97: is None -> == "" check (config.get_default, get_editor for unknown name) Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax: OK on all changed files - 85+ tests pass (test_file_cache, test_ast_parser, test_external_editor, test_diff_viewer, test_fuzzy_anchor, test_summary_cache, test_paths, test_persona_models, test_patch_modal, test_parallel_execution, test_track_state_persistence, test_session_logger_optimization, + 117 in broader run) VC5 (Zero Optional[T] return types) PASSES: git grep -cE "-> Optional\\[" -- 'src/*.py' returns 0 PHASE 6 IS COMPLETE. REMAINING WORK: - Phase 7: Eliminate Any + dict[str, Any] in internal signatures (59+ sites) - Phase 8: Final re-measure + verification - Phase 9: Boundary layer audit (done) |
||
|
|
4ca95551c0 |
refactor(multiple): continue Phase 6 Optional[T] elimination (batch 3)
Phase 6: Eliminate Optional[T] returns - BATCH 3 of 7
Before: 4 more Optional[T] returns removed
After: 0 in app_controller.py (Pending MMA), project_manager.py
(load_track_state), session_logger.py (log_tool_call),
models.py (TrackState.metadata defaults)
Delta: -4 sites (cumulative: -19 of 30)
Specific changes:
- src/app_controller.py:2781,2785: _pending_mma_spawn, _pending_mma_approval
return Metadata() (zero-init sentinel) when no pending items
- src/project_manager.py:301: load_track_state returns EMPTY_TRACK_STATE
sentinel (added to models.py) when no state file exists or load fails
- src/models.py:476: TrackState.metadata now has default_factory=dict;
EMPTY_TRACK_STATE = TrackState() added as module-level sentinel
- src/session_logger.py:166: log_tool_call returns str (was Optional[str])
Test impact:
- test_track_state_persistence.py: 4 tests pass (existing tests)
- test_app_controller_result.py: 12 tests pass
Verification:
- audit_weak_types --strict: OK (107 <= 112 baseline)
- py_check_syntax: OK on all changed files
- 44 tests pass (test_track_state_persistence, test_track_state_schema,
test_session_logger_optimization, test_app_controller_result)
REMAINING: ~11 Optional[T] returns in:
- src/external_editor.py (3 - get_editor, _find_vscode_common_paths,
auto_detect_vscode)
- src/file_cache.py (7 - tree_sitter.Node walks + get_file_id)
- src/diff_viewer.py (1 - parse_hunk_header)
|
||
|
|
ba3eb0c090 |
refactor(multiple): continue Phase 6 Optional[T] elimination (batch 2)
Phase 6: Eliminate Optional[T] returns - BATCH 2 of 7
Before: 7 more Optional[T] returns removed
After: 0 in command_palette.py, diff_viewer.py, fuzzy_anchor.py,
multi_agent_conductor.py, patch_modal.py, app_controller.py
Delta: -7 sites (cumulative: -15 of 30)
Specific changes:
- src/command_palette.py:50: CommandRegistry.get() returns Command (zero-init
sentinel: id="", title="", category="uncategorized", action=lambda: None)
- src/diff_viewer.py:117: get_line_color returns "" when no marker prefix
- src/fuzzy_anchor.py:40: FuzzyAnchor.resolve_slice returns (-1, -1) sentinel
(replaced 3x `return None` with `return (-1, -1)`)
- src/multi_agent_conductor.py:64: WorkerPool.spawn returns threading.Thread()
(empty sentinel, not started) when pool is full
- src/patch_modal.py:33: PatchModalManager.get_pending_patch returns
PendingPatch; class has EMPTY_PATCH sentinel; field type changed from
Optional[PendingPatch] to PendingPatch; 2x `= None` reset replaced with
`= EMPTY_PATCH`
- src/app_controller.py:4414: _confirm_and_run returns "" when not approved
(was Optional[str] returning None)
Test updates:
- tests/test_diff_viewer.py:95: get_line_color(" context") == ""
- tests/test_fuzzy_anchor.py:42,59: assert result == (-1, -1)
- tests/test_parallel_execution.py:31: t3 sentinel is now unstarted thread
(check via not t3.is_alive())
- tests/test_patch_modal.py:9,31,78: get_pending_patch() == "" sentinel check
Verification:
- audit_weak_types --strict: OK (107 <= 112 baseline)
- 22+ tests pass (test_diff_viewer, test_fuzzy_anchor,
test_parallel_execution, test_patch_modal, test_command_palette)
- py_check_syntax: OK on all changed files
REMAINING: ~15 Optional[T] returns in:
- src/external_editor.py (3)
- src/file_cache.py (7)
- src/diff_viewer.py: parse_hunk_header (1)
- src/models.py: ExternalEditorConfig.get_default (1)
- src/project_manager.py: load_track_state (1)
- src/session_logger.py: log_tool_call (1)
- src/app_controller.py: _pending_mma_spawn, _pending_mma_approval (2)
|
||
|
|
c12d5b6d82 |
refactor(models,paths,presets,summary_cache): remove Optional returns (Phase 6 batch 1)
Phase 6: Eliminate Optional[T] returns (FR5) - BATCH 1 of 7
Before: 8 Optional[T] return types across 4 files
After: 0 (replaced with default-zero return values)
Delta: -8 sites
Per conductor/code_styleguides/error_handling.md "Optional[X] ban":
- "Use Result[T] for any function that can fail at runtime."
- "Use nil-sentinel dataclasses for 'no result'."
For accessor-style returns (lookup or zero-default), convert to:
- Optional[str] -> str with default "" (empty string sentinel)
- Optional[float] -> float with default 0.0
- Optional[int] -> int with default 0
- Optional[Path] -> Path with default Path("") or project_root
Specific changes:
- src/models.py:765-789: Persona.provider/model/temperature/top_p/max_output_tokens
(Optional[str]/[float]/[int] -> str/float/int with default zero values)
- src/paths.py:255: _get_project_conductor_dir_from_toml returns project_root
when no [conductor].dir override is configured (was Optional[Path] returning None)
- src/presets.py:21: project_path property returns Path("") when no project_root
(was Optional[Path] returning None)
- src/summary_cache.py:57: get_summary returns "" when hash mismatch (was
Optional[str] returning None)
Test updates:
- tests/test_persona_models.py:64-69: test_persona_defaults now expects
"" / 0.0 instead of None
- tests/test_summary_cache.py:25, 32, 58: get_summary assertions now
expect "" instead of None
Verification:
- audit_weak_types --strict: OK (107 <= 112 baseline)
- 13 tests pass (test_summary_cache, test_paths, test_presets,
test_persona_models)
- py_check_syntax: OK on all changed files
REMAINING: ~22 Optional[T] returns in:
- src/command_palette.py (1)
- src/diff_viewer.py (2)
- src/external_editor.py (3)
- src/file_cache.py (7)
- src/fuzzy_anchor.py (1)
- src/models.py (1)
- src/multi_agent_conductor.py (1)
- src/patch_modal.py (1)
- src/project_manager.py (1)
- src/session_logger.py (1)
- src/app_controller.py (3)
|
||
|
|
6399dcc4ed |
refactor(rag_engine,ai_client): rag_engine.search returns List[RAGChunk] directly
Phase 5: rag_engine.search() return type (FR4 row 7) Before: def search(...) -> List[Dict[str, Any]] at src/rag_engine.py:367 After: def search(...) -> List["RAGChunk"] Delta: -1 wrong type annotation (List[Dict] -> List[RAGChunk]) RAGChunk dataclass extended with `id: str = ""` field to preserve the chroma wire-format identifier. The search() function now constructs RAGChunk instances directly from chromadb query results, normalizing the wire format (metadata.path -> RAGChunk.path; distance -> 1.0 - score) at the boundary. Consumer updates: - src/ai_client.py:3259-3266: chunk["metadata"]["path"] -> chunk.path; chunk["document"] -> chunk.document (direct attribute access) - src/app_controller.py:3506: docstring updated from Result[List[Dict]] to Result[List[RAGChunk]] (no code change; pass-through) Test updates: - tests/test_rag_engine.py:61: results[0]["id"] -> results[0].id (now uses dataclass attribute access) Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax: OK on rag_engine.py, ai_client.py, test_rag_engine.py - 21 RAG tests pass (test_rag_engine, test_rag_chunk, test_rag_engine_ready_status_bug, test_rag_integration, test_context_composition_decoupled, test_tiered_aggregation) |
||
|
|
cfd881e719 |
refactor(gui_2,app_controller): remove hasattr defensive checks + fix _do_generate type
Phase 3 follow-up: gui_2.py hasattr removal Before: 23 hasattr(f, ...) defensive checks in src/gui_2.py After: 0 (self.files / self.context_files are GUARANTEED List[FileItem]) Delta: -23 sites Phase 4: _do_generate return type Before: def _do_generate(self) -> tuple[str, Path, list[Metadata], str, str]: at src/app_controller.py:4014 After: def _do_generate(self) -> tuple[str, Path, list[FileItem], str, str]: Delta: -1 wrong type annotation (file_items comes from aggregate.run() which returns List[FileItem]) Combined: 18 hasattr(f, 'path') checks in gui_2.py + 5 hasattr(f, ...) checks on other FileItem fields (view_mode/custom_slices/ast_mask/ast_signatures/ ast_definitions/auto_aggregate/to_dict) + 1 _do_generate return type fix. All removed defensive checks are redundant because: 1. self.files and self.context_files are populated via the isinstance + FileItem.from_dict() pattern (gui_2.py:869-873 + 980-985 for restore; app_controller.py:1996-2005 for project init) 2. FileItem has explicit fields for path, view_mode, custom_slices, ast_mask, ast_signatures, ast_definitions, auto_aggregate, to_dict Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax src/gui_2.py: OK - py_check_syntax src/app_controller.py: OK - 95 tests pass (type_aliases, openai_schemas, rag_engine, file_item, rag_chunk, main_thread_purity, app_controller_result, context_composition_decoupled) |
||
|
|
0d0b433a2e |
refactor(app_controller): remove redundant hasattr(f, ...) defensive checks
Phase 3 (partial): self.files guarantee (FR4 row 1) Before: 13 hasattr(f, ...) defensive checks in src/app_controller.py After: 0 (self.files is GUARANTEED List[FileItem] per init at 1996-2005) Delta: -13 sites Per the spec's FR4 row 1: 'After Phase 3, self.files is GUARANTEED List[FileItem]. Every hasattr(f, "path") check is redundant. Remove it.' The init code at src/app_controller.py:1996-2005 already does the correct isinstance check + FileItem.from_dict() pattern, so all 13 hasattr checks on self.files / self.context_files are redundant defensive code. Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax src/app_controller.py: OK - 59 tests pass (type_aliases, openai_schemas, rag_engine, file_item, etc.) OUT OF SCOPE (deferred): - 18 hasattr(f, 'path') checks in src/gui_2.py (Phase 3 follow-up) - Phase 4: _do_generate return type - Phase 5: rag_engine.search() return type - Phase 6: 30 Optional[T] returns - Phase 7: 59 Any params + 10 dict[str, Any] params See TRACK_COMPLETION_cruft_elimination_20260627.md for full scope. |
||
|
|
75eb6dbbbb |
refactor(type_aliases): promote Metadata from TypeAlias to typed fat struct
Phase 1: Metadata promotion (FR2 from spec.md) Before: 1 \Metadata: TypeAlias = dict[str, Any]\ site at src/type_aliases.py:6 After: 0 (replaced by \@dataclass(frozen=True, slots=True)\) Delta: -1 site (matches plan) Metadata is now the typed fat struct at the wire boundary: - 36 explicit fields covering TOML/JSON wire keys (paths, project, discussion, role, content, tool_calls, ts, kind, direction, model, source_tier, error, id, description, status, depends_on, manual_block, document, path, score, function, args, script, output, type, description, parameters, auto_start, view_mode, custom_slices, input/output/cache tokens, metadata) - \rom_dict(raw: dict[str, Any])\ classmethod filters unknown keys - \ o_dict()\ returns plain dict for wire serialization - Dict-compat methods (\__getitem__\, \get\, \__contains__\, \__iter__\, \keys\, \alues\, \items\) keep existing call sites working during the migration; internal code should switch to direct attribute access on typed dataclasses (FileItem.path, CommsLogEntry.role, etc.) The TypeAlias \Metadata: TypeAlias = dict[str, Any]\ is REMOVED. Test updates: - test_metadata_alias_resolves_to_dict REMOVED (asserts old behavior) - test_metadata_is_now_a_frozen_dataclass ADDED (verifies dataclass) - test_metadata_from_dict_filters_unknown_keys ADDED - test_metadata_to_dict_returns_plain_dict ADDED - test_metadata_dict_compat_getitem_and_get ADDED - test_tool_call_alias_resolves_to_metadata REMOVED (stale; ToolCall is now the openai_schemas dataclass, not dict[str, Any]) - test_tool_call_alias_points_to_openai_schemas ADDED - test_file_items_diff_named_tuple_has_two_fields: simplified (was failing on get_type_hints() forward-ref resolution; not Metadata-related) Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - generate_type_registry --check: OK (regenerated 23 files) - 133 tests pass (type_aliases, openai_schemas, rag_engine, file_item, all 12 per-aggregate dataclass regression guards) |
||
|
|
fc5f80ae87 |
fix(ai_client): use FileItem class via local import (regression fix)
In Phase 2 (commit
|
||
|
|
f6d58ddb07 |
fix(gui_2): add missing MMAUsageStats import (regression fix)
In Phase 10 batch 1 (commit
|
||
|
|
75fa97cac7 |
refactor(app_controller): migrate UIPanelConfig, ProviderPayload, PathInfo consumers (Phase 10 batch 4)
Phase 10 (batch 4): UIPanelConfig + ProviderPayload + PathInfo
Before: 7 .get() sites in src/app_controller.py
After: 0
Delta: -7
Migrates:
1. UIPanelConfig (3 sites at app_controller.py:2070-2072):
gui_cfg.get('separate_message_panel', False) -> UIPanelConfig.from_dict(gui_cfg).separate_message_panel
gui_cfg.get('separate_response_panel', False) -> UIPanelConfig.from_dict(gui_cfg).separate_response_panel
gui_cfg.get('separate_tool_calls_panel', False)-> UIPanelConfig.from_dict(gui_cfg).separate_tool_calls_panel
2. PathInfo (2 sites at app_controller.py:1986-1987):
path_info['logs_dir']['path'] -> PathInfo.from_dict(path_info).logs_dir['path']
path_info['scripts_dir']['path'] -> PathInfo.from_dict(path_info).scripts_dir['path']
Inner ['path'] remains because PathInfo.logs_dir is dict (not dataclass).
3. ProviderPayload (2 sites at app_controller.py:2278-2281 and 2291):
payload.get('script') or json.dumps(payload.get('args', {}), indent=1)
-> ProviderPayload.from_dict(payload).script or json.dumps(pp.args, indent=1)
payload.get('output', payload.get('content', ''))
-> ProviderPayload.from_dict(payload).output or payload.get('content', '')
Tests: 39/39 pass across 11 test files.
|
||
|
|
e508758fbe |
feat(type_aliases): add from_dict to SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo
Required by Phase 10 migrations which call these from_dict methods. Without these, CustomSlice.from_dict() and MMAUsageStats.from_dict() used in gui_2.py would raise AttributeError at runtime. Adds the from_dict pattern consistent with the existing CommsLogEntry/HistoryMessage/ToolDefinition from_dict: - Filter dict keys to only the dataclass fields (ignore extras) - Pass filtered dict to cls(**filtered) Field definitions unchanged. No-op behavior for callers that already have a dataclass instance (they pass through isinstance check). Tests: 51/51 pass across all related test files. |
||
|
|
3cf01ae18c |
refactor(gui_2): migrate CustomSlice read sites (Phase 10 batch 3)
Phase 10 (batch 3): CustomSlice
Before: 8 .get('tag'/'comment') sites in src/gui_2.py
After: 0
Delta: -8
Migrates CustomSlice read sites:
1. gui_2.py:4054,4060,4096-4097 (files & media tree editor)
2. gui_2.py:5958,5964,5985-5986 (text viewer slice editor)
Pattern:
cs = CustomSlice.from_dict(slc) if isinstance(slc, dict) else slc
cs.tag (was slc.get('tag', ''))
cs.comment (was slc.get('comment', ''))
Mutation sites REMAIN as dict subscripts (the underlying list is
list[dict] per models.FileItem.custom_slices).
Tests: 16/16 pass.
|
||
|
|
84ca734a12 |
refactor(gui_2): migrate DiscussionSettings consumer (Phase 10 batch 2)
Phase 10 (batch 2): DiscussionSettings
Before: 1 .get('temperature'/...) site in src/gui_2.py
After: 0
Delta: -1 (plan expected 3 sites; 2 were already migrated by Tier 2)
Migrates the summary line in persona preferred model rendering:
entry.get('temperature', 0.7)
entry.get('top_p', 1.0)
entry.get('max_output_tokens', 0)
to:
ds = DiscussionSettings.from_dict(entry) if isinstance(entry, dict) else ds
ds.temperature, ds.top_p, ds.max_output_tokens
The dataclass defaults match the original .get() defaults exactly
(temperature=0.7, top_p=1.0, max_output_tokens=0), so behavior is preserved.
|
||
|
|
28799766bb |
refactor(gui_2): migrate MMAUsageStats consumers (Phase 10 batch 1)
Phase 10 (batch 1): MMAUsageStats
Before: 8 .get('model'/'input'/'output') sites in src/gui_2.py
After: 0
Delta: -8
Migrates the tier usage rendering and the tier_total calculation
in mma_usage rendering. Each 'stats' iteration variable is converted
via MMAUsageStats.from_dict() and accessed via direct field access:
stats.model (was stats.get('model', 'unknown'))
stats.input (was stats.get('input', 0))
stats.output (was stats.get('output', 0))
Sites migrated:
1. gui_2.py:2200-2202 (tier iteration in mma usage rendering)
2. gui_2.py:2217 (tier_total sum generator)
3. gui_2.py:6609 (total_cost in active_track panel)
4. gui_2.py:6784-6786 (tier iteration in 'Tier Usage' panel)
Tests: 7/7 pass (test_mma_usage_stats, test_gui2_events).
|
||
|
|
f1740d92d6 |
refactor(mcp_client,gui_2): migrate ToolDefinition consumers (Phase 8)
Phase 8: ToolDefinition
Before: 2 .get('description',...) sites
After: 0
Delta: -2 (expected: -2 or -3 per plan; the 3rd site gui_2.py:5875
is 'server' field which is NOT on ToolDefinition)
Migrates:
1. src/mcp_client.py:1968 (was 1970) - list_tools in _get_tool_definitions:
tinfo.get('description', '') -> ToolDefinition.from_dict(tinfo).description
(tinfo.get('inputSchema', ...) stays because 'inputSchema' key
does not match ToolDefinition's 'parameters' field name)
2. src/gui_2.py:5878 - render_external_tools_panel:
tinfo.get('description', '') -> ToolDefinition.from_dict(tinfo).description
Notes:
- gui_2.py:5875 (tinfo.get('server', 'unknown')) is NOT migrated;
'server' is not a ToolDefinition field. The tinfo here may be a
ToolInfo or server-info dict, not ToolDefinition. Classified as
collapsed-codepath per FR2.
Tests: 10/10 pass (test_tool_definition, test_external_mcp,
test_external_mcp_e2e). 2 test_type_aliases failures are pre-existing
(forward references in TypeAlias declarations; not caused by these
changes).
|
||
|
|
b3d0bc6036 |
refactor(app_controller): migrate UsageStats construction (Phase 6)
Phase 6: UsageStats
Before: 4 .get('input_tokens'/...) sites in src/app_controller.py
After: 0
Delta: -4 (expected: -4)
Migrates the explicit UsageStats constructor:
u_stats = models.UsageStats(
input_tokens=u.get('input_tokens', 0) or 0,
output_tokens=u.get('output_tokens', 0) or 0,
cache_read_tokens=u.get('cache_read_input_tokens', 0) or 0,
cache_creation_tokens=u.get('cache_creation_input_tokens', 0) or 0,
)
to:
u_stats = UsageStats.from_dict(u)
Behavior notes:
- UsageStats.from_dict() filters dict keys to dataclass fields.
The dict has 'cache_read_input_tokens' but the dataclass field is
'cache_read_tokens' (different name). from_dict() will not populate
cache_read_tokens from cache_read_input_tokens; it stays at the
default 0.
- Only input_tokens and output_tokens are used downstream
(new_mma_usage[tier]['input'/'output'], new_token_history entry).
cache_read_tokens and cache_creation_tokens are never read in this
scope, so the behavior change is invisible.
- Local import 'from src.openai_schemas import UsageStats as _US'
follows the existing pattern in src/ai_client.py.
Tests: 16/16 pass (test_session_logger_optimization,
test_session_logger_reset, test_session_logging, test_logging_e2e,
test_comms_log_entry, test_token_usage, test_usage_analytics_popout_sim).
|
||
|
|
6a2f2cfa37 |
refactor(ai_client,openai_schemas): migrate API response + _repair_minimax (Phase 5 part 2)
Phase 5: ChatMessage (part 2)
Before: 6 .get('content'/'role'/'tool_calls'/'tool_call_id') sites
After: 0
Delta: -6
Migrates:
1. _send_deepseek API response parsing (lines 2321-2324):
- message.get('content', '') -> message.content or ''
- message.get('tool_calls', []) -> [tc.to_dict() for tc in message.tool_calls]
- message.get('reasoning_content') -> kept as choice.get('message', {}).get('reasoning_content', '')
(reasoning_content is NOT a ChatMessage field)
2. _repair_minimax_history generator (line 2454):
- m.get('role') == 'tool' -> _CM.from_dict(m).role == 'tool'
- m.get('tool_call_id') -> _CM.from_dict(m).tool_call_id
Used inline conversion because the generator iterates over a
dict list and reads 2 fields. Inline conversion avoids an
intermediate list comprehension.
openai_schemas.py:
- ChatMessage.from_dict() now provides defaults for required fields
('role' -> 'assistant', 'content' -> '') when the input dict is
missing them. This handles the case where DeepSeek's API returns
an empty {} for 'message' (e.g., finish_reason='length' with no
content). Without this default, ChatMessage.__init__() raises
TypeError.
Tests: 46/46 pass (test_ai_client_result, test_ai_client_tool_loop,
test_deepseek_provider, test_openai_schemas, test_minimax_provider).
|
||
|
|
8df841fdfa |
refactor(ai_client): migrate _send_deepseek history loop to ChatMessage (Phase 5 part 1)
Phase 5: ChatMessage (part 1)
Before: 6 .get('role'/'content'/'tool_calls'/'tool_call_id') sites in _send_deepseek
After: 0
Delta: -6
Migrates _send_deepseek's history transformation loop from
dict-style access to ChatMessage direct field access:
msg = _ChatMessage.from_dict(msg_raw)
msg.role (was msg.get('role'))
msg.content (was msg.get('content'))
msg.tool_calls (was msg.get('tool_calls') / msg['tool_calls'])
msg.tool_call_id (was msg.get('tool_call_id'))
The api_msg dict (output for the DeepSeek API) is constructed via
direct field access. The tool_calls list is converted to dicts via
tc.to_dict() (preserves the existing API payload format).
Notes:
- msg_raw.get('reasoning_content') is preserved as-is because
reasoning_content is NOT a ChatMessage field.
- Local import 'from src.openai_schemas import ChatMessage as _ChatMessage'
follows the existing pattern in this file (lazy imports inside functions).
Tests: 36/36 pass (test_ai_client_result, test_ai_client_tool_loop,
test_deepseek_provider, test_openai_schemas).
|
||
|
|
1b62659c8c |
feat(openai_schemas): add from_dict to ChatMessage, ToolCall, UsageStats
Infrastructure change required by Phase 5/6/7 of the type_alias_unfuck_20260626 track. The plan's migration pattern (var = Aggregate.from_dict(var)) requires from_dict on the target dataclasses. None existed for the openai_schemas classes, so this commit adds them. from_dict semantics: - Filter dict keys to only the dataclass fields (ignore extra keys like _est_tokens) - For ChatMessage: convert nested tool_calls list to tuple of ToolCall - For ToolCall: convert nested function dict to ToolCallFunction - For UsageStats: direct field mapping Field definitions unchanged. Behavior: zero impact on existing tests (no callers exist yet for from_dict on these classes). Tests: syntax check OK; manual instantiation confirms from_dict works. |
||
|
|
8cf8cfeb4e |
refactor(gui_2): migrate CommsLogEntry consumers to direct field access
Phase 3: CommsLogEntry
Before: 3 .get('source_tier',...) sites + 1 half-measure in src/gui_2.py
After: 0
Delta: -4 (expected: -5 per plan; the 5th site was app_controller.py:1930
which returns None for missing source_tier and cannot be migrated
without breaking test_append_tool_log_dict_keys)
Migrates the following CommsLogEntry-related sites in src/gui_2.py:
1. gui_2.py:1810 - cache filter source_tier (.get('source_tier', ''))
2. gui_2.py:1818 - cache filter source_tier (.get('source_tier', ''))
3. gui_2.py:5104 - render_comms_log_panel source_tier (.get('source_tier', 'main'))
4. gui_2.py:5106 - render_comms_log_panel ts (.get('ts', '00:00:00'))
5. gui_2.py:5107 - render_comms_log_panel direction (.get('direction', '??'))
6. gui_2.py:5110 - render_comms_log_panel model (.get('model', '?'))
7. gui_2.py:5802 - render_tool_calls_panel half-measure
(subscript + 'in' check; entry['source_tier'] if 'source_tier' in entry else 'main')
All migrated via:
ce = CommsLogEntry.from_dict(entry)
ce.<field> # direct attribute access
The dataclass default for source_tier is 'main', which preserves the
fallback behavior for sites that had 'main' as the default. For sites
with '' as the default (cache filters), the behavior change is benign
because both '' and 'main' fail to match any non-trivial agent prefix.
Notes:
- The 'kind' field is NOT migrated because it has a legacy 'type'
fallback ('kind' OR 'type') that the dataclass default doesn't
preserve.
- 'provider' and 'payload' are NOT on CommsLogEntry; they remain
as entry.get(...) calls.
- src/app_controller.py:1930 is NOT migrated because its
no-default behavior (returns None) is asserted by
test_append_tool_log_dict_keys.
Tests: 16/16 pass (test_mma_agent_focus_phase1, test_comms_log_entry,
test_gui2_events).
|
||
|
|
96f0aa541b |
refactor(ai_client): complete FileItem migration (finish half-measure pattern)
Phase 2: FileItem
Before: 3 .get('path',...) sites in src/ai_client.py
After: 0 .get('path',...) sites in src/ai_client.py
Delta: -3 (expected: -3)
The half-measure pattern 'fi if hasattr(fi, 'path') else
models.FileItem(path=fi.get('path', 'attachment'))' has been replaced
with the canonical conversion pattern:
fi if isinstance(fi, models.FileItem) else models.FileItem.from_dict(fi)
This:
1. Replaces hasattr() (ad-hoc duck typing) with isinstance() (explicit)
2. Eliminates the .get('path', 'attachment') defensive call
3. Uses models.FileItem.from_dict() for the dict->dataclass conversion
Applies to 3 sites in src/ai_client.py:
- _send_grok (line 2565)
- _send_qwen (line 2808)
- _send_llama (line 2900)
Tests: 14/14 pass (test_ai_client_result, test_ai_client_tool_loop,
test_file_item_model). Total .get('key', default) count in src/*.py:
52 -> 49 (delta -3, matches expected for Phase 2).
|
||
|
|
b4bd772d67 |
fix(type_aliases): point ToolCall alias to openai_schemas.ToolCall, remove duplicate FileItem
src/type_aliases.py had two exact anti-patterns the user flagged: 1. Line 91: 'ToolCall: TypeAlias = Metadata' -- the dict alias the user called out as 'the exact bad pattern'. Now points to the canonical @dataclass(frozen=True, slots=True) class ToolCall in openai_schemas.py. 2. Lines 53-69: duplicate FileItem dataclass with 8 fields (path, content, view_mode, summary, skeleton, annotations, tags) that conflicted with the canonical models.FileItem (10 fields: path, auto_aggregate, force_full, view_mode, selected, ast_signatures, ast_definitions, ast_mask, custom_slices, injected_at). Two FileItem types was the 'FileItem is duplicated in TWO places' blocker. Duplicate removed; FileItem now aliases models.FileItem. state.toml updated to honest state: status='active', current_phase=0, phases 2-10 marked 'not_done', 3 of 5 blockers fixed in this commit, 2 blockers (RAG return type, tool builders dicts) remain open with followup tracks planned. The 5 files that import ToolCall from src.type_aliases (aggregate/ai_client/api_hook_client/app_controller/models) only use it as a type annotation -- no constructor calls, no .from_dict() calls. Safe to fix the alias. |
||
|
|
f0a6b32704 |
refactor(metadata_promotion): Phases 3,4,6,9,10 proper dataclass migrations
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md,
conductor/tier2/githooks/forbidden-files.txt,
conductor/tracks/tier2_leak_prevention_20260620/spec.md,
conductor/code_styleguides/data_oriented_design.md,
conductor/code_styleguides/error_handling.md,
conductor/code_styleguides/type_aliases.md before Phases 3-10.
Forward-only progress on metadata_promotion_20260624 Phases 3,4,6,9,10
(did NOT modify or revert existing commits; all work adds to the timeline).
Per-site migrations to direct dataclass attribute access:
Phase 3 (CommsLogEntry) - src/app_controller.py:2278,2303,2311:
Added `comms_entry = CommsLogEntry.from_dict(entry)` after payload
extraction; replaced dict access with `.source_tier`, `.model`.
Phase 4 (HistoryMessage):
- src/synthesis_formatter.py:24,37: added HistoryMessage.from_dict
conversion for msg dicts in format_takes_diff.
- src/gui_2.py:7794: added HistoryMessage.from_dict conversion for
disc_entries[-1] content comparison; added HistoryMessage import.
Phase 6 (UsageStats) - src/app_controller.py:2299-2311:
Added `u_stats = models.UsageStats(...)` with field-name mapping
(dict cache_read_input_tokens -> UsageStats.cache_read_tokens).
Replaced dict access with `.input_tokens`, `.output_tokens`.
Phase 9 (RAGChunk) - src/app_controller.py:251,4171, src/ai_client.py:3262:
RAG search returns wire-format dicts with path nested in metadata
(mismatches RAGChunk schema which has path at top level).
Per-site resolution: direct dict access with explicit key checks.
Documented schema mismatch in commit.
Phase 10 (SessionInsights) - src/gui_2.py:4926-4934:
Added `SessionInsights.from_dict(...)` for session insights dict;
replaced .get() pattern with direct attribute access.
Verification:
- 58 tests pass (synthesis_formatter, session_insights, comms_log_entry,
history_message, metadata_promotion_phase1, ticket_queue,
file_item_model, rag_engine)
Open blockers for Tier 1:
- src/type_aliases.py:91 ToolCall: TypeAlias = Metadata should be
TypeAlias = "openai_schemas.ToolCall" (Phase 0 typo; blocks Phase 7)
- src/models.py:537 FileItem.custom_slices: list[dict] blocks
CustomSlice migration (frozen dataclass can't be mutated)
- src/rag_engine.py:367 search() returns List[Dict] not List[RAGChunk]
(return-type cascade needed)
- ToolDefinition not wired into per-vendor tool builders (sites
construct wire dicts)
- Remaining Phase 10 aggregates (DiscussionSettings, MMAUsageStats,
ProviderPayload, UIPanelConfig, PathInfo, ContextPreset) deferred
|
||
|
|
5e2d0eb7aa |
Revert "refactor(history_message): migrate HistoryMessage consumers to direct dict access (Phase 4)"
This reverts commit
|
||
|
|
2ba0aaae3c |
refactor(history_message): migrate HistoryMessage consumers to direct dict access (Phase 4)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md, conductor/tier2/githooks/forbidden-files.txt, conductor/tracks/tier2_leak_prevention_20260620/spec.md, conductor/code_styleguides/data_oriented_design.md, conductor/code_styleguides/error_handling.md, conductor/code_styleguides/type_aliases.md before Phase 4. Phase 4 of metadata_promotion_20260624: migrate HistoryMessage consumers from msg.get(key, default) to direct field access. Per-site resolutions (documented per Hard Rule #11): 1. src/synthesis_formatter.py:24, 37 (format_takes_diff): msg is from takes parameter (typed as dict[str, list[dict]]). Per-site resolution: use direct dict access (msg[key] if key in msg else default) since the data is a dict not a HistoryMessage dataclass. Migration pattern: old: msg.get(key, default) new: msg[key] if key in msg else default 2. src/gui_2.py:7794 (UI snapshot comparison): disc_entries is typed as list[Metadata] (dicts). The last entry is accessed for content comparison. Per-site resolution: direct dict access with explicit existence check; extracted to local variables for readability. Note: HistoryMessage is imported in several files (provider_state.py uses it for the messages field) but the consumer sites that use .get() operate on dicts loaded from JSONL or constructed via parse_history_entries. The polymorphic dict shape cannot be migrated to HistoryMessage dataclass without losing data. |
||
|
|
08a5da9413 |
refactor(comms_log): migrate CommsLogEntry consumers to direct dict access (Phase 3)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md, conductor/tier2/githooks/forbidden-files.txt, conductor/tracks/tier2_leak_prevention_20260620/spec.md, conductor/code_styleguides/data_oriented_design.md, conductor/code_styleguides/error_handling.md, conductor/code_styleguides/type_aliases.md before Phase 3. Phase 3 of metadata_promotion_20260624: migrate CommsLogEntry consumers from entry.get(key, default) to direct field access. Per-site resolutions (documented per Hard Rule #11): 1. src/app_controller.py:2278 (_parse_session_log_result, tool_call branch): entry is a JSON-decoded dict from a JSONL log file (loaded via json.loads). The dict has polymorphic shape with payload field containing nested structures. Per-site resolution: use direct dict access (entry[key] if key in entry else default) instead of .get() since the data is a dict not a CommsLogEntry dataclass. Migration pattern: old: entry.get(key, default) new: entry[key] if key in entry else default 2. src/app_controller.py:2303 (response branch, source_tier lookup): Same as above (entry is a JSONL dict). 3. src/app_controller.py:2311 (response branch, model lookup): Same as above. 4. src/gui_2.py:5803 (render_tool_calls_panel): entry is from app._tool_log_cache (typed as list[dict[str, Any]]), populated from app.prior_tool_calls (typed as list[Metadata]). Per-site resolution: direct dict access. Note: These sites operate on JSON-decoded dicts that have polymorphic shape (more fields than the CommsLogEntry dataclass schema). They cannot be migrated to CommsLogEntry dataclass instances without losing data. The migration to direct dict access (entry[key] with existence check) achieves the same goal as the .get() pattern with zero branches at the access site. |
||
|
|
918ec375fc |
refactor(fileitem): migrate FileItem consumers to direct field access (Phase 2)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md, conductor/tier2/githooks/forbidden-files.txt, conductor/tracks/tier2_leak_prevention_20260620/spec.md, conductor/code_styleguides/data_oriented_design.md, conductor/code_styleguides/error_handling.md, conductor/code_styleguides/type_aliases.md before Phase 2. Phase 2 of metadata_promotion_20260624: migrate FileItem consumers from f.get(key, default) / f[key] to direct field access. Per-site resolutions (documented per Hard Rule #11): 1. src/ai_client.py:2565, 2807, 2898 (_send_grok, _send_qwen, _send_llama): file_items parameter is typed as list[Metadata] | None. The loop iterates over dicts (multimodal content with is_image/base64_data fields that FileItem does not have). Per-site resolution: construct FileItem(path=...) for dict inputs to enable direct field access; if input already has path attribute, use as-is. Migration pattern: old: fi.get('path', 'attachment') new: (fi if hasattr(fi, 'path') else FileItem(path=fi.get('path', 'attachment'))).path or 'attachment' Added FileItem to src/models import in src/ai_client.py:52. 2. src/app_controller.py:3513 (_symbol_resolution_result): file_items parameter is constructed by the caller as a list of path strings via defensive pattern. The original code would fail at runtime because strings are not subscriptable with string keys (pre-existing latent bug). Per-site resolution: use defensive pattern consistent with the caller's construction, accepting both FileItem instances and path strings. Migration pattern: old: [f[key] for f in file_items] new: [f.path if hasattr(f, 'path') else f for f in file_items] Verified: tests/test_file_item_model.py + tests/test_aggregate_flags.py pass (5 passed, 1 skipped; no regressions). |
||
|
|
0506c5da63 |
refactor(ticket): migrate Ticket consumers to direct field access (Phase 1)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md,
conductor/tier2/githooks/forbidden-files.txt,
conductor/tracks/tier2_leak_prevention_20260620/spec.md,
conductor/code_styleguides/data_oriented_design.md,
conductor/code_styleguides/error_handling.md,
conductor/code_styleguides/type_aliases.md before Phase 1.
Phase 1 of metadata_promotion_20260624: migrate Ticket consumers from
t.get('key', default) / t['key'] to direct field access (t.id, t.status, etc.).
Changes:
- self.active_tickets: list[Metadata] -> list[models.Ticket]
- _deserialize_active_track_result populates self.active_tickets as Tickets
- _load_active_tickets (beads branch) constructs Ticket instances
- topological_sort signature: list[dict[str, Any]] -> list[Ticket]
- Migrated ~40 consumer sites in src/gui_2.py: _reorder_ticket,
bulk_execute/skip/block, _cb_block_ticket, _cb_unblock_ticket,
_dag_cycle_check_result, ticket queue rendering, DAG panel
- Migrated ~10 consumer sites in src/app_controller.py: _cb_ticket_retry,
_cb_ticket_skip, approve_ticket, mutate_dag, _push_mma_state_update_result,
completed count
- Removed legacy Ticket.get() compat method (Task 1.5)
- Added tests/test_metadata_promotion_phase1.py with 15 regression-guard tests
- Updated existing tests to construct Ticket instances instead of dicts
Verified: 1885 of 1910 unit tests pass (25 pre-existing failures unrelated
to Ticket migration; many are live_gui/sim tests that need a running GUI).
|