v0.2.11 : mémoire (mem_search/read/add/edit) remplace les skills + outil edit (old/new) + run_shell renommé bash ; migration auto SKILLS->MEMORY

This commit is contained in:
nathaninline committed 2026-06-26 15:59:24 +02:00
1 parent d3a2f1c428
commit 4c8aafa898
12 files changed
+660 -124

No files matched your search

+64 -21
View File
@@ -30,20 +30,44 @@ func baseSystemPrompt(caps Caps) string {
if !caps.Agent {
return ""
}
cwd, _ := os.Getwd()
var b strings.Builder
b.WriteString("Tu es un assistant expert opérant dans jean, un agent qui agit directement sur cette machine. Tu aides l'utilisateur en inspectant le système, en exécutant des commandes et en gérant des skills réutilisables.\n\n")
b.WriteString("Méthode :\n")
b.WriteString("You are an expert coding assistant operating inside jean, a coding agent harness. You help users by reading files, executing commands, editing code, writing new files, and leveraging persistent memory to remember things across sessions.\n\n")
b.WriteString("Available tools:\n")
for _, l := range []string{
"Raisonne brièvement (1 à 2 phrases) puis AGIS. Dès qu'une action est possible, appelle l'outil immédiatement — ne l'annonce pas, fais-le.",
"Ne répète JAMAIS deux fois la même étape de réflexion ni le même appel d'outil. Si tu hésites (« je devrais peut-être vérifier… »), arrête de réfléchir et appelle l'outil maintenant.",
"Pour toute question système (fichiers, process, réseau, OS), utilise tes outils au lieu de supposer — tu as un accès réel, ne demande pas la permission pour inspecter.",
"Quand la tâche est faite, donne une réponse finale courte et arrête-toi. N'enchaîne pas d'outils inutiles.",
"Sois concis dans tes réponses.",
"Affiche clairement les chemins de fichiers quand tu travailles dessus.",
"bash: Execute a shell command on this machine (ls, grep, find, cat, sed, read logs, run scripts…) and return stdout, stderr and the exit code. This is your real access to the system.",
"edit: Modify a file on disk by exact replacement (old → new; old must match exactly once). Use this to patch a file instead of rewriting it whole.",
"mem_search: Search your memory (Markdown pages under MEMORY/). Returns a ranked list of {file, title, snippet}.",
"mem_read: Read a memory page (Markdown file), line-numbered, with optional offset/limit.",
"mem_add: Create a new memory page (one topic per page, descriptive kebab-case name, first line is a short # title).",
"mem_edit: Edit a memory page by exact replacement (old → new; old must match exactly once).",
} {
b.WriteString("- " + l + "\n")
}
b.WriteString("\nDate du jour : " + time.Now().Format("2006-01-02"))
b.WriteString("\nGuidelines:\n")
for _, l := range []string{
"Reason briefly (1-2 sentences) then ACT. As soon as an action is possible, call the tool immediately — don't announce it, do it.",
"Never repeat the same reasoning step or the same tool call twice. If you hesitate (\"maybe I should check…\"), stop thinking and call the tool now.",
"Use bash for any system question (files, processes, network, OS) instead of assuming — you have real access, don't ask permission to inspect.",
"Use bash to read files (cat, sed -n); to change a file, use the edit tool (old → new) instead of rewriting it whole with bash.",
"Use mem_search FIRST whenever the user asks about something you might already know (preferences, ongoing projects, past decisions). Open the most relevant page with mem_read; snippets are hints, not the answer.",
"Save anything worth keeping across sessions with mem_add; update an existing page with mem_edit. Don't save ephemeral conversation state.",
"Prefer targeted, non-interactive commands; chain them with && when they depend on each other.",
"Avoid destructive commands (rm -rf, mkfs, dd…) unless the user explicitly asks.",
"If a command fails, read stderr and the exit code, then fix it — don't re-run the same command unchanged.",
"Never invent a tool: you only have bash, edit and the mem_* tools. Anything else (reading code, web access) goes through bash.",
"When the task is done, give a short final answer and stop. Don't chain useless tool calls.",
"Be concise in your responses.",
"Show file paths clearly when working with files.",
} {
b.WriteString("- " + l + "\n")
}
b.WriteString("\nCurrent date: " + time.Now().Format("2006-01-02"))
b.WriteString("\nCurrent working directory: " + cwd)
return b.String()
}
@@ -57,7 +81,7 @@ func machineSystemPrompt(caps Caps) string {
}
host, _ := os.Hostname()
if host == "" {
host = "inconnu"
host = "unknown"
}
who := ""
if u, err := user.Current(); err == nil {
@@ -66,17 +90,7 @@ func machineSystemPrompt(caps Caps) string {
cwd, _ := os.Getwd()
var b strings.Builder
b.WriteString("Outil disponible — run_shell(command, timeout) : exécute une commande shell sur CETTE machine et renvoie stdout, stderr et le code de sortie. C'est ton accès réel ; ne dis jamais que tu n'y as pas accès.\n")
b.WriteString("Conseils run_shell :\n")
for _, l := range []string{
"Sers-t'en pour toute question système (OS, disque, process, fichiers, réseau, lire un script) plutôt que de supposer.",
"Préfère des commandes ciblées et non-interactives ; chaîne-les avec && quand elles sont dépendantes.",
"Évite les commandes destructrices (rm -rf, mkfs, dd…) sauf demande explicite de l'utilisateur.",
"Si une commande échoue, lis stderr et le code de sortie, puis corrige — ne relance pas la même commande à l'identique.",
} {
b.WriteString("- " + l + "\n")
}
b.WriteString(fmt.Sprintf("Machine : hôte=%s, %s/%s", host, runtime.GOOS, runtime.GOARCH))
b.WriteString(fmt.Sprintf("Machine: host=%s, %s/%s", host, runtime.GOOS, runtime.GOARCH))
if who != "" {
b.WriteString(", user=" + who)
}
@@ -128,6 +142,35 @@ func runShell(command string, timeoutSec int) string {
return strings.Join(parts, "\n\n")
}
// fileEdit applies a single exact-text replacement to a file on disk: oldText
// must appear EXACTLY once (otherwise it errors), so the model can patch a file
// without rewriting it whole. Returns a short status string for the tool result.
func fileEdit(path, oldText, newText string) string {
if strings.TrimSpace(path) == "" {
return "[erreur] chemin vide"
}
if oldText == "" {
return "[erreur] old vide"
}
b, err := os.ReadFile(path)
if err != nil {
return "[erreur] " + err.Error()
}
content := string(b)
n := strings.Count(content, oldText)
if n == 0 {
return "[erreur] old introuvable dans le fichier"
}
if n > 1 {
return fmt.Sprintf("[erreur] old apparaît %d fois — ajoute du contexte pour le rendre unique", n)
}
updated := strings.Replace(content, oldText, newText, 1)
if err := os.WriteFile(path, []byte(updated), 0o644); err != nil {
return "[erreur] " + err.Error()
}
return fmt.Sprintf("[ok] %s modifié (1 remplacement)", path)
}
// tailRunes returns the last n runes of s (used to cap tool output).
func tailRunes(s string, n int) string {
r := []rune(s)