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 before module_taxonomy_refactor_20260627/Phase3b.
The v2 spec/plan (c35cc494) is the canonical guide. Phases 0, 1, 2 are
done in the branch. Phase 3a (mma.py, cd828e52) and Phase 3g (persona
to personas.py, d7872bea) are already committed; back-compat re-exports
exist in src/models.py. The remaining work: 3b (project.py), 3c
(project_files.py), 3d-3f + 3h-3i (6 merges), 4 (delete
AGENT_TOOL_NAMES), 5 (reduce models.py), 6 (verify + report).
The cruft_elimination track is no longer a blocker: the ProjectContext
+ 5 sub dataclasses are at models.py:797-873 (the cruft track merged
them in earlier). The v2 plan can extract them.
failcount state: 0/0 (prior reset via c35cc494).
Six fixes for the c11_python doc sync (chronology row 3):
- C5 (Result notation): Result[str, ErrorInfo] -> Result[str] at
docs/guide_ai_client.md lines 452 + 469; also error_handling.md
line 801 (historical deprecation section).
- C6 (RAGChunk schema): docs/guide_models.md lines 343-349 corrected
to match src/rag_engine.py:19-25 (id, document, path, score, metadata).
- C17 (type_aliases.md table): rewrote alias table to reflect post-2026-06-25
reality (Metadata is @dataclass(frozen=True, slots=True) with 36 fields;
11 per-aggregate dataclasses listed with source locations; removed
stale 'underlying type is dict[str, Any]' claim at line 73 + the
'keep Metadata as dict[str, Any]' claim at line 81).
- C19 (OBLITERATE principle): added 'OBLITERATE Principle' section to
error_handling.md after Migration Playbook; clarified in Hard Rules
that argument types that may be None (caller choice) are NOT banned.
- C2 (audit script name): docs/AGENTS.md references updated to point
to scripts/audit_optional_returns.py (the all-src/ successor to
scripts/audit_optional_in_3_files.py).
Also: docs/reports/CONTRADICTIONS_REPORT_20260627.md — the contradictions
index that drives these fixes. Kept for reference.
C16 + C18 were already addressed in commit 770c2fdb (python.md §10
Documented Exceptions table + §17.10 audit inventory).
Implements the 7th audit script referenced in python.md §17.8. Scans
src/*.py for local imports (§17.9a), _PREFIX aliasing (§17.9b), and
repeated .from_dict() in the same expression (§17.9c, info-only).
Three changes in this commit:
1. scripts/audit_imports.py: AST-based scanner; exits 1 in --strict on
LOCAL_IMPORT or PREFIX_ALIAS. Whitelist-aware via
scripts/audit_imports_whitelist.toml (load with --show-whitelist;
disable with --no-whitelist).
2. scripts/audit_imports_whitelist.toml: 21 files whitelisted with per-file
reason (vendor SDK warmup, hot-reload re-imports, circular-dep avoidance).
Suppresses 187 LOCAL_IMPORT sites; 0 strict violations remain.
3. conductor/code_styleguides/python.md: updated §17.8 (4th audit entry)
and §17.9a (3 documented exceptions + whitelist mechanism).
Tests: tests/test_audit_imports.py (7 tests, all passing).
Implements the 7th audit script referenced in python.md §17.8. Scans
src/*.py for local imports (§17.9a), _PREFIX aliasing (§17.9b), and
repeated .from_dict() in the same expression (§17.9c, info-only).
Three changes in this commit:
1. scripts/audit_imports.py: AST-based scanner; exits 1 in --strict on
LOCAL_IMPORT or PREFIX_ALIAS. Whitelist-aware via
scripts/audit_imports_whitelist.toml (load with --show-whitelist;
disable with --no-whitelist).
2. scripts/audit_imports_whitelist.toml: 21 files whitelisted with per-file
reason (vendor SDK warmup, hot-reload re-imports, circular-dep avoidance).
Suppresses 187 LOCAL_IMPORT sites; 0 strict violations remain.
3. conductor/code_styleguides/python.md: updated §17.8 (4th audit entry)
and §17.9a (3 documented exceptions + whitelist mechanism).
Tests: tests/test_audit_imports.py (7 tests, all passing).
TIER-1 READ AGENTS.md + conductor/workflow.md + conductor/edit_workflow.md
+ conductor/code_styleguides/data_oriented_design.md + conductor/code_styleguides/error_handling.md
+ conductor/code_styleguides/type_aliases.md + conductor/code_styleguides/code_path_audit.md
+ conductor/tracks/module_taxonomy_refactor_20260627/spec.md + conductor/tracks/module_taxonomy_refactor_20260627/plan.md
+ docs/reports/FOLLOWUP_module_taxonomy_refactor_20260627_recoverable.md before this commit.
v2 fixes v1 gaps that gave Tier 2 discretion:
1. THE 4-CRITERIA DECISION RULE (the taxonomy law):
- C1: Cross-system usage (consumed by >= 3 unrelated systems)
- C2: State machine / lifecycle
- C3: Test file already exists
- C4: Substantial size (> 30 lines OR > 5 fields)
- Rule: C1 OR C2 OR C3 -> DEDICATED FILE; ONLY C4 -> MERGE INTO DESTINATION; NONE -> KEEP
2. THE DATA/VIEW/OPS SPLIT (the GUI boundary):
- Data classes go in data files (src/<system>.py)
- View code (ImGui rendering) goes in src/gui_2.py
- Ops (operations on data) go with the data
- Exception: imgui_scopes.py is the EXCEPTION (Python with context managers)
3. ZERO TIER 2 DISCRETION:
- Every move is pre-decided in the spec
- Tier 2 executes, doesn't decide
- v1 had 22 commits because of exploration; v2 has 16 because the work is prescriptive
4. PRESERVED Pydantic PROXIES:
- _create_generate_request, _create_confirm_request, __getattr__ stay in models.py
- They're API-specific; moving them is out of scope for v2
Applied to all 11 classes in models.py:
- DEDICATED: Ticket, Track, WorkerContext, TrackState, TrackMetadata, ThinkingSegment -> src/mma.py (6 classes; C1+C2+C3+C4)
- DEDICATED: FileItem, Preset, ContextPreset, ContextFileEntry, NamedViewPreset -> src/project_files.py (5 classes; C1+C3+C4)
- DEDICATED: ProjectContext + 5 sub + config IO -> src/project.py (1+5+functions; C1+C3+C4)
- MERGE: Tool, ToolPreset -> src/tool_presets.py (C1 NO)
- MERGE: BiasProfile -> src/tool_bias.py (C1 NO)
- MERGE: TextEditorConfig, ExternalEditorConfig -> src/external_editor.py (C1 NO)
- MERGE: Persona -> src/personas.py (C1 NO)
- MERGE: WorkspaceProfile -> src/workspace_manager.py (C1 NO)
- MERGE: MCPServerConfig, MCPConfiguration, VectorStoreConfig, RAGConfig, load_mcp_config -> src/mcp_client.py (C1 YES, coupled to MCP)
- DELETE: AGENT_TOOL_NAMES (redundant with mcp_tool_specs.tool_names())
Net: 65 -> 61 files (possibly 60 if models.py eliminated)
16 atomic commits (down from v1's 22)
14 VCs (added VC13 + VC14: verify the 4-criteria rule and data/view/ops split are documented)
The git stash ban is in place at 3 layers (commit 6240b07b). The timeline-
is-immutable principle is explicit in the agent prompt. The next Tier 2
should not be able to corrupt files the same way.
CRITICAL CORRECTION: the 5 'DAMAGED' tasks in the track report are NOT
data loss. The class definitions (Tool, ToolPreset, BiasProfile,
TextEditorConfig, ExternalEditorConfig, MCPServerConfig,
MCPConfiguration, VectorStoreConfig, RAGConfig, load_mcp_config,
WorkspaceProfile) are STILL in src/models.py with full bodies.
The actual state:
- 11 class definitions in models.py (data INTACT)
- 0 class definitions in destination files (the move was incomplete)
- 1 broken script that Tier 2 ran (the '5 tasks damaged' report)
What the user's anger is about (justified):
- Tier 2 used 'git stash' (now banned at 3 layers in commit 6240b07b)
- Tier 2 made a non-descriptive 'misc' commit
- Tier 2 reported 'DAMAGED' but the data was actually fine
What the user gets:
- Track is RECOVERABLE - just add the 11 classes to their destination files
- New Tier 2 should reset the 5 'damaged' tasks to 'pending' in state.toml
- Phase 1 + Phase 2 of the track are DONE
- The remaining work is mechanical: 5 commits to add class defs to
destination files, then 5 commits to remove them from models.py
Concrete next steps (for new Tier 2):
1. Add Tool + ToolPreset to src/tool_presets.py
2. Add BiasProfile to src/tool_bias.py
3. Add TextEditorConfig + ExternalEditorConfig to src/external_editor.py
4. Add MCP config classes to src/mcp_client.py
5. Add WorkspaceProfile to src/workspace_manager.py
6. (Then) remove from models.py
7. Create src/project.py + src/project_files.py
8. Delete AGENT_TOOL_NAMES
9. Verify
The previous TRACK_ABORTED report is INCORRECT. This report
supersedes it. The data is fine; only the move operation is
incomplete.
ROOT CAUSE: Tier 2 used 'git stash' during the cruft_elimination_20260627
track execution and corrupted the user's in-progress files. The user
explicitly stated: 'if an agent fucks up, their tendency to want to revert
is not correct and instead they must live with the timeline and just do
corrections with a new commit. They can grab artifacts, code, etc, from
old commits but they cannot reset to that.'
This commit adds HARD BANs on git stash* and git clean -fd* at 3 layers
(per the existing 3-layer defense model documented in
conductor/tier2/agents/tier2-autonomous.md):
LAYER 1: AGENTS.md
- Added new HARD BAN: 'git stash* (any form: git stash, git stash pop,
git stash apply, git stash drop, git stash clear) is FORBIDDEN.
Stashing inverts the safety net of the working tree'
LAYER 2: conductor/tier2/opencode.json.fragment (Tier 2 autonomous)
- Added 'git stash*', 'git stash pop*', 'git stash apply*',
'git stash drop*', 'git stash clear*', 'git clean -fd*', 'git clean -fdx*'
to BOTH the top-level permission.bash deny list AND the
agent.tier2-autonomous.permission.bash deny list
- Also added 'git revert*' (was missing from fragment; already banned in prompt)
- These are now HARD DENIED at the OpenCode permission layer; the agent
cannot run them even if it tries
LAYER 3: conductor/tier2/agents/tier2-autonomous.md
- Added 'git stash* (any form)' to the Hard Bans list
- Added 'THE TIMELINE-IS-IMMUTABLE PRINCIPLE' section spelling out
exactly what to do when you fuck up:
- When you make a wrong commit, write a NEW commit that fixes it
- The git history is immutable on this branch
- You CAN grab artifacts from old commits via 'git show <sha>:<path> > <new-path>'
- You CANNOT reset the branch HEAD to an old commit
- 'git revert', 'git reset --hard', 'git reset --soft', 'git stash' are
all attempts to rewrite history and BANNED
- Correct pattern: pause, read the actual file, write a forward
corrective commit with a commit message that explains the fix
This addresses the root cause of the 2026-06-27 cruft_elimination
corruption. Future Tier 2 autonomous runs will be blocked from running
git stash* at 2 layers (OpenCode permission deny + Tier 2 prompt hard
ban list) and reminded at the agent-prompt layer (THE TIMELINE-IS-
IMMUTABLE PRINCIPLE section).
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'.
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.
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
Per architecture (data != view != ops):
- Data classes (PendingPatch, EMPTY_PATCH, DiffHunk, DiffFile) live in src/patch_modal.py
- PatchModalManager (ops on the data) also stays; it's used only by tests/test_patch_modal.py
(no production src/ code references PatchModalManager; no ImGui rendering of patches uses it)
- src/gui_2.py imports DiffHunk/DiffFile from src.patch_modal (data dependency)
The original spec wanted to merge patch_modal.py into gui_2.py. That would conflate
data (DiffHunk/DiffFile) and ops (PatchModalManager) into the view layer, which
violates the app_controller-owns-state / gui-is-pure-view architecture established
in Phase 1.1 (bg_shader state fix) and Phase 1.3 (command_palette split).
Verification:
- uv run python -c 'from src.patch_modal import PendingPatch, DiffHunk, DiffFile, EMPTY_PATCH, PatchModalManager' OK
- 41 tests pass: test_diff_viewer, test_patch_modal, test_command_palette,
test_commands_no_top_level_command_palette, test_handle_reset_session,
test_app_controller_sigint
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).
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)
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.
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).
User: 'isn't AGENT_TOOL_NAMES a redundant thing thats directly associated
with the mcp_client.py?' - YES, confirmed.
The existing test test_tool_names_subset_of_models_agent_tool_names
literally asserts: tool_names() ⊆ AGENT_TOOL_NAMES. So AGENT_TOOL_NAMES
is just a hardcoded snapshot of mcp_tool_specs.tool_names().
Action: DELETE AGENT_TOOL_NAMES from models.py (not just move it).
Derive at consumer sites: list(mcp_tool_specs.tool_names()).
8 consumer sites to update:
- 3 in src/app_controller.py:2110, 2972, 3273
- 5 in tests/test_arch_boundary_phase2.py:23, 29, 31, 32, 33
The cross-check test becomes either redundant or converts to a
positive assertion (e.g., assert that the derived list has at
least the canonical tool count).
models.py reduces further: from ~60 to ~30 lines after deletion.
This further reduces the models.py footprint. Combined with the
previous audit (move vendor files to ai_client.py, split out mma.py
+ project.py + project_files.py), models.py becomes essentially
empty - just the Pydantic proxy code that may also move to api_hooks.py.
Net effect: models.py could be ELIMINATED entirely (becomes ~0 lines
or just an __init__.py marker). The followup should consider whether
to delete models.py completely.
Tier 2 marked Phase 2 (VC8) as 'spec mismatch' because the spec says
'add ProjectContext with all fields observed in flat_config' but
doesn't enumerate which fields. Tier 2 needs the spec to be specific
before it can resume.
This correction specifies the exact schema based on the actual code:
flat_config returns a NESTED dict with 6 top-level fields:
- project (Meta: name, summary_only, execution_mode)
- output (Output: namespace, output_dir)
- files (Files: base_dir, paths)
- screenshots (Screenshots: base_dir, paths)
- context_presets (opaque dict pass-through)
- discussion (Discussion: roles, history)
The 11 sub-fields are derived from aggregate.run's access patterns
(src/aggregate.py:484-525). output_dir and files.base_dir are REQUIRED
(direct subscript); all others use .get() with defaults.
Recommended design: 6 sub-dataclasses (ProjectMeta, ProjectOutput,
ProjectFiles, ProjectScreenshots, ProjectDiscussion, ProjectContext),
each matching the nested dict shape. ProjectContext has dict-compat
methods (__getitem__ + get) so consumers don't need migration.
Two migration options:
- Option A (incremental): ProjectContext has dict-compat; consumers
unchanged. Flat fix.
- Option B (full): Migrate all 8 consumer sites + 2 test mocks to
use sub-dataclass access. ~40 lines across 10 files.
Acceptance: 5 corrected VC8 criteria. Tier 2 can resume Phase 2 directly.
TIER-1 READ conductor/tracks/cruft_elimination_20260627/spec.md + src/project_manager.py:268 + src/aggregate.py:484-525 + src/type_aliases.py + src/models.py before this commit.