# 0031 — Expandir tools/file/ con write, list, append, delete

## Objetivo

Ampliar el paquete `tools/file/` con operaciones de escritura, listado, append y borrado. Mantener el patron deny-by-default, validacion de symlinks, y respetar el flag `read_only` del config.

## Contexto

- `tools/file/file.go` actualmente solo tiene `read_file` (107 lineas)
- Seguridad existente: deny-by-default, symlink resolution via `EvalSymlinks`, output truncation a 64KB
- Helpers existentes: `validatePath()` y `resolveReal()` ya estan listos para reutilizarse
- Config `FileOpsCfg` tiene campos `AllowedPaths []string` y `ReadOnly bool` — ReadOnly ya existe pero no se usa porque no hay operaciones de escritura
- Los agentes necesitan interactuar con carpetas de trabajo (workspaces, proyectos, outputs)

## Arquitectura

```
tools/file/file.go          → mantener read_file + validatePath (existente)
tools/file/write.go         NEW → write_file tool
tools/file/list.go          NEW → list_directory tool
tools/file/append.go        NEW → append_file tool
tools/file/delete.go        NEW → delete_file tool
tools/file/file_test.go     → ampliar tests existentes
tools/file/write_test.go    NEW → tests de escritura
tools/file/list_test.go     NEW → tests de listado
tools/file/delete_test.go   NEW → tests de borrado
agents/runtime.go           → registrar nuevas tools en buildToolRegistry()
```

### Patron pure core / impure shell

- `pkg/` — sin cambios
- `tools/file/` — cada tool sigue el patron Def (puro) + Exec (impuro)
- `agents/` — solo cambio en registro de tools

## Tareas

### Fase 1: Refactor de validacion compartida

- [ ] **1.1** Extraer `validatePath()` y `resolveReal()` a `tools/file/validate.go` (ya son funciones internas, solo moverlas para reutilizarlas)
- [ ] **1.2** Crear helper `validateWritePath(absPath, cfg)` que ademas verifica `ReadOnly == false`

### Fase 2: write_file

- [ ] **2.1** Crear `tools/file/write.go` con `NewWriteFile(cfg) tools.Tool`
- [ ] **2.2** Parametros: `path` (string, required), `content` (string, required)
- [ ] **2.3** Validaciones:
  - Rechazar si `cfg.ReadOnly == true`
  - `validatePath()` contra AllowedPaths
  - Crear directorios padre si no existen (`os.MkdirAll`)
  - Limite de contenido: max 1MB de input
- [ ] **2.4** Devolver confirmacion con bytes escritos y path

### Fase 3: list_directory

- [ ] **3.1** Crear `tools/file/list.go` con `NewListDirectory(cfg) tools.Tool`
- [ ] **3.2** Parametros: `path` (string, required), `recursive` (boolean, optional, default false)
- [ ] **3.3** Validaciones:
  - `validatePath()` contra AllowedPaths
  - Limite de entries: max 500 archivos en el output
  - No seguir symlinks fuera de AllowedPaths
- [ ] **3.4** Output: lista con nombre, tamaño, tipo (file/dir), fecha modificacion

### Fase 4: append_file

- [ ] **4.1** Crear `tools/file/append.go` con `NewAppendFile(cfg) tools.Tool`
- [ ] **4.2** Parametros: `path` (string, required), `content` (string, required)
- [ ] **4.3** Validaciones:
  - Rechazar si `cfg.ReadOnly == true`
  - `validatePath()` contra AllowedPaths
  - Si el archivo no existe, crearlo (igual que write)
  - Limite de tamaño: verificar que archivo existente + contenido nuevo < 10MB
- [ ] **4.4** Devolver confirmacion con bytes añadidos y tamaño total

### Fase 5: delete_file

- [ ] **5.1** Crear `tools/file/delete.go` con `NewDeleteFile(cfg) tools.Tool`
- [ ] **5.2** Parametros: `path` (string, required)
- [ ] **5.3** Validaciones:
  - Rechazar si `cfg.ReadOnly == true`
  - `validatePath()` contra AllowedPaths
  - **Solo archivos**: no permitir borrar directorios (prevencion de `rm -rf` accidental)
  - Resolver symlinks antes de borrar (no borrar el symlink si apunta fuera de AllowedPaths)
- [ ] **5.4** Devolver confirmacion con path eliminado

### Fase 6: Registro en runtime

- [ ] **6.1** En `agents/runtime.go` → `buildToolRegistry()`, registrar las 4 tools nuevas condicionalmente:
  ```go
  if cfg.Tools.FileOps.Enabled {
      reg.Register(file.NewReadFile(cfg.Tools.FileOps))
      if !cfg.Tools.FileOps.ReadOnly {
          reg.Register(file.NewWriteFile(cfg.Tools.FileOps))
          reg.Register(file.NewAppendFile(cfg.Tools.FileOps))
          reg.Register(file.NewDeleteFile(cfg.Tools.FileOps))
      }
      reg.Register(file.NewListDirectory(cfg.Tools.FileOps))
  }
  ```
- [ ] **6.2** `list_directory` se registra siempre (no requiere escritura)

### Fase 7: Tests

- [ ] **7.1** Test: `write_file` crea archivo nuevo en AllowedPaths
- [ ] **7.2** Test: `write_file` rechaza si ReadOnly es true
- [ ] **7.3** Test: `write_file` rechaza paths fuera de AllowedPaths
- [ ] **7.4** Test: `write_file` rechaza contenido > 1MB
- [ ] **7.5** Test: `list_directory` lista correctamente archivos y subdirectorios
- [ ] **7.6** Test: `list_directory` respeta limite de 500 entries
- [ ] **7.7** Test: `list_directory` no sigue symlinks fuera de AllowedPaths
- [ ] **7.8** Test: `append_file` añade contenido al final
- [ ] **7.9** Test: `append_file` crea archivo si no existe
- [ ] **7.10** Test: `delete_file` borra archivo existente
- [ ] **7.11** Test: `delete_file` rechaza borrar directorios
- [ ] **7.12** Test: `delete_file` rechaza si ReadOnly es true
- [ ] **7.13** Test: symlink que apunta fuera de AllowedPaths es rechazado en todas las tools
- [ ] **7.14** Test: path traversal (`../`) es rechazado en todas las tools

### Fase 8: Cleanup

- [ ] **8.1** Actualizar `CLAUDE.md` seccion de tools con las nuevas herramientas
- [ ] **8.2** Actualizar `.claude/rules/create_tool.md` si hay nuevos patrones
- [ ] **8.3** `go build -tags goolm ./...` y `go test -tags goolm ./...`

---

## Ejemplo de uso

Config del agente:
```yaml
tools:
  file:
    enabled: true
    allowed_paths:
      - "/home/ubuntu/workspace/proyecto-x"
    read_only: false
```

Interaccion en Element:
```
Usuario: Lista los archivos en /home/ubuntu/workspace/proyecto-x/src
Bot: [usa list_directory] Encontre 12 archivos:
  - main.go (2.3 KB, 2026-04-01)
  - handler.go (1.1 KB, 2026-04-02)
  - ...

Usuario: Escribe un archivo test.txt con "hola mundo"
Bot: [usa write_file] Archivo creado: /home/ubuntu/workspace/proyecto-x/test.txt (10 bytes)

Usuario: Añade una linea mas al test.txt
Bot: [usa append_file] Contenido añadido: 15 bytes (total: 25 bytes)

Usuario: Borra el test.txt
Bot: [usa delete_file] Archivo eliminado: /home/ubuntu/workspace/proyecto-x/test.txt
```

Intento fuera de AllowedPaths:
```
Usuario: Lee /etc/passwd
Bot: Error: path "/etc/passwd" not under any allowed path
```

## Decisiones de diseno

- **ReadOnly como gate**: `write_file`, `append_file`, `delete_file` solo se registran si `ReadOnly == false`. `read_file` y `list_directory` siempre se registran si file tools esta habilitado
- **Solo archivos en delete**: borrar directorios es demasiado peligroso para un agente autonomo. Si necesita borrar un directorio, puede borrar archivos uno por uno
- **Limites de tamaño**: 1MB para write (evita saturar disco), 64KB para read output (evita saturar contexto LLM), 500 entries para list (evita listados enormes)
- **Crear padres automaticamente**: `write_file` hace `MkdirAll` para crear la estructura de directorios. Simplifica el uso sin riesgo de seguridad (los paths padre tambien estan bajo AllowedPaths)
- **Reutilizar validatePath()**: misma logica de seguridad para todas las operaciones. Un solo punto de validacion

## Prerequisitos

- Ninguno

## Riesgos

- **Escritura accidental**: un agente con LLM podria decidir escribir archivos incorrectos. Mitigacion: AllowedPaths restringe donde puede escribir, y el system prompt debe instruir al agente sobre cuando escribir
- **Race conditions**: dos agentes escribiendo el mismo archivo. Mitigacion: file locking no es necesario en la primera version; los agentes tipicamente tienen workspaces separados
- **Disk exhaustion**: un agente escribiendo en loop. Mitigacion: rate limiting del tool registry + limite de 1MB por write
