Private
Public Access
0
0
Files
ed 9147578155 conductor(plan): write 2-phase implementation plan for data_structure_strengthening_20260606
~22 tasks across 2 phases, each with explicit Red-Green-Refactor TDD steps:
- Phase 1 (1.1-1.12): Foundation. type_aliases.py (10 TypeAliases + 1
  NamedTuple) with 8 unit tests. Mechanical replacement of 345 weak
  sites in 6 files (ai_client 139, app_controller 86, models 51,
  api_hook_client 32, project_manager 20, aggregate 17). Each file
  has a per-substitution table for the mechanical replacement. Audit
  script gains --strict mode + baseline file (CI gate). 4 audit tests.
- Phase 2 (2.1-2.10): FileItemsDiff NamedTuple integrated.
  generate_type_registry.py (AST-based; 3 modes: default, --check,
  --diff). Initial registry generated in docs/type_registry/ (8+ .md
  files). 6 generator tests. Type aliases styleguide + product-guidelines
  updates. Manual smoke test. Track archived.

The type registry generator uses --check mode for CI: it regenerates to
a temp dir and diffs against the committed registry; exit 1 if drift.
The agent's track-completion workflow is: regenerate -> review diff ->
commit. CI enforces --check on every PR.

Self-review at the end maps every spec section to a task (no gaps),
confirms zero placeholders, and verifies type/method-name consistency
across phases (all 10 aliases + FileItemsDiff defined in Task 1.2; used
consistently in Tasks 1.3-1.8 and Phase 2).
2026-06-06 18:15:15 -04:00

52 KiB

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

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)
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

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:

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)
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:

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:

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
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

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
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

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
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

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
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

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
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

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
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:

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)
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:

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)
uv run python scripts/audit_weak_types.py --strict; echo "exit: $?"

Expected: prints "STRICT OK" and exit 0.

  • Step 6: Commit
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:

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': '<TODAY_ISO>',
  '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 <TODAY_ISO> 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
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

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
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
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:

phase_1 = { status = "pending", checkpointsha = "", name = "..." }

Change to:

phase_1 = { status = "completed", checkpointsha = "<first-7-of-SHA>", name = "..." }
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
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

git add src/type_aliases.py <changed_files>
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

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)
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

#!/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)
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
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
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):

## 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
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
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
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
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

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
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.