Files
codewhale/crates
Hunter Bown cf616e03bd feat(tools): spillover-file writer + 7-day boot prune (#422)
#500 (tool-output spillover preview pane) explicitly depends on
#422 (the storage writer) and #423 (the UI annotation). This ships
the storage half so the other two unblock cleanly.

### What's wired

New module \`crates/tui/src/tools/truncate.rs\`:

- \`spillover_root()\` — resolves \`~/.deepseek/tool_outputs/\`.
- \`spillover_path(id)\` — sanitises the tool call id (ASCII
  alphanumerics + \`-\`/\`_\`, drops \`.\` so \`..\` can't escape) and
  returns \`<root>/<id>.txt\`.
- \`write_spillover(id, content)\` — atomic write via the existing
  \`utils::write_atomic\` helper. Creates parent directory on demand.
- \`prune_older_than(max_age)\` — drops files older than \`max_age\`
  by mtime. Returns the count pruned. Per-file errors are logged
  and skipped, never propagated.
- \`maybe_spillover(id, content, threshold, head_bytes)\` —
  convenience for the "too long? spill it." pattern. Walks back to
  the previous UTF-8 char boundary so the head slice is always
  valid \`str\`.

Constants:
- \`SPILLOVER_DIR_NAME = "tool_outputs"\`
- \`SPILLOVER_THRESHOLD_BYTES = 100 KiB\` (mirrors
  \`MAX_MEMORY_SIZE\` for cross-feature consistency)
- \`SPILLOVER_MAX_AGE = 7 days\` (mirrors workspace snapshot prune)

Boot wiring in \`run_interactive\` calls \`prune_older_than\`
unconditionally; non-fatal — any error is logged at WARN and the
TUI starts anyway.

### Module-level \`#[allow(dead_code)]\`

The boot-prune is the only live caller today. The storage helpers
(\`write_spillover\`, \`maybe_spillover\`, \`spillover_path\`) are
intentionally unused outside the module's own tests until #423 / #500
land — those follow-ups need the bytes to exist, and the contracts
are pinned by tests so they can't drift before then. Module-level
\`#![allow(dead_code)]\` documents the deferral with a comment
pointing at the follow-up issues.

### Tests

8 unit tests in \`tools::truncate::tests\`:
- \`sanitise_id\` keeps safe chars, drops dangerous ones (\`..\`,
  \`/etc/passwd\` traversal attempts).
- \`write_spillover\` creates the directory and writes content.
- \`write_spillover\` rejects empty / fully-invalid ids.
- \`maybe_spillover\` returns \`None\` below threshold.
- \`maybe_spillover\` writes + returns the head slice above
  threshold.
- \`maybe_spillover\` walks back to a char boundary so the head
  string is never mid-codepoint (regression test using 4-byte
  whale emojis).
- \`prune_older_than\` is a no-op when the root doesn't exist.
- \`prune_older_than\` keeps fresh files and drops stale ones via a
  Unix \`utimensat\` backdating helper.

Tests serialize through a static \`Mutex\` because they share
process-global \`$HOME\`; the \`with_test_home\` helper documents
the SAFETY contract for the env-var override.

### Verification

cargo fmt --all -- --check                                          ✓
cargo clippy --workspace --all-targets --all-features --locked --   -D warnings   ✓
cargo test --workspace --all-features --locked                      ✓ 1873 + supporting (was 1865)

Closes #422 (storage half). #423 and #500 remain open with the
bytes now reachable on disk for them to cite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 04:38:00 -05:00
..