diff options
Diffstat (limited to '.rules')
| -rw-r--r-- | .rules/changelog/2026-03/31/01.md | 21 | ||||
| -rw-r--r-- | .rules/plan/dispatch-tool-files-plan.md | 154 |
2 files changed, 175 insertions, 0 deletions
diff --git a/.rules/changelog/2026-03/31/01.md b/.rules/changelog/2026-03/31/01.md new file mode 100644 index 0000000..ff5291f --- /dev/null +++ b/.rules/changelog/2026-03/31/01.md @@ -0,0 +1,21 @@ +# 2026-03-31 — Add interface dependency and full test suite + +## Changes + +### Dependency Setup +- Added `dispatch-tools-interface` as a runtime dependency in `gemspec` (`~> 0.1`) +- Added local path reference in `Gemfile` for development + +### Source Updates +- `lib/dispatch/tool/files.rb`: Added `require "dispatch/tools/interface"` and defined `SandboxError`, `FileNotFoundError`, `FileExistsError` error classes +- `spec/spec_helper.rb`: Added interface, tmpdir, and fileutils requires +- `spec/dispatch/tool/files_spec.rb`: Removed placeholder failing test + +### Test Suite (7 spec files) +- `sandbox_spec.rb` — Path resolution, traversal attacks, symlinks, absolute paths, `within_worktree?` +- `read_file_spec.rb` — Full read, line ranges, line numbers, not found, binary detection +- `write_file_spec.rb` — Write new, overwrite, parent dirs, byte count, sandbox +- `edit_file_spec.rb` — Single/multiple edits, sequential application, not found, ambiguous match +- `create_file_spec.rb` — Create new, fail on existing, parent dirs, sandbox +- `list_files_spec.rb` — Recursive/non-recursive, glob patterns, relative paths, directory errors +- `search_files_spec.rb` — Plain text, regex, invalid regex, path scoping, pattern filter, result limit diff --git a/.rules/plan/dispatch-tool-files-plan.md b/.rules/plan/dispatch-tool-files-plan.md new file mode 100644 index 0000000..353f31c --- /dev/null +++ b/.rules/plan/dispatch-tool-files-plan.md @@ -0,0 +1,154 @@ +# Dispatch Tool Files — Gem Implementation Plan + +This plan covers the full implementation of the `dispatch-tool-files` gem. + +--- + +## Overview + +This gem provides file operation tools for Subagents. All operations are sandboxed to the agent's worktree — no file access outside the worktree is permitted. + +**Dependency:** `dispatch-tools-interface` + +--- + +## Gem Structure + +``` +dispatch-tool-files/ +├── lib/ +│ └── dispatch/ +│ └── tool/ +│ └── files/ +│ ├── read_file.rb +│ ├── write_file.rb +│ ├── edit_file.rb +│ ├── create_file.rb +│ ├── list_files.rb +│ ├── search_files.rb +│ ├── sandbox.rb +│ └── register.rb +├── spec/ +│ └── dispatch/ +│ └── tool/ +│ └── files/ +│ ├── read_file_spec.rb +│ ├── write_file_spec.rb +│ ├── edit_file_spec.rb +│ ├── create_file_spec.rb +│ ├── list_files_spec.rb +│ ├── search_files_spec.rb +│ └── sandbox_spec.rb +├── dispatch-tool-files.gemspec +├── Gemfile +├── Rakefile +└── README.md +``` + +--- + +## 1. Sandbox Module (`Dispatch::Tool::Files::Sandbox`) + +All tools use this module to validate that file paths stay within the worktree. + +### Methods + +- `resolve_path(path, worktree_path:)` — Resolve a relative path against the worktree root. Expand symlinks, resolve `..`, and verify the resulting absolute path starts with `worktree_path`. Raises `Dispatch::Tool::Files::SandboxError` if the path escapes. +- `within_worktree?(path, worktree_path:)` — Boolean check. + +### Security Considerations + +- Must handle symlink attacks (symlink pointing outside worktree). +- Must handle `../` traversal. +- Must handle absolute paths (reject or re-root them). + +--- + +## 2. Tools + +Each tool is a `Dispatch::Tools::Definition` instance. All tools require `worktree_path` in the `context` hash. + +### `read_file` + +- **Parameters:** `path` (required, String), `start_line` (optional, Integer, 0-based), `end_line` (optional, Integer, 0-based, -1 for EOF). +- **Behavior:** Read file contents. If line range specified, return only those lines. Prefix each line with its line number. +- **Success output:** File contents as a string with line numbers. +- **Failure:** File not found, path outside sandbox, binary file detection. + +### `write_file` + +- **Parameters:** `path` (required, String), `content` (required, String). +- **Behavior:** Write/overwrite the entire file with the given content. Creates parent directories if needed. +- **Success output:** Confirmation message with path and byte count. +- **Failure:** Path outside sandbox, permission errors. + +### `edit_file` + +- **Parameters:** `path` (required, String), `edits` (required, Array of `{ old_text: String, new_text: String }`). +- **Behavior:** For each edit, find `old_text` in the file and replace with `new_text`. Edits are applied sequentially. If `old_text` is not found, the edit fails. +- **Success output:** Confirmation with number of edits applied. +- **Failure:** File not found, `old_text` not found, ambiguous match (multiple occurrences — require more context). + +### `create_file` + +- **Parameters:** `path` (required, String), `content` (required, String). +- **Behavior:** Create a new file. Fails if the file already exists (use `write_file` to overwrite). Creates parent directories. +- **Success output:** Confirmation message. +- **Failure:** File already exists, path outside sandbox. + +### `list_files` + +- **Parameters:** `path` (optional, String, defaults to `.`), `pattern` (optional, String, glob pattern), `recursive` (optional, Boolean, default `true`). +- **Behavior:** List files in the directory. Apply glob pattern if provided. Returns paths relative to the worktree root. +- **Success output:** Newline-separated list of file paths. +- **Failure:** Directory not found, path outside sandbox. + +### `search_files` + +- **Parameters:** `query` (required, String), `path` (optional, String, defaults to `.`), `pattern` (optional, String, file glob to filter), `is_regex` (optional, Boolean, default `false`). +- **Behavior:** Search for text in files. Returns matching lines with file paths and line numbers. Limit results to a reasonable maximum (e.g. 100 matches). +- **Success output:** Formatted search results. +- **Failure:** Invalid regex, path outside sandbox. + +--- + +## 3. Registration (`Dispatch::Tool::Files.register(registry)`) + +A convenience method that registers all file tools into a `Dispatch::Tools::Registry`. + +```ruby +registry = Dispatch::Tools::Registry.new +Dispatch::Tool::Files.register(registry) +# Now registry contains: read_file, write_file, edit_file, create_file, list_files, search_files +``` + +--- + +## 4. Error Classes + +- `Dispatch::Tool::Files::Error` — base error. +- `Dispatch::Tool::Files::SandboxError` — path escapes the worktree. +- `Dispatch::Tool::Files::FileNotFoundError` — file does not exist. +- `Dispatch::Tool::Files::FileExistsError` — file already exists (for create_file). + +--- + +## 5. Testing + +- **Sandbox tests:** Path resolution, traversal attacks, symlink attacks, absolute path rejection. +- **Per-tool tests:** Use a temporary directory as a fake worktree. + - `read_file`: read full file, read line range, file not found, binary detection. + - `write_file`: write new file, overwrite existing, create parent dirs. + - `edit_file`: single edit, multiple edits, old_text not found, sequential application. + - `create_file`: create new, fail on existing. + - `list_files`: list all, glob filter, recursive/non-recursive. + - `search_files`: plain text search, regex search, file pattern filter, result limiting. + +--- + +## Key Constraints + +- **All paths must be validated through the Sandbox** before any filesystem operation. +- Tools return `Dispatch::Tools::Result` (success or failure) — they never raise exceptions to the caller. +- The `context[:worktree_path]` is the absolute path to the worktree root, provided by the Rails agent loop. +- Binary file detection: `read_file` should detect and refuse to read binary files (check for null bytes in first N bytes). |
