13. Codebase analysis y editing

Por: Artiko
goosecodebase-analysiscode-editingsemanticcall-graph

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:

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:

  1. Identifica el scope de la variable (función, módulo, global).
  2. Encuentra todas las referencias ligadas (no string matches).
  3. Aplica el cambio.
  4. 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:

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ácticoAnálisis semántico
Match exacto de strings/regexMatch por significado
RápidoMás lento (involucra LLM)
DeterminísticoProbabilístico
Falla con sinónimosCapta 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:

  1. git mv para preservar history.
  2. Busca todos los import.*from '.*auth' con AST.
  3. Actualiza paths.
  4. Corre bun build o 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:

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:

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:

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.