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
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.gitNewValidationRequest = exports.gitGetNextPRNumber = exports.gitClosePRRobust = exports.gitClosePR = exports.gitPRUpdateComments = exports.gitLoadPR = exports.gitGetClosedPRs = exports.gitGetAllPR = exports.gitGetPRMetadata = exports.gitIsPRClosedRobust = exports.gitIsPRClosed = exports.gitSyncPR = exports.gitIDRegistryRename = exports.gitIDRegistryExists = exports.gitFileStrictMatter = exports.gitEnsureMatterID = exports.gitRegisterExistingID = exports.gitReloadIDRegistry = exports.gitGenerateNextID = exports.gitDeleteFile = exports.gitRenameFile = exports.gitEditFile = exports.gitCreateOrEditFile = exports.gitGetBranchHealth = exports.gitCheckConfiguration = exports.gitShowConfiguration = exports.gitSetupRepository = exports.gitEnsureRemoteConfiguration = exports.gitEnsureRepositoryConfiguration = exports.gitInit = exports.gitDeleteNote = exports.gitWriteNote = exports.gitReadNote = exports.gitGetDiffFiles = exports.gitGetAllBranches = exports.gitGetUnmergedBranchesForFile = exports.gitReadFileOutsideRepo = exports.gitGetFileHistory = exports.gitGetFilePreview = exports.gitGetFileContent = exports.gitGetFilesSummary = exports.gitFileExistsInBranch = exports.gitListFilesOutsideRepo = exports.gitListFilesInBranch = exports.gitLastCommit = exports.gitIsFileMerged = exports.isValidInt = exports.gitLoad = exports.unlock = exports.lock = void 0;
18
- exports.gitNewPR = void 0;
17
+ exports.gitGetNextPRNumber = exports.gitClosePRRobust = exports.gitClosePR = exports.gitPRUpdateComments = exports.gitLoadPR = exports.gitGetClosedPRs = exports.gitGetAllPR = exports.gitGetPRMetadata = exports.gitIsPRClosedRobust = exports.gitIsPRClosed = exports.gitSyncPR = exports.gitIDRegistryRename = exports.gitIDRegistryExists = exports.gitFileStrictMatter = exports.gitEnsureMatterID = exports.gitRegisterExistingID = exports.gitReloadIDRegistry = exports.gitGenerateNextID = exports.gitDeleteFile = exports.gitRenameFile = exports.gitEditFile = exports.gitCreateOrEditFile = exports.gitGetValidationBranchHealth = exports.gitGetBranchHealth = exports.gitCheckConfiguration = exports.gitShowConfiguration = exports.gitSetupRepository = exports.gitEnsureRemoteConfiguration = exports.gitEnsureRepositoryConfiguration = exports.gitInit = exports.gitDeleteNote = exports.gitWriteNote = exports.gitReadNote = exports.gitGetDiffFiles = exports.gitGetAllBranches = exports.gitGetUnmergedBranchesForFile = exports.gitReadFileOutsideRepo = exports.gitGetFileHistory = exports.gitGetFilePreview = exports.gitGetFileContent = exports.gitGetFilesSummary = exports.gitFileExistsInBranch = exports.gitListFilesOutsideRepo = exports.gitListFilesInBranch = exports.gitLastCommit = exports.gitIsFileMerged = exports.isValidInt = exports.gitLoad = exports.unlock = exports.lock = void 0;
18
+ exports.gitNewPR = exports.gitNewValidationRequest = void 0;
19
19
  // === OPÉRATIONS GIT BAS NIVEAU ===
20
20
  // Fonctions atomiques pour manipuler Git directement
21
21
  var repo_tools_1 = require("./repo.tools");
@@ -53,6 +53,7 @@ Object.defineProperty(exports, "gitSetupRepository", { enumerable: true, get: fu
53
53
  Object.defineProperty(exports, "gitShowConfiguration", { enumerable: true, get: function () { return repo_1.gitShowConfiguration; } });
54
54
  Object.defineProperty(exports, "gitCheckConfiguration", { enumerable: true, get: function () { return repo_1.gitCheckConfiguration; } });
55
55
  Object.defineProperty(exports, "gitGetBranchHealth", { enumerable: true, get: function () { return repo_1.gitGetBranchHealth; } });
56
+ Object.defineProperty(exports, "gitGetValidationBranchHealth", { enumerable: true, get: function () { return repo_1.gitGetValidationBranchHealth; } });
56
57
  Object.defineProperty(exports, "gitCreateOrEditFile", { enumerable: true, get: function () { return repo_1.gitCreateOrEditFile; } });
57
58
  Object.defineProperty(exports, "gitEditFile", { enumerable: true, get: function () { return repo_1.gitEditFile; } });
58
59
  Object.defineProperty(exports, "gitRenameFile", { enumerable: true, get: function () { return repo_1.gitRenameFile; } });
@@ -1,5 +1,14 @@
1
1
  import { SimpleGit } from 'simple-git';
2
2
  import { RulesGitConfig, GitCommitHistory, RuleUser, GitHealthStatus } from '../types';
3
+ /**
4
+ * Structure du cache matter pour un fichier
5
+ */
6
+ interface MatterStrict {
7
+ id?: number;
8
+ title?: string;
9
+ service?: string;
10
+ oldfile?: string;
11
+ }
3
12
  /**
4
13
  * Vérifie si le fichier de registre d'IDs existe dans le repository Git
5
14
  *
@@ -52,7 +61,7 @@ export declare function gitReloadIDRegistry(config?: RulesGitConfig): void;
52
61
  /**
53
62
  * Enregistre un ID existant dans le registre
54
63
  *
55
- * @param id L'ID à enregistrer
64
+ * @param matter Le matter contenant l'ID à enregistrer
56
65
  * @param branch Branche du fichier (optionnel, pour vérification de propriété)
57
66
  * @param file Nom du fichier (optionnel, pour vérification de propriété)
58
67
  * @param config Configuration Git optionnelle
@@ -60,8 +69,11 @@ export declare function gitReloadIDRegistry(config?: RulesGitConfig): void;
60
69
  *
61
70
  * **Note:** Si `branch` et `file` sont fournis, la fonction vérifie si l'ID appartient
62
71
  * déjà au même fichier. Si oui, aucune erreur n'est levée (cas de re-scan).
72
+ *
73
+ * **Note:** `setMatterCache()` est appelé automatiquement avant l'enregistrement
74
+ * pour garantir la cohérence du cache.
63
75
  */
64
- export declare function gitRegisterExistingID(id: number, branch?: string, file?: string, config?: RulesGitConfig): void;
76
+ export declare function gitRegisterExistingID(matter: MatterStrict, branch?: string, file?: string, config?: RulesGitConfig): void;
65
77
  /**
66
78
  * Renomme un fichier dans le cache du registre d'IDs
67
79
  *
@@ -84,11 +96,18 @@ export declare function gitRegisterExistingID(id: number, branch?: string, file?
84
96
  export declare function gitIDRegistryRename(oldFile: string, newFile: string, branch: string, config?: RulesGitConfig): void;
85
97
  /**
86
98
  * Valide et assure qu'un matter a un ID valide
99
+ *
100
+ * **Principe d'identité stable**: Un fichier conserve son ID à travers les branches.
101
+ * Cette fonction cherche d'abord si le fichier a déjà un ID dans le registre
102
+ * (peu importe la branche), et le réutilise pour garantir la cohérence.
103
+ *
87
104
  * @param matter Le matter à valider/compléter
88
105
  * @param config Configuration Git optionnelle
106
+ * @param branch Branche du fichier (optionnel, peut être undefined lors de createPullRequest)
107
+ * @param file Nom du fichier (requis pour chercher l'ID existant)
89
108
  * @returns Le matter mis à jour avec un ID valide
90
109
  */
91
- export declare function gitEnsureMatterID(matter: any, config?: RulesGitConfig): any;
110
+ export declare function gitEnsureMatterID(matter: MatterStrict, config?: RulesGitConfig, branch?: string, file?: string): any;
92
111
  /**
93
112
  * Lecture rapide et stricte du matter d'un fichier (id + title uniquement)
94
113
  *
@@ -243,3 +262,27 @@ export declare function gitDeleteFile(git: SimpleGit, filePath: string, branch:
243
262
  * @returns Promise<GitHealthStatus> Diagnostic complet avec liste des problèmes et recommandations de réparation
244
263
  */
245
264
  export declare function gitGetBranchHealth(git: SimpleGit, branch?: string): Promise<GitHealthStatus>;
265
+ /**
266
+ * Version optimisée de gitGetBranchHealth pour les branches de validation
267
+ *
268
+ * Cette fonction utilise une approche en cascade pour diagnostiquer rapidement
269
+ * l'état d'une branche de validation sans effectuer de checkout coûteux.
270
+ *
271
+ * Tests effectués dans l'ordre (arrêt au premier échec) :
272
+ * 1. Branche existe ? (git rev-parse --verify)
273
+ * 2. Note Git présente sur HEAD ? (gitReadNote avec maxCommit=1)
274
+ * 3. Note correspond au bon PR ? (note.id === prNumber)
275
+ *
276
+ * Avantages :
277
+ * - ✅ Pas de checkout (gain 80-90% performance)
278
+ * - ✅ Lecture directe du HEAD sans changer de branche
279
+ * - ✅ Tests cascade (arrêt dès qu'un problème est détecté)
280
+ * - ✅ Optimisé pour branches temporaires (validations)
281
+ *
282
+ * @param git Instance SimpleGit
283
+ * @param branch Nom de la branche de validation à diagnostiquer
284
+ * @param config Configuration Git optionnelle
285
+ * @returns GitHealthStatus avec diagnostic optimisé
286
+ */
287
+ export declare function gitGetValidationBranchHealth(git: SimpleGit, branch: string, config?: RulesGitConfig): Promise<GitHealthStatus>;
288
+ export {};
@@ -53,6 +53,7 @@ exports.gitRenameFile_fs = gitRenameFile_fs;
53
53
  exports.gitRenameFile = gitRenameFile;
54
54
  exports.gitDeleteFile = gitDeleteFile;
55
55
  exports.gitGetBranchHealth = gitGetBranchHealth;
56
+ exports.gitGetValidationBranchHealth = gitGetValidationBranchHealth;
56
57
  const fs_1 = require("fs");
57
58
  const errors_1 = require("../errors");
58
59
  const path_1 = require("path");
@@ -70,16 +71,32 @@ class IDRegistryService {
70
71
  static get() {
71
72
  return IDRegistryService.instance || (IDRegistryService.instance = new IDRegistryService());
72
73
  }
74
+ toString() {
75
+ return JSON.stringify(this.registry, null, 2);
76
+ }
77
+ clear() {
78
+ this.registry = null;
79
+ this.repoPath = '';
80
+ this.isDirty = false;
81
+ }
73
82
  /**
74
83
  * Initialise le service avec le chemin du repository
75
84
  */
76
85
  init(repoPath) {
77
- // Skip if already initialized
78
- if (this.registry) {
79
- return;
80
- }
81
- if (this.repoPath !== repoPath || !this.registry) {
86
+ // BUGFIX: Toujours mettre à jour repoPath même si le registre existe déjà
87
+ // Car gitReloadIDRegistry peut être appelé avec un nouveau repoPath pour un nouveau test
88
+ // et le repo précédent peut avoir été nettoyé
89
+ if (this.repoPath !== repoPath) {
82
90
  this.repoPath = repoPath;
91
+ // Si le registre existe déjà, le recharger depuis le nouveau chemin
92
+ if (this.registry) {
93
+ this.registry = this.loadFromDisk();
94
+ this.isDirty = false;
95
+ return;
96
+ }
97
+ }
98
+ // Si le registre n'existe pas encore, le créer
99
+ if (!this.registry) {
83
100
  this.registry = this.loadFromDisk();
84
101
  this.isDirty = false;
85
102
  }
@@ -172,16 +189,64 @@ class IDRegistryService {
172
189
  this.isDirty = true;
173
190
  return newID;
174
191
  }
192
+ /**
193
+ * Obtient l'ID d'un fichier (peu importe la branche)
194
+ *
195
+ * Un fichier a UN SEUL ID quelque soit la branche où il se trouve.
196
+ * Cette fonction cherche le fichier dans toutes les branches du cache.
197
+ *
198
+ * @param file Nom du fichier (ex: 'procedure.md')
199
+ * @returns L'ID du fichier ou undefined si le fichier n'a pas d'ID
200
+ */
201
+ getFileID(file) {
202
+ if (!this.registry) {
203
+ return undefined;
204
+ }
205
+ // Chercher le fichier dans toutes les branches
206
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
207
+ const [, cachedFile] = cacheKey.split(':');
208
+ if (cachedFile === file) {
209
+ return matter.id;
210
+ }
211
+ }
212
+ return undefined;
213
+ }
175
214
  /**
176
215
  * Vérifie si un ID appartient déjà à un fichier spécifique
216
+ *
217
+ * Un fichier garde son ID peu importe la branche, donc cette fonction
218
+ * cherche dans toutes les branches pour vérifier la propriété.
219
+ *
220
+ * @param id L'ID à vérifier
221
+ * @param file Nom du fichier
222
+ * @returns true si ce fichier possède cet ID
177
223
  */
178
- isIDOwnedByFile(id, branch, file) {
224
+ isIDOwnedByFile(id, file) {
179
225
  if (!this.registry) {
180
226
  return false;
181
227
  }
182
- const cacheKey = `${branch}:${file}`;
183
- const cachedMatter = this.registry.matters[cacheKey];
184
- return cachedMatter?.id === id;
228
+ // Chercher le fichier dans toutes les branches
229
+ const fileID = this.getFileID(file);
230
+ return fileID === id;
231
+ }
232
+ /**
233
+ * Trouve quel fichier possède un ID donné
234
+ *
235
+ * @param id L'ID à chercher
236
+ * @returns Le nom du fichier qui possède cet ID, ou undefined
237
+ */
238
+ getFileByID(id) {
239
+ if (!this.registry) {
240
+ return undefined;
241
+ }
242
+ // Chercher l'ID dans le cache
243
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
244
+ if (matter.id === id) {
245
+ const [, file] = cacheKey.split(':');
246
+ return file;
247
+ }
248
+ }
249
+ return undefined;
185
250
  }
186
251
  /**
187
252
  * Enregistre un ID existant
@@ -198,7 +263,8 @@ class IDRegistryService {
198
263
  // Si l'ID est déjà utilisé
199
264
  if (this.registry.used.includes(id)) {
200
265
  // Vérifier si c'est le même fichier (pas un doublon)
201
- if (branch && file && this.isIDOwnedByFile(id, branch, file)) {
266
+ // Note: isIDOwnedByFile() cherche maintenant dans TOUTES les branches
267
+ if (file && this.isIDOwnedByFile(id, file)) {
202
268
  // Même fichier → OK, pas d'erreur
203
269
  return;
204
270
  }
@@ -212,6 +278,10 @@ class IDRegistryService {
212
278
  if (id > this.registry.last) {
213
279
  this.registry.last = id;
214
280
  }
281
+ // ⚠️ IMPORTANT: Ne PAS créer d'entrée dans matters ici
282
+ // setMatterCache() DOIT être appelé AVANT registerExistingID() par l'appelant
283
+ // car registerExistingID() n'a pas le contexte complet (title, service)
284
+ // Si branch/file sont fournis mais que le cache n'existe pas, c'est une erreur de programmation
215
285
  this.isDirty = true;
216
286
  }
217
287
  /**
@@ -348,6 +418,9 @@ function gitGenerateNextID(config) {
348
418
  const gitConf = (0, repo_tools_1.gitLoad)(config);
349
419
  idRegistryService.init(gitConf.repoPath);
350
420
  const newID = idRegistryService.generateNextID();
421
+ // FIXME: Double save - gitGenerateNextID() et gitRegisterExistingID() appellent tous deux save()
422
+ // Refactorisation future: Retirer save() d'ici et le faire uniquement dans gitRegisterExistingID()
423
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
351
424
  idRegistryService.save();
352
425
  return newID;
353
426
  }
@@ -370,13 +443,16 @@ function gitGenerateNextID(config) {
370
443
  */
371
444
  function gitReloadIDRegistry(config) {
372
445
  const gitConf = (0, repo_tools_1.gitLoad)(config);
446
+ if (config?.reset) {
447
+ idRegistryService.clear();
448
+ }
373
449
  idRegistryService.init(gitConf.repoPath);
374
450
  idRegistryService.forceReload();
375
451
  }
376
452
  /**
377
453
  * Enregistre un ID existant dans le registre
378
454
  *
379
- * @param id L'ID à enregistrer
455
+ * @param matter Le matter contenant l'ID à enregistrer
380
456
  * @param branch Branche du fichier (optionnel, pour vérification de propriété)
381
457
  * @param file Nom du fichier (optionnel, pour vérification de propriété)
382
458
  * @param config Configuration Git optionnelle
@@ -384,11 +460,36 @@ function gitReloadIDRegistry(config) {
384
460
  *
385
461
  * **Note:** Si `branch` et `file` sont fournis, la fonction vérifie si l'ID appartient
386
462
  * déjà au même fichier. Si oui, aucune erreur n'est levée (cas de re-scan).
463
+ *
464
+ * **Note:** `setMatterCache()` est appelé automatiquement avant l'enregistrement
465
+ * pour garantir la cohérence du cache.
387
466
  */
388
- function gitRegisterExistingID(id, branch, file, config) {
467
+ function gitRegisterExistingID(matter, branch, file, config) {
389
468
  const gitConf = (0, repo_tools_1.gitLoad)(config);
390
469
  idRegistryService.init(gitConf.repoPath);
391
- idRegistryService.registerExistingID(id, branch, file);
470
+ // ✅ Validation: matter doit avoir id et title
471
+ if (!matter.id || !matter.title) {
472
+ throw new errors_1.GitOperationError('Le matter est invalide (id et title requis)', 'invalid_matter');
473
+ }
474
+ // ✅ IMPORTANT: Vérifier AVANT de mettre à jour le cache
475
+ // Si l'ID existe déjà, vérifier qu'il appartient au MÊME fichier
476
+ const registry = idRegistryService.getRegistry();
477
+ if (registry.used.includes(matter.id)) {
478
+ const ownerFile = idRegistryService.getFileByID(matter.id);
479
+ if (ownerFile && ownerFile !== file) {
480
+ // ID utilisé par un AUTRE fichier → Erreur
481
+ const error = new Error(`ID ${matter.id} est déjà utilisé`);
482
+ error.code = 'id_already_used';
483
+ throw error;
484
+ }
485
+ // Même fichier OU pas de fichier dans le cache → OK, continuer
486
+ }
487
+ // ✅ Mettre à jour le cache (maintenant qu'on sait que l'ID est valide)
488
+ if (branch && file) {
489
+ idRegistryService.setMatterCache(branch, file, matter);
490
+ }
491
+ // ✅ Enregistrer l'ID dans used[] si pas déjà dedans
492
+ idRegistryService.registerExistingID(matter.id, branch, file);
392
493
  idRegistryService.save();
393
494
  }
394
495
  /**
@@ -418,25 +519,61 @@ function gitIDRegistryRename(oldFile, newFile, branch, config) {
418
519
  }
419
520
  /**
420
521
  * Valide et assure qu'un matter a un ID valide
522
+ *
523
+ * **Principe d'identité stable**: Un fichier conserve son ID à travers les branches.
524
+ * Cette fonction cherche d'abord si le fichier a déjà un ID dans le registre
525
+ * (peu importe la branche), et le réutilise pour garantir la cohérence.
526
+ *
421
527
  * @param matter Le matter à valider/compléter
422
528
  * @param config Configuration Git optionnelle
529
+ * @param branch Branche du fichier (optionnel, peut être undefined lors de createPullRequest)
530
+ * @param file Nom du fichier (requis pour chercher l'ID existant)
423
531
  * @returns Le matter mis à jour avec un ID valide
424
532
  */
425
- function gitEnsureMatterID(matter, config) {
426
- // Si ID existe et est valide
533
+ function gitEnsureMatterID(matter, config, branch, file) {
534
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
535
+ idRegistryService.init(gitConf.repoPath);
536
+ // ✅ NOUVEAU: Chercher d'abord l'ID existant du fichier (peu importe la branche)
537
+ const existingFileID = file ? idRegistryService.getFileID(file) : undefined;
538
+ if (existingFileID) {
539
+ // Le fichier a déjà un ID → le réutiliser pour garantir la cohérence
540
+ matter.id = existingFileID;
541
+ // Mettre à jour le cache si branch est définie (sinon ce sera fait plus tard)
542
+ if (branch && file && matter.title) {
543
+ idRegistryService.setMatterCache(branch, file, {
544
+ id: matter.id,
545
+ title: matter.title,
546
+ service: matter.service
547
+ });
548
+ }
549
+ return matter;
550
+ }
551
+ // Si matter a un ID fourni manuellement (ex: import ou test)
427
552
  // FIXME 999 should be a constant in config
428
553
  if (matter.id && typeof matter.id === 'number' && matter.id > 999) {
429
554
  try {
430
- // Pas de branch/file car utilisé pour nouveaux fichiers ou contexte inconnu
431
- gitRegisterExistingID(matter.id, undefined, undefined, config);
555
+ // Essayer d'enregistrer cet ID
556
+ gitRegisterExistingID(matter, branch, file, config);
432
557
  return matter;
433
558
  }
434
559
  catch (error) {
435
- // ID déjà utilisé générer un nouveau (silencieux, géré par le caller)
560
+ // ID déjà utilisé par un AUTRE fichier générer un nouveau
561
+ // (Si même fichier, registerExistingID ne lève pas d'erreur)
436
562
  }
437
563
  }
438
564
  // Générer un nouvel ID
439
565
  matter.id = gitGenerateNextID(config);
566
+ // FIXME: Double save ici - gitGenerateNextID() fait save() puis gitRegisterExistingID() fait save() aussi
567
+ // Refactorisation future: Retirer save() de gitGenerateNextID() et le faire uniquement dans gitRegisterExistingID()
568
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
569
+ // ✅ Mettre à jour le cache avec le nouvel ID (seulement si branch est définie)
570
+ if (branch && file && matter.title) {
571
+ idRegistryService.setMatterCache(branch, file, {
572
+ id: matter.id,
573
+ title: matter.title,
574
+ service: matter.service
575
+ });
576
+ }
440
577
  return matter;
441
578
  }
442
579
  /**
@@ -887,19 +1024,48 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
887
1024
  if (gitConf.strictID) {
888
1025
  // Mode strict : valider que l'ID existe et est correct
889
1026
  validateMatter(parsed.matter);
1027
+ // ✅ gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement si matter est fourni
890
1028
  // Enregistrer l'ID existant
891
1029
  // ✅ Passer branch (PR) et file (filePath) pour vérifier propriété
892
- gitRegisterExistingID(parsed.matter.id, PR, filePath, gitConf);
1030
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
893
1031
  }
894
1032
  else {
895
1033
  // Mode permissif : générer l'ID si manquant
896
- parsed.matter = gitEnsureMatterID(parsed.matter, gitConf);
897
- // Re-sérialiser avec l'ID mis à jour
898
- content = matterSerialize(parsed.content, parsed.matter);
1034
+ // IMPORTANT: Ne PAS ré-assigner l'ID s'il est déjà valide
1035
+ // Le contenu a peut-être déjà été traité par gitEnsureMatterID() dans le caller
1036
+ const hasValidID = parsed.matter.id &&
1037
+ typeof parsed.matter.id === 'number' &&
1038
+ parsed.matter.id > 999;
1039
+ if (!hasValidID) {
1040
+ // Seulement si l'ID est manquant ou invalide
1041
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1042
+ // Re-sérialiser avec l'ID mis à jour
1043
+ content = matterSerialize(parsed.content, parsed.matter);
1044
+ }
1045
+ else {
1046
+ // ✅ L'ID existe déjà : gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement
1047
+ try {
1048
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
1049
+ }
1050
+ catch (error) {
1051
+ // Si l'ID est déjà utilisé par un autre fichier, générer un nouvel ID
1052
+ if (error.code === 'id_already_used') {
1053
+ // En mode non-strict, générer un nouvel ID si doublon détecté
1054
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1055
+ content = matterSerialize(parsed.content, parsed.matter);
1056
+ // ✅ Ne pas propager l'erreur, continuer avec le nouvel ID généré
1057
+ }
1058
+ else {
1059
+ throw error;
1060
+ }
1061
+ }
1062
+ }
899
1063
  }
900
1064
  }
901
1065
  catch (error) {
902
- if (error.code === 'id_already_used') {
1066
+ // En mode strict, propager l'erreur id_already_used
1067
+ // En mode permissif, cette erreur devrait déjà être gérée dans le bloc ci-dessus
1068
+ if (error.code === 'id_already_used' && gitConf.strictID) {
903
1069
  throw new errors_1.GitOperationError(`Impossible de créer le fichier: ${error.message}`, 'duplicate_id', { filePath, error });
904
1070
  }
905
1071
  // Propager les autres erreurs de validation
@@ -1398,6 +1564,81 @@ async function gitGetBranchHealth(git, branch) {
1398
1564
  (health.clean || (health.modifiedFiles === 0 && health.stagedFiles === 0));
1399
1565
  return health;
1400
1566
  }
1567
+ /**
1568
+ * Version optimisée de gitGetBranchHealth pour les branches de validation
1569
+ *
1570
+ * Cette fonction utilise une approche en cascade pour diagnostiquer rapidement
1571
+ * l'état d'une branche de validation sans effectuer de checkout coûteux.
1572
+ *
1573
+ * Tests effectués dans l'ordre (arrêt au premier échec) :
1574
+ * 1. Branche existe ? (git rev-parse --verify)
1575
+ * 2. Note Git présente sur HEAD ? (gitReadNote avec maxCommit=1)
1576
+ * 3. Note correspond au bon PR ? (note.id === prNumber)
1577
+ *
1578
+ * Avantages :
1579
+ * - ✅ Pas de checkout (gain 80-90% performance)
1580
+ * - ✅ Lecture directe du HEAD sans changer de branche
1581
+ * - ✅ Tests cascade (arrêt dès qu'un problème est détecté)
1582
+ * - ✅ Optimisé pour branches temporaires (validations)
1583
+ *
1584
+ * @param git Instance SimpleGit
1585
+ * @param branch Nom de la branche de validation à diagnostiquer
1586
+ * @param config Configuration Git optionnelle
1587
+ * @returns GitHealthStatus avec diagnostic optimisé
1588
+ */
1589
+ async function gitGetValidationBranchHealth(git, branch, config) {
1590
+ const gitConfig = config || (0, repo_tools_1.gitLoad)();
1591
+ const health = {
1592
+ branch,
1593
+ exists: false,
1594
+ accessible: false,
1595
+ clean: true, // Par défaut OK pour validation (pas de working dir check)
1596
+ mergeInProgress: false,
1597
+ conflictedFiles: 0,
1598
+ modifiedFiles: 0,
1599
+ untrackedFiles: 0,
1600
+ stagedFiles: 0,
1601
+ indexReadable: true,
1602
+ healthy: false,
1603
+ issues: [],
1604
+ recommendations: []
1605
+ };
1606
+ try {
1607
+ // 1. CASCADE: Vérifier existence (très rapide, sans checkout)
1608
+ const branchExists = await git.raw(['rev-parse', '--verify', branch])
1609
+ .then(() => true)
1610
+ .catch(() => false);
1611
+ if (!branchExists) {
1612
+ health.issues.push(`Branche '${branch}' n'existe pas`);
1613
+ health.recommendations.push(`Créer la branche ou vérifier le nom`);
1614
+ return health;
1615
+ }
1616
+ health.exists = true;
1617
+ health.accessible = true; // Si rev-parse OK, branche accessible
1618
+ // 2. CASCADE: Vérifier note sur HEAD (rapide, sans checkout)
1619
+ const headCommit = await git.raw(['rev-parse', branch]).then(s => s.trim());
1620
+ const note = await (0, repo_tools_1.gitReadNote)(git, headCommit, gitConfig.gitNotes.namespace, 1);
1621
+ if (!note) {
1622
+ health.issues.push(`Pas de note Git sur HEAD de '${branch}'`);
1623
+ health.recommendations.push(`Note perdue, utiliser git-notes-list-lost.sh pour la localiser`);
1624
+ return health;
1625
+ }
1626
+ // 3. CASCADE: Vérifier que la note correspond au PR
1627
+ const prNumber = parseInt(branch.split('-').pop() || '0', 10);
1628
+ if (note.id !== prNumber) {
1629
+ health.issues.push(`Note incorrecte: ID=${note.id}, attendu PR=${prNumber}`);
1630
+ health.recommendations.push(`Corriger la note ou supprimer la branche`);
1631
+ return health;
1632
+ }
1633
+ // ✅ Tous les tests essentiels passés
1634
+ health.healthy = true;
1635
+ }
1636
+ catch (error) {
1637
+ health.issues.push(`Erreur diagnostic: ${error.message}`);
1638
+ health.recommendations.push(`Vérifier l'état du dépôt Git`);
1639
+ }
1640
+ return health;
1641
+ }
1401
1642
  /**
1402
1643
  * Fonction helper pour créer un statut unhealthy
1403
1644
  */