Indexation sécurisée de grandes bases de code

La recherche sémantique est l’un des principaux leviers de performance des agents. Dans notre évaluation récente, elle a amélioré la précision des réponses de 12,5 % en moyenne, produit des modifications de code plus susceptibles d’être conservées dans les bases de code et augmenté la satisfaction globale des demandes.
Pour alimenter la recherche sémantique, Cursor construit un index interrogeable de votre base de code lorsque vous ouvrez un projet. Pour les petits projets, cela se fait presque instantanément. Mais les grands dépôts contenant des dizaines de milliers de fichiers peuvent prendre des heures à être traités si l’indexation est effectuée de manière naïve, et la recherche sémantique n’est pas disponible tant qu’au moins 80 % de ce travail n’est pas terminé.
Nous avons cherché des moyens d’accélérer l’indexation en partant d’un constat simple : la plupart des équipes travaillent à partir de copies presque identiques de la même base de code. En fait, les clones d’une même base de code présentent en moyenne 92 % de similarité entre les utilisateurs d’une même organisation.
Cela signifie qu’au lieu de reconstruire chaque index à partir de zéro lorsqu’une personne rejoint l’équipe ou change de machine, nous pouvons réutiliser en toute sécurité l’index existant d’un membre de l’équipe. Cela réduit le délai avant la première requête de plusieurs heures à quelques secondes sur les plus grands dépôts.
Création du premier index
Cursor construit sa première représentation d’une base de code à l’aide d’un arbre de Merkle, ce qui lui permet de détecter exactement quels fichiers et répertoires ont changé sans tout retraiter. L’arbre de Merkle contient un hachage cryptographique de chaque fichier, ainsi que des hachages de chaque dossier basés sur les hachages de leurs enfants.

De petites modifications côté client changent uniquement les hachages du fichier modifié lui‑même et les hachages des répertoires parents jusqu’à la racine de la base de code. Cursor compare ces hachages à la version du serveur pour voir exactement où les deux arbres de Merkle divergent. Les entrées dont les hachages diffèrent sont synchronisées. Les entrées qui correspondent sont ignorées lors de la synchronisation. Toute entrée manquante sur le client est supprimée du serveur, et toute entrée manquante sur le serveur est ajoutée. Le processus de synchronisation ne modifie jamais les fichiers côté client.
L’approche par arbre de Merkle réduit considérablement la quantité de données à transférer à chaque synchronisation. Dans un espace de travail contenant cinquante mille fichiers, rien que les noms de fichiers et les hachages SHA-256 représentent environ 3,2 Mo. Sans l’arbre, vous transféreriez ces données à chaque mise à jour. Avec l’arbre, Cursor ne parcourt que les branches où les hachages diffèrent.
Lorsqu’un fichier change, Cursor le découpe en blocs syntaxiques. Ces blocs sont convertis en embeddings qui permettent la recherche sémantique. La création des embeddings est l’étape coûteuse, c’est pourquoi Cursor l’effectue de manière asynchrone en arrière‑plan.
La plupart des modifications laissent la plupart des blocs inchangés. Cursor met en cache les embeddings en fonction du contenu des blocs. Les blocs inchangés profitent du cache, et les réponses de l’agent restent rapides sans avoir à payer ce coût à nouveau au moment de l’inférence. L’index résultant est rapide à mettre à jour et léger à maintenir.
Trouver le meilleur index à réutiliser
Le pipeline d'indexation ci‑dessus téléverse chaque fichier lorsqu'une base de code est nouvelle dans Cursor. Les nouveaux utilisateurs au sein d'une organisation n'ont toutefois pas besoin de repasser par tout ce processus.
Lorsqu'un nouvel utilisateur rejoint l'équipe, le client calcule l'arbre de Merkle pour une nouvelle base de code et en dérive une valeur appelée empreinte de similarité (simhash) à partir de cet arbre. Il s'agit d'une valeur unique qui sert de résumé des empreintes de contenu des fichiers dans la base de code.
Le client téléverse le simhash vers le serveur. Le serveur l'utilise ensuite comme vecteur pour effectuer une recherche dans une base de données vectorielle constituée de l'ensemble des simhash actuels de tous les autres index Cursor de la même équipe (ou du même utilisateur) que le client. Pour chaque résultat renvoyé par la base de données vectorielle, nous vérifions s'il correspond à l'empreinte de similarité du client avec une valeur supérieure à un certain seuil. Le cas échéant, nous utilisons cet index comme index initial pour la nouvelle base de code.
Cette copie se fait en arrière‑plan. Pendant ce temps, le client peut effectuer de nouvelles recherches sémantiques sur l'index d'origine en cours de copie, ce qui lui offre un délai jusqu'à la première requête extrêmement court.
Mais cela ne fonctionne que si deux contraintes sont respectées. Les résultats doivent refléter la base de code locale de l'utilisateur, même lorsqu'elle diffère de l'index copié. Et le client ne peut jamais voir de résultats pour du code qu'il n'a pas déjà en local.

Validation de l'accès
Pour garantir qu'aucun fichier ne soit divulgué entre différentes copies de la base de code, nous réutilisons les propriétés cryptographiques de l'arbre de Merkle.
Chaque nœud de l'arbre est un hachage cryptographique du contenu qu'il englobe. Vous ne pouvez calculer ce hachage que si vous disposez du fichier. Lorsqu'un workspace démarre à partir d'un index copié, le client envoie son arbre de Merkle complet ainsi que le hachage de similarité. Cela associe un hachage à chaque chemin chiffré dans la base de code.
Le serveur stocke cet arbre comme un ensemble de preuves de contenu. Lors de la recherche, le serveur filtre les résultats en comparant ces hachages à l'arbre du client. Si le client ne peut pas prouver qu'il possède un fichier, le résultat est ignoré.
Cela permet au client d'effectuer immédiatement des requêtes et de ne voir des résultats que pour le code qu'il partage avec l'index copié. La synchronisation en arrière-plan résorbe ensuite les différences restantes. Une fois que les racines des arbres de Merkle du client et du serveur correspondent, le serveur supprime les preuves de contenu et les requêtes futures s'exécutent sur l'index entièrement synchronisé.
Intégration plus rapide
La réutilisation des index des membres de l’équipe réduit le temps de configuration pour les dépôts de toutes tailles. L’effet est d’autant plus marqué que le dépôt est volumineux :
-
Pour le dépôt médian, le délai jusqu’à la première requête passe de 7,87 secondes à 525 millisecondes
-
Au 90e percentile, il chute de 2,82 minutes à 1,87 seconde
-
Au 99e percentile, il chute de 4,03 heures à 21 secondes.
Ces changements éliminent une source majeure de travail répétitif et permettent à Cursor de comprendre même des bases de code très volumineuses en quelques secondes plutôt qu’en heures.