agentic-api 2.0.314 → 2.0.491

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.
Files changed (48) hide show
  1. package/dist/src/agents/prompts.d.ts +1 -1
  2. package/dist/src/agents/prompts.js +9 -7
  3. package/dist/src/agents/simulator.d.ts +7 -3
  4. package/dist/src/agents/simulator.executor.d.ts +9 -3
  5. package/dist/src/agents/simulator.executor.js +43 -17
  6. package/dist/src/agents/simulator.js +47 -19
  7. package/dist/src/agents/simulator.prompts.d.ts +9 -8
  8. package/dist/src/agents/simulator.prompts.js +68 -62
  9. package/dist/src/agents/simulator.types.d.ts +4 -1
  10. package/dist/src/agents/simulator.utils.js +0 -2
  11. package/dist/src/execute/helpers.d.ts +75 -0
  12. package/dist/src/execute/helpers.js +139 -0
  13. package/dist/src/execute/index.d.ts +11 -0
  14. package/dist/src/execute/index.js +44 -0
  15. package/dist/src/execute/legacy.d.ts +46 -0
  16. package/dist/src/{execute.js → execute/legacy.js} +130 -232
  17. package/dist/src/execute/modelconfig.d.ts +19 -0
  18. package/dist/src/execute/modelconfig.js +56 -0
  19. package/dist/src/execute/responses.d.ts +55 -0
  20. package/dist/src/execute/responses.js +594 -0
  21. package/dist/src/execute/shared.d.ts +83 -0
  22. package/dist/src/execute/shared.js +188 -0
  23. package/dist/src/index.js +1 -1
  24. package/dist/src/pricing.llm.d.ts +1 -1
  25. package/dist/src/pricing.llm.js +39 -18
  26. package/dist/src/rag/embeddings.js +8 -2
  27. package/dist/src/rag/rag.manager.js +27 -15
  28. package/dist/src/rules/git/git.e2e.helper.js +21 -2
  29. package/dist/src/rules/git/git.health.d.ts +4 -2
  30. package/dist/src/rules/git/git.health.js +58 -16
  31. package/dist/src/rules/git/index.d.ts +1 -1
  32. package/dist/src/rules/git/index.js +3 -2
  33. package/dist/src/rules/git/repo.d.ts +46 -3
  34. package/dist/src/rules/git/repo.js +264 -23
  35. package/dist/src/rules/git/repo.pr.js +117 -13
  36. package/dist/src/rules/types.d.ts +11 -0
  37. package/dist/src/rules/utils.matter.js +16 -7
  38. package/dist/src/scrapper.js +1 -0
  39. package/dist/src/stategraph/stategraph.d.ts +26 -1
  40. package/dist/src/stategraph/stategraph.js +43 -2
  41. package/dist/src/stategraph/stategraph.storage.js +4 -0
  42. package/dist/src/stategraph/types.d.ts +5 -0
  43. package/dist/src/types.d.ts +42 -7
  44. package/dist/src/types.js +8 -7
  45. package/dist/src/usecase.js +1 -1
  46. package/dist/src/utils.js +28 -4
  47. package/package.json +9 -7
  48. package/dist/src/execute.d.ts +0 -63
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DummyWritable = void 0;
4
+ exports.sendFeedback = sendFeedback;
5
+ exports.normalizeOptionsForResponses = normalizeOptionsForResponses;
6
+ exports.normalizedFunctionCallFromResponse = normalizedFunctionCallFromResponse;
7
+ exports.normalizeOutputFromResponses = normalizeOutputFromResponses;
8
+ exports.convertMessagesToResponsesInput = convertMessagesToResponsesInput;
9
+ //
10
+ // Writable vide pour les tests
11
+ exports.DummyWritable = {
12
+ write: () => { }
13
+ };
14
+ //
15
+ // Envoi de feedback temps réel vers l'UX
16
+ // Informe l'utilisateur de l'état de l'analyse : agent actif, description, usage, state
17
+ function sendFeedback(params) {
18
+ const { agent, stdout, description, usage, state, verbose } = params;
19
+ const feedback = {
20
+ agent,
21
+ description,
22
+ usage,
23
+ state
24
+ };
25
+ // if(verbose) {
26
+ // console.log('--- DBG sendFeedback:',agent, description || '--', state);
27
+ // }
28
+ //
29
+ // send agent state and description
30
+ stdout.write(`\n<step>${JSON.stringify(feedback)}</step>\n`);
31
+ }
32
+ /**
33
+ * Normalise les options pour l'API Responses
34
+ *
35
+ * Transforme les paramètres du format Chat Completions vers Responses API:
36
+ * - Tools: { type: "function", function: {...} } → { type: "function", name, description, parameters, strict }
37
+ * - Autres transformations futures si nécessaire
38
+ *
39
+ * @param options - Options à normaliser
40
+ * @returns Options normalisées pour Responses API
41
+ */
42
+ function normalizeOptionsForResponses(options) {
43
+ const normalized = { ...options };
44
+ // Transformer les tools au format Responses API
45
+ if (normalized.tools && normalized.tools.length > 0) {
46
+ normalized.tools = normalized.tools.map((tool) => {
47
+ if (tool.type === 'function' && tool.function) {
48
+ // Format Chat Completions → Responses API
49
+ return {
50
+ type: 'function',
51
+ name: tool.function.name,
52
+ description: tool.function.description,
53
+ parameters: tool.function.parameters,
54
+ strict: tool.function.strict ?? true
55
+ };
56
+ }
57
+ // Déjà au bon format ou type différent
58
+ return tool;
59
+ });
60
+ }
61
+ return normalized;
62
+ }
63
+ /**
64
+ * Normalise un function call du format Responses vers Chat Completions
65
+ *
66
+ * Responses API: { type: "function_call", name, arguments, call_id }
67
+ * Chat Completions: { id, type: "function", function: { name, arguments } }
68
+ *
69
+ * Si déjà au format Chat Completions (avec .function), retourne tel quel
70
+ *
71
+ * @param functionCall - Function call au format Responses ou Chat Completions
72
+ * @returns Function call au format Chat Completions
73
+ */
74
+ function normalizedFunctionCallFromResponse(functionCall) {
75
+ // Si déjà au format Chat Completions, retourner tel quel
76
+ if (functionCall.function) {
77
+ return functionCall;
78
+ }
79
+ // Transformer du format Responses vers Chat Completions
80
+ return {
81
+ id: functionCall.call_id,
82
+ type: "function",
83
+ function: {
84
+ name: functionCall.name,
85
+ arguments: functionCall.arguments
86
+ }
87
+ };
88
+ }
89
+ /**
90
+ * Normalise l'output de l'API Responses vers le format Chat Completions
91
+ *
92
+ * Transforme la structure Responses API vers le format attendu par le reste du code
93
+ *
94
+ * Format Responses API (result.output array):
95
+ * - { type: "reasoning", summary: [] }
96
+ * - { type: "message", content: [{ type: "output_text", text: "..." }] }
97
+ * - { type: "function_call", name, arguments, call_id }
98
+ *
99
+ * Format Chat Completions compatible:
100
+ * - { choices: [{ message: { content, tool_calls, reasoning_text } }], usage: {...}, id: "..." }
101
+ *
102
+ * @param result - Résultat de stream.finalResponse()
103
+ * @returns Résultat au format Chat Completions avec reasoning_text si présent
104
+ */
105
+ function normalizeOutputFromResponses(result) {
106
+ let text = '';
107
+ let reasoningText = ''; // ✅ Capturer reasoning_text pour modèles reasoning (o-series, gpt-5)
108
+ const functionCalls = [];
109
+ // Parcourir les output items pour extraire text, reasoning et function_calls
110
+ if (result.output && Array.isArray(result.output)) {
111
+ for (const item of result.output) {
112
+ // Function calls sont directement dans output
113
+ if (item.type === 'function_call') {
114
+ functionCalls.push(item);
115
+ }
116
+ // ✅ Capturer reasoning (v5.22.0)
117
+ else if (item.type === 'reasoning' && item.summary) {
118
+ reasoningText = Array.isArray(item.summary) ? item.summary.join('\n') : String(item.summary);
119
+ }
120
+ // Messages contiennent le texte
121
+ else if (item.type === 'message' && item.content) {
122
+ for (const content of item.content) {
123
+ if (content.type === 'output_text' && content.text) {
124
+ text += content.text;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ // Transformer tous les function_calls vers le format Chat Completions
131
+ const toolCalls = functionCalls.map(normalizedFunctionCallFromResponse);
132
+ // Retourner au format Chat Completions avec reasoning_text optionnel
133
+ return {
134
+ choices: [{
135
+ message: {
136
+ tool_calls: toolCalls,
137
+ content: text,
138
+ reasoning_text: reasoningText || undefined // ✅ Optionnel, seulement si présent
139
+ }
140
+ }],
141
+ usage: {
142
+ prompt_tokens: result.usage?.input_tokens || 0,
143
+ completion_tokens: result.usage?.output_tokens || 0,
144
+ total_tokens: (result.usage?.input_tokens || 0) + (result.usage?.output_tokens || 0)
145
+ },
146
+ id: result.id
147
+ };
148
+ }
149
+ /**
150
+ * Convertit les messages du stateGraph vers le format Responses API input
151
+ * Gère les messages normaux, tool_calls (Chat Completions), et function_call_output (Responses)
152
+ *
153
+ * Utilisée par:
154
+ * - readCompletionsStream (responses.ts) pour le follow-up après tool calls
155
+ * - executeAgentSet (responses.ts) pour préparer l'input initial
156
+ *
157
+ * @param messages Messages du stateGraph
158
+ * @returns Array d'items au format Responses API input
159
+ */
160
+ function convertMessagesToResponsesInput(messages) {
161
+ return messages.flatMap((m) => {
162
+ // ✅ Messages function_call_output ont un format différent (pas de role)
163
+ if (m.type === 'function_call_output') {
164
+ return [{ type: m.type, call_id: m.call_id, output: m.output }];
165
+ }
166
+ // ✅ Messages avec tool_calls → convertir vers le format Responses API
167
+ // Documentation: "input_list += response.output" ajoute reasoning + function_calls
168
+ if (m.tool_calls && m.tool_calls.length > 0) {
169
+ const result = [];
170
+ // Ajouter le message assistant si content présent
171
+ if (m.content) {
172
+ result.push({ role: m.role, content: m.content });
173
+ }
174
+ // Convertir chaque tool_call (Chat Completions) en function_call (Responses)
175
+ for (const tc of m.tool_calls) {
176
+ result.push({
177
+ type: "function_call",
178
+ call_id: tc.id,
179
+ name: tc.function.name,
180
+ arguments: tc.function.arguments
181
+ });
182
+ }
183
+ return result;
184
+ }
185
+ // Messages normaux avec role
186
+ return [{ role: m.role, content: m.content }];
187
+ });
188
+ }
package/dist/src/index.js CHANGED
@@ -24,7 +24,7 @@ __exportStar(require("./utils"), exports);
24
24
  __exportStar(require("./types"), exports);
25
25
  // StateGraph - nouvelle architecture de gestion des discussions
26
26
  __exportStar(require("./stategraph"), exports);
27
- // Execute
27
+ // Execute (avec feature toggle legacy/responses)
28
28
  __exportStar(require("./execute"), exports);
29
29
  __exportStar(require("./pricing.llm"), exports);
30
30
  // Scrapper
@@ -8,7 +8,7 @@ type ModelPricing = {
8
8
  export declare const modelPricing: Record<string, ModelPricing>;
9
9
  export declare function calculateCost(model: string, usage?: CompletionUsage): number;
10
10
  export declare function accumulateCost(currentUsage: Usage, model: string, usage?: CompletionUsage): number;
11
- export declare function LLM(openai: any): any;
11
+ export declare function LLM(openai: any, forceThinking?: boolean): any;
12
12
  export declare const LLMxai: any;
13
13
  /**
14
14
  * Get model mapping for OpenAI
@@ -22,6 +22,7 @@ exports.modelPricing = {
22
22
  "gpt-4o-mini-search-preview": { input: 0.0000015, cachedInput: 0.00000075, output: 0.000006 },
23
23
  // GPT-5 family
24
24
  "gpt-5": { input: 0.00000125, output: 0.00001 },
25
+ "gpt-5.1": { input: 0.00000125, output: 0.00001 },
25
26
  "gpt-5-mini": { input: 0.00000025, output: 0.000002 },
26
27
  "gpt-5-nano": { input: 0.00000005, output: 0.0000004 },
27
28
  "o1": { input: 0.000015, cachedInput: 0.0000075, output: 0.00006 },
@@ -62,10 +63,14 @@ function accumulateCost(currentUsage, model, usage) {
62
63
  // depending on the API source, return the correct mapping between ALIAS and destination models and options.
63
64
  // - LOW-fast: openai => gpt-5-nano, xAI => grok-4-nano
64
65
  // - etc.
65
- function LLM(openai) {
66
+ function LLM(openai, forceThinking) {
66
67
  //
67
- // Detect provider based on baseURL
68
+ // Detect provider based on baseURL
68
69
  const mapping = openai?.baseURL ? LLMmapping[openai?.baseURL] : LLMmapping["default"];
70
+ // FIXME: this is a temporary solution to force reasoning effort to high if thinking is enabled
71
+ if (mapping.reasoning_effort && forceThinking) {
72
+ mapping.reasoning_effort = "high";
73
+ }
69
74
  return mapping;
70
75
  }
71
76
  exports.LLMxai = {
@@ -79,6 +84,11 @@ exports.LLMxai = {
79
84
  model: "grok-4-fast-non-reasoning", // Fast non-reasoning model
80
85
  stream: true
81
86
  },
87
+ "LOW-medium": {
88
+ temperature: 0.2,
89
+ model: "grok-4-fast-non-reasoning", // Fast non-reasoning model
90
+ stream: true
91
+ },
82
92
  "MEDIUM-fast": {
83
93
  temperature: 0.2,
84
94
  model: "grok-4-fast-reasoning", // Fast reasoning model
@@ -151,20 +161,20 @@ exports.LLMopenai = {
151
161
  frequency_penalty: 0.0,
152
162
  presence_penalty: 0.0,
153
163
  model: "gpt-5-nano",
154
- reasoning_effort: "medium",
164
+ reasoning_effort: "low",
155
165
  verbosity: "low",
156
166
  stream: true
157
167
  },
158
- "MEDIUM-fast": {
168
+ "LOW-medium": {
159
169
  temperature: 1,
160
170
  frequency_penalty: 0.0,
161
171
  presence_penalty: 0.0,
162
- model: "gpt-5-mini",
163
- reasoning_effort: "minimal",
172
+ model: "gpt-5-nano",
173
+ reasoning_effort: "medium",
164
174
  verbosity: "low",
165
175
  stream: true
166
176
  },
167
- "LOW-4.1": {
177
+ "LOW-4fast": {
168
178
  temperature: .2,
169
179
  frequency_penalty: 0.0,
170
180
  presence_penalty: 0.0,
@@ -185,6 +195,15 @@ exports.LLMopenai = {
185
195
  model: "gpt-4.1",
186
196
  stream: true
187
197
  },
198
+ "MEDIUM-fast": {
199
+ temperature: 1,
200
+ frequency_penalty: 0.0,
201
+ presence_penalty: 0.0,
202
+ model: "gpt-5-mini",
203
+ reasoning_effort: "minimal",
204
+ verbosity: "low",
205
+ stream: true
206
+ },
188
207
  "MEDIUM": {
189
208
  temperature: 1,
190
209
  frequency_penalty: 0.0,
@@ -195,14 +214,14 @@ exports.LLMopenai = {
195
214
  stream: true
196
215
  },
197
216
  "HIGH-fast": {
198
- model: "gpt-5",
199
- reasoning_effort: "minimal",
217
+ model: "gpt-5.1",
218
+ reasoning_effort: "none",
200
219
  verbosity: "low",
201
220
  temperature: 1,
202
221
  stream: true
203
222
  },
204
- "HIGH-low": {
205
- model: "gpt-5",
223
+ "HIGH": {
224
+ model: "gpt-5.1",
206
225
  reasoning_effort: "low",
207
226
  verbosity: "low",
208
227
  stream: true
@@ -214,17 +233,19 @@ exports.LLMopenai = {
214
233
  stream: true
215
234
  },
216
235
  "SEARCH": {
217
- model: "gpt-4o-mini-search-preview",
218
- web_search_options: {
219
- user_location: {
220
- type: "approximate",
221
- approximate: {
236
+ model: "gpt-5-mini",
237
+ reasoning: { effort: "low" },
238
+ tools: [
239
+ {
240
+ type: "web_search_preview",
241
+ user_location: {
242
+ type: "approximate",
222
243
  country: "CH",
223
244
  city: "Geneva",
224
245
  region: "Geneva",
225
246
  },
226
- },
227
- },
247
+ }
248
+ ],
228
249
  },
229
250
  };
230
251
  const LLMmapping = {
@@ -190,25 +190,31 @@ class Embeddings {
190
190
  // Sauvegarder l'ancienne config pour rollback en cas d'erreur
191
191
  const oldConfig = this.config;
192
192
  const oldVectorsFile = this.vectorsFile;
193
+ console.log(`\n 🔧 Embeddings.update() appelé`);
194
+ console.log(` Ancien baseDir: ${oldConfig.baseDir}`);
195
+ console.log(` Nouveau baseDir: ${newConfig.baseDir}`);
193
196
  try {
194
197
  // Mettre à jour la configuration
195
198
  this.config = newConfig;
196
199
  this.vectorsFile = path_1.default.join(newConfig.baseDir, types_1.RAG_FILES.VECTORS);
197
200
  this.space = newConfig.dimensions || 1536;
198
201
  this.distance = newConfig.distance || 'cosine';
202
+ console.log(` Nouveau vectorsFile: ${this.vectorsFile}`);
203
+ console.log(` Fichier existe: ${(0, fs_1.existsSync)(this.vectorsFile)}`);
199
204
  // Recharger l'index si ce n'est pas en mode inmemory
200
205
  if (!this.inmemory) {
201
206
  // Vérifier que les fichiers existent
202
207
  if (!(0, fs_1.existsSync)(this.vectorsFile)) {
203
208
  throw new Error(`Fichier d'index manquant: ${this.vectorsFile}`);
204
209
  }
210
+ console.log(` 📥 Rechargement de l'index...`);
205
211
  // Recharger l'index
206
212
  this.loadIndex();
213
+ console.log(` 📥 Rechargement des métadonnées et mapping...`);
207
214
  // Recharger les métadonnées et le mapping
208
215
  this.loadMetadata();
209
216
  this.loadMapping();
210
- if (this.debug)
211
- console.log(`✅ Embedding mis à jour avec nouvelle config: ${newConfig.baseDir}`);
217
+ console.log(` ✅ Embedding mis à jour avec nouvelle config: ${newConfig.baseDir}`);
212
218
  }
213
219
  }
214
220
  catch (error) {
@@ -231,31 +231,40 @@ class RAGManager {
231
231
  */
232
232
  notifyUpdate(name, opts) {
233
233
  const embeddingData = this.loadedEmbeddings.get(name);
234
+ console.log(`\n🔔 notifyUpdate('${name}') appelé`);
235
+ console.log(` Embedding en cache: ${embeddingData ? 'OUI' : 'NON'}`);
236
+ // Si l'embedding n'est pas encore chargé, rien à faire
237
+ // Il sera chargé avec la nouvelle config au prochain load()
234
238
  if (!embeddingData) {
235
- if (this.config.verbose)
236
- console.log(`ℹ️ Aucun embedding chargé pour '${name}'`);
239
+ console.log(` ⏭️ Skip: sera chargé avec la nouvelle config au prochain load()`);
240
+ return;
241
+ }
242
+ // Charger la nouvelle configuration depuis le registre
243
+ const entry = this.getEntry(name);
244
+ if (!entry) {
245
+ console.log(` ⚠️ Entry '${name}' introuvable dans le registre`);
237
246
  return;
238
247
  }
248
+ console.log(` 📁 entry.configPath: ${entry.configPath}`);
249
+ // L'embedding est chargé, forcer son rechargement via update()
250
+ // update() modifie l'instance EN PLACE, donc tous ceux qui ont une référence voient la nouvelle version
239
251
  const { embedding } = embeddingData;
240
252
  try {
241
- // Charger la nouvelle configuration
242
- const entry = this.getEntry(name);
243
- if (!entry) {
244
- throw new Error(`RAG '${name}' n'existe plus dans le registre`);
245
- }
246
253
  const newConfig = this.loadConfig(entry.configPath);
254
+ console.log(` 🔄 Appel de embedding.update() avec baseDir: ${newConfig.baseDir}`);
247
255
  // FIXME: Vérifier si l'embedding est busy avant de faire l'update
248
- // Pour l'instant, on appelle update() directement
256
+ // update() va recharger l'index, les métadonnées et le mapping depuis newConfig.baseDir
257
+ // update() modifie l'instance en place, donc tous les utilisateurs voient la nouvelle version
249
258
  embedding.update(newConfig);
250
- if (this.config.verbose) {
251
- const action = opts?.action === 'rename'
252
- ? `rename ${opts.oldFile} ${opts.newFile}`
253
- : 'rebuild';
254
- console.log(`✅ Embedding '${name}' notifié de la mise à jour (${action})`);
255
- }
259
+ // Mettre à jour le timestamp du cache
260
+ embeddingData.loadTime = Date.now();
261
+ console.log(` Embedding rechargé avec succès`);
262
+ console.log(` ✅ embedding.isReady(): ${embedding.isReady()}`);
256
263
  }
257
264
  catch (error) {
258
- console.error(`❌ Erreur lors de la notification d'update pour '${name}':`, error);
265
+ console.error(` ❌ Erreur lors de la mise à jour de l'embedding '${name}':`, error);
266
+ // En cas d'erreur, update() a déjà fait un rollback de sa propre config
267
+ // L'instance continue de fonctionner avec l'ancienne config
259
268
  // Ne pas throw pour éviter de bloquer le flow
260
269
  }
261
270
  }
@@ -777,6 +786,9 @@ class RAGManager {
777
786
  this.cachedRegistry = { ...registry };
778
787
  this.saveRegistry(registry);
779
788
  console.log(`✅ RAG '${name}' construit avec succès`);
789
+ console.log(` 📁 configPath final: ${entry.configPath}`);
790
+ console.log(` 📦 Embeddings chargés en cache: ${this.loadedEmbeddings.size}`);
791
+ console.log(` 🔍 Embedding '${name}' dans le cache: ${this.loadedEmbeddings.has(name) ? 'OUI' : 'NON'}`);
780
792
  // Notifier les embeddings chargés de se mettre à jour
781
793
  this.notifyUpdate(name);
782
794
  }
@@ -82,15 +82,34 @@ class GitE2EHelper {
82
82
  await fs_1.promises.mkdir(uploadDir, { recursive: true });
83
83
  // Initialiser le repository Git
84
84
  await this.git.init();
85
+ // FIXME: remove this hack that wait for the .git directory to be created
86
+ // ✅ Attendre que le répertoire .git soit complètement créé
87
+ const gitDir = (0, path_1.join)(this.tempRepoPath, '.git');
88
+ // let attempts = 0;
89
+ // while (!existsSync(gitDir) && attempts < 50) {
90
+ // await new Promise(resolve => setTimeout(resolve, 10));
91
+ // attempts++;
92
+ // }
93
+ if (!(0, fs_1.existsSync)(gitDir)) {
94
+ throw new Error('.git directory not created after git init');
95
+ }
85
96
  // Configuration Git de base
86
97
  await this.git.addConfig('user.name', 'E2E Test');
87
98
  await this.git.addConfig('user.email', 'e2e@test.com');
88
99
  await this.git.addConfig('init.defaultBranch', this.config.mainBranch);
89
100
  // Créer la branche main directement
90
101
  await this.git.checkout(['-b', this.config.mainBranch]);
91
- // Créer un commit initial
102
+ // Créer un commit initial avec un README.md valide (avec front-matter pour les tests withID)
92
103
  const readmePath = (0, path_1.join)(this.tempRepoPath, 'README.md');
93
- await fs_1.promises.writeFile(readmePath, '# E2E Test Repository\n\nTemporary repository for E2E testing.');
104
+ const readmeContent = `---
105
+ id: 1000
106
+ title: E2E Test Repository
107
+ ---
108
+
109
+ # E2E Test Repository
110
+
111
+ Temporary repository for E2E testing.`;
112
+ await fs_1.promises.writeFile(readmePath, readmeContent);
94
113
  await this.git.add('README.md');
95
114
  await this.git.commit('Initial commit');
96
115
  // Créer la branche draft à partir de main
@@ -1,4 +1,4 @@
1
- import { RulesGitConfig, GitHealthStatus } from '../types';
1
+ import { RulesGitConfig, GitHealthStatus, GitPrNoteMigrationReport } from '../types';
2
2
  /**
3
3
  * Rapport d'analyse des IDs
4
4
  */
@@ -73,8 +73,10 @@ export declare class GitHealthManager {
73
73
  * - Notes orphelines sur anciens commits → copie vers HEAD + suppression
74
74
  * - Branches sans historique de notes → ignore silencieusement
75
75
  * - Erreurs de traitement par branche → continue avec les autres
76
+ *
77
+ * @returns GitPrNoteMigrationReport avec le nombre de notes migrées, déjà OK, et perdues
76
78
  */
77
- migrateNotes(): Promise<void>;
79
+ migrateNotes(): Promise<GitPrNoteMigrationReport>;
78
80
  /**
79
81
  * Analyse les IDs des documents Markdown dans une branche
80
82
  *
@@ -31,8 +31,7 @@ class GitHealthManager {
31
31
  await this.git.checkout(branch);
32
32
  }
33
33
  catch (checkoutError) {
34
- // Si checkout normal échoue, forcer le checkout
35
- console.log(`🔧 Checkout forcé vers ${branch}...`, checkoutError);
34
+ // Si checkout normal échoue, continuer (forceRepair gérera l'erreur)
36
35
  }
37
36
  try {
38
37
  // Use the robust health diagnostic function
@@ -142,12 +141,14 @@ class GitHealthManager {
142
141
  const draftHealth = await (0, git_1.gitGetBranchHealth)(this.git, draftBranch);
143
142
  results.push(draftHealth);
144
143
  this.displayHealthSummary(draftHealth);
145
- // Check all validation branches
144
+ // Check all validation branches with optimized function
146
145
  const allBranches = await (0, git_1.gitGetAllBranches)(this.git);
147
146
  const validationBranches = allBranches.filter(b => b.startsWith(validationPrefix));
148
147
  if (validationBranches.length > 0) {
148
+ console.log(`🔍 Diagnostic optimisé de ${validationBranches.length} branche(s) de validation...`);
149
149
  for (const branch of validationBranches) {
150
- const health = await (0, git_1.gitGetBranchHealth)(this.git, branch);
150
+ // OPTIMISATION: Utiliser la version légère pour les validations
151
+ const health = await (0, git_1.gitGetValidationBranchHealth)(this.git, branch, this.config);
151
152
  results.push(health);
152
153
  this.displayHealthSummary(health);
153
154
  }
@@ -294,20 +295,23 @@ class GitHealthManager {
294
295
  * - Notes orphelines sur anciens commits → copie vers HEAD + suppression
295
296
  * - Branches sans historique de notes → ignore silencieusement
296
297
  * - Erreurs de traitement par branche → continue avec les autres
298
+ *
299
+ * @returns GitPrNoteMigrationReport avec le nombre de notes migrées, déjà OK, et perdues
297
300
  */
298
301
  async migrateNotes() {
299
302
  const { mainBranch, validationPrefix, gitNotes } = this.config;
300
303
  if (!validationPrefix || !gitNotes.namespace) {
301
304
  console.error("Erreur: Le préfixe de validation ou le namespace des notes n'est pas configuré.");
302
- return;
305
+ return { migrated: 0, alreadyOk: 0, lost: [] };
303
306
  }
304
307
  const allBranches = await (0, git_1.gitGetAllBranches)(this.git);
305
308
  const validationBranches = allBranches.filter(b => b.startsWith(validationPrefix));
306
309
  if (validationBranches.length === 0) {
307
- return;
310
+ return { migrated: 0, alreadyOk: 0, lost: [] };
308
311
  }
309
312
  let migratedCount = 0;
310
313
  let alreadyOkCount = 0;
314
+ const lostNotes = [];
311
315
  for (const branch of validationBranches) {
312
316
  try {
313
317
  const lastCommit = (await this.git.revparse(branch)).trim();
@@ -325,12 +329,20 @@ class GitHealthManager {
325
329
  }
326
330
  const revListOutput = await this.git.raw('rev-list', `${mergeBase}..${branch}`);
327
331
  const branchCommits = revListOutput.split('\n').filter(Boolean);
332
+ // Extract PR number from branch name (e.g., rule-validation-31 -> 31)
333
+ const prNumber = parseInt(branch.split('-').pop() || '0', 10);
328
334
  let oldNoteCommit = null;
329
335
  for (const commitHash of branchCommits) {
330
336
  const note = await (0, git_1.gitReadNote)(this.git, commitHash, gitNotes.namespace, 1);
331
337
  if (note) {
332
- oldNoteCommit = commitHash;
333
- break; // Found the first note, stop searching
338
+ // CRITICAL FIX: Verify the note belongs to this PR before migrating
339
+ if (note.id === prNumber) {
340
+ oldNoteCommit = commitHash;
341
+ break; // Found the correct note for this PR
342
+ }
343
+ else {
344
+ console.log(` ⚠️ ${branch}: Note trouvée avec ID=${note.id}, attendu ID=${prNumber} - ignorée`);
345
+ }
334
346
  }
335
347
  }
336
348
  // 3. If a note was found on an old commit, migrate it
@@ -341,6 +353,11 @@ class GitHealthManager {
341
353
  console.log(`✅ ${branch}: Migration terminée`);
342
354
  migratedCount++;
343
355
  }
356
+ else {
357
+ // CRITICAL: Note perdue pour cette validation
358
+ console.error(`🚨 ${branch}: NOTE PERDUE - PR ${prNumber} n'a plus de note dans son historique`);
359
+ lostNotes.push({ branch, prNumber });
360
+ }
344
361
  }
345
362
  catch (error) {
346
363
  console.error(`❌ ${branch}: Erreur lors de la migration:`, error);
@@ -348,9 +365,14 @@ class GitHealthManager {
348
365
  }
349
366
  }
350
367
  // Summary only if there was work to do
351
- if (migratedCount > 0 || alreadyOkCount !== validationBranches.length) {
352
- console.log(`✅ Migration des notes terminée: ${migratedCount} migrées, ${alreadyOkCount} déjà OK`);
368
+ if (migratedCount > 0 || alreadyOkCount !== validationBranches.length || lostNotes.length > 0) {
369
+ console.log(`✅ Migration des notes terminée: ${migratedCount} migrées, ${alreadyOkCount} déjà OK${lostNotes.length > 0 ? `, ${lostNotes.length} perdues 🚨` : ''}`);
353
370
  }
371
+ return {
372
+ migrated: migratedCount,
373
+ alreadyOk: alreadyOkCount,
374
+ lost: lostNotes
375
+ };
354
376
  }
355
377
  /**
356
378
  * Analyse les IDs des documents Markdown dans une branche
@@ -428,9 +450,9 @@ class GitHealthManager {
428
450
  else {
429
451
  hasValidID = true;
430
452
  // Enregistrer l'ID existant dans le registry
431
- // ✅ Passer branch et file pour vérifier si l'ID appartient au même fichier
453
+ // ✅ gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement si matter est fourni
432
454
  try {
433
- (0, git_1.gitRegisterExistingID)(matter.id, branchName, file, this.config);
455
+ (0, git_1.gitRegisterExistingID)(matter, branchName, file, this.config);
434
456
  }
435
457
  catch (error) {
436
458
  if (error.code === 'id_already_used') {
@@ -548,9 +570,25 @@ class GitHealthManager {
548
570
  // Parser le matter complet
549
571
  const parsed = (0, utils_matter_1.matterParse)(fileData.content);
550
572
  const { matter: fullMatter, content } = parsed;
551
- // Générer un nouvel ID
552
- fullMatter.id = (0, git_1.gitGenerateNextID)(this.config);
553
- console.log(` 🔧 ${file}: Génération d'un nouvel ID: ${fullMatter.id}`);
573
+ // Générer un nouvel ID si manquant
574
+ if (!fullMatter.id || typeof fullMatter.id !== 'number' || fullMatter.id <= 999) {
575
+ fullMatter.id = (0, git_1.gitGenerateNextID)(this.config);
576
+ console.log(` 🔧 ${file}: Génération d'un nouvel ID: ${fullMatter.id}`);
577
+ }
578
+ // Générer un titre si manquant (basé sur le nom du fichier ou le premier titre de section)
579
+ if (!fullMatter.title || fullMatter.title.trim() === '') {
580
+ // Essayer d'extraire le premier titre de section (# Title)
581
+ const titleMatch = content.match(/^#\s+(.+)$/m);
582
+ if (titleMatch) {
583
+ fullMatter.title = titleMatch[1].trim();
584
+ }
585
+ else {
586
+ // Sinon, utiliser le nom du fichier sans extension
587
+ const fileName = file.replace(/\.md$/, '').replace(/[-_]/g, ' ');
588
+ fullMatter.title = fileName.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
589
+ }
590
+ console.log(` 🔧 ${file}: Génération d'un titre: ${fullMatter.title}`);
591
+ }
554
592
  // Re-sérialiser et sauvegarder
555
593
  const updatedContent = (0, utils_matter_1.matterSerialize)(content, fullMatter);
556
594
  // ✅ IMPORTANT: Désactiver withID car l'ID est déjà généré et enregistré
@@ -618,6 +656,10 @@ class GitHealthManager {
618
656
  if (autoFix && (report.missingID.length > 0 || report.missingTitle.length > 0 || report.invalidID.length > 0 || report.duplicateID.length > 0)) {
619
657
  finalReport = await this.repairSafeIDs(report, { dryRun });
620
658
  }
659
+ // Calculer le nombre de fichiers réparés
660
+ // C'est la différence entre les problèmes avant et après réparation
661
+ const fixed = (report.missingID.length + report.missingTitle.length + report.invalidID.length + report.duplicateID.length) -
662
+ (finalReport.missingID.length + finalReport.missingTitle.length + finalReport.invalidID.length + finalReport.duplicateID.length);
621
663
  // Retourner dans l'ancien format pour compatibilité
622
664
  return {
623
665
  scanned: finalReport.scanned,
@@ -626,7 +668,7 @@ class GitHealthManager {
626
668
  missingTitle: finalReport.missingTitle.length,
627
669
  invalidID: finalReport.invalidID.length,
628
670
  duplicateID: finalReport.duplicateID.length,
629
- fixed: report.scanned - finalReport.scanned + finalReport.valid - report.valid,
671
+ fixed: Math.max(0, fixed), // S'assurer que fixed n'est pas négatif
630
672
  errors: finalReport.errors
631
673
  };
632
674
  }
@@ -1,5 +1,5 @@
1
1
  export { lock, unlock, gitLoad, isValidInt, gitIsFileMerged, gitLastCommit, gitListFilesInBranch, gitListFilesOutsideRepo, gitFileExistsInBranch, gitGetFilesSummary, gitGetFileContent, gitGetFilePreview, gitGetFileHistory, gitReadFileOutsideRepo, gitGetUnmergedBranchesForFile, gitGetAllBranches, gitGetDiffFiles, gitReadNote, gitWriteNote, gitDeleteNote, } from './repo.tools';
2
- export { gitInit, gitEnsureRepositoryConfiguration, gitEnsureRemoteConfiguration, gitSetupRepository, gitShowConfiguration, gitCheckConfiguration, gitGetBranchHealth, gitCreateOrEditFile, gitEditFile, gitRenameFile, gitDeleteFile, gitGenerateNextID, gitReloadIDRegistry, gitRegisterExistingID, gitEnsureMatterID, gitFileStrictMatter, gitIDRegistryExists, gitIDRegistryRename, } from './repo';
2
+ export { gitInit, gitEnsureRepositoryConfiguration, gitEnsureRemoteConfiguration, gitSetupRepository, gitShowConfiguration, gitCheckConfiguration, gitGetBranchHealth, gitGetValidationBranchHealth, gitCreateOrEditFile, gitEditFile, gitRenameFile, gitDeleteFile, gitGenerateNextID, gitReloadIDRegistry, gitRegisterExistingID, gitEnsureMatterID, gitFileStrictMatter, gitIDRegistryExists, gitIDRegistryRename, } from './repo';
3
3
  export { gitSyncPR, gitIsPRClosed, gitIsPRClosedRobust, gitGetPRMetadata, gitGetAllPR, gitGetClosedPRs, gitLoadPR, gitPRUpdateComments, gitClosePR, gitClosePRRobust, gitGetNextPRNumber, gitNewValidationRequest, gitNewPR, } from './repo.pr';
4
4
  export * from './git.e2e.helper';
5
5
  export * from './git.health';