# Octocode 0.18.0: Un Benchmark de Recuperación Reproducible y Expansión GraphRAG a Nivel de Archivo

> Octocode 0.18.0 incluye un benchmark de búsqueda semántica de código reproducible — 127 consultas curadas con ground truth por rango de líneas, más un harness de Loc-Bench sobre 560 issues reales de GitHub — y añade expansión GraphRAG a nivel de archivo que trae archivos estructuralmente relacionados a la búsqueda antes del reranking. Open source, local-first, Apache-2.0.

[0.17.1 te permitió ejecutar cada embedding que genera Octocode en hardware que tú controlas](/blog/octocode-0-17-1-local-embedding-providers) — cualquier servidor compatible con OpenAI, cualquier modelo, sin que tu código salga nunca de tu red. Esa versión iba sobre _dónde_ se ejecuta la búsqueda.

**0.18.0 va sobre qué tan buena es realmente la búsqueda — y demostrarlo con números que puedes reproducir.**

La mayoría de las herramientas de búsqueda de código presumen calidad. Muy pocas lanzan un benchmark que puedas ejecutar tú mismo. Esta versión incluye dos: un benchmark de recuperación curado contra el propio código fuente de Octocode, y un harness de [Loc-Bench](https://github.com/czlll/Loc-Bench_V1) sobre 560 issues reales de GitHub. También añade **expansión GraphRAG a nivel de archivo** — un paso de búsqueda que trae archivos estructuralmente relacionados antes de que el reranker los puntúe.

Esto es lo que cambió, y lo que dicen los números — incluyendo las partes que no funcionaron.

---

## ¿Por Qué Hacer un Benchmark de Búsqueda de Código?

Cuando cambias una heurística de chunking o un peso de fusión, quieres saber si la recuperación mejoró o empeoró. Mirar a ojo unas pocas consultas no te lo dice — solo confirma lo que ya creías. La única respuesta honesta es un conjunto fijo de consultas, un ground truth fijo, y un número que se mueve.

Así que 0.18.0 incluye uno de verdad. Vive en [`benchmark/`](https://github.com/Muvon/octocode/tree/master/benchmark) en el repositorio, y mide el pipeline completo de Octocode de principio a fin: chunking → embedding → búsqueda vectorial → fusión híbrida → reranking.

### El ground truth

`code.csv` contiene **127 consultas**, cada una anotada con 1–3 ubicaciones de código correctas como rangos de líneas:

```
query,result1,result2,result3
```

Cada resultado es `src/path/file.rs:start-end:relevance`, donde la relevancia `2` es primaria (responde directamente a la consulta) y `1` es secundaria (contexto útil). Un resultado de búsqueda coincide por **superposición de rangos de líneas** — un acierto en las líneas 40–90 coincide con el ground truth 45–92 porque los rangos se intersecan.

El corpus está **fijado a un único commit** (`b1771ba`). El ground truth está anotado contra esos números de línea exactos, así que indexar cualquier otro checkout desviaría los rangos y rompería silenciosamente las puntuaciones. Fija primero, luego indexa.

Cómo se construyó el ground truth importa, porque una clave de respuestas descuidada hace que cualquier benchmark mienta:

1. **Candidatos generados por IA** a través de cada módulo principal y archivo de documentación.
2. **Verificación de fuente** — cada archivo y rango de líneas referenciado fue leído y comprobado contra el código real.
3. **Validación multi-agente** — agentes en paralelo re-verificaron de forma independiente los rangos de líneas, las rutas y los grados de relevancia.
4. **Correcciones informadas por la búsqueda** — si la búsqueda encontró la misma lógica en una ubicación alternativa válida, esa ubicación se _añadió_ como resultado secundario, no se eliminó.

### Las consultas difíciles

Las últimas 27 de las 127 son deliberadamente adversariales: **lenguaje natural que no refleja ningún nombre de función ni comentario.** Prueban la comprensión semántica, no la suerte con palabras clave. Algunas:

| Intención de la consulta                           | Por qué es difícil                                                  |
| -------------------------------------------------- | ------------------------------------------------------------------- |
| Evitar bucles infinitos en chunks superpuestos     | una guarda de 5 líneas, sin palabras clave de código en la consulta |
| Saltarse la indexación cuando el repo no cambió    | enterrado 100+ líneas dentro de una función de 900 líneas           |
| Estimación aproximada de tokens sin un tokenizer   | la respuesta es una sola línea: `s.len() / 4`                       |
| Evitar reindexación concurrente en el servidor MCP | `AtomicBool` + `compare_exchange`, un patrón de concurrencia        |

Si las consultas estándar puntúan cerca de 1.0 pero las difíciles se quedan atrás, el benchmark está haciendo su trabajo: expone debilidades reales de recuperación en lugar de halagar a la herramienta.

### Las métricas

Cuatro métricas de IR estándar, todas "más alto es mejor":

- **Hit@k** — ¿apareció _algún_ resultado correcto en el top k? (cobertura)
- **MRR** — rango recíproco del _primer_ resultado correcto. (¿qué tan alto está el primer acierto?)
- **NDCG@10** — recompensa poner los resultados _más_ relevantes más arriba, ponderando grado de relevancia y posición. (calidad del ranking)
- **Recall@k** — ¿qué fracción de _todos_ los bloques del ground truth se encontró? (completitud)

Las definiciones y un ejemplo resuelto están en [`benchmark/README.md`](https://github.com/Muvon/octocode/tree/master/benchmark).

---

## Los Números: Un Piso Local, Sin API Key

Estos resultados usan el stack **totalmente local, sin API key** — `jina-embeddings-v2-base-code` vía `fastembed`, **sin reranker**. Eso los convierte en un _piso_, no en un techo: un reranker de pago consciente del código sube más alto. Ejecutado contra el propio código fuente de Octocode, 127 consultas de código:

| Configuración                             | Hit@5     | Hit@10    | MRR       | NDCG@10   | Recall@10 |
| ----------------------------------------- | --------- | --------- | --------- | --------- | --------- |
| Solo vector denso                         | 0.598     | 0.717     | 0.485     | 0.528     | 0.671     |
| Híbrido, pesos RRF por defecto (0.7/0.3)  | 0.598     | 0.717     | 0.485     | 0.528     | 0.671     |
| **Híbrido, ajustado a keyword (0.3/0.7)** | **0.732** | **0.835** | **0.572** | **0.620** | **0.807** |

Dos cosas saltan a la vista.

**Los pesos RRF por defecto se comportan como solo-vector.** Con la fusión inclinada 0.7 hacia la señal densa, los resultados vectoriales dominan y la señal de keyword apenas registra — las dos filas superiores son idénticas.

**Inclinarse hacia keyword es una ganancia grande y gratis para el código.** Da la vuelta a los pesos a 0.3/0.7 — dejando que la señal BM25/keyword lidere — y Hit@5 sube **+22%** y Recall@10 **+20%**, sin coste añadido. ¿Por qué? El código está lleno de identificadores exactos — `compare_exchange`, `get_code_blocks_by_path`, `RelationshipDirection` — y la coincidencia exacta de tokens tiene un peso desproporcionado cuando lo que buscas _es_ un token. La similitud puramente semántica difumina eso; la fusión con keyword lo afina.

Es el tipo de resultado que solo encuentras midiendo. No es intuitivo que para el código quieras que la keyword _pese más_ que el embedding.

---

## Lo Que No Ayudó (Y Por Qué Está en el Benchmark)

Un benchmark que solo reporta victorias es marketing. Este reporta también las regresiones — la matriz completa de 6 variantes está en [`benchmark/RESULTS.md`](https://github.com/Muvon/octocode/tree/master/benchmark/RESULTS.md).

**Un reranker genérico empeoró las cosas.** Poner un cross-encoder local de propósito general (`bge-reranker-base`) sobre la mejor configuración no ayudó — _regresó_ Hit@5 de **0.732 de vuelta a 0.598**, deshaciendo toda la ganancia del ajuste a keyword. Y fue aproximadamente **4× más lento** (≈1900s vs ≈490s en la ejecución).

La lección no es "los rerankers son malos". Es que **la recuperación de código necesita un reranker consciente del código.** Un reranker entrenado en prosa puntúa `fn verify_hmac` contra "validación de firma de webhook" peor de lo que ya lo hizo la fusión híbrida. Un reranker ajustado a código como `voyage:rerank-2.5` es otra historia — pero tienes que verificarlo en tu propio eval, no asumirlo. El benchmark es lo que te lo permite.

---

## Expansión GraphRAG a Nivel de Archivo

La característica estrella junto al benchmark. La búsqueda densa y la de keyword puntúan bloques de forma aislada — no saben que el archivo que quieres _importa_ el archivo que encontraron, o _llama_ a una función definida al lado. La **expansión GraphRAG a nivel de archivo** cierra esa brecha.

Después de que la búsqueda híbrida inicial produce candidatos rankeados, Octocode toma los más fuertes, recorre el grafo de GraphRAG en busca de archivos **estructuralmente relacionados por imports, llamadas o extends**, extrae un puñado de bloques de esos archivos vecinos, y entrega el conjunto de candidatos _ampliado_ al reranker:

```rust
// Seeds are already RRF-ranked; only expand from the strongest few.
const SEED_LIMIT: usize = 10;        // expand from top 10 hits
const MAX_NEIGHBOR_FILES: usize = 8; // pull at most 8 related files
const BLOCKS_PER_FILE: usize = 3;    // 3 blocks per neighbor
```

Los vecinos se puntúan por `weight × confidence` a través de todos los archivos seed, los enlaces más fuertes primero. Es a nivel de archivo porque el grafo es a nivel de archivo (`CodeNode.id == ruta relativa`). Y es **best-effort**: cualquier error del grafo degrada a pasar los resultados originales directamente — nunca rompe la búsqueda.

Actívalo en `config.toml`:

```toml
[search]
graph_expansion = false  # default; requires [graphrag] enabled
```

Se entrega **desactivado por defecto**, a propósito. Aquí está la parte honesta del benchmark:

> `+graph` = expansión GraphRAG a nivel de archivo, que **solo mueve resultados una vez que un reranker vuelve a puntuar el conjunto ampliado.**

Sin un reranker, la expansión añade candidatos pero no cambia el ranking — por eso `hybrid_30_70` y `hybrid_30_70+graph` puntúan idéntico en la matriz local sin key de arriba. El beneficio de la expansión llega cuando un reranker (consciente del código) vuelve a puntuar el conjunto más grande y promueve un archivo estructuralmente relacionado que la similitud bruta rankeó demasiado bajo. La expansión ingenua también puede _diluir_ la precisión. Así que la guía grabada en el comentario de configuración es contundente: **haz A/B en tu propio eval antes de confiar en ella.** El benchmark existe precisamente para que puedas hacerlo.

---

## Loc-Bench: Issue → Código, Nada Auto-Anotado

El benchmark curado lo anotamos nosotros. Una pregunta justa: ¿son las consultas _demasiado_ amables con nuestra propia herramienta? 0.18.0 incluye un segundo harness que nos saca por completo de la ecuación.

[`benchmark/locbench.py`](https://github.com/Muvon/octocode/tree/master/benchmark) evalúa Octocode sobre **Loc-Bench V1** (`czlll/Loc-Bench_V1`): **560 issues reales de GitHub** de repos populares de Python. La consulta es el _texto original del issue_. El ground truth es el conjunto de archivos y funciones que el _fix real ya fusionado_ editó (el campo curado `edit_functions` del dataset). Nada está auto-anotado — es la tarea estándar de la industria de "dado un reporte de bug, encuentra el código a cambiar".

Por instancia, el harness clona el repo (blobless, cacheado), hace checkout del commit base, ejecuta `octocode index`, busca con el texto del issue, y puntúa los archivos recuperados contra el fix real — tanto a **nivel de archivo** (hit@k, recall@k, MRR, accuracy) como a **nivel de hunk** (¿un resultado recuperado se superpuso con las líneas que el parche realmente tocó?), desglosado por categoría de issue:

```bash
python3 benchmark/locbench.py --limit 10 --verbose   # smoke test
python3 benchmark/locbench.py                         # full 560
python3 benchmark/locbench.py --category "Bug Report"
```

Esto es una herramienta de medición, no una afirmación de leaderboard — está ahí para que puedas ejecutar localización de issues del mundo real en tu propio hardware y modelos, y para que cada cambio futuro de Octocode pueda medirse contra un benchmark que no escribimos nosotros.

---

## La Matriz de Variantes

`score.py` mide una configuración. [`run_matrix.py`](https://github.com/Muvon/octocode/tree/master/benchmark) hace un barrido de varias sobre **un único índice compartido** — así lo único que cambia entre filas son los parámetros en tiempo de búsqueda, no el corpus ni los embeddings. Escribe una tabla de comparación en `RESULTS.md` (y `RESULTS.json` crudo), y acumula entre ejecuciones, de modo que una re-ejecución enfocada se fusiona con la tabla existente.

```bash
# Fija el corpus al commit del ground truth primero — o los rangos de líneas se desvían
git worktree add /tmp/corpus b1771ba

# Matriz local completa (fastembed, sin API key): solo-vector vs barrido de pesos híbridos
CORPUS=/tmp/corpus python3 benchmark/run_matrix.py

# Añade un cross-encoder reranker local (aún sin key) — reutiliza el mismo índice
CORPUS=/tmp/corpus SKIP_INDEX=1 ONLY=rerank \
  RERANK_MODEL=fastembed:bge-reranker-base python3 benchmark/run_matrix.py
```

Variantes: `vector_only`, `hybrid_70_30` (pesos por defecto), `hybrid_30_70` (inclinado a keyword), `+graph` (expansión GraphRAG), y `+rerank` (cross-encoder). Si configuras `VOYAGE_API_KEY`, las variantes de reranker de pago consciente del código se activan automáticamente — así es como encuentras el techo real por encima del piso local.

---

## Todo lo Demás

**Metadatos del registro MCP y esquema del servidor actualizados.** Mantiene el descriptor del servidor MCP publicado de Octocode al día con la superficie real de herramientas, para que los registros y clientes vean el esquema correcto. Más el mantenimiento habitual de dependencias y tooling — sin cambios de comportamiento.

---

## Actualizar

```bash
# Homebrew
brew upgrade muvon/tap/octocode

# Instalador universal
curl -fsSL https://raw.githubusercontent.com/Muvon/octocode/master/install.sh | sh

# Cargo
cargo install octocode --version 0.18.0
```

Nada en esta versión cambia tu índice o configuración — actualizar es seguro, y tu setup existente sigue funcionando. Dos cosas que probar:

1. **Inclina tus pesos de fusión hacia keyword.** Si buscas código más que prosa, el benchmark dice que el RRF liderado por keyword es una ganancia de precisión gratis. Mídelo en tu repo.
2. **Ejecuta el benchmark.** Clona Octocode, fija el corpus, y ejecuta `run_matrix.py` contra tu propio modelo de embedding y reranker. Los números de arriba son un piso local — encuentra tu propio techo.

Para probar la expansión GraphRAG, activa `[graphrag]`, pon `search.graph_expansion = true`, combínala con un reranker consciente del código, y haz A/B contra tu baseline antes de dejarla activada.

---

Octocode es open source (Apache 2.0) en [github.com/Muvon/octocode](https://github.com/Muvon/octocode) — benchmark, ground truth y resultados crudos incluidos. Es el motor de búsqueda de código detrás de [Octomind](https://octomind.run), y ahora su calidad de recuperación es algo que puedes medir tú mismo, no solo creer bajo nuestra palabra.
