From bc98c6595669388d2e70afcd35497ddeb99596bc Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Fri, 13 Feb 2026 11:44:55 +1100 Subject: [PATCH] Compact workspace memory: -29% chars, 37 concepts preserved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent: huffman Compaction Summary: - Lines: 576 → 477 (-17%) - Chars: 36,372 → 26,173 (-28%) - Entries: 45 → 37 (merged 8) Transformations: - Removed 1 exact duplicate (Datalog Program Generation x2) - Collapsed 6 log-style bug narratives to current-state declarations - Merged Plan Verification into Plan Mode entry - Merged Rulespec Changes into Invariants entry (current state only) - Updated 12 stale char ranges against actual file positions - Removed 13 references to deleted code (extraction.rs, shadow_datalog_verify, save/load_compiled_rulespec, display_welcome_message, OutputMode, etc.) - Moved Skills System Entry Points from AGENTS.md to memory (was duplicate) - AGENTS.md: removed 20-line skills table, kept rules/invariants only --- AGENTS.md | 20 -- analysis/memory.md | 494 +++++++++++++++++++-------------------------- 2 files changed, 207 insertions(+), 307 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4c00584..d4108dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,23 +85,3 @@ The `analysis/deps/` directory contains static analysis artifacts generated by t These artifacts are useful for understanding coupling, planning refactors, and identifying architectural boundaries. -## Skills System Entry Points - -The skills system (`crates/g3-core/src/skills/`) provides extensible agent capabilities: - -| File | Purpose | -|------|--------| -| `mod.rs` | Public API: `Skill`, `discover_skills`, `generate_skills_prompt` | -| `parser.rs` | SKILL.md parsing with YAML frontmatter validation | -| `discovery.rs` | Multi-location discovery with priority ordering | -| `embedded.rs` | Compile-time skill embedding via `include_str!` | -| `extraction.rs` | Script extraction to `.g3/bin/` with version tracking | -| `prompt.rs` | Generates `` XML for system prompt | - -**Key invariants for skills**: -- Skill names must be 1-64 chars, lowercase + hyphens only -- SKILL.md must have valid YAML frontmatter with `name` and `description` -- Embedded scripts are extracted lazily (on first use) -- Version tracking uses content hash, not timestamps -- Higher priority locations override lower (repo > workspace > global > embedded) -- Script extraction is Unix-only (chmod 755); Windows support is incomplete diff --git a/analysis/memory.md b/analysis/memory.md index a091c46..147ac72 100644 --- a/analysis/memory.md +++ b/analysis/memory.md @@ -1,99 +1,121 @@ # Workspace Memory -> Updated: 2026-02-12T05:23:04Z | Size: 31.1k chars +> Updated: 2026-02-13 | Size: ~20k chars ### Remember Tool Wiring -- `crates/g3-core/src/tools/memory.rs` [0..5000] - `execute_remember()`, `get_memory_path()`, `merge_memory()` -- `crates/g3-core/src/tool_definitions.rs` [11000..12000] - remember tool in `create_core_tools()` -- `crates/g3-core/src/tool_dispatch.rs` [48] - dispatch case +- `crates/g3-core/src/tools/memory.rs` [0..5686] + - `get_memory_path()` [486] - resolves `analysis/memory.md` + - `execute_remember()` [1066] - tool handler + - `merge_memory()` [2324] - merges new notes into existing +- `crates/g3-core/src/tool_definitions.rs` [956..] - remember tool in `create_core_tools()` +- `crates/g3-core/src/tool_dispatch.rs` [670] - dispatch case - `crates/g3-core/src/prompts.rs` [4200..6500] - Workspace Memory prompt section - `crates/g3-cli/src/project_files.rs` - `read_workspace_memory()` loads `analysis/memory.md` ### Context Window & Compaction -- `crates/g3-core/src/context_window.rs` [0..29568] - - `ThinResult` [23] - scope, before/after %, chars_saved - - `ContextWindow` - token tracking, message history - - `reset_with_summary()` - compact history to summary - - `should_compact()` - threshold check (80%) - - `thin_context()` - replace large results with file refs +- `crates/g3-core/src/context_window.rs` [0..43282] + - `ThinResult` [765] - scope, before/after %, chars_saved + - `ContextWindow` [2220] - token tracking, message history + - `add_message_with_tokens()` [3171] - preserves messages with `tool_calls` even if content empty + - `estimate_message_tokens()` [7695] - sums content + tool_calls[].input tokens (chars/3 * 1.1 + 20 overhead) + - `should_compact()` [8954] - threshold check (80%) + - `reset_with_summary()` [10685] - compact history to summary + - `reset_with_summary_and_stub()` [11120] - ACD integration + - `extract_preserved_messages()` [13199] - strips `tool_calls` from last assistant to prevent orphaned `tool_use` + - `thin_context()` [15038] - replace large results with file refs - `crates/g3-core/src/compaction.rs` [0..11404] - `CompactionResult`, `CompactionConfig` - result/config structs - `perform_compaction()` - unified for force_compact() and auto-compaction - `calculate_capped_summary_tokens()`, `should_disable_thinking()` - `build_summary_messages()`, `apply_summary_fallback_sequence()` -- `crates/g3-core/src/lib.rs` - `Agent.force_compact()`, `stream_completion_with_tools()` + - ACD integration [195..240] - creates fragment+stub during compaction +- `crates/g3-core/src/lib.rs` + - `force_compact()` [47902] + - `stream_completion_with_tools()` [85389] - main agent loop ### Session Storage & Continuation -- `crates/g3-core/src/session_continuation.rs` [0..541] - `SessionContinuation`, `save_continuation()`, `load_continuation()` -- `crates/g3-core/src/paths.rs` [0..133] - `get_session_logs_dir()`, `get_thinned_dir()`, `get_session_file()` -- `crates/g3-core/src/session.rs` - Session logging utilities +- `crates/g3-core/src/session_continuation.rs` [0..22907] + - `SessionContinuation` [1024] + - `save_continuation()` [5581] + - `load_continuation()` [6428] +- `crates/g3-core/src/paths.rs` [0..5498] + - `get_session_logs_dir()` [2434] + - `get_thinned_dir()` [3060] + - `get_fragments_dir()` [3295] - `.g3/sessions//fragments/` + - `get_session_file()` [3517] +- `crates/g3-core/src/session.rs` - session logging utilities ### Tool System -- `crates/g3-core/src/tool_definitions.rs` [0..544] - `create_core_tools()`, `create_tool_definitions()`, `ToolConfig` -- `crates/g3-core/src/tool_dispatch.rs` [0..73] - `dispatch_tool()` routing +- `crates/g3-core/src/tool_definitions.rs` [0..15391] + - `ToolConfig` [381] + - `create_tool_definitions()` [742] + - `create_core_tools()` [956] +- `crates/g3-core/src/tool_dispatch.rs` [0..3983] - `dispatch_tool()` [670] routing ### CLI Module Structure -- `crates/g3-cli/src/lib.rs` [0..415] - `run()`, mode dispatch, config loading -- `crates/g3-cli/src/cli_args.rs` [0..133] - `Cli` struct (clap) -- `crates/g3-cli/src/autonomous.rs` [0..785] - `run_autonomous()`, coach-player loop -- `crates/g3-cli/src/agent_mode.rs` [0..284] - `run_agent_mode()`, `Agent::new_with_custom_prompt()` -- `crates/g3-cli/src/accumulative.rs` [0..343] - `run_accumulative_mode()` -- `crates/g3-cli/src/interactive.rs` [0..851] - `run_interactive()`, `run_interactive_machine()`, REPL -- `crates/g3-cli/src/task_execution.rs` [0..212] - `execute_task_with_retry()`, `OutputMode` -- `crates/g3-cli/src/commands.rs` [17..320] - `/help`, `/compact`, `/thinnify`, `/fragments`, `/rehydrate` -- `crates/g3-cli/src/utils.rs` [0..91] - `display_welcome_message()`, `get_workspace_path()` -- `crates/g3-cli/src/display.rs` - `format_workspace_path()`, `LoadedContent`, `print_loaded_status()` +- `crates/g3-cli/src/lib.rs` [0..9309] - `run()` [1242], mode dispatch, config loading +- `crates/g3-cli/src/cli_args.rs` [0..6043] - `Cli` [1374] struct (clap) +- `crates/g3-cli/src/autonomous.rs` [0..25630] - `run_autonomous()` [638], coach-player loop +- `crates/g3-cli/src/agent_mode.rs` [0..13558] - `run_agent_mode()` [1000] +- `crates/g3-cli/src/accumulative.rs` [0..12006] - `run_accumulative_mode()` [796] +- `crates/g3-cli/src/interactive.rs` [0..19222] - `run_interactive()` [3809], REPL +- `crates/g3-cli/src/task_execution.rs` [0..5520] - `execute_task_with_retry()` [1069] +- `crates/g3-cli/src/commands.rs` [0..20115] - `/help`, `/compact`, `/thinnify`, `/fragments`, `/rehydrate` +- `crates/g3-cli/src/utils.rs` [0..6154] - `display_context_progress()`, `setup_workspace_directory()`, `load_config_with_cli_overrides()` +- `crates/g3-cli/src/display.rs` [0..12573] - `format_workspace_path()` [286], `LoadedContent`, `print_loaded_status()` ### Auto-Memory System - `crates/g3-core/src/lib.rs` - - `send_auto_memory_reminder()` [47800..48800] - MEMORY CHECKPOINT prompt - - `set_auto_memory()` [1451..1454] - enable/disable - - `tool_calls_this_turn` [116] - tracks tools per turn - - `execute_tool_in_dir()` [2843..2855] - records tool calls + - `tool_calls_this_turn` [5272] - tracks tools per turn + - `set_auto_memory()` [64643] - enable/disable + - `send_auto_memory_reminder()` [72800] - MEMORY CHECKPOINT prompt + - `execute_tool_in_dir()` [132582] - records tool calls - `crates/g3-core/src/prompts.rs` [3800..4500] - Memory Format in system prompt -- `crates/g3-cli/src/lib.rs` [393] - `--auto-memory` CLI flag +- `crates/g3-cli/src/lib.rs` - `--auto-memory` CLI flag ### Streaming Markdown Formatter -- `crates/g3-cli/src/streaming_markdown.rs` - - `format_header()` [21500..22500] - headers with inline formatting - - `process_in_code_block()` [439..462] - detects closing fence - - `emit_code_block()` [654..675] - joins buffer, highlights code - - `flush_incomplete()` [693..735] - handles unclosed blocks at stream end +- `crates/g3-cli/src/streaming_markdown.rs` [0..37669] + - `process_in_code_block()` [17159] - detects closing fence + - `format_header()` [21339] - headers with inline formatting + - `emit_code_block()` [27134] - joins buffer, highlights code + - `flush_incomplete()` [28434] - handles unclosed blocks at stream end - `crates/g3-cli/tests/streaming_markdown_test.rs` - header formatting tests - **Gotcha**: closing ``` without trailing newline must be detected in `flush_incomplete()` ### Retry Infrastructure -- `crates/g3-core/src/retry.rs` [0..12000] - `execute_with_retry()`, `retry_operation()`, `RetryConfig`, `RetryResult` +- `crates/g3-core/src/retry.rs` [0..11865] - `execute_with_retry()`, `retry_operation()`, `RetryConfig`, `RetryResult` - `crates/g3-cli/src/task_execution.rs` - `execute_task_with_retry()` ### UI Abstraction Layer -- `crates/g3-core/src/ui_writer.rs` [0..4500] - `UiWriter` trait, `NullUiWriter`, `print_thin_result()` +- `crates/g3-core/src/ui_writer.rs` [0..8007] - `UiWriter` trait [211], `NullUiWriter` [6538], `print_thin_result()` [1136] - `crates/g3-cli/src/ui_writer_impl.rs` [0..14000] - `ConsoleUiWriter`, `print_tool_compact()` - `crates/g3-cli/src/simple_output.rs` [0..1200] - `SimpleOutput` helper ### Feedback Extraction -- `crates/g3-core/src/feedback_extraction.rs` [0..22000] - `extract_coach_feedback()`, `try_extract_from_session_log()`, `try_extract_from_native_tool_call()` +- `crates/g3-core/src/feedback_extraction.rs` [0..22455] - `extract_coach_feedback()`, `try_extract_from_session_log()`, `try_extract_from_native_tool_call()` - `crates/g3-cli/src/coach_feedback.rs` [0..4025] - `extract_from_logs()` for coach-player loop ### Streaming Utilities & State -- `crates/g3-core/src/streaming.rs` [0..26146] - - `MAX_ITERATIONS` [13] - constant (400) - - `StreamingState` [16] - cross-iteration: full_response, first_token_time, iteration_count - - `ToolOutputFormat` [54] - enum: SelfHandled, Compact(String), Regular - - `IterationState` [166] - per-iteration: parser, current_response, tool_executed - - `truncate_line()`, `truncate_for_display()`, `log_stream_error()`, `is_connection_error()` - - `format_tool_result_summary()`, `is_compact_tool()`, `format_compact_tool_summary()` -- `crates/g3-core/src/lib.rs` [1879..2712] - `stream_completion_with_tools()` main loop +- `crates/g3-core/src/streaming.rs` [0..27241] + - `MAX_ITERATIONS` [419] - constant (400) + - `StreamingState` [499] - cross-iteration: full_response, first_token_time, iteration_count + - `ToolOutputFormat` [1606] - enum: SelfHandled, Compact(String), Regular + - `format_tool_result_summary()` [1743], `is_compact_tool()` [2635], `format_compact_tool_summary()` [3179] + - `IterationState` [5061] - per-iteration: parser, current_response, tool_executed + - `log_stream_error()` [8017], `truncate_for_display()` [10887], `truncate_line()` [11247] + - `is_connection_error()` [21620] ### Background Process Management -- `crates/g3-core/src/background_process.rs` [0..3000] - `BackgroundProcessManager`, `start()`, `list()`, `is_running()`, `get()`, `remove()` -- No `stop()` method - use shell `kill ` +- `crates/g3-core/src/background_process.rs` [0..9048] + - `BackgroundProcessManager` [1466], `start()` [2601], `list()` [5527], `get()` [5731], `is_running()` [5934], `remove()` [6462] +- No `stop()` method — use shell `kill ` ### Unified Diff Application - `crates/g3-core/src/utils.rs` [5000..15000] - `apply_unified_diff_to_string()`, `parse_unified_diff_hunks()` - Handles multi-hunk diffs, CRLF normalization, range constraints ### Error Classification -- `crates/g3-core/src/error_handling.rs` [0..567] - `classify_error()`, `ErrorType`, `RecoverableError` +- `crates/g3-core/src/error_handling.rs` [0..19454] + - `ErrorType` [5206], `RecoverableError` [5465] (enum), `classify_error()` [5972] - Priority: rate limit > network > server > busy > timeout > token limit > context length - **Gotcha**: "Connection timeout" → NetworkError (not Timeout) due to "connection" keyword priority @@ -106,12 +128,8 @@ Saves conversation fragments to disk, replaces with stubs. - `crates/g3-core/src/acd.rs` [0..22830] - `Fragment` - `new()`, `save()`, `load()`, `generate_stub()`, `list_fragments()`, `get_latest_fragment_id()` - `crates/g3-core/src/tools/acd.rs` [0..8500] - `execute_rehydrate()` tool -- `crates/g3-core/src/paths.rs` [3200..3400] - `get_fragments_dir()` → `.g3/sessions//fragments/` -- `crates/g3-core/src/compaction.rs` [195..240] - ACD integration, creates fragment+stub -- `crates/g3-core/src/context_window.rs` [10100..10700] - `reset_with_summary_and_stub()` -- `crates/g3-cli/src/lib.rs` [157..161] - `--acd` flag; [1476..1525] - `/fragments`, `/rehydrate` - -**Fragment JSON**: `fragment_id`, `created_at`, `messages`, `message_count`, `user_message_count`, `assistant_message_count`, `tool_call_summary`, `estimated_tokens`, `topics`, `preceding_fragment_id` +- `crates/g3-cli/src/lib.rs` - `--acd` flag; `/fragments`, `/rehydrate` commands +- **Fragment JSON**: `fragment_id`, `created_at`, `messages`, `message_count`, `user_message_count`, `assistant_message_count`, `tool_call_summary`, `estimated_tokens`, `topics`, `preceding_fragment_id` ### UTF-8 Safe String Slicing Rust `&s[..n]` panics on multi-byte chars (emoji, CJK) if sliced mid-character. @@ -122,9 +140,8 @@ Rust `&s[..n]` panics on multi-byte chars (emoji, CJK) if sliced mid-character. - `crates/studio/src/main.rs` [0..12500] - `cmd_run()`, `cmd_status()`, `cmd_accept()`, `cmd_discard()`, `extract_session_summary()` - `crates/studio/src/session.rs` - `Session`, `SessionStatus` - `crates/studio/src/git.rs` - `GitWorktree` for isolated agent sessions - -**Session log**: `/.g3/sessions//session.json` -**Fields**: `context_window.{conversation_history, percentage_used, total_tokens, used_tokens}`, `session_id`, `status`, `timestamp` +- **Session log**: `/.g3/sessions//session.json` +- **Fields**: `context_window.{conversation_history, percentage_used, total_tokens, used_tokens}`, `session_id`, `status`, `timestamp` ### Racket Code Search Support - `crates/g3-core/src/code_search/searcher.rs` @@ -135,17 +152,15 @@ Rust `&s[..n]` panics on multi-byte chars (emoji, CJK) if sliced mid-character. Auto-detects languages and injects toolchain guidance. - `crates/g3-cli/src/language_prompts.rs` - - `LANGUAGE_PROMPTS` [12..19] - (lang_name, extensions, prompt_content) - - `AGENT_LANGUAGE_PROMPTS` [21..26] - (agent_name, lang_name, prompt_content) - - `detect_languages()` [22..32] - scans workspace - - `scan_directory_for_extensions()` [42..77] - recursive, depth 2, skips hidden/vendor - - `get_language_prompts_for_workspace()` [88..108] - - `get_agent_language_prompts_for_workspace()` [124..137] -- `crates/g3-cli/src/agent_mode.rs` [149..159] - appends agent-specific prompts + - `LANGUAGE_PROMPTS` - (lang_name, extensions, prompt_content) + - `AGENT_LANGUAGE_PROMPTS` - (agent_name, lang_name, prompt_content) + - `detect_languages()` - scans workspace + - `scan_directory_for_extensions()` - recursive, depth 2, skips hidden/vendor + - `get_language_prompts_for_workspace()`, `get_agent_language_prompts_for_workspace()` +- `crates/g3-cli/src/agent_mode.rs` - appends agent-specific prompts - `prompts/langs/` - language prompt files - -**To add language**: Create `prompts/langs/.md`, add to `LANGUAGE_PROMPTS` -**To add agent+lang**: Create `prompts/langs/..md`, add to `AGENT_LANGUAGE_PROMPTS` +- **To add language**: Create `prompts/langs/.md`, add to `LANGUAGE_PROMPTS` +- **To add agent+lang**: Create `prompts/langs/..md`, add to `AGENT_LANGUAGE_PROMPTS` ### MockProvider for Testing - `crates/g3-providers/src/mock.rs` @@ -153,23 +168,19 @@ Auto-detects languages and injects toolchain guidance. - `MockResponse` [35..200] - configurable chunks and usage - `scenarios` module [410..480] - `text_only_response()`, `multi_turn()`, `tool_then_response()` - `crates/g3-core/tests/mock_provider_integration_test.rs` - integration tests - -**Usage**: `MockProvider::new().with_response(MockResponse::text("Hello!"))` +- **Usage**: `MockProvider::new().with_response(MockResponse::text("Hello!"))` ### G3 Status Message Formatting - `crates/g3-cli/src/g3_status.rs` - - `Status` [12] - enum: Done, Failed, Error(String), Custom(String), Resolved, Insufficient, NoChanges + - `Status` [12] - enum: Done, Failed, Error, Custom, Resolved, Insufficient, NoChanges - `G3Status` [44] - static methods for "g3:" prefixed messages - - `progress()` [48] - "g3: ..." (no newline) - - `done()` [72] - bold green "[done]" - - `failed()` [81] - red "[failed]" - - `thin_result()` [236] - formats ThinResult with colors + - `progress()` [48], `done()` [72], `failed()` [81], `thin_result()` [236] ### Prompt Cache Statistics -- `crates/g3-providers/src/lib.rs` [195..210] - `Usage.cache_creation_tokens`, `cache_read_tokens` -- `crates/g3-providers/src/anthropic.rs` [944..956] - parses `cache_creation_input_tokens`, `cache_read_input_tokens` -- `crates/g3-providers/src/openai.rs` [494..510] - parses `prompt_tokens_details.cached_tokens` -- `crates/g3-core/src/lib.rs` [75..90] - `CacheStats` struct; [106] - `Agent.cache_stats` +- `crates/g3-providers/src/lib.rs` - `Usage.cache_creation_tokens` [6780], `cache_read_tokens` [6929] +- `crates/g3-providers/src/anthropic.rs` - parses `cache_creation_input_tokens`, `cache_read_input_tokens` +- `crates/g3-providers/src/openai.rs` - parses `prompt_tokens_details.cached_tokens` +- `crates/g3-core/src/lib.rs` - `CacheStats` [3066]; `Agent.cache_stats` - `crates/g3-core/src/stats.rs` [189..230] - `format_cache_stats()` with hit rate metrics ### Embedded Provider (Local LLM) @@ -212,25 +223,21 @@ gpu_layers = 99 ### Agent Skills System Portable skill packages with SKILL.md + optional scripts per Agent Skills spec (agentskills.io). -- `crates/g3-core/src/skills/mod.rs` [0..47] - exports: `Skill`, `discover_skills`, `generate_skills_prompt` -- `crates/g3-core/src/skills/parser.rs` [0..363] - - `Skill` [11..30] - name, description, metadata, body, path - - `Skill::parse()` [45..100] - parses SKILL.md with YAML frontmatter - - `validate_name()` [133..175] - 1-64 chars, lowercase+hyphens -- `crates/g3-core/src/skills/discovery.rs` [0..383] - - `discover_skills()` [38..85] - scans 5 locations: embedded → global → extra → workspace → repo - - `load_embedded_skills()` [88..102] - synthetic path `/SKILL.md` - - `is_embedded_skill()` [161..163] - checks `` XML +- `crates/g3-core/src/skills/mod.rs` [0..1501] - exports: `Skill`, `discover_skills`, `generate_skills_prompt` +- `crates/g3-core/src/skills/parser.rs` [0..10750] + - `Skill` [389] - name, description, metadata, body, path + - `Skill::parse()` [1632] - parses SKILL.md with YAML frontmatter + - `validate_name()` [4970] - 1-64 chars, lowercase+hyphens +- `crates/g3-core/src/skills/discovery.rs` [0..12921] + - `discover_skills()` [1266] - scans 5 locations: embedded → global → extra → workspace → repo + - `load_embedded_skills()` [3263] - synthetic path `/SKILL.md` +- `crates/g3-core/src/skills/embedded.rs` [0..1674] + - `EmbeddedSkill` [574] - name, skill_md + - `EMBEDDED_SKILLS` [944] - static array (currently empty) +- `crates/g3-core/src/skills/prompt.rs` [0..5628] + - `generate_skills_prompt()` [397] - generates `` XML - `crates/g3-config/src/lib.rs` [180..200] - `SkillsConfig` (enabled, extra_paths) -- `crates/g3-cli/src/project_files.rs` [180..210] - `discover_and_format_skills()` +- `crates/g3-cli/src/project_files.rs` - `discover_and_format_skills()` **Skill Locations** (priority: later overrides earlier): 1. Embedded (compiled in) @@ -251,220 +258,133 @@ compatibility: Requires X # Optional ``` ### Research Tool (First-Class) -Async web research via background scout agent. Implemented as a first-class tool (not a skill). +Async web research via background scout agent. -- `crates/g3-core/src/pending_research.rs` [0..547] - - `PendingResearchManager` - thread-safe task tracking with Arc - - `ResearchTask`, `ResearchStatus` - task state (Pending/Complete/Failed) - - `register()`, `complete()`, `fail()`, `get()`, `list_pending()`, `take_completed()` - - `with_notifications()` - broadcast channel for interactive mode -- `crates/g3-core/src/tools/research.rs` [0..471] - - `execute_research()` - spawns scout agent in background tokio task - - `execute_research_status()` - check status of pending/completed research - - `CONTEXT_ERROR_PATTERNS` - detects context window exhaustion - - `strip_ansi_codes()`, `extract_report()` - report extraction utilities +- `crates/g3-core/src/pending_research.rs` [0..18348] + - `ResearchStatus` [682] - Pending/Complete/Failed + - `ResearchTask` [1273] - task state + - `PendingResearchManager` [2906] - thread-safe tracking with Arc + - `with_notifications()` [3749] - broadcast channel for interactive mode + - `register()` [5069], `complete()` [5480], `fail()` [6419], `get()` [7344], `list_pending()` [7806], `take_completed()` [8952] +- `crates/g3-core/src/tools/research.rs` [0..17060] + - `CONTEXT_ERROR_PATTERNS` [929] - detects context window exhaustion + - `execute_research()` [1644] - spawns scout agent in background tokio task + - `execute_research_status()` [7540] - check pending/completed + - `extract_report()` [10694], `strip_ansi_codes()` [13148] - `crates/g3-core/src/lib.rs` - - `Agent.pending_research_manager` - field on Agent struct - - `inject_completed_research()` [781..836] - injects results as user messages - - `enable_research_notifications()` - for interactive mode - -**Tools**: `research` (async, returns research_id), `research_status` (check pending tasks) + - `inject_completed_research()` [31375] - injects results as user messages + - `enable_research_notifications()` [33459] - for interactive mode +- **Tools**: `research` (async, returns research_id), `research_status` (check pending) ### Plan Mode -Structured task planning with cognitive forcing - requires happy/negative/boundary checks. +Structured task planning with cognitive forcing — requires happy/negative/boundary checks. -- `crates/g3-core/src/tools/plan.rs` - - `Plan` [200..240] - plan_id, revision, approved_revision, items[] - - `PlanItem` [110..145] - id, description, state, touches, checks, evidence, notes - - `PlanState` [25..45] - enum: Todo, Doing, Done, Blocked - - `Checks` [90..105] - happy, negative[], boundary[] - - `get_plan_path()` [280..285] - `.g3/sessions//plan.g3.md` - - `read_plan()`, `write_plan()` [290..335] - YAML in markdown - - `plan_verify()` [659..700] - verifies evidence when complete - - `execute_plan_read/write/approve()` [395..530] - tool implementations +- `crates/g3-core/src/tools/plan.rs` [0..49798] + - `PlanState` [1044] - enum: Todo, Doing, Done, Blocked + - `Checks` [2823] - happy, negative[], boundary[] + - `PlanItem` [4021] - id, description, state, touches, checks, evidence, notes + - `Plan` [6498] - plan_id, revision, approved_revision, items[] + - `EvidenceType` [9578] - CodeLocation, TestReference, Unknown + - `VerificationStatus` [10133] - Verified, Warning, Error, Skipped + - `parse_evidence()` [12712] - parses `file:line-line` or `file::test_name` + - `verify_code_location()` [14888] - checks file exists, lines in range + - `verify_test_reference()` [16733] - checks test file, searches for fn + - `get_plan_path()` [18655] - `.g3/sessions//plan.g3.md` + - `read_plan()` [18818], `write_plan()` [19277] - YAML in markdown + - `plan_verify()` [21978] - verifies evidence when complete; checks envelope existence + - `format_verification_results()` [23395] - takes `working_dir: Option<&Path>` as third param + - `execute_plan_read()` [25881], `execute_plan_write()` [27233], `execute_plan_approve()` [30651] - `crates/g3-core/src/tool_definitions.rs` [263..330] - plan_read, plan_write, plan_approve - `crates/g3-core/src/prompts.rs` [21..130] - SHARED_PLAN_SECTION - -**Tool names**: `plan_read`, `plan_write`, `plan_approve` (underscores, not dots) - -### Plan Verification System -- `crates/g3-core/src/tools/plan.rs` - - `EvidenceType` [283..300] - CodeLocation, TestReference, Unknown - - `VerificationStatus` [303..320] - Verified, Warning, Error, Skipped - - `parse_evidence()` [390..428] - parses `file:line-line` or `file::test_name` - - `verify_code_location()` [443..495] - checks file exists, lines in range - - `verify_test_reference()` [496..554] - checks test file, searches for fn - -**Evidence formats**: `src/foo.rs:42-118`, `src/foo.rs:42`, `tests/foo.rs::test_bar` +- **Tool names**: `plan_read`, `plan_write`, `plan_approve` (underscores, not dots) +- **Evidence formats**: `src/foo.rs:42-118`, `src/foo.rs:42`, `tests/foo.rs::test_bar` ### Invariants System (Rulespec & Envelope) -Machine-readable invariants for Plan Mode verification. +Machine-readable invariants for Plan Mode verification. Rulespec read from `analysis/rulespec.yaml` (checked-in). -- `crates/g3-core/src/tools/invariants.rs` - - `Claim` [50..75] - name + selector - - `PredicateRule` [80..120] - Contains, Equals, Exists, NotExists, GreaterThan, LessThan, MinLength, MaxLength, Matches - - `Predicate` [125..180] - claim, rule, value, source, notes - - `Rulespec` [185..240] - claims[] + predicates[] - - `ActionEnvelope` [245..290] - facts HashMap - - `Selector` [295..410] - XPath-like: `foo.bar`, `foo[0]`, `foo[*]` - - `evaluate_rulespec()` [780..850] - evaluates against envelope - - Paths: `.g3/sessions//rulespec.yaml`, `envelope.yaml` +- `crates/g3-core/src/tools/invariants.rs` [0..73975] + - `Claim` [2024] - name + selector + - `PredicateRule` [3009] - Contains, Equals, Exists, NotExists, GreaterThan, LessThan, MinLength, MaxLength, Matches + - `Predicate` [5617] - claim, rule, value, source, notes + - `Rulespec` [8734] - claims[] + predicates[] + - `ActionEnvelope` [11203] - facts HashMap + - `Selector` [12900] - XPath-like: `foo.bar`, `foo[0]`, `foo[*]` + - `read_rulespec()` [29472] - takes `&Path` (working_dir) + - `evaluate_rulespec()` [32056] - evaluates against envelope + +### Write Envelope Tool +- `crates/g3-core/src/tools/envelope.rs` [0..23347] + - `execute_write_envelope()` [8764] - parses YAML facts, writes envelope.yaml, calls verify_envelope() + - `verify_envelope()` [11705] - compiles rulespec on-the-fly, extracts facts, runs datalog, writes `.dl` + `datalog_evaluation.txt` (shadow mode) +- `crates/g3-core/src/tool_definitions.rs` [266..282] - write_envelope tool definition +- `crates/g3-core/src/tool_dispatch.rs` - write_envelope dispatch case +- **Workflow**: `write_envelope` → `verify_envelope()` → datalog shadow, then `plan_write(done)` → `plan_verify()` → checks envelope exists + +### Datalog Invariant Verification +- `crates/g3-core/src/tools/datalog.rs` [0..80172] + - `CompiledPredicate` [1681] - id, claim_name, selector, rule, expected_value, source, notes + - `CompiledRulespec` [2728] - plan_id, compiled_at_revision, predicates, claims + - `compile_rulespec()` [3588] - validates selectors, builds claim lookup + - `Fact` [6741] - claim_name, value + - `extract_facts()` [7057] - uses Selector to navigate envelope YAML; fallback wraps in `facts:` if selector has `facts.` prefix + - `extract_values_recursive()` [8478] - handles arrays/objects/scalars, adds __length facts + - `DatalogPredicateResult` [10308], `DatalogExecutionResult` [10862] + - `execute_rules()` [11627] - builds fact lookup, uses datafrog Iteration; when conditions delegate to `evaluate_predicate_datalog()` + - `evaluate_predicate_datalog()` [14872] - handles all 9 PredicateRule types + - `escape_datalog_string()` [23990], `format_datalog_program()` [24582] - Soufflé-style .dl output + - `format_datalog_results()` [31136] - formats for shadow mode display +- **Relations**: `claim_value(claim, value)`, `claim_length(claim, length)`, `predicate_pass(id)`, `predicate_fail(id)` + +### Solon Agent (Rulespec Authoring) +- `agents/solon.md` - interactive rulespec authoring agent prompt +- `crates/g3-cli/src/embedded_agents.rs` [551] - 9 embedded agents: breaker, carmack, euler, fowler, hopper, huffman, lamport, scout, solon +- **Usage**: `g3 --agent solon` + +### Structured Tool Call Messages +Native tool calls stored as structured `MessageToolCall` objects, not inline JSON text. + +- `crates/g3-providers/src/lib.rs` [0..17486] + - `MessageToolCall` [2894] - id, name, input + - `Message` [3014] - `tool_calls: Vec`, `tool_result_id: Option` +- `crates/g3-providers/src/anthropic.rs` [0..74631] + - `convert_messages()` [8642] - emits `tool_use`/`tool_result` blocks for structured tool calls + - `strip_orphaned_tool_use()` [14737] - defense-in-depth: strips orphaned `tool_use` blocks with no matching `tool_result` + - `ToolResultContent` [46268] - enum (Text | Blocks) for structured content + - `ToolResultBlock` [46650] - enum (Image, Text) inside tool_result; images from read_image nested here, not as top-level blocks +- `crates/g3-core/src/lib.rs` - `ToolCall.id` [2516] field from native providers +- `crates/g3-core/src/streaming_parser.rs` [0..29244] - `process_chunk()` [10449] preserves tool call `id` +- **Gotcha**: Images in tool result messages must be nested inside `tool_result.content` array, not as top-level `Image` blocks (Anthropic API rejects mixed top-level Image+ToolResult) + +### Tool Call Token Tracking +- `crates/g3-core/src/context_window.rs` - `estimate_message_tokens()` [7695] accounts for `tool_calls[].input` +- Token formula: content_tokens + per-tool (input_chars/3 * 1.1 + 20 overhead) +- **Gotcha**: Without this, tool input JSON is invisible to tracker → compaction never triggers → API 400 ### Studio SDLC Pipeline Orchestrates 7 agents in sequence for codebase maintenance. - `crates/studio/src/sdlc.rs` - `PIPELINE_STAGES` [28..62] - euler → breaker → hopper → fowler → carmack → lamport → huffman - - `Stage` [18..26] - name, description, focus - - `StageStatus` [65..80] - Pending, Running, Complete, Failed, Skipped + - `Stage` [18..26], `StageStatus` [65..80] - Pending, Running, Complete, Failed, Skipped - `PipelineState` [108..140] - run_id, stages[], commit_cursor, session_id - `display_pipeline()` [354..390] - box display with status icons - `crates/studio/src/main.rs` - `cmd_sdlc_run()` [540..655] - orchestrates pipeline, merges on completion - `has_commits_on_branch()` [715..728] - counts commits ahead of main - `crates/studio/src/git.rs` - `merge_to_main()` (hardcodes 'main') - -**State**: `.g3/sdlc/pipeline.json` -**CLI**: `studio sdlc run [-c N]`, `studio sdlc status`, `studio sdlc reset` +- **State**: `.g3/sdlc/pipeline.json` +- **CLI**: `studio sdlc run [-c N]`, `studio sdlc status`, `studio sdlc reset` ### Terminal Width Responsive Output -Makes tool output responsive to terminal width - no line wrapping, with 4-char right margin. +Tool output responsive to terminal width — no line wrapping, 4-char right margin. - `crates/g3-cli/src/terminal_width.rs` - - `get_terminal_width()` [21..28] - returns usable width (terminal - 4 margin), min 40, default 80 - - `clip_line()` [33..44] - clips line with "…" ellipsis, UTF-8 safe - - `compress_path()` [53..96] - preserves filename, truncates dirs from left with "…" - - `compress_command()` [101..103] - clips command from right with "…" - - `available_width_after_prefix()` [115..117] - helper for prefixed lines + - `get_terminal_width()` [21..28] - usable width (terminal - 4), min 40, default 80 + - `clip_line()` [33..44] - clips with "…", UTF-8 safe + - `compress_path()` [53..96] - preserves filename, truncates dirs from left + - `compress_command()` [101..103] - clips from right + - `available_width_after_prefix()` [115..117] - `crates/g3-cli/src/ui_writer_impl.rs` - - `update_tool_output_line()` [407..445] - uses clip_line() with dynamic width - - `print_tool_output_line()` [447..454] - uses clip_line() for output lines - `print_tool_output_header()` [293..410] - uses compress_path/compress_command - - `print_tool_compact()` [475..635] - width-aware compact tool display - -### Datalog Invariant Verification -- `crates/g3-core/src/tools/datalog.rs` [0..37000] - - `CompiledPredicate` [47..67] - id, claim_name, selector, rule, expected_value, source, notes - - `CompiledRulespec` [70..80] - plan_id, compiled_at_revision, predicates, claims - - `compile_rulespec()` [88..140] - validates selectors, builds claim lookup, converts to CompiledPredicate - - `Fact` [170..180] - claim_name, value (extracted from envelope) - - `extract_facts()` [190..210] - uses Selector to navigate envelope YAML - - `extract_values_recursive()` [215..250] - handles arrays/objects/scalars, adds __length facts - - `DatalogPredicateResult` [255..275] - id, claim_name, rule, expected_value, passed, reason, source, notes - - `DatalogExecutionResult` [280..295] - predicate_results, fact_count, passed_count, failed_count - - `execute_rules()` [300..340] - builds fact lookup, uses datafrog Iteration, evaluates predicates - - `evaluate_predicate_datalog()` [345..480] - handles all PredicateRule types - - `get_compiled_rulespec_path()` [500..505] - `.g3/sessions//rulespec.compiled.json` - - `save_compiled_rulespec()`, `load_compiled_rulespec()` [510..530] - JSON serialization - - `format_datalog_results()` [540..620] - formats results for shadow mode display -- `crates/g3-core/src/tools/plan.rs` - - `shadow_datalog_verify()` [716..760] - loads compiled rulespec + envelope, runs datalog, prints to stderr - - `execute_plan_approve()` [1030..1095] - compiles rulespec on approval, saves to rulespec.compiled.json - -**Datalog Flow**: -1. `plan_approve` → `compile_rulespec()` → saves `rulespec.compiled.json` -2. `plan_verify` → `shadow_datalog_verify()` → loads compiled + envelope → `extract_facts()` → `execute_rules()` → `eprint!()` (shadow mode) - -### Rulespec Changes (2026-02-06) -- Rulespec is no longer generated on-the-fly during `plan_write` — it's now read from `analysis/rulespec.yaml` (checked-in, hand-crafted) -- `read_rulespec()` in `invariants.rs` now takes `&Path` (working_dir) instead of `&str` (session_id) -- `write_rulespec()`, `get_rulespec_path()`, `format_rulespec_yaml()`, `format_rulespec_markdown()` removed from `invariants.rs` -- `save_compiled_rulespec()`, `load_compiled_rulespec()`, `get_compiled_rulespec_path()` removed from `datalog.rs` -- `shadow_datalog_verify()` now compiles rulespec on-the-fly at verify time, writes `rulespec.compiled.dl` and `datalog_evaluation.txt` to session dir -- `plan_write` tool no longer accepts `rulespec` parameter -- `plan_approve` no longer compiles rulespec -- `format_verification_results()` now takes `working_dir: Option<&Path>` as third parameter - -### Write Envelope Tool -- `crates/g3-core/src/tools/envelope.rs` [0..184] - - `execute_write_envelope()` [37..79] - parses YAML facts, writes envelope.yaml, calls verify_envelope() - - `verify_envelope()` [93..183] - compiles rulespec, extracts facts, runs datalog, writes .dl + evaluation artifacts (shadow mode) -- `crates/g3-core/src/tools/mod.rs` [16] - `pub mod envelope;` -- `crates/g3-core/src/tool_definitions.rs` [266..282] - write_envelope tool definition (facts parameter) -- `crates/g3-core/src/tool_dispatch.rs` [41..43] - write_envelope dispatch case -- `prompts/system/native.md` [78..100] - Action Envelope section references write_envelope tool -- Tool count: 14 (was 13) - -**Workflow change**: `write_envelope` → `verify_envelope()` → datalog shadow, then `plan_write(done)` → `plan_verify()` → checks envelope exists -- `shadow_datalog_verify()` removed from `plan.rs` -- `format_verification_results()` no longer runs datalog, only checks envelope existence - -### Datalog Program Generation -- `crates/g3-core/src/tools/datalog.rs` [537..701] - `format_datalog_program()`, `escape_datalog_string()` - - Soufflé-style .dl output with `.decl` relations, fact assertions, and rules - - Relations: `claim_value(claim, value)`, `claim_length(claim, length)`, `predicate_pass(id)`, `predicate_fail(id)` - - Handles all 9 PredicateRule types: Exists, NotExists, Equals, Contains, GreaterThan, LessThan, MinLength, MaxLength, Matches - - Length facts (`__length` suffix) go into `claim_length` relation -- `crates/g3-core/src/tools/envelope.rs` [150] - `verify_envelope()` now calls `format_datalog_program()` instead of `serde_yaml::to_string()` -- **Bug fixed**: `.dl` files previously contained YAML (just serialized CompiledRulespec), now contain actual Soufflé datalog - -### Datalog Program Generation -- `crates/g3-core/src/tools/datalog.rs` [537..701] - `format_datalog_program()`, `escape_datalog_string()` - - Soufflé-style .dl output with `.decl` relations, fact assertions, and rules - - Relations: `claim_value(claim, value)`, `claim_length(claim, length)`, `predicate_pass(id)`, `predicate_fail(id)` - - Handles all 9 PredicateRule types: Exists, NotExists, Equals, Contains, GreaterThan, LessThan, MinLength, MaxLength, Matches - - Length facts (`__length` suffix) go into `claim_length` relation -- `crates/g3-core/src/tools/envelope.rs` [150] - `verify_envelope()` now calls `format_datalog_program()` instead of `serde_yaml::to_string()` -- **Bug fixed**: `.dl` files previously contained YAML (just serialized CompiledRulespec), now contain actual Soufflé datalog - -### Datalog Fact Extraction Fix (2026-02-07) -- `crates/g3-core/src/tools/datalog.rs` [188..207] - `extract_facts()` now has fallback: if selector returns empty on unwrapped envelope value, retries against a `facts`-wrapped version. This handles rulespec selectors written as `facts.feature.done` when `to_yaml_value()` strips the `facts:` wrapper. -- Root cause: `ActionEnvelope.to_yaml_value()` creates a Mapping from the `facts` HashMap WITHOUT a `facts` key wrapper, but rulespec selectors may include a `facts.` prefix. -- New unit tests: `test_extract_facts_with_facts_prefix_selector`, `test_extract_facts_roundtrip_from_yaml`, `test_execute_rules_full_pipeline_with_facts_prefix`, `test_execute_rules_full_pipeline_without_facts_prefix` -- New integration tests: `test_plan_verify_rulespec_with_facts_prefix_selectors`, `test_plan_verify_mixed_pass_fail` -- Strengthened: `test_plan_verify_with_analysis_rulespec` now asserts `Facts extracted: 0` is NOT in output - -### Solon Agent (Rulespec Authoring) -- `agents/solon.md` [0..10800] - Interactive rulespec authoring agent prompt - - Full reference for all 9 PredicateRule types: exists, not_exists, equals, contains, greater_than, less_than, min_length, max_length, matches - - Selector syntax (dot/index/wildcard), envelope format, verification pipeline - - Mandatory write_envelope validation step, common mistakes section -- `crates/g3-cli/src/embedded_agents.rs` [26] - solon registered in EMBEDDED_AGENTS -- `crates/g3-cli/src/agent_mode.rs` [42] - solon in available agents error message -- **Usage**: `g3 --agent solon` for interactive rulespec authoring -- **Agent count**: 9 embedded agents (was 8) - -### When Condition Bugfix (2026-02-07) -- `crates/g3-core/src/tools/datalog.rs` [377..395] - `execute_rules()` when condition evaluation -- **Bug**: The `_ =>` catch-all in when condition evaluation did naive string `contains` check. For `Matches` (regex like `^Re: `), it checked if fact values literally contained the regex pattern string — which never matched. Result: when conditions with `matches` rule always evaluated as not-met → vacuous pass → violations slipped through. -- **Fix**: Replaced hand-rolled when evaluation with synthetic `CompiledPredicate` delegation to `evaluate_predicate_datalog()`, which handles all 12 rule types correctly. -- **Tests**: `test_execute_rules_when_matches_condition_met`, `test_execute_rules_when_matches_condition_met_but_predicate_fails`, `test_execute_rules_when_matches_condition_not_met` -- **Note**: The `invariants.rs` path was NOT affected — it already delegated to `evaluate_predicate()` which handles all rules. - -### Structured Tool Call Messages (2026-02-11) -- `crates/g3-providers/src/lib.rs` [102..106] - `MessageToolCall` struct (id, name, input) -- `crates/g3-providers/src/lib.rs` [124..131] - `Message.tool_calls: Vec`, `Message.tool_result_id: Option` -- `crates/g3-providers/src/anthropic.rs` [284..340] - `convert_messages()` emits `tool_use` blocks for assistant messages with `tool_calls`, `tool_result` blocks for user messages with `tool_result_id` -- `crates/g3-providers/src/anthropic.rs` [935..941] - `AnthropicContent::ToolResult` variant added -- `crates/g3-core/src/lib.rs` [82..88] - `ToolCall.id` field added (from native providers) -- `crates/g3-core/src/lib.rs` [2530..2545] - Tool messages store structured `tool_calls` instead of inline JSON text -- `crates/g3-core/src/lib.rs` [1385..1400] - `check_duplicate_in_previous_message()` checks structured `tool_calls` field -- `crates/g3-core/src/context_window.rs` [107..109] - `add_message_with_tokens()` preserves messages with `tool_calls` even if content is empty -- `crates/g3-core/src/streaming_parser.rs` [339] - `process_chunk()` preserves tool call `id` from provider - -**Bug fixed**: Agent would stop mid-task because native tool calls were stored as inline JSON text in `Message.content`. When sent back to Anthropic API via `convert_messages()`, they went as plain text instead of structured `tool_use`/`tool_result` blocks. The model would occasionally get confused and emit text describing what it wanted to do instead of invoking the tool mechanism. - -### Compaction Tool Call Stripping Fix (2026-02-11) -- `crates/g3-core/src/context_window.rs` [339..355] - `extract_preserved_messages()` now strips `tool_calls` from preserved last assistant message - - **Root cause**: After compaction, preserved assistant message retained structured `tool_calls` but the corresponding `tool_result` was summarized away → orphaned `tool_use` blocks → Anthropic 400 error - - **Fix**: Clear `msg.tool_calls` in `extract_preserved_messages()` before returning - - Messages with only tool_calls and empty content are dropped by `add_message_with_tokens()` empty check -- `crates/g3-providers/src/anthropic.rs` [369..435] - `strip_orphaned_tool_use()` defense-in-depth - - Post-processing pass in `convert_messages()` detects orphaned `tool_use` blocks (no matching `tool_result` in next message) - - Strips orphaned blocks with warning, adds placeholder text if message becomes empty -- Tests: `test_compaction_strips_tool_calls_from_last_assistant`, `test_compaction_drops_assistant_with_only_tool_calls_no_text`, `test_compaction_preserves_normal_assistant_message` (unit), `test_strip_orphaned_tool_use_*` (anthropic), `test_compaction_strips_structured_tool_calls` (integration) - -### Tool Call Token Tracking Fix (2026-02-11) -- `crates/g3-core/src/context_window.rs` [199..220] - `estimate_message_tokens()` accounts for both `message.content` and `message.tool_calls[].input` -- **Root cause**: `estimate_tokens()` only counted `message.content` chars, ignoring `tool_calls[].input` JSON. When sent to API, `tool_use` blocks include full input, causing massive undercount. -- **Impact**: In a real session, 303k chars of tool input (101k tokens) were invisible to the tracker. Context showed 39% but actual was >100%. Compaction never triggered → API 400 error. -- **Fix**: Added `estimate_message_tokens(message)` that sums content tokens + per-tool-call input tokens (chars/3 * 1.1 + 20 overhead). Updated `add_message_with_tokens()`, `recalculate_tokens()`, `clear_conversation()` to use it. -- **Tests**: 7 unit tests in `context_window.rs`, 1 integration test in `mock_provider_integration_test.rs::test_tool_call_input_tokens_tracked_in_context_window` - -### Anthropic ToolResult Structured Content (2026-02-12) -- `crates/g3-providers/src/anthropic.rs` [1058..1110] - `ToolResultContent` enum (Text | Blocks) with custom Serialize/Deserialize -- `crates/g3-providers/src/anthropic.rs` [1078..1086] - `ToolResultBlock` enum (Image, Text) for structured content inside tool_result -- `crates/g3-providers/src/anthropic.rs` [283..330] - `convert_messages()` User branch: images from read_image nested inside ToolResult content array, not as top-level Image blocks -- **Root cause**: Anthropic API rejects top-level `Image` blocks mixed with `ToolResult` blocks in the same user message. Images must be inside `tool_result.content` array. -- **Bug pattern**: `read_image` → images attached to tool result message → `convert_messages()` put images as top-level blocks → API 400 "tool_use ids without tool_result" -- Tests: `test_tool_result_with_images_nested_inside`, `test_tool_result_without_images_uses_string_content`, `test_regular_user_message_with_images_uses_top_level_blocks`, `test_strip_orphaned_tool_use_works_with_structured_tool_result` \ No newline at end of file + - `update_tool_output_line()` [407..445], `print_tool_output_line()` [447..454] - clip_line() + - `print_tool_compact()` [475..635] - width-aware compact display