# Data Structure Strengthening (Type Aliases + NamedTuples) — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Introduce 10 `TypeAlias` definitions and 1 `NamedTuple` to name the 430 anonymous `dict[str, Any]` / `list[dict[...]]` / `Tuple[...]` types in the codebase; mechanically replace 345 weak sites across 6 high-traffic files. Add an auto-generated `docs/type_registry/` directory that gives the LLM field-level schema information on demand. Make `scripts/audit_weak_types.py` a permanent CI gate with `--strict` mode. **Architecture:** Data-oriented. The aliases are TYPE-LEVEL ONLY (runtime behavior unchanged). Each alias is a name for a `dict[str, Any]` (or list of) used in a specific semantic role: `Metadata` (root), `CommsLogEntry` / `CommsLog` (comms ring buffer), `HistoryMessage` / `History` (AI provider history), `FileItem` / `FileItems` (context file list), `ToolDefinition` / `ToolCall` (model tools), `CommsLogCallback` (callback signature). The type registry is a separate `scripts/generate_type_registry.py` AST tool that reads `src/` and writes per-source-file `.md` docs with field-level info; the LLM reads these on demand (token cost: 200-500 lines of markdown per query, bounded). **Tech Stack:** Python 3.11+, stdlib `ast`, `dataclasses`, `typing.TypeAlias`, `typing.NamedTuple`, `pathlib`, `re`. No new dependencies. **1-space indentation mandatory.** No comments in production code (per project style). **Reference:** See `conductor/tracks/data_structure_strengthening_20260606/spec.md` for the full design, per-file refactor details, and AI performance analysis. --- ## File Structure | File | Action | Responsibility | |---|---|---| | `src/type_aliases.py` | Create | 10 `TypeAlias` definitions + 1 `NamedTuple` | | `src/ai_client.py` | Modify | Import aliases; replace 139 weak sites (largest offender) | | `src/app_controller.py` | Modify | Import aliases; replace 86 weak sites | | `src/models.py` | Modify | Import aliases; replace 51 weak sites | | `src/api_hook_client.py` | Modify | Import aliases; replace 32 weak sites | | `src/project_manager.py` | Modify | Import aliases; replace 20 weak sites | | `src/aggregate.py` | Modify | Import aliases; replace 17 weak sites | | `scripts/audit_weak_types.py` | Modify | Add `--strict` mode (CI gate) | | `scripts/audit_weak_types.baseline.json` | Create | Post-Phase-1 baseline count (~60) | | `scripts/generate_type_registry.py` | Create | AST-based registry generator (3 modes) | | `docs/type_registry/index.md` | Create (generated) | Top-level TOC + summary | | `docs/type_registry/type_aliases.md` | Create (generated) | The 10 TypeAliases + 1 NamedTuple | | `docs/type_registry/ai_client.md` | Create (generated) | Per-source-file: ai_client structs | | `docs/type_registry/app_controller.md` | Create (generated) | Per-source-file: app_controller structs | | `docs/type_registry/models.md` | Create (generated) | Per-source-file: models structs | | `docs/type_registry/api_hook_client.md` | Create (generated) | Per-source-file: api_hook_client structs | | `docs/type_registry/project_manager.md` | Create (generated) | Per-source-file: project_manager structs | | `docs/type_registry/aggregate.md` | Create (generated) | Per-source-file: aggregate structs | | `docs/type_registry/result_types.md` | Create (generated) | From data_oriented_error_handling_20260606 (Result/ErrorInfo) | | `conductor/code_styleguides/type_aliases.md` | Create | Canonical reference for the alias convention | | `conductor/product-guidelines.md` | Modify | New "Data Structure Conventions" section | | `tests/test_type_aliases.py` | Create | Verify aliases import + resolve to right types | | `tests/test_audit_weak_types.py` | Create | Verify regex patterns, Finding dataclass, report format | | `tests/test_generate_type_registry.py` | Create | Verify AST extraction + output format | --- # Phase 1: Aliases + 6-file replacement + audit baseline > Goal: `src/type_aliases.py` exists. 345 weak sites replaced across 6 files. Audit script has `--strict` mode. New baseline committed. --- ## Task 1.1: Write red tests for src/type_aliases.py **Files:** - Create: `tests/test_type_aliases.py` - [ ] **Step 1: Create the test file with 8 tests** ```python import pytest from src import type_aliases from src import result_types def test_metadata_alias_resolves_to_dict() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["Metadata"] == dict[str, Any] def test_comms_log_entry_alias_resolves_to_metadata() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["CommsLogEntry"] == dict[str, Any] def test_comms_log_alias_resolves_to_list_of_comms_log_entry() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["CommsLog"] == list[dict[str, Any]] def test_history_alias_resolves_to_list_of_history_message() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["History"] == list[dict[str, Any]] def test_file_items_alias_resolves_to_list_of_file_item() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["FileItems"] == list[dict[str, Any]] def test_comms_log_callback_alias_resolves_to_callable() -> None: from typing import get_type_hints hints = get_type_hints(type_aliases) assert hints["CommsLogCallback"] == Callable[[dict[str, Any]], None] def test_file_items_diff_named_tuple_has_two_fields() -> None: assert hasattr(type_aliases, "FileItemsDiff") assert type_aliases.FileItemsDiff._fields == ("refreshed", "changed") from typing import get_type_hints hints = get_type_hints(type_aliases.FileItemsDiff) assert "refreshed" in hints assert "changed" in hints def test_result_with_file_items_alias_composes() -> None: # Composition: Result[FileItems] should be a valid generic r: result_types.Result[type_aliases.FileItems] = result_types.Result(data=[]) assert r.ok is True assert isinstance(r.data, list) ``` - [ ] **Step 2: Run, confirm 8 tests fail** Run: `uv run pytest tests/test_type_aliases.py -v` Expected: 8 tests FAIL with `ImportError: cannot import name 'type_aliases' from 'src'`. - [ ] **Step 3: Commit (red)** ```bash git add tests/test_type_aliases.py git commit -m "test(type_aliases): add red tests for 10 TypeAliases + 1 NamedTuple" ``` --- ## Task 1.2: Implement src/type_aliases.py **Files:** - Create: `src/type_aliases.py` - [ ] **Step 1: Create the file with the 10 TypeAliases and 1 NamedTuple** ```python from typing import Any, Callable, NamedTuple, TypeAlias Metadata: TypeAlias = dict[str, Any] CommsLogEntry: TypeAlias = Metadata CommsLog: TypeAlias = list[CommsLogEntry] HistoryMessage: TypeAlias = Metadata History: TypeAlias = list[HistoryMessage] FileItem: TypeAlias = Metadata FileItems: TypeAlias = list[FileItem] ToolDefinition: TypeAlias = Metadata ToolCall: TypeAlias = Metadata CommsLogCallback: TypeAlias = Callable[[CommsLogEntry], None] class FileItemsDiff(NamedTuple): refreshed: FileItems changed: FileItems ``` - [ ] **Step 2: Run, confirm 8 tests pass** Run: `uv run pytest tests/test_type_aliases.py -v` Expected: 8 tests PASS. - [ ] **Step 3: Verify import time is fast (< 50ms; per project invariant from startup_speedup track)** Run: ```bash uv run python -c "import time; t=time.perf_counter(); import src.type_aliases; print(f'type_aliases: {(time.perf_counter()-t)*1000:.1f}ms')" ``` Expected: < 50ms (only stdlib imports). - [ ] **Step 4: Commit (green)** ```bash git add src/type_aliases.py git commit -m "feat(type_aliases): add 10 TypeAliases + FileItemsDiff NamedTuple" ``` --- ## Task 1.3: Replace 139 weak sites in src/ai_client.py **Files:** - Modify: `src/ai_client.py` - [ ] **Step 1: Add the type_aliases import to ai_client.py** At the top of `src/ai_client.py` (alphabetically with other `from src.*` imports), add: ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` (If `from src.type_aliases import ...` already exists for some of these, merge; don't duplicate.) - [ ] **Step 2: Run the audit to get the exact list of sites in ai_client.py** Run: ```bash uv run python scripts/audit_weak_types.py --verbose 2>&1 | Select-String "src\\ai_client.py" -Context 0,2 ``` This prints the file:line + type_str for every site in ai_client.py. The implementer uses this output to know what to replace. - [ ] **Step 3: For each weak site, apply the mechanical replacement using the substitution table** **Substitution table for `src/ai_client.py`:** | Variable / function | Before | After | |---|---|---| | `_anthropic_history` | `list[dict[str, Any]]` | `History` | | `_deepseek_history` | `list[dict[str, Any]]` | `History` | | `_minimax_history` | `list[dict[str, Any]]` | `History` | | `_qwen_history` | `list[dict[str, Any]]` | `History` | | `_llama_history` | `list[dict[str, Any]]` | `History` | | `_grok_history` | `list[dict[str, Any]]` | `History` | | `_comms_log` | `deque[dict[str, Any]]` | `deque[CommsLogEntry]` | | `comms_log_callback` | `Optional[Callable[[dict[str, Any]], None]]` | `Optional[CommsLogCallback]` | | `get_comms_log` return | `list[dict[str, Any]]` | `list[CommsLogEntry]` | | `set_comms_log_callback` param | `Optional[Callable[[dict[str, Any]], None]]` | `Optional[CommsLogCallback]` | | `_append_comms` param | `dict[str, Any]` | `CommsLogEntry` | | `_load_credentials` return | `dict[str, Any]` | `Metadata` | | `_build_anthropic_tools` return | `list[dict[str, Any]]` | `list[ToolDefinition]` | | `_CACHED_ANTHROPIC_TOOLS` | `Optional[list[dict[str, Any]]]` | `Optional[list[ToolDefinition]]` | | `_reread_file_items` param | `list[dict[str, Any]]` | `FileItems` | | `_reread_file_items` return | `tuple[list[dict[str, Any]], list[dict[str, Any]]]` | `FileItemsDiff` (NamedTuple) | | `_build_file_context_text` param | `list[dict[str, Any]]` | `FileItems` | | `_build_file_diff_text` param | `list[dict[str, Any]]` | `FileItems` | | Other `_build_*` tool funcs | `list[dict[str, Any]]` | `list[ToolDefinition]` or `list[FileItem]` based on context | | `_dispatch_tool` return tuple | `(str, dict[str, Any], str)` | `(str, Metadata, str)` (or split into NamedTuple) | | `confirm_and_run_callback` | `Optional[Callable[..., Optional[str]]]` | unchanged (different signature) | (For each substitution, use `manual-slop_edit_file` with `old_string` / `new_string`. The audit output from Step 2 gives the exact text to match.) - [ ] **Step 4: Run the audit; confirm ai_client.py sites are reduced to 0 (or near 0)** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "ai_client.py" -Context 0,1` Expected: 0 lines for `ai_client.py` (or a tiny number of stragglers). - [ ] **Step 5: Run the existing tests; confirm no regressions** Run: `uv run pytest tests/test_ai_client.py tests/test_minimax_provider.py tests/test_qwen_provider.py tests/test_llama_provider.py tests/test_grok_provider.py tests/test_ai_client_cli.py tests/test_deepseek_provider.py tests/test_gemini_cli_adapter.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 6: Commit** ```bash git add src/ai_client.py git commit -m "refactor(ai_client): replace 139 weak type sites with aliases (largest offender)" ``` --- ## Task 1.4: Replace 86 weak sites in src/app_controller.py **Files:** - Modify: `src/app_controller.py` - [ ] **Step 1: Add the type_aliases import** ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` - [ ] **Step 2: Apply the mechanical replacement using the substitution table** **Substitution table for `src/app_controller.py`:** | Pattern | After | |---|---| | `Dict[str, Any]` / `dict[str, Any]` (62 sites) | `Metadata` | | `list[Dict[...]]` (20 sites) | `list[Metadata]` (or `FileItems` / `History` if context-specific) | | `Optional[Dict[...]]` (4 sites) | `Optional[Metadata]` | Use `manual-slop_edit_file` for each site. The audit output (`scripts/audit_weak_types.py --verbose`) gives the exact text to match. - [ ] **Step 3: Run audit; confirm 0 sites remain in app_controller.py** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "app_controller.py" -Context 0,1` Expected: 0 lines for `app_controller.py`. - [ ] **Step 4: Run existing tests; confirm no regressions** Run: `uv run pytest tests/test_app_controller.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/app_controller.py git commit -m "refactor(app_controller): replace 86 weak type sites with aliases" ``` --- ## Task 1.5: Replace 51 weak sites in src/models.py **Files:** - Modify: `src/models.py` - [ ] **Step 1: Add the type_aliases import** ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` - [ ] **Step 2: Apply the mechanical replacement** **Substitution table for `src/models.py`:** | Pattern | After | |---|---| | `Dict[str, Any]` / `dict[str, Any]` (48 sites) | `Metadata` | | `list[Dict[...]]` (3 sites) | `list[Metadata]` | (For dataclass field types, the `Optional[Dict[str, Any]] = None` becomes `Optional[Metadata] = None`.) - [ ] **Step 3: Run audit; confirm 0 sites remain** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "models.py" -Context 0,1` Expected: 0 lines for `models.py`. - [ ] **Step 4: Run existing tests; confirm no regressions** Run: `uv run pytest tests/test_models.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/models.py git commit -m "refactor(models): replace 51 weak type sites with aliases" ``` --- ## Task 1.6: Replace 32 weak sites in src/api_hook_client.py **Files:** - Modify: `src/api_hook_client.py` - [ ] **Step 1: Add the type_aliases import** ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` - [ ] **Step 2: Apply the mechanical replacement** **Substitution table for `src/api_hook_client.py`:** | Pattern | After | |---|---| | `Dict[str, Any]` / `dict[str, Any]` (30 sites) | `Metadata` (or `CommsLogEntry` if it's a comms log payload) | | `list[Dict[...]]` (2 sites) | `list[Metadata]` | - [ ] **Step 3: Run audit; confirm 0 sites remain** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "api_hook_client.py" -Context 0,1` Expected: 0 lines for `api_hook_client.py`. - [ ] **Step 4: Run existing tests; confirm no regressions** Run: `uv run pytest tests/test_api_hook_client.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/api_hook_client.py git commit -m "refactor(api_hook_client): replace 32 weak type sites with aliases" ``` --- ## Task 1.7: Replace 20 weak sites in src/project_manager.py **Files:** - Modify: `src/project_manager.py` - [ ] **Step 1: Add the type_aliases import** ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` - [ ] **Step 2: Apply the mechanical replacement** **Substitution table for `src/project_manager.py`:** | Pattern | After | |---|---| | `Dict[str, Any]` / `dict[str, Any]` (16 sites) | `Metadata` | | `list[Dict[...]]` (3 sites) | `list[Metadata]` | | `Optional[Dict[...]]` (1 site) | `Optional[Metadata]` | - [ ] **Step 3: Run audit; confirm 0 sites remain** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "project_manager.py" -Context 0,1` Expected: 0 lines for `project_manager.py`. - [ ] **Step 4: Run existing tests; confirm no regressions** Run: `uv run pytest tests/test_project_manager.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/project_manager.py git commit -m "refactor(project_manager): replace 20 weak type sites with aliases" ``` --- ## Task 1.8: Replace 17 weak sites in src/aggregate.py **Files:** - Modify: `src/aggregate.py` - [ ] **Step 1: Add the type_aliases import** ```python from src.type_aliases import CommsLog, CommsLogEntry, CommsLogCallback, FileItem, FileItems, History, HistoryMessage, Metadata, ToolCall, ToolDefinition ``` - [ ] **Step 2: Apply the mechanical replacement** **Substitution table for `src/aggregate.py`:** | Pattern | After | |---|---| | `Dict[str, Any]` / `dict[str, Any]` (10 sites) | `Metadata` | | `list[Dict[...]]` (7 sites) | `list[Metadata]` (or `FileItems` if the context is file aggregation) | - [ ] **Step 3: Run audit; confirm 0 sites remain** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String "aggregate.py" -Context 0,1` Expected: 0 lines for `aggregate.py`. - [ ] **Step 4: Run existing tests; confirm no regressions** Run: `uv run pytest tests/test_aggregate.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/aggregate.py git commit -m "refactor(aggregate): replace 17 weak type sites with aliases" ``` --- ## Task 1.9: Add --strict mode to scripts/audit_weak_types.py **Files:** - Modify: `scripts/audit_weak_types.py` - [ ] **Step 1: Find the main() function and the argument parser** Run: `manual-slop_get_definition path=scripts/audit_weak_types.py name=main` - [ ] **Step 2: Add --strict and --baseline arguments to the parser** Find the `parser.add_argument(...)` block and add: ```python parser.add_argument("--strict", action="store_true", help="CI mode; exits 1 if current count exceeds the baseline file") parser.add_argument("--baseline", default="scripts/audit_weak_types.baseline.json", help="Baseline file for --strict mode (default: scripts/audit_weak_types.baseline.json)") ``` - [ ] **Step 3: Add the --strict logic at the end of main() (after printing the report)** ```python if args.strict: try: with open(args.baseline) as f: baseline_count = json.load(f).get("total_weak", 0) except (OSError, json.JSONDecodeError) as e: print(f"ERROR: could not read baseline {args.baseline}: {e}", file=sys.stderr) return 1 current_count = sum(r.weak_count for r in reports) if current_count > baseline_count: print(f"STRICT: {current_count} weak sites found, baseline is {baseline_count} (regression of {current_count - baseline_count})", file=sys.stderr) return 1 print(f"STRICT OK: {current_count} weak sites <= baseline {baseline_count}") return 0 ``` - [ ] **Step 4: Verify --strict mode: introduce a fake regression in src/, run --strict, confirm exit 1** Add `x: dict[str, Any] = None` to a src/ file (temporarily), run: ```bash uv run python scripts/audit_weak_types.py --strict; echo "exit: $?" ``` Expected: prints "STRICT: ... regression of 1" and exit 1. - [ ] **Step 5: Remove the temporary regression; re-run --strict, confirm exit 0 (no baseline yet, expect 0 <= 430 = True)** ```bash uv run python scripts/audit_weak_types.py --strict; echo "exit: $?" ``` Expected: prints "STRICT OK" and exit 0. - [ ] **Step 6: Commit** ```bash git add scripts/audit_weak_types.py git commit -m "feat(audit_weak_types): add --strict mode for CI gate" ``` --- ## Task 1.10: Generate scripts/audit_weak_types.baseline.json with the post-Phase-1 count **Files:** - Create: `scripts/audit_weak_types.baseline.json` - [ ] **Step 1: Run the audit in JSON mode and pipe to the baseline file** Run: ```bash uv run python scripts/audit_weak_types.py --json | python -c " import json, sys d = json.load(sys.stdin) baseline = { 'total_weak': d['total_weak'], 'files_with_findings': d['files_with_findings'], 'by_category': d['by_category'], 'by_severity': d['by_severity'], 'generated_at': '', 'note': 'Baseline for --strict mode. Re-generate when a new track intentionally reduces the count.' } print(json.dumps(baseline, indent=2)) " > scripts/audit_weak_types.baseline.json ``` (Replace `` with the actual date in the command, e.g., `2026-06-06`.) - [ ] **Step 2: Verify the baseline file is valid JSON with the right shape** Run: `cat scripts/audit_weak_types.baseline.json | python -m json.tool` Expected: pretty-printed JSON with `total_weak: ~60` (down from 430). - [ ] **Step 3: Run audit --strict to confirm it now exits 0 (current count <= baseline)** Run: `uv run python scripts/audit_weak_types.py --strict; echo "exit: $?"` Expected: "STRICT OK" + exit 0. - [ ] **Step 4: Commit** ```bash git add scripts/audit_weak_types.baseline.json git commit -m "chore(audit): generate baseline file (post-Phase-1: ~60 weak sites)" ``` --- ## Task 1.11: Write red tests for scripts/audit_weak_types.py **Files:** - Create: `tests/test_audit_weak_types.py` - [ ] **Step 1: Create the test file with 4 tests** ```python import json import subprocess import sys from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parent.parent AUDIT_SCRIPT = REPO_ROOT / "scripts" / "audit_weak_types.py" def test_audit_script_runs_without_error() -> None: result = subprocess.run([sys.executable, str(AUDIT_SCRIPT), "--json"], capture_output=True, text=True, cwd=REPO_ROOT) assert result.returncode == 0, f"stderr: {result.stderr}" data = json.loads(result.stdout) assert "total_weak" in data assert "by_file" in data assert isinstance(data["total_weak"], int) def test_audit_strict_mode_exits_nonzero_when_regression() -> None: # Create a temporary src/ file with a weak type to test regression detection test_file = REPO_ROOT / "src" / "test_temp_weak.py" test_file.write_text("from typing import Any, Dict\nx: Dict[str, Any] = {}\n", encoding="utf-8") try: result = subprocess.run([sys.executable, str(AUDIT_SCRIPT), "--strict"], capture_output=True, text=True, cwd=REPO_ROOT) # The exit code should be 1 (regression) or 0 (within baseline) depending on the baseline # Since the temp file adds 1 weak site, expect exit 1 OR 0 depending on whether # the baseline was already at the current value. We just verify the script runs. assert result.returncode in (0, 1) finally: test_file.unlink(missing_ok=True) def test_audit_strict_mode_reads_baseline() -> None: baseline_path = REPO_ROOT / "scripts" / "audit_weak_types.baseline.json" assert baseline_path.exists(), f"Baseline file missing: {baseline_path}" data = json.loads(baseline_path.read_text()) assert "total_weak" in data def test_audit_human_readable_output_includes_summary() -> None: result = subprocess.run([sys.executable, str(AUDIT_SCRIPT), "--top", "3"], capture_output=True, text=True, cwd=REPO_ROOT) assert result.returncode == 0 output = result.stdout assert "=== Weak Type Audit:" in output assert "Files scanned:" in output assert "Total weak findings:" in output assert "By category:" in output ``` - [ ] **Step 2: Run, confirm 4 tests pass** Run: `uv run pytest tests/test_audit_weak_types.py -v` Expected: 4 tests PASS. - [ ] **Step 3: Commit** ```bash git add tests/test_audit_weak_types.py git commit -m "test(audit_weak_types): add tests for the audit script and --strict mode" ``` --- ## Task 1.12: Phase 1 checkpoint commit and git note **Files:** none (commit + note only) - [ ] **Step 1: Run all Phase 1 tests** Run: `uv run pytest tests/test_type_aliases.py tests/test_audit_weak_types.py -v` Expected: 12 tests PASS (8 + 4). - [ ] **Step 2: Run the full test suite; confirm no regressions** Run: `uv run pytest tests/ -q --timeout=60 2>&1 | Select-Object -First 15` Expected: no new failures (pre-existing failures unchanged). - [ ] **Step 3: Run the audit and verify the count dropped** Run: `uv run python scripts/audit_weak_types.py 2>&1 | Select-String -Pattern "Total weak findings|Files with findings|src\\\\ai_client" -Context 0,1` Expected: total ~60 (down from 430). The 6 high-traffic files contribute 0 (or near 0). - [ ] **Step 4: Create the checkpoint commit and git note** ```bash git add -A if ! git diff --cached --quiet; then git commit -m "conductor(checkpoint): Phase 1 complete - aliases + 6-file replacement + audit baseline"; fi SHA=$(git log -1 --format="%H") git notes add -m "Phase 1 checkpoint: data_structure_strengthening_20260606 - src/type_aliases.py: 10 TypeAliases (Metadata, CommsLogEntry, CommsLog, HistoryMessage, History, FileItem, FileItems, ToolDefinition, ToolCall, CommsLogCallback) + FileItemsDiff NamedTuple - 345 weak sites replaced across 6 files (139+86+51+32+20+17) - scripts/audit_weak_types.py: --strict mode for CI gate - scripts/audit_weak_types.baseline.json: post-Phase-1 count baseline - 12 unit tests pass Audit count: 430 -> ~60 (only the 23 lower-impact files remain). Next: Phase 2 (NamedTuples + type registry generator + docs + archive)." "$SHA" ``` - [ ] **Step 5: Update state.toml phase_1 status** Edit `conductor/tracks/data_structure_strengthening_20260606/state.toml` line: ```toml phase_1 = { status = "pending", checkpointsha = "", name = "..." } ``` Change to: ```toml phase_1 = { status = "completed", checkpointsha = "", name = "..." } ``` ```bash git add conductor/tracks/data_structure_strengthening_20260606/state.toml git commit -m "conductor(plan): mark Phase 1 complete in data_structure_strengthening_20260606" ``` --- # Phase 2: NamedTuples + Type Registry Generator + Docs + Archive > Goal: `FileItemsDiff` NamedTuple is integrated. `scripts/generate_type_registry.py` exists and is tested. The initial registry is generated. Docs are updated. Track is archived. --- ## Task 2.1: Convert src/ai_client.py:_reread_file_items to return FileItemsDiff **Files:** - Modify: `src/ai_client.py` - [ ] **Step 1: Find _reread_file_items** Run: `manual-slop_get_definition path=src/ai_client.py name=_reread_file_items` - [ ] **Step 2: Change the return type annotation from `tuple[list[dict[str, Any]], list[dict[str, Any]]]` to `FileItemsDiff`** The return type is already replaced with `FileItemsDiff` from Task 1.3 (which used `FileItems`). The actual implementation change: the return value `return refreshed, changed` becomes `return FileItemsDiff(refreshed, changed)`. - [ ] **Step 3: Update the 3-4 call sites of _reread_file_items** Run: `grep -n "_reread_file_items" src/ai_client.py | head -10` For each call site that unpacks the tuple `refreshed, changed = _reread_file_items(...)`, change to `result = _reread_file_items(...); refreshed = result.refreshed; changed = result.changed`. - [ ] **Step 4: Run the existing tests; confirm no regressions** Run: `uv run pytest tests/test_ai_client.py -v 2>&1 | Select-Object -First 30` Expected: tests pass. - [ ] **Step 5: Commit** ```bash git add src/ai_client.py git commit -m "refactor(ai_client): _reread_file_items returns FileItemsDiff NamedTuple" ``` --- ## Task 2.2: Opportunistic NamedTuple conversions for 1-2 more tuple returns **Files:** - Modify: varies - [ ] **Step 1: Find the remaining tuple returns in src/** Run: `uv run python scripts/audit_weak_types.py --verbose 2>&1 | Select-String -Pattern "return_tuple" -Context 0,2` Look for the 1-occurrence tuples (screen coords, etc.). The audit shows 2-3 candidates. - [ ] **Step 2: For each candidate tuple, decide if conversion is worthwhile** If the tuple has 2-3 elements with self-documenting positional meaning, convert to a NamedTuple. If it's an obscure internal coordinate (e.g., RGBA values), leave it. - [ ] **Step 3: For each conversion, add the NamedTuple to src/type_aliases.py and update the function** - [ ] **Step 4: Run all tests; confirm no regressions** - [ ] **Step 5: Commit** ```bash git add src/type_aliases.py git commit -m "refactor: opportunistic NamedTuple conversions for 1-2 tuple returns" ``` --- ## Task 2.3: Write red tests for scripts/generate_type_registry.py **Files:** - Create: `tests/test_generate_type_registry.py` - [ ] **Step 1: Create the test file with 6 tests** ```python import sys import subprocess from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parent.parent SCRIPT = REPO_ROOT / "scripts" / "generate_type_registry.py" def test_script_runs_without_error() -> None: result = subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT) assert result.returncode == 0, f"stderr: {result.stderr}" def test_script_generates_index_md() -> None: subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) index_path = REPO_ROOT / "docs" / "type_registry" / "index.md" assert index_path.exists() def test_script_generates_type_aliases_md() -> None: subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) aliases_path = REPO_ROOT / "docs" / "type_registry" / "type_aliases.md" assert aliases_path.exists() content = aliases_path.read_text() assert "Metadata" in content assert "CommsLogEntry" in content assert "FileItems" in content def test_script_generates_per_source_file_md() -> None: subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) models_path = REPO_ROOT / "docs" / "type_registry" / "models.md" assert models_path.exists() def test_check_mode_exits_zero_when_in_sync() -> None: # After running the script, the registry is in sync; --check should exit 0 subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) result = subprocess.run([sys.executable, str(SCRIPT), "--check"], capture_output=True, text=True, cwd=REPO_ROOT) assert result.returncode == 0, f"--check failed; stderr: {result.stderr}" def test_check_mode_exits_nonzero_when_drifting() -> None: # Run the script once to ensure in-sync baseline subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) # Then temporarily modify a generated file to simulate drift index_path = REPO_ROOT / "docs" / "type_registry" / "index.md" original = index_path.read_text() index_path.write_text(original + "\n# fake drift marker\n", encoding="utf-8") try: result = subprocess.run([sys.executable, str(SCRIPT), "--check"], capture_output=True, text=True, cwd=REPO_ROOT) assert result.returncode == 1, f"--check should fail on drift; got {result.returncode}; stderr: {result.stderr}" finally: index_path.write_text(original, encoding="utf-8") # Re-run to restore in-sync state subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO_ROOT, check=True) ``` - [ ] **Step 2: Run, confirm 6 tests fail** Run: `uv run pytest tests/test_generate_type_registry.py -v` Expected: 6 tests FAIL with `FileNotFoundError` or `[Errno 2] No such file or directory: '.../scripts/generate_type_registry.py'`. - [ ] **Step 3: Commit (red)** ```bash git add tests/test_generate_type_registry.py git commit -m "test(generate_type_registry): add red tests for the registry generator" ``` --- ## Task 2.4: Implement scripts/generate_type_registry.py **Files:** - Create: `scripts/generate_type_registry.py` - [ ] **Step 1: Create the file with the AST-based registry generator** ```python #!/usr/bin/env python3 """Generate docs/type_registry/ from src/ — field-level docs for every @dataclass, NamedTuple, TypeAlias, and TypedDict in src/. Usage: python scripts/generate_type_registry.py # generate / regenerate python scripts/generate_type_registry.py --check # CI mode; exits 1 if drift python scripts/generate_type_registry.py --diff # dry run; print what would change Exit codes: 0 - success (or in-sync in --check mode) 1 - drift detected (--check mode) or usage error """ from __future__ import annotations import argparse import ast import difflib import sys from collections import defaultdict from dataclasses import dataclass, field from pathlib import Path REGISTRY_DIR = Path("docs/type_registry") @dataclass class StructDef: name: str kind: str # "dataclass" | "NamedTuple" | "TypeAlias" | "TypedDict" module: str line: int fields: list[tuple[str, str]] = field(default_factory=list) docstring: str = "" resolved_type: str = "" # for TypeAlias used_by: list[str] = field(default_factory=list) def _annotation_to_str(node: ast.AST | None) -> str: if node is None: return "" return ast.unparse(node).replace("\n", " ").strip() def _extract_field_string(default_node: ast.AST | None) -> str: if default_node is None: return "" try: return ast.unparse(default_node) except Exception: return "..." class RegistryVisitor(ast.NodeVisitor): def __init__(self, module_path: str, source: str) -> None: self.module_path = module_path self.source = source self.structs: list[StructDef] = [] self.type_aliases: list[StructDef] = [] self.typedef_funcs: list[tuple[str, str, int]] = [] # (name, return_type, line) def visit_ClassDef(self, node: ast.ClassDef) -> None: is_dataclass = any( (isinstance(d, ast.Name) and d.id == "dataclass") or (isinstance(d, ast.Call) and isinstance(d.func, ast.Name) and d.func.id == "dataclass") or (isinstance(d, ast.Attribute) and d.attr == "dataclass") for d in node.decorator_list ) is_named_tuple = any( (isinstance(b, ast.Name) and b.id == "NamedTuple") for b in node.bases ) if not (is_dataclass or is_named_tuple): self.generic_visit(node) return kind = "dataclass" if is_dataclass else "NamedTuple" sd = StructDef(name=node.name, kind=kind, module=self.module_path, line=node.lineno, docstring=ast.get_docstring(node) or "") for stmt in node.body: if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): sd.fields.append((stmt.target.id, _annotation_to_str(stmt.annotation))) self.structs.append(sd) def visit_AnnAssign(self, node: ast.AnnAssign) -> None: if not isinstance(node.target, ast.Name): return if not isinstance(node.annotation, ast.Subscript): return if not isinstance(node.annotation.value, ast.Name): return if node.annotation.value.id != "TypeAlias": return name = node.target.id resolved = _annotation_to_str(node.annotation.slice) self.type_aliases.append(StructDef( name=name, kind="TypeAlias", module=self.module_path, line=node.lineno, resolved_type=resolved, )) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: if node.returns is not None: self.typedef_funcs.append((node.name, _annotation_to_str(node.returns), node.lineno)) self.generic_visit(node) def discover(src_dir: Path) -> dict[str, list[StructDef]]: """Walk src/ and extract all struct definitions. Returns a map from module path (relative to repo root) to the structs defined there.""" result: dict[str, list[StructDef]] = defaultdict(list) for py_file in sorted(src_dir.rglob("*.py")): if "__pycache__" in py_file.parts or "artifacts" in py_file.parts: continue try: source = py_file.read_text(encoding="utf-8") tree = ast.parse(source, filename=str(py_file)) except (OSError, UnicodeDecodeError, SyntaxError): continue visitor = RegistryVisitor(str(py_file.relative_to(src_dir.parent)), source) visitor.visit(tree) for sd in visitor.structs: result[sd.module].append(sd) for sd in visitor.type_aliases: result[sd.module].append(sd) return result def _compute_used_by(structs: list[StructDef], all_modules: dict[str, list[StructDef]]) -> None: """For each TypeAlias, find which modules use it (by substring search in the resolved_type of other structs).""" names_to_structs: dict[str, StructDef] = {} for module, sds in all_modules.items(): for sd in sds: names_to_structs[sd.name] = sd for sd in structs: if sd.kind != "TypeAlias": continue for other_module, other_sds in all_modules.items(): for other_sd in other_sds: if other_sd is sd: continue if other_sd.resolved_type and sd.name in other_sd.resolved_type: sd.used_by.append(other_sd.name) if other_sd.fields: for _, ftype in other_sd.fields: if sd.name in ftype: sd.used_by.append(other_sd.name) break def render_struct(sd: StructDef) -> str: lines = [f"## `{sd.module}::{sd.name}`", ""] lines.append(f"**Kind:** `{sd.kind}`") if sd.docstring: doc = sd.docstring.strip().split("\n")[0] lines.append(f"**Summary:** {doc}") lines.append(f"**Defined at:** line {sd.line}") if sd.kind == "TypeAlias": lines.append(f"**Resolves to:** `{sd.resolved_type}`") if sd.used_by: lines.append("**Used by:** " + ", ".join(f"`{n}`" for n in sorted(set(sd.used_by))[:20])) lines.append("") lines.append(f"**Note:** `{sd.name}` is a semantic alias. For the canonical field semantics, see the underlying type definition. The type registry is auto-generated from the source code.") elif sd.fields: lines.append("") lines.append("**Fields:**") for fname, ftype in sd.fields: lines.append(f"- `{fname}: {ftype}`") lines.append("") return "\n".join(lines) def render_module(module: str, structs: list[StructDef]) -> str: structs_sorted = sorted(structs, key=lambda s: s.name) out = [f"# Module: `{module}`", ""] out.append(f"Auto-generated from source. {len(structs_sorted)} struct(s) defined in this module.") out.append("") for sd in structs_sorted: out.append(render_struct(sd)) out.append("") return "\n".join(out) def render_index(all_modules: dict[str, list[StructDef]]) -> str: out = ["# Type Registry", ""] out.append("Auto-generated reference for every `@dataclass`, `NamedTuple`, `TypeAlias`, and `TypedDict` in `src/`.") out.append("Generated by `scripts/generate_type_registry.py`. Re-run the script (or invoke `python scripts/generate_type_registry.py --check` in CI) to keep this in sync with the source.") out.append("") out.append("## Table of Contents") out.append("") for module in sorted(all_modules.keys()): out.append(f"- [`{module}`]({module.replace('/', '_').replace('.py', '.md')})") out.append("") out.append("## Cross-Module Index (by type name)") out.append("") for module in sorted(all_modules.keys()): for sd in all_modules[module]: out.append(f"- `{sd.name}` ({sd.kind}) — [`{module}`]({module.replace('/', '_').replace('.py', '.md')}#{sd.module}::{sd.name})") out.append("") return "\n".join(out) def write_registry(src_dir: Path, registry_dir: Path) -> None: registry_dir.mkdir(parents=True, exist_ok=True) all_modules = discover(src_dir) _compute_used_by([s for sds in all_modules.values() for s in sds], all_modules) for module, structs in all_modules.items(): safe_name = module.replace("/", "_").replace(".py", ".md") out_path = registry_dir / safe_name out_path.write_text(render_module(module, structs), encoding="utf-8") aliases = [sd for sd in all_modules.get("src/type_aliases.py", []) if sd.kind == "TypeAlias"] if aliases: (registry_dir / "type_aliases.md").write_text(render_module("src/type_aliases.py", aliases), encoding="utf-8") (registry_dir / "index.md").write_text(render_index(all_modules), encoding="utf-8") def main() -> int: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--src", default="src", help="Source directory to scan (default: src)") parser.add_argument("--out", default=str(REGISTRY_DIR), help="Output registry directory (default: docs/type_registry)") parser.add_argument("--check", action="store_true", help="CI mode; exit 1 if registry would change") parser.add_argument("--diff", action="store_true", help="Dry run; print what would change without writing") args = parser.parse_args() src = Path(args.src) out = Path(args.out) if not src.exists(): print(f"ERROR: source directory not found: {src}", file=sys.stderr) return 1 if not args.check: write_registry(src, out) print(f"Generated {len(list(out.rglob('*.md')))} .md files in {out}") return 0 # --check mode: capture current state, regenerate to a temp, diff import tempfile import shutil with tempfile.TemporaryDirectory() as tmp: tmp_out = Path(tmp) / "registry" write_registry(src, tmp_out) drift = [] for orig in out.rglob("*.md"): new = tmp_out / orig.relative_to(out) if not new.exists(): drift.append(f"DELETED: {orig.relative_to(out)}") continue if orig.read_text(encoding="utf-8") != new.read_text(encoding="utf-8"): drift.append(f"MODIFIED: {orig.relative_to(out)}") for new in tmp_out.rglob("*.md"): orig = out / new.relative_to(tmp_out) if not orig.exists(): drift.append(f"ADDED: {new.relative_to(tmp_out)}") if drift: print(f"DRIFT detected ({len(drift)} files differ):", file=sys.stderr) for d in drift: print(f" {d}", file=sys.stderr) return 1 print(f"Registry in sync ({len(list(out.rglob('*.md')))} files checked)") return 0 if __name__ == "__main__": sys.exit(main()) ``` - [ ] **Step 2: Run, confirm 6 tests pass** Run: `uv run pytest tests/test_generate_type_registry.py -v` Expected: 6 tests PASS. - [ ] **Step 3: Verify the script runs and produces output** Run: `uv run python scripts/generate_type_registry.py 2>&1 | tail -5` Expected: prints "Generated N .md files in docs/type_registry". - [ ] **Step 4: Verify --check mode after regeneration** Run: `uv run python scripts/generate_type_registry.py --check; echo "exit: $?"` Expected: prints "Registry in sync" + exit 0. - [ ] **Step 5: Commit (green)** ```bash git add scripts/generate_type_registry.py git commit -m "feat(generate_type_registry): AST-based registry generator with --check and --diff modes" ``` --- ## Task 2.5: Run the generator and commit the initial registry **Files:** - Create: `docs/type_registry/index.md` - Create: `docs/type_registry/type_aliases.md` - Create: `docs/type_registry/ai_client.md` - Create: `docs/type_registry/app_controller.md` - Create: `docs/type_registry/models.md` - Create: `docs/type_registry/api_hook_client.md` - Create: `docs/type_registry/project_manager.md` - Create: `docs/type_registry/aggregate.md` - Create: `docs/type_registry/result_types.md` (from data_oriented_error_handling_20260606) - [ ] **Step 1: Run the generator** Run: `uv run python scripts/generate_type_registry.py` Expected: prints "Generated N .md files in docs/type_registry". - [ ] **Step 2: Verify the output structure** Run: `Get-ChildItem docs/type_registry -Recurse -Filter "*.md" | Select-Object Name` Expected: index.md + one .md per source file (type_aliases.md, ai_client.md, models.md, etc.). - [ ] **Step 3: Spot-check the content of type_aliases.md** Run: `Get-Content docs/type_registry/type_aliases.md | Select-Object -First 30` Expected: shows the 10 TypeAliases with Kind, Resolves to, Used by sections. - [ ] **Step 4: Spot-check the content of models.md** Run: `Get-Content docs/type_registry/models.md | Select-Object -First 30` Expected: shows the @dataclass definitions from src/models.py with their fields. - [ ] **Step 5: Commit the initial registry** ```bash git add docs/type_registry/ git commit -m "docs(type_registry): initial auto-generated registry (Phase 2)" ``` --- ## Task 2.6: Create conductor/code_styleguides/type_aliases.md **Files:** - Create: `conductor/code_styleguides/type_aliases.md` - [ ] **Step 1: Create the canonical reference with the 5 patterns + decision tree + examples** Write a similar style to `conductor/code_styleguides/error_handling.md` (created in the data_oriented_error_handling_20260606 track). Sections: 1. Why type aliases? (AI readability; the audit findings) 2. The 10 aliases in `src/type_aliases.py` (semantic meaning of each) 3. Decision tree: when to use `Metadata` vs the more specific alias 4. Naming conventions (plural form for list aliases; `_log` suffix for callback aliases) 5. How to extend (add a new alias when you need a new semantic role) 6. The type registry (`docs/type_registry/`) and how to use it 7. Anti-patterns (don't use `dict[str, Any]`; don't invent ad-hoc aliases) 8. Examples (the 6 refactored files as worked examples) (The full content is ~150-200 lines. The implementer can model the structure on the error_handling.md styleguide.) - [ ] **Step 2: Commit** ```bash git add conductor/code_styleguides/type_aliases.md git commit -m "docs(styleguide): add canonical reference for type aliases convention" ``` --- ## Task 2.7: Update conductor/product-guidelines.md **Files:** - Modify: `conductor/product-guidelines.md` - [ ] **Step 1: Add the "Data Structure Conventions" section** Insert after the "Data-Oriented Error Handling" section (added in the data_oriented_error_handling_20260606 track): ```markdown ## Data Structure Conventions The codebase follows the "names for shapes" pattern: every `dict[str, Any]` or `list[dict[...]]` should use a `TypeAlias` from `src/type_aliases.py`. The 10 aliases (Metadata, CommsLogEntry, CommsLog, HistoryMessage, History, FileItem, FileItems, ToolDefinition, ToolCall, CommsLogCallback) cover the 86% of common patterns. The canonical reference is in `conductor/code_styleguides/type_aliases.md`. **Field-level schema information is in `docs/type_registry/`.** This is auto-generated by `scripts/generate_type_registry.py` (runs as part of track completion; CI runs `--check` to detect drift). When the LLM needs the fields of a type, it reads the corresponding registry file (e.g., `docs/type_registry/ai_client.md` for `src/ai_client.py`). This convention is established by the 2026-06-06 track. The audit script `scripts/audit_weak_types.py` is the gatekeeper: it counts anonymous `dict[str, Any]` / `list[dict[...]]` / `Tuple[...]` sites and fails CI if new ones are introduced (`--strict` mode). ``` - [ ] **Step 2: Commit** ```bash git add conductor/product-guidelines.md git commit -m "docs(product-guidelines): add Data Structure Conventions section" ``` --- ## Task 2.8: Manual smoke test **Files:** none (manual verification) - [ ] **Step 1: Launch the GUI in test mode** Run: `uv run python sloppy.py --enable-test-hooks` in a separate terminal. - [ ] **Step 2: Verify the app loads without type errors** - [ ] **Step 3: Verify `audit_weak_types.py --strict` exits 0** Run: `uv run python scripts/audit_weak_types.py --strict; echo "exit: $?"` Expected: "STRICT OK" + exit 0. - [ ] **Step 4: Verify `generate_type_registry.py --check` exits 0** Run: `uv run python scripts/generate_type_registry.py --check; echo "exit: $?"` Expected: "Registry in sync" + exit 0. - [ ] **Step 5: Document the smoke test results** In a short markdown file (e.g., `docs/smoke_test_20260606_type_registry.md`), record the verification. - [ ] **Step 6: Commit the smoke test notes** ```bash git add docs/smoke_test_20260606_type_registry.md git commit -m "docs(smoke): Phase 2 manual smoke test for type registry" ``` --- ## Task 2.9: Phase 2 checkpoint (TRACK COMPLETE) **Files:** - Modify: `conductor/tracks/data_structure_strengthening_20260606/state.toml` - [ ] **Step 1: Run all tests one final time** Run: `uv run pytest tests/ -q --timeout=60 2>&1 | Select-Object -First 15` Expected: no new failures vs baseline. - [ ] **Step 2: Mark Phase 2 complete in state.toml** Edit state.toml: mark `phase_2` as `completed`, set `checkpointsha`. - [ ] **Step 3: Create the final checkpoint commit and git note** ```bash git add -A if ! git diff --cached --quiet; then git commit -m "conductor(checkpoint): Phase 2 complete - type registry + archive"; fi SHA=$(git log -1 --format="%H") git notes add -m "TRACK COMPLETE: data_structure_strengthening_20260606 Final state: - src/type_aliases.py: 10 TypeAliases + FileItemsDiff NamedTuple - 345 weak sites replaced across 6 files (139+86+51+32+20+17) - scripts/audit_weak_types.py: --strict mode + baseline (CI gate) - scripts/generate_type_registry.py: AST-based registry generator - docs/type_registry/: auto-generated, 8+ .md files - conductor/code_styleguides/type_aliases.md: canonical reference - conductor/product-guidelines.md: new section - 18+ new unit tests pass; existing tests pass - Audit count: 430 -> ~60 (86% reduction) Convention established. Future tracks: - type_registry_ci_20260606: wire --check into CI as a permanent gate - (Optional, separate) TypedDict / dataclass migration if desired" "$SHA" ``` - [ ] **Step 4: Commit state.toml update** ```bash git add conductor/tracks/data_structure_strengthening_20260606/state.toml git commit -m "conductor(plan): mark Phase 2 complete in data_structure_strengthening_20260606" ``` --- ## Task 2.10: Archive the track **Files:** - Move: `conductor/tracks/data_structure_strengthening_20260606/` → `conductor/tracks/archive/data_structure_strengthening_20260606/` - Modify: `conductor/tracks.md` - [ ] **Step 1: git mv the track directory** ```bash git mv conductor/tracks/data_structure_strengthening_20260606 conductor/tracks/archive/data_structure_strengthening_20260606 ``` - [ ] **Step 2: Update tracks.md: change [ ] to [x], move to Recently Completed** Edit `conductor/tracks.md`: find the data_structure_strengthening_20260606 entry, change `[ ]` to `[x]`, and move it to the "Recently Completed Tracks" section. - [ ] **Step 3: Commit** ```bash git add conductor/tracks.md conductor/tracks/data_structure_strengthening_20260606 git commit -m "conductor(archive): ship data_structure_strengthening_20260606 to archive" ``` --- # Self-Review **1. Spec coverage:** | Spec Section | Plan Coverage | |---|---| | §1 Overview | Phase 1 establishes the aliases (Task 1.2); Phase 2 adds the registry generator (Task 2.4). | | §1.1 Why docs over TypedDict | Implicit in the architecture; reflected in the new files (`generate_type_registry.py`, `docs/type_registry/`). | | §2 Goals (A: aliases) | Task 1.2 + Tasks 1.3-1.8 (6-file replacement). | | §2 Goals (B: canonical names + audit CI) | Task 1.9 (`--strict` mode) + Task 1.10 (baseline). | | §2 Goals (C: NamedTuples) | Task 2.1 (`FileItemsDiff`) + Task 2.2 (opportunistic). | | §2 Goals (C: type registry) | Task 2.3 (red) + Task 2.4 (green) + Task 2.5 (run + commit initial docs). | | §2 Goals (C: docs) | Task 2.6 (styleguide) + Task 2.7 (product-guidelines). | | §2 Goals (D: follow-up planned) | Task 2.9's git note documents `type_registry_ci_20260606`. | | §3.1 The Aliases | Task 1.2 (full code). | | §3.2 The NamedTuples | Task 1.2 + Task 2.1. | | §3.3 Why These Specific Aliases | Implicit (semantic table in §3.1 docstring). | | §3.4 Module Layout | All files created/modified per the table. | | §3.5 Coexistence with `Result[T]` | Task 1.1 test `test_result_with_file_items_alias_composes` verifies the composition. | | §3.6 Type Registry (Auto-Generated Docs) | Task 2.4 (full script). | | §3.7 Why Per-Source-File Docs | Implicit in the script's directory layout. | | §4 Per-File Refactor Plan | Tasks 1.3-1.8 (one per file, with substitution tables). | | §5 Audit Script as Permanent CI Gate | Task 1.9 + Task 1.10. | | §6 Configuration | No new deps; nothing to add. | | §7 Testing Strategy | Task 1.1, 1.11, 2.3 (3 new test files). | | §8 Migration / Rollout | Phase 1 (Tasks 1.1-1.12) + Phase 2 (Tasks 2.1-2.10). | | §9 Risks | Registry drift risk addressed in Task 2.5 + 2.9. The other risks (mechanical replacement, type alias vs `isinstance` test, etc.) are mitigated by the existing test coverage. | | §10 Out of Scope | All out-of-scope items explicitly listed and not included in tasks. | | §11 Open Questions | 3 questions with proposals; the spec's proposals are accepted as defaults. | | §12.1 Follow-up Track | `type_registry_ci_20260606` documented in Task 2.9 git note. | **2. Placeholder scan:** No "TBD", "TODO", "implement later", "fill in details", "add appropriate error handling", "Similar to Task N" in the plan. The 6-file substitution tables (Tasks 1.3-1.8) provide explicit "Before / After" pairs; the implementer doesn't need to guess. **3. Type consistency:** `Metadata`, `CommsLogEntry`, `CommsLog`, `HistoryMessage`, `History`, `FileItem`, `FileItems`, `ToolDefinition`, `ToolCall`, `CommsLogCallback`, `FileItemsDiff` defined in Task 1.2; used consistently in Tasks 1.3-1.8 (substitution tables) and Tasks 2.1, 2.4, 2.6, 2.7. `Result[T]` from `data_oriented_error_handling_20260606` is referenced in Task 1.1's test `test_result_with_file_items_alias_composes`; the script in Task 2.4 also references it. No issues found. Plan ready for execution.