# filesystem — Manual

## What it does
Reads and edits files and directories. The action that matters most is picking the cheapest deterministic way to make a change: an AI-assisted edit is the last resort, never the first.

## Editing policy — in this order

1. **`edit`** — exact text. Pass `old` (which must occur **exactly once** in the file) and `new`. Read the file first and copy `old` verbatim, including indentation. This is the default for almost every change, and it either applies exactly or fails loudly — it cannot quietly edit the wrong line.
2. **`edit_lines`** — line numbers. Pass an `operations` array when you know the exact lines ("change line 15"). Deterministic, no AI call.
3. **`smart_edit`** — AI-assisted, for changes that genuinely cannot be expressed as text or line numbers: "rename this symbol everywhere", "add type hints to every function". It costs an LLM call and reads the whole file. Try the two above first, always.
4. **`write`** — create a file or rewrite it entirely (pass `content`).
5. **`query`** — ask a question about a file's content and get an answer instead of the file.
6. Everything else — `read`, `append`, `list`, `find`, `find_up`, `grep`, `diff`, `info`, `copy`, `move`, `delete`, `exists`, `mkdir`.

## Parameters

| Parameter | Used by | Description |
|-----------|---------|-------------|
| `action` | required | One of the actions listed above. |
| `path` | required | File or directory. `~` is expanded. |
| `content` | `write`, `append` | Text to write or append. |
| `old` / `new` | `edit` | Exact text to replace, and its replacement. |
| `operations` | `edit_lines` | Array of `{"op": "replace"\|"delete"\|"insert", "start", "end", "after", "content"}`. Lines are 1-based and inclusive; `insert` uses `after`. |
| `destination` | `move`, `copy`, `diff` | Target path, or the second file to compare against. Missing parent directories are created. |
| `pattern` | `find`, `find_up`, `grep` | Glob for `find` (e.g. `*.log`), exact filename for `find_up`, search text for `grep`. |
| `glob` | `grep` | Optional filename filter for a recursive search, e.g. `*.py`. |
| `regex` | `grep` | `true` for pattern matching, default `false` = literal substring. |
| `question` | `query` | The question to answer from the file. |
| `instruction` | `smart_edit` | The change to make, in natural language. |
| `offset` / `limit` | `read` | First line (1-based) and how many to return. |
| `numbered` | `read` | Default `true`, prefixed with 1-based line numbers. Set `false` for raw content you are about to copy into an edit. |
| `recursive` | `list` | Full tree instead of the top level. |

## Actions worth knowing

```json
{"action": "info", "path": "src/app.py"}
{"action": "read", "path": "src/app.py", "offset": 40, "limit": 30}
{"action": "read", "path": "src/app.py", "numbered": false}

{"action": "edit", "path": "src/app.py", "old": "    return None", "new": "    return {}"}
{"action": "edit_lines", "path": "src/app.py",
 "operations": [{"op": "replace", "start": 15, "end": 15, "content": "    limit = 50"}]}

{"action": "grep", "path": "src", "pattern": "TODO", "glob": "*.py"}
{"action": "find", "path": ".", "pattern": "*.log"}
{"action": "find_up", "path": "src/deep/file.py", "pattern": "pyproject.toml"}
{"action": "diff", "path": "src/app.py", "destination": "src/app.py.bak"}
```

- **`info` before `read`** on an unknown file — it reports the size, which tells you whether a full read is going to flood the context.
- **`find_up`** walks from `path` towards the root looking for an exact filename — the way to locate `pyproject.toml`/`.git` from a nested directory.
- **`diff`** is a unified diff between two **files** — `path` against `destination` (both required; directories are rejected).

## `query` and `smart_edit`

Both spend an LLM call, so both are for what the deterministic actions cannot do.

`query` answers a question *instead of* returning the file — use it to avoid reading a large file whose content you only need one fact from:

```
{"action": "query", "path": "src/app.py", "question": "What does calculate() return?"}
{"action": "query", "path": "src/app.py", "question": "Where is class UserManager defined?"}
{"action": "query", "path": "deploy.sh", "question": "Which environment variables does this read?"}
```

`smart_edit` takes an instruction in natural language:

```
{"action": "smart_edit", "path": "src/app.py", "instruction": "Rename process to handle_request"}
{"action": "smart_edit", "path": "src/app.py", "instruction": "Add type hints to every function"}
{"action": "smart_edit", "path": "src/app.py", "instruction": "Replace the hardcoded URL with a constant BASE_URL"}
```

It reads the whole file and can touch more than you asked. When the change *is* expressible as exact text or line numbers, `edit`/`edit_lines` do it exactly, for free, and fail loudly instead of approximately.

## Access

In multi-user mode every path is resolved inside `user_data/<user_id>/`; a path that escapes it returns `Access denied: ... outside allowed paths`. Do not work around it by writing to `/tmp` or an absolute path — ask for the file to be placed in the workspace instead. In single-user/admin mode the allowed roots are the working directory and the configured paths.

## Common mistakes

- `edit` with an `old` string that appears twice — the call is refused, correctly. Include more surrounding context until it is unique.
- `old` copied from a numbered `read` — the line-number column is not part of the file. Read with `numbered: false` when copying text into an edit.
- `write` to change one line: it replaces the whole file, and anything you did not retype is gone. Use `edit`.
- `smart_edit` for something `edit` could do — an LLM call to change a line is a waste, and it can touch more than you asked.
- `delete` on a directory — it removes the whole tree recursively, with no prompt and no undo.
- `mkdir` for a path whose parent does not exist: it does not create intermediate directories. `write` does.
