@devilnside/pi-auto-improve 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/docs/features/2026-05-10-auto-improve-design.md +208 -0
- package/extensions/auto-improve.ts +554 -0
- package/package.json +20 -0
- package/skills/auto-improve/SKILL.md +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @ajoye/pi-auto-improve
|
|
2
|
+
|
|
3
|
+
Système d'auto-amélioration pour [Pi Coding Agent](https://pi.dev). Permet à l'agent d'apprendre de ses erreurs via un système de feedback utilisateur.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:@ajoye/pi-auto-improve
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Ou essayer sans installer :
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi -e npm:@ajoye/pi-auto-improve
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Puis redémarre Pi ou tape `/reload`.
|
|
18
|
+
|
|
19
|
+
## Fonctionnalités
|
|
20
|
+
|
|
21
|
+
- **Feedback manuel** — Commandes `/good` et `/bad` pour évaluer les réponses
|
|
22
|
+
- **Boutons Telegram** — 👍/👎 sur chaque réponse en mode Telegram
|
|
23
|
+
- **Analyse d'échec** — Sur feedback négatif, l'agent analyse ce qui a mal tourné et propose une leçon
|
|
24
|
+
- **Leçons validées** — Chaque leçon doit être validée par l'utilisateur avant stockage
|
|
25
|
+
- **3 scopes** — Global, par domaine (debug, refactoring, feature, review, general), par projet
|
|
26
|
+
- **Injection automatique** — Les leçons globales sont injectées dans le prompt système à chaque tour
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Feedback
|
|
31
|
+
|
|
32
|
+
- `/good [commentaire]` — Enregistrer un feedback positif
|
|
33
|
+
- `/bad [commentaire]` — Feedback négatif + déclenche l'analyse
|
|
34
|
+
|
|
35
|
+
### Leçons
|
|
36
|
+
|
|
37
|
+
- `/lessons [scope]` — Afficher les leçons (`global`, `domain`, `project`, `all`)
|
|
38
|
+
- `/lesson-add <domain> <règle>` — Ajouter manuellement une leçon
|
|
39
|
+
- `/lesson-remove <id>` — Désactiver une leçon
|
|
40
|
+
|
|
41
|
+
### Domaines
|
|
42
|
+
|
|
43
|
+
- `debug` — Tests, erreurs, stack traces
|
|
44
|
+
- `refactoring` — Modifications multiples de fichiers
|
|
45
|
+
- `feature` — Nouveaux fichiers, nouvelles fonctionnalités
|
|
46
|
+
- `review` — Lecture de code
|
|
47
|
+
- `general` — Par défaut
|
|
48
|
+
|
|
49
|
+
## Architecture
|
|
50
|
+
|
|
51
|
+
Les données sont stockées dans `~/.pi/agent/data/auto-improve/` :
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
data/auto-improve/
|
|
55
|
+
├── feedback.jsonl # Historique des feedbacks
|
|
56
|
+
├── lessons/
|
|
57
|
+
│ ├── global.json # Leçons globales (toujours chargées)
|
|
58
|
+
│ ├── domain-debug.json # Leçons par domaine
|
|
59
|
+
│ └── ...
|
|
60
|
+
└── projects/ # Leçons par projet (hash du chemin)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Design
|
|
64
|
+
|
|
65
|
+
Voir [docs/features/2026-05-10-auto-improve-design.md](docs/features/2026-05-10-auto-improve-design.md) pour le spec complet.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Pi Auto-Improve — Design
|
|
2
|
+
|
|
3
|
+
**Date :** 2026-05-10
|
|
4
|
+
**Statut :** Validé
|
|
5
|
+
|
|
6
|
+
## Résumé
|
|
7
|
+
|
|
8
|
+
Système d'auto-amélioration pour Pi composé d'un skill (logique métier) et d'une extension légère (intégration Pi). L'utilisateur donne du feedback manuel (CLI ou boutons Telegram 👍/👎). Sur feedback négatif, Pi analyse l'échec, propose une leçon à valider. Les leçons sont stockées en JSON structuré, avec des règles globales toujours chargées au démarrage et des leçons domaine/projet consultées à la demande.
|
|
9
|
+
|
|
10
|
+
## Approche retenue
|
|
11
|
+
|
|
12
|
+
Skill + Extension légère (Approche C) :
|
|
13
|
+
- **Skill** = logique métier (analyse, génération de leçons, consolidation)
|
|
14
|
+
- **Extension** = intégration Pi (commandes, hooks, Telegram, chargement contexte)
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
~/Projects/pi-self-improve/
|
|
20
|
+
├── README.md
|
|
21
|
+
├── docs/features/ # Design docs
|
|
22
|
+
├── skill/
|
|
23
|
+
│ └── auto-improve/
|
|
24
|
+
│ └── SKILL.md # Logique d'analyse et leçons
|
|
25
|
+
├── extension/
|
|
26
|
+
│ └── auto-improve.ts # Commandes, hooks, Telegram
|
|
27
|
+
├── data/
|
|
28
|
+
│ ├── feedback.jsonl # Historique brut des feedbacks
|
|
29
|
+
│ ├── lessons/
|
|
30
|
+
│ │ ├── global.json # Leçons globales
|
|
31
|
+
│ │ ├── domain-debug.json
|
|
32
|
+
│ │ ├── domain-refactoring.json
|
|
33
|
+
│ │ ├── domain-feature.json
|
|
34
|
+
│ │ ├── domain-review.json
|
|
35
|
+
│ │ └── domain-general.json
|
|
36
|
+
│ └── projects/
|
|
37
|
+
│ └── <project-hash>.json # Leçons spécifiques par projet
|
|
38
|
+
└── scripts/
|
|
39
|
+
└── install.sh # Installation dans ~/.pi/agent/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Format de stockage
|
|
43
|
+
|
|
44
|
+
### feedback.jsonl
|
|
45
|
+
|
|
46
|
+
Un JSON par ligne, append-only.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"id": "abc123",
|
|
51
|
+
"timestamp": "2026-05-10T14:30:00Z",
|
|
52
|
+
"type": "positive | negative",
|
|
53
|
+
"source": "cli | telegram",
|
|
54
|
+
"session": "sess-456",
|
|
55
|
+
"project": "/var/home/ajoye/Projects/my-app",
|
|
56
|
+
"domain": "refactoring",
|
|
57
|
+
"context": "Refactor du module auth",
|
|
58
|
+
"user_comment": "Trop de changements d'un coup"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Champs :
|
|
63
|
+
- `id` — identifiant unique (timestamp-based)
|
|
64
|
+
- `timestamp` — ISO 8601
|
|
65
|
+
- `type` — `positive` ou `negative`
|
|
66
|
+
- `source` — `cli` (commande `/good`/`/bad`) ou `telegram` (bouton)
|
|
67
|
+
- `session` — ID de la session Pi
|
|
68
|
+
- `project` — chemin du projet (cwd au moment du feedback)
|
|
69
|
+
- `domain` — domaine détecté ou `general` par défaut
|
|
70
|
+
- `context` — résumé court de la tâche en cours
|
|
71
|
+
- `user_comment` — texte libre optionnel
|
|
72
|
+
|
|
73
|
+
### lessons/*.json et projects/*.json
|
|
74
|
+
|
|
75
|
+
Même structure, scope différent.
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"project": "global",
|
|
80
|
+
"updated": "2026-05-10T14:35:00Z",
|
|
81
|
+
"lessons": [
|
|
82
|
+
{
|
|
83
|
+
"id": "l-001",
|
|
84
|
+
"created": "2026-05-10T14:35:00Z",
|
|
85
|
+
"domain": "refactoring",
|
|
86
|
+
"rule": "Toujours découper les refactors larges en petites étapes incrémentales, une fonction ou un fichier à la fois",
|
|
87
|
+
"rationale": "L'utilisateur préfère voir chaque étape validée avant de continuer",
|
|
88
|
+
"source_feedback": ["abc123"],
|
|
89
|
+
"positive_examples": 2,
|
|
90
|
+
"violations": 0,
|
|
91
|
+
"active": true
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Champs :
|
|
98
|
+
- `id` — identifiant unique de la leçon
|
|
99
|
+
- `created` — date de création
|
|
100
|
+
- `domain` — `debug`, `refactoring`, `feature`, `review`, `general`
|
|
101
|
+
- `rule` — la leçon en langage naturel, concise et actionnable
|
|
102
|
+
- `rationale` — pourquoi cette règle existe
|
|
103
|
+
- `source_feedback` — IDs des feedbacks ayant généré cette leçon
|
|
104
|
+
- `positive_examples` — compteur de fois où la règle a été suivie avec succès
|
|
105
|
+
- `violations` — compteur de fois où la règle a été enfreinte
|
|
106
|
+
- `active` — `false` = désactivée sans suppression
|
|
107
|
+
|
|
108
|
+
Hash projet : hash court (6-8 chars) du chemin absolu du projet.
|
|
109
|
+
|
|
110
|
+
## Extension (auto-improve.ts)
|
|
111
|
+
|
|
112
|
+
### Commandes
|
|
113
|
+
|
|
114
|
+
| Commande | Description |
|
|
115
|
+
|----------|-------------|
|
|
116
|
+
| `/good [commentaire]` | Enregistre un feedback positif |
|
|
117
|
+
| `/bad [commentaire]` | Feedback négatif + déclenche l'analyse |
|
|
118
|
+
| `/lessons [scope]` | Affiche les leçons (global, domain, project) |
|
|
119
|
+
| `/lesson-add <domain> <règle>` | Ajout manuel d'une leçon |
|
|
120
|
+
| `/lesson-remove <id>` | Désactive une leçon |
|
|
121
|
+
|
|
122
|
+
### Hooks
|
|
123
|
+
|
|
124
|
+
- **`on("session_start")`** — Charge `global.json` et l'injecte dans le contexte
|
|
125
|
+
- **`on("tool_call", filter="bash")`** — Détecte le domaine courant basé sur les actions
|
|
126
|
+
|
|
127
|
+
### Intégration Telegram
|
|
128
|
+
|
|
129
|
+
Après chaque réponse en mode Telegram, ajout de boutons cachés :
|
|
130
|
+
```
|
|
131
|
+
<!-- telegram_button label="👍" prompt="Feedback positif pour la dernière réponse." -->
|
|
132
|
+
<!-- telegram_button label="👎" prompt="Feedback négatif pour la dernière réponse." -->
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Le feedback négatif via bouton déclenche le même flux que `/bad`.
|
|
136
|
+
|
|
137
|
+
### Chargement au démarrage
|
|
138
|
+
|
|
139
|
+
Le fichier `global.json` est lu, formaté en Markdown, injecté dans le contexte :
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
## Leçons apprises (auto-improve)
|
|
143
|
+
- [refactoring] Toujours découper les refactors en petites étapes incrémentales
|
|
144
|
+
- [debug] Commencer par reproduire le bug avant de proposer des fixes
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Skill (auto-improve/SKILL.md)
|
|
148
|
+
|
|
149
|
+
### Frontmatter
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
---
|
|
153
|
+
name: auto-improve
|
|
154
|
+
description: "Use when the user gives negative feedback (👎, /bad) or asks to analyze a failure. Handles failure analysis, lesson generation, and lesson consolidation."
|
|
155
|
+
---
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Responsabilités
|
|
159
|
+
|
|
160
|
+
1. **Analyser l'échec** — Examiner la conversation récente, identifier la cause
|
|
161
|
+
2. **Formuler une leçon** — Règle concise, actionnable, en français
|
|
162
|
+
3. **Détecter le domaine** — `debug`, `refactoring`, `feature`, `review`, `general`
|
|
163
|
+
4. **Proposer à l'utilisateur** — Validation avant stockage
|
|
164
|
+
5. **Consolider** — Fusionner avec les leçons existantes si similaire
|
|
165
|
+
|
|
166
|
+
### Processus
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
Feedback négatif reçu
|
|
170
|
+
→ Relire les derniers échanges
|
|
171
|
+
→ Identifier la cause probable
|
|
172
|
+
→ Vérifier si une leçon similaire existe
|
|
173
|
+
→ Si oui : proposer une fusion
|
|
174
|
+
→ Si non : proposer une nouvelle leçon
|
|
175
|
+
→ Présenter à l'utilisateur : règle + justification
|
|
176
|
+
→ Si validé → écrire dans le JSON
|
|
177
|
+
→ Si refusé → ne rien stocker
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Consultation à la demande
|
|
181
|
+
|
|
182
|
+
`/lessons domain debug` ou `/lessons project` :
|
|
183
|
+
- Lire le JSON correspondant
|
|
184
|
+
- Filtrer `active: true`
|
|
185
|
+
- Présenter en liste concise
|
|
186
|
+
|
|
187
|
+
## Chargement hybride
|
|
188
|
+
|
|
189
|
+
- **Toujours chargé** : `global.json` → injecté au démarrage de chaque session
|
|
190
|
+
- **À la demande** : leçons domaine et projet → consultées via `/lessons` ou par le skill quand c'est pertinent
|
|
191
|
+
|
|
192
|
+
## Domaines supportés
|
|
193
|
+
|
|
194
|
+
| Domaine | Détection |
|
|
195
|
+
|---------|-----------|
|
|
196
|
+
| `debug` | Commandes de test, grep d'erreurs, lecture de stack traces |
|
|
197
|
+
| `refactoring` | Modifications multiples de fichiers existants |
|
|
198
|
+
| `feature` | Création de nouveaux fichiers, ajout de fonctionnalités |
|
|
199
|
+
| `review` | Lecture de code sans modification |
|
|
200
|
+
| `general` | Par défaut, quand aucun domaine clair |
|
|
201
|
+
|
|
202
|
+
## Décisions de design
|
|
203
|
+
|
|
204
|
+
1. **Pas d'auto-correction immédiate** — Sur feedback négatif, analyse + proposition de leçon uniquement. L'utilisateur valide.
|
|
205
|
+
2. **Feedback positif stocké aussi** — Sert pour les compteurs `positive_examples` et pour renforcer les leçons existantes.
|
|
206
|
+
3. **Leçons désactivables** — `active: false` plutôt que suppression, pour garder l'historique.
|
|
207
|
+
4. **Pas de ML** — Tout repose sur l'analyse par le LLM et la validation humaine.
|
|
208
|
+
5. **Hash court pour les projets** — Pas de chemins complets dans les noms de fichiers.
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { appendFile, readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { resolve, dirname, join } from "node:path";
|
|
7
|
+
|
|
8
|
+
// ── Configuration ──────────────────────────────────────────────────────────
|
|
9
|
+
const DATA_DIR = resolve(process.env.HOME!, ".pi/agent/data/auto-improve");
|
|
10
|
+
const FEEDBACK_FILE = join(DATA_DIR, "feedback.jsonl");
|
|
11
|
+
const LESSONS_DIR = join(DATA_DIR, "lessons");
|
|
12
|
+
const PROJECTS_DIR = join(DATA_DIR, "projects");
|
|
13
|
+
|
|
14
|
+
const DOMAINS = ["debug", "refactoring", "feature", "review", "general"] as const;
|
|
15
|
+
type Domain = (typeof DOMAINS)[number];
|
|
16
|
+
|
|
17
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
18
|
+
function generateId(): string {
|
|
19
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function projectHash(cwd: string): string {
|
|
23
|
+
return createHash("sha256").update(cwd).digest("hex").slice(0, 8);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function timestamp(): string {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function ensureDir(path: string) {
|
|
31
|
+
if (!existsSync(path)) await mkdir(path, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function ensureFile(path: string, defaultContent: string) {
|
|
35
|
+
if (!existsSync(path)) await writeFile(path, defaultContent, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function domainFromActions(recentActions: string[]): Domain {
|
|
39
|
+
const combined = recentActions.join(" ").toLowerCase();
|
|
40
|
+
if (/test|spec|grep.*error|stack.?trace|fail|exception/.test(combined)) return "debug";
|
|
41
|
+
if (/edit|modify|refactor|rename|move/.test(combined)) return "refactoring";
|
|
42
|
+
if (/create|new file|add.*feature|implement|write.*new/.test(combined)) return "feature";
|
|
43
|
+
if (/review|read.*only|audit|check/.test(combined)) return "review";
|
|
44
|
+
return "general";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Feedback ───────────────────────────────────────────────────────────────
|
|
48
|
+
interface FeedbackEntry {
|
|
49
|
+
id: string;
|
|
50
|
+
timestamp: string;
|
|
51
|
+
type: "positive" | "negative";
|
|
52
|
+
source: "cli" | "telegram";
|
|
53
|
+
session: string;
|
|
54
|
+
project: string;
|
|
55
|
+
domain: string;
|
|
56
|
+
context: string;
|
|
57
|
+
user_comment?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function appendFeedback(entry: FeedbackEntry): Promise<void> {
|
|
61
|
+
await ensureDir(DATA_DIR);
|
|
62
|
+
await appendFile(FEEDBACK_FILE, JSON.stringify(entry) + "\n", "utf8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Lessons ────────────────────────────────────────────────────────────────
|
|
66
|
+
interface Lesson {
|
|
67
|
+
id: string;
|
|
68
|
+
created: string;
|
|
69
|
+
domain: Domain;
|
|
70
|
+
rule: string;
|
|
71
|
+
rationale: string;
|
|
72
|
+
source_feedback: string[];
|
|
73
|
+
positive_examples: number;
|
|
74
|
+
violations: number;
|
|
75
|
+
active: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface LessonFile {
|
|
79
|
+
project: string;
|
|
80
|
+
updated: string;
|
|
81
|
+
lessons: Lesson[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function emptyLessonFile(project: string): LessonFile {
|
|
85
|
+
return { project, updated: timestamp(), lessons: [] };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function readLessonFile(path: string): Promise<LessonFile> {
|
|
89
|
+
await ensureDir(dirname(path));
|
|
90
|
+
await ensureFile(path, JSON.stringify(emptyLessonFile("unknown"), null, 2));
|
|
91
|
+
const raw = await readFile(path, "utf8");
|
|
92
|
+
return JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function writeLessonFile(path: string, data: LessonFile): Promise<void> {
|
|
96
|
+
data.updated = timestamp();
|
|
97
|
+
await writeFile(path, JSON.stringify(data, null, 2), "utf8");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function globalPath(): string {
|
|
101
|
+
return join(LESSONS_DIR, "global.json");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function domainPath(domain: Domain): string {
|
|
105
|
+
return join(LESSONS_DIR, `domain-${domain}.json`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function projectPath(cwd: string): string {
|
|
109
|
+
return join(PROJECTS_DIR, `${projectHash(cwd)}.json`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Format lessons as markdown for injection ───────────────────────────────
|
|
113
|
+
function formatLessonsMarkdown(lessons: Lesson[]): string {
|
|
114
|
+
if (lessons.length === 0) return "";
|
|
115
|
+
const active = lessons.filter((l) => l.active);
|
|
116
|
+
if (active.length === 0) return "";
|
|
117
|
+
const lines = active.map((l) => `- [${l.domain}] ${l.rule}`);
|
|
118
|
+
return "## Leçons apprises (auto-improve)\n" + lines.join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function loadGlobalLessons(): Promise<string> {
|
|
122
|
+
const data = await readLessonFile(globalPath());
|
|
123
|
+
return formatLessonsMarkdown(data.lessons);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Track recent bash actions for domain detection ─────────────────────────
|
|
127
|
+
const recentActions: string[] = [];
|
|
128
|
+
const MAX_ACTIONS = 10;
|
|
129
|
+
|
|
130
|
+
function trackAction(command: string) {
|
|
131
|
+
recentActions.push(command);
|
|
132
|
+
if (recentActions.length > MAX_ACTIONS) recentActions.shift();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Context summary from session ───────────────────────────────────────────
|
|
136
|
+
function getContextSummary(entries: any[]): string {
|
|
137
|
+
// Get last few user messages as context
|
|
138
|
+
const userMsgs = entries
|
|
139
|
+
.filter(
|
|
140
|
+
(e: any) =>
|
|
141
|
+
e.type === "message" &&
|
|
142
|
+
e.message?.role === "user" &&
|
|
143
|
+
typeof e.message?.content === "string"
|
|
144
|
+
)
|
|
145
|
+
.slice(-3);
|
|
146
|
+
return userMsgs
|
|
147
|
+
.map((e: any) => e.message.content.slice(0, 100))
|
|
148
|
+
.join(" | ");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Extension ──────────────────────────────────────────────────────────────
|
|
152
|
+
export default function (pi: ExtensionAPI) {
|
|
153
|
+
// Ensure data structure on startup
|
|
154
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
155
|
+
await ensureDir(LESSONS_DIR);
|
|
156
|
+
await ensureDir(PROJECTS_DIR);
|
|
157
|
+
await ensureFile(
|
|
158
|
+
globalPath(),
|
|
159
|
+
JSON.stringify(emptyLessonFile("global"), null, 2)
|
|
160
|
+
);
|
|
161
|
+
for (const d of DOMAINS) {
|
|
162
|
+
await ensureFile(
|
|
163
|
+
domainPath(d),
|
|
164
|
+
JSON.stringify(emptyLessonFile(`domain-${d}`), null, 2)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Track bash actions for domain detection
|
|
170
|
+
pi.on("tool_call", async (event) => {
|
|
171
|
+
if (event.toolName === "bash" && event.input?.command) {
|
|
172
|
+
trackAction(event.input.command);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Inject global lessons into system prompt
|
|
177
|
+
pi.on("before_agent_start", async (event) => {
|
|
178
|
+
const md = await loadGlobalLessons();
|
|
179
|
+
if (md) {
|
|
180
|
+
return { systemPrompt: event.systemPrompt + "\n\n" + md };
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// ── Command: /good ─────────────────────────────────────────────────────
|
|
185
|
+
pi.registerCommand("good", {
|
|
186
|
+
description: "Enregistrer un feedback positif sur la dernière réponse",
|
|
187
|
+
handler: async (args, ctx) => {
|
|
188
|
+
const sessionId =
|
|
189
|
+
ctx.sessionManager.getSessionFile()?.split("/").pop() ?? "unknown";
|
|
190
|
+
const domain = domainFromActions(recentActions);
|
|
191
|
+
const context = getContextSummary(ctx.sessionManager.getEntries());
|
|
192
|
+
|
|
193
|
+
await appendFeedback({
|
|
194
|
+
id: generateId(),
|
|
195
|
+
timestamp: timestamp(),
|
|
196
|
+
type: "positive",
|
|
197
|
+
source: "cli",
|
|
198
|
+
session: sessionId,
|
|
199
|
+
project: ctx.cwd,
|
|
200
|
+
domain,
|
|
201
|
+
context,
|
|
202
|
+
user_comment: args || undefined,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
ctx.ui.notify("👍 Feedback positif enregistré", "info");
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ── Command: /bad ──────────────────────────────────────────────────────
|
|
210
|
+
pi.registerCommand("bad", {
|
|
211
|
+
description:
|
|
212
|
+
"Enregistrer un feedback négatif et déclencher l'analyse d'auto-amélioration",
|
|
213
|
+
handler: async (args, ctx) => {
|
|
214
|
+
const sessionId =
|
|
215
|
+
ctx.sessionManager.getSessionFile()?.split("/").pop() ?? "unknown";
|
|
216
|
+
const domain = domainFromActions(recentActions);
|
|
217
|
+
const context = getContextSummary(ctx.sessionManager.getEntries());
|
|
218
|
+
const feedbackId = generateId();
|
|
219
|
+
|
|
220
|
+
await appendFeedback({
|
|
221
|
+
id: feedbackId,
|
|
222
|
+
timestamp: timestamp(),
|
|
223
|
+
type: "negative",
|
|
224
|
+
source: "cli",
|
|
225
|
+
session: sessionId,
|
|
226
|
+
project: ctx.cwd,
|
|
227
|
+
domain,
|
|
228
|
+
context,
|
|
229
|
+
user_comment: args || undefined,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
ctx.ui.notify("👎 Feedback négatif enregistré — analyse en cours...", "warning");
|
|
233
|
+
|
|
234
|
+
// Send analysis prompt to the agent
|
|
235
|
+
const comment = args ? ` Commentaire de l'utilisateur : "${args}"` : "";
|
|
236
|
+
pi.sendUserMessage(
|
|
237
|
+
`Feedback négatif reçu (id: ${feedbackId}, domaine détecté: ${domain}).${comment}\n\n` +
|
|
238
|
+
`Utilise le skill auto-improve pour analyser ce qui n'a pas fonctionné dans tes dernières réponses. ` +
|
|
239
|
+
`Propose une leçon à l'utilisateur et attends sa validation avant de la sauvegarder via l'outil save_lesson.`,
|
|
240
|
+
{ deliverAs: "steer" }
|
|
241
|
+
);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// ── Command: /lessons ──────────────────────────────────────────────────
|
|
246
|
+
pi.registerCommand("lessons", {
|
|
247
|
+
description:
|
|
248
|
+
"Afficher les leçons apprises. Usage: /lessons [global|domain|project|all]",
|
|
249
|
+
handler: async (args, ctx) => {
|
|
250
|
+
const scope = args?.trim().toLowerCase() || "global";
|
|
251
|
+
let output = "";
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
if (scope === "global" || scope === "all") {
|
|
255
|
+
const data = await readLessonFile(globalPath());
|
|
256
|
+
const active = data.lessons.filter((l) => l.active);
|
|
257
|
+
output += `📚 Leçons globales (${active.length}) :\n`;
|
|
258
|
+
for (const l of active) {
|
|
259
|
+
output += ` [${l.domain}] ${l.rule}\n`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (scope === "domain" || scope === "all") {
|
|
264
|
+
for (const d of DOMAINS) {
|
|
265
|
+
const data = await readLessonFile(domainPath(d));
|
|
266
|
+
const active = data.lessons.filter((l) => l.active);
|
|
267
|
+
if (active.length > 0) {
|
|
268
|
+
output += `\n📚 Leçons ${d} (${active.length}) :\n`;
|
|
269
|
+
for (const l of active) {
|
|
270
|
+
output += ` ${l.rule}\n`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (scope === "project" || scope === "all") {
|
|
277
|
+
const pPath = projectPath(ctx.cwd);
|
|
278
|
+
const data = await readLessonFile(pPath);
|
|
279
|
+
const active = data.lessons.filter((l) => l.active);
|
|
280
|
+
output += `\n📚 Leçons projet (${active.length}) :\n`;
|
|
281
|
+
for (const l of active) {
|
|
282
|
+
output += ` [${l.domain}] ${l.rule}\n`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
ctx.ui.notify(output || "Aucune leçon trouvée.", "info");
|
|
287
|
+
} catch (err: any) {
|
|
288
|
+
ctx.ui.notify(`Erreur: ${err.message}`, "error");
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ── Command: /lesson-add ───────────────────────────────────────────────
|
|
294
|
+
pi.registerCommand("lesson-add", {
|
|
295
|
+
description:
|
|
296
|
+
"Ajouter manuellement une leçon. Usage: /lesson-add <domain> <règle>",
|
|
297
|
+
handler: async (args, ctx) => {
|
|
298
|
+
const parts = args?.trim().split(/\s+/);
|
|
299
|
+
if (!parts || parts.length < 2) {
|
|
300
|
+
ctx.ui.notify(
|
|
301
|
+
"Usage: /lesson-add <domain> <règle>\nDomaines: debug, refactoring, feature, review, general",
|
|
302
|
+
"warning"
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const domain = parts[0] as Domain;
|
|
308
|
+
if (!DOMAINS.includes(domain)) {
|
|
309
|
+
ctx.ui.notify(
|
|
310
|
+
`Domaine invalide: ${domain}. Utilise: ${DOMAINS.join(", ")}`,
|
|
311
|
+
"error"
|
|
312
|
+
);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const rule = parts.slice(1).join(" ");
|
|
317
|
+
const scope = ctx.hasUI ? "global" : "global"; // default to global for manual adds
|
|
318
|
+
|
|
319
|
+
const path = globalPath();
|
|
320
|
+
const data = await readLessonFile(path);
|
|
321
|
+
const lesson: Lesson = {
|
|
322
|
+
id: `l-${generateId()}`,
|
|
323
|
+
created: timestamp(),
|
|
324
|
+
domain,
|
|
325
|
+
rule,
|
|
326
|
+
rationale: "Ajoutée manuellement par l'utilisateur",
|
|
327
|
+
source_feedback: [],
|
|
328
|
+
positive_examples: 0,
|
|
329
|
+
violations: 0,
|
|
330
|
+
active: true,
|
|
331
|
+
};
|
|
332
|
+
data.lessons.push(lesson);
|
|
333
|
+
await writeLessonFile(path, data);
|
|
334
|
+
|
|
335
|
+
ctx.ui.notify(`✅ Leçon ajoutée: [${domain}] ${rule}`, "info");
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ── Command: /lesson-remove ────────────────────────────────────────────
|
|
340
|
+
pi.registerCommand("lesson-remove", {
|
|
341
|
+
description:
|
|
342
|
+
"Désactiver une leçon. Usage: /lesson-remove <id>",
|
|
343
|
+
handler: async (args, ctx) => {
|
|
344
|
+
const id = args?.trim();
|
|
345
|
+
if (!id) {
|
|
346
|
+
ctx.ui.notify("Usage: /lesson-remove <id>", "warning");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Search across all lesson files
|
|
351
|
+
const filesToSearch = [
|
|
352
|
+
globalPath(),
|
|
353
|
+
...DOMAINS.map(domainPath),
|
|
354
|
+
projectPath(ctx.cwd),
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
let found = false;
|
|
358
|
+
for (const path of filesToSearch) {
|
|
359
|
+
if (!existsSync(path)) continue;
|
|
360
|
+
const data = await readLessonFile(path);
|
|
361
|
+
const lesson = data.lessons.find((l) => l.id === id);
|
|
362
|
+
if (lesson) {
|
|
363
|
+
lesson.active = false;
|
|
364
|
+
await writeLessonFile(path, data);
|
|
365
|
+
ctx.ui.notify(`🗑️ Leçon désactivée: ${lesson.rule}`, "info");
|
|
366
|
+
found = true;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!found) {
|
|
372
|
+
ctx.ui.notify(`Leçon ${id} non trouvée.`, "warning");
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// ── Tool: save_lesson ──────────────────────────────────────────────────
|
|
378
|
+
// Used by the agent (guided by the skill) to save a validated lesson
|
|
379
|
+
pi.registerTool({
|
|
380
|
+
name: "save_lesson",
|
|
381
|
+
label: "Save Lesson",
|
|
382
|
+
description:
|
|
383
|
+
"Sauvegarder une leçon validée par l'utilisateur. Spécifier le scope, domaine, règle et justification.",
|
|
384
|
+
promptSnippet: "Sauvegarder une leçon d'auto-amélioration validée",
|
|
385
|
+
parameters: Type.Object({
|
|
386
|
+
scope: Type.Union([
|
|
387
|
+
Type.Literal("global"),
|
|
388
|
+
Type.Literal("domain"),
|
|
389
|
+
Type.Literal("project"),
|
|
390
|
+
], { description: "Scope de la leçon: global, domain, ou project" }),
|
|
391
|
+
domain: Type.Union([
|
|
392
|
+
Type.Literal("debug"),
|
|
393
|
+
Type.Literal("refactoring"),
|
|
394
|
+
Type.Literal("feature"),
|
|
395
|
+
Type.Literal("review"),
|
|
396
|
+
Type.Literal("general"),
|
|
397
|
+
], { description: "Domaine de la leçon" }),
|
|
398
|
+
rule: Type.String({ description: "La leçon en français, concise et actionnable" }),
|
|
399
|
+
rationale: Type.String({ description: "Pourquoi cette règle existe" }),
|
|
400
|
+
source_feedback_id: Type.Optional(
|
|
401
|
+
Type.String({ description: "ID du feedback ayant généré cette leçon" })
|
|
402
|
+
),
|
|
403
|
+
merge_with: Type.Optional(
|
|
404
|
+
Type.String({ description: "ID d'une leçon existante à fusionner" })
|
|
405
|
+
),
|
|
406
|
+
}),
|
|
407
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
408
|
+
let path: string;
|
|
409
|
+
if (params.scope === "global") {
|
|
410
|
+
path = globalPath();
|
|
411
|
+
} else if (params.scope === "domain") {
|
|
412
|
+
path = domainPath(params.domain);
|
|
413
|
+
} else {
|
|
414
|
+
path = projectPath(ctx.cwd);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const data = await readLessonFile(path);
|
|
418
|
+
|
|
419
|
+
// Check for merge
|
|
420
|
+
if (params.merge_with) {
|
|
421
|
+
const existing = data.lessons.find((l) => l.id === params.merge_with);
|
|
422
|
+
if (existing) {
|
|
423
|
+
existing.rule = params.rule;
|
|
424
|
+
existing.rationale = params.rationale;
|
|
425
|
+
existing.domain = params.domain;
|
|
426
|
+
if (params.source_feedback_id) {
|
|
427
|
+
existing.source_feedback.push(params.source_feedback_id);
|
|
428
|
+
}
|
|
429
|
+
await writeLessonFile(path, data);
|
|
430
|
+
return {
|
|
431
|
+
content: [
|
|
432
|
+
{
|
|
433
|
+
type: "text",
|
|
434
|
+
text: `Leçon fusionnée: [${params.domain}] ${params.rule}`,
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
details: { merged: true, id: existing.id },
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// New lesson
|
|
443
|
+
const lesson: Lesson = {
|
|
444
|
+
id: `l-${generateId()}`,
|
|
445
|
+
created: timestamp(),
|
|
446
|
+
domain: params.domain,
|
|
447
|
+
rule: params.rule,
|
|
448
|
+
rationale: params.rationale,
|
|
449
|
+
source_feedback: params.source_feedback_id
|
|
450
|
+
? [params.source_feedback_id]
|
|
451
|
+
: [],
|
|
452
|
+
positive_examples: 0,
|
|
453
|
+
violations: 0,
|
|
454
|
+
active: true,
|
|
455
|
+
};
|
|
456
|
+
data.lessons.push(lesson);
|
|
457
|
+
await writeLessonFile(path, data);
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
{
|
|
462
|
+
type: "text",
|
|
463
|
+
text: `Leçon sauvegardée (${params.scope}): [${params.domain}] ${params.rule}`,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
details: { id: lesson.id, scope: params.scope },
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// ── Tool: list_lessons ─────────────────────────────────────────────────
|
|
472
|
+
// Let the agent query lessons programmatically
|
|
473
|
+
pi.registerTool({
|
|
474
|
+
name: "list_lessons",
|
|
475
|
+
label: "List Lessons",
|
|
476
|
+
description:
|
|
477
|
+
"Lister les leçons apprises pour un scope donné. Utile pour vérifier les leçons existantes avant d'en proposer une nouvelle.",
|
|
478
|
+
promptSnippet: "Lister les leçons d'auto-amélioration existantes",
|
|
479
|
+
parameters: Type.Object({
|
|
480
|
+
scope: Type.Union([
|
|
481
|
+
Type.Literal("global"),
|
|
482
|
+
Type.Literal("domain"),
|
|
483
|
+
Type.Literal("project"),
|
|
484
|
+
Type.Literal("all"),
|
|
485
|
+
], { description: "Scope à lister" }),
|
|
486
|
+
domain: Type.Optional(
|
|
487
|
+
Type.Union([
|
|
488
|
+
Type.Literal("debug"),
|
|
489
|
+
Type.Literal("refactoring"),
|
|
490
|
+
Type.Literal("feature"),
|
|
491
|
+
Type.Literal("review"),
|
|
492
|
+
Type.Literal("general"),
|
|
493
|
+
], { description: "Filtrer par domaine" })
|
|
494
|
+
),
|
|
495
|
+
}),
|
|
496
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
497
|
+
const results: { scope: string; lessons: Lesson[] }[] = [];
|
|
498
|
+
|
|
499
|
+
if (params.scope === "global" || params.scope === "all") {
|
|
500
|
+
const data = await readLessonFile(globalPath());
|
|
501
|
+
const filtered = params.domain
|
|
502
|
+
? data.lessons.filter(
|
|
503
|
+
(l) => l.active && l.domain === params.domain
|
|
504
|
+
)
|
|
505
|
+
: data.lessons.filter((l) => l.active);
|
|
506
|
+
if (filtered.length > 0) results.push({ scope: "global", lessons: filtered });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (params.scope === "domain" || params.scope === "all") {
|
|
510
|
+
if (params.domain) {
|
|
511
|
+
const data = await readLessonFile(domainPath(params.domain));
|
|
512
|
+
const filtered = data.lessons.filter((l) => l.active);
|
|
513
|
+
if (filtered.length > 0)
|
|
514
|
+
results.push({ scope: `domain-${params.domain}`, lessons: filtered });
|
|
515
|
+
} else {
|
|
516
|
+
for (const d of DOMAINS) {
|
|
517
|
+
const data = await readLessonFile(domainPath(d));
|
|
518
|
+
const filtered = data.lessons.filter((l) => l.active);
|
|
519
|
+
if (filtered.length > 0)
|
|
520
|
+
results.push({ scope: `domain-${d}`, lessons: filtered });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (params.scope === "project" || params.scope === "all") {
|
|
526
|
+
const data = await readLessonFile(projectPath(ctx.cwd));
|
|
527
|
+
const filtered = params.domain
|
|
528
|
+
? data.lessons.filter(
|
|
529
|
+
(l) => l.active && l.domain === params.domain
|
|
530
|
+
)
|
|
531
|
+
: data.lessons.filter((l) => l.active);
|
|
532
|
+
if (filtered.length > 0) results.push({ scope: "project", lessons: filtered });
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (results.length === 0) {
|
|
536
|
+
return {
|
|
537
|
+
content: [{ type: "text", text: "Aucune leçon trouvée pour ce scope/domaine." }],
|
|
538
|
+
details: {},
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const lines = results.flatMap((r) =>
|
|
543
|
+
r.lessons.map(
|
|
544
|
+
(l) => `[${r.scope}][${l.domain}] ${l.id}: ${l.rule}`
|
|
545
|
+
)
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
550
|
+
details: { results },
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devilnside/pi-auto-improve",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Auto-improvement skill for Pi Coding Agent — feedback analysis, lesson generation and consolidation",
|
|
5
|
+
"keywords": ["pi-package"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "ajoye",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/devilnside/pi-auto-improve"
|
|
11
|
+
},
|
|
12
|
+
"pi": {
|
|
13
|
+
"extensions": ["./extensions"],
|
|
14
|
+
"skills": ["./skills"]
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
18
|
+
"typebox": "*"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auto-improve
|
|
3
|
+
description: "Use when the user gives negative feedback (👎, /bad) or asks to analyze a failure. Handles failure analysis, lesson generation, and lesson consolidation."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Auto-Improve
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Analyse les échecs de l'agent, génère des leçons actionnables, et les consolide avec les leçons existantes. L'utilisateur valide chaque leçon avant stockage.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- L'utilisateur donne un feedback négatif (`/bad`, bouton 👎)
|
|
15
|
+
- L'utilisateur donne un feedback positif (`/good`, bouton 👍)
|
|
16
|
+
- L'utilisateur demande une analyse d'échec
|
|
17
|
+
- L'utilisateur utilise `/lesson-add` ou `/lesson-remove`
|
|
18
|
+
|
|
19
|
+
## Boutons Telegram — OBLIGATOIRE
|
|
20
|
+
|
|
21
|
+
**À CHAQUE réponse** (sauf si la réponse EST un feedback `/good`/`/bad`), ajouter les boutons à la fin :
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<!-- telegram_button label="👍" prompt="/good" -->
|
|
25
|
+
<!-- telegram_button label="👎" prompt="/bad" -->
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Ne PAS ajouter les boutons si :
|
|
29
|
+
- La réponse est elle-même un traitement de feedback
|
|
30
|
+
- L'utilisateur vient de cliquer 👍 ou 👎 (ça ferait une boucle)
|
|
31
|
+
|
|
32
|
+
## Gestion des feedbacks entrants
|
|
33
|
+
|
|
34
|
+
### Feedback positif (`/good`)
|
|
35
|
+
|
|
36
|
+
1. Utiliser l'outil `save_lesson` avec `scope: "global"`, `domain: "general"`, une règle positive si pertinent, ou simplement accuser réception
|
|
37
|
+
2. Répondre brièvement : "Merci ! 👍"
|
|
38
|
+
3. NE PAS ajouter les boutons 👍/👎 à cette réponse
|
|
39
|
+
|
|
40
|
+
### Feedback négatif (`/bad`)
|
|
41
|
+
|
|
42
|
+
Suivre le processus d'analyse ci-dessous. NE PAS ajouter les boutons 👍/👎 à la réponse de validation de leçon.
|
|
43
|
+
|
|
44
|
+
## Processus d'analyse (feedback négatif)
|
|
45
|
+
|
|
46
|
+
```dot
|
|
47
|
+
digraph analyze {
|
|
48
|
+
"Feedback négatif reçu" -> "Relire les derniers échanges (5-10 derniers messages)";
|
|
49
|
+
"Relire les derniers échanges" -> "Identifier la cause probable de l'insatisfaction";
|
|
50
|
+
"Identifier la cause probable" -> "Vérifier leçons existantes via list_lessons";
|
|
51
|
+
"Vérifier leçons existantes" -> "Leçon similaire?";
|
|
52
|
+
"Leçon similaire?" -> "Oui: Proposer fusion (affiner règle existante)" [label="Oui"];
|
|
53
|
+
"Leçon similaire?" -> "Non: Proposer nouvelle leçon" [label="Non"];
|
|
54
|
+
"Proposer fusion" -> "Présenter à l'utilisateur";
|
|
55
|
+
"Proposer nouvelle leçon" -> "Présenter à l'utilisateur";
|
|
56
|
+
"Présenter à l'utilisateur" -> "Validé?";
|
|
57
|
+
"Validé?" -> "Oui: Sauvegarder via save_lesson" [label="Oui"];
|
|
58
|
+
"Validé?" -> "Non: Ne rien stocker" [label="Non"];
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Domaines
|
|
63
|
+
|
|
64
|
+
| Domaine | Détection |
|
|
65
|
+
|---------|-----------|
|
|
66
|
+
| `debug` | Commandes de test, grep d'erreurs, stack traces |
|
|
67
|
+
| `refactoring` | Modifications multiples de fichiers existants |
|
|
68
|
+
| `feature` | Création de nouveaux fichiers, ajout de fonctionnalités |
|
|
69
|
+
| `review` | Lecture de code sans modification |
|
|
70
|
+
| `general` | Par défaut |
|
|
71
|
+
|
|
72
|
+
## Format de leçon proposée
|
|
73
|
+
|
|
74
|
+
Quand une leçon est proposée, la présenter ainsi :
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
📝 Nouvelle leçon proposée :
|
|
78
|
+
Domaine : [domaine]
|
|
79
|
+
Règle : [règle concise en français]
|
|
80
|
+
Justification : [pourquoi]
|
|
81
|
+
|
|
82
|
+
Valider ? (oui/non)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Format de fusion proposée
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
🔄 Fusion de leçons proposée :
|
|
89
|
+
Leçon existante : [ancienne règle]
|
|
90
|
+
Nouvelle règle fusionnée : [règle mise à jour]
|
|
91
|
+
Justification : [pourquoi la fusion]
|
|
92
|
+
|
|
93
|
+
Valider ? (oui/non)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Règles
|
|
97
|
+
|
|
98
|
+
1. Toujours formuler les leçons en français, de manière concise et actionnable
|
|
99
|
+
2. Toujours valider avec l'utilisateur avant de stocker via `save_lesson`
|
|
100
|
+
3. Préférer fusionner plutôt que dupliquer
|
|
101
|
+
4. Détecter le domaine automatiquement si possible, sinon `general`
|
|
102
|
+
5. Ne jamais stocker sans validation explicite de l'utilisateur
|
|
103
|
+
6. Sur Telegram, toujours inclure les boutons 👍/👎 sauf si la réponse est un traitement de feedback
|