Ir al contenido

Grafo de código

En lugar de leer archivos «a ciegas», Context Code construye un grafo de conocimiento de tu proyecto: funciones, clases, símbolos, imports y llamadas entre ellos, en varios lenguajes (TypeScript/JavaScript, Python, Rust, C/C++, C#, entre otros), además de los enlaces internos de tu documentación Markdown.

  • Comunidades de código: módulos que se relacionan entre sí, detectados automáticamente — la estructura real del proyecto, no la de las carpetas.
  • Nodos centrales («god nodes»): piezas con demasiadas dependencias, candidatas a refactor.
  • Conexiones inesperadas: relaciones que cruzan comunidades o carpetas distintas y que a simple vista no parecen relacionadas.

El grafo no se saca de buscar texto: los archivos se parsean de verdad (árbol de sintaxis, tree-sitter), así que un nombre dentro de una cadena o de un comentario no inventa una relación.

  • Imports de TypeScript y JavaScript por AST: import … from, export … from, require(…) e import(…) dinámico. Al resolver el destino se tienen en cuenta los alias de tsconfig.json/jsconfig.json (compilerOptions.paths y baseUrl, siguiendo los extends), los barrels (index.ts, index.js…) y los re-exports: si a.ts hace export * from './b', el grafo dibuja también la arista hacia b. Un import { X } from "@/core" deja de ser una dependencia «externa» y pasa a apuntar al archivo real.
  • Herencia: class A extends B e implements en TypeScript, class A(B) en Python, impl Trait for Tipo en Rust y las clases base de C++ y C#. Salen como aristas propias —Herencia e Implementa—, separadas de las llamadas.
  • C, C++ y C# también por AST: sus funciones, clases, structs y métodos se extraen con tree-sitter igual que TS/JS, Rust y Python. Sus #include y using se siguen resolviendo por patrón, y los namespace se ignoran a propósito (no son piezas navegables).

Una llamada de TypeScript nunca «salta» a una función de Rust ni a una de Python: son lenguajes distintos y el grafo no se inventa esas aristas. Pero las dos costuras que más importan en una app cliente/servidor sí se detectan y se dibujan.

Comandos de Tauri. Cada invoke("mi_comando") del front se une con la fn mi_comando marcada #[tauri::command] en Rust, con una arista Invoke Tauri. Y si el comando no existe —un invoke a un nombre que ya nadie implementa, es decir, un botón que no hace nada—, el grafo crea un nodo roto y lo cuenta aparte en las estadísticas.

Rutas HTTP. Las rutas declaradas por Express, NestJS, FastAPI, Flask, ASP.NET o axum se convierten en nodos de ruta (GET /api/x) con una arista Ruta → handler hacia la función que las atiende.

Todo esto queda resumido en el informe .context/grafo/GRAPH_REPORT.md, en una sección Puentes front↔back con dos listas muy concretas:

  • Invokes sin comando en Rust — cada línea es un botón que no hace nada, con el archivo desde el que se llama.
  • Rutas sin handler identificado — rutas declaradas a las que no se ha podido asociar una función.

Si el proyecto no tiene ni invokes ni rutas, la sección no aparece.

El grafo se explora en una vista 3D nativa: útil para entender de un vistazo la arquitectura de un proyecto grande, ver qué se conecta con qué y examinar las relaciones de cada nodo.

La vista 3D del grafo: comunidades de código, nodos centrales y sus relaciones.

En la leyenda, además de los tipos de siempre, hay ahora Ruta HTTP entre los nodos y cuatro relaciones nuevas entre las aristas: Herencia, Implementa, Invoke Tauri y Ruta → handler. Cada entrada se enciende y se apaga con un clic, y con el segundo botón se aísla (deja solo ese tipo).

Atajos de teclado dentro de la vista:

TeclaQué hace
1 … 6Alternan Markdown, Carpeta, Etiqueta, Roto, Código y MD+Código
7Alterna los nodos de Ruta HTTP
0Mostrar todos los tipos otra vez
Ctrl+K / ⌘KSalta al buscador (filtrar por nombre o ruta)

El agente no lee el JSON del grafo: usa la herramienta GraphQuery, que devuelve texto ya resumido y recortado a un tope de tokens. Además de las consultas de siempre (summary, neighbors, subgraph, path, tag, orphans), la 3.6.9 trae cuatro que responden justo a las preguntas que se hacen al tocar código ajeno:

ConsultaPara qué sirve
exploreLa más útil: en una sola llamada devuelve el código fuente literal del símbolo, quién lo llama, a qué llama y su radio de impacto.
symbolBusca símbolos por nombre o subcadena y lista archivo, línea, tipo y cuántas llamadas entran y salen de cada uno.
callers / calleesQuién llama a un símbolo, o a qué llama él, agrupado por archivo. Admite depth de 1 a 3.
impactQué se rompe si lo tocas: cuántos nodos dependen de él, en cuántos archivos, desglosado por profundidad y con la lista de archivos afectados. depth de 1 a 4, por defecto 2.

Cuando un resultado no cabe, el agente lo sabe: impact avisa si el recorrido llegó al tope de nodos y explore avisa si tuvo que recortar el fuente.

No hay que saber nada de esto para aprovecharlo. Basta con preguntar en lenguaje llano:

explícame handle_livekit_join y qué rompe si lo cambio

El agente resuelve las dos cosas con una sola consulta explore —te trae la función entera, quién la llama, a qué llama y los archivos que dependen de ella— en vez de encadenar media docena de búsquedas y lecturas de archivos completos.

Un grafo no puede crecer sin fin, así que hay topes: 20 000 símbolos, 20 000 llamadas y 12 000 imports. Lo importante es cómo se reparten: primero el código de tu proyecto y solo después las carpetas vendorizadas —las que acaban en -master, las runtime*, third_party, external, include y similares—, y dentro de cada grupo por profundidad y ruta.

Es un orden determinista: dos regeneraciones seguidas dan el mismo grafo. Antes, en un repo con dependencias vendorizadas grandes, el tope se lo comían ellas y el código propio ni entraba.

El grafo se actualiza al abrir la vista Grafo, con el comando /grafo en el chat o con el botón Regenerar de la propia vista. Al abrir la vista se pinta primero el grafo que ya había y solo se reprocesa si algún archivo cambió desde la última vez; el botón Regenerar siempre reconstruye entero.

El agente navega el grafo para localizar justo el fragmento de código que necesita, en lugar de cargar archivos completos al modelo. Menos contexto enviado significa menos tokens — respuestas más baratas y más rápidas, sobre todo en proyectos grandes.