From aca84b881bebe43eb320c14f50f5f2b38ac5f4cc Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 14:28:16 -0400 Subject: [PATCH] docs(reports): ANY_TYPE_AUDIT_20260621 - Any-type usage & componentization opportunities --- docs/reports/ANY_TYPE_AUDIT_20260621.md | 569 ++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 docs/reports/ANY_TYPE_AUDIT_20260621.md diff --git a/docs/reports/ANY_TYPE_AUDIT_20260621.md b/docs/reports/ANY_TYPE_AUDIT_20260621.md new file mode 100644 index 00000000..b700060a --- /dev/null +++ b/docs/reports/ANY_TYPE_AUDIT_20260621.md @@ -0,0 +1,569 @@ +# Audit Report: `Any` Type Usage & Data-Oriented Componentization Opportunities + +**Date:** 2026-06-21 +**Author:** Tier 2 Tech Lead (autonomous sandbox) +**Track:** `data_structure_strengthening_20260606` (follow-on) +**Status:** Findings report; **NOT a track spec** — Tier 1 is expected to devise the follow-up track. + +--- + +## 1. Executive Summary + +The `data_structure_strengthening_20260606` track replaced 416 `dict[str, Any]` / `list[dict[...]]` / `Tuple[...]` annotations with 10 `TypeAlias` definitions + 1 `NamedTuple` (528 → 112 weak sites; 79% reduction). The 10 `TypeAlias` definitions are **renames** — they point to the same underlying `dict[str, Any]` / `list[dict[str, Any]]` shapes. The alias names document intent; they do not add type safety. + +This report audits the **remaining `Any` usage** (~300 occurrences across 41 files in `src/`) and identifies **fat-struct componentization opportunities** that can be promoted to true `dataclass(frozen=True)` definitions, following the pattern already established by `src/vendor_capabilities.py`. The 5 highest-value candidates are: + +| Rank | File | Fat Struct | Sites | Estimated Value | +|---|---|---|---:|---| +| **P1** | `src/mcp_client.py` | `MCP_TOOL_SPECS` (45 tools) | 8 Any | **HIGH** — 45 × ~4 params = ~180 implicit fields | +| **P1** | `src/openai_compatible.py` | `NormalizedResponse` + `OpenAICompatibleRequest` | 17 Any | **HIGH** — message/tool-call/usage schemas are well-known | +| **P2** | `src/ai_client.py` | 7 × `*_history: list[Metadata]` + 7 × `*_history_lock` | 41 Any | **HIGH** — unification is a `ProviderHistory` dict | +| **P2** | `src/log_registry.py` | `data: dict[str, dict[str, Any]]` | 7 Any | MEDIUM — session metadata has 5 well-defined fields | +| **P3** | `src/api_hooks.py` | `_serialize_for_api(obj: Any) -> Any` + `broadcast(payload)` | 16 Any | LOW — internal serialization; lower semantic gain | + +**The recommended sequencing** is to run `code_path_audit_20260607` FIRST (now that the 4 foundational tracks have shipped: `qwen_llama_grok`, `data_oriented_error_handling`, **`data_structure_strengthening`**, `mcp_architecture_refactor`). The audit's `ActionProfile` for the 3 in-scope actions (AI message lifecycle, discussion save/load, GUI startup) will identify which fat-struct sites are in the **hot path** vs. cold. The componentization work then targets the hot-path fat structs first. + +The follow-up track (proposed §6 below) is the **"Any-Type Componentization" track** — a 6-phase refactor that converts the 5 fat-struct candidates above into true `dataclass(frozen=True)` definitions, following the `vendor_capabilities` template. + +--- + +## 2. Methodology + +### 2.1 Scope + +This report covers `Any` type annotations in `src/**/*.py`. The 41 files surveyed: + +``` +ai_client.py (41), app_controller.py (25), openai_compatible.py (17), +api_hooks.py (16), gui_2.py (13), events.py (13), mcp_client.py (8), +hot_reloader.py (7), log_registry.py (7), models.py (7), command_palette.py (6), +commands.py (6), rag_engine.py (6), theme_models.py (6), history.py (6), +api_hooks_helpers.py (6), conductor_tech_lead.py (5), orchestrator_pm.py (5), +imgui_scopes.py (5), file_cache.py (1), warmup.py (1), ... [21 more files ≤4] +``` + +### 2.2 The 5 Patterns of `Any` Usage + +Across all 41 files, `Any` falls into exactly 5 patterns. The patterns are ranked by **% of total occurrences**: + +| # | Pattern | % of `Any` | Replaceable? | +|---|---|---:|---| +| 1 | `dict[str, Any]` — JSON-shaped payloads (config, API bodies, tool specs) | ~35% | YES → `Metadata` (existing) or new `ToolInput`/`ApiPayload`/`SessionData` | +| 2 | `*_history: list[Metadata]` / `list[Any]` — per-provider message lists | ~12% | YES → unified `ProviderHistory` dict | +| 3 | SDK client holders (`_gemini_chat: Any = None`, etc.) | ~8% | NO (lazy-init pattern; heterogeneous types) | +| 4 | Dynamic dispatch (`__getattr__` returning `Any`) | ~6% | NO (intentional delegation) | +| 5 | Generic serialization (`obj: Any) -> Any`) | ~5% | NO (genuinely generic) | + +**~57% of `Any` usages are replaceable with concrete dataclasses.** The remaining ~43% are intentional (SDK holders, dynamic dispatch, serialization). + +### 2.3 The Reference Pattern: `src/vendor_capabilities.py` + +`vendor_capabilities.py` is the **canonical "module-level abstraction layer"** the user pointed to. Its structure (76 lines): + +```python +@dataclass(frozen=True) +class VendorCapabilities: + vendor: str + model: str + vision: bool = False + tool_calling: bool = True + caching: bool = False + # ... 22 named fields total +_REGISTRY: dict[tuple[str, str], VendorCapabilities] = {} + +def register(cap: VendorCapabilities) -> None: ... +def get_capabilities(vendor: str, model: str) -> VendorCapabilities: ... +``` + +**Properties that make this pattern successful:** + +| Property | Why it matters | +|---|---| +| `frozen=True` | Immutable; thread-safe; no accidental mutation | +| Named fields | Every capability is addressable by name (no `dict['vision']` lookups) | +| Module-level registry | O(1) lookup; no instantiation overhead | +| Wildcard `*` model | Fallback for unregistered models | +| Flat (no nesting) | Single cache-line access for most queries | +| Registration pattern | Extensible without modifying existing code | + +**All 5 fat-struct candidates below should follow this template.** + +--- + +## 3. The Inventory: Top 5 Fat-Struct Candidates + +### 3.1 P1 — `src/mcp_client.py: MCP_TOOL_SPECS` (45 tools, 8 Any usages) + +**Current state** (`src/mcp_client.py:1954-1972`): + +```python +def get_tool_schemas() -> list[dict[str, Any]]: + ... +MCP_TOOL_SPECS: list[dict[str, Any]] = [ + { + "name": "py_remove_def", + "description": "Excises a specific class or function from a Python file.", + "parameters": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "Path to the .py file." }, + "name": { "type": "string", "description": "The name of the class or function to remove." } + }, + "required": ["path", "name"] + } + }, + # ... 44 more dicts of identical shape +] +TOOL_NAMES: set[str] = {t['name'] for t in MCP_TOOL_SPECS} +``` + +**Problem:** 45 tool specs × ~3-5 parameters = ~180 implicit fields. The set comprehension `{t['name'] for t in MCP_TOOL_SPECS}` demonstrates the access pattern — repeated string-key lookups on untyped dicts. The dispatch map (`_dispatch_table`) is keyed by string tool names; static analysis cannot verify the key set. + +**Proposed componentization** (following the `vendor_capabilities` pattern): + +```python +# src/mcp_tool_specs.py (new; module-level abstraction) +@dataclass(frozen=True) +class ToolParameter: + name: str + type: str # "string" | "integer" | "boolean" | "object" | "array" + description: str + required: bool = False + enum: Optional[list[str]] = None + +@dataclass(frozen=True) +class ToolSpec: + name: str + description: str + parameters: tuple[ToolParameter, ...] + category: str = "file" # "file" | "ast" | "network" | "surgical" + +_REGISTRY: dict[str, ToolSpec] = {} + +def register(spec: ToolSpec) -> None: ... +def get_tool_spec(name: str) -> ToolSpec: ... +def get_tool_schemas() -> list[ToolSpec]: ... +def tool_names() -> set[str]: ... +``` + +**Call sites to update:** `mcp_client.py:1772 dispatch()`, `mcp_client.py:1939 async_dispatch()`, the `TOOL_NAMES` set, the `_dispatch_table` map (could become a `dict[str, Callable]` instead of string-keyed). + +**Estimated value:** **HIGH** — 45 tools × ~4 params each = ~180 implicit fields become explicit. Enables IDE autocomplete of tool names + parameters. Static analysis can verify dispatch keys. + +--- + +### 3.2 P1 — `src/openai_compatible.py: NormalizedResponse + OpenAICompatibleRequest` (17 Any) + +**Current state** (`src/openai_compatible.py:22-42`): + +```python +@dataclass(frozen=True) +class NormalizedResponse: + text: str + tool_calls: list[dict[str, Any]] # FAT: JSON tool call shape + usage_input_tokens: int + usage_output_tokens: int + usage_cache_read_tokens: int + usage_cache_creation_tokens: int + raw_response: Any # FAT: SDK-specific response + +@dataclass +class OpenAICompatibleRequest: + messages: list[dict[str, Any]] # FAT: message shape + model: str + temperature: float = 0.0 + top_p: float = 1.0 + max_tokens: int = 8192 + tools: Optional[list[dict[str, Any]]] = None # FAT: tool schema + tool_choice: str = "auto" + stream: bool = False + stream_callback: Optional[Callable[[str], None]] = None + extra_body: Optional[dict[str, Any]] = None # FAT: arbitrary params +``` + +**Three distinct fat-struct shapes** are in this file: +1. **Tool call** (id, type, function: {name, arguments}) +2. **Chat message** (role, content, optional tool_calls/tool_call_id/name) +3. **Usage stats** (input_tokens, output_tokens, cache_read, cache_creation) + +**Proposed componentization:** + +```python +# src/openai_schemas.py (new; shared between openai_compatible.py and ai_client.py) + +@dataclass(frozen=True) +class ToolCall: + id: str + type: str = "function" + function: "ToolCallFunction" + +@dataclass(frozen=True) +class ToolCallFunction: + name: str + arguments: str # JSON string + +@dataclass(frozen=True) +class ChatMessage: + role: str # "system" | "user" | "assistant" | "tool" + content: str + tool_calls: Optional[tuple[ToolCall, ...]] = None + tool_call_id: Optional[str] = None + name: Optional[str] = None + +@dataclass(frozen=True) +class UsageStats: + input_tokens: int + output_tokens: int + cache_read_tokens: int = 0 + cache_creation_tokens: int = 0 + +# NormalizedResponse becomes: +@dataclass(frozen=True) +class NormalizedResponse: + text: str + tool_calls: tuple[ToolCall, ...] + usage: UsageStats + raw_response: Any # Unavoidable: SDK-specific + +# OpenAICompatibleRequest becomes: +@dataclass +class OpenAICompatibleRequest: + messages: list[ChatMessage] + model: str + temperature: float = 0.0 + # ... etc + tools: Optional[list[ToolSpec]] = None # Use the §3.1 ToolSpec +``` + +**Call sites to update:** `_send_grok()`, `_send_minimax()`, `_send_llama()` in `ai_client.py` (3 functions); `openai_compatible.py` itself (~5 internal functions). + +**Estimated value:** **HIGH** — The OpenAI chat completion API is well-documented; the schema is stable; the LLM-readable documentation at is the source of truth. The 17 Any usages become 3 well-named dataclasses. + +**Cross-reference to §3.1:** The `tools: Optional[list[ToolSpec]]` field reuses the `ToolSpec` from the `mcp_client.py` refactor. One component, two consumers. + +--- + +### 3.3 P2 — `src/ai_client.py: 7 × ProviderHistory` (41 Any) + +**Current state** (`src/ai_client.py:108-134`): + +```python +_anthropic_history: list[Metadata] = [] +_deepseek_history: list[Metadata] = [] +_minimax_history: list[Metadata] = [] +_qwen_history: list[Metadata] = [] +_grok_history: list[Metadata] = [] +_llama_history: list[Metadata] = [] +# Plus 6 lock variables: +_anthropic_history_lock: threading.Lock = threading.Lock() +_deepseek_history_lock: threading.Lock = threading.Lock() +# ... etc +``` + +Plus the SDK client holders (Patterns 3, "keep as-is"): + +```python +_gemini_client: Optional[genai.Client] = None +_gemini_chat: Any = None +_gemini_cache: Any = None +_deepseek_client: Any = None +_minimax_client: Any = None +_qwen_client: Any = None +_grok_client: Any = None +_llama_client: Any = None +``` + +**Problem:** 7 per-provider history lists + 7 locks = **14 module-level globals**. Each `_send_()` function mutates its own history. The `reset_session()` function knows about all 14. The cross-cutting concern is "history management" but it's spread across 14 variables. + +**Proposed componentization** (componentizing the history aspect; keeping the SDK clients as-is per Pattern 3): + +```python +# src/provider_state.py (new) + +@dataclass +class ProviderHistory: + messages: list[Metadata] = field(default_factory=list) + lock: threading.Lock = field(default_factory=threading.Lock) + + def append(self, message: Metadata) -> None: + with self.lock: + self.messages.append(message) + + def get_all(self) -> list[Metadata]: + with self.lock: + return list(self.messages) + + def replace_all(self, messages: list[Metadata]) -> None: + with self.lock: + self.messages = list(messages) + + def clear(self) -> None: + with self.lock: + self.messages = [] + +# Module-level: one dict instead of 14 globals +_PROVIDER_HISTORIES: dict[str, ProviderHistory] = { + "anthropic": ProviderHistory(), + "deepseek": ProviderHistory(), + "minimax": ProviderHistory(), + "qwen": ProviderHistory(), + "grok": ProviderHistory(), + "llama": ProviderHistory(), +} + +def get_history(provider: str) -> ProviderHistory: + return _PROVIDER_HISTORIES[provider] +``` + +**Call sites to update:** All `_send_()` functions (~6 files in `ai_client.py`); the `reset_session()` function; the `cleanup()` function. **Replaces 14 globals with 1 dict + 1 function.** + +**Estimated value:** **HIGH** — 14 globals → 1 dict + class. Encapsulates the lock + list behind a 4-method interface. Makes the cross-provider pattern visible: every provider has a history + lock; the `_PROVIDER_HISTORIES` dict makes the per-provider table a first-class object. Mirrors the `vendor_capabilities` `dict[tuple[str, str], VendorCapabilities]` pattern exactly. + +**Cross-reference to §3.2:** The `Metadata = list[dict[str, Any]]` in `ProviderHistory.messages` could be tightened to `list[ChatMessage]` (from §3.2) if the cross-provider schema can be unified. Realistic: the LLM-provider history format is **mostly** OpenAI-compatible (`{role, content}`) but with provider-specific extras (`tool_calls` for OpenAI; `reasoning_content` for Anthropic; `parts` for Gemini). A `ProviderHistory` whose `messages` is `list[ChatMessage | dict]` (union type) is realistic for a single-track scope; full unification is a separate refactor. + +--- + +### 3.4 P2 — `src/log_registry.py: Session metadata` (7 Any) + +**Current state** (`src/log_registry.py:58-71`): + +```python +self.data: dict[str, dict[str, Any]] = {} # session_id -> session content + +def get_old_non_whitelisted_sessions(self) -> list[dict[str, Any]]: + ... +``` + +The outer key is `session_id: str`. The inner dict has implicit fields: `path`, `start_time`, `whitelisted`, `metadata`. + +**Proposed componentization:** + +```python +@dataclass(frozen=True) +class SessionMetadata: + message_count: int = 0 + errors: int = 0 + size_kb: int = 0 + whitelisted: bool = False + reason: str = '' + timestamp: Optional[str] = None + +@dataclass(frozen=True) +class Session: + session_id: str + path: str + start_time: str # ISO format + whitelisted: bool = False + metadata: Optional[SessionMetadata] = None + +@dataclass +class LogRegistry: + registry_path: str + data: dict[str, Session] = field(default_factory=dict) # typed! +``` + +**Call sites to update:** `session_logger.py` (`open_session()`, `close_session()`); `log_pruner.py` (`prune_old_logs()`); `gui_2.py` (Log Management panel). + +**Estimated value:** MEDIUM — Self-contained file; isolated change. Eliminates a nested `dict[str, dict[str, Any]]` (2 levels of structural anonymity) in favor of 2 named dataclasses. + +--- + +### 3.5 P3 — `src/api_hooks.py: Generic payload + serialization` (16 Any) + +**Current state** (`src/api_hooks.py:48-134`): + +```python +def _get_app_attr(app: Any, name: str, default: Any = None) -> Any: ... +def _set_app_attr(app: Any, name: str, value: Any) -> None: ... +def _serialize_for_api(obj: Any) -> Any: ... +def broadcast(self, channel: str, payload: dict[str, Any]) -> None: ... +``` + +**Problem:** `_get_app_attr` / `_set_app_attr` are dynamic-dispatch helpers (Pattern 4, "keep as-is"). But `_serialize_for_api` and `broadcast` are the **JSON wire format** — they could be typed. + +**Proposed componentization:** + +```python +# Recursive type for serializable JSON payloads (Python 3.12+ has type; earlier needs TypeAlias) +JsonPrimitive: TypeAlias = str | int | float | bool | None +JsonValue: TypeAlias = JsonPrimitive | list["JsonValue"] | dict[str, "JsonValue"] + +def _serialize_for_api(obj: Any) -> JsonValue: ... + +@dataclass(frozen=True) +class WebSocketMessage: + channel: str + payload: JsonValue + +def broadcast(self, message: WebSocketMessage) -> None: ... +``` + +**Estimated value:** LOW — Internal serialization; lower semantic gain. The `JsonValue` recursive type is the main value; it makes the wire format explicit. + +--- + +## 4. Patterns That Are NOT Componentization Candidates + +These are the `Any` usages that should **stay as-is** (intentional flexibility): + +### 4.1 SDK Client Holders (Pattern 3) + +`_gemini_chat: Any = None`, `_deepseek_client: Any = None`, etc. in `src/ai_client.py`. These are **lazy-initialized** module-level singletons. Each provider's SDK client has a different type (`genai.Client`, `anthropic.Anthropic`, `openai.OpenAI`, etc.). They don't share a base class or Protocol. + +A `ProviderClients` dataclass that wraps all 7 clients would be possible (and is the §3.3 discussion), but the **client types** still have to be `Any` or `Optional[ProviderX]` because the SDKs are heterogeneous. The §3.3 refactor unifies the **history aspect** (which IS homogeneous — 6 providers, all `list[Metadata]` with locks) but leaves the client holders as Pattern 3. + +### 4.2 Dynamic Dispatch (`__getattr__`) (Pattern 4) + +`src/app_controller.py:1273 __getattr__`, `src/gui_2.py:742 __getattr__`, `src/commands.py:43 __getattr__`, `src/models.py:271 __getattr__`. These return `Any` because the delegated object is dynamically selected. The `__getattr__` is a known Python pattern; the return type is genuinely unknown at compile time. + +### 4.3 Generic Serialization (`obj: Any) -> Any`) (Pattern 5) + +`src/api_hooks.py:134 _serialize_for_api`, `src/app_controller.py:2144 _resolve_log_ref`. These process unknown-shaped data. The output shape mirrors the input shape. If the input is "anything from disk", the output is also "anything that can be re-serialized to disk." + +--- + +## 5. The `code_path_audit_20260607` Pre-Requisite + +The `code_path_audit_20260607` track (spec approved 2026-06-07; revised 2026-06-08 for post-4-tracks timing) is now **unblocked**: the 4 foundational tracks it depends on (`qwen_llama_grok`, `data_oriented_error_handling`, `data_structure_strengthening`, `mcp_architecture_refactor`) have shipped (or are archivable). The audit's `trace_action` API will produce per-action profiles showing: + +- Which `Any` usages are in the **hot path** (e.g., `_send_` is called per request) +- Which are in **cold paths** (e.g., `reset_session()` is called per project switch) +- Which are in **initialization-only paths** (e.g., `_load_app_state()` is called once at startup) + +**The fat-struct componentization work is informed by this audit.** A `dict[str, Any]` in a hot path has a higher ROI to componentize than the same shape in a cold path (where the runtime cost is amortized). The `code_path_audit` report's `optimization_candidates.md` should specifically call out the 5 fat-struct candidates in §3 with their per-action cost estimates. + +### 5.1 Coordination Notes + +- The `code_path_audit_20260607` track's spec already mentions "fat struct" patterns indirectly (via the Casey Muratori / Andrew Reece / Ryan Fleury framing). The new `Any-typing componentization` follow-up track can cite the audit's `expensive_ops` index for each fat-struct candidate. +- The audit's `actions/ai_message_lifecycle.tree` will show the call path from `_send_()` → `_reread_file_items()` → `_build_file_diff_text()` (the §3.3 history mutation path). This is the hot path. +- The audit's `actions/discussion_save_load.tree` will show the `project_manager.save_project()` → `json.dumps()` (the §3.4 Session serialization path). + +### 5.2 Sequencing + +| Order | Track | Why | +|---|---|---| +| 1 | `code_path_audit_20260607` (run the audit) | Produces the per-action data needed to prioritize §3's 5 candidates | +| 2 | `any_type_componentization_202606XX` (Tier 1 spec + plan) | Devised by Tier 1 with the audit's output as input | +| 3 | Tier 2 implementation | 6 phases per the proposed track below | + +--- + +## 6. Proposed Follow-up Track: `any_type_componentization_2026MMDD` + +**Suggested name:** `any_type_componentization_2026MMDD` +**Owner:** Tier 1 (spec) → Tier 2 (implementation) +**Priority:** Medium (developer + AI-readability; not a regression blocker) +**Blocked by:** `code_path_audit_20260607` (the audit's report informs the spec) +**Blocks:** None directly; enables follow-up `TypedDict migration` (per the original `data_structure_strengthening` plan §12.1) + +### 6.1 Goals (Priority Order) + +| Priority | Goal | +|---|---| +| **A (primary)** | Convert the 5 fat-struct candidates (§3) into `dataclass(frozen=True)` definitions following the `vendor_capabilities` template | +| **B (architectural)** | Unify the 7 per-provider histories in `ai_client.py` (§3.3) behind a single `ProviderHistory` class + dict | +| **C (documentation)** | Update `conductor/code_styleguides/type_aliases.md` (from `data_structure_strengthening_20260606`) with a new "When to Promote `TypeAlias` to `dataclass`" section | +| **D (forward-looking)** | Re-evaluate the `code_path_audit`'s `expensive_ops` index after the componentization to confirm hot-path costs are reduced | + +### 6.2 Non-Goals (Track Scope Discipline) + +- **NOT** converting all 300 `Any` usages. Only the 5 fat-struct candidates in §3. +- **NOT** converting SDK client holders (Pattern 3, §4.1). They stay as `Any` — heterogeneous types. +- **NOT** changing the `__getattr__` dynamic-dispatch pattern (Pattern 4, §4.2). It stays as `Any` — intentional. +- **NOT** typing the generic serialization functions (Pattern 5, §4.3). They stay as `Any` — input-driven. +- **NOT** changing function signatures at the runtime level. The componentization is type-level + serialization-format-level. + +### 6.3 Suggested Phases + +| Phase | Work | +|---|---| +| 1 | `src/mcp_tool_specs.py` — new module with `ToolParameter` + `ToolSpec`; convert `MCP_TOOL_SPECS` to `list[ToolSpec]`; update `get_tool_schemas()`, `TOOL_NAMES`, dispatch map | +| 2 | `src/openai_schemas.py` — new module with `ToolCall` + `ChatMessage` + `UsageStats`; convert `NormalizedResponse` and `OpenAICompatibleRequest`; update `_send_grok`/`_send_minimax`/`_send_llama` | +| 3 | `src/provider_state.py` — new module with `ProviderHistory`; convert 7 histories + 7 locks to dict; update all `_send_()` and `reset_session()` | +| 4 | `src/log_registry.py` — convert `Session` + `SessionMetadata`; update `session_logger.py` + `log_pruner.py` + `gui_2.py` | +| 5 | `src/api_hooks.py` — add `JsonValue` recursive type; convert `WebSocketMessage`; update `broadcast()` | +| 6 | Styleguide update + audit report + archive | + +### 6.4 Estimated Scope (per the `data_structure_strengthening` precedent) + +- **6 source files modified** (5 fat-struct files + `ai_client.py` for the history unification) +- **3 new source files** (`mcp_tool_specs.py`, `openai_schemas.py`, `provider_state.py`) +- **3 new test files** (per the TDD red-first protocol) +- **1 styleguide update** (`type_aliases.md` — "When to Promote `TypeAlias` to `dataclass`" section) +- **1 end-of-track report** (`docs/reports/TRACK_COMPLETION_any_type_componentization_.md`) +- **~30-50 atomic commits** (vs. `data_structure_strengthening`'s 22, because the per-file refactor is more complex) +- **Audit followup**: re-run `code_path_audit_20260607` to confirm hot-path costs are reduced + +### 6.5 Convention to Document (styleguide) + +The new styleguide section (per `data_structure_strengthening`'s `conductor/code_styleguides/type_aliases.md`): + +```markdown +## When to Promote `TypeAlias` to `dataclass` + +A `TypeAlias` like `Metadata: TypeAlias = dict[str, Any]` is a **rename** — the +underlying shape is unchanged. This is appropriate when: + +- The shape is **truly open** (extra keys are allowed; the dict is a bag) +- The shape is **self-describing** (caller reads `entry.get("path")` without + needing to know which keys are required) +- The shape is **transient** (JSON-serialized, then deserialized; no + in-memory struct invariants) + +Promote to `dataclass(frozen=True)` when: + +- The shape has **a known set of required fields** with **specific types** + (e.g., a chat completion's `usage: UsageStats` with 4 int fields) +- Multiple sites access the same fields with **string keys** + (`payload["usage"]["input_tokens"]` × 5 sites = 5× the bug surface) +- The shape is **stable across serialization boundaries** (i.e., the + on-disk / on-wire format is documented and won't change per-call) +- The shape is **shared across multiple modules** (the same schema is + used by `ai_client.py` and `openai_compatible.py` and `api_hooks.py`) + +The reference pattern is `src/vendor_capabilities.py`. When in doubt, +follow that template: `frozen=True` dataclass + module-level registry + +factory functions. + +The fat-struct candidates identified in +`docs/reports/ANY_TYPE_AUDIT_20260621.md` (§3) are the canonical +worked examples. +``` + +--- + +## 7. Out of Scope (Explicit) + +The following are intentionally NOT in this report's recommendations: + +- **All 300 `Any` usages as a flat list.** The 5-pattern taxonomy (§2.2) groups them; the §3 fat-struct candidates are the actionable subset. +- **Conversion of `dict[str, Any]` to `TypedDict`.** Per the original `data_structure_strengthening` plan §10, this is deferred. The proposed `dataclass(frozen=True)` approach is simpler and addresses the same problem (semantic naming). +- **Conversion of `dict[str, Any]` to Pydantic models.** The project doesn't use Pydantic for these shapes; introducing it would be a much larger architectural decision. +- **The 23 lower-impact files** (those with 1-9 weak `dict[str, Any]` sites each). These are deferred; the audit's `expensive_ops` index will re-prioritize them after the hot-path fat structs are componentized. +- **Re-typing the existing `TypeAlias` definitions** (e.g., making `Metadata: TypeAlias = dict[str, Any]` a `class Metadata(dict)`). The aliases document intent; converting them to types is a separate decision. + +--- + +## 8. Cross-References + +- `src/type_aliases.py` — the 10 `TypeAlias` definitions + `FileItemsDiff` `NamedTuple` (per `data_structure_strengthening_20260606`) +- `src/result_types.py` — `Result[T]`, `ErrorInfo`, `NilPath`, `NilRAGState` (per `data_oriented_error_handling_20260606`) +- `src/vendor_capabilities.py` — the reference pattern (frozen dataclass + module-level registry) +- `src/code_path_audit.py` — future home of the `code_path_audit_20260607` tool (per the existing spec) +- `conductor/code_styleguides/data_oriented_design.md` — the canonical DOD reference (per the `nagent_review_20260608` framing) +- `conductor/code_styleguides/error_handling.md` — the `Result[T]` convention (complementary) +- `conductor/code_styleguides/type_aliases.md` — the type-alias convention (per `data_structure_strengthening_20260606`) +- `docs/reports/TRACK_COMPLETION_data_structure_strengthening_20260606.md` — the parent track +- `docs/reports/EXCEPTION_HANDLING_AUDIT_20260616.md` — the precedent for this audit (211 sites → audit report → migration plan) +- `conductor/tracks/code_path_audit_20260607/` — the prerequisite track (post-4-tracks timing) +- `conductor/tracks/nagent_review_20260608/` — the Casey Muratori / Ryan Fleury / Andrew Reece framing + +--- + +## 9. Conclusion + +The `data_structure_strengthening_20260606` track established the `TypeAlias` convention for naming shapes. The next logical step is **promoting the hot-path fat structs to `dataclass(frozen=True)` definitions** — the same `vendor_capabilities` pattern that the user pointed to. This report identifies 5 high-value candidates (§3), the patterns that should NOT be touched (§4), and a 6-phase proposed follow-up track (§6) that is informed by the prerequisite `code_path_audit_20260607` work. + +**Tier 1 is expected to devise the follow-up track spec** with the audit's per-action data as input. The spec's scope, priority, and exact phasing can be tuned to the audit's findings. The track name (`any_type_componentization_2026MMDD`) and the 6 phases in §6.3 are starting points. + +The single most important insight: **the `vendor_capabilities.py` pattern works because it identifies a `tuple[str, str]` (vendor × model) as a first-class key in a `dict[tuple, VendorCapabilities]`. The same pattern applied to the 5 fat-struct candidates in §3 produces the same win: shape becomes addressable, dict-key-lookups become field-access, and the static analysis can verify the contract.**