agentic-api 2.0.31 → 2.0.314

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