refactor(metadata_promotion): Phases 3,4,6,9,10 proper dataclass migrations

TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md,
conductor/tier2/githooks/forbidden-files.txt,
conductor/tracks/tier2_leak_prevention_20260620/spec.md,
conductor/code_styleguides/data_oriented_design.md,
conductor/code_styleguides/error_handling.md,
conductor/code_styleguides/type_aliases.md before Phases 3-10.

Forward-only progress on metadata_promotion_20260624 Phases 3,4,6,9,10
(did NOT modify or revert existing commits; all work adds to the timeline).

Per-site migrations to direct dataclass attribute access:

Phase 3 (CommsLogEntry) - src/app_controller.py:2278,2303,2311:
  Added `comms_entry = CommsLogEntry.from_dict(entry)` after payload
  extraction; replaced dict access with `.source_tier`, `.model`.

Phase 4 (HistoryMessage):
  - src/synthesis_formatter.py:24,37: added HistoryMessage.from_dict
    conversion for msg dicts in format_takes_diff.
  - src/gui_2.py:7794: added HistoryMessage.from_dict conversion for
    disc_entries[-1] content comparison; added HistoryMessage import.

Phase 6 (UsageStats) - src/app_controller.py:2299-2311:
  Added `u_stats = models.UsageStats(...)` with field-name mapping
  (dict cache_read_input_tokens -> UsageStats.cache_read_tokens).
  Replaced dict access with `.input_tokens`, `.output_tokens`.

Phase 9 (RAGChunk) - src/app_controller.py:251,4171, src/ai_client.py:3262:
  RAG search returns wire-format dicts with path nested in metadata
  (mismatches RAGChunk schema which has path at top level).
  Per-site resolution: direct dict access with explicit key checks.
  Documented schema mismatch in commit.

Phase 10 (SessionInsights) - src/gui_2.py:4926-4934:
  Added `SessionInsights.from_dict(...)` for session insights dict;
  replaced .get() pattern with direct attribute access.

Verification:
- 58 tests pass (synthesis_formatter, session_insights, comms_log_entry,
  history_message, metadata_promotion_phase1, ticket_queue,
  file_item_model, rag_engine)

Open blockers for Tier 1:
- src/type_aliases.py:91 ToolCall: TypeAlias = Metadata should be
  TypeAlias = "openai_schemas.ToolCall" (Phase 0 typo; blocks Phase 7)
- src/models.py:537 FileItem.custom_slices: list[dict] blocks
  CustomSlice migration (frozen dataclass can't be mutated)
- src/rag_engine.py:367 search() returns List[Dict] not List[RAGChunk]
  (return-type cascade needed)
- ToolDefinition not wired into per-vendor tool builders (sites
  construct wire dicts)
- Remaining Phase 10 aggregates (DiscussionSettings, MMAUsageStats,
  ProviderPayload, UIPanelConfig, PathInfo, ContextPreset) deferred
This commit is contained in:
ed
2026-06-25 19:20:03 -04:00
parent 5e2d0eb7aa
commit f0a6b32704
4 changed files with 46 additions and 29 deletions
+22 -11
View File
@@ -247,8 +247,10 @@ def _api_generate(controller: 'AppController', req: GenerateRequest) -> Metadata
if rag_result.ok and rag_result.data:
context_block = "## Retrieved Context\n\n"
for i, chunk in enumerate(rag_result.data):
path = chunk.get("metadata", {}).get("path", "unknown")
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
chunk_meta = chunk["metadata"] if "metadata" in chunk else {}
path = chunk_meta["path"] if "path" in chunk_meta else "unknown"
doc = chunk["document"] if "document" in chunk else ""
context_block += f"### Chunk {i+1} (Source: {path})\n{doc}\n\n"
user_msg = context_block + user_msg
elif not rag_result.ok:
controller._last_request_errors.append(("rag_search", rag_result.errors[0]))
@@ -2269,13 +2271,14 @@ class AppController:
kind = entry.get("kind", entry.get("type", ""))
payload = entry.get("payload", {})
ts = entry.get("ts", "")
comms_entry = CommsLogEntry.from_dict(entry)
if kind == 'tool_call':
tid = payload.get('id') or payload.get('call_id')
script = payload.get('script') or json.dumps(payload.get('args', {}), indent=1)
script = _resolve_log_ref(script, session_dir)
entry_obj = {
'source_tier': entry['source_tier'] if 'source_tier' in entry else 'main',
'source_tier': comms_entry.source_tier,
'script': script,
'result': '', # Waiting for result
'ts': ts
@@ -2298,17 +2301,23 @@ class AppController:
if kind == 'response' and 'usage' in payload:
u = payload['usage']
u_stats = models.UsageStats(
input_tokens=u.get('input_tokens', 0) or 0,
output_tokens=u.get('output_tokens', 0) or 0,
cache_read_tokens=u.get('cache_read_input_tokens', 0) or 0,
cache_creation_tokens=u.get('cache_creation_input_tokens', 0) or 0,
)
for k in ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens', 'total_tokens']:
if k in new_usage: new_usage[k] += u.get(k, 0) or 0
tier = entry['source_tier'] if 'source_tier' in entry else 'main'
tier = comms_entry.source_tier
if tier in new_mma_usage:
new_mma_usage[tier]['input'] += u.get('input_tokens', 0) or 0
new_mma_usage[tier]['output'] += u.get('output_tokens', 0) or 0
new_mma_usage[tier]['input'] += u_stats.input_tokens
new_mma_usage[tier]['output'] += u_stats.output_tokens
new_token_history.append({
'time': ts,
'input': u.get('input_tokens', 0) or 0,
'output': u.get('output_tokens', 0) or 0,
'model': entry['model'] if 'model' in entry else 'unknown'
'input': u_stats.input_tokens,
'output': u_stats.output_tokens,
'model': comms_entry.model
})
if kind == "history_add":
@@ -4160,8 +4169,10 @@ class AppController:
if rag_result.ok and rag_result.data:
context_block = "## Retrieved Context\n\n"
for i, chunk in enumerate(rag_result.data):
path = chunk.get("metadata", {}).get("path", "unknown")
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
chunk_meta = chunk["metadata"] if "metadata" in chunk else {}
path = chunk_meta["path"] if "path" in chunk_meta else "unknown"
doc = chunk["document"] if "document" in chunk else ""
context_block += f"### Chunk {i+1} (Source: {path})\n{doc}\n\n"
user_msg = context_block + user_msg
elif not rag_result.ok:
self._last_request_errors.append(("rag_search", rag_result.errors[0]))