13. Codebase analysis y editing
13. Codebase analysis y editing
Hasta ahora trabajamos con Goose en proyectos chicos donde leer 4 archivos cubre todo. En repos reales — 100k+ líneas, decenas de microservicios, dependencias inter-archivo no obvias — los agentes genéricos fallan. Goose tiene capabilities específicos para escalar.
Enhanced code editing
Cambios estructurados que respetan el AST, no solo búsqueda y reemplazo de strings.
El problema del search-and-replace
Renombrá la variable userId a customerId
Búsqueda y reemplazo simple:
sed -i 's/userId/customerId/g' src/**/*.ts
Pero rompe:
- Comentarios que mencionan
userIdlegítimamente. - Strings que contienen
userIdcomo literal (logs, mensajes de error). - Otros símbolos que casualmente contienen
userId(getUserIdentifierse vuelvegetCustomerIdentifier).
Como lo hace Goose enhanced editing
flowchart LR
R["Request:<br/>rename userId a customerId"] --> AST[Parsear AST]
AST --> Find["Encontrar declaraciones<br/>y todas las refs"]
Find --> S["Aplicar cambio<br/>solo a refs ligadas"]
S --> Verify["Verificar que el código<br/>sigue compilando"]
El agente:
- Identifica el scope de la variable (función, módulo, global).
- Encuentra todas las referencias ligadas (no string matches).
- Aplica el cambio.
- Corre el typechecker / linter para validar.
> Renombrá customerId a clientId en toda la app
✓ Identificadas 47 referencias en 12 archivos
✓ Cambio aplicado
✓ tsc --noEmit: 0 errores
Codebase analysis
Antes de modificar, entender la estructura del repo.
Capabilities incluidas:
- Indexar archivos por tipo, tamaño, fecha modificación.
- Search semántico: buscar por significado, no solo regex.
- Call graph: quién llama a qué función.
- Dependency graph: qué módulo depende de cuál.
- Hot paths: qué partes del código se modifican más.
Comandos relevantes
> /index
🪿 Indexando codebase... (12,432 archivos, 1.2M líneas)
✓ Index listo en 8.4s
> /find similar src/auth/login.ts
Archivos con estructura similar a login.ts:
- src/auth/register.ts (similarity: 0.84)
- src/auth/reset-password.ts (similarity: 0.71)
> /callers loginUser
Funciones que llaman a loginUser:
- src/routes/auth.ts:34 (POST /login)
- src/middleware/refresh.ts:12 (refreshSession)
- src/test/auth.test.ts:87 (test fixture)
Análisis semántico vs sintáctico
| Análisis sintáctico | Análisis semántico |
|---|---|
| Match exacto de strings/regex | Match por significado |
| Rápido | Más lento (involucra LLM) |
| Determinístico | Probabilístico |
| Falla con sinónimos | Capta sinónimos / paráfrasis |
Goose combina ambos: usa sintáctico para indexing rápido (con tools como tree-sitter) y semántico cuando la tarea lo requiere (con embeddings o calls al LLM).
Embeddings index
Goose puede mantener un vector index del codebase:
# .goose/config.yaml
codebase_analysis:
embeddings:
enabled: true
model: text-embedding-3-small
chunk_size: 500
refresh_on_save: true
Una vez indexado:
> ¿Dónde se calcula la facturación con descuentos?
🪿 [embeddings] Top 5 archivos relevantes:
- src/billing/calculator.ts (similarity: 0.91)
- src/discounts/apply.ts (similarity: 0.88)
- src/billing/tax.ts (similarity: 0.74)
El agente recibe estos archivos en contexto automáticamente sin que vos tengas que listarlos.
File management integrado
Goose maneja archivos con awareness del repo:
Operaciones atómicas
> Movés src/auth.ts a src/auth/index.ts y actualizás todos los imports
✓ Renombrado
✓ Imports actualizados en 23 archivos
✓ Tests siguen pasando
Bajo el capó hace:
git mvpara preservar history.- Busca todos los
import.*from '.*auth'con AST. - Actualiza paths.
- Corre
bun buildo equivalente para validar.
Diff antes de aplicar
> /diff
src/billing/calculator.ts:
- function calculate(items: Item[]): number {
+ function calculate(items: Item[], options?: CalcOptions): number {
src/billing/index.ts:
- export { calculate } from './calculator';
+ export { calculate, CalcOptions } from './calculator';
Antes de aplicar, podés:
- Aceptar todo:
Y - Rechazar todo:
N - Aceptar selectivamente:
s(interactivo)
Undo
> /undo
🪿 Revertí los últimos cambios en src/billing/*
Revierte file system al estado anterior al último cambio aplicado por el agente.
Patrones de uso para repos grandes
Pattern 1: scope explícito
En vez de “refactorizá X”, limitá:
> Refactorizá src/auth/ a usar inyección de dependencias.
> Solo tocá esa carpeta. No modifiques tests por ahora.
El agente hace /index src/auth/, ignora el resto.
Pattern 2: divide y vencé
Para tareas que cruzan muchos módulos:
> /mode plan
> Migrar este monolito a microservicios. Hacé un plan por módulo.
Plan output:
1. extract /auth → service-auth
2. extract /billing → service-billing
3. extract /notifications → service-notifications
[...]
Después ejecutás un módulo a la vez:
> /mode act
> Ejecutá solo el step 1
Pattern 3: pair programming explícito
Para tareas críticas, hacé que el agente te explique antes de actuar:
> Estoy por hacer este cambio: [descripción]. Antes de tocar código, explicá:
> 1. Qué archivos vas a modificar
> 2. Qué tests podrían fallar
> 3. Qué supuestos estás haciendo
Cuando estás seguro:
> Ok, procedé con esos cambios.
Pattern 4: codebase question-answering
A veces no querés modificar nada, solo entender:
> /mode plan
> ¿Cómo funciona el flujo de autenticación en este proyecto?
> Quiero saber:
> - Qué archivos están involucrados
> - Qué libraries de terceros usa
> - Dónde se valida el token
> - Cómo se manejan refresh tokens
Output: un mapa narrativo del código que podés guardar en docs/auth-flow.md.
Códigos generados vs código humano
Cuando el agente escribe código, debería ser indistinguible de tu código humano. No querés tags como ”// generated by Goose” en producción — eso es ruido. Pero sí querés:
- Mismo estilo: indentación, comments, naming.
- Mismas convenciones: error handling, logging, tests.
- Mismas dependencies: no introducir libraries nuevas sin razón.
Para garantizar esto, tu persistent.md tiene que ser explícito sobre las convenciones (ya cubierto en el capítulo 6).
Tests y validación post-cambio
Una práctica robusta:
# .goose/config.yaml
post_edit_validation:
enabled: true
commands:
- "bun typecheck"
- "bun lint"
- "bun test --run"
on_failure: ask # ask, retry, abort
Después de cada cambio aplicado, Goose corre estos comandos. Si fallan:
ask: te pregunta qué hacer.retry: el agente intenta arreglar lo que rompió.abort: revierte el cambio.
retry es especialmente útil: el agente hace cambio → tests fallan → ve el output del fallo → corrige → retry. Loop hasta que tests pasen.
Limitaciones honestas
1. Indexing inicial cuesta
Indexar un monorepo de 1M de líneas tarda minutos y consume CPU/memoria. Es one-time pero pesado.
2. Embeddings no son gratis
Si activás embeddings con re-index frecuente, el costo en API calls puede ser significativo. Para repos chicos no hace falta; para grandes paga.
3. Refactors muy grandes son riesgosos
Un cambio que toca 500 archivos sigue siendo riesgo. Goose puede ejecutar el cambio pero dividir es responsabilidad tuya: hacelo en commits chicos para poder revertir granularmente.
4. Languages soportados varían
Enhanced editing funciona muy bien en TypeScript / JavaScript / Python / Go / Rust. Lenguajes menos populares (Elixir, Crystal, etc.) caen al editing simple basado en strings.
¿Qué viene?
Cubrimos casi todo. En el último capítulo cerramos con deployment empresarial: custom distributions, plataformas tipo VMware Tanzu, environment variables completas, logging, observabilidad y best practices acumuladas.