|
| 1 | +# Implementation Plan: Issue #611 - Sessions Files and By-File Subcommands |
| 2 | + |
| 3 | +**Status**: Draft |
| 4 | +**Research Doc**: docs/research/research-issue-611.md |
| 5 | +**Author**: Claude Code |
| 6 | +**Date**: 2026-03-11 |
| 7 | +**Estimated Effort**: 4-6 hours |
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +### Summary |
| 12 | +Add `sessions files <session-id>` and `sessions by-file <file-path>` subcommands to terraphim-agent. Extract file paths from tool invocations in session messages and categorize them as read vs written. |
| 13 | + |
| 14 | +### Approach |
| 15 | +Extend the existing session infrastructure: |
| 16 | +1. Add data types (`FileAccess`, `FileOperation`) to `terraphim_sessions` |
| 17 | +2. Add service methods (`extract_files`, `sessions_by_file`) to `SessionService` |
| 18 | +3. Extend `SessionsSubcommand` enum and parsing in `terraphim_agent` |
| 19 | +4. Add handler methods with table and JSON output |
| 20 | + |
| 21 | +### Scope |
| 22 | +**In Scope:** |
| 23 | +- `sessions files <session-id> [--json]` subcommand |
| 24 | +- `sessions by-file <file-path> [--json]` subcommand |
| 25 | +- File path extraction from 7 tool types (Read, Edit, Write, MultiEdit, NotebookEdit, Glob, Grep) |
| 26 | +- Read vs Write categorization |
| 27 | +- Table output (default) and JSON output (--json) |
| 28 | + |
| 29 | +**Out of Scope:** |
| 30 | +- File content diff tracking |
| 31 | +- Git integration |
| 32 | +- Real-time file watching |
| 33 | +- Path canonicalization/normalization |
| 34 | + |
| 35 | +**Avoid At All Cost:** |
| 36 | +- Complex path resolution (relative vs absolute) |
| 37 | +- File system access (stat, exists checks) |
| 38 | +- Content hashing or comparison |
| 39 | + |
| 40 | +## Architecture |
| 41 | + |
| 42 | +### Component Diagram |
| 43 | +``` |
| 44 | +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ |
| 45 | +│ CLI Parser │────▶│ SessionsSubcommand│────▶│ Handler Logic │ |
| 46 | +│ (commands.rs) │ │ (commands.rs) │ │ (handler.rs) │ |
| 47 | +└─────────────────┘ └──────────────────┘ └────────┬────────┘ |
| 48 | + │ |
| 49 | + ┌────────────────────────┐ |
| 50 | + ▼ |
| 51 | + ┌──────────────────┐ |
| 52 | + │ SessionService │ |
| 53 | + │ (terraphim_) │ |
| 54 | + └────────┬─────────┘ |
| 55 | + │ |
| 56 | + ┌────────▼────────┐ |
| 57 | + │ Session Cache │ |
| 58 | + │ (HashMap) │ |
| 59 | + └────────┬────────┘ |
| 60 | + │ |
| 61 | + ┌────────▼────────┐ |
| 62 | + │ ContentBlock:: │ |
| 63 | + │ ToolUse parsing │ |
| 64 | + └─────────────────┘ |
| 65 | +``` |
| 66 | + |
| 67 | +### Data Flow |
| 68 | +``` |
| 69 | +User Input: "sessions files abc123" |
| 70 | + ↓ |
| 71 | +Parse: SessionsSubcommand::Files { session_id: "abc123", json: false } |
| 72 | + ↓ |
| 73 | +Handler: handle_sessions() -> handle_files_subcommand() |
| 74 | + ↓ |
| 75 | +Service: session_service.extract_files("abc123") |
| 76 | + ↓ |
| 77 | +Extract: Iterate messages -> ContentBlock::ToolUse -> extract path |
| 78 | + ↓ |
| 79 | +Categorize: tool_name -> FileOperation::Read/Write |
| 80 | + ↓ |
| 81 | +Output: Table or JSON with file paths and operations |
| 82 | +``` |
| 83 | + |
| 84 | +### Key Design Decisions |
| 85 | +| Decision | Rationale | Alternatives Rejected | |
| 86 | +|----------|-----------|----------------------| |
| 87 | +| Add types to terraphim_sessions | Single source of truth | Inline in handler | |
| 88 | +| Simple string matching for paths | Avoids filesystem dependencies | Path canonicalization | |
| 89 | +| Tool name determines read/write | Clear categorization | Analyzing tool output | |
| 90 | + |
| 91 | +### Simplicity Check |
| 92 | +**What if this could be easy?** |
| 93 | +- Extract paths from JSON, no filesystem access |
| 94 | +- Tool name determines operation type |
| 95 | +- Simple substring matching for by-file search |
| 96 | + |
| 97 | +## File Changes |
| 98 | + |
| 99 | +### Modified Files |
| 100 | +| File | Changes | |
| 101 | +|------|---------| |
| 102 | +| `terraphim_sessions/src/model.rs` | Add `FileAccess`, `FileOperation` types | |
| 103 | +| `terraphim_sessions/src/service.rs` | Add `extract_files()`, `sessions_by_file()` | |
| 104 | +| `terraphim_agent/src/repl/commands.rs` | Extend `SessionsSubcommand`, add parsing | |
| 105 | +| `terraphim_agent/src/repl/handler.rs` | Add handler methods for new subcommands | |
| 106 | + |
| 107 | +## API Design |
| 108 | + |
| 109 | +### Public Types |
| 110 | +```rust |
| 111 | +/// File access operation type |
| 112 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
| 113 | +pub enum FileOperation { |
| 114 | + Read, |
| 115 | + Write, |
| 116 | +} |
| 117 | + |
| 118 | +impl Display for FileOperation { ... } |
| 119 | + |
| 120 | +/// Record of a file access in a session |
| 121 | +#[derive(Debug, Clone, Serialize, Deserialize)] |
| 122 | +pub struct FileAccess { |
| 123 | + pub path: String, |
| 124 | + pub operation: FileOperation, |
| 125 | + pub timestamp: Option<jiff::Timestamp>, |
| 126 | + pub tool_name: String, |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +### Public Functions |
| 131 | +```rust |
| 132 | +impl SessionService { |
| 133 | + /// Extract all file accesses from a session |
| 134 | + pub async fn extract_files(&self, session_id: &SessionId) -> Vec<FileAccess>; |
| 135 | + |
| 136 | + /// Find sessions that accessed a file (substring match) |
| 137 | + pub async fn sessions_by_file(&self, file_path: &str) -> Vec<(Session, Vec<FileAccess>)>; |
| 138 | +} |
| 139 | + |
| 140 | +impl Session { |
| 141 | + /// Extract file accesses from this session |
| 142 | + pub fn extract_file_accesses(&self) -> Vec<FileAccess>; |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +### Command Parsing |
| 147 | +```rust |
| 148 | +pub enum SessionsSubcommand { |
| 149 | + // ... existing variants |
| 150 | + |
| 151 | + /// List files touched by a session |
| 152 | + Files { |
| 153 | + session_id: String, |
| 154 | + json: bool, |
| 155 | + }, |
| 156 | + |
| 157 | + /// Find sessions that touched a file |
| 158 | + ByFile { |
| 159 | + file_path: String, |
| 160 | + json: bool, |
| 161 | + }, |
| 162 | +} |
| 163 | +``` |
| 164 | + |
| 165 | +## Test Strategy |
| 166 | + |
| 167 | +### Unit Tests |
| 168 | +| Test | Location | Purpose | |
| 169 | +|------|----------|---------| |
| 170 | +| `test_extract_files_read` | `terraphim_sessions/model.rs` | Verify Read tool extraction | |
| 171 | +| `test_extract_files_write` | `terraphim_sessions/model.rs` | Verify Write tool extraction | |
| 172 | +| `test_extract_files_notebook` | `terraphim_sessions/model.rs` | Verify notebook_path extraction | |
| 173 | +| `test_sessions_by_file` | `terraphim_sessions/service.rs` | Verify file search | |
| 174 | + |
| 175 | +### Integration Tests |
| 176 | +| Test | Location | Purpose | |
| 177 | +|------|----------|---------| |
| 178 | +| `test_sessions_files_command` | `terraphim_agent/tests` | Full CLI flow | |
| 179 | +| `test_sessions_byfile_command` | `terraphim_agent/tests` | Full CLI flow | |
| 180 | + |
| 181 | +## Implementation Steps |
| 182 | + |
| 183 | +### Step 1: Add FileAccess Types to terraphim_sessions |
| 184 | +**Files:** `crates/terraphim_sessions/src/model.rs` |
| 185 | +**Description:** Add `FileOperation` enum and `FileAccess` struct |
| 186 | +**Tests:** Unit tests for type construction |
| 187 | +**Estimated:** 30 minutes |
| 188 | + |
| 189 | +```rust |
| 190 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
| 191 | +pub enum FileOperation { |
| 192 | + Read, |
| 193 | + Write, |
| 194 | +} |
| 195 | + |
| 196 | +#[derive(Debug, Clone, Serialize, Deserialize)] |
| 197 | +pub struct FileAccess { |
| 198 | + pub path: String, |
| 199 | + pub operation: FileOperation, |
| 200 | + pub timestamp: Option<jiff::Timestamp>, |
| 201 | + pub tool_name: String, |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +### Step 2: Add File Extraction to Session Model |
| 206 | +**Files:** `crates/terraphim_sessions/src/model.rs` |
| 207 | +**Description:** Implement `extract_file_accesses()` method on `Session` |
| 208 | +**Tests:** Test with various tool input formats |
| 209 | +**Dependencies:** Step 1 |
| 210 | +**Estimated:** 1 hour |
| 211 | + |
| 212 | +```rust |
| 213 | +impl Session { |
| 214 | + pub fn extract_file_accesses(&self) -> Vec<FileAccess> { |
| 215 | + // Iterate messages, find ToolUse blocks, extract paths |
| 216 | + // Return Vec<FileAccess> |
| 217 | + } |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | +### Step 3: Add Service Methods |
| 222 | +**Files:** `crates/terraphim_sessions/src/service.rs` |
| 223 | +**Description:** Add `extract_files()` and `sessions_by_file()` to `SessionService` |
| 224 | +**Tests:** Unit tests with test sessions |
| 225 | +**Dependencies:** Step 2 |
| 226 | +**Estimated:** 45 minutes |
| 227 | + |
| 228 | +```rust |
| 229 | +impl SessionService { |
| 230 | + pub async fn extract_files(&self, session_id: &SessionId) -> Vec<FileAccess> { |
| 231 | + self.get_session(session_id).await |
| 232 | + .map(|s| s.extract_file_accesses()) |
| 233 | + .unwrap_or_default() |
| 234 | + } |
| 235 | + |
| 236 | + pub async fn sessions_by_file(&self, file_path: &str) -> Vec<(Session, Vec<FileAccess>)> { |
| 237 | + // Search all sessions, return those with matching file paths |
| 238 | + } |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +### Step 4: Extend SessionsSubcommand |
| 243 | +**Files:** `crates/terraphim_agent/src/repl/commands.rs` |
| 244 | +**Description:** Add `Files` and `ByFile` variants, update parsing |
| 245 | +**Tests:** Command parsing tests |
| 246 | +**Dependencies:** None |
| 247 | +**Estimated:** 45 minutes |
| 248 | + |
| 249 | +### Step 5: Add Handler Methods |
| 250 | +**Files:** `crates/terraphim_agent/src/repl/handler.rs` |
| 251 | +**Description:** Add handlers for `Files` and `ByFile` subcommands |
| 252 | +**Tests:** Integration with SessionService |
| 253 | +**Dependencies:** Steps 3, 4 |
| 254 | +**Estimated:** 1 hour |
| 255 | + |
| 256 | +```rust |
| 257 | +async fn handle_sessions(&mut self, subcommand: SessionsSubcommand) -> Result<()> { |
| 258 | + match subcommand { |
| 259 | + // ... existing handlers |
| 260 | + SessionsSubcommand::Files { session_id, json } => { |
| 261 | + self.handle_files(session_id, json).await |
| 262 | + } |
| 263 | + SessionsSubcommand::ByFile { file_path, json } => { |
| 264 | + self.handle_by_file(file_path, json).await |
| 265 | + } |
| 266 | + } |
| 267 | +} |
| 268 | +``` |
| 269 | + |
| 270 | +### Step 6: Add Unit Tests |
| 271 | +**Files:** `crates/terraphim_sessions/src/model.rs`, `service.rs` |
| 272 | +**Description:** Comprehensive tests for extraction logic |
| 273 | +**Estimated:** 45 minutes |
| 274 | + |
| 275 | +### Step 7: Verification |
| 276 | +**Command:** `cargo check --workspace` |
| 277 | +**Command:** `cargo test -p terraphim_sessions` |
| 278 | +**Command:** `cargo test -p terraphim_agent --features repl-sessions` |
| 279 | +**Estimated:** 15 minutes |
| 280 | + |
| 281 | +## Rollback Plan |
| 282 | + |
| 283 | +If issues discovered: |
| 284 | +1. Revert changes to the 4 modified files |
| 285 | +2. Keep research and design documents for future reference |
| 286 | + |
| 287 | +## Dependencies |
| 288 | + |
| 289 | +No new dependencies required. |
| 290 | + |
| 291 | +## Performance Considerations |
| 292 | + |
| 293 | +| Metric | Target | Measurement | |
| 294 | +|--------|--------|-------------| |
| 295 | +| Extraction time | < 10ms per session | Benchmark with 1000 message session | |
| 296 | +| Memory overhead | O(n) where n = file accesses | Profiling | |
| 297 | +| Search time | < 100ms for 1000 sessions | Benchmark | |
| 298 | + |
| 299 | +## Open Items |
| 300 | + |
| 301 | +None. |
| 302 | + |
| 303 | +## Approval |
| 304 | + |
| 305 | +- [ ] Technical review complete |
| 306 | +- [ ] Test strategy approved |
| 307 | +- [ ] Human approval received |
0 commit comments