agentic-api 2.0.31 → 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 (102) hide show
  1. package/dist/src/agents/agents.example.js +21 -22
  2. package/dist/src/agents/authentication.js +1 -2
  3. package/dist/src/agents/prompts.d.ts +5 -4
  4. package/dist/src/agents/prompts.js +44 -87
  5. package/dist/src/agents/reducer.core.d.ts +24 -2
  6. package/dist/src/agents/reducer.core.js +125 -35
  7. package/dist/src/agents/reducer.loaders.d.ts +55 -1
  8. package/dist/src/agents/reducer.loaders.js +114 -1
  9. package/dist/src/agents/reducer.types.d.ts +45 -2
  10. package/dist/src/agents/semantic.js +1 -2
  11. package/dist/src/agents/simulator.d.ts +11 -3
  12. package/dist/src/agents/simulator.executor.d.ts +14 -4
  13. package/dist/src/agents/simulator.executor.js +81 -23
  14. package/dist/src/agents/simulator.js +128 -42
  15. package/dist/src/agents/simulator.prompts.d.ts +9 -7
  16. package/dist/src/agents/simulator.prompts.js +66 -86
  17. package/dist/src/agents/simulator.types.d.ts +23 -5
  18. package/dist/src/agents/simulator.utils.d.ts +7 -2
  19. package/dist/src/agents/simulator.utils.js +31 -11
  20. package/dist/src/agents/system.js +1 -2
  21. package/dist/src/execute/helpers.d.ts +75 -0
  22. package/dist/src/execute/helpers.js +139 -0
  23. package/dist/src/execute/index.d.ts +11 -0
  24. package/dist/src/execute/index.js +44 -0
  25. package/dist/src/execute/legacy.d.ts +46 -0
  26. package/dist/src/execute/legacy.js +460 -0
  27. package/dist/src/execute/modelconfig.d.ts +19 -0
  28. package/dist/src/execute/modelconfig.js +56 -0
  29. package/dist/src/execute/responses.d.ts +55 -0
  30. package/dist/src/execute/responses.js +594 -0
  31. package/dist/src/execute/shared.d.ts +83 -0
  32. package/dist/src/execute/shared.js +188 -0
  33. package/dist/src/index.d.ts +1 -1
  34. package/dist/src/index.js +2 -2
  35. package/dist/src/{princing.openai.d.ts → pricing.llm.d.ts} +6 -0
  36. package/dist/src/pricing.llm.js +255 -0
  37. package/dist/src/prompts.d.ts +13 -4
  38. package/dist/src/prompts.js +221 -114
  39. package/dist/src/rag/embeddings.d.ts +36 -18
  40. package/dist/src/rag/embeddings.js +131 -128
  41. package/dist/src/rag/index.d.ts +5 -5
  42. package/dist/src/rag/index.js +14 -17
  43. package/dist/src/rag/parser.d.ts +2 -1
  44. package/dist/src/rag/parser.js +11 -14
  45. package/dist/src/rag/rag.examples.d.ts +27 -0
  46. package/dist/src/rag/rag.examples.js +151 -0
  47. package/dist/src/rag/rag.manager.d.ts +383 -0
  48. package/dist/src/rag/rag.manager.js +1390 -0
  49. package/dist/src/rag/types.d.ts +128 -12
  50. package/dist/src/rag/types.js +100 -1
  51. package/dist/src/rag/usecase.d.ts +37 -0
  52. package/dist/src/rag/usecase.js +96 -7
  53. package/dist/src/rules/git/git.e2e.helper.js +22 -2
  54. package/dist/src/rules/git/git.health.d.ts +61 -2
  55. package/dist/src/rules/git/git.health.js +333 -11
  56. package/dist/src/rules/git/index.d.ts +2 -2
  57. package/dist/src/rules/git/index.js +13 -1
  58. package/dist/src/rules/git/repo.d.ts +160 -0
  59. package/dist/src/rules/git/repo.js +777 -0
  60. package/dist/src/rules/git/repo.pr.js +117 -13
  61. package/dist/src/rules/git/repo.tools.d.ts +22 -1
  62. package/dist/src/rules/git/repo.tools.js +50 -1
  63. package/dist/src/rules/types.d.ts +27 -14
  64. package/dist/src/rules/utils.matter.d.ts +0 -4
  65. package/dist/src/rules/utils.matter.js +35 -7
  66. package/dist/src/scrapper.d.ts +15 -22
  67. package/dist/src/scrapper.js +58 -110
  68. package/dist/src/stategraph/index.d.ts +1 -1
  69. package/dist/src/stategraph/stategraph.d.ts +56 -2
  70. package/dist/src/stategraph/stategraph.js +134 -6
  71. package/dist/src/stategraph/stategraph.storage.js +8 -0
  72. package/dist/src/stategraph/types.d.ts +27 -0
  73. package/dist/src/types.d.ts +46 -9
  74. package/dist/src/types.js +8 -7
  75. package/dist/src/usecase.d.ts +11 -2
  76. package/dist/src/usecase.js +27 -35
  77. package/dist/src/utils.d.ts +32 -18
  78. package/dist/src/utils.js +87 -129
  79. package/package.json +10 -3
  80. package/dist/src/agents/digestor.test.d.ts +0 -1
  81. package/dist/src/agents/digestor.test.js +0 -45
  82. package/dist/src/agents/reducer.example.d.ts +0 -28
  83. package/dist/src/agents/reducer.example.js +0 -118
  84. package/dist/src/agents/reducer.process.d.ts +0 -16
  85. package/dist/src/agents/reducer.process.js +0 -143
  86. package/dist/src/agents/reducer.tools.d.ts +0 -29
  87. package/dist/src/agents/reducer.tools.js +0 -157
  88. package/dist/src/agents/simpleExample.d.ts +0 -3
  89. package/dist/src/agents/simpleExample.js +0 -38
  90. package/dist/src/agents/system-review.d.ts +0 -5
  91. package/dist/src/agents/system-review.js +0 -181
  92. package/dist/src/agents/systemReview.d.ts +0 -4
  93. package/dist/src/agents/systemReview.js +0 -22
  94. package/dist/src/execute.d.ts +0 -49
  95. package/dist/src/execute.js +0 -564
  96. package/dist/src/princing.openai.js +0 -54
  97. package/dist/src/rag/tools.d.ts +0 -76
  98. package/dist/src/rag/tools.js +0 -196
  99. package/dist/src/rules/user.mapper.d.ts +0 -61
  100. package/dist/src/rules/user.mapper.js +0 -160
  101. package/dist/src/rules/utils/slug.d.ts +0 -22
  102. package/dist/src/rules/utils/slug.js +0 -35
@@ -0,0 +1,1390 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RAGManager = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ const types_1 = require("./types");
10
+ const embeddings_1 = require("./embeddings");
11
+ const parser_1 = require("./parser");
12
+ const usecase_1 = require("./usecase");
13
+ /** Configuration RAG par défaut */
14
+ const DEFAULT_RAG_CONFIG = {
15
+ dimensions: 1536,
16
+ distance: 'cosine',
17
+ version: '1.0',
18
+ startId: 1000,
19
+ hnswConfig: { ef: 200, m: 32 }
20
+ };
21
+ const DEFAULT_RAG_INSTANCE = {};
22
+ /**
23
+ * Gestionnaire multi-RAG stateless pour gérer plusieurs index RAG
24
+ */
25
+ class RAGManager {
26
+ constructor(config) {
27
+ this.cachedRegistry = null;
28
+ this.lastLoadTime = 0;
29
+ /**
30
+ * Map des embeddings chargés pour permettre les updates réactifs
31
+ * Note: Pour une meilleure gestion mémoire, appeler cleanDeadReferences() périodiquement
32
+ */
33
+ this.loadedEmbeddings = new Map();
34
+ if (!config.baseDir || !(0, fs_1.existsSync)(config.baseDir)) {
35
+ throw new Error(`Le dossier du registre RAG n'existe pas: ${config.baseDir}`);
36
+ }
37
+ this.config = {
38
+ tempDir: path_1.default.join(config.baseDir, 'temp'),
39
+ archiveDir: path_1.default.join(config.baseDir, 'archives'),
40
+ maxArchives: 5,
41
+ ...config
42
+ };
43
+ this.registryFile = path_1.default.join(this.config.baseDir, 'registry.json');
44
+ // Créer les dossiers nécessaires
45
+ this.ensureDirectories();
46
+ // Charger le registre initial
47
+ this.loadRegistry();
48
+ //
49
+ // store instance in memory
50
+ DEFAULT_RAG_INSTANCE[this.config.baseDir] = this;
51
+ const registry = this.getRegistry();
52
+ const defaultName = registry.defaultName || 'RAGRegistry';
53
+ DEFAULT_RAG_INSTANCE[defaultName] = this;
54
+ }
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);
62
+ }
63
+ /**
64
+ * Nettoie le cache des instances (utile pour les tests)
65
+ */
66
+ static clearCache() {
67
+ Object.keys(DEFAULT_RAG_INSTANCE).forEach(key => {
68
+ delete DEFAULT_RAG_INSTANCE[key];
69
+ });
70
+ }
71
+ /**
72
+ * Crée les dossiers nécessaires s'ils n'existent pas
73
+ */
74
+ ensureDirectories() {
75
+ [this.config.baseDir, this.config.tempDir, this.config.archiveDir].forEach(dir => {
76
+ if (!(0, fs_1.existsSync)(dir)) {
77
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
78
+ }
79
+ });
80
+ }
81
+ /**
82
+ * Charge le registre depuis le fichier JSON et scan les dossiers
83
+ */
84
+ loadRegistry() {
85
+ this.lastLoadTime = Date.now();
86
+ // Charger le registre existant ou créer un nouveau
87
+ let registry;
88
+ if ((0, fs_1.existsSync)(this.registryFile)) {
89
+ try {
90
+ registry = JSON.parse((0, fs_1.readFileSync)(this.registryFile, 'utf8'));
91
+ }
92
+ catch (error) {
93
+ console.warn('Erreur lors du chargement du registre, création d\'un nouveau:', error);
94
+ registry = {
95
+ version: '1.0',
96
+ registries: {},
97
+ defaultName: 'RAGRegistry'
98
+ };
99
+ }
100
+ }
101
+ else {
102
+ registry = {
103
+ version: '1.0',
104
+ registries: {},
105
+ defaultName: 'RAGRegistry'
106
+ };
107
+ }
108
+ // Définir defaultName par défaut s'il n'est pas défini
109
+ if (!registry.defaultName) {
110
+ registry.defaultName = 'RAGRegistry';
111
+ }
112
+ // Scanner les dossiers pour découvrir les RAG existants
113
+ this.scanDirectoriesForRAGs(registry);
114
+ // Sauvegarder le registre mis à jour (seulement si des changements détectés)
115
+ this.saveRegistry(registry);
116
+ // Mettre en cache
117
+ this.cachedRegistry = registry;
118
+ return registry;
119
+ }
120
+ /**
121
+ * Scanne les dossiers pour découvrir les RAG existants
122
+ */
123
+ scanDirectoriesForRAGs(registry) {
124
+ if (!(0, fs_1.existsSync)(this.config.baseDir)) {
125
+ return;
126
+ }
127
+ const entries = (0, fs_1.readdirSync)(this.config.baseDir, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ if (entry.isDirectory() &&
130
+ entry.name !== 'temp' &&
131
+ entry.name !== 'archives' &&
132
+ entry.name !== 'registry.json') {
133
+ const ragPath = path_1.default.join(this.config.baseDir, entry.name);
134
+ const metadataFile = path_1.default.join(ragPath, 'rag-metadata.json');
135
+ // Vérifier si c'est un dossier RAG valide
136
+ if ((0, fs_1.existsSync)(metadataFile)) {
137
+ // Lire les métadonnées pour obtenir le nom d'alias potentiel
138
+ let aliasName;
139
+ try {
140
+ const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
141
+ aliasName = metadata.name || metadata.displayName;
142
+ }
143
+ catch (e) {
144
+ // Ignorer les erreurs de parsing
145
+ }
146
+ // Ajouter ou mettre à jour l'entrée dans le registre
147
+ if (!registry.registries[entry.name]) {
148
+ const ragEntry = {
149
+ name: aliasName || entry.name, // Utiliser l'alias si disponible
150
+ configPath: ragPath,
151
+ status: 'active'
152
+ };
153
+ // Ajouter sous la clé du dossier
154
+ registry.registries[entry.name] = ragEntry;
155
+ // Ajouter aussi sous l'alias si différent
156
+ if (aliasName && aliasName !== entry.name) {
157
+ registry.registries[aliasName] = ragEntry;
158
+ }
159
+ }
160
+ else {
161
+ // Mettre à jour le chemin si nécessaire
162
+ registry.registries[entry.name].configPath = ragPath;
163
+ // Mettre à jour aussi l'alias si présent
164
+ const existingName = registry.registries[entry.name].name;
165
+ if (existingName && existingName !== entry.name && registry.registries[existingName]) {
166
+ registry.registries[existingName].configPath = ragPath;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Vérifie si un RAG existe dans le registre (peu importe son statut)
175
+ * Recherche par clé du dictionnaire OU par entry.name
176
+ */
177
+ exists(name) {
178
+ const registry = this.getRegistry();
179
+ // Chercher d'abord par clé (comportement normal)
180
+ const entry = name in registry.registries || Object.values(registry.registries).some(entry => entry.name === name);
181
+ return entry;
182
+ }
183
+ /**
184
+ * Obtient le registre en cache ou le recharge si nécessaire
185
+ */
186
+ getRegistry() {
187
+ const timeout = 3600 * 4; // 4 hours
188
+ if (!this.cachedRegistry || Date.now() - this.lastLoadTime > timeout) {
189
+ return this.loadRegistry();
190
+ }
191
+ return this.cachedRegistry;
192
+ }
193
+ /**
194
+ * Sauvegarde le registre dans le fichier JSON
195
+ */
196
+ saveRegistry(registry) {
197
+ try {
198
+ this.cachedRegistry = registry;
199
+ (0, fs_1.writeFileSync)(this.registryFile, JSON.stringify(registry, null, 2), 'utf8');
200
+ }
201
+ catch (error) {
202
+ console.error('Erreur lors de la sauvegarde du registre RAG:', error);
203
+ throw new Error(`Impossible de sauvegarder le registre RAG: ${error}`);
204
+ }
205
+ }
206
+ /**
207
+ * Notifie les embeddings chargés de se mettre à jour
208
+ *
209
+ * @param name Nom du RAG à mettre à jour
210
+ * @param opts Options de mise à jour (action: 'rename' pour renommer un document)
211
+ *
212
+ * FIXME: Gérer le cas où l'embedding est en cours d'exécution (recherche/indexation)
213
+ * Actuellement, l'update est appelé immédiatement sans vérifier si l'embedding est busy.
214
+ * Solutions possibles:
215
+ * - Ajouter un flag `isBusy` dans Embeddings
216
+ * - Implémenter une queue d'updates
217
+ * - Utiliser un système de locks
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * // Après un build
222
+ * this.notifyUpdate('procedures-stable');
223
+ *
224
+ * // Après un rename de document
225
+ * this.notifyUpdate('procedures-stable', {
226
+ * action: 'rename',
227
+ * oldFile: 'old.md',
228
+ * newFile: 'new.md'
229
+ * });
230
+ * ```
231
+ */
232
+ notifyUpdate(name, opts) {
233
+ const embeddingData = this.loadedEmbeddings.get(name);
234
+ console.log(`\n🔔 notifyUpdate('${name}') appelé`);
235
+ console.log(` Embedding en cache: ${embeddingData ? 'OUI' : 'NON'}`);
236
+ // Si l'embedding n'est pas encore chargé, rien à faire
237
+ // Il sera chargé avec la nouvelle config au prochain load()
238
+ if (!embeddingData) {
239
+ console.log(` ⏭️ Skip: sera chargé avec la nouvelle config au prochain load()`);
240
+ return;
241
+ }
242
+ // Charger la nouvelle configuration depuis le registre
243
+ const entry = this.getEntry(name);
244
+ if (!entry) {
245
+ console.log(` ⚠️ Entry '${name}' introuvable dans le registre`);
246
+ return;
247
+ }
248
+ console.log(` 📁 entry.configPath: ${entry.configPath}`);
249
+ // L'embedding est chargé, forcer son rechargement via update()
250
+ // update() modifie l'instance EN PLACE, donc tous ceux qui ont une référence voient la nouvelle version
251
+ const { embedding } = embeddingData;
252
+ try {
253
+ const newConfig = this.loadConfig(entry.configPath);
254
+ console.log(` 🔄 Appel de embedding.update() avec baseDir: ${newConfig.baseDir}`);
255
+ // FIXME: Vérifier si l'embedding est busy avant de faire l'update
256
+ // update() va recharger l'index, les métadonnées et le mapping depuis newConfig.baseDir
257
+ // update() modifie l'instance en place, donc tous les utilisateurs voient la nouvelle version
258
+ embedding.update(newConfig);
259
+ // Mettre à jour le timestamp du cache
260
+ embeddingData.loadTime = Date.now();
261
+ console.log(` ✅ Embedding rechargé avec succès`);
262
+ console.log(` ✅ embedding.isReady(): ${embedding.isReady()}`);
263
+ }
264
+ catch (error) {
265
+ console.error(` ❌ Erreur lors de la mise à jour de l'embedding '${name}':`, error);
266
+ // En cas d'erreur, update() a déjà fait un rollback de sa propre config
267
+ // L'instance continue de fonctionner avec l'ancienne config
268
+ // Ne pas throw pour éviter de bloquer le flow
269
+ }
270
+ }
271
+ /**
272
+ * Génère un nom d'archive avec timestamp YYYMMDD
273
+ */
274
+ generateArchiveName(name) {
275
+ const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
276
+ return `${name}-${timestamp}`;
277
+ }
278
+ /**
279
+ * Archive un RAG existant
280
+ */
281
+ async archive(name) {
282
+ const entry = this.getEntry(name);
283
+ if (!entry || !(0, fs_1.existsSync)(entry.configPath)) {
284
+ return null;
285
+ }
286
+ const archiveName = this.generateArchiveName(name);
287
+ const archivePath = path_1.default.join(this.config.archiveDir, archiveName);
288
+ try {
289
+ // Copier le dossier vers les archives
290
+ (0, fs_1.renameSync)(entry.configPath, archivePath);
291
+ if (this.config.verbose)
292
+ console.log(`✅ RAG archivé: ${name} -> ${archiveName}`);
293
+ // Nettoyer les anciennes archives si nécessaire
294
+ await this.cleanOldArchives(name);
295
+ return archiveName;
296
+ }
297
+ catch (error) {
298
+ console.error(`Erreur lors de l'archivage de ${name}:`, error);
299
+ return null;
300
+ }
301
+ }
302
+ /**
303
+ * Charge la configuration depuis le fichier metadata
304
+ */
305
+ loadConfig(configPath) {
306
+ const metadataFile = path_1.default.join(configPath, 'rag-metadata.json');
307
+ if (!(0, fs_1.existsSync)(metadataFile)) {
308
+ throw new Error(`Fichier metadata manquant: ${metadataFile}`);
309
+ }
310
+ try {
311
+ const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
312
+ // Extraire la configuration depuis les métadonnées
313
+ const ragConfig = {
314
+ baseDir: configPath,
315
+ dimensions: metadata.config?.dimensions || 1536,
316
+ distance: metadata.config?.distance || 'cosine',
317
+ version: metadata.config?.version || metadata.version || '1.0',
318
+ startId: metadata.config?.startId || 1000,
319
+ hnswConfig: metadata.config?.hnswConfig || { ef: 200, m: 32 }
320
+ };
321
+ return ragConfig;
322
+ }
323
+ catch (error) {
324
+ throw new Error(`Erreur lors du chargement de la configuration RAG: ${error}`);
325
+ }
326
+ }
327
+ /**
328
+ * Sauvegarde les métadonnées RAG dans un dossier
329
+ *
330
+ * @param dirPath Chemin du dossier où sauvegarder les métadonnées
331
+ * @param metadata Métadonnées complètes du RAG
332
+ * @private
333
+ */
334
+ saveMetadata(dirPath, metadata) {
335
+ const metadataFile = path_1.default.join(dirPath, types_1.RAG_FILES.METADATA);
336
+ try {
337
+ (0, fs_1.writeFileSync)(metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
338
+ if (this.config.verbose) {
339
+ console.log(`✅ Métadonnées sauvegardées: ${metadataFile}`);
340
+ }
341
+ }
342
+ catch (error) {
343
+ throw new Error(`Échec de la sauvegarde des métadonnées: ${error}`);
344
+ }
345
+ }
346
+ /**
347
+ * Sauvegarde le mapping RAG dans un dossier
348
+ *
349
+ * @param dirPath Chemin du dossier où sauvegarder le mapping
350
+ * @param mapping Mapping ID → ref des sections
351
+ * @param buildResult Résultat de la construction pour les métadonnées du mapping
352
+ * @private
353
+ */
354
+ saveMapping(dirPath, mapping, buildResult) {
355
+ const mappingFile = path_1.default.join(dirPath, types_1.RAG_FILES.MAPPING);
356
+ const mappingData = {
357
+ type: 'mapping',
358
+ version: buildResult.version,
359
+ tag: buildResult.tag,
360
+ mapping,
361
+ createdAt: new Date().toISOString()
362
+ };
363
+ try {
364
+ (0, fs_1.writeFileSync)(mappingFile, JSON.stringify(mappingData, null, 2), 'utf8');
365
+ if (this.config.verbose) {
366
+ console.log(`✅ Mapping sauvegardé: ${mappingFile}`);
367
+ }
368
+ }
369
+ catch (error) {
370
+ throw new Error(`Échec de la sauvegarde du mapping: ${error}`);
371
+ }
372
+ }
373
+ /**
374
+ * Nettoie les anciennes archives d'un RAG
375
+ */
376
+ async cleanOldArchives(name) {
377
+ if (!this.config.maxArchives || this.config.maxArchives <= 0)
378
+ return;
379
+ try {
380
+ const archives = (0, fs_1.readdirSync)(this.config.archiveDir)
381
+ .filter(dir => dir.startsWith(`${name}-`))
382
+ .sort()
383
+ .reverse(); // Plus récent en premier
384
+ if (archives.length > this.config.maxArchives) {
385
+ const toDelete = archives.slice(this.config.maxArchives);
386
+ for (const archive of toDelete) {
387
+ const archivePath = path_1.default.join(this.config.archiveDir, archive);
388
+ (0, fs_1.rmSync)(archivePath, { recursive: true, force: true });
389
+ console.log(`🗑️ Archive supprimée: ${archive}`);
390
+ }
391
+ }
392
+ }
393
+ catch (error) {
394
+ console.warn(`Erreur lors du nettoyage des archives pour ${name}:`, error);
395
+ }
396
+ }
397
+ /**
398
+ * Récupère l'entrée d'un RAG dans le registre
399
+ *
400
+ * @param name Nom du RAG
401
+ * @returns L'entrée du registre ou undefined si non trouvé
402
+ */
403
+ getEntry(name) {
404
+ const registry = this.getRegistry();
405
+ // Chercher d'abord par clé (comportement normal)
406
+ const entry = registry.registries[name] || Object.values(registry.registries).find(e => e.name === name);
407
+ return entry;
408
+ }
409
+ /**
410
+ * Crée un nouveau RAG dans le registre et retourne le chemin temporaire
411
+ *
412
+ * Le registre stocke:
413
+ * - configPath: le tempPath (source de construction)
414
+ * - destDir: le chemin de destination finale
415
+ * - status: 'building'
416
+ * - description: label descriptif court (optionnel)
417
+ *
418
+ * Après build(), configPath sera remplacé par destDir et status = 'active'
419
+ *
420
+ * @param name Nom du RAG
421
+ * @param options Options de création
422
+ * @returns Chemin du dossier temporaire créé
423
+ *
424
+ * @example
425
+ * ```typescript
426
+ * // Création simple
427
+ * await ragManager.create('my-rag', { description: 'Mon RAG' });
428
+ *
429
+ * // Création avec fork
430
+ * await ragManager.create('child-rag', {
431
+ * description: 'PR validation',
432
+ * fork: 'parent-rag',
433
+ * exclude: ['modif-1.md', 'modif-2.md']
434
+ * });
435
+ * ```
436
+ */
437
+ async create(name, options) {
438
+ if (this.exists(name)) {
439
+ throw new Error(`RAG '${name}' existe déjà`);
440
+ }
441
+ // Support legacy: string → description
442
+ const opts = typeof options === 'string' ? { description: options } : (options || {});
443
+ // Créer dossier temporaire avec timestamp
444
+ const tempPath = path_1.default.join(this.config.tempDir, `${name}-${Date.now()}`);
445
+ (0, fs_1.mkdirSync)(tempPath, { recursive: true });
446
+ // Chemin de destination finale
447
+ const destDir = path_1.default.join(this.config.baseDir, name);
448
+ // Créer l'entrée dans le registre
449
+ const registry = this.getRegistry();
450
+ registry.registries[name] = {
451
+ name,
452
+ configPath: tempPath, // Source temporaire (tant que building)
453
+ destDir: destDir, // Destination finale (pour après build)
454
+ status: 'building',
455
+ description: opts.description, // Label descriptif court
456
+ checkpoint: opts.checkpoint // Hash du dernier commit
457
+ };
458
+ // Définir comme défaut si c'est le premier
459
+ if (Object.keys(registry.registries).length === 1) {
460
+ registry.defaultName = name;
461
+ }
462
+ this.saveRegistry(registry);
463
+ if (this.config.verbose)
464
+ console.log(`✅ RAG créé: ${name} (temp: ${tempPath}, dest: ${destDir})`);
465
+ // Si option fork, copier les documents du parent
466
+ if (opts.fork) {
467
+ if (this.config.verbose) {
468
+ console.log(`🔄 Fork depuis '${opts.fork}'...`);
469
+ }
470
+ const forkResult = this.fork(opts.fork, name, { exclude: opts.exclude });
471
+ if (this.config.verbose) {
472
+ console.log(`✅ Fork terminé: ${forkResult.documents} documents copiés (${forkResult.files} fichiers)`);
473
+ }
474
+ }
475
+ return tempPath; // Retourner le chemin temp
476
+ }
477
+ /**
478
+ * Sauvegarde un document dans le dossier de construction d'un RAG
479
+ *
480
+ * Le registre est consulté pour trouver le dossier (configPath en mode building)
481
+ *
482
+ * @param name Nom du RAG
483
+ * @param filename Nom du fichier (ex: 'my-document.md')
484
+ * @param content Contenu markdown avec front-matter
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * await ragManager.create('my-rag');
489
+ * ragManager.addDocument('my-rag', 'procedure.md', markdownContent);
490
+ * ```
491
+ */
492
+ addDocument(name, filename, content) {
493
+ // Récupérer le chemin depuis le registre
494
+ const entry = this.getEntry(name);
495
+ if (!entry) {
496
+ throw new Error(`RAG '${name}' n'existe pas`);
497
+ }
498
+ // if (status.status !== 'building') {
499
+ // throw new Error(`RAG '${name}' n'est pas en construction (status: ${status.status})`);
500
+ // }
501
+ const buildPath = entry.configPath;
502
+ if (!(0, fs_1.existsSync)(buildPath)) {
503
+ throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
504
+ }
505
+ const filePath = path_1.default.join(buildPath, filename);
506
+ const shaPath = path_1.default.join(buildPath, filename + '.sha');
507
+ // ✅ Sauvegarder le fichier
508
+ (0, fs_1.writeFileSync)(filePath, content, 'utf8');
509
+ // ✅ Sauvegarder le fingerprint
510
+ const sha = (0, usecase_1.calculateFingerprint)(content);
511
+ (0, fs_1.writeFileSync)(shaPath, sha, 'utf8');
512
+ }
513
+ /**
514
+ * Prépare un RAG enfant en copiant les documents d'un RAG parent
515
+ *
516
+ * Cette méthode fork un RAG existant en copiant tous ses documents:
517
+ * - `filename.md` - Fichier markdown original
518
+ * - `filename.md.sha` - Empreinte SHA du fichier original
519
+ * - `filename.enhanced.md` - Version enrichie (si disponible)
520
+ * - `filename.query.json` - Métadonnées et use cases (si disponible)
521
+ *
522
+ * Les fichiers d'index (vectors.dat, mapping.json, metadata.json) ne sont PAS copiés
523
+ * car ils seront reconstruits par build()
524
+ *
525
+ * @param fromRagName Nom du RAG parent
526
+ * @param toRagName Nom du RAG enfant (doit déjà exister via create())
527
+ * @param options Options de fork
528
+ * @returns Objet avec le nombre de documents et fichiers copiés
529
+ *
530
+ * @example
531
+ * ```typescript
532
+ * // Créer le RAG enfant
533
+ * await ragManager.create('rule-validation-1', 'PR validation');
534
+ *
535
+ * // Fork depuis le parent en excluant les fichiers modifiés
536
+ * const result = await ragManager.fork('rule-editor', 'rule-validation-1', {
537
+ * exclude: ['modif-1.md', 'modif-2.md']
538
+ * });
539
+ * console.log(`✅ ${result.documents} documents copiés (${result.files} fichiers)`);
540
+ *
541
+ * // Ajouter les documents modifiés
542
+ * ragManager.addDocument('rule-validation-1', 'modif-1.md', newContent1);
543
+ * ragManager.addDocument('rule-validation-1', 'modif-2.md', newContent2);
544
+ *
545
+ * // Builder l'index
546
+ * await ragManager.build('rule-validation-1', allDocs);
547
+ * ```
548
+ */
549
+ fork(fromRagName, toRagName, options = {}) {
550
+ const excludes = options.exclude || [];
551
+ if (this.config.verbose) {
552
+ console.log(`🔄 Fork de '${fromRagName}' vers '${toRagName}' (${excludes.length} exclusions)`);
553
+ }
554
+ const result = this.addDocumentFrom(fromRagName, toRagName, excludes);
555
+ if (this.config.verbose) {
556
+ console.log(`✅ Fork terminé: ${result.documents} documents copiés (${result.files} fichiers)`);
557
+ }
558
+ return result;
559
+ }
560
+ /**
561
+ * [USAGE INTERNE] Copie tous les documents d'un RAG parent vers un RAG destination
562
+ *
563
+ * Cette méthode copie tous les fichiers associés aux documents indexés:
564
+ * - `filename.md` - Fichier markdown original
565
+ * - `filename.md.sha` - Empreinte SHA du fichier original
566
+ * - `filename.enhanced.md` - Version enrichie (si disponible)
567
+ * - `filename.query.json` - Métadonnées et use cases (si disponible)
568
+ *
569
+ * @private
570
+ * @param fromRagName Nom du RAG source
571
+ * @param toRagName Nom du RAG destination
572
+ * @param excludes Liste des fichiers à ne PAS copier (ex: ['procedure-1.md', 'procedure-2.md'])
573
+ * @returns Objet avec le nombre de documents copiés et le nombre de fichiers copiés
574
+ */
575
+ addDocumentFrom(fromRagName, toRagName, excludes = []) {
576
+ // Récupérer le RAG source
577
+ const source = this.getEntry(fromRagName);
578
+ if (!source) {
579
+ throw new Error(`RAG source '${fromRagName}' n'existe pas`);
580
+ }
581
+ // Vérifier que le RAG source est actif
582
+ if (source.status !== 'active') {
583
+ throw new Error(`RAG source '${fromRagName}' n'est pas actif (status: ${source.status})`);
584
+ }
585
+ // Récupérer le RAG destination
586
+ const dest = this.getEntry(toRagName);
587
+ if (!dest) {
588
+ throw new Error(`RAG destination '${toRagName}' n'existe pas`);
589
+ }
590
+ // Charger les métadonnées du parent pour obtenir la liste des documents
591
+ let parentMetadata;
592
+ try {
593
+ const parentEmbeddings = this.load(fromRagName);
594
+ parentMetadata = parentEmbeddings.getMetadata();
595
+ }
596
+ catch (error) {
597
+ throw new Error(`Impossible de charger les métadonnées du RAG '${fromRagName}': ${error}`);
598
+ }
599
+ // Créer un Set des fichiers à exclure (normaliser les noms)
600
+ const excludeSet = new Set(excludes.map(f => f.replace(/\.md$/, '')));
601
+ // Liste des extensions de fichiers à copier
602
+ const extensions = [
603
+ '.md', // Fichier original
604
+ '.md.sha', // Empreinte
605
+ '.enhanced.md', // Version enrichie
606
+ '.query.json' // Métadonnées et use cases
607
+ ];
608
+ let copiedDocuments = 0;
609
+ let copiedFiles = 0;
610
+ // Parcourir tous les documents dans les métadonnées
611
+ for (const docRef of Object.values(parentMetadata.documents)) {
612
+ const baseName = docRef.filename.replace(/\.md$/, '');
613
+ // Vérifier si ce document est dans la liste d'exclusion
614
+ if (excludeSet.has(baseName)) {
615
+ continue;
616
+ }
617
+ let documentCopied = false;
618
+ // Copier tous les fichiers existants pour ce document
619
+ for (const ext of extensions) {
620
+ const sourceFile = path_1.default.join(source.configPath, baseName + ext);
621
+ const destFile = path_1.default.join(dest.configPath, baseName + ext);
622
+ if ((0, fs_1.existsSync)(sourceFile)) {
623
+ (0, fs_1.copyFileSync)(sourceFile, destFile);
624
+ copiedFiles++;
625
+ documentCopied = true;
626
+ }
627
+ }
628
+ if (documentCopied) {
629
+ copiedDocuments++;
630
+ }
631
+ }
632
+ // Note: Les fichiers d'index (vectors.dat, mapping.json, metadata.json)
633
+ // ne sont PAS copiés car ils seront reconstruits par build()
634
+ return { documents: copiedDocuments, files: copiedFiles };
635
+ }
636
+ /**
637
+ * Compare le contenu d'un document avec celui stocké dans le RAG
638
+ *
639
+ * @param name Nom du RAG
640
+ * @param file Nom du fichier (ex: 'ppe-budget.md')
641
+ * @param content Contenu à comparer
642
+ * @returns true si identique, false sinon
643
+ */
644
+ documentEqual(name, file, content) {
645
+ const entry = this.getEntry(name);
646
+ if (!entry) {
647
+ throw new Error(`RAG '${name}' n'existe pas`);
648
+ }
649
+ const configPath = entry.configPath;
650
+ const mdPath = path_1.default.join(configPath, file);
651
+ const shaPath = path_1.default.join(configPath, file + '.sha');
652
+ // Vérifier si le fichier et son sha existent
653
+ if (!(0, fs_1.existsSync)(mdPath) || !(0, fs_1.existsSync)(shaPath)) {
654
+ return false; // Fichier n'existe pas encore
655
+ }
656
+ // Comparer les fingerprints
657
+ try {
658
+ const storedSha = (0, fs_1.readFileSync)(shaPath, 'utf8');
659
+ const newSha = (0, usecase_1.calculateFingerprint)(content);
660
+ return storedSha === newSha;
661
+ }
662
+ catch (error) {
663
+ return false; // Erreur de lecture = considérer comme différent
664
+ }
665
+ }
666
+ /**
667
+ * Charge les use cases d'un document depuis un RAG
668
+ *
669
+ * @param name Nom du RAG
670
+ * @param filename Nom du fichier de base (ex: 'procedure.md')
671
+ * @returns Les use cases du document
672
+ *
673
+ * @example
674
+ * ```typescript
675
+ * const queries = ragManager.loadUseCases('my-rag', 'procedure.md');
676
+ * console.log(`${queries.queries.length} use cases trouvés`);
677
+ * ```
678
+ */
679
+ loadUseCases(name, filename) {
680
+ const entry = this.getEntry(name);
681
+ if (!entry) {
682
+ throw new Error(`RAG '${name}' n'existe pas`);
683
+ }
684
+ const configPath = entry.configPath;
685
+ const baseName = filename.replace(/\.md$/, '');
686
+ const queryFile = baseName + '.query.json';
687
+ return (0, usecase_1.loadUseCases)(queryFile, { baseDir: configPath });
688
+ }
689
+ /**
690
+ * Construit un RAG de manière atomique
691
+ *
692
+ * Utilise le registre pour trouver le dossier de construction (configPath)
693
+ * et la destination finale (destDir)
694
+ *
695
+ * @param name Nom du RAG
696
+ * @param documents Documents parsés (optionnel, sinon charge depuis configPath)
697
+ * @param ragConfig Configuration optionnelle du RAG
698
+ */
699
+ async build(name, documents, ragConfig) {
700
+ let docs;
701
+ let isLegacyMode = false;
702
+ let entry;
703
+ let buildPath;
704
+ let destDir;
705
+ // Vérifier que le RAG existe
706
+ if (!this.exists(name)) {
707
+ throw new Error(`RAG '${name}' n'existe pas. Appelez d'abord create() ou create() avec fork.`);
708
+ }
709
+ entry = this.getEntry(name);
710
+ buildPath = entry.configPath;
711
+ destDir = entry.destDir;
712
+ // Déterminer le mode
713
+ if (documents) {
714
+ // ✅ Mode legacy : documents fournis
715
+ isLegacyMode = true;
716
+ docs = documents;
717
+ // Valider les documents
718
+ for (const document of docs) {
719
+ (0, types_1.isValidRAGMatter)(document.matter);
720
+ }
721
+ }
722
+ else {
723
+ // ✅ Mode normal : charger depuis le dossier
724
+ if (!(0, fs_1.existsSync)(buildPath)) {
725
+ throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
726
+ }
727
+ // ✅ Charger les fichiers : .enhanced.md en priorité, sinon .md
728
+ docs = await this._loadDocumentsFromPath(buildPath);
729
+ }
730
+ console.log(`🔄 Construction du RAG: ${name}`);
731
+ // Marquer comme en construction
732
+ const registry = this.getRegistry();
733
+ entry.status = 'building';
734
+ this.cachedRegistry = { ...registry };
735
+ try {
736
+ // Configuration pour la construction
737
+ const buildConfig = {
738
+ baseDir: buildPath,
739
+ ...DEFAULT_RAG_CONFIG,
740
+ ...ragConfig
741
+ };
742
+ // Construire dans le dossier de construction
743
+ const embeddings = new embeddings_1.Embeddings(buildConfig);
744
+ // ✅ NOUVEAU : Récupérer le résultat de la construction
745
+ const buildResult = await embeddings.indexFromDocuments(docs);
746
+ console.log(`✅ Construction terminée dans: ${buildPath}`);
747
+ // ✅ NOUVEAU : Créer et sauvegarder les métadonnées
748
+ const metadata = {
749
+ version: buildResult.version,
750
+ tag: buildResult.tag,
751
+ createdAt: new Date().toISOString(),
752
+ config: buildConfig,
753
+ documents: buildResult.documents,
754
+ stats: {
755
+ totalDocuments: docs.length,
756
+ totalSections: buildResult.totalVectors,
757
+ vectorDimensions: buildResult.vectorDimensions,
758
+ indexSize: `${Math.round(buildResult.totalVectors * buildResult.vectorDimensions * 4 / 1024 / 1024)}MB`
759
+ },
760
+ files: {
761
+ vectorsFile: types_1.RAG_FILES.VECTORS,
762
+ mappingFile: types_1.RAG_FILES.MAPPING,
763
+ queriesFile: types_1.RAG_FILES.QUERIES
764
+ }
765
+ };
766
+ //
767
+ // active the RAG after the build is successful
768
+ entry.status = 'active';
769
+ // Sauvegarder les métadonnées et le mapping
770
+ this.saveMetadata(buildPath, metadata);
771
+ this.saveMapping(buildPath, buildResult.mapping, buildResult);
772
+ // ✅ En mode legacy, on construit directement dans configPath (pas de déplacement)
773
+ if (!isLegacyMode && destDir) {
774
+ // Archiver l'ancienne version si elle existe
775
+ if ((0, fs_1.existsSync)(destDir)) {
776
+ await this.archive(name);
777
+ }
778
+ // Basculement atomique: buildPath -> destDir
779
+ (0, fs_1.renameSync)(buildPath, destDir);
780
+ // Mettre à jour configPath avec destDir
781
+ entry.configPath = destDir;
782
+ delete entry.destDir;
783
+ }
784
+ // Marquer comme actif
785
+ entry.status = 'active';
786
+ this.cachedRegistry = { ...registry };
787
+ this.saveRegistry(registry);
788
+ console.log(`✅ RAG '${name}' construit avec succès`);
789
+ console.log(` 📁 configPath final: ${entry.configPath}`);
790
+ console.log(` 📦 Embeddings chargés en cache: ${this.loadedEmbeddings.size}`);
791
+ console.log(` 🔍 Embedding '${name}' dans le cache: ${this.loadedEmbeddings.has(name) ? 'OUI' : 'NON'}`);
792
+ // Notifier les embeddings chargés de se mettre à jour
793
+ this.notifyUpdate(name);
794
+ }
795
+ catch (error) {
796
+ // Nettoyer en cas d'erreur (seulement en mode legacy)
797
+ if (isLegacyMode && (0, fs_1.existsSync)(buildPath)) {
798
+ (0, fs_1.rmSync)(buildPath, { recursive: true, force: true });
799
+ }
800
+ entry.status = 'error';
801
+ this.cachedRegistry = { ...registry };
802
+ console.error(`❌ Erreur lors de la construction de '${name}':`, error);
803
+ throw error;
804
+ }
805
+ }
806
+ /**
807
+ * Charge tous les documents depuis un dossier
808
+ * Version interne qui prend un chemin direct
809
+ *
810
+ * @param baseDir Chemin du dossier
811
+ * @param endsWith Extension des fichiers à charger
812
+ * @returns Liste des documents parsés et validés
813
+ */
814
+ async _loadDocumentsFromPath(baseDir, endsWith) {
815
+ const docs = [];
816
+ if (endsWith) {
817
+ // ✅ Mode legacy : charger les fichiers avec une extension spécifique
818
+ const files = (0, fs_1.readdirSync)(baseDir).filter(f => f.endsWith(endsWith));
819
+ for (const file of files) {
820
+ try {
821
+ const filePath = path_1.default.join(baseDir, file);
822
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
823
+ // ✅ Utiliser le nom original (sans .enhanced) pour la référence
824
+ const originalName = endsWith === '.enhanced.md'
825
+ ? file.replace('.enhanced.md', '.md')
826
+ : file;
827
+ const parsedDoc = (0, parser_1.getSections)(content, originalName);
828
+ // Valider le document
829
+ (0, types_1.isValidRAGMatter)(parsedDoc.matter);
830
+ docs.push(parsedDoc);
831
+ }
832
+ catch (error) {
833
+ console.warn(`⚠️ Document invalide ignoré: ${file}`, error);
834
+ }
835
+ }
836
+ }
837
+ else {
838
+ // ✅ Mode intelligent : pour chaque .md, utiliser .enhanced.md s'il existe, sinon .md
839
+ const mdFiles = (0, fs_1.readdirSync)(baseDir)
840
+ .filter(f => f.endsWith('.md') && !f.endsWith('.enhanced.md'));
841
+ for (const mdFile of mdFiles) {
842
+ try {
843
+ const baseName = mdFile.replace('.md', '');
844
+ const enhancedPath = path_1.default.join(baseDir, baseName + '.enhanced.md');
845
+ const mdPath = path_1.default.join(baseDir, mdFile);
846
+ // Utiliser .enhanced.md en priorité, sinon .md
847
+ const filePath = (0, fs_1.existsSync)(enhancedPath) ? enhancedPath : mdPath;
848
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
849
+ const parsedDoc = (0, parser_1.getSections)(content, mdFile);
850
+ // Valider le document
851
+ (0, types_1.isValidRAGMatter)(parsedDoc.matter);
852
+ docs.push(parsedDoc);
853
+ }
854
+ catch (error) {
855
+ console.warn(`⚠️ Document invalide ignoré: ${mdFile}`, error);
856
+ }
857
+ }
858
+ }
859
+ if (docs.length === 0) {
860
+ throw new Error(`Aucun document valide trouvé dans ${baseDir}`);
861
+ }
862
+ return docs;
863
+ }
864
+ /**
865
+ * Charge tous les documents depuis un dossier du RAG
866
+ * Version publique qui utilise le nom du RAG
867
+ *
868
+ * @param name Nom du RAG
869
+ * @param endsWith Extension des fichiers à charger (par défaut '.enhanced.md')
870
+ * @returns Liste des documents parsés et validés
871
+ */
872
+ async loadDocumentsFromDir(name, endsWith = '.enhanced.md') {
873
+ const entry = this.getEntry(name);
874
+ if (!entry) {
875
+ throw new Error(`RAG '${name}' n'existe pas`);
876
+ }
877
+ return this._loadDocumentsFromPath(entry.configPath, endsWith);
878
+ }
879
+ /**
880
+ * Supprime un RAG du registre
881
+ */
882
+ async delete(name, archiveFlag = true) {
883
+ if (!this.exists(name)) {
884
+ throw new Error(`RAG '${name}' n'existe pas`);
885
+ }
886
+ const entry = this.getEntry(name);
887
+ const registry = this.getRegistry();
888
+ // Archiver si demandé
889
+ if (archiveFlag) {
890
+ await this.archive(name);
891
+ }
892
+ else if ((0, fs_1.existsSync)(entry.configPath)) {
893
+ (0, fs_1.rmSync)(entry.configPath, { recursive: true, force: true });
894
+ }
895
+ // Supprimer du registre
896
+ delete registry.registries[name];
897
+ // Supprimer de la map des embeddings chargés
898
+ this.loadedEmbeddings.delete(name);
899
+ // Changer le défaut si nécessaire
900
+ if (registry.defaultName === name) {
901
+ const remainingRAGs = Object.keys(registry.registries);
902
+ registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
903
+ }
904
+ this.saveRegistry(registry);
905
+ console.log(`✅ RAG supprimé: ${name}`);
906
+ }
907
+ /**
908
+ * Renomme un document dans un RAG existant
909
+ *
910
+ * Cette méthode renomme tous les fichiers associés à un document:
911
+ * - `oldFile.md` → `newFile.md`
912
+ * - `oldFile.md.sha` → `newFile.md.sha`
913
+ * - `oldFile.enhanced.md` → `newFile.enhanced.md`
914
+ * - `oldFile.query.json` → `newFile.query.json`
915
+ *
916
+ * Et met à jour les références dans les fichiers JSON (mapping, metadata, queries)
917
+ *
918
+ * @param ragName Nom du RAG concerné
919
+ * @param oldFile Ancien nom du fichier (ex: 'old-name.md')
920
+ * @param newFile Nouveau nom du fichier (ex: 'new-name.md')
921
+ *
922
+ * @example
923
+ * ```typescript
924
+ * ragManager.renameDocument('rule-validation-1', 'old-procedure.md', 'new-procedure.md');
925
+ * ```
926
+ */
927
+ renameDocument(ragName, oldFile, newFile) {
928
+ const entry = this.getEntry(ragName);
929
+ if (!entry) {
930
+ if (this.config.verbose) {
931
+ console.log(`⚠️ RAG '${ragName}' n'existe pas, skip rename document`);
932
+ }
933
+ return;
934
+ }
935
+ const configPath = entry.configPath;
936
+ //
937
+ // Liste des fichiers à renommer (sans extension pour le basename)
938
+ const oldBase = oldFile.replace(/\.md$/, '');
939
+ const newBase = newFile.replace(/\.md$/, '');
940
+ const filesToRename = [
941
+ { old: `${oldBase}.md`, new: `${newBase}.md` },
942
+ { old: `${oldBase}.md.sha`, new: `${newBase}.md.sha` },
943
+ { old: `${oldBase}.enhanced.md`, new: `${newBase}.enhanced.md` },
944
+ { old: `${oldBase}.query.json`, new: `${newBase}.query.json` }
945
+ ];
946
+ //
947
+ // Renommer les fichiers physiques
948
+ let renamedCount = 0;
949
+ for (const { old, new: newName } of filesToRename) {
950
+ const oldPath = path_1.default.join(configPath, old);
951
+ const newPath = path_1.default.join(configPath, newName);
952
+ if ((0, fs_1.existsSync)(oldPath)) {
953
+ try {
954
+ (0, fs_1.renameSync)(oldPath, newPath);
955
+ renamedCount++;
956
+ }
957
+ catch (error) {
958
+ console.warn(`⚠️ Erreur renommage ${old} → ${newName}:`, error);
959
+ }
960
+ }
961
+ }
962
+ if (renamedCount === 0) {
963
+ if (this.config.verbose) {
964
+ console.log(`⚠️ Aucun fichier à renommer pour ${oldFile} dans RAG '${ragName}'`);
965
+ }
966
+ return;
967
+ }
968
+ //
969
+ // Mettre à jour les références dans les fichiers JSON
970
+ this.updateJSONReferences(configPath, oldFile, newFile);
971
+ if (this.config.verbose) {
972
+ console.log(`✅ Document renommé dans RAG '${ragName}': ${oldFile} → ${newFile} (${renamedCount} fichiers)`);
973
+ }
974
+ //
975
+ // Notifier les embeddings chargés de se mettre à jour avec l'action rename
976
+ this.notifyUpdate(ragName, {
977
+ action: 'rename',
978
+ oldFile,
979
+ newFile
980
+ });
981
+ }
982
+ /**
983
+ * Met à jour les références d'un fichier dans les JSON du RAG
984
+ *
985
+ * @param configPath Chemin du RAG
986
+ * @param oldFile Ancien nom du fichier
987
+ * @param newFile Nouveau nom du fichier
988
+ * @private
989
+ */
990
+ updateJSONReferences(configPath, oldFile, newFile) {
991
+ const metadataFile = path_1.default.join(configPath, types_1.RAG_FILES.METADATA);
992
+ const mappingFile = path_1.default.join(configPath, types_1.RAG_FILES.MAPPING);
993
+ //
994
+ // Mettre à jour metadata.json
995
+ if ((0, fs_1.existsSync)(metadataFile)) {
996
+ try {
997
+ const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
998
+ //
999
+ // Mettre à jour les références dans documents
1000
+ if (metadata.documents) {
1001
+ const docs = Object.entries(metadata.documents);
1002
+ for (const [key, doc] of docs) {
1003
+ if (doc.filename === oldFile) {
1004
+ doc.filename = newFile;
1005
+ }
1006
+ //
1007
+ // Mettre à jour aussi les références dans les sections
1008
+ if (key.startsWith(oldFile)) {
1009
+ const newKey = key.replace(oldFile, newFile);
1010
+ delete metadata.documents[key];
1011
+ metadata.documents[newKey] = doc;
1012
+ }
1013
+ }
1014
+ }
1015
+ (0, fs_1.writeFileSync)(metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
1016
+ }
1017
+ catch (error) {
1018
+ console.warn(`⚠️ Erreur mise à jour metadata.json:`, error);
1019
+ }
1020
+ }
1021
+ //
1022
+ // Mettre à jour mapping.json
1023
+ if ((0, fs_1.existsSync)(mappingFile)) {
1024
+ try {
1025
+ const mappingData = JSON.parse((0, fs_1.readFileSync)(mappingFile, 'utf8'));
1026
+ //
1027
+ // Mettre à jour les clés du mapping
1028
+ if (mappingData.mapping) {
1029
+ const entries = Object.entries(mappingData.mapping);
1030
+ for (const [id, ref] of entries) {
1031
+ if (ref.includes(oldFile)) {
1032
+ mappingData.mapping[id] = ref.replace(oldFile, newFile);
1033
+ }
1034
+ }
1035
+ }
1036
+ (0, fs_1.writeFileSync)(mappingFile, JSON.stringify(mappingData, null, 2), 'utf8');
1037
+ }
1038
+ catch (error) {
1039
+ console.warn(`⚠️ Erreur mise à jour mapping.json:`, error);
1040
+ }
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Renomme un RAG existant
1045
+ *
1046
+ * @param from Nom actuel du RAG
1047
+ * @param to Nouveau nom du RAG
1048
+ * @param description Description optionnelle du RAG
1049
+ * @throws Error si le nom est trop court, si une collision existe, ou si le RAG source n'existe pas
1050
+ *
1051
+ * @example
1052
+ * ```typescript
1053
+ * await ragManager.rename('old-name', 'new-name', 'Description mise à jour');
1054
+ * ```
1055
+ */
1056
+ async rename(from, to, description) {
1057
+ // Validation du nom de destination
1058
+ const MIN_NAME_LENGTH = 3;
1059
+ if (to.length < MIN_NAME_LENGTH) {
1060
+ throw new Error(`Le nom '${to}' est trop court (minimum ${MIN_NAME_LENGTH} caractères)`);
1061
+ }
1062
+ // Vérifier que le RAG source existe
1063
+ if (!this.exists(from)) {
1064
+ throw new Error(`RAG source '${from}' n'existe pas`);
1065
+ }
1066
+ // Éviter les collisions
1067
+ if (this.exists(to)) {
1068
+ throw new Error(`RAG de destination '${to}' existe déjà - collision détectée`);
1069
+ }
1070
+ // Éviter les noms réservés
1071
+ const reservedNames = ['temp', 'archives', 'registry', 'config'];
1072
+ if (reservedNames.includes(to.toLowerCase())) {
1073
+ throw new Error(`Le nom '${to}' est réservé et ne peut pas être utilisé`);
1074
+ }
1075
+ const sourceEntry = this.getEntry(from);
1076
+ const sourcePath = sourceEntry.configPath;
1077
+ const destPath = path_1.default.join(this.config.baseDir, to);
1078
+ try {
1079
+ // Renommer le dossier physique
1080
+ if ((0, fs_1.existsSync)(sourcePath)) {
1081
+ (0, fs_1.renameSync)(sourcePath, destPath);
1082
+ if (this.config.verbose)
1083
+ console.log(`📁 Dossier renommé: ${sourcePath} → ${destPath}`);
1084
+ }
1085
+ // Mettre à jour le registre
1086
+ const registry = this.getRegistry();
1087
+ registry.registries[to] = {
1088
+ name: to,
1089
+ configPath: destPath,
1090
+ status: sourceEntry.status,
1091
+ description: description || sourceEntry.description
1092
+ };
1093
+ delete registry.registries[from];
1094
+ // Mettre à jour le défaut si nécessaire
1095
+ if (registry.defaultName === from) {
1096
+ registry.defaultName = to;
1097
+ }
1098
+ // Mettre à jour la map des embeddings chargés
1099
+ const embeddingData = this.loadedEmbeddings.get(from);
1100
+ if (embeddingData) {
1101
+ this.loadedEmbeddings.set(to, embeddingData);
1102
+ this.loadedEmbeddings.delete(from);
1103
+ // Notifier l'embedding de se mettre à jour avec le nouveau chemin
1104
+ this.notifyUpdate(to);
1105
+ }
1106
+ // Mettre à jour le cache
1107
+ this.saveRegistry(registry);
1108
+ console.log(`✅ RAG renommé: ${from} → ${to}`);
1109
+ }
1110
+ catch (error) {
1111
+ console.error(`❌ Erreur lors du renommage de '${from}' en '${to}':`, error);
1112
+ throw new Error(`Impossible de renommer le RAG: ${error}`);
1113
+ }
1114
+ }
1115
+ /**
1116
+ * Met à jour le statut d'un RAG
1117
+ *
1118
+ * @param name Nom du RAG
1119
+ * @param status Nouveau statut ('active' ou 'paused')
1120
+ * @throws Error si le RAG n'existe pas ou si le statut est invalide
1121
+ *
1122
+ * @example
1123
+ * ```typescript
1124
+ * ragManager.updateStatus('knowledge', 'paused');
1125
+ * ```
1126
+ */
1127
+ pause(name) {
1128
+ if (!this.exists(name)) {
1129
+ throw new Error(`RAG '${name}' n'existe pas`);
1130
+ }
1131
+ const entry = this.getEntry(name);
1132
+ if (!entry) {
1133
+ throw new Error(`Entrée RAG '${name}' non trouvée dans le registre`);
1134
+ }
1135
+ // FIXME missing test
1136
+ if (entry.status !== 'active') {
1137
+ throw new Error(`Impossible de mettre en pause '${name}' car il n'est pas actif`);
1138
+ }
1139
+ // Mettre à jour le statut
1140
+ entry.status = 'paused';
1141
+ // Notifier l'embedding de mettre à jour sont état
1142
+ this.notifyUpdate(name);
1143
+ // Sauvegarder le registre
1144
+ const registry = this.getRegistry();
1145
+ this.saveRegistry(registry);
1146
+ }
1147
+ unpause(name) {
1148
+ if (!this.exists(name)) {
1149
+ throw new Error(`RAG '${name}' n'existe pas`);
1150
+ }
1151
+ const entry = this.getEntry(name);
1152
+ if (!entry) {
1153
+ throw new Error(`Entrée RAG '${name}' non trouvée dans le registre`);
1154
+ }
1155
+ // Mettre à jour le statut
1156
+ entry.status = 'active';
1157
+ // Notifier l'embedding de mettre à jour sont état
1158
+ this.notifyUpdate(name);
1159
+ // Sauvegarder le registre
1160
+ const registry = this.getRegistry();
1161
+ this.saveRegistry(registry);
1162
+ }
1163
+ /**
1164
+ * Nettoie les embeddings chargés obsolètes (plus vieux que maxAge)
1165
+ * Utile pour libérer de la mémoire
1166
+ *
1167
+ * @param maxAge Âge maximum en millisecondes (défaut: 1 heure)
1168
+ */
1169
+ cleanDeadReferences(maxAge = 3600000) {
1170
+ const now = Date.now();
1171
+ const deadKeys = [];
1172
+ for (const [name, data] of this.loadedEmbeddings.entries()) {
1173
+ // Supprimer les embeddings trop vieux ou non valides
1174
+ if (now - data.loadTime >= maxAge) {
1175
+ deadKeys.push(name);
1176
+ }
1177
+ }
1178
+ //
1179
+ // remove embeddings from cached registry
1180
+ for (const key of deadKeys) {
1181
+ this.loadedEmbeddings.delete(key);
1182
+ }
1183
+ if (deadKeys.length > 0 && this.config.verbose) {
1184
+ console.log(`🗑️ Nettoyage: ${deadKeys.length} embedding(s) obsolète(s) supprimé(s)`);
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Obtient le nombre d'embeddings actuellement chargés
1189
+ */
1190
+ getLoadedCount() {
1191
+ return this.loadedEmbeddings.size;
1192
+ }
1193
+ /**
1194
+ * Charge un RAG par son nom et l'enregistre pour les updates réactifs
1195
+ *
1196
+ * Permet le chargement si:
1197
+ * - Statut = 'active' : RAG complètement prêt
1198
+ * - Statut = 'building' : RAG en construction mais déjà utilisable (recherche partielle)
1199
+ */
1200
+ load(name) {
1201
+ if (!this.exists(name)) {
1202
+ throw new Error(`RAG '${name}' n'existe pas`);
1203
+ }
1204
+ const entry = this.getEntry(name);
1205
+ if (!entry) {
1206
+ throw new Error(`RAG '${name}' n'existe pas`);
1207
+ }
1208
+ // Permettre le chargement si actif ou en cours de construction
1209
+ if (entry.status !== 'active' && entry.status !== 'building') {
1210
+ throw new Error(`RAG '${name}' n'est pas disponible (statut: ${entry.status})`);
1211
+ }
1212
+ //
1213
+ // use cached embedding if available
1214
+ const embeddingData = this.loadedEmbeddings.get(name);
1215
+ if (embeddingData) {
1216
+ return embeddingData.embedding;
1217
+ }
1218
+ // Vérifier que le dossier contient bien les fichiers nécessaires avant de charger
1219
+ const metadataFile = path_1.default.join(entry.configPath, types_1.RAG_FILES.METADATA);
1220
+ if (!(0, fs_1.existsSync)(metadataFile)) {
1221
+ const statusMsg = entry.status === 'building'
1222
+ ? `Le RAG '${name}' est en cours de construction. Les fichiers ne sont pas encore disponibles.`
1223
+ : `Le RAG '${name}' est corrompu (fichier metadata manquant).`;
1224
+ throw new Error(statusMsg);
1225
+ }
1226
+ // Charger la configuration depuis le fichier metadata
1227
+ const ragConfig = this.loadConfig(entry.configPath);
1228
+ // Instancier et vérifier l'Embeddings
1229
+ const embeddings = new embeddings_1.Embeddings(ragConfig);
1230
+ if (!embeddings.isReady()) {
1231
+ throw new Error(`RAG '${name}' n'est pas prêt (fichiers manquants)`);
1232
+ }
1233
+ // Enregistrer l'embedding pour les updates réactifs
1234
+ this.loadedEmbeddings.set(name, {
1235
+ embedding: embeddings,
1236
+ loadTime: Date.now()
1237
+ });
1238
+ if (this.config.verbose) {
1239
+ const statusMsg = entry.status === 'building' ? '(en construction)' : '';
1240
+ console.log(`✅ RAG chargé: ${name} ${statusMsg}`);
1241
+ }
1242
+ return embeddings;
1243
+ }
1244
+ /**
1245
+ * Vérifie l'état d'un RAG
1246
+ */
1247
+ verify(name) {
1248
+ if (!this.exists(name)) {
1249
+ return false;
1250
+ }
1251
+ const entry = this.getEntry(name);
1252
+ const registry = this.getRegistry();
1253
+ // Vérifier que le dossier existe
1254
+ if (!(0, fs_1.existsSync)(entry.configPath)) {
1255
+ // Mettre à jour le statut
1256
+ entry.status = 'error';
1257
+ this.saveRegistry(registry);
1258
+ return false;
1259
+ }
1260
+ try {
1261
+ // Charger la configuration depuis le metadata
1262
+ const ragConfig = this.loadConfig(entry.configPath);
1263
+ const embeddingData = this.loadedEmbeddings.get(name);
1264
+ const embeddings = (embeddingData) ? embeddingData.embedding : new embeddings_1.Embeddings(ragConfig);
1265
+ const isReady = embeddings.isReady();
1266
+ // Mettre à jour le statut (stateless - pas de sauvegarde automatique)
1267
+ // Si le RAG est en "building", ne pas changer son statut
1268
+ if (entry.status !== 'building') {
1269
+ entry.status = isReady ? 'active' : 'error';
1270
+ }
1271
+ this.cachedRegistry = { ...registry };
1272
+ // Un RAG en "building" est considéré comme valide (il existe)
1273
+ return isReady || entry.status === 'building';
1274
+ }
1275
+ catch (error) {
1276
+ // Erreur lors du chargement de la configuration
1277
+ // Ne pas changer le statut si c'est "building"
1278
+ if (entry.status !== 'building') {
1279
+ entry.status = 'error';
1280
+ }
1281
+ this.cachedRegistry = { ...registry };
1282
+ return entry.status === 'building'; // Un RAG en "building" est valide
1283
+ }
1284
+ }
1285
+ /**
1286
+ * Liste tous les RAG disponibles
1287
+ */
1288
+ list() {
1289
+ const registry = this.getRegistry();
1290
+ return Object.values(registry.registries) || [];
1291
+ }
1292
+ /**
1293
+ * Obtient le statut d'un RAG
1294
+ */
1295
+ getStatus(name) {
1296
+ if (!this.exists(name)) {
1297
+ throw new Error(`RAG '${name}' n'existe pas`);
1298
+ }
1299
+ return this.getEntry(name);
1300
+ }
1301
+ /**
1302
+ * Obtient la configuration d'un RAG depuis son metadata
1303
+ */
1304
+ getConfig(name) {
1305
+ if (!this.exists(name)) {
1306
+ throw new Error(`RAG '${name}' n'existe pas`);
1307
+ }
1308
+ const entry = this.getEntry(name);
1309
+ try {
1310
+ return this.loadConfig(entry.configPath);
1311
+ }
1312
+ catch (error) {
1313
+ console.warn(`Erreur lors du chargement de la config pour '${name}':`, error);
1314
+ throw error;
1315
+ }
1316
+ }
1317
+ /**
1318
+ * Définit le RAG par défaut
1319
+ */
1320
+ async setDefault(name) {
1321
+ if (!this.exists(name)) {
1322
+ throw new Error(`RAG '${name}' n'existe pas`);
1323
+ }
1324
+ const registry = this.getRegistry();
1325
+ registry.defaultName = name;
1326
+ this.saveRegistry(registry);
1327
+ console.log(`✅ RAG par défaut défini: ${name}`);
1328
+ }
1329
+ /**
1330
+ * Obtient le RAG par défaut
1331
+ */
1332
+ getDefault() {
1333
+ const registry = this.getRegistry();
1334
+ return registry.defaultName || 'RAGRegistry';
1335
+ }
1336
+ /**
1337
+ * Obtient les statistiques de tous les RAG
1338
+ */
1339
+ getStats() {
1340
+ const rags = this.list();
1341
+ const stats = [];
1342
+ for (const rag of rags) {
1343
+ try {
1344
+ const isValid = this.verify(rag.name);
1345
+ let metadata = null;
1346
+ if (isValid) {
1347
+ try {
1348
+ const embeddings = this.load(rag.name);
1349
+ metadata = embeddings.getMetadata();
1350
+ }
1351
+ catch (error) {
1352
+ // Si le chargement échoue, essayer d'obtenir au moins la config
1353
+ const config = this.getConfig(rag.name);
1354
+ if (config) {
1355
+ metadata = {
1356
+ stats: {
1357
+ totalSections: 0,
1358
+ vectorDimensions: config.dimensions || 1536,
1359
+ indexSize: 'inconnu'
1360
+ }
1361
+ };
1362
+ }
1363
+ }
1364
+ }
1365
+ stats.push({
1366
+ name: rag.name,
1367
+ status: rag.status,
1368
+ isValid,
1369
+ configPath: rag.configPath,
1370
+ metadata: metadata ? {
1371
+ totalSections: metadata.stats?.totalSections || 0,
1372
+ vectorDimensions: metadata.stats?.vectorDimensions || 0,
1373
+ indexSize: metadata.stats?.indexSize || 'inconnu'
1374
+ } : null
1375
+ });
1376
+ }
1377
+ catch (error) {
1378
+ stats.push({
1379
+ name: rag.name,
1380
+ status: 'error',
1381
+ isValid: false,
1382
+ configPath: rag.configPath,
1383
+ error: error instanceof Error ? error.message : 'Erreur inconnue'
1384
+ });
1385
+ }
1386
+ }
1387
+ return stats;
1388
+ }
1389
+ }
1390
+ exports.RAGManager = RAGManager;