~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).
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.pyexists. 345 weak sites replaced across 6 files. Audit script has--strictmode. 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:
FileItemsDiffNamedTuple is integrated.scripts/generate_type_registry.pyexists 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]]]toFileItemsDiff
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:
- Why type aliases? (AI readability; the audit findings)
- The 10 aliases in
src/type_aliases.py(semantic meaning of each) - Decision tree: when to use
Metadatavs the more specific alias - Naming conventions (plural form for list aliases;
_logsuffix for callback aliases) - How to extend (add a new alias when you need a new semantic role)
- The type registry (
docs/type_registry/) and how to use it - Anti-patterns (don't use
dict[str, Any]; don't invent ad-hoc aliases) - 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 --strictexits 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 --checkexits 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.