cats 0.1.21

Coding Agent ToolS - A comprehensive toolkit for building AI-powered coding agents
Documentation
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.21] - 2026-05-02

### Fixed
- **edit: "multiple matches" error now shows line numbers and context** — when
  `oldString` appears more than once the error now reports the count, line number
  of every occurrence, and 2–3 lines of surrounding context so the LLM can
  immediately construct a unique `oldString`. The message also explicitly warns
  against falling back to bash `>>` append (which causes duplicate content).

## [0.1.20] - 2026-04-29

### Fixed
- **executor: compact log shows `__truncated` marker instead of `{}`** — when tool call
  arguments fail JSON parsing (e.g. truncated by max_tokens), the compact log now records
  `{"__truncated": true, "__len": N}` so the log clearly shows a truncation occurred
  rather than silently displaying empty arguments.
- **executor: EOF parse errors produce actionable message** — when the LLM response is
  truncated mid-JSON (detected via `EOF while parsing`), the error returned to the LLM now
  reads: `"tool call was truncated (response too large — N bytes). Break the content into
  smaller pieces or use bash with heredoc."` Applies to all tools (`write`, `bash`,
  `edit`, etc.) via both `execute_tool_calls` and `execute_tool_calls_structured`.

## [0.1.19] - 2026-04-29

### Fixed
- **multiedit: atomic failure message** — when one edit in a `multiedit` batch fails, the error now
  explicitly states that no changes were written to disk and instructs the LLM to re-submit all edits
  with a corrected `oldString` rather than assuming earlier edits were applied.
- **edit/multiedit: oldString not found hint** — error message now tells the LLM to use the `read`
  tool to fetch exact current file content instead of constructing `oldString` from memory.

## [0.1.18] - 2026-04-29

### Fixed
- **grep: panic on multibyte characters at line truncation boundary**`&line[..MAX_LINE_LENGTH]`
  sliced by byte index. Now uses `char_indices()` to find the nearest valid char boundary.
- **result_handler: panic on multibyte characters at result truncation boundary**`&result_message[..max_size_bytes]`
  sliced by byte index. Now uses `char_indices()` to find the nearest valid char boundary.

## [0.1.17] - 2026-04-29

### Fixed
- **read: panic on multibyte characters at line truncation boundary**`&line[..MAX_LINE_LENGTH]`
  sliced by byte index which could fall inside a multibyte UTF-8 character (e.g. em dash ``).
  Now uses `char_indices()` to find the nearest valid char boundary before truncating.

## [0.1.16] - 2026-04-26

### Fixed
- **edit/read/write/multiedit: camelCase field names rejected by validate_args**`filePath`, `oldString`,
  `newString` were silently blocked before reaching the `parse_*_args` functions that already handled them.
  Now `validate_args` accepts both snake_case and camelCase variants for all four tools.
- **multiedit: camelCase fields in edits array**`EditOperation` deserialization failed with
  `missing field 'old_string'` when LLMs sent `oldString`/`newString`/`replaceAll`. Added
  `#[serde(alias)]` attributes to accept both naming conventions.
- **edit: CRLF line-ending corruption in fallback strategies**`line_trimmed_replacer`,
  `whitespace_normalized_replacer`, and `trimmed_boundary_replacer` used `str::lines()` (strips `\r`)
  then reconstructed blocks with `join("\n")`, making `str::find` fail on CRLF files. Switched to
  `split('\n')` throughout so `\r` is preserved in reconstructed substrings.
- **edit: multiple-match early exit blocked fallback strategies**`replace_in_content` returned
  `Err("Found multiple matches")` immediately when the exact string appeared more than once, before
  trying any fuzzy fallback. The error is now only raised after all strategies are exhausted without
  finding a unique match (mirrors OpenCode behaviour).
- **read: offset was 0-based instead of 1-based** — LLMs trained on OpenCode expect `offset=1` to
  mean "start at line 1", but cats treated it as "skip 1 line". Changed to 1-based: `skip = offset - 1`.

### Added
- **edit: EscapeNormalizedReplacer** — new fallback strategy that unescapes literal `\n`, `\t`, `\r`, `\"`
  sequences LLMs sometimes emit due to JSON double-escaping, then retries the replacement.
- **edit: IndentationFlexibleReplacer** — new fallback strategy that strips minimum common indentation
  from both the search string and candidate blocks before comparing, tolerating 2-space vs 4-space
  mismatches and copy-paste indentation drift.
- **edit: BlockAnchorReplacer** — new fallback strategy that locates blocks by matching trimmed
  first/last lines (anchors), then scores middle-line similarity using Levenshtein distance (via the
  existing `edit-distance` crate). Accepts the best candidate above a 30% threshold when multiple
  anchor matches exist, enabling fuzzy matching on slightly-stale code blocks.

## [0.1.15] - 2026-04-15

### Added
- **Cooperative Cancellation for Tool Execution**: Added `CancelSignal` (Arc<AtomicBool>) infrastructure so running tools (especially bash) can be killed immediately when the user presses ESC
  - New `CancelSignal` struct with `request_cancel()`, `reset()`, and `is_cancelled()` methods
  - Added `cancel_signal` field to `ToolState` with accessor methods (`request_cancel()`, `reset_cancel()`, `is_cancelled()`, `cancel_signal()`)
  - Re-exported `CancelSignal` from `lib.rs` for downstream consumers
  - BashTool now clones the cancel signal before spawning child processes to avoid holding the Mutex lock
  - Added cancel checks in BashTool stdout and stderr read loops — on cancel, kills child process and returns early
  - Added cancel check before starting stderr read (skip if already cancelled)
  - On cancel, returns a user-friendly result: "Command cancelled by user (ESC)" with exit_code -1

## [0.1.14] - 2026-04-15

### Fixed
- **Edit Tool OOB Panics**: Prevented out-of-bounds panics in `whitespace_normalized_replacer` and `trimmed_boundary_replacer` when dealing with edge cases in text replacement
- **Edit Tool Empty Lines Panic**: Prevented panic in `line_trimmed_replacer` when search_lines becomes empty after trailing newline trim

## [0.1.13] - 2026-04-15

### Fixed
- **Grep/Glob Tool Silent Failures**: Fixed silent failures when LLM sends empty string values for `path` and `pattern` parameters
  - Made `path` parameter required (non-optional) in both `grep` and `glob` tools, matching the `list` tool pattern
  - Added explicit `grep` and `glob` handlers in `converter.rs` that filter out empty string values
  - Updated `validate_args` to check for non-empty pattern values (not just presence)
  - Updated `parse_grep_args` and `parse_glob_args` to filter empty patterns and paths with `.filter(|s| !s.is_empty())`
  - Error messages now clearly state "path parameter is required. Send '.' for current directory" instead of confusing "pattern argument" errors
  - Affects GLM-5 and GLM-5-Turbo models which were sending `{"path":"","pattern":"..."}` causing the pattern to be lost during JSON deserialization

## [0.1.12] - 2026-06-01

### Fixed
- **Grep/Glob Tool Silent Failures**: Fixed silent failures when LLM sends empty string values for `path` and `pattern` parameters
  - Made `path` parameter required (non-optional) in both `grep` and `glob` tools, matching the `list` tool pattern
  - Added explicit `grep` and `glob` handlers in `converter.rs` that filter out empty string values
  - Updated `validate_args` to check for non-empty pattern values (not just presence)
  - Updated `parse_grep_args` and `parse_glob_args` to filter empty patterns and paths with `.filter(|s| !s.is_empty())`
  - Error messages now clearly state "path parameter is required. Send '.' for current directory" instead of confusing "pattern argument" errors
  - Affects GLM-5 and GLM-5-Turbo models which were sending `{"path":"","pattern":"..."}` causing the pattern to be lost during JSON deserialization

## [0.1.11] - 2026-03-10

### Fixed
- **List Tool Path Parameter**: Made `path` parameter required to prevent GLM-5 from sending null/empty values
  - Previously, optional path parameter caused issues with certain LLMs (GLM-5) that would send null or empty strings
  - Path is now a required parameter, ensuring explicit directory specification
  - Improves reliability when used with various LLM providers

### Changed
- **List Tool Debug Logging**: Replaced `tracing` crate with simple `debug!` macro for debug logging
  - Reduces dependency overhead for basic debug output
  - Simplifies the codebase by removing unnecessary tracing infrastructure

## [0.1.10] - 2026-03-10

### Fixed
- **Bash Tool Empty Workdir Parameter Bug**: Empty string workdir parameters are now treated as `None` instead of being processed as valid paths
  - Previously, when LLM provided `{"workdir": ""}`, the tool would fail with "No such file or directory" error
  - Added `.filter(|s| !s.is_empty())` in `parse_bash_args()` to convert empty strings to `None`
  - This prevents command execution failures and agent confusion when empty workdir is provided
  - Related to fix in 0.1.7 for list tool - same pattern applied to bash tool

## [0.1.9] - 2026-03-10

### Fixed
- **CLI Tool Argument Parsing**: Fixed command-line interface to properly parse JSON arguments
  - CLI now parses JSON arguments from strings like `{"path":"/home/leo"}` and converts them to named args
  - Previously, JSON strings were treated as positional args, causing tools to ignore them
  - This fix enables proper CLI usage: `cats list '{"path":"/home/leo"}'`
  - Combined with list tool JSON converter handler for complete fix

## [0.1.8] - 2026-03-10

### Fixed
- **JSON Converter for List Tool**: Added explicit handler for `list` tool in JSON-to-ToolArgs conversion
  - Previously, the `list` tool fell into the default case which could cause parameter conversion issues
  - Now properly handles empty path strings by filtering them out (treating empty as None)
  - Ensures robust parameter handling when LLM calls the list tool via function calling APIs
  - Related to fix in 0.1.7 - provides defense-in-depth for empty path parameter handling

## [0.1.7] - 2026-03-10

### Fixed
- **List Tool Empty Path Parameter Bug**: Empty string path parameters are now treated as `None` instead of being processed as valid paths
  - Previously, when LLM provided `{"path": ""}`, the tool would list the current working directory instead of the intended directory
  - Added `.filter(|s| !s.is_empty())` in `parse_list_args()` to convert empty strings to `None`
  - This prevents agent confusion and repeated tool calls when empty paths are provided
  - Fixes issue where agent would appear "stuck" repeatedly listing the wrong directory

## [0.1.6] - 2026-02-27

### Changed
- **TLS Backend**: Switched from native-tls to rustls for cross-compilation support
  - `reqwest` now uses `rustls-tls-webpki-roots` instead of default native-tls
  - Enables static linking with musl for portable binaries
  - No OpenSSL dependency required

## [0.1.5] - 2026-02-23

### Fixed
- **Working Directory Resolution**: Tools now read working directory from `ToolState` at execution time instead of capturing it at instantiation
  - This fixes the issue where tools would use the wrong directory when invoked from a different context
  - Mirrors OpenCode's approach using async context
  - Affected tools: `bash`, `read`, `write`, `edit`, `multiedit`, `glob`, `grep`, `list`

### Added
- `ToolRegistry::set_working_directory()` - Set the working directory for all tools
- `ToolRegistry::get_working_directory()` - Get the current working directory from tool state

## [0.1.4] - 2026-02-21

### Changed
- **BREAKING**: Default feature changed from `old` to `opencode`
- `create_tool_registry_with_open_window_size()` now takes `Option<usize>` instead of `usize`

### Added
- OpenCode-compatible tool set is now the default:
  - `bash` - Execute shell commands
  - `read` - Read file contents with windowing
  - `write` - Write content to files
  - `edit` - Edit files with str_replace
  - `glob` - Find files by pattern
  - `grep` - Search file contents
  - `ls` - List directory contents
  - `webfetch` - Fetch web content (optional, requires `reqwest`)
  - `question` - Ask user questions
  - `task` - Launch sub-agents (placeholder)
  - `todo` - Todo list management
  - `patch` - Patch files (placeholder)
  - `lsp` - LSP integration (placeholder)
  - `skill` - Skill execution (placeholder)

### Removed
- `old` toolset is no longer the default (still available via `default = ["old"]` feature)

## [0.1.1] - 2025-10-29

### Fixed
- CLI now dynamically lists all available tools from registry instead of hardcoded subset
- Removed non-existent tools (`edit`, `insert`) from CLI help output
- Added missing tools to CLI help: `run_command`, all editing tools (`create_file`, `replace_text`, `insert_text`, `delete_text`, `delete_line`, `overwrite_file`, `delete_function`), file management tools (`delete_path`, `move_path`, `copy_path`, `create_directory`), and `classify_task`
- CLI help output now stays automatically synchronized with the tool registry

## [0.1.0] - 2025-10-26

### Added
- Initial public release of CATS (Coding Agent ToolS)
- Extracted from Simpaticoder monorepo as independent crate
- Complete tool suite for AI coding agents:
  - File Navigation tools: `open`, `goto`, `scroll_up`, `scroll_down`
  - Search tools: `find_file`, `search_file`, `search_dir`
  - Editing tools: `create_file`, `replace_text`, `insert_text`, `delete_text`, `delete_line`, `overwrite_file`, `delete_function`
  - File Management tools: `delete_path`, `move_path`, `copy_path`, `create_directory`
  - Execution tools: `run_command`
  - Utility tools: `_state`, `filemap`, `submit`, `classify_task`
  - Optional `count_tokens` tool (requires `tiktoken` feature)
- Comprehensive documentation and examples
- Dual-license: MIT OR Apache-2.0
- CI/CD pipeline for automated testing
- Cross-platform support (Linux, macOS, Windows)

### Changed
- Renamed from `simpaticoder-tools` to `cats`
- Updated all internal references and documentation
- Published to crates.io as standalone crate
- Repository: https://github.com/podtan/cats

### Notes
- This is the first independent release
- Formerly part of the Simpaticoder project
- API surface is stable and production-ready
- No backward compatibility with `simpaticoder-tools` crate name

[Unreleased]: https://github.com/podtan/cats/compare/v0.1.15...HEAD
[0.1.15]: https://github.com/podtan/cats/compare/v0.1.14...v0.1.15
[0.1.14]: https://github.com/podtan/cats/compare/v0.1.13...v0.1.14
[0.1.13]: https://github.com/podtan/cats/compare/v0.1.12...v0.1.13
[0.1.12]: https://github.com/podtan/cats/compare/v0.1.11...v0.1.12
[0.1.11]: https://github.com/podtan/cats/compare/v0.1.10...v0.1.11
[0.1.10]: https://github.com/podtan/cats/compare/v0.1.9...v0.1.10
[0.1.9]: https://github.com/podtan/cats/compare/v0.1.8...v0.1.9
[0.1.8]: https://github.com/podtan/cats/compare/v0.1.7...v0.1.8
[0.1.7]: https://github.com/podtan/cats/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/podtan/cats/releases/tag/v0.1.6
[0.1.5]: https://github.com/podtan/cats/releases/tag/v0.1.5
[0.1.4]: https://github.com/podtan/cats/releases/tag/v0.1.4
[0.1.1]: https://github.com/podtan/cats/releases/tag/v0.1.1
[0.1.0]: https://github.com/podtan/cats/releases/tag/v0.1.0