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,12 +53,19 @@ class RAGManager {
53
53
  DEFAULT_RAG_INSTANCE[defaultName] = this;
54
54
  }
55
55
  static get(config) {
56
- // Vérifier si une instance existe déjà pour ce baseDir
57
- if (DEFAULT_RAG_INSTANCE[config.baseDir]) {
58
- return DEFAULT_RAG_INSTANCE[config.baseDir];
59
- }
60
- // Créer une nouvelle instance
61
- return new RAGManager(config);
56
+ // Normaliser le baseDir pour éviter les doublons dus aux trailing slashes
57
+ // ou aux variations de paths (relatif/absolu, symlinks, etc.)
58
+ const normalizedBaseDir = path_1.default.resolve(config.baseDir).replace(/\/+$/, '');
59
+ // Vérifier si une instance existe déjà pour ce baseDir normalisé
60
+ if (DEFAULT_RAG_INSTANCE[normalizedBaseDir]) {
61
+ return DEFAULT_RAG_INSTANCE[normalizedBaseDir];
62
+ }
63
+ // Créer une nouvelle instance avec le baseDir normalisé
64
+ const normalizedConfig = {
65
+ ...config,
66
+ baseDir: normalizedBaseDir
67
+ };
68
+ return new RAGManager(normalizedConfig);
62
69
  }
63
70
  /**
64
71
  * Nettoie le cache des instances (utile pour les tests)
@@ -231,31 +238,40 @@ class RAGManager {
231
238
  */
232
239
  notifyUpdate(name, opts) {
233
240
  const embeddingData = this.loadedEmbeddings.get(name);
241
+ console.log(`\n🔔 notifyUpdate('${name}') appelé`);
242
+ console.log(` Embedding en cache: ${embeddingData ? 'OUI' : 'NON'}`);
243
+ // Si l'embedding n'est pas encore chargé, rien à faire
244
+ // Il sera chargé avec la nouvelle config au prochain load()
234
245
  if (!embeddingData) {
235
- if (this.config.verbose)
236
- console.log(`ℹ️ Aucun embedding chargé pour '${name}'`);
246
+ console.log(` ⏭️ Skip: sera chargé avec la nouvelle config au prochain load()`);
237
247
  return;
238
248
  }
249
+ // Charger la nouvelle configuration depuis le registre
250
+ const entry = this.getEntry(name);
251
+ if (!entry) {
252
+ console.log(` ⚠️ Entry '${name}' introuvable dans le registre`);
253
+ return;
254
+ }
255
+ console.log(` 📁 entry.configPath: ${entry.configPath}`);
256
+ // L'embedding est chargé, forcer son rechargement via update()
257
+ // update() modifie l'instance EN PLACE, donc tous ceux qui ont une référence voient la nouvelle version
239
258
  const { embedding } = embeddingData;
240
259
  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
260
  const newConfig = this.loadConfig(entry.configPath);
261
+ console.log(` 🔄 Appel de embedding.update() avec baseDir: ${newConfig.baseDir}`);
247
262
  // FIXME: Vérifier si l'embedding est busy avant de faire l'update
248
- // Pour l'instant, on appelle update() directement
263
+ // update() va recharger l'index, les métadonnées et le mapping depuis newConfig.baseDir
264
+ // update() modifie l'instance en place, donc tous les utilisateurs voient la nouvelle version
249
265
  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
- }
266
+ // Mettre à jour le timestamp du cache
267
+ embeddingData.loadTime = Date.now();
268
+ console.log(` Embedding rechargé avec succès`);
269
+ console.log(` ✅ embedding.isReady(): ${embedding.isReady()}`);
256
270
  }
257
271
  catch (error) {
258
- console.error(`❌ Erreur lors de la notification d'update pour '${name}':`, error);
272
+ console.error(` ❌ Erreur lors de la mise à jour de l'embedding '${name}':`, error);
273
+ // En cas d'erreur, update() a déjà fait un rollback de sa propre config
274
+ // L'instance continue de fonctionner avec l'ancienne config
259
275
  // Ne pas throw pour éviter de bloquer le flow
260
276
  }
261
277
  }
@@ -486,18 +502,31 @@ class RAGManager {
486
502
  if (!entry) {
487
503
  throw new Error(`RAG '${name}' n'existe pas`);
488
504
  }
489
- // if (status.status !== 'building') {
490
- // throw new Error(`RAG '${name}' n'est pas en construction (status: ${status.status})`);
491
- // }
492
505
  const buildPath = entry.configPath;
493
506
  if (!(0, fs_1.existsSync)(buildPath)) {
494
507
  throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
495
508
  }
496
509
  const filePath = path_1.default.join(buildPath, filename);
497
- const shaPath = path_1.default.join(buildPath, filename + '.sha');
498
- // Sauvegarder le fichier
510
+ // Sauvegarder le fichier source uniquement
511
+ // Le .sha est écrit par markDocumentProcessed() APRÈS génération de .enhanced.md et .query.json
499
512
  (0, fs_1.writeFileSync)(filePath, content, 'utf8');
500
- // ✅ Sauvegarder le fingerprint
513
+ }
514
+ /**
515
+ * Marque un document comme entièrement traité (enhanced + query.json générés)
516
+ * Le .sha est écrit APRÈS le traitement complet pour garantir la cohérence
517
+ *
518
+ * @param name Nom du RAG
519
+ * @param filename Nom du fichier (ex: 'procedure.md')
520
+ * @param content Contenu du document traité
521
+ */
522
+ markDocumentProcessed(name, filename, content) {
523
+ const entry = this.getEntry(name);
524
+ if (!entry) {
525
+ throw new Error(`RAG '${name}' n'existe pas`);
526
+ }
527
+ const buildPath = entry.configPath;
528
+ const shaPath = path_1.default.join(buildPath, filename + '.sha');
529
+ // ✅ Sauvegarder le fingerprint pour marquer le document comme traité
501
530
  const sha = (0, usecase_1.calculateFingerprint)(content);
502
531
  (0, fs_1.writeFileSync)(shaPath, sha, 'utf8');
503
532
  }
@@ -677,6 +706,72 @@ class RAGManager {
677
706
  const queryFile = baseName + '.query.json';
678
707
  return (0, usecase_1.loadUseCases)(queryFile, { baseDir: configPath });
679
708
  }
709
+ /**
710
+ * Charge tous les use cases de tous les documents d'un RAG
711
+ *
712
+ * Utilise le fichier rag-metadata.json pour obtenir la liste des documents valides
713
+ * puis charge chaque fichier .query.json correspondant
714
+ *
715
+ * @param name Nom du RAG
716
+ * @returns Objet contenant tous les use cases par document
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * const allQueries = ragManager.loadAllUseCases('my-rag');
721
+ * console.log(`${Object.keys(allQueries.documents).length} documents avec use cases`);
722
+ * console.log(`${allQueries.totalQueries} questions au total`);
723
+ * ```
724
+ */
725
+ loadAllUseCases(name) {
726
+ const entry = this.getEntry(name);
727
+ if (!entry) {
728
+ throw new Error(`RAG '${name}' n'existe pas`);
729
+ }
730
+ //
731
+ // Charger le metadata pour obtenir la liste des documents
732
+ const metadataFile = path_1.default.join(entry.configPath, types_1.RAG_FILES.METADATA);
733
+ if (!(0, fs_1.existsSync)(metadataFile)) {
734
+ throw new Error(`Fichier metadata manquant: ${metadataFile}`);
735
+ }
736
+ let metadata;
737
+ try {
738
+ metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
739
+ }
740
+ catch (error) {
741
+ throw new Error(`Erreur lors du chargement du metadata: ${error}`);
742
+ }
743
+ //
744
+ // Extraire les filenames uniques depuis metadata.documents
745
+ const uniqueFilenames = new Set();
746
+ for (const docRef of Object.values(metadata.documents)) {
747
+ if (docRef.filename) {
748
+ uniqueFilenames.add(docRef.filename);
749
+ }
750
+ }
751
+ //
752
+ // Charger les use cases pour chaque document
753
+ const documents = {};
754
+ let totalQueries = 0;
755
+ const filenames = [];
756
+ for (const filename of uniqueFilenames) {
757
+ const baseName = filename.replace(/\.md$/, '');
758
+ const queryFile = path_1.default.join(entry.configPath, baseName + '.query.json');
759
+ if ((0, fs_1.existsSync)(queryFile)) {
760
+ try {
761
+ const queries = JSON.parse((0, fs_1.readFileSync)(queryFile, 'utf8'));
762
+ documents[filename] = queries;
763
+ totalQueries += queries.queries?.length || 0;
764
+ filenames.push(filename);
765
+ }
766
+ catch (error) {
767
+ //
768
+ // Log l'erreur mais continue avec les autres documents
769
+ console.warn(`⚠️ Erreur chargement use cases pour ${filename}:`, error.message);
770
+ }
771
+ }
772
+ }
773
+ return { documents, totalQueries };
774
+ }
680
775
  /**
681
776
  * Construit un RAG de manière atomique
682
777
  *
@@ -777,6 +872,9 @@ class RAGManager {
777
872
  this.cachedRegistry = { ...registry };
778
873
  this.saveRegistry(registry);
779
874
  console.log(`✅ RAG '${name}' construit avec succès`);
875
+ console.log(` 📁 configPath final: ${entry.configPath}`);
876
+ console.log(` 📦 Embeddings chargés en cache: ${this.loadedEmbeddings.size}`);
877
+ console.log(` 🔍 Embedding '${name}' dans le cache: ${this.loadedEmbeddings.has(name) ? 'OUI' : 'NON'}`);
780
878
  // Notifier les embeddings chargés de se mettre à jour
781
879
  this.notifyUpdate(name);
782
880
  }
@@ -869,28 +967,34 @@ class RAGManager {
869
967
  */
870
968
  async delete(name, archiveFlag = true) {
871
969
  if (!this.exists(name)) {
872
- throw new Error(`RAG '${name}' n'existe pas`);
970
+ console.warn(`⚠️ RAG '${name}' n'existe pas, skip delete`);
971
+ return new Error(`RAG '${name}' n'existe pas`);
873
972
  }
874
- const entry = this.getEntry(name);
875
- const registry = this.getRegistry();
876
- // Archiver si demandé
877
- if (archiveFlag) {
878
- await this.archive(name);
879
- }
880
- else if ((0, fs_1.existsSync)(entry.configPath)) {
881
- (0, fs_1.rmSync)(entry.configPath, { recursive: true, force: true });
882
- }
883
- // Supprimer du registre
884
- delete registry.registries[name];
885
- // Supprimer de la map des embeddings chargés
886
- this.loadedEmbeddings.delete(name);
887
- // Changer le défaut si nécessaire
888
- if (registry.defaultName === name) {
889
- const remainingRAGs = Object.keys(registry.registries);
890
- registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
973
+ try {
974
+ const entry = this.getEntry(name);
975
+ const registry = this.getRegistry();
976
+ // Archiver si demandé
977
+ if (archiveFlag) {
978
+ await this.archive(name);
979
+ }
980
+ else if ((0, fs_1.existsSync)(entry.configPath)) {
981
+ (0, fs_1.rmSync)(entry.configPath, { recursive: true, force: true });
982
+ }
983
+ // Supprimer du registre
984
+ delete registry.registries[name];
985
+ // Supprimer de la map des embeddings chargés
986
+ this.loadedEmbeddings.delete(name);
987
+ // Changer le défaut si nécessaire
988
+ if (registry.defaultName === name) {
989
+ const remainingRAGs = Object.keys(registry.registries);
990
+ registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
991
+ }
992
+ this.saveRegistry(registry);
993
+ console.log(`✅ RAG supprimé: ${name}`);
994
+ }
995
+ catch (error) {
996
+ return error;
891
997
  }
892
- this.saveRegistry(registry);
893
- console.log(`✅ RAG supprimé: ${name}`);
894
998
  }
895
999
  /**
896
1000
  * Renomme un document dans un RAG existant
@@ -1029,7 +1133,8 @@ class RAGManager {
1029
1133
  }
1030
1134
  }
1031
1135
  /**
1032
- * Renomme un RAG existant
1136
+ * Renomme un RAG existant (nom dans le registre uniquement)
1137
+ * Le dossier physique n'est PAS modifié, seul le nom dans la config change.
1033
1138
  *
1034
1139
  * @param from Nom actuel du RAG
1035
1140
  * @param to Nouveau nom du RAG
@@ -1039,6 +1144,7 @@ class RAGManager {
1039
1144
  * @example
1040
1145
  * ```typescript
1041
1146
  * await ragManager.rename('old-name', 'new-name', 'Description mise à jour');
1147
+ * // Le dossier reste /path/to/old-name mais le RAG s'appelle 'new-name'
1042
1148
  * ```
1043
1149
  */
1044
1150
  async rename(from, to, description) {
@@ -1061,20 +1167,14 @@ class RAGManager {
1061
1167
  throw new Error(`Le nom '${to}' est réservé et ne peut pas être utilisé`);
1062
1168
  }
1063
1169
  const sourceEntry = this.getEntry(from);
1064
- const sourcePath = sourceEntry.configPath;
1065
- const destPath = path_1.default.join(this.config.baseDir, to);
1066
1170
  try {
1067
- // Renommer le dossier physique
1068
- if ((0, fs_1.existsSync)(sourcePath)) {
1069
- (0, fs_1.renameSync)(sourcePath, destPath);
1070
- if (this.config.verbose)
1071
- console.log(`📁 Dossier renommé: ${sourcePath} → ${destPath}`);
1072
- }
1073
- // Mettre à jour le registre
1171
+ //
1172
+ // Ne PAS renommer le dossier physique - juste le nom dans le registre
1173
+ // Le configPath reste inchangé (pointe vers le même dossier)
1074
1174
  const registry = this.getRegistry();
1075
1175
  registry.registries[to] = {
1076
1176
  name: to,
1077
- configPath: destPath,
1177
+ configPath: sourceEntry.configPath, // ✅ Garde le même chemin
1078
1178
  status: sourceEntry.status,
1079
1179
  description: description || sourceEntry.description
1080
1180
  };
@@ -1088,12 +1188,10 @@ class RAGManager {
1088
1188
  if (embeddingData) {
1089
1189
  this.loadedEmbeddings.set(to, embeddingData);
1090
1190
  this.loadedEmbeddings.delete(from);
1091
- // Notifier l'embedding de se mettre à jour avec le nouveau chemin
1092
- this.notifyUpdate(to);
1093
1191
  }
1094
1192
  // Mettre à jour le cache
1095
1193
  this.saveRegistry(registry);
1096
- console.log(`✅ RAG renommé: ${from} → ${to}`);
1194
+ console.log(`✅ RAG renommé: ${from} → ${to} (dossier inchangé: ${sourceEntry.configPath})`);
1097
1195
  }
1098
1196
  catch (error) {
1099
1197
  console.error(`❌ Erreur lors du renommage de '${from}' en '${to}':`, error);
@@ -5,6 +5,8 @@ export declare const RAG_FILES: {
5
5
  readonly QUERIES: "rag-queries.json";
6
6
  };
7
7
  export interface RAGConfig {
8
+ /** Provider de l'embedding (default openai) */
9
+ provider?: string;
8
10
  /** Répertoire de base pour les documents et index */
9
11
  baseDir: string;
10
12
  /** Nom du fichier de vecteurs (sans extension) */
@@ -132,25 +132,22 @@ async function extractAndSaveDocumentUseCases(document, baseDir, version, tag, o
132
132
  const existingQueries = JSON.parse((0, fs_1.readFileSync)(queryFile, 'utf8'));
133
133
  // Comparer le fingerprint
134
134
  if (existingQueries.fingerprint === fingerprint) {
135
- if (verbose) {
136
- console.log(`⏭️ Use cases à jour (fingerprint identique): ${document.filename}`);
137
- }
135
+ console.log(`⏭️ Skip queries: ${document.filename}`);
138
136
  return existingQueries;
139
137
  }
140
- else if (verbose) {
141
- console.log(`🔄 Contenu modifié (fingerprint différent): ${document.filename}`);
142
- }
138
+ console.log(`🔄 Source modifiée queries: ${document.filename}`);
143
139
  }
144
140
  catch (error) {
145
141
  // Si erreur de lecture, on continue avec l'extraction
146
- if (verbose) {
147
- console.warn(`⚠️ Erreur lecture use cases existants: ${error}`);
148
- }
142
+ console.warn(`⚠️ Erreur lecture use cases: ${error}`);
149
143
  }
150
144
  }
151
145
  // Extraction nécessaire (fichier inexistant, fingerprint différent, ou force=true)
152
- if (verbose && force) {
153
- console.log(`🔄 Re-extraction forcée: ${document.filename}`);
146
+ if (force) {
147
+ console.log(`🔄 Force queries: ${document.filename}`);
148
+ }
149
+ else {
150
+ console.log(`✨ Nouveau queries: ${document.filename}`);
154
151
  }
155
152
  const { queries, cost } = await extractDocumentUseCases(document);
156
153
  const docQueries = {
@@ -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') {
@@ -529,6 +551,44 @@ class GitHealthManager {
529
551
  ];
530
552
  let fixed = 0;
531
553
  const fixedFiles = new Set();
554
+ // ✅ ÉTAPE CRITIQUE: Charger le cache pour toutes les branches avant la réparation
555
+ // Cela permet à getFileID() de trouver l'ID existant du fichier
556
+ if (!dryRun && filesToFix.length > 0) {
557
+ console.log(` 🔍 Chargement du cache pour toutes les branches...`);
558
+ const allBranches = await (0, git_1.gitGetAllBranches)(this.git);
559
+ const uniqueFiles = new Set();
560
+ for (const fileRef of filesToFix) {
561
+ const [, ...fileParts] = fileRef.split(':');
562
+ const file = fileParts.join(':');
563
+ uniqueFiles.add(file);
564
+ }
565
+ // Mettre en cache la liste des fichiers par branche pour éviter les appels répétés
566
+ const branchFilesCache = new Map();
567
+ for (const branch of allBranches) {
568
+ try {
569
+ const filesInBranch = await (0, git_1.gitListFilesInBranch)(this.git, branch);
570
+ branchFilesCache.set(branch, filesInBranch);
571
+ }
572
+ catch (e) {
573
+ // Ignorer silencieusement les erreurs (branche peut être inaccessible)
574
+ branchFilesCache.set(branch, []);
575
+ }
576
+ }
577
+ // Charger le cache pour chaque fichier dans les branches où il existe
578
+ for (const file of uniqueFiles) {
579
+ for (const branch of allBranches) {
580
+ try {
581
+ const filesInBranch = branchFilesCache.get(branch) || [];
582
+ if (filesInBranch.includes(file)) {
583
+ await (0, git_1.gitFileStrictMatter)(this.git, file, branch, this.config);
584
+ }
585
+ }
586
+ catch (e) {
587
+ // Ignorer silencieusement les erreurs (fichier peut ne pas exister)
588
+ }
589
+ }
590
+ }
591
+ }
532
592
  for (const fileRef of filesToFix) {
533
593
  // Éviter de réparer deux fois le même fichier
534
594
  if (fixedFiles.has(fileRef)) {
@@ -548,11 +608,44 @@ class GitHealthManager {
548
608
  // Parser le matter complet
549
609
  const parsed = (0, utils_matter_1.matterParse)(fileData.content);
550
610
  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}`);
611
+ // CORRECTION: Utiliser gitEnsureMatterID() pour réutiliser l'ID existant du fichier
612
+ // Si le fichier existe déjà dans une autre branche, son ID sera réutilisé
613
+ // Sinon, un nouvel ID sera généré automatiquement
614
+ const matterWithID = (0, git_1.gitEnsureMatterID)(fullMatter, this.config, branchName, file);
615
+ if (matterWithID.id !== fullMatter.id) {
616
+ if (fullMatter.id && fullMatter.id > 999) {
617
+ console.log(` 🔧 ${file}: ID ${fullMatter.id} remplacé par ID existant ${matterWithID.id}`);
618
+ }
619
+ else {
620
+ console.log(` 🔧 ${file}: Nouvel ID assigné: ${matterWithID.id}`);
621
+ }
622
+ }
623
+ fullMatter.id = matterWithID.id;
624
+ // Générer un titre si manquant (basé sur le nom du fichier ou le premier titre de section)
625
+ if (!fullMatter.title || fullMatter.title.trim() === '') {
626
+ // Essayer d'extraire le premier titre de section (# Title)
627
+ const titleMatch = content.match(/^#\s+(.+)$/m);
628
+ if (titleMatch) {
629
+ fullMatter.title = titleMatch[1].trim();
630
+ }
631
+ else {
632
+ // Sinon, utiliser le nom du fichier sans extension
633
+ const fileName = file.replace(/\.md$/, '').replace(/[-_]/g, ' ');
634
+ fullMatter.title = fileName.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
635
+ }
636
+ console.log(` 🔧 ${file}: Génération d'un titre: ${fullMatter.title}`);
637
+ }
554
638
  // Re-sérialiser et sauvegarder
555
639
  const updatedContent = (0, utils_matter_1.matterSerialize)(content, fullMatter);
640
+ // ✅ CRITIQUE: Vérifier si le contenu a réellement changé avant de créer un commit
641
+ // Si le contenu n'a pas changé, ne pas créer de commit pour éviter de déplacer les notes Git
642
+ if (updatedContent === fileData.content) {
643
+ // Le contenu n'a pas changé, juste mettre à jour le cache sans créer de commit
644
+ console.log(` ℹ️ ${file}: Aucun changement nécessaire (ID ${fullMatter.id} déjà correct)`);
645
+ fixed++;
646
+ fixedFiles.add(fileRef);
647
+ continue;
648
+ }
556
649
  // ✅ IMPORTANT: Désactiver withID car l'ID est déjà généré et enregistré
557
650
  // Évite la double validation qui cause "ID déjà utilisé"
558
651
  await (0, repo_1.gitCreateOrEditFile)(this.git, file, branchName, updatedContent, { name: 'GitHealthManager', email: 'health@system' }, { ...this.config, canForce: true, withID: false });
@@ -618,6 +711,10 @@ class GitHealthManager {
618
711
  if (autoFix && (report.missingID.length > 0 || report.missingTitle.length > 0 || report.invalidID.length > 0 || report.duplicateID.length > 0)) {
619
712
  finalReport = await this.repairSafeIDs(report, { dryRun });
620
713
  }
714
+ // Calculer le nombre de fichiers réparés
715
+ // C'est la différence entre les problèmes avant et après réparation
716
+ const fixed = (report.missingID.length + report.missingTitle.length + report.invalidID.length + report.duplicateID.length) -
717
+ (finalReport.missingID.length + finalReport.missingTitle.length + finalReport.invalidID.length + finalReport.duplicateID.length);
621
718
  // Retourner dans l'ancien format pour compatibilité
622
719
  return {
623
720
  scanned: finalReport.scanned,
@@ -626,7 +723,7 @@ class GitHealthManager {
626
723
  missingTitle: finalReport.missingTitle.length,
627
724
  invalidID: finalReport.invalidID.length,
628
725
  duplicateID: finalReport.duplicateID.length,
629
- fixed: report.scanned - finalReport.scanned + finalReport.valid - report.valid,
726
+ fixed: Math.max(0, fixed), // S'assurer que fixed n'est pas négatif
630
727
  errors: finalReport.errors
631
728
  };
632
729
  }
@@ -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';