All checks were successful
Beta Release / beta (push) Successful in 1m1s
- Fix token count reset on app restart: persist realTokens in conversation.json - Fix token/context window values: Studio 150K (summarize at 120K), Terminal 100K - Fix table rendering in terminal tab: correct thead/tbody display model - Fix copy button always top-right in Studio code blocks - Add markdown horizontal rule (---) support in Studio and Terminal - Fix bullet list double dot: remove CSS ::before duplicate bullet point - Add image attachments support (VLM description, file mentions @file.ext) - Add sudo detection with cache (sync.Once) - Fix message content serialization (TextContent wrapper) - Guide AI to use read_file instead of cat in studio prompt 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
1074 lines
39 KiB
Markdown
1074 lines
39 KiB
Markdown
# Rapport d'Architecture : CharmBracelet Crush
|
||
|
||
> Analyse complète de l'application [charmbracelet/crush](https://github.com/charmbracelet/crush.git) — comment elle communique avec les IA, gère les outils, optimise les tokens et les performances.
|
||
|
||
---
|
||
|
||
## Table des matières
|
||
|
||
1. [Architecture globale](#1-architecture-globale)
|
||
2. [Ce qui est envoyé à l'IA](#2-ce-qui-est-envoyé-à-lia)
|
||
3. [Système de prompts](#3-système-de-prompts)
|
||
4. [Système de résumé / compaction](#4-système-de-résumé--compaction)
|
||
5. [Fonctionnement des outils (Tools)](#5-fonctionnement-des-outils-tools)
|
||
6. [Optimisation de la consommation de tokens](#6-optimisation-de-la-consommation-de-tokens)
|
||
7. [Optimisations de performance](#7-optimisations-de-performance)
|
||
8. [Système de permissions](#8-système-de-permissions)
|
||
9. [Providers et modèles](#9-providers-et-modèles)
|
||
10. [Système de skills](#10-système-de-skills)
|
||
11. [Intégration LSP](#11-intégration-lsp)
|
||
12. [Intégration MCP](#12-intégration-mcp)
|
||
13. [Structure de la base de données](#13-structure-de-la-base-de-données)
|
||
14. [Fichiers clés à explorer](#14-fichiers-clés-à-explorer)
|
||
15. [Leçons pour notre application](#15-leçons-pour-notre-application)
|
||
|
||
---
|
||
|
||
## 1. Architecture globale
|
||
|
||
```
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
|
||
│ TUI / CLI │────▶│ Backend │────▶│ Coordinator │
|
||
│ (UI chat) │ │ (workspace) │ │ (orchestrateur) │
|
||
└──────────────┘ └──────────────┘ └────────┬─────────┘
|
||
│
|
||
┌────────▼─────────┐
|
||
│ SessionAgent │
|
||
│ (moteur IA) │
|
||
└────────┬─────────┘
|
||
│
|
||
┌────────────────────┼────────────────────┐
|
||
│ │ │
|
||
┌────────▼──────┐ ┌────────▼──────┐ ┌────────▼──────┐
|
||
│ fantasy │ │ SQLite DB │ │ Tools │
|
||
│ (LLM client) │ │ (messages) │ │ (22+ outils) │
|
||
└───────────────┘ └───────────────┘ └───────────────┘
|
||
```
|
||
|
||
**Flow de données complet :**
|
||
|
||
```
|
||
User Prompt
|
||
→ Backend.SendMessage()
|
||
→ Coordinator.Run()
|
||
→ UpdateModels() (recharge models + tools)
|
||
→ mergeCallOptions() (merge JSON des options provider)
|
||
→ SessionAgent.Run()
|
||
→ preparePrompt() (construit l'historique, filtre les orphelins)
|
||
→ fantasy.Agent.Stream() avec :
|
||
- System prompt = coder.md.tpl + instructions MCP
|
||
- System prompt prefix = prefix provider
|
||
- Messages = historique filtré (tronqué au résumé si existe)
|
||
- Files = pièces jointes binaires
|
||
- Tools = ensemble filtré d'outils
|
||
- Provider options = options merged Anthropic/OpenAI/Google/etc.
|
||
- Cache control = ephemeral sur dernier system + 2 derniers messages
|
||
→ Auto-summarize si fenêtre de contexte quasi pleine
|
||
→ Queue + recurse pour messages en attente
|
||
```
|
||
|
||
### Fichiers clés de l'architecture
|
||
|
||
| Fichier | Rôle |
|
||
|---------|------|
|
||
| `internal/agent/agent.go` | Moteur principal — construction des messages, streaming, résumé |
|
||
| `internal/agent/coordinator.go` | Orchestration — création agents, assembly tools/models |
|
||
| `internal/agent/prompts.go` | Factory de prompts système |
|
||
| `internal/agent/templates/coder.md.tpl` | Template du prompt système principal (405 lignes) |
|
||
| `internal/backend/backend.go` | Gestion des workspaces |
|
||
| `internal/backend/agent.go` | API transport-agnostic vers le coordinator |
|
||
|
||
---
|
||
|
||
## 2. Ce qui est envoyé à l'IA
|
||
|
||
### 2.1 System Prompt
|
||
|
||
Le system prompt est construit à partir du template `coder.md.tpl` et contient :
|
||
|
||
```
|
||
System Message:
|
||
├── <critical_rules> — 13 règles absolues (read before edit, autonomous, etc.)
|
||
├── <communication_style> — Style de réponse (concis, <4 lignes)
|
||
├── <workflow> — Séquence de travail (search → read → edit → test)
|
||
├── <decision_making> — Décisions autonomes vs. bloquantes
|
||
├── <editing_files> — Règles d'édition (exact match, whitespace)
|
||
├── <whitespace_and_exact_matching> — Checklist précision
|
||
├── <task_completion> — Complétion exhaustive des tâches
|
||
├── <error_handling> — Stratégies de récupération d'erreurs
|
||
├── <memory_instructions> — Gestion des fichiers mémoire
|
||
├── <code_conventions> — Conventions de code
|
||
├── <testing> — Règles de test après changements
|
||
├── <tool_usage> — Utilisation des outils
|
||
├── <proactiveness> — Proactivité vs. intention utilisateur
|
||
├── <final_answers> — Format des réponses finales
|
||
├── <env> — Variables dynamiques :
|
||
│ ├── Working Directory: {{.WorkingDir}}
|
||
│ ├── Is Git Repo: {{.IsGitRepo}}
|
||
│ ├── Platform: {{.Platform}}
|
||
│ ├── Date: {{.Date}}
|
||
│ └── Git Status (branch, git status --short | head -20, git log --oneline -n 3)
|
||
├── <lsp> (optionnel) — État des serveurs LSP
|
||
├── <available_skills> — Skills disponibles en XML
|
||
├── <skills_usage> — Instructions d'utilisation des skills
|
||
├── <memory> — Fichiers de contexte (crush.md, AGENTS.md, etc.)
|
||
└── <mcp-instructions> — Instructions MCP injectées dynamiquement
|
||
```
|
||
|
||
### 2.2 Historique des messages
|
||
|
||
Chaque appel à l'IA inclut l'historique complet de la session, formaté comme :
|
||
|
||
```
|
||
Messages (array de fantasy.Message):
|
||
├── [0] User: "<system_reminder>This is a reminder that your todo list is currently empty...</system_reminder>"
|
||
├── [1] User: "Premier prompt de l'utilisateur"
|
||
├── [2] Assistant: "Réponse IA + tool_calls"
|
||
├── [3] Tool: "Résultat du tool call"
|
||
├── [4] Assistant: "Réponse suivante"
|
||
├── ...
|
||
└── [N] User: "Nouveau prompt"
|
||
```
|
||
|
||
**Types de contenu dans les messages :**
|
||
- `ReasoningContent` — Chaîne de pensée (pour modèles thinking)
|
||
- `TextContent` — Texte brut
|
||
- `ImageURLContent` — Images (URL ou base64)
|
||
- `BinaryContent` — Fichiers binaires
|
||
- `ToolCall` — Appel d'outil (ID, nom, input)
|
||
- `ToolResult` — Résultat d'outil (text, error, ou media)
|
||
- `Finish` — Métadonnées de fin (end_turn, max_tokens, tool_use, canceled, error)
|
||
|
||
### 2.3 Fichiers joints
|
||
|
||
Les pièces jointes sont traitées différemment selon leur type :
|
||
- **Fichiers texte** : contenus inline dans le prompt utilisateur via `PromptWithTextAttachments()`
|
||
- **Fichiers binaires** : envoyés comme `FilePart` séparés
|
||
- **Images** : envoyées comme `ImageURLContent` (base64) si le modèle les supporte
|
||
|
||
### 2.4 Options provider fusionnées
|
||
|
||
Les options sont fusionnées en 3 couches (le plus profond gagne) :
|
||
|
||
```
|
||
1. catwalk.Model.Options.ProviderOptions (défauts du catalogue)
|
||
2. ProviderConfig.ProviderOptions (config du provider)
|
||
3. SelectedModel.ProviderOptions (choix utilisateur)
|
||
→ JSON merge avec sémantique "deepest wins"
|
||
```
|
||
|
||
Options spécifiques par provider :
|
||
- **Anthropic** : thinking, reasoning_effort, beta headers (`interleaved-thinking-2025-05-14`)
|
||
- **OpenAI** : responses API, reasoning
|
||
- **Google** : thinking_config
|
||
- **OpenRouter** : reasoning, suffixe `:exacto`
|
||
|
||
---
|
||
|
||
## 3. Système de prompts
|
||
|
||
### 3.1 Templates embarqués
|
||
|
||
| Template | But | Taille |
|
||
|----------|-----|--------|
|
||
| `coder.md.tpl` | Prompt système principal de l'agent coder | ~405 lignes |
|
||
| `task.md.tpl` | Prompt système des sous-agents | ~15 lignes |
|
||
| `initialize.md.tpl` | Prompt d'initialisation du codebase | Analyse + génération AGENTS.md |
|
||
| `summary.md` | Prompt de résumé/compaction | Sections structurées obligatoires |
|
||
| `title.md` | Génération de titre de session | ≤50 chars |
|
||
| `agent_tool.md` | Instructions du tool `agent` | Sous-agent read-only |
|
||
| `agentic_fetch.md` | Instructions du tool `agentic_fetch` | Sous-agent web |
|
||
| `agentic_fetch_prompt.md.tpl` | Prompt du sous-agent de fetch | Template dynamique |
|
||
|
||
### 3.2 Prompt de résumé (`summary.md`)
|
||
|
||
Ce prompt est crucial — il définit comment la conversation est compactée :
|
||
|
||
**Sections obligatoires du résumé :**
|
||
1. **Current State** — État actuel de la tâche
|
||
2. **Files & Changes** — Tous les fichiers modifiés et les changements
|
||
3. **Technical Context** — Stack technique, dépendances, patterns
|
||
4. **Strategy & Approach** — Stratégie adoptée
|
||
5. **Exact Next Steps** — Prochaines étapes exactes
|
||
|
||
**Philosophie :** *"No limit. Err on the side of too much detail rather than too little."*
|
||
|
||
### 3.3 Prompt de sous-agent (`task.md.tpl`)
|
||
|
||
Minimal et ciblé :
|
||
```
|
||
Rules:
|
||
1. Be concise, direct
|
||
2. Share file names and paths
|
||
3. Use absolute paths only
|
||
|
||
<env>
|
||
Working Directory: {{.WorkingDir}}
|
||
Is Git Repo: {{.IsGitRepo}}
|
||
Platform: {{.Platform}}
|
||
Date: {{.Date}}
|
||
</env>
|
||
```
|
||
|
||
### 3.4 Variables dynamiques injectées
|
||
|
||
Le système injecte des données en temps réel via le template :
|
||
|
||
```go
|
||
type PromptData struct {
|
||
WorkingDir string
|
||
IsGitRepo bool
|
||
Platform string
|
||
Date string
|
||
GitStatus string // branch + git status --short | head -20 + git log --oneline -n 3
|
||
Config Config
|
||
AvailSkillXML string // XML des skills disponibles
|
||
ContextFiles string // Contenu des fichiers de contexte
|
||
}
|
||
```
|
||
|
||
### 3.5 Fichiers de contexte (Memory)
|
||
|
||
Chemins par défaut explorés et injectés dans `<memory>` :
|
||
```
|
||
.github/copilot-instructions.md
|
||
.cursorrules
|
||
.cursor/rules/
|
||
CLAUDE.md
|
||
CLAUDE.local.md
|
||
GEMINI.md
|
||
crush.md
|
||
CRUSH.md
|
||
AGENTS.md
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Système de résumé / compaction
|
||
|
||
### 4.1 Déclenchement automatique
|
||
|
||
Deux conditions de déclenchement vérifiées après **chaque étape** de l'agent :
|
||
|
||
```
|
||
remaining = contextWindow - (completionTokens + promptTokens)
|
||
|
||
if contextWindow > 200,000:
|
||
threshold = 20,000 tokens (buffer fixe)
|
||
else:
|
||
threshold = 20% × contextWindow (ratio)
|
||
|
||
if remaining ≤ threshold AND !disableAutoSummarize:
|
||
→ DÉCLENCHE LE RÉSUMÉ
|
||
|
||
if contextWindow == 0 (modèle inconnu/local):
|
||
→ PAS de résumé auto (évite la troncature aveugle)
|
||
```
|
||
|
||
**Constantes clés :**
|
||
- `largeContextWindowThreshold = 200,000`
|
||
- `largeContextWindowBuffer = 20,000`
|
||
- `smallContextWindowRatio = 0.2`
|
||
|
||
### 4.2 Processus de résumé
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ 1. Récupérer tous les messages de session │
|
||
│ 2. Convertir en fantasy.Message[] │
|
||
│ 3. Créer message assistant │
|
||
│ avec IsSummaryMessage: true │
|
||
│ 4. Streamer le résumé via summary.md prompt │
|
||
│ + todo list courante │
|
||
│ 5. Update session: │
|
||
│ - SummaryMessageID = summary.ID │
|
||
│ - CompletionTokens = résumé output │
|
||
│ - PromptTokens = 0 │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 4.3 Comment la compaction fonctionne au chargement
|
||
|
||
```go
|
||
func getSessionMessages(session):
|
||
msgs = loadAllMessages(sessionID)
|
||
|
||
if session.SummaryMessageID != "":
|
||
summaryIndex = findIndex(msgs, summaryMessageID)
|
||
msgs = msgs[summaryIndex:] // TRONQUE tout avant le résumé
|
||
msgs[0].Role = "user" // Change rôle assistant → user
|
||
|
||
return msgs
|
||
```
|
||
|
||
**Résultat :** Seuls le résumé + les messages après le résumé sont envoyés à l'IA. Tout l'historique précédent est éliminé du contexte.
|
||
|
||
### 4.4 Détection de boucle (Loop Detection)
|
||
|
||
Mécanisme secondaire de déclenchement du résumé :
|
||
|
||
```
|
||
- Fenêtre glissante de 10 dernières étapes
|
||
- Signature = SHA-256(ToolName + \x00 + Input + \x00 + Output + \x00)
|
||
- Si une signature apparaît > 5 fois dans la fenêtre → BOUCLE DÉTECTÉE
|
||
- Déclenche le résumé + arrêt de l'agent
|
||
```
|
||
|
||
### 4.5 Reprise après interruption
|
||
|
||
Si le résumé est déclenché pendant des appels d'outils en cours :
|
||
```
|
||
Re-queue le prompt original avec :
|
||
"The previous session was interrupted because it got too long,
|
||
the initial user request was: <original_prompt>"
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Fonctionnement des outils (Tools)
|
||
|
||
### 5.1 Liste complète des outils (22+)
|
||
|
||
| Outil | Type | Séquentiel/Parallèle | But |
|
||
|-------|------|---------------------|-----|
|
||
| `bash` | Core | Séquentiel | Exécution de commandes shell |
|
||
| `edit` | Core | Séquentiel | Remplacement find-and-replace dans un fichier |
|
||
| `multiedit` | Core | Séquentiel | Multiples remplacements séquentiels |
|
||
| `write` | Core | Séquentiel | Création/écrasement de fichier |
|
||
| `view` | Core | Séquentiel | Lecture de fichier avec line numbers |
|
||
| `ls` | Core | Séquentiel | Arborescence de répertoire |
|
||
| `glob` | Core | Séquentiel | Recherche de fichiers par pattern |
|
||
| `grep` | Core | Séquentiel | Recherche dans le contenu de fichiers |
|
||
| `fetch` | Core | Parallèle | Fetch URL brut (text/markdown/html) |
|
||
| `agentic_fetch` | Core | Parallèle | Fetch IA avec extraction/résumé |
|
||
| `sourcegraph` | Core | Parallèle | Recherche de code sur GitHub |
|
||
| `download` | Core | Parallèle | Téléchargement de fichier |
|
||
| `agent` | Agent | Parallèle | Sous-agent de recherche/analyse |
|
||
| `todos` | Core | Séquentiel | Gestion de la todo list |
|
||
| `crush_info` | Core | Séquentiel | État runtime de Crush |
|
||
| `crush_logs` | Core | Séquentiel | Logs internes de Crush |
|
||
| `job_output` | Core | Séquentiel | Output de processus background |
|
||
| `job_kill` | Core | Séquentiel | Terminaison de processus background |
|
||
| `lsp_diagnostics` | LSP | Séquentiel | Diagnostics LSP |
|
||
| `lsp_references` | LSP | Séquentiel | Références LSP |
|
||
| `lsp_restart` | LSP | Séquentiel | Redémarrage LSP |
|
||
| `list_mcp_resources` | MCP | Parallèle | Liste ressources MCP |
|
||
| `read_mcp_resource` | MCP | Parallèle | Lecture ressource MCP |
|
||
| `mcp_{server}_{tool}` | MCP | Parallèle | Outils dynamiques MCP |
|
||
|
||
### 5.2 Architecture d'un outil
|
||
|
||
Chaque outil suit le pattern :
|
||
|
||
```go
|
||
fantasy.NewAgentTool(name, description, params, func(ctx context.Context, params Params) (fantasy.ToolResponse, error) {
|
||
// 1. Validation des paramètres
|
||
// 2. Extraction du contexte (sessionID, messageID, workingDir)
|
||
// 3. Vérification de permission (si mutation)
|
||
// 4. Exécution de la logique
|
||
// 5. Retour de la réponse avec métadonnées
|
||
})
|
||
```
|
||
|
||
**Deux constructeurs :**
|
||
- `fantasy.NewAgentTool` — Outil séquentiel (bloquant)
|
||
- `fantasy.NewParallelAgentTool` — Outil parallèle (peut tourner en //)
|
||
|
||
### 5.3 Types de réponses
|
||
|
||
```go
|
||
fantasy.NewTextResponse(content) // Succès texte
|
||
fantasy.NewTextErrorResponse(message) // Erreur (IsError=true)
|
||
fantasy.NewImageResponse(data, mimeType) // Image
|
||
fantasy.NewMediaResponse(data, mimeType) // Autre média
|
||
fantasy.WithResponseMetadata(resp, meta) // Attache métadonnées typées
|
||
```
|
||
|
||
### 5.4 Détails des outils clés
|
||
|
||
#### `bash` — Exécution de commandes
|
||
|
||
```
|
||
Paramètres: {Description, Command, WorkingDir, RunInBackground, AutoBackgroundAfter}
|
||
|
||
Sécurité:
|
||
├── safeCommands: ls, cat, head, tail, pwd, echo, which, env, git status/diff/log
|
||
│ → Pas de permission requise (read-only)
|
||
├── Banned: curl, wget, sudo, apt, npm, etc.
|
||
│ → Bloqué au niveau shell
|
||
├── Background: si RunInBackground=true ou > AutoBackgroundAfter (60s)
|
||
│ → Retourne ShellID pour suivi
|
||
└── Output: tronqué à 30,000 chars (début + fin + compte lignes tronquées)
|
||
```
|
||
|
||
#### `view` — Lecture de fichiers
|
||
|
||
```
|
||
Paramètres: {FilePath, Offset, Limit}
|
||
|
||
Comportement:
|
||
├── crush: prefix → lecture depuis FS embarqué (skills builtins)
|
||
├── Chemins relatifs → résolus via SmartJoin(workingDir, ...)
|
||
├── Permission requise si hors workingDir
|
||
├── Max fichier: 100KB, défaut 2000 lignes
|
||
├── Images JPG/PNG/GIF/WebP → NewImageResponse
|
||
├── Ajoute numéros de ligne (padding 6 chars)
|
||
├── LSP: ouvre fichier, attend 300ms pour diagnostics
|
||
├── Enregistre lecture via filetracker.RecordRead()
|
||
└── Suggestions si fichier non trouvé
|
||
```
|
||
|
||
#### `edit` — Édition de fichiers
|
||
|
||
```
|
||
Paramètres: {FilePath, OldString, NewString, ReplaceAll}
|
||
|
||
3 modes:
|
||
├── OldString="" → Créer nouveau fichier
|
||
├── NewString="" → Supprimer contenu
|
||
└── Les deux → Remplacer contenu
|
||
|
||
Sécurité:
|
||
├── Doit avoir lu le fichier avant (filetracker check)
|
||
├── Fichier non modifié depuis la lecture (ModTime check)
|
||
├── OldString unique (sauf ReplaceAll=true)
|
||
├── Permission "write" avec diff affiché
|
||
├── Gestion CRLF automatique
|
||
└── LSP notifié après écriture
|
||
```
|
||
|
||
#### `grep` — Recherche de contenu
|
||
|
||
```
|
||
Paramètres: {Pattern, Path, Include, LiteralText}
|
||
|
||
Optimisations:
|
||
├── ripgrep (rg --json) en priorité, fallback Go regex
|
||
├── Respecte .gitignore et .crushignore
|
||
├── Cache de regex compilés (csync.Map thread-safe)
|
||
├── Résultats triés par date de modification (plus récent d'abord)
|
||
├── Max 100 résultats
|
||
├── Lignes tronquées à 500 chars
|
||
└── Timeout configurable
|
||
```
|
||
|
||
### 5.5 Outils spéciaux
|
||
|
||
#### `agent` — Sous-agent
|
||
|
||
```
|
||
Flow:
|
||
1. Valide params.Prompt non vide
|
||
2. Crée une session enfant (CreateTaskSession)
|
||
3. Lance un SessionAgent séparé (NonInteractive: true)
|
||
4. Outils du sous-agent: glob, grep, ls, sourcegraph, view (READ-ONLY)
|
||
5. Propage le coût de la session enfant → session parent
|
||
```
|
||
|
||
#### `agentic_fetch` — Fetch intelligent
|
||
|
||
```
|
||
Flow:
|
||
1. Mode URL: fetch + convert (HTML→MD)
|
||
→ Si contenu > 50KB: sauve en temp file, dit au sous-agent d'utiliser view/grep
|
||
2. Mode Search: construit prompt pour web_search + web_fetch
|
||
3. Sous-agent avec petit modèle + outils restreints
|
||
4. Auto-approve des permissions pour le sous-agent
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Optimisation de la consommation de tokens
|
||
|
||
### 6.1 Résumé automatique (Auto-Summarization)
|
||
|
||
**La plus grande optimisation.** Voir [Section 4](#4-système-de-résumé--compaction).
|
||
|
||
Quand la fenêtre de contexte approche sa limite, toute la conversation est remplacée par un résumé détaillé. Ce résumé devient le nouveau point de départ.
|
||
|
||
### 6.2 Anthropic Prompt Caching
|
||
|
||
Marqueurs `ephemeral` ajoutés automatiquement :
|
||
|
||
```go
|
||
// Cache control placement:
|
||
├── Dernier message système → ephemeral
|
||
├── Avant-dernier message → ephemeral
|
||
└── Antépénultième message → ephemeral
|
||
```
|
||
|
||
Cela permet à Anthropic de mettre en cache ces messages et de réduire les tokens d'input sur les tours suivants.
|
||
|
||
### 6.3 Filtrage des orphelins de tool calls
|
||
|
||
```go
|
||
// Avant d'envoyer à l'IA:
|
||
filterOrphanedToolResults() // Supprime tool results sans tool call correspondant
|
||
syntheticToolResultsForOrphanedCalls() // Injecte erreurs synthétiques pour tool calls orphelins
|
||
```
|
||
|
||
Cela nettoie l'historique des messages inutiles qui consomment des tokens.
|
||
|
||
### 6.4 Deux modèles (Large/Small)
|
||
|
||
```
|
||
├── Large Model: tâches principales (coder, résumé)
|
||
└── Small Model: titre de session, agentic_fetch
|
||
→ Économise les tokens de haute qualité pour les tâches simples
|
||
```
|
||
|
||
### 6.5 Limitations de taille des outils
|
||
|
||
| Outil | Limite | Impact token |
|
||
|-------|--------|-------------|
|
||
| bash output | 30,000 chars | Tronque les longues sorties |
|
||
| view | 100KB max, 2000 lignes | Limite les gros fichiers |
|
||
| grep | 100 résultats, 500 chars/ligne | Limite les recherches larges |
|
||
| glob | 100 fichiers | Limite les résultats |
|
||
| ls | 1000 fichiers | Limite les répertoires massifs |
|
||
| fetch | 100KB | Limite les pages web |
|
||
| sourcegraph | 20 résultats max | Limite les recherches code |
|
||
| description tools | FirstLineDescription() | N'envoie que la 1ère ligne de description |
|
||
|
||
### 6.6 Short Tool Descriptions
|
||
|
||
```go
|
||
// Par défaut: seul le premier ligne non-vide de la description est envoyé
|
||
FirstLineDescription(fullMarkdownDescription)
|
||
|
||
// Désactivable via: CRUSH_SHORT_TOOL_DESCRIPTIONS=0
|
||
```
|
||
|
||
Chaque outil a une description markdown complète, mais seule la première ligne est envoyée à l'IA — économie significative sur 22+ outils.
|
||
|
||
### 6.7 Workaround média par provider
|
||
|
||
Pour les providers non-Anthropic/Bedrock, les images dans les tool results sont converties en messages utilisateur séparés avec pièces jointes — évitant les erreurs API et les tokens gaspillés.
|
||
|
||
### 6.8 Injection system_reminder minimale
|
||
|
||
Seul un rappel minimal est injecté comme premier message utilisateur :
|
||
```
|
||
"<system_reminder>This is a reminder that your todo list is currently empty.
|
||
DO NOT mention this to the user explicitly...</system_reminder>"
|
||
```
|
||
|
||
Ce message est court et sert de garde-fou sans consommer beaucoup de tokens.
|
||
|
||
### 6.9 Estimation de tokens
|
||
|
||
```go
|
||
ApproxTokenCount(text) = len(text) / 4 // Heuristique 4 chars = 1 token
|
||
```
|
||
|
||
Utilisé pour le logging et les métriques, pas pour le contrôle strict.
|
||
|
||
---
|
||
|
||
## 7. Optimisations de performance
|
||
|
||
### 7.1 Concurrence
|
||
|
||
```
|
||
├── sync.WaitGroup pour chargement providers (Catwalk + Hyper en //)
|
||
├── sync.OnceValue pour cache Hyper (computed once)
|
||
├── fastwalk pour découverte des skills (concurent, suit symlinks)
|
||
├── errgroup.Group pour readiness des agents (system prompt + tools en //)
|
||
├── csync.Map pour maps concurrent-safe (providers, regex cache)
|
||
├── sync.RWMutex pour skill tracker
|
||
└── Shell mutex sérialise les commandes par instance shell
|
||
```
|
||
|
||
### 7.2 File d'attente de messages (Message Queue)
|
||
|
||
```
|
||
Si agent occupé → message en queue
|
||
↓
|
||
Dans PrepareStep → injecte messages en queue comme user messages supplémentaires
|
||
↓
|
||
Pas de perte de messages, traitement séquentiel garanti
|
||
```
|
||
|
||
### 7.3 Cache providers
|
||
|
||
```
|
||
├── Cache JSON: $XDG_DATA_HOME/crush/providers.json
|
||
├── ETag support pour revalidation HTTP
|
||
├── Fallback: frais → cache → embarqué
|
||
└── Chargement concurrent Catwalk + Hyper
|
||
```
|
||
|
||
### 7.4 CGO et GC
|
||
|
||
```
|
||
├── CGO_ENABLED=0 (pas de CGO overhead)
|
||
└── GOEXPERIMENT=greenteagc (GC optimisé)
|
||
```
|
||
|
||
### 7.5 Base de données SQLite
|
||
|
||
```
|
||
├── SQLC pour code SQL type-safe généré
|
||
├── Transactions avec retry (3 tentatives) pour conflits UNIQUE
|
||
├── Atomic SQL increment pour token usage (évite race conditions)
|
||
└── Index sur (session_id, created_at) pour requêtes messages rapides
|
||
```
|
||
|
||
### 7.6 Pub/Sub
|
||
|
||
```
|
||
├── Système d'événements découplé
|
||
├── Canal d'événements par workspace
|
||
└── Pas de polling — push-based
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Système de permissions
|
||
|
||
### 8.1 Actions
|
||
|
||
| Action | Quand | Outils concernés |
|
||
|--------|-------|-----------------|
|
||
| `"read"` | Lecture hors workingDir | view, ls |
|
||
| `"write"` | Modification de fichiers | edit, multiedit, write |
|
||
| `"execute"` | Commande shell non-safe | bash |
|
||
| `"fetch"` | Requête réseau | fetch, agentic_fetch |
|
||
| `"download"` | Téléchargement | download |
|
||
| `"list"` | Liste ressources MCP | list_mcp_resources |
|
||
| `"read"` | Lecture ressource MCP | read_mcp_resource |
|
||
|
||
### 8.2 Auto-approbations
|
||
|
||
- Commandes bash `safeCommands` (ls, cat, pwd, git status, etc.) → **pas de permission**
|
||
- Docker MCP tools whitelistés (`mcp_docker_mcp-find`, etc.) → **auto-approuvé**
|
||
- Sous-agents (agent, agentic_fetch) → **auto-approuvé**
|
||
|
||
### 8.3 Sécurité shell
|
||
|
||
```
|
||
Banned commands: alias, aria2c, axel, chrome, curl, curlie, firefox,
|
||
http-prompt, httpie, links, lynx, nc, safari, scp, ssh, telnet, w3m,
|
||
wget, xh, doas, su, sudo, apk, apt, apt-cache, apt-get, dnf, dpkg,
|
||
emerge, home-manager, makepkg, opkg, pacman, paru, pkg, pkg_add,
|
||
pkg_delete, portage, rpm, yay, yum, zypper, at, batch, chkconfig,
|
||
crontab, fdisk, mkfs, mount, parted, service, systemctl, umount,
|
||
firewall-cmd, ifconfig, ip, iptables, netstat, pfctl, route, ufw
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Providers et modèles
|
||
|
||
### 9.1 Types de providers supportés
|
||
|
||
```
|
||
├── OpenAI
|
||
├── Anthropic
|
||
├── OpenRouter (suffixe :exacto pour modèles supportés)
|
||
├── Vercel
|
||
├── Azure
|
||
├── AWS Bedrock
|
||
├── Google (Gemini)
|
||
├── Google Vertex AI
|
||
├── OpenAI-compatible (générique)
|
||
└── Hyper (Charm's meta-provider)
|
||
```
|
||
|
||
### 9.2 Hyper Provider
|
||
|
||
```
|
||
├── Endpoint: https://hyper.charm.land/api/v1/fantasy
|
||
├── Activation: HYPER, HYPERCRUSH, HYPER_ENABLE, HYPER_ENABLED env vars
|
||
├── Modèles: GLM-5, GLM-5.1, gpt-oss-120b, Kimi K2.5, Kimi K2.6
|
||
├── Routage: Anthropic/OpenAI/Google/OpenAI-compat selon model ID
|
||
└── Header: x-crush-id pour identification
|
||
```
|
||
|
||
### 9.3 Construction du provider
|
||
|
||
```go
|
||
buildProvider(config) → fantasy.Provider:
|
||
1. Parse le type de provider
|
||
2. Configure base URL, API key, headers
|
||
3. Ajoute beta headers si thinking model (Anthropic)
|
||
4. Pour Hyper: route vers le bon provider selon model ID
|
||
5. Pour OpenRouter: ajoute :exacto si supporté
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Système de skills
|
||
|
||
### 10.1 Standard Agent Skills
|
||
|
||
Crush implémente le standard ouvert [agentskills.io](https://agentskills.io).
|
||
|
||
### 10.2 Découverte
|
||
|
||
```
|
||
Chemins explorés:
|
||
├── $CRUSH_SKILLS_DIR
|
||
├── ~/.config/agents/skills/
|
||
├── ~/.config/crush/skills/
|
||
├── .agents/skills/
|
||
├── .crush/skills/
|
||
├── .claude/skills/
|
||
├── .cursor/skills/
|
||
└── Custom via options.skills_paths
|
||
|
||
Recherche:
|
||
├── fastwalk (concurent, suit symlinks)
|
||
├── Cherche fichiers SKILL.md
|
||
├── Parse YAML frontmatter (entre ---)
|
||
├── Validation: nom alphanum-hyphens ≤64 chars, description ≤1024 chars
|
||
└── Déduplication: dernier occurence gagne (user > builtin)
|
||
```
|
||
|
||
### 10.3 Injection dans le prompt
|
||
|
||
```xml
|
||
<available_skills>
|
||
<skill>
|
||
<name>skill-name</name>
|
||
<description>Description courte</description>
|
||
<location>/path/to/SKILL.md</location>
|
||
<type>builtin</type>
|
||
</skill>
|
||
</available_skills>
|
||
|
||
<skills_usage>
|
||
When a user task matches a skill's description, read the skill's SKILL.md file...
|
||
</skills_usage>
|
||
```
|
||
|
||
**Important :** Seules les métadonnées (nom, description, chemin) sont injectées dans le system prompt. Les **instructions complètes** ne sont lues que quand l'agent décide d'activer le skill — économie de tokens.
|
||
|
||
### 10.4 Skill Tracker
|
||
|
||
```go
|
||
type Tracker struct {
|
||
active map[string]bool // Skills actifs (post-dedup, post-filter)
|
||
loaded map[string]bool // Skills dont les instructions ont été lues
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
MarkLoaded(name) // Marque un skill comme lu (uniquement si dans active set)
|
||
IsLoaded(name) // Vérifie si déjà chargé
|
||
LoadedNames() // Liste des skills chargés
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Intégration LSP
|
||
|
||
### 11.1 Configuration
|
||
|
||
```yaml
|
||
lsp:
|
||
- command: "gopls"
|
||
args: ["serve"]
|
||
env: {}
|
||
file_types: [".go"]
|
||
root_markers: ["go.mod"]
|
||
init_options: {}
|
||
```
|
||
|
||
### 11.2 Outils LSP
|
||
|
||
- `lsp_diagnostics` — Diagnostics par fichier ou projet entier (max 10 fichiers, 5s wait)
|
||
- `lsp_references` — Recherche de références symboliques (grep + LSP FindReferences)
|
||
- `lsp_restart` — Redémarrage d'un ou tous les clients LSP
|
||
|
||
### 11.3 Intégration dans les outils
|
||
|
||
```
|
||
view → openInLSPs + wait 300ms pour diagnostics
|
||
edit → notifyLSPs + wait 5s pour diagnostics
|
||
write → notifyLSPs + wait 5s pour diagnostics
|
||
```
|
||
|
||
Les diagnostics sont appendés dans les réponses des outils via des tags XML :
|
||
```xml
|
||
<file_diagnostics>...</file_diagnostics>
|
||
<project_diagnostics>...</project_diagnostics>
|
||
<diagnostic_summary>errors: N, warnings: M</diagnostic_summary>
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Intégration MCP
|
||
|
||
### 12.1 Configuration
|
||
|
||
```yaml
|
||
mcp:
|
||
- name: "my-server"
|
||
command: "npx" # Mode stdio
|
||
args: ["-y", "my-mcp"]
|
||
env: {}
|
||
url: "" # OU mode HTTP/SSE
|
||
timeout: 30s
|
||
disabled_tools: []
|
||
```
|
||
|
||
### 12.2 Outils MCP dynamiques
|
||
|
||
- Outils nommés `mcp_{server}_{tool}`
|
||
- Schéma extrait de `InputSchema` MCP
|
||
- Permission requise (sauf whitelist Docker)
|
||
- Support image/média dans les résultats
|
||
|
||
### 12.3 Instructions MCP injectées
|
||
|
||
Les instructions des serveurs MCP connectés sont injectées dans le system prompt via `<mcp-instructions>`.
|
||
|
||
---
|
||
|
||
## 13. Structure de la base de données
|
||
|
||
### 13.1 Tables principales
|
||
|
||
```sql
|
||
-- Sessions
|
||
sessions (
|
||
id, title, prompt_tokens, completion_tokens,
|
||
summary_message_id, -- ID du message résumé (NULL si pas de résumé)
|
||
cost, todos, -- Coût et todo list
|
||
created_at, updated_at
|
||
)
|
||
|
||
-- Messages
|
||
messages (
|
||
id, session_id, role, -- user/assistant/system/tool
|
||
parts, -- JSON array de {type, data}
|
||
model, provider,
|
||
is_summary_message, -- Flag de compaction
|
||
created_at, finished_at
|
||
)
|
||
|
||
-- Fichiers (version history)
|
||
files (
|
||
id, session_id, path,
|
||
content, version, -- Auto-incrémenté par path
|
||
is_new,
|
||
created_at
|
||
)
|
||
|
||
-- Fichiers lus (read tracking)
|
||
read_files (
|
||
path, session_id,
|
||
last_read_time -- Pour "read before edit"
|
||
)
|
||
```
|
||
|
||
### 13.2 Requêtes clés
|
||
|
||
```sql
|
||
-- Messages d'une session (chronologique)
|
||
SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC;
|
||
|
||
-- Update atomique des tokens (évite race conditions)
|
||
UPDATE sessions SET
|
||
prompt_tokens = prompt_tokens + ?,
|
||
completion_tokens = completion_tokens + ?,
|
||
cost = cost + ?
|
||
WHERE id = ?;
|
||
|
||
-- Derniers fichiers par path (version max)
|
||
SELECT f.* FROM files f
|
||
INNER JOIN (
|
||
SELECT path, MAX(version) as max_ver
|
||
FROM files WHERE session_id = ?
|
||
GROUP BY path
|
||
) latest ON f.path = latest.path AND f.version = latest.max_ver;
|
||
|
||
-- Stats: utilisation des outils via JSON
|
||
SELECT json_extract(part.data, '$.name') as tool_name, COUNT(*)
|
||
FROM messages, json_each(messages.parts) as part
|
||
WHERE json_extract(part.value, '$.type') = 'tool_call'
|
||
GROUP BY tool_name;
|
||
```
|
||
|
||
---
|
||
|
||
## 14. Fichiers clés à explorer
|
||
|
||
### Architecture et flow principal
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/agent/agent.go` | Moteur IA — construction messages, streaming, résumé, queue |
|
||
| `internal/agent/coordinator.go` | Orchestration — création agents, tools, models, provider options |
|
||
| `internal/agent/prompts.go` | Factory de prompts système |
|
||
| `internal/agent/loop_detection.go` | Détection de boucles (SHA-256 signatures) |
|
||
|
||
### Templates (ce qui est envoyé à l'IA)
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/agent/templates/coder.md.tpl` | System prompt principal (405 lignes) |
|
||
| `internal/agent/templates/task.md.tpl` | Prompt sous-agent |
|
||
| `internal/agent/templates/summary.md` | Prompt de résumé/compaction |
|
||
| `internal/agent/templates/title.md` | Prompt de génération de titre |
|
||
| `internal/agent/templates/agent_tool.md` | Instructions tool agent |
|
||
| `internal/agent/templates/agentic_fetch_prompt.md.tpl` | Prompt sous-agent fetch |
|
||
|
||
### Outils
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/agent/agent_tool.go` | Tool agent (sous-agent spawner) |
|
||
| `internal/agent/agentic_fetch_tool.go` | Tool agentic_fetch |
|
||
| `internal/agent/tools/bash.go` | Tool bash |
|
||
| `internal/agent/tools/edit.go` | Tool edit |
|
||
| `internal/agent/tools/multiedit.go` | Tool multiedit |
|
||
| `internal/agent/tools/write.go` | Tool write |
|
||
| `internal/agent/tools/view.go` | Tool view |
|
||
| `internal/agent/tools/glob.go` | Tool glob |
|
||
| `internal/agent/tools/grep.go` | Tool grep |
|
||
| `internal/agent/tools/ls.go` | Tool ls |
|
||
| `internal/agent/tools/fetch.go` | Tool fetch |
|
||
| `internal/agent/tools/sourcegraph.go` | Tool sourcegraph |
|
||
| `internal/agent/tools/web_search.go` | Tool web_search (DuckDuckGo) |
|
||
| `internal/agent/tools/web_fetch.go` | Tool web_fetch |
|
||
| `internal/agent/tools/download.go` | Tool download |
|
||
| `internal/agent/tools/todos.go` | Tool todos |
|
||
| `internal/agent/tools/diagnostics.go` | Tool LSP diagnostics + helpers |
|
||
| `internal/agent/tools/references.go` | Tool LSP references |
|
||
| `internal/agent/tools/lsp_restart.go` | Tool LSP restart |
|
||
| `internal/agent/tools/crush_info.go` | Tool crush_info |
|
||
| `internal/agent/tools/crush_logs.go` | Tool crush_logs |
|
||
| `internal/agent/tools/mcp-tools.go` | Outils MCP dynamiques |
|
||
|
||
### Messages et données
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/message/message.go` | Service de messages (CRUD + events) |
|
||
| `internal/message/content.go` | Types de contenu + conversion fantasy.Message |
|
||
| `internal/message/attachment.go` | Pièces jointes |
|
||
| `internal/history/file.go` | Historique de versions fichiers |
|
||
| `internal/db/sql/sessions.sql` | Requêtes SQL sessions |
|
||
| `internal/db/sql/messages.sql` | Requêtes SQL messages |
|
||
| `internal/db/sql/files.sql` | Requêtes SQL fichiers |
|
||
| `internal/db/sql/stats.sql` | Requêtes SQL statistiques |
|
||
|
||
### Configuration et providers
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/config/config.go` | Structures de config (models, providers, tools, agents) |
|
||
| `internal/config/provider.go` | Chargement des providers (Catwalk + Hyper + cache) |
|
||
| `internal/backend/config.go` | API config backend |
|
||
| `internal/agent/hyper/provider.go` | Provider Hyper |
|
||
| `internal/agent/hyper/provider.json` | Définition des modèles Hyper |
|
||
|
||
### Infrastructure
|
||
|
||
| Fichier | Ce qu'il contient |
|
||
|---------|------------------|
|
||
| `internal/shell/shell.go` | Shell POSIX cross-platform (mvdan/sh) |
|
||
| `internal/diff/diff.go` | Génération de diffs unifiés |
|
||
| `internal/skills/skills.go` | Système de skills (découverte, injection) |
|
||
| `internal/skills/tracker.go` | Suivi des skills chargés |
|
||
| `internal/lsp/manager.go` | Gestionnaire LSP |
|
||
| `internal/filetracker/service.go` | Suivi des fichiers lus |
|
||
|
||
---
|
||
|
||
## 15. Leçons pour notre application
|
||
|
||
### 15.1 Patterns à adopter
|
||
|
||
| Pattern | Pourquoi | Comment Crush le fait |
|
||
|---------|----------|----------------------|
|
||
| **Résumé automatique adaptatif** | Gère les longues conversations sans perte | Seuil 20K tokens (fenêtres >200K) ou 20% (fenêtres <200K), skip si fenêtre inconnue |
|
||
| **Deux niveaux de modèle** | Économise les tokens coûteux | Large pour coder, Small pour titres et fetch |
|
||
| **Skills avec lazy loading** | N'injecte que les métadonnées, charge les instructions à la demande | Métadonnées dans system prompt, instructions lues via view |
|
||
| **Short tool descriptions** | Économise des tokens sur 22+ outils | FirstLineDescription() par défaut |
|
||
| **Anthropic prompt caching** | Réduit les tokens d'input sur les tours suivants | `ephemeral` sur dernier system + 2 derniers messages |
|
||
| **Filetracker read-before-edit** | Sécurité + contexte pour l'IA | Enregistre chaque lecture, vérifie avant édition |
|
||
| **Loop detection** | Arrête les boucles infinies coûteuses | SHA-256 signature sur fenêtre de 10 étapes, max 5 répétitions |
|
||
| **Orphan filtering** | Nettoie l'historique des messages inutiles | Supprime tool results orphelins, injecte erreurs synthétiques |
|
||
| **Atomic SQL increments** | Pas de race conditions sur les compteurs | `prompt_tokens = prompt_tokens + ?` au lieu de read-modify-write |
|
||
| **Message queue** | Pas de perte de messages | Queue + injection dans PrepareStep |
|
||
|
||
### 15.2 Optimisations token spécifiques
|
||
|
||
1. **Troncature de sortie** — bash (30K), grep (500 chars/ligne), view (2000 lignes)
|
||
2. **Limites de résultats** — glob (100), grep (100), ls (1000), sourcegraph (20)
|
||
3. **Première ligne de description** — Seulement la 1ère ligne des descriptions markdown d'outils
|
||
4. **Estimation heuristique** — `len(text) / 4` pour logging sans appel API
|
||
5. **Résumé détaillé structuré** — Sections obligatoires assurent pas de perte d'information critique
|
||
6. **Reset des compteurs après résumé** — PromptTokens=0, CompletionTokens=résumé seulement
|
||
|
||
### 15.3 Points d'attention
|
||
|
||
- **Le system prompt fait ~405 lignes** — C'est énorme mais structuré en sections XML claires
|
||
- **Pas de troncature du system prompt** — Les skills et context files sont injectés en entier
|
||
- **Le résumé n'a pas de limite** — *"Err on the side of too much detail"*
|
||
- **La détection de boucle est basique** — SHA-256 sur ToolName+Input+Output, mais ne détecte pas les boucles sémantiques
|
||
- **Pas de token counting précis** — Heuristique 4 chars/token, pas de tiktoken
|
||
- **Le file history persiste** — Les versions de fichiers survivent au résumé (pas dans le contexte LLM mais disponibles dans l'UI)
|
||
|
||
### 15.4 Architecture recommandée
|
||
|
||
```
|
||
Pour notre app, inspirons-nous de:
|
||
|
||
1. Coordinator Pattern
|
||
└── Orchestrateur central qui assemble models + tools + prompts
|
||
└── Agents par session avec state isolé
|
||
|
||
2. Summary/Compaction Pipeline
|
||
└── Seuils adaptatifs (fenêtre large vs small)
|
||
└── Résumé structuré avec sections obligatoires
|
||
└── Reset des compteurs après compaction
|
||
|
||
3. Tool Architecture
|
||
└── Interface uniforme (params, execute, response)
|
||
└── Permission system par action
|
||
└── Output truncation systématique
|
||
└── Metadata sur chaque réponse
|
||
|
||
4. Provider Layer
|
||
└── JSON merge en 3 couches pour options
|
||
└── Support multi-provider avec routage
|
||
└── Cache avec ETag + fallback
|
||
|
||
5. Lazy Skill Loading
|
||
└── Métadonnées dans system prompt
|
||
└── Instructions complètes à la demande
|
||
└── Tracker pour éviter les rechargements
|
||
```
|
||
|
||
---
|
||
|
||
## Annexe : Schéma de dépendances
|
||
|
||
```
|
||
main.go
|
||
└── internal/cmd/ (CLI commands)
|
||
└── internal/backend/ (workspace management)
|
||
└── internal/app/ (application logic)
|
||
└── internal/agent/coordinator.go (orchestration)
|
||
├── internal/agent/agent.go (moteur IA)
|
||
│ ├── internal/agent/templates/ (prompts)
|
||
│ ├── internal/agent/prompt/ (prompt builder)
|
||
│ ├── internal/message/ (message types)
|
||
│ └── internal/agent/loop_detection.go
|
||
├── internal/agent/tools/ (22+ outils)
|
||
├── internal/skills/ (skill discovery)
|
||
├── internal/config/ (configuration)
|
||
├── internal/lsp/ (LSP management)
|
||
├── internal/shell/ (shell execution)
|
||
├── internal/db/ (SQLite persistence)
|
||
├── internal/filetracker/ (read tracking)
|
||
└── internal/diff/ (diff generation)
|
||
```
|
||
|
||
---
|
||
|
||
*Rapport généré le 26 avril 2026 — Basé sur l'analyse du commit `HEAD` de [charmbracelet/crush](https://github.com/charmbracelet/crush)*
|