From d508ddd5080fbbfc6810becd20e8f2de8ed84130 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Mon, 12 Jan 2026 10:20:33 +0530 Subject: [PATCH] Move project memory from .g3/ to analysis/ for version control Project memory is now stored at analysis/memory.md instead of .g3/memory.md. This change enables: - Shared memory across git worktrees (studio agent sessions) - Version-controlled memory that persists across clones - Memory changes tracked in git history and reviewable in PRs Changes: - crates/g3-core/src/tools/memory.rs: Update get_memory_path() to use analysis/ - crates/g3-cli/src/project_files.rs: Update read_project_memory() path - crates/g3-core/src/prompts.rs: Update documentation references (2 occurrences) - analysis/memory.md: Add memory file (copied from .g3/memory.md) --- analysis/memory.md | 167 +++++++++++++++++++++++++++++ crates/g3-cli/src/project_files.rs | 4 +- crates/g3-core/src/prompts.rs | 4 +- crates/g3-core/src/tools/memory.rs | 6 +- 4 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 analysis/memory.md diff --git a/analysis/memory.md b/analysis/memory.md new file mode 100644 index 0000000..720727c --- /dev/null +++ b/analysis/memory.md @@ -0,0 +1,167 @@ +# Project Memory +> Updated: 2026-01-12T04:44:39Z | Size: 9.9k 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 definition in `create_core_tools()` +- `crates/g3-core/src/tool_dispatch.rs` [48] - dispatch case for "remember" +- `crates/g3-core/src/prompts.rs` [4200..6500] - Project Memory section in native prompt +- `crates/g3-cli/src/lib.rs` [1472..1495] - `read_project_memory()` loads memory at startup + +### Context Window & Compaction +- `crates/g3-core/src/context_window.rs` [0..815] - `ContextWindow`, `reset_with_summary()`, `should_compact()`, `thin_context()` +- `crates/g3-core/src/lib.rs` [0..132483] - `Agent` struct, `force_compact()`, `stream_completion_with_tools()` + +### 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 + +### 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 + +### CLI Argument Parsing +- `crates/g3-cli/src/lib.rs` [270..380] - `Cli` struct with clap derive macros +- `crates/g3-cli/src/lib.rs` [1700..2200] - `run_interactive()` with `/` command handlers + +### Streaming Markdown Formatter +- `crates/g3-cli/src/streaming_markdown.rs` [21500..22500] - `format_header()` processes headers with inline formatting +- `crates/g3-cli/tests/streaming_markdown_test.rs` - Tests for markdown formatting including `test_bold_inside_header`, `test_italic_inside_header`, `test_code_inside_header`, `test_mixed_formatting_inside_header` + +### Auto-Memory Feature +- `crates/g3-core/src/lib.rs` [1459..1522] - `send_auto_memory_reminder()` sends reminder to LLM after tool calls +- `crates/g3-core/src/lib.rs` [1451..1454] - `set_auto_memory()` enables/disables auto-memory +- `crates/g3-core/src/lib.rs` [116] - `tool_calls_this_turn: Vec` tracks tools called per turn +- `crates/g3-cli/src/lib.rs` [393] - `auto_memory: bool` CLI flag definition +- `crates/g3-cli/src/lib.rs` [641..642, 684..685] - Flag applied to agent in console/machine modes +- `crates/g3-cli/src/lib.rs` [1340..1350, 1394..1404] - Auto-memory reminder called in single-shot mode +- `crates/g3-cli/src/lib.rs` [1758, 1931, 2216] - Auto-memory reminder called in interactive mode + +### Tool Call Tracking +- `crates/g3-core/src/lib.rs` [2843..2855] - `execute_tool_in_dir()` tracks all tool calls for auto-memory + +### Agent Mode +- `crates/g3-cli/src/lib.rs` [695..910] - `run_agent_mode()` handles specialized agent execution with custom prompts +- `crates/g3-cli/src/lib.rs` [820..835] - Agent creation with `Agent::new_with_custom_prompt()` +- `crates/g3-cli/src/lib.rs` [837] - `agent.set_agent_mode()` enables agent-specific session tracking + +### CLI Entry Points and Modes +- `crates/g3-cli/src/lib.rs` [0..140000] - `run()` main entry, `run_agent_mode()`, `run_flock_mode()`, `run_accumulative_mode()`, `run_autonomous()`, `run_interactive()`, `run_interactive_machine()` +- `crates/g3-cli/src/lib.rs` - `execute_task()` (~line 1990), `execute_task_machine()` (~line 2262) - duplicated retry logic + +### Retry Infrastructure +- `crates/g3-core/src/retry.rs` [0..12000] - `execute_with_retry()`, `retry_operation()`, `RetryConfig`, `RetryResult` - used by g3-planner but not g3-cli + +### UI Abstraction Layer +- `crates/g3-core/src/ui_writer.rs` [0..4500] - `UiWriter` trait, `NullUiWriter` +- `crates/g3-cli/src/ui_writer_impl.rs` [0..14000] - `ConsoleUiWriter` implementation +- `crates/g3-cli/src/machine_ui_writer.rs` [0..4000] - `MachineUiWriter` implementation +- `crates/g3-cli/src/simple_output.rs` [0..1200] - `SimpleOutput` helper (separate from UiWriter) + +### 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()` + +### Streaming Utilities +- `crates/g3-core/src/streaming.rs` [0..10000] - `truncate_line()`, `truncate_for_display()`, `log_stream_error()`, `is_connection_error()` + +### Background Process Management +- `crates/g3-core/src/background_process.rs` [0..3000] - `BackgroundProcessManager`, `start()`, `list()`, `is_running()`, `get()`, `remove()` +- Design: No `stop()` method - processes are stopped via shell tool using `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 lines] - `classify_error()`, `ErrorType`, `RecoverableError` +- Priority order: rate limit > network > server > busy > timeout > token limit > context length +- Note: "Connection timeout" classifies as NetworkError (not Timeout) due to "connection" keyword priority + +### CLI Module Extractions +- `crates/g3-cli/src/metrics.rs` [0..5416] - `TurnMetrics`, `format_elapsed_time()`, `generate_turn_histogram()` +- `crates/g3-cli/src/project_files.rs` [0..5577] - `read_agents_config()`, `read_project_readme()`, `read_project_memory()`, `extract_readme_heading()` +- `crates/g3-cli/src/coach_feedback.rs` [0..4025] - `extract_from_logs()` for coach-player loop feedback extraction + +### Context Compaction +- `crates/g3-core/src/compaction.rs` [0..11213] - `perform_compaction()`, `CompactionResult`, `CompactionConfig`, `calculate_capped_summary_tokens()`, `should_disable_thinking()`, `build_summary_messages()`, `apply_summary_fallback_sequence()` +- Unified compaction used by both `force_compact()` and auto-compaction in `stream_completion_with_tools()` + +### Streaming Markdown Formatter (Code Blocks) +- `crates/g3-cli/src/streaming_markdown.rs` [693..735] - `flush_incomplete()` handles unclosed blocks at end of stream +- `crates/g3-cli/src/streaming_markdown.rs` [654..675] - `emit_code_block()` joins block_buffer and highlights code +- `crates/g3-cli/src/streaming_markdown.rs` [439..462] - `process_in_code_block()` detects closing fence on newline +- Bug fix: closing ``` without trailing newline must be detected in flush_incomplete(), not just process_in_code_block() + +### ACD (Aggressive Context Dehydration) +- `crates/g3-core/src/acd.rs` [0..22000] - `Fragment`, `Fragment::new()`, `Fragment::save()`, `Fragment::load()`, `generate_stub()`, `list_fragments()`, `get_latest_fragment_id()` +- `crates/g3-core/src/tools/acd.rs` [0..8500] - `execute_rehydrate()` tool implementation +- `crates/g3-core/src/paths.rs` [3200..3400] - `get_fragments_dir()` returns `.g3/sessions//fragments/` +- `crates/g3-core/src/compaction.rs` [195..240] - ACD integration in `perform_compaction()`, creates fragment and stub when `acd_enabled` +- `crates/g3-core/src/context_window.rs` [10100..10700] - `reset_with_summary_and_stub()` adds stub before summary +- `crates/g3-cli/src/lib.rs` [157..161] - `--acd` CLI flag +- `crates/g3-cli/src/lib.rs` [1476..1525] - `/fragments` and `/rehydrate` commands + +### ACD Fragment Storage Format +```json +{ + "fragment_id": "abc123", + "created_at": "2026-01-11T...", + "messages": [...], + "message_count": 47, + "user_message_count": 23, + "assistant_message_count": 24, + "tool_call_summary": {"read_file": 4, "shell": 5}, + "estimated_tokens": 18500, + "topics": ["implemented auth", "fixed bug"], + "preceding_fragment_id": "xyz789" +} +``` + +### UTF-8 Safe String Slicing Pattern +**Problem**: Rust string slices (`&s[..n]`) use byte indices, not character indices. Multi-byte UTF-8 characters (emoji, bullets `•`, `×`, `⚡`) cause panics if sliced mid-character. + +**Solution**: Use `char_indices()` to find byte boundaries: +```rust +// Get byte index of the Nth character +let byte_idx = s.char_indices() + .nth(char_limit) + .map(|(i, _)| i) + .unwrap_or(s.len()); +let truncated = &s[..byte_idx]; + +// For length checks, use chars().count() not len() +if s.chars().count() <= max_len { ... } +``` + +**Danger zones**: Display truncation, ACD stubs, user input handling, any string with non-ASCII characters. + +### CLI Module Structure (Post-Refactor) +- `crates/g3-cli/src/lib.rs` [0..415] - Entry point, `run()`, mode dispatch, config loading +- `crates/g3-cli/src/cli_args.rs` [0..133] - `Cli` struct with clap derive macros, argument parsing +- `crates/g3-cli/src/autonomous.rs` [0..785] - `run_autonomous()`, `run_flock_mode()`, coach-player feedback loop +- `crates/g3-cli/src/agent_mode.rs` [0..284] - `run_agent_mode()` specialized agent execution +- `crates/g3-cli/src/accumulative.rs` [0..343] - `run_accumulative_mode()` iterative requirements +- `crates/g3-cli/src/interactive.rs` [0..851] - `run_interactive()`, `run_interactive_machine()`, REPL with `/` commands +- `crates/g3-cli/src/task_execution.rs` [0..212] - `execute_task_with_retry()`, `OutputMode` enum - unified retry logic +- `crates/g3-cli/src/utils.rs` [0..91] - `display_welcome_message()`, `get_workspace_path()` + +### Studio - Multi-Agent Workspace Manager +- `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`, session metadata management +- `crates/studio/src/git.rs` - `GitWorktree`, git worktree management for isolated agent sessions + +**Session log format**: Session logs are stored at `/.g3/sessions//session.json` with structure: +```json +{ + "context_window": { + "conversation_history": [{"role": "...", "content": "..."}], + "percentage_used": 45.2, + "total_tokens": 200000, + "used_tokens": 90400 + }, + "session_id": "...", + "status": "...", + "timestamp": "..." +} +``` \ No newline at end of file diff --git a/crates/g3-cli/src/project_files.rs b/crates/g3-cli/src/project_files.rs index 740f140..3e26d00 100644 --- a/crates/g3-cli/src/project_files.rs +++ b/crates/g3-cli/src/project_files.rs @@ -64,10 +64,10 @@ pub fn read_project_readme(workspace_dir: &Path) -> Option { None } -/// Read project memory from .g3/memory.md in the workspace directory. +/// Read project memory from analysis/memory.md in the workspace directory. /// Returns formatted content with emoji prefix and size info, or None if not found. pub fn read_project_memory(workspace_dir: &Path) -> Option { - let memory_path = workspace_dir.join(".g3").join("memory.md"); + let memory_path = workspace_dir.join("analysis").join("memory.md"); if !memory_path.exists() { return None; diff --git a/crates/g3-core/src/prompts.rs b/crates/g3-core/src/prompts.rs index 1367188..cc18097 100644 --- a/crates/g3-core/src/prompts.rs +++ b/crates/g3-core/src/prompts.rs @@ -99,7 +99,7 @@ Do not explain what you're going to do - just do it by calling the tools. # Project Memory -Project memory is automatically loaded at startup alongside README.md and AGENTS.md. It contains an index of features -> code locations, patterns, and entry points. If you need to re-read memory from disk (e.g., after another agent updates it), use `read_file .g3/memory.md`. +Project memory is automatically loaded at startup alongside README.md and AGENTS.md. It contains an index of features -> code locations, patterns, and entry points. If you need to re-read memory from disk (e.g., after another agent updates it), use `read_file analysis/memory.md`. **IMPORTANT**: After completing a task where you discovered code locations, you **MUST** call the `remember` tool to save them.. @@ -321,7 +321,7 @@ If you can complete it with 1-2 tool calls, skip TODO. # Project Memory -Project memory (if available) is automatically loaded at startup. It contains feature locations and patterns discovered in previous sessions. If you need to re-read memory from disk (e.g., after another agent updates it), use `read_file .g3/memory.md`. +Project memory (if available) is automatically loaded at startup. It contains feature locations and patterns discovered in previous sessions. If you need to re-read memory from disk (e.g., after another agent updates it), use `read_file analysis/memory.md`. **ALWAYS** call `remember` at the END of your turn when you discovered: - A feature's location (file + char range + function/struct names) diff --git a/crates/g3-core/src/tools/memory.rs b/crates/g3-core/src/tools/memory.rs index 8f5d561..8e9eefe 100644 --- a/crates/g3-core/src/tools/memory.rs +++ b/crates/g3-core/src/tools/memory.rs @@ -14,12 +14,12 @@ use crate::ToolCall; use super::executor::ToolContext; /// Get the path to the memory file. -/// Memory is stored at `.g3/memory.md` in the working directory. +/// Memory is stored at `analysis/memory.md` in the working directory (version controlled). fn get_memory_path(working_dir: Option<&str>) -> PathBuf { let base = working_dir .map(PathBuf::from) .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); - base.join(".g3").join("memory.md") + base.join("analysis").join("memory.md") } /// Format the file size in a human-readable way. @@ -45,7 +45,7 @@ pub async fn execute_remember( let memory_path = get_memory_path(ctx.working_dir); - // Ensure .g3 directory exists + // Ensure analysis directory exists if let Some(parent) = memory_path.parent() { std::fs::create_dir_all(parent)?; }