bots-garden/mini-mepublic Fork
⑂ v1.0.2
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 8734926Unverified · on v1.0.2 · k33g · Oct 6, 2026
lsp-tool.md · 39 lines · 6.4 KBmarkdown
Blame HistoryOpen raw

L'outil lsp : pourquoi un seul outil, et pourquoi un champ symbol — explication

De quoi s'agit-il ?

Jusqu'au 18 septembre 2026, le modèle connaissait un projet par bash (grep, ls, go build) et par les outils de fichiers. C'est ainsi que travaille un humain sans éditeur, et c'est lent et fragile pour les questions qu'un éditeur règle à l'instant : où est-ce défini, qui l'appelle, quel est ce type, que dit le compilateur. Les serveurs de langage répondent à ces questions pour tous les langages courants au travers d'un même protocole, le Language Server Protocol (LSP). Le ticket 17 demandait d'en donner un client à mm : des serveurs configurés, un outil intégré, une documentation avec des exemples de prompt système, et un avertissement plutôt qu'un échec quand un serveur manque.

Pourquoi un seul outil avec un champ action

Six requêtes étaient voulues : définition, références, hover, symboles du document, diagnostics, renommage. Elles auraient pu être six outils. Elles n'en font qu'un, lsp, avec une énumération action, pour le modèle que vise cet agent — un modèle de 12 milliards de paramètres qui lit fiablement une liste de cinq outils, moins bien une liste de onze. Un seul outil porte aussi une seule description, et c'est dans la description que vit la méthode : « references avant de changer un symbole partagé, diagnostics après avoir édité ». Six descriptions l'auraient dit six fois, ou pas du tout.

Pourquoi un champ symbol, et des positions essayées dans l'ordre

Les requêtes LSP prennent une position : ligne en base 0 et colonne en unités UTF-16. Le premier essai de bout en bout a montré le modèle envoyant column: 0 à chaque appel — même quand la question donnait la colonne — et bouclant sur la même erreur. Demander à un petit modèle de compter des caractères, c'est lui demander un nombre qu'il ne sait pas produire. L'outil prend donc l'identifiant à la place : symbol: greet. Le client cherche ses occurrences en mot entier dans le fichier et les essaie dans l'ordre jusqu'à ce que le serveur réponde quelque chose ; les occurrences sur des lignes de commentaire passent en dernier, parce qu'un nom est souvent cité dans le commentaire au-dessus de sa déclaration et que le serveur n'a rien à dire d'un commentaire. Le second essai, avec symbol, a répondu à la même question en deux appels avec les bonnes lignes. line et column exacts restent acceptés, en base 1 comme read_file numbered=true, pour le rare modèle qui les a.

La même idée gouverne les réponses : chaque emplacement revient sous la forme chemin:ligne:colonne <la ligne source>. Le modèle n'a pas besoin d'ouvrir le fichier pour savoir ce qu'est un résultat, donc il ne l'ouvre pas — un appel au lieu de trois.

Pourquoi les fichiers sont synchronisés depuis le disque avant chaque requête

Un éditeur informe son serveur de chaque frappe. mm n'a pas de frappes : le modèle édite avec edit_file, et le fichier sur disque est la vérité. Avant chaque requête, le client relit donc le fichier et envoie son contenu — didOpen la première fois, un didChange du document entier quand il a bougé depuis — et le serveur répond toujours sur ce que le modèle vient d'écrire. Rien d'autre n'a à penser à prévenir qui que ce soit.

Pourquoi rename écrit lui-même les fichiers, et demande d'abord

Un renommage est un WorkspaceEdit : des plages à remplacer dans plusieurs fichiers. Le client les applique sur disque — de la fin de chaque fichier vers le début pour que les positions antérieures restent valides, les colonnes UTF-16 converties en octets — puis resynchronise les fichiers changés pour que la question suivante porte sur le code renommé. Dans le terminal il s'exécute comme edit_file ; là où une façade a une boîte de permission (avec yolo: false : le mode ligne, la fenêtre, un éditeur en ACP) il demande, parce qu'il écrit, alors que les cinq actions de navigation ne demandent pas : une boîte à chaque lecture apprend aux gens à cliquer « autoriser » sans lire.

Pourquoi un serveur manquant est un avertissement

Une configuration voyage avec un projet ; les machines où elle tourne diffèrent. Un serveur nommé dans le YAML mais absent du PATH est signalé une fois au démarrage et retiré de la description de l'outil ; quand aucun n'est trouvé, l'outil n'est pas déclaré du tout et la bannière le dit. L'agent démarre dans tous les cas. Déclarer un serveur que l'outil ne peut pas lancer aurait donné au modèle un outil qui échoue toujours — le piège de l'échec silencieux contre lequel tout le projet est construit.

Alternatives écartées

  • Un outil par action. Écarté pour le modèle : une liste d'outils plus longue, six descriptions, la méthode dispersée.
  • Un client JSON-RPC écrit à la main. Envisagé, pour garder zéro dépendance. L'utilisateur a choisi go.lsp.dev/protocol et go.lsp.dev/jsonrpc2 : requêtes et résultats typés, les types union dont le protocole regorge déjà décodés. Le prix : deux dépendances directes et un plancher Go 1.26, exigé par ces modules.
  • Laisser le modèle compter les colonnes. Écarté par l'expérience, voir plus haut.
  • Démarrer tous les serveurs configurés au lancement. Écarté : un serveur Python sur un projet Go tournerait pour rien. Les serveurs démarrent au premier appel sur un fichier qu'ils servent et s'arrêtent à la sortie de mm.
  • Pousser les diagnostics au modèle de lui-même. Écarté : le modèle les demande quand le skill et le prompt le lui disent (après une édition). Les pousser sans demande mettrait dans l'historique du texte que le modèle n'a pas réclamé et ne sait pas situer.

Liens avec le reste

internal/lsp est le client : cycle de vie des serveurs, synchronisation des fichiers, les six opérations, la mise en forme, le renommage appliqué au disque. internal/tools déclare l'outil au-dessus et décide de ce qui est affiché ou émis en événements, comme les autres outils. internal/config porte lsp.servers et refuse deux serveurs sur une même extension. main.go vérifie le PATH, affiche les avertissements et arrête les serveurs à la sortie. La ligne de récapitulatif a gagné un compteur 🔎 N lsp call(s), tenu à part de ⚙ commands pour la même raison que 📝 file op(s) : interroger le compilateur n'est pas exécuter une commande.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# L'outil `lsp` : pourquoi un seul outil, et pourquoi un champ `symbol` — explication

## De quoi s'agit-il ?

Jusqu'au 18 septembre 2026, le modèle connaissait un projet par `bash` (grep, ls, go build) et par les outils de fichiers. C'est ainsi que travaille un humain sans éditeur, et c'est lent et fragile pour les questions qu'un éditeur règle à l'instant : où est-ce défini, qui l'appelle, quel est ce type, que dit le compilateur. Les serveurs de langage répondent à ces questions pour tous les langages courants au travers d'un même protocole, le Language Server Protocol (LSP). Le ticket 17 demandait d'en donner un client à `mm` : des serveurs configurés, un outil intégré, une documentation avec des exemples de prompt système, et un avertissement plutôt qu'un échec quand un serveur manque.

## Pourquoi un seul outil avec un champ `action`

Six requêtes étaient voulues : définition, références, hover, symboles du document, diagnostics, renommage. Elles auraient pu être six outils. Elles n'en font qu'un, `lsp`, avec une énumération `action`, pour le modèle que vise cet agent — un modèle de 12 milliards de paramètres qui lit fiablement une liste de cinq outils, moins bien une liste de onze. Un seul outil porte aussi une seule description, et c'est dans la description que vit la méthode : « references avant de changer un symbole partagé, diagnostics après avoir édité ». Six descriptions l'auraient dit six fois, ou pas du tout.

## Pourquoi un champ `symbol`, et des positions essayées dans l'ordre

Les requêtes LSP prennent une position : ligne en base 0 et colonne en unités UTF-16. Le premier essai de bout en bout a montré le modèle envoyant `column: 0` à chaque appel — même quand la question donnait la colonne — et bouclant sur la même erreur. Demander à un petit modèle de compter des caractères, c'est lui demander un nombre qu'il ne sait pas produire. L'outil prend donc l'identifiant à la place : `symbol: greet`. Le client cherche ses occurrences en mot entier dans le fichier et les essaie dans l'ordre jusqu'à ce que le serveur réponde quelque chose ; les occurrences sur des lignes de commentaire passent en dernier, parce qu'un nom est souvent cité dans le commentaire au-dessus de sa déclaration et que le serveur n'a rien à dire d'un commentaire. Le second essai, avec `symbol`, a répondu à la même question en deux appels avec les bonnes lignes. `line` et `column` exacts restent acceptés, en base 1 comme `read_file numbered=true`, pour le rare modèle qui les a.

La même idée gouverne les réponses : chaque emplacement revient sous la forme `chemin:ligne:colonne  <la ligne source>`. Le modèle n'a pas besoin d'ouvrir le fichier pour savoir ce qu'est un résultat, donc il ne l'ouvre pas — un appel au lieu de trois.

## Pourquoi les fichiers sont synchronisés depuis le disque avant chaque requête

Un éditeur informe son serveur de chaque frappe. `mm` n'a pas de frappes : le modèle édite avec `edit_file`, et le fichier sur disque est la vérité. Avant chaque requête, le client relit donc le fichier et envoie son contenu — `didOpen` la première fois, un `didChange` du document entier quand il a bougé depuis — et le serveur répond toujours sur ce que le modèle vient d'écrire. Rien d'autre n'a à penser à prévenir qui que ce soit.

## Pourquoi `rename` écrit lui-même les fichiers, et demande d'abord

Un renommage est un `WorkspaceEdit` : des plages à remplacer dans plusieurs fichiers. Le client les applique sur disque — de la fin de chaque fichier vers le début pour que les positions antérieures restent valides, les colonnes UTF-16 converties en octets — puis resynchronise les fichiers changés pour que la question suivante porte sur le code renommé. Dans le terminal il s'exécute comme `edit_file` ; là où une façade a une boîte de permission (avec `yolo: false` : le mode ligne, la fenêtre, un éditeur en ACP) il demande, parce qu'il écrit, alors que les cinq actions de navigation ne demandent pas : une boîte à chaque lecture apprend aux gens à cliquer « autoriser » sans lire.

## Pourquoi un serveur manquant est un avertissement

Une configuration voyage avec un projet ; les machines où elle tourne diffèrent. Un serveur nommé dans le YAML mais absent du `PATH` est signalé une fois au démarrage et retiré de la description de l'outil ; quand aucun n'est trouvé, l'outil n'est pas déclaré du tout et la bannière le dit. L'agent démarre dans tous les cas. Déclarer un serveur que l'outil ne peut pas lancer aurait donné au modèle un outil qui échoue toujours — le piège de l'échec silencieux contre lequel tout le projet est construit.

## Alternatives écartées

- **Un outil par action.** Écarté pour le modèle : une liste d'outils plus longue, six descriptions, la méthode dispersée.
- **Un client JSON-RPC écrit à la main.** Envisagé, pour garder zéro dépendance. L'utilisateur a choisi `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2` : requêtes et résultats typés, les types union dont le protocole regorge déjà décodés. Le prix : deux dépendances directes et un plancher Go 1.26, exigé par ces modules.
- **Laisser le modèle compter les colonnes.** Écarté par l'expérience, voir plus haut.
- **Démarrer tous les serveurs configurés au lancement.** Écarté : un serveur Python sur un projet Go tournerait pour rien. Les serveurs démarrent au premier appel sur un fichier qu'ils servent et s'arrêtent à la sortie de `mm`.
- **Pousser les diagnostics au modèle de lui-même.** Écarté : le modèle les demande quand le skill et le prompt le lui disent (après une édition). Les pousser sans demande mettrait dans l'historique du texte que le modèle n'a pas réclamé et ne sait pas situer.

## Liens avec le reste

`internal/lsp` est le client : cycle de vie des serveurs, synchronisation des fichiers, les six opérations, la mise en forme, le renommage appliqué au disque. `internal/tools` déclare l'outil au-dessus et décide de ce qui est affiché ou émis en événements, comme les autres outils. `internal/config` porte `lsp.servers` et refuse deux serveurs sur une même extension. `main.go` vérifie le `PATH`, affiche les avertissements et arrête les serveurs à la sortie. La ligne de récapitulatif a gagné un compteur `🔎 N lsp call(s)`, tenu à part de `⚙ commands` pour la même raison que `📝 file op(s)` : interroger le compilateur n'est pas exécuter une commande.