Indexación segura de bases de código grandes

La búsqueda semántica es uno de los principales factores del rendimiento del Agente. En nuestra evaluación reciente, mejoró la precisión de las respuestas en un 12,5 % en promedio, produjo cambios de código que tenían más probabilidades de conservarse en las bases de código y aumentó la satisfacción general con las solicitudes.
Para potenciar la búsqueda semántica, Cursor crea un índice de búsqueda de tu base de código cuando abres un proyecto. En proyectos pequeños, esto ocurre casi al instante. Pero los repositorios grandes, con decenas de miles de archivos, pueden tardar horas en procesarse si se indexan de forma poco optimizada, y la búsqueda semántica no está disponible hasta que al menos el 80 % de ese trabajo haya finalizado.
Buscamos formas de acelerar la indexación basándonos en la sencilla observación de que la mayoría de los equipos trabajan con copias casi idénticas de la misma base de código. De hecho, los clones de la misma base de código tienen una similitud promedio del 92 % entre usuarios dentro de una organización.
Esto significa que, en lugar de reconstruir cada índice desde cero cuando alguien se une al equipo o cambia de máquina, podemos reutilizar de forma segura el índice existente de un compañero. Esto reduce el tiempo hasta la primera consulta de horas a segundos en los repositorios más grandes.
Creación del primer índice
Cursor construye su primera vista de una base de código utilizando un árbol de Merkle, lo que le permite detectar exactamente qué archivos y directorios han cambiado sin tener que volver a procesarlo todo. El árbol de Merkle incluye un hash criptográfico de cada archivo, junto con los hashes de cada carpeta basados en los hashes de sus elementos hijo.

Los pequeños cambios realizados en el cliente solo modifican los hashes del archivo editado y los hashes de los directorios padre hasta la raíz de la base de código. Cursor compara esos hashes con la versión del servidor para ver exactamente dónde divergen los dos árboles de Merkle. Las entradas cuyos hashes difieren se sincronizan. Las entradas que coinciden se omiten. Cualquier entrada que falte en el cliente se elimina del servidor, y cualquier entrada que falte en el servidor se añade. El proceso de sincronización nunca modifica archivos en el lado del cliente.
El enfoque del árbol de Merkle reduce significativamente la cantidad de datos que deben transferirse en cada sincronización. En un espacio de trabajo con cincuenta mil archivos, solo los nombres de archivo y los hashes SHA-256 suman aproximadamente 3,2 MB. Sin el árbol, tendrías que mover esos datos en cada actualización. Con el árbol, Cursor solo recorre las ramas donde los hashes difieren.
Cuando un archivo cambia, Cursor lo divide en fragmentos sintácticos. Estos fragmentos se convierten en las embeddings que permiten la búsqueda semántica. Crear embeddings es la parte más costosa, por lo que Cursor lo hace de forma asíncrona en segundo plano.
La mayoría de las ediciones dejan la mayoría de los fragmentos sin cambios. Cursor almacena en caché las embeddings según el contenido de cada fragmento. Los fragmentos sin cambios se benefician de la caché, y las respuestas del agente se mantienen rápidas sin volver a pagar ese coste en tiempo de inferencia. El índice resultante es rápido de actualizar y ligero de mantener.
Encontrar el mejor índice para reutilizar
El pipeline de indexación anterior sube todos los archivos cuando una base de código es nueva en Cursor. Sin embargo, los usuarios nuevos dentro de una organización no necesitan pasar por todo ese proceso.
Cuando se incorpora un usuario nuevo, el cliente calcula el árbol de Merkle para una base de código nueva y deriva un valor llamado hash de similitud (simhash) a partir de ese árbol. Este es un único valor que actúa como un resumen de los hashes de contenido de archivos en la base de código.
El cliente sube el simhash al servidor. El servidor luego lo usa como un vector para buscar en una base de datos vectorial compuesta por todos los simhashes actuales de todos los demás índices de Cursor en el mismo equipo (o del mismo usuario) que el cliente. Para cada resultado devuelto por la base de datos vectorial, comprobamos si coincide con el hash de similitud del cliente por encima de un valor umbral. Si lo hace, usamos ese índice como índice inicial para la nueva base de código.
Esta copia ocurre en segundo plano. Mientras tanto, el cliente puede realizar nuevas búsquedas semánticas sobre el índice original que se está copiando, lo que da como resultado un tiempo hasta la primera consulta muy corto para el cliente.
Pero esto solo funciona si se cumplen dos condiciones. Los resultados deben reflejar la base de código local del usuario, incluso cuando difiere del índice copiado. Y el cliente nunca puede ver resultados de código que aún no tenga.

Prueba de acceso
Para garantizar que los archivos no se filtren entre copias de la base de código, reutilizamos las propiedades criptográficas del árbol de Merkle.
Cada nodo del árbol es un hash criptográfico del contenido que se encuentra por debajo de él. Solo puedes calcular ese hash si tienes el archivo. Cuando un espacio de trabajo se inicia a partir de un índice copiado, el cliente carga su árbol de Merkle completo junto con el hash de similitud. Esto asocia un hash con cada ruta cifrada en la base de código.
El servidor almacena este árbol como un conjunto de pruebas de contenido. Durante la búsqueda, el servidor filtra los resultados comprobando esos hashes con el árbol del cliente. Si el cliente no puede demostrar que tiene un archivo, el resultado se descarta.
Esto permite que el cliente realice consultas de inmediato y vea resultados solo para el código que comparte con el índice copiado. La sincronización en segundo plano resuelve las diferencias restantes. Una vez que las raíces del árbol de Merkle del cliente y del servidor coinciden, el servidor elimina las pruebas de contenido y las consultas futuras se ejecutan contra el índice totalmente sincronizado.
Incorporación más rápida
Reutilizar los índices de tu equipo reduce el tiempo de configuración de repositorios de todos los tamaños. El efecto se amplifica a medida que crece el tamaño del repositorio:
-
Para el repositorio mediano, el tiempo hasta la primera consulta baja de 7,87 segundos a 525 milisegundos
-
En el percentil 90 baja de 2,82 minutos a 1,87 segundos
-
En el percentil 99 baja de 4,03 horas a 21 segundos.
Estos cambios eliminan una fuente importante de trabajo repetitivo y permiten que Cursor comprenda incluso bases de código muy grandes en cuestión de segundos, no de horas.