agentic-api 2.0.314 → 2.0.585

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 (76) hide show
  1. package/README.md +37 -34
  2. package/dist/src/agents/prompts.d.ts +1 -1
  3. package/dist/src/agents/prompts.js +9 -7
  4. package/dist/src/agents/reducer.core.js +2 -2
  5. package/dist/src/agents/simulator.d.ts +33 -4
  6. package/dist/src/agents/simulator.dashboard.d.ts +140 -0
  7. package/dist/src/agents/simulator.dashboard.js +344 -0
  8. package/dist/src/agents/simulator.executor.d.ts +9 -3
  9. package/dist/src/agents/simulator.executor.js +43 -17
  10. package/dist/src/agents/simulator.js +103 -19
  11. package/dist/src/agents/simulator.prompts.d.ts +9 -8
  12. package/dist/src/agents/simulator.prompts.js +68 -62
  13. package/dist/src/agents/simulator.types.d.ts +39 -4
  14. package/dist/src/agents/simulator.utils.d.ts +22 -1
  15. package/dist/src/agents/simulator.utils.js +27 -2
  16. package/dist/src/execute/helpers.d.ts +75 -0
  17. package/dist/src/execute/helpers.js +139 -0
  18. package/dist/src/execute/index.d.ts +11 -0
  19. package/dist/src/execute/index.js +44 -0
  20. package/dist/src/execute/legacy.d.ts +46 -0
  21. package/dist/src/{execute.js → execute/legacy.js} +130 -232
  22. package/dist/src/execute/modelconfig.d.ts +29 -0
  23. package/dist/src/execute/modelconfig.js +72 -0
  24. package/dist/src/execute/responses.d.ts +55 -0
  25. package/dist/src/execute/responses.js +595 -0
  26. package/dist/src/execute/shared.d.ts +83 -0
  27. package/dist/src/execute/shared.js +188 -0
  28. package/dist/src/index.d.ts +5 -1
  29. package/dist/src/index.js +21 -2
  30. package/dist/src/llm/config.d.ts +25 -0
  31. package/dist/src/llm/config.js +38 -0
  32. package/dist/src/llm/index.d.ts +48 -0
  33. package/dist/src/llm/index.js +115 -0
  34. package/dist/src/llm/openai.d.ts +6 -0
  35. package/dist/src/llm/openai.js +154 -0
  36. package/dist/src/llm/pricing.d.ts +26 -0
  37. package/dist/src/llm/pricing.js +129 -0
  38. package/dist/src/llm/xai.d.ts +17 -0
  39. package/dist/src/llm/xai.js +90 -0
  40. package/dist/src/pricing.llm.d.ts +3 -15
  41. package/dist/src/pricing.llm.js +10 -230
  42. package/dist/src/prompts.d.ts +0 -1
  43. package/dist/src/prompts.js +51 -118
  44. package/dist/src/rag/embeddings.d.ts +5 -1
  45. package/dist/src/rag/embeddings.js +23 -7
  46. package/dist/src/rag/parser.js +1 -1
  47. package/dist/src/rag/rag.manager.d.ts +33 -2
  48. package/dist/src/rag/rag.manager.js +159 -61
  49. package/dist/src/rag/types.d.ts +2 -0
  50. package/dist/src/rag/usecase.js +8 -11
  51. package/dist/src/rules/git/git.e2e.helper.js +21 -2
  52. package/dist/src/rules/git/git.health.d.ts +4 -2
  53. package/dist/src/rules/git/git.health.js +113 -16
  54. package/dist/src/rules/git/index.d.ts +1 -1
  55. package/dist/src/rules/git/index.js +3 -2
  56. package/dist/src/rules/git/repo.d.ts +57 -7
  57. package/dist/src/rules/git/repo.js +326 -39
  58. package/dist/src/rules/git/repo.pr.d.ts +8 -0
  59. package/dist/src/rules/git/repo.pr.js +161 -13
  60. package/dist/src/rules/git/repo.tools.d.ts +5 -1
  61. package/dist/src/rules/git/repo.tools.js +54 -7
  62. package/dist/src/rules/types.d.ts +25 -0
  63. package/dist/src/rules/utils.matter.d.ts +0 -20
  64. package/dist/src/rules/utils.matter.js +58 -81
  65. package/dist/src/scrapper.js +3 -2
  66. package/dist/src/stategraph/stategraph.d.ts +26 -1
  67. package/dist/src/stategraph/stategraph.js +43 -2
  68. package/dist/src/stategraph/stategraph.storage.js +4 -0
  69. package/dist/src/stategraph/types.d.ts +5 -0
  70. package/dist/src/types.d.ts +42 -7
  71. package/dist/src/types.js +8 -7
  72. package/dist/src/usecase.js +1 -1
  73. package/dist/src/utils.d.ts +0 -8
  74. package/dist/src/utils.js +26 -29
  75. package/package.json +9 -7
  76. package/dist/src/execute.d.ts +0 -63
@@ -53,11 +53,14 @@ 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");
59
60
  const fs = __importStar(require("fs/promises"));
60
61
  const repo_tools_1 = require("./repo.tools");
62
+ const repo_pr_1 = require("./repo.pr");
63
+ const utils_matter_1 = require("../utils.matter");
61
64
  /**
62
65
  * Service singleton pour gérer le registre d'IDs en mémoire
63
66
  */
@@ -70,16 +73,32 @@ class IDRegistryService {
70
73
  static get() {
71
74
  return IDRegistryService.instance || (IDRegistryService.instance = new IDRegistryService());
72
75
  }
76
+ toString() {
77
+ return JSON.stringify(this.registry, null, 2);
78
+ }
79
+ clear() {
80
+ this.registry = null;
81
+ this.repoPath = '';
82
+ this.isDirty = false;
83
+ }
73
84
  /**
74
85
  * Initialise le service avec le chemin du repository
75
86
  */
76
87
  init(repoPath) {
77
- // Skip if already initialized
78
- if (this.registry) {
79
- return;
80
- }
81
- if (this.repoPath !== repoPath || !this.registry) {
88
+ // BUGFIX: Toujours mettre à jour repoPath même si le registre existe déjà
89
+ // Car gitReloadIDRegistry peut être appelé avec un nouveau repoPath pour un nouveau test
90
+ // et le repo précédent peut avoir été nettoyé
91
+ if (this.repoPath !== repoPath) {
82
92
  this.repoPath = repoPath;
93
+ // Si le registre existe déjà, le recharger depuis le nouveau chemin
94
+ if (this.registry) {
95
+ this.registry = this.loadFromDisk();
96
+ this.isDirty = false;
97
+ return;
98
+ }
99
+ }
100
+ // Si le registre n'existe pas encore, le créer
101
+ if (!this.registry) {
83
102
  this.registry = this.loadFromDisk();
84
103
  this.isDirty = false;
85
104
  }
@@ -172,16 +191,64 @@ class IDRegistryService {
172
191
  this.isDirty = true;
173
192
  return newID;
174
193
  }
194
+ /**
195
+ * Obtient l'ID d'un fichier (peu importe la branche)
196
+ *
197
+ * Un fichier a UN SEUL ID quelque soit la branche où il se trouve.
198
+ * Cette fonction cherche le fichier dans toutes les branches du cache.
199
+ *
200
+ * @param file Nom du fichier (ex: 'procedure.md')
201
+ * @returns L'ID du fichier ou undefined si le fichier n'a pas d'ID
202
+ */
203
+ getFileID(file) {
204
+ if (!this.registry) {
205
+ return undefined;
206
+ }
207
+ // Chercher le fichier dans toutes les branches
208
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
209
+ const [, cachedFile] = cacheKey.split(':');
210
+ if (cachedFile === file) {
211
+ return matter.id;
212
+ }
213
+ }
214
+ return undefined;
215
+ }
175
216
  /**
176
217
  * Vérifie si un ID appartient déjà à un fichier spécifique
218
+ *
219
+ * Un fichier garde son ID peu importe la branche, donc cette fonction
220
+ * cherche dans toutes les branches pour vérifier la propriété.
221
+ *
222
+ * @param id L'ID à vérifier
223
+ * @param file Nom du fichier
224
+ * @returns true si ce fichier possède cet ID
177
225
  */
178
- isIDOwnedByFile(id, branch, file) {
226
+ isIDOwnedByFile(id, file) {
179
227
  if (!this.registry) {
180
228
  return false;
181
229
  }
182
- const cacheKey = `${branch}:${file}`;
183
- const cachedMatter = this.registry.matters[cacheKey];
184
- return cachedMatter?.id === id;
230
+ // Chercher le fichier dans toutes les branches
231
+ const fileID = this.getFileID(file);
232
+ return fileID === id;
233
+ }
234
+ /**
235
+ * Trouve quel fichier possède un ID donné
236
+ *
237
+ * @param id L'ID à chercher
238
+ * @returns Le nom du fichier qui possède cet ID, ou undefined
239
+ */
240
+ getFileByID(id) {
241
+ if (!this.registry) {
242
+ return undefined;
243
+ }
244
+ // Chercher l'ID dans le cache
245
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
246
+ if (matter.id === id) {
247
+ const [, file] = cacheKey.split(':');
248
+ return file;
249
+ }
250
+ }
251
+ return undefined;
185
252
  }
186
253
  /**
187
254
  * Enregistre un ID existant
@@ -198,7 +265,8 @@ class IDRegistryService {
198
265
  // Si l'ID est déjà utilisé
199
266
  if (this.registry.used.includes(id)) {
200
267
  // Vérifier si c'est le même fichier (pas un doublon)
201
- if (branch && file && this.isIDOwnedByFile(id, branch, file)) {
268
+ // Note: isIDOwnedByFile() cherche maintenant dans TOUTES les branches
269
+ if (file && this.isIDOwnedByFile(id, file)) {
202
270
  // Même fichier → OK, pas d'erreur
203
271
  return;
204
272
  }
@@ -212,6 +280,10 @@ class IDRegistryService {
212
280
  if (id > this.registry.last) {
213
281
  this.registry.last = id;
214
282
  }
283
+ // ⚠️ IMPORTANT: Ne PAS créer d'entrée dans matters ici
284
+ // setMatterCache() DOIT être appelé AVANT registerExistingID() par l'appelant
285
+ // car registerExistingID() n'a pas le contexte complet (title, service)
286
+ // Si branch/file sont fournis mais que le cache n'existe pas, c'est une erreur de programmation
215
287
  this.isDirty = true;
216
288
  }
217
289
  /**
@@ -348,6 +420,9 @@ function gitGenerateNextID(config) {
348
420
  const gitConf = (0, repo_tools_1.gitLoad)(config);
349
421
  idRegistryService.init(gitConf.repoPath);
350
422
  const newID = idRegistryService.generateNextID();
423
+ // FIXME: Double save - gitGenerateNextID() et gitRegisterExistingID() appellent tous deux save()
424
+ // Refactorisation future: Retirer save() d'ici et le faire uniquement dans gitRegisterExistingID()
425
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
351
426
  idRegistryService.save();
352
427
  return newID;
353
428
  }
@@ -370,13 +445,16 @@ function gitGenerateNextID(config) {
370
445
  */
371
446
  function gitReloadIDRegistry(config) {
372
447
  const gitConf = (0, repo_tools_1.gitLoad)(config);
448
+ if (config?.reset) {
449
+ idRegistryService.clear();
450
+ }
373
451
  idRegistryService.init(gitConf.repoPath);
374
452
  idRegistryService.forceReload();
375
453
  }
376
454
  /**
377
455
  * Enregistre un ID existant dans le registre
378
456
  *
379
- * @param id L'ID à enregistrer
457
+ * @param matter Le matter contenant l'ID à enregistrer
380
458
  * @param branch Branche du fichier (optionnel, pour vérification de propriété)
381
459
  * @param file Nom du fichier (optionnel, pour vérification de propriété)
382
460
  * @param config Configuration Git optionnelle
@@ -384,11 +462,36 @@ function gitReloadIDRegistry(config) {
384
462
  *
385
463
  * **Note:** Si `branch` et `file` sont fournis, la fonction vérifie si l'ID appartient
386
464
  * déjà au même fichier. Si oui, aucune erreur n'est levée (cas de re-scan).
465
+ *
466
+ * **Note:** `setMatterCache()` est appelé automatiquement avant l'enregistrement
467
+ * pour garantir la cohérence du cache.
387
468
  */
388
- function gitRegisterExistingID(id, branch, file, config) {
469
+ function gitRegisterExistingID(matter, branch, file, config) {
389
470
  const gitConf = (0, repo_tools_1.gitLoad)(config);
390
471
  idRegistryService.init(gitConf.repoPath);
391
- idRegistryService.registerExistingID(id, branch, file);
472
+ // ✅ Validation: matter doit avoir id et title
473
+ if (!matter.id || !matter.title) {
474
+ throw new errors_1.GitOperationError('Le matter est invalide (id et title requis)', 'invalid_matter');
475
+ }
476
+ // ✅ IMPORTANT: Vérifier AVANT de mettre à jour le cache
477
+ // Si l'ID existe déjà, vérifier qu'il appartient au MÊME fichier
478
+ const registry = idRegistryService.getRegistry();
479
+ if (registry.used.includes(matter.id)) {
480
+ const ownerFile = idRegistryService.getFileByID(matter.id);
481
+ if (ownerFile && ownerFile !== file) {
482
+ // ID utilisé par un AUTRE fichier → Erreur
483
+ const error = new Error(`ID ${matter.id} est déjà utilisé`);
484
+ error.code = 'id_already_used';
485
+ throw error;
486
+ }
487
+ // Même fichier OU pas de fichier dans le cache → OK, continuer
488
+ }
489
+ // ✅ Mettre à jour le cache (maintenant qu'on sait que l'ID est valide)
490
+ if (branch && file) {
491
+ idRegistryService.setMatterCache(branch, file, matter);
492
+ }
493
+ // ✅ Enregistrer l'ID dans used[] si pas déjà dedans
494
+ idRegistryService.registerExistingID(matter.id, branch, file);
392
495
  idRegistryService.save();
393
496
  }
394
497
  /**
@@ -418,25 +521,61 @@ function gitIDRegistryRename(oldFile, newFile, branch, config) {
418
521
  }
419
522
  /**
420
523
  * Valide et assure qu'un matter a un ID valide
524
+ *
525
+ * **Principe d'identité stable**: Un fichier conserve son ID à travers les branches.
526
+ * Cette fonction cherche d'abord si le fichier a déjà un ID dans le registre
527
+ * (peu importe la branche), et le réutilise pour garantir la cohérence.
528
+ *
421
529
  * @param matter Le matter à valider/compléter
422
530
  * @param config Configuration Git optionnelle
531
+ * @param branch Branche du fichier (optionnel, peut être undefined lors de createPullRequest)
532
+ * @param file Nom du fichier (requis pour chercher l'ID existant)
423
533
  * @returns Le matter mis à jour avec un ID valide
424
534
  */
425
- function gitEnsureMatterID(matter, config) {
426
- // Si ID existe et est valide
535
+ function gitEnsureMatterID(matter, config, branch, file) {
536
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
537
+ idRegistryService.init(gitConf.repoPath);
538
+ // ✅ NOUVEAU: Chercher d'abord l'ID existant du fichier (peu importe la branche)
539
+ const existingFileID = file ? idRegistryService.getFileID(file) : undefined;
540
+ if (existingFileID) {
541
+ // Le fichier a déjà un ID → le réutiliser pour garantir la cohérence
542
+ matter.id = existingFileID;
543
+ // Mettre à jour le cache si branch est définie (sinon ce sera fait plus tard)
544
+ if (branch && file && matter.title) {
545
+ idRegistryService.setMatterCache(branch, file, {
546
+ id: matter.id,
547
+ title: matter.title,
548
+ service: matter.service
549
+ });
550
+ }
551
+ return matter;
552
+ }
553
+ // Si matter a un ID fourni manuellement (ex: import ou test)
427
554
  // FIXME 999 should be a constant in config
428
555
  if (matter.id && typeof matter.id === 'number' && matter.id > 999) {
429
556
  try {
430
- // Pas de branch/file car utilisé pour nouveaux fichiers ou contexte inconnu
431
- gitRegisterExistingID(matter.id, undefined, undefined, config);
557
+ // Essayer d'enregistrer cet ID
558
+ gitRegisterExistingID(matter, branch, file, config);
432
559
  return matter;
433
560
  }
434
561
  catch (error) {
435
- // ID déjà utilisé générer un nouveau (silencieux, géré par le caller)
562
+ // ID déjà utilisé par un AUTRE fichier générer un nouveau
563
+ // (Si même fichier, registerExistingID ne lève pas d'erreur)
436
564
  }
437
565
  }
438
- // Générer un nouvel ID
566
+ // Générer un nouvel ID (si aucun ID fourni ou ID invalide)
439
567
  matter.id = gitGenerateNextID(config);
568
+ // FIXME: Double save ici - gitGenerateNextID() fait save() puis gitRegisterExistingID() fait save() aussi
569
+ // Refactorisation future: Retirer save() de gitGenerateNextID() et le faire uniquement dans gitRegisterExistingID()
570
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
571
+ // ✅ Mettre à jour le cache avec le nouvel ID (seulement si branch est définie)
572
+ if (branch && file && matter.title) {
573
+ idRegistryService.setMatterCache(branch, file, {
574
+ id: matter.id,
575
+ title: matter.title,
576
+ service: matter.service
577
+ });
578
+ }
440
579
  return matter;
441
580
  }
442
581
  /**
@@ -862,13 +1001,20 @@ async function gitCheckConfiguration(git) {
862
1001
  return check;
863
1002
  }
864
1003
  /**
865
- * Crée un fichier dans la branche draft (opération bas niveau)
866
- * @param git Instance Git
1004
+ * Crée ou modifie un fichier dans une branche Git et fait un commit automatique
1005
+ *
1006
+ * @param git Instance SimpleGit
867
1007
  * @param filePath Chemin du fichier
868
1008
  * @param PR Nom de la branche de Pull Request
869
1009
  * @param content Contenu du fichier
870
- * @param user Utilisateur qui effectue l'opération
871
- * @param config Configuration Git (optionnel, utilise la config globale)
1010
+ * @param user Utilisateur qui fait le commit
1011
+ * @param config Configuration Git optionnelle
1012
+ * @returns Historique du commit créé
1013
+ *
1014
+ * @fixme Ajouter un paramètre optionnel `commitMessage?: string` pour permettre de personnaliser
1015
+ * le message de commit au lieu d'utiliser toujours `commit: ${filePath}` par défaut.
1016
+ * Cela permettrait aux callers de spécifier des messages plus descriptifs.
1017
+ *
872
1018
  * @throws GitOperationError si la création échoue
873
1019
  */
874
1020
  async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
@@ -882,24 +1028,52 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
882
1028
  if (gitConf.withID) {
883
1029
  try {
884
1030
  // Import dynamique pour éviter les dépendances circulaires
885
- const { matterParse, matterSerialize } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
886
- const parsed = matterParse(content);
1031
+ const parsed = (0, utils_matter_1.matterParse)(content);
887
1032
  if (gitConf.strictID) {
888
1033
  // Mode strict : valider que l'ID existe et est correct
889
1034
  validateMatter(parsed.matter);
1035
+ // ✅ gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement si matter est fourni
890
1036
  // Enregistrer l'ID existant
891
1037
  // ✅ Passer branch (PR) et file (filePath) pour vérifier propriété
892
- gitRegisterExistingID(parsed.matter.id, PR, filePath, gitConf);
1038
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
893
1039
  }
894
1040
  else {
895
1041
  // 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);
1042
+ // IMPORTANT: Ne PAS ré-assigner l'ID s'il est déjà valide
1043
+ // Le contenu a peut-être déjà été traité par gitEnsureMatterID() dans le caller
1044
+ const hasValidID = parsed.matter.id &&
1045
+ typeof parsed.matter.id === 'number' &&
1046
+ parsed.matter.id > 999;
1047
+ if (!hasValidID) {
1048
+ // Seulement si l'ID est manquant ou invalide
1049
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1050
+ // Re-sérialiser avec l'ID mis à jour
1051
+ content = (0, utils_matter_1.matterSerialize)(parsed.content, parsed.matter);
1052
+ }
1053
+ else {
1054
+ // ✅ L'ID existe déjà : gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement
1055
+ try {
1056
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
1057
+ }
1058
+ catch (error) {
1059
+ // Si l'ID est déjà utilisé par un autre fichier, générer un nouvel ID
1060
+ if (error.code === 'id_already_used') {
1061
+ // En mode non-strict, générer un nouvel ID si doublon détecté
1062
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1063
+ content = (0, utils_matter_1.matterSerialize)(parsed.content, parsed.matter);
1064
+ // ✅ Ne pas propager l'erreur, continuer avec le nouvel ID généré
1065
+ }
1066
+ else {
1067
+ throw error;
1068
+ }
1069
+ }
1070
+ }
899
1071
  }
900
1072
  }
901
1073
  catch (error) {
902
- if (error.code === 'id_already_used') {
1074
+ // En mode strict, propager l'erreur id_already_used
1075
+ // En mode permissif, cette erreur devrait déjà être gérée dans le bloc ci-dessus
1076
+ if (error.code === 'id_already_used' && gitConf.strictID) {
903
1077
  throw new errors_1.GitOperationError(`Impossible de créer le fichier: ${error.message}`, 'duplicate_id', { filePath, error });
904
1078
  }
905
1079
  // Propager les autres erreurs de validation
@@ -923,13 +1097,15 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
923
1097
  //
924
1098
  // without note the branche is not a valid PR branch
925
1099
  const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
1100
+ if (oldNote && gitConf.verbose) {
1101
+ console.debug('[gitCreateOrEditFile] metadata.files before update:', oldNote.files);
1102
+ }
926
1103
  const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
927
1104
  // ✅ Mettre à jour le cache du matter après sauvegarde
928
1105
  // NOTE: Le cache est TOUJOURS mis à jour, indépendamment de withID
929
1106
  // C'est une opération de cache, pas une validation
930
1107
  try {
931
- const { matterParse } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
932
- const parsed = matterParse(content);
1108
+ const parsed = (0, utils_matter_1.matterParse)(content);
933
1109
  idRegistryService.init(gitConf.repoPath);
934
1110
  idRegistryService.setMatterCache(PR, filePath, {
935
1111
  id: parsed.matter.id,
@@ -958,11 +1134,15 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
958
1134
  }
959
1135
  // Determine the current head after the commit attempt.
960
1136
  const newHead = (await git.revparse(['HEAD'])).trim();
961
- const newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, PR, oldNote.mergeBase);
1137
+ let newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, PR, oldNote.mergeBase);
1138
+ newFiles = await sanitizePRFiles(git, newFiles, PR);
962
1139
  const updatedNote = {
963
1140
  ...oldNote,
964
1141
  files: newFiles,
965
1142
  };
1143
+ if (gitConf.verbose) {
1144
+ console.debug('[gitCreateOrEditFile] metadata.files after update:', updatedNote.files);
1145
+ }
966
1146
  // Write the note to the current HEAD of the PR branch.
967
1147
  // If no commit was made, this overwrites the note on oldHead.
968
1148
  // If a new commit was made, this writes the note on newHead.
@@ -1165,12 +1345,24 @@ async function gitRenameFile(git, oldFileName, newFileName, branch, user, config
1165
1345
  //
1166
1346
  //FIXME: add a check about the branch (file can be NEW and on branch)
1167
1347
  const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
1348
+ let result;
1168
1349
  if ((0, fs_1.existsSync)(fullOldPath)) {
1169
- return await gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config);
1350
+ result = await gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config);
1170
1351
  }
1171
1352
  else {
1172
- return await gitRenameFile_git(git, oldFileName, newFileName, branch, user, config);
1353
+ result = await gitRenameFile_git(git, oldFileName, newFileName, branch, user, config);
1354
+ if (branch !== 'NEW' && branch.startsWith(gitConf.validationPrefix)) {
1355
+ try {
1356
+ await (0, repo_pr_1.gitPRReplaceFile)(git, branch, { remove: oldFileName, add: newFileName }, gitConf);
1357
+ }
1358
+ catch (metadataError) {
1359
+ if (gitConf.verbose) {
1360
+ console.warn('⚠️ Failed to update PR metadata after rename:', metadataError);
1361
+ }
1362
+ }
1363
+ }
1173
1364
  }
1365
+ return result;
1174
1366
  }
1175
1367
  catch (error) {
1176
1368
  // Pour les vraies branches Git, ne pas faire de fallback filesystem
@@ -1398,6 +1590,81 @@ async function gitGetBranchHealth(git, branch) {
1398
1590
  (health.clean || (health.modifiedFiles === 0 && health.stagedFiles === 0));
1399
1591
  return health;
1400
1592
  }
1593
+ /**
1594
+ * Version optimisée de gitGetBranchHealth pour les branches de validation
1595
+ *
1596
+ * Cette fonction utilise une approche en cascade pour diagnostiquer rapidement
1597
+ * l'état d'une branche de validation sans effectuer de checkout coûteux.
1598
+ *
1599
+ * Tests effectués dans l'ordre (arrêt au premier échec) :
1600
+ * 1. Branche existe ? (git rev-parse --verify)
1601
+ * 2. Note Git présente sur HEAD ? (gitReadNote avec maxCommit=1)
1602
+ * 3. Note correspond au bon PR ? (note.id === prNumber)
1603
+ *
1604
+ * Avantages :
1605
+ * - ✅ Pas de checkout (gain 80-90% performance)
1606
+ * - ✅ Lecture directe du HEAD sans changer de branche
1607
+ * - ✅ Tests cascade (arrêt dès qu'un problème est détecté)
1608
+ * - ✅ Optimisé pour branches temporaires (validations)
1609
+ *
1610
+ * @param git Instance SimpleGit
1611
+ * @param branch Nom de la branche de validation à diagnostiquer
1612
+ * @param config Configuration Git optionnelle
1613
+ * @returns GitHealthStatus avec diagnostic optimisé
1614
+ */
1615
+ async function gitGetValidationBranchHealth(git, branch, config) {
1616
+ const gitConfig = config || (0, repo_tools_1.gitLoad)();
1617
+ const health = {
1618
+ branch,
1619
+ exists: false,
1620
+ accessible: false,
1621
+ clean: true, // Par défaut OK pour validation (pas de working dir check)
1622
+ mergeInProgress: false,
1623
+ conflictedFiles: 0,
1624
+ modifiedFiles: 0,
1625
+ untrackedFiles: 0,
1626
+ stagedFiles: 0,
1627
+ indexReadable: true,
1628
+ healthy: false,
1629
+ issues: [],
1630
+ recommendations: []
1631
+ };
1632
+ try {
1633
+ // 1. CASCADE: Vérifier existence (très rapide, sans checkout)
1634
+ const branchExists = await git.raw(['rev-parse', '--verify', branch])
1635
+ .then(() => true)
1636
+ .catch(() => false);
1637
+ if (!branchExists) {
1638
+ health.issues.push(`Branche '${branch}' n'existe pas`);
1639
+ health.recommendations.push(`Créer la branche ou vérifier le nom`);
1640
+ return health;
1641
+ }
1642
+ health.exists = true;
1643
+ health.accessible = true; // Si rev-parse OK, branche accessible
1644
+ // 2. CASCADE: Vérifier note sur HEAD (rapide, sans checkout)
1645
+ const headCommit = await git.raw(['rev-parse', branch]).then(s => s.trim());
1646
+ const note = await (0, repo_tools_1.gitReadNote)(git, headCommit, gitConfig.gitNotes.namespace, 1);
1647
+ if (!note) {
1648
+ health.issues.push(`Pas de note Git sur HEAD de '${branch}'`);
1649
+ health.recommendations.push(`Note perdue, utiliser git-notes-list-lost.sh pour la localiser`);
1650
+ return health;
1651
+ }
1652
+ // 3. CASCADE: Vérifier que la note correspond au PR
1653
+ const prNumber = parseInt(branch.split('-').pop() || '0', 10);
1654
+ if (note.id !== prNumber) {
1655
+ health.issues.push(`Note incorrecte: ID=${note.id}, attendu PR=${prNumber}`);
1656
+ health.recommendations.push(`Corriger la note ou supprimer la branche`);
1657
+ return health;
1658
+ }
1659
+ // ✅ Tous les tests essentiels passés
1660
+ health.healthy = true;
1661
+ }
1662
+ catch (error) {
1663
+ health.issues.push(`Erreur diagnostic: ${error.message}`);
1664
+ health.recommendations.push(`Vérifier l'état du dépôt Git`);
1665
+ }
1666
+ return health;
1667
+ }
1401
1668
  /**
1402
1669
  * Fonction helper pour créer un statut unhealthy
1403
1670
  */
@@ -1422,9 +1689,7 @@ function createUnhealthyStatus(branch, exists, accessible, clean, mergeInProgres
1422
1689
  * Vérifie si un merge est en cours en regardant les fichiers Git
1423
1690
  */
1424
1691
  async function checkMergeInProgress(repoPath) {
1425
- const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
1426
- const { join } = await Promise.resolve().then(() => __importStar(require('path')));
1427
- const gitDir = join(repoPath, '.git');
1692
+ const gitDir = (0, path_1.join)(repoPath, '.git');
1428
1693
  const mergeFiles = [
1429
1694
  'MERGE_HEAD',
1430
1695
  'MERGE_MODE',
@@ -1432,5 +1697,27 @@ async function checkMergeInProgress(repoPath) {
1432
1697
  'CHERRY_PICK_HEAD',
1433
1698
  'REVERT_HEAD'
1434
1699
  ];
1435
- return mergeFiles.some(file => existsSync(join(gitDir, file)));
1700
+ return mergeFiles.some(file => (0, fs_1.existsSync)((0, path_1.join)(gitDir, file)));
1701
+ }
1702
+ async function sanitizePRFiles(git, files, branch) {
1703
+ if (!files || files.length === 0) {
1704
+ return [];
1705
+ }
1706
+ const checks = await Promise.all(files.map(async (file) => ({
1707
+ file,
1708
+ exists: await (0, repo_tools_1.gitFileExistsInBranch)(git, file, branch)
1709
+ })));
1710
+ const sanitized = [];
1711
+ const seen = new Set();
1712
+ for (const { file, exists } of checks) {
1713
+ if (!exists) {
1714
+ continue;
1715
+ }
1716
+ if (seen.has(file)) {
1717
+ continue;
1718
+ }
1719
+ seen.add(file);
1720
+ sanitized.push(file);
1721
+ }
1722
+ return sanitized;
1436
1723
  }
@@ -79,6 +79,14 @@ export declare function gitGetAllPR(git: SimpleGit, options?: {
79
79
  */
80
80
  export declare function gitGetClosedPRs(git: SimpleGit, gitConfig: RulesGitConfig): Promise<PRInfo[]>;
81
81
  export declare function gitLoadPR(git: SimpleGit, branch: string): Promise<PRInfo>;
82
+ /**
83
+ * Remplace explicitement un nom de fichier dans les métadonnées d'une PR.
84
+ * Utilisé pour refléter immédiatement les renames sans dépendre de git diff.
85
+ */
86
+ export declare function gitPRReplaceFile(git: SimpleGit, branch: string, options?: {
87
+ remove?: string;
88
+ add?: string;
89
+ }, config?: RulesGitConfig): Promise<void>;
82
90
  export declare function gitPRUpdateComments(git: SimpleGit, branch: string, details: RulePullRequestDetails, config?: RulesGitConfig): Promise<PRInfo>;
83
91
  /**
84
92
  * DEPRECATED: use gitClosePRRobust instead