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
@@ -33,6 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.gitIDRegistryExists = gitIDRegistryExists;
37
+ exports.gitGenerateNextID = gitGenerateNextID;
38
+ exports.gitReloadIDRegistry = gitReloadIDRegistry;
39
+ exports.gitRegisterExistingID = gitRegisterExistingID;
40
+ exports.gitIDRegistryRename = gitIDRegistryRename;
41
+ exports.gitEnsureMatterID = gitEnsureMatterID;
42
+ exports.gitFileStrictMatter = gitFileStrictMatter;
36
43
  exports.gitEnsureRepositoryConfiguration = gitEnsureRepositoryConfiguration;
37
44
  exports.gitEnsureRemoteConfiguration = gitEnsureRemoteConfiguration;
38
45
  exports.gitInit = gitInit;
@@ -46,11 +53,628 @@ exports.gitRenameFile_fs = gitRenameFile_fs;
46
53
  exports.gitRenameFile = gitRenameFile;
47
54
  exports.gitDeleteFile = gitDeleteFile;
48
55
  exports.gitGetBranchHealth = gitGetBranchHealth;
56
+ exports.gitGetValidationBranchHealth = gitGetValidationBranchHealth;
49
57
  const fs_1 = require("fs");
50
58
  const errors_1 = require("../errors");
51
59
  const path_1 = require("path");
52
60
  const fs = __importStar(require("fs/promises"));
53
61
  const repo_tools_1 = require("./repo.tools");
62
+ /**
63
+ * Service singleton pour gérer le registre d'IDs en mémoire
64
+ */
65
+ class IDRegistryService {
66
+ constructor() {
67
+ this.registry = null;
68
+ this.repoPath = '';
69
+ this.isDirty = false;
70
+ }
71
+ static get() {
72
+ return IDRegistryService.instance || (IDRegistryService.instance = new IDRegistryService());
73
+ }
74
+ toString() {
75
+ return JSON.stringify(this.registry, null, 2);
76
+ }
77
+ clear() {
78
+ this.registry = null;
79
+ this.repoPath = '';
80
+ this.isDirty = false;
81
+ }
82
+ /**
83
+ * Initialise le service avec le chemin du repository
84
+ */
85
+ init(repoPath) {
86
+ // ✅ BUGFIX: Toujours mettre à jour repoPath même si le registre existe déjà
87
+ // Car gitReloadIDRegistry peut être appelé avec un nouveau repoPath pour un nouveau test
88
+ // et le repo précédent peut avoir été nettoyé
89
+ if (this.repoPath !== repoPath) {
90
+ this.repoPath = repoPath;
91
+ // Si le registre existe déjà, le recharger depuis le nouveau chemin
92
+ if (this.registry) {
93
+ this.registry = this.loadFromDisk();
94
+ this.isDirty = false;
95
+ return;
96
+ }
97
+ }
98
+ // Si le registre n'existe pas encore, le créer
99
+ if (!this.registry) {
100
+ this.registry = this.loadFromDisk();
101
+ this.isDirty = false;
102
+ }
103
+ }
104
+ /**
105
+ * Force le rechargement du registre depuis le disque
106
+ * Utilisé principalement pour les tests qui simulent un redémarrage
107
+ */
108
+ forceReload() {
109
+ if (!this.repoPath) {
110
+ throw new Error('IDRegistryService not initialized. Call init() first.');
111
+ }
112
+ this.registry = this.loadFromDisk();
113
+ this.isDirty = false;
114
+ }
115
+ /**
116
+ * Charge le registre depuis le disque
117
+ */
118
+ loadFromDisk() {
119
+ const registryPath = (0, path_1.join)(this.repoPath, '.git', 'with-ids.json');
120
+ if (!(0, fs_1.existsSync)(registryPath)) {
121
+ return {
122
+ last: 980,
123
+ used: [],
124
+ updated: new Date().toISOString(),
125
+ matters: {}
126
+ };
127
+ }
128
+ try {
129
+ const data = JSON.parse((0, fs_1.readFileSync)(registryPath, 'utf8'));
130
+ // Migration : ajouter matters si absent (compatibilité anciens registres)
131
+ if (!data.matters) {
132
+ data.matters = {};
133
+ }
134
+ return data;
135
+ }
136
+ catch (error) {
137
+ console.warn('⚠️ Registre d\'IDs corrompu, création d\'un nouveau');
138
+ return {
139
+ last: 980,
140
+ used: [],
141
+ updated: new Date().toISOString(),
142
+ matters: {}
143
+ };
144
+ }
145
+ }
146
+ /**
147
+ * Sauvegarde le registre sur le disque (seulement si modifié)
148
+ */
149
+ save() {
150
+ if (!this.registry || !this.isDirty) {
151
+ return;
152
+ }
153
+ const gitDir = (0, path_1.join)(this.repoPath, '.git');
154
+ const registryPath = (0, path_1.join)(gitDir, 'with-ids.json');
155
+ // S'assurer que le répertoire .git existe
156
+ if (!(0, fs_1.existsSync)(gitDir)) {
157
+ console.warn(`⚠️ Cannot save ID registry: .git directory not found at ${this.repoPath}`);
158
+ return; // Ne pas sauvegarder si le repo n'est pas initialisé
159
+ }
160
+ try {
161
+ this.registry.updated = new Date().toISOString();
162
+ (0, fs_1.writeFileSync)(registryPath, JSON.stringify(this.registry, null, 2), 'utf8');
163
+ this.isDirty = false;
164
+ }
165
+ catch (error) {
166
+ console.warn(`⚠️ Failed to save ID registry:`, error);
167
+ // Ne pas propager l'erreur, le registre est une optimisation
168
+ }
169
+ }
170
+ /**
171
+ * Récupère le registre (lecture seule)
172
+ */
173
+ getRegistry() {
174
+ if (!this.registry) {
175
+ throw new Error('IDRegistryService not initialized. Call init() first.');
176
+ }
177
+ return this.registry;
178
+ }
179
+ /**
180
+ * Génère un nouvel ID unique
181
+ */
182
+ generateNextID() {
183
+ if (!this.registry) {
184
+ throw new Error('IDRegistryService not initialized. Call init() first.');
185
+ }
186
+ const newID = this.registry.last + 20;
187
+ this.registry.last = newID;
188
+ this.registry.used.push(newID);
189
+ this.isDirty = true;
190
+ return newID;
191
+ }
192
+ /**
193
+ * Obtient l'ID d'un fichier (peu importe la branche)
194
+ *
195
+ * Un fichier a UN SEUL ID quelque soit la branche où il se trouve.
196
+ * Cette fonction cherche le fichier dans toutes les branches du cache.
197
+ *
198
+ * @param file Nom du fichier (ex: 'procedure.md')
199
+ * @returns L'ID du fichier ou undefined si le fichier n'a pas d'ID
200
+ */
201
+ getFileID(file) {
202
+ if (!this.registry) {
203
+ return undefined;
204
+ }
205
+ // Chercher le fichier dans toutes les branches
206
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
207
+ const [, cachedFile] = cacheKey.split(':');
208
+ if (cachedFile === file) {
209
+ return matter.id;
210
+ }
211
+ }
212
+ return undefined;
213
+ }
214
+ /**
215
+ * Vérifie si un ID appartient déjà à un fichier spécifique
216
+ *
217
+ * Un fichier garde son ID peu importe la branche, donc cette fonction
218
+ * cherche dans toutes les branches pour vérifier la propriété.
219
+ *
220
+ * @param id L'ID à vérifier
221
+ * @param file Nom du fichier
222
+ * @returns true si ce fichier possède cet ID
223
+ */
224
+ isIDOwnedByFile(id, file) {
225
+ if (!this.registry) {
226
+ return false;
227
+ }
228
+ // Chercher le fichier dans toutes les branches
229
+ const fileID = this.getFileID(file);
230
+ return fileID === id;
231
+ }
232
+ /**
233
+ * Trouve quel fichier possède un ID donné
234
+ *
235
+ * @param id L'ID à chercher
236
+ * @returns Le nom du fichier qui possède cet ID, ou undefined
237
+ */
238
+ getFileByID(id) {
239
+ if (!this.registry) {
240
+ return undefined;
241
+ }
242
+ // Chercher l'ID dans le cache
243
+ for (const [cacheKey, matter] of Object.entries(this.registry.matters)) {
244
+ if (matter.id === id) {
245
+ const [, file] = cacheKey.split(':');
246
+ return file;
247
+ }
248
+ }
249
+ return undefined;
250
+ }
251
+ /**
252
+ * Enregistre un ID existant
253
+ *
254
+ * @param id L'ID à enregistrer
255
+ * @param branch Branche du fichier (optionnel, pour vérification de propriété)
256
+ * @param file Nom du fichier (optionnel, pour vérification de propriété)
257
+ * @throws Error si l'ID est déjà utilisé par un autre fichier
258
+ */
259
+ registerExistingID(id, branch, file) {
260
+ if (!this.registry) {
261
+ throw new Error('IDRegistryService not initialized. Call init() first.');
262
+ }
263
+ // Si l'ID est déjà utilisé
264
+ if (this.registry.used.includes(id)) {
265
+ // Vérifier si c'est le même fichier (pas un doublon)
266
+ // Note: isIDOwnedByFile() cherche maintenant dans TOUTES les branches
267
+ if (file && this.isIDOwnedByFile(id, file)) {
268
+ // Même fichier → OK, pas d'erreur
269
+ return;
270
+ }
271
+ // ID utilisé par un autre fichier → Erreur
272
+ const error = new Error(`ID ${id} est déjà utilisé`);
273
+ error.code = 'id_already_used';
274
+ throw error;
275
+ }
276
+ // Nouvel ID → Enregistrer
277
+ this.registry.used.push(id);
278
+ if (id > this.registry.last) {
279
+ this.registry.last = id;
280
+ }
281
+ // ⚠️ IMPORTANT: Ne PAS créer d'entrée dans matters ici
282
+ // setMatterCache() DOIT être appelé AVANT registerExistingID() par l'appelant
283
+ // car registerExistingID() n'a pas le contexte complet (title, service)
284
+ // Si branch/file sont fournis mais que le cache n'existe pas, c'est une erreur de programmation
285
+ this.isDirty = true;
286
+ }
287
+ /**
288
+ * Récupère un matter depuis le cache
289
+ * Note: Le champ 'updated' est filtré pour ne pas être exposé à l'extérieur
290
+ */
291
+ getMatterCache(branch, file) {
292
+ if (!this.registry) {
293
+ throw new Error('IDRegistryService not initialized. Call init() first.');
294
+ }
295
+ const cacheKey = `${branch}:${file}`;
296
+ const cached = this.registry.matters[cacheKey];
297
+ if (!cached) {
298
+ return undefined;
299
+ }
300
+ // Retourner uniquement les champs publics (sans 'updated')
301
+ return {
302
+ id: cached.id,
303
+ title: cached.title,
304
+ service: cached.service,
305
+ oldfile: cached.oldfile // FIXME (check if needed) ✅ Inclure oldfile temporaire si présent
306
+ };
307
+ }
308
+ /**
309
+ * Met à jour le cache d'un matter
310
+ */
311
+ setMatterCache(branch, file, matter) {
312
+ if (!this.registry) {
313
+ throw new Error('IDRegistryService not initialized. Call init() first.');
314
+ }
315
+ const cacheKey = `${branch}:${file}`;
316
+ this.registry.matters[cacheKey] = {
317
+ id: matter.id,
318
+ title: matter.title,
319
+ service: matter.service,
320
+ oldfile: matter.oldfile, // ✅ Inclure oldfile temporaire
321
+ updated: new Date().toISOString()
322
+ };
323
+ this.isDirty = true;
324
+ }
325
+ /**
326
+ * Supprime une entrée du cache
327
+ */
328
+ deleteMatterCache(branch, file) {
329
+ if (!this.registry) {
330
+ throw new Error('IDRegistryService not initialized. Call init() first.');
331
+ }
332
+ const cacheKey = `${branch}:${file}`;
333
+ delete this.registry.matters[cacheKey];
334
+ this.isDirty = true;
335
+ }
336
+ /**
337
+ * Renomme une entrée dans le cache du registre
338
+ *
339
+ * Cette méthode gère le renommage d'un fichier dans le cache:
340
+ * - Supprime l'ancienne clé `${branch}:${oldFile}`
341
+ * - Crée une nouvelle clé `${branch}:${newFile}` avec les mêmes données
342
+ * - Préserve l'ID et les métadonnées
343
+ *
344
+ * @param oldFile Ancien nom du fichier
345
+ * @param newFile Nouveau nom du fichier
346
+ * @param branch Branche concernée
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * // Après un rename Git
351
+ * idRegistryService.rename('old-name.md', 'new-name.md', 'rule-validation-1');
352
+ * ```
353
+ */
354
+ rename(oldFile, newFile, branch) {
355
+ if (!this.registry) {
356
+ throw new Error('IDRegistryService not initialized. Call init() first.');
357
+ }
358
+ const oldKey = `${branch}:${oldFile}`;
359
+ const newKey = `${branch}:${newFile}`;
360
+ // Récupérer le cache existant
361
+ const cached = this.registry.matters[oldKey];
362
+ if (!cached) {
363
+ // Pas de cache pour l'ancien fichier, rien à renommer
364
+ return;
365
+ }
366
+ // Supprimer l'ancienne clé
367
+ delete this.registry.matters[oldKey];
368
+ //
369
+ // Créer la nouvelle clé avec les mêmes données
370
+ // ⚠️ NE PAS inclure oldfile - c'est temporaire uniquement pour notification client
371
+ this.registry.matters[newKey] = {
372
+ id: cached.id,
373
+ title: cached.title,
374
+ service: cached.service,
375
+ updated: new Date().toISOString()
376
+ };
377
+ this.isDirty = true;
378
+ }
379
+ }
380
+ IDRegistryService.instance = null;
381
+ // ✅ Instance singleton du service
382
+ const idRegistryService = IDRegistryService.get();
383
+ /**
384
+ * Vérifie si le fichier de registre d'IDs existe dans le repository Git
385
+ *
386
+ * Cette fonction est utile pour détecter si le système d'ID natif a été initialisé
387
+ * dans le repository. Le fichier `with-ids.json` est créé automatiquement lors du
388
+ * premier appel à `gitGenerateNextID()` ou `gitEnsureMatterID()`.
389
+ *
390
+ * @param config Configuration Git optionnelle (utilise la config par défaut si non fournie)
391
+ * @returns `true` si le fichier `.git/with-ids.json` existe, `false` sinon
392
+ *
393
+ * @example
394
+ * ```typescript
395
+ * const config = gitLoad({ repoPath: './my-repo' });
396
+ *
397
+ * if (gitIDRegistryExists(config)) {
398
+ * console.log('Le registre d\'IDs existe déjà');
399
+ * // On peut charger le registre existant
400
+ * gitReloadIDRegistry(config);
401
+ * } else {
402
+ * console.log('Première utilisation du système d\'IDs');
403
+ * // Le registre sera créé au premier gitGenerateNextID()
404
+ * }
405
+ * ```
406
+ */
407
+ function gitIDRegistryExists(config) {
408
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
409
+ const registryPath = (0, path_1.join)(gitConf.repoPath, '.git', 'with-ids.json');
410
+ return (0, fs_1.existsSync)(registryPath);
411
+ }
412
+ /**
413
+ * Génère un nouvel ID unique en incrémentant de +20
414
+ * @param config Configuration Git optionnelle
415
+ * @returns Un nouvel ID unique
416
+ */
417
+ function gitGenerateNextID(config) {
418
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
419
+ idRegistryService.init(gitConf.repoPath);
420
+ const newID = idRegistryService.generateNextID();
421
+ // FIXME: Double save - gitGenerateNextID() et gitRegisterExistingID() appellent tous deux save()
422
+ // Refactorisation future: Retirer save() d'ici et le faire uniquement dans gitRegisterExistingID()
423
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
424
+ idRegistryService.save();
425
+ return newID;
426
+ }
427
+ /**
428
+ * Force le rechargement du registre d'IDs depuis le disque
429
+ *
430
+ * **Utilisé principalement pour les tests qui simulent un redémarrage du serveur.**
431
+ *
432
+ * En production, cette fonction n'est pas nécessaire car le singleton est
433
+ * réinitialisé à chaque redémarrage du process Node.js.
434
+ *
435
+ * @param config Configuration Git optionnelle
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * // Dans un test : simuler un redémarrage
440
+ * await gitCreateOrEditFile(...); // Modifie le registre
441
+ * gitReloadIDRegistry(); // Force le rechargement depuis le disque
442
+ * ```
443
+ */
444
+ function gitReloadIDRegistry(config) {
445
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
446
+ if (config?.reset) {
447
+ idRegistryService.clear();
448
+ }
449
+ idRegistryService.init(gitConf.repoPath);
450
+ idRegistryService.forceReload();
451
+ }
452
+ /**
453
+ * Enregistre un ID existant dans le registre
454
+ *
455
+ * @param matter Le matter contenant l'ID à enregistrer
456
+ * @param branch Branche du fichier (optionnel, pour vérification de propriété)
457
+ * @param file Nom du fichier (optionnel, pour vérification de propriété)
458
+ * @param config Configuration Git optionnelle
459
+ * @throws GitOperationError si l'ID est déjà utilisé par un autre fichier
460
+ *
461
+ * **Note:** Si `branch` et `file` sont fournis, la fonction vérifie si l'ID appartient
462
+ * déjà au même fichier. Si oui, aucune erreur n'est levée (cas de re-scan).
463
+ *
464
+ * **Note:** `setMatterCache()` est appelé automatiquement avant l'enregistrement
465
+ * pour garantir la cohérence du cache.
466
+ */
467
+ function gitRegisterExistingID(matter, branch, file, config) {
468
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
469
+ idRegistryService.init(gitConf.repoPath);
470
+ // ✅ Validation: matter doit avoir id et title
471
+ if (!matter.id || !matter.title) {
472
+ throw new errors_1.GitOperationError('Le matter est invalide (id et title requis)', 'invalid_matter');
473
+ }
474
+ // ✅ IMPORTANT: Vérifier AVANT de mettre à jour le cache
475
+ // Si l'ID existe déjà, vérifier qu'il appartient au MÊME fichier
476
+ const registry = idRegistryService.getRegistry();
477
+ if (registry.used.includes(matter.id)) {
478
+ const ownerFile = idRegistryService.getFileByID(matter.id);
479
+ if (ownerFile && ownerFile !== file) {
480
+ // ID utilisé par un AUTRE fichier → Erreur
481
+ const error = new Error(`ID ${matter.id} est déjà utilisé`);
482
+ error.code = 'id_already_used';
483
+ throw error;
484
+ }
485
+ // Même fichier OU pas de fichier dans le cache → OK, continuer
486
+ }
487
+ // ✅ Mettre à jour le cache (maintenant qu'on sait que l'ID est valide)
488
+ if (branch && file) {
489
+ idRegistryService.setMatterCache(branch, file, matter);
490
+ }
491
+ // ✅ Enregistrer l'ID dans used[] si pas déjà dedans
492
+ idRegistryService.registerExistingID(matter.id, branch, file);
493
+ idRegistryService.save();
494
+ }
495
+ /**
496
+ * Renomme un fichier dans le cache du registre d'IDs
497
+ *
498
+ * Cette fonction met à jour le cache lorsqu'un fichier est renommé:
499
+ * - Supprime l'entrée avec l'ancien nom
500
+ * - Crée une nouvelle entrée avec le nouveau nom
501
+ * - Préserve l'ID et les métadonnées
502
+ *
503
+ * @param oldFile Ancien nom du fichier
504
+ * @param newFile Nouveau nom du fichier
505
+ * @param branch Branche concernée
506
+ * @param config Configuration Git optionnelle
507
+ *
508
+ * @example
509
+ * ```typescript
510
+ * // Après un rename dans Git
511
+ * gitIDRegistryRename('procedure-old.md', 'procedure-new.md', 'rule-validation-1');
512
+ * ```
513
+ */
514
+ function gitIDRegistryRename(oldFile, newFile, branch, config) {
515
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
516
+ idRegistryService.init(gitConf.repoPath);
517
+ idRegistryService.rename(oldFile, newFile, branch);
518
+ idRegistryService.save();
519
+ }
520
+ /**
521
+ * Valide et assure qu'un matter a un ID valide
522
+ *
523
+ * **Principe d'identité stable**: Un fichier conserve son ID à travers les branches.
524
+ * Cette fonction cherche d'abord si le fichier a déjà un ID dans le registre
525
+ * (peu importe la branche), et le réutilise pour garantir la cohérence.
526
+ *
527
+ * @param matter Le matter à valider/compléter
528
+ * @param config Configuration Git optionnelle
529
+ * @param branch Branche du fichier (optionnel, peut être undefined lors de createPullRequest)
530
+ * @param file Nom du fichier (requis pour chercher l'ID existant)
531
+ * @returns Le matter mis à jour avec un ID valide
532
+ */
533
+ function gitEnsureMatterID(matter, config, branch, file) {
534
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
535
+ idRegistryService.init(gitConf.repoPath);
536
+ // ✅ NOUVEAU: Chercher d'abord l'ID existant du fichier (peu importe la branche)
537
+ const existingFileID = file ? idRegistryService.getFileID(file) : undefined;
538
+ if (existingFileID) {
539
+ // Le fichier a déjà un ID → le réutiliser pour garantir la cohérence
540
+ matter.id = existingFileID;
541
+ // Mettre à jour le cache si branch est définie (sinon ce sera fait plus tard)
542
+ if (branch && file && matter.title) {
543
+ idRegistryService.setMatterCache(branch, file, {
544
+ id: matter.id,
545
+ title: matter.title,
546
+ service: matter.service
547
+ });
548
+ }
549
+ return matter;
550
+ }
551
+ // Si matter a un ID fourni manuellement (ex: import ou test)
552
+ // FIXME 999 should be a constant in config
553
+ if (matter.id && typeof matter.id === 'number' && matter.id > 999) {
554
+ try {
555
+ // Essayer d'enregistrer cet ID
556
+ gitRegisterExistingID(matter, branch, file, config);
557
+ return matter;
558
+ }
559
+ catch (error) {
560
+ // ID déjà utilisé par un AUTRE fichier → générer un nouveau
561
+ // (Si même fichier, registerExistingID ne lève pas d'erreur)
562
+ }
563
+ }
564
+ // Générer un nouvel ID
565
+ matter.id = gitGenerateNextID(config);
566
+ // FIXME: Double save ici - gitGenerateNextID() fait save() puis gitRegisterExistingID() fait save() aussi
567
+ // Refactorisation future: Retirer save() de gitGenerateNextID() et le faire uniquement dans gitRegisterExistingID()
568
+ // quand matter est fourni (processus atomique: generate → setCache → register → save)
569
+ // ✅ Mettre à jour le cache avec le nouvel ID (seulement si branch est définie)
570
+ if (branch && file && matter.title) {
571
+ idRegistryService.setMatterCache(branch, file, {
572
+ id: matter.id,
573
+ title: matter.title,
574
+ service: matter.service
575
+ });
576
+ }
577
+ return matter;
578
+ }
579
+ /**
580
+ * Valide le format du matter (ID et title obligatoires)
581
+ * @param matter Le matter à valider
582
+ * @throws GitOperationError si le matter est invalide
583
+ */
584
+ function validateMatter(matter) {
585
+ if (!matter || typeof matter !== 'object') {
586
+ throw new errors_1.GitOperationError('Le matter est invalide', 'invalid_matter');
587
+ }
588
+ //
589
+ // ✅ FIX: Vérifier que l'ID existe et est un nombre
590
+ if (!matter.id || typeof matter.id !== 'number') {
591
+ throw new errors_1.GitOperationError('Le matter doit contenir un champ "id" de type number', 'missing_id', { matter });
592
+ }
593
+ if (matter.id <= 999) {
594
+ throw new errors_1.GitOperationError(`L'ID ${matter.id} est réservé (doit être >= 1000)`, 'invalid_id', { id: matter.id });
595
+ }
596
+ if (!matter.title || typeof matter.title !== 'string') {
597
+ throw new errors_1.GitOperationError('Le matter doit contenir un champ "title" de type string', 'missing_title', { matter });
598
+ }
599
+ }
600
+ /**
601
+ * Extraction rapide du matter depuis une chaîne de contenu (helper interne)
602
+ * @internal
603
+ */
604
+ function extractMatterFromContent(content) {
605
+ if (!content) {
606
+ return { id: 0, title: undefined, service: undefined, oldfile: undefined };
607
+ }
608
+ // Regex pour extraire id (format: "id: 1234" ou "id: '1234'")
609
+ const idMatch = content.match(/^id:\s*['"]?(\d+)['"]?/m);
610
+ const id = idMatch ? parseInt(idMatch[1], 10) : undefined;
611
+ // Regex pour extraire title (format: "title: Mon Titre" ou "title: 'Mon Titre'")
612
+ const titleMatch = content.match(/^title:\s*["']?([^"'\n]+)["']?/m);
613
+ const title = titleMatch ? titleMatch[1].trim() : undefined;
614
+ const serviceMatch = content.match(/^service:\s*["']?([^"'\n]+)["']?/m);
615
+ const service = serviceMatch ? serviceMatch[1].trim() : undefined;
616
+ //
617
+ // ✅ Extraction de oldfile (champ temporaire pour notifier les renames)
618
+ const oldfileMatch = content.match(/^oldfile:\s*["']?([^"'\n]+)["']?/m);
619
+ const oldfile = oldfileMatch ? oldfileMatch[1].trim() : undefined;
620
+ return { id, title, service, oldfile };
621
+ }
622
+ /**
623
+ * Lecture rapide et stricte du matter d'un fichier (id + title uniquement)
624
+ *
625
+ * **Optimisation : Utilise le cache du registre d'IDs pour éviter de lire le fichier.**
626
+ *
627
+ * Cette fonction :
628
+ * 1. Vérifie d'abord le cache dans `.git/with-ids.json`
629
+ * 2. Si absent ou invalidé, lit le fichier avec regex (pas de parsing complet)
630
+ * 3. Met à jour le cache automatiquement
631
+ *
632
+ * @param git Instance SimpleGit
633
+ * @param filePath Chemin du fichier
634
+ * @param branch Nom de la branche
635
+ * @param config Configuration Git optionnelle
636
+ * @returns { id, title } ou { id: undefined, title: undefined } si absent
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * const matter = await gitFileStrictMatter(git, 'doc.md', 'main');
641
+ * console.log(matter.id); // 1234
642
+ * console.log(matter.title); // "Mon Document"
643
+ * ```
644
+ */
645
+ async function gitFileStrictMatter(git, filePath, branch, config) {
646
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
647
+ idRegistryService.init(gitConf.repoPath);
648
+ try {
649
+ // ✅ ÉTAPE 1: Vérifier le cache
650
+ const cached = idRegistryService.getMatterCache(branch, filePath);
651
+ if (cached) {
652
+ // Cache trouvé, retourner directement
653
+ return {
654
+ id: cached.id,
655
+ title: cached.title,
656
+ oldfile: cached.oldfile // ✅ Inclure oldfile temporaire
657
+ };
658
+ }
659
+ // ❌ ÉTAPE 2: Cache absent, lire le fichier
660
+ const content = await git.show([`${branch}:${filePath}`]);
661
+ if (!content) {
662
+ return { id: undefined, title: undefined, oldfile: undefined };
663
+ }
664
+ // ✅ ÉTAPE 3: Extraction rapide avec regex (sans matterParse complet)
665
+ const matter = extractMatterFromContent(content);
666
+ // ✅ ÉTAPE 4: Mettre à jour le cache
667
+ idRegistryService.setMatterCache(branch, filePath, matter);
668
+ idRegistryService.save();
669
+ return matter;
670
+ }
671
+ catch (error) {
672
+ if (gitConf.verbose) {
673
+ console.warn(`⚠️ Erreur lecture matter de ${filePath}:`, error);
674
+ }
675
+ return { id: undefined, title: undefined, oldfile: undefined };
676
+ }
677
+ }
54
678
  /**
55
679
  * Vérifie et configure un repository Git existant pour s'assurer qu'il a la configuration requise
56
680
  * @param git Instance SimpleGit
@@ -391,6 +1015,64 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
391
1015
  let currentBranch;
392
1016
  try {
393
1017
  const gitConf = (0, repo_tools_1.gitLoad)(config);
1018
+ // ===== VALIDATION DES IDs (si activée) =====
1019
+ if (gitConf.withID) {
1020
+ try {
1021
+ // Import dynamique pour éviter les dépendances circulaires
1022
+ const { matterParse, matterSerialize } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
1023
+ const parsed = matterParse(content);
1024
+ if (gitConf.strictID) {
1025
+ // Mode strict : valider que l'ID existe et est correct
1026
+ validateMatter(parsed.matter);
1027
+ // ✅ gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement si matter est fourni
1028
+ // Enregistrer l'ID existant
1029
+ // ✅ Passer branch (PR) et file (filePath) pour vérifier propriété
1030
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
1031
+ }
1032
+ else {
1033
+ // Mode permissif : générer l'ID si manquant
1034
+ // ✅ IMPORTANT: Ne PAS ré-assigner l'ID s'il est déjà valide
1035
+ // Le contenu a peut-être déjà été traité par gitEnsureMatterID() dans le caller
1036
+ const hasValidID = parsed.matter.id &&
1037
+ typeof parsed.matter.id === 'number' &&
1038
+ parsed.matter.id > 999;
1039
+ if (!hasValidID) {
1040
+ // Seulement si l'ID est manquant ou invalide
1041
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1042
+ // Re-sérialiser avec l'ID mis à jour
1043
+ content = matterSerialize(parsed.content, parsed.matter);
1044
+ }
1045
+ else {
1046
+ // ✅ L'ID existe déjà : gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement
1047
+ try {
1048
+ gitRegisterExistingID(parsed.matter, PR, filePath, gitConf);
1049
+ }
1050
+ catch (error) {
1051
+ // Si l'ID est déjà utilisé par un autre fichier, générer un nouvel ID
1052
+ if (error.code === 'id_already_used') {
1053
+ // En mode non-strict, générer un nouvel ID si doublon détecté
1054
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
1055
+ content = matterSerialize(parsed.content, parsed.matter);
1056
+ // ✅ Ne pas propager l'erreur, continuer avec le nouvel ID généré
1057
+ }
1058
+ else {
1059
+ throw error;
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+ catch (error) {
1066
+ // En mode strict, propager l'erreur id_already_used
1067
+ // En mode permissif, cette erreur devrait déjà être gérée dans le bloc ci-dessus
1068
+ if (error.code === 'id_already_used' && gitConf.strictID) {
1069
+ throw new errors_1.GitOperationError(`Impossible de créer le fichier: ${error.message}`, 'duplicate_id', { filePath, error });
1070
+ }
1071
+ // Propager les autres erreurs de validation
1072
+ throw error;
1073
+ }
1074
+ }
1075
+ // ===== FIN VALIDATION IDs =====
394
1076
  //
395
1077
  // secure main and draft branches (on production)
396
1078
  if (!gitConf?.canForce && PR == gitConf.mainBranch) {
@@ -408,6 +1090,26 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
408
1090
  // without note the branche is not a valid PR branch
409
1091
  const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
410
1092
  const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
1093
+ // ✅ Mettre à jour le cache du matter après sauvegarde
1094
+ // NOTE: Le cache est TOUJOURS mis à jour, indépendamment de withID
1095
+ // C'est une opération de cache, pas une validation
1096
+ try {
1097
+ const { matterParse } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
1098
+ const parsed = matterParse(content);
1099
+ idRegistryService.init(gitConf.repoPath);
1100
+ idRegistryService.setMatterCache(PR, filePath, {
1101
+ id: parsed.matter.id,
1102
+ title: parsed.matter.title
1103
+ // ⚠️ Ne PAS persister oldfile dans le cache - temporaire uniquement pour client
1104
+ });
1105
+ idRegistryService.save();
1106
+ }
1107
+ catch (cacheError) {
1108
+ // Erreur non critique, ne pas bloquer la sauvegarde
1109
+ if (gitConf.verbose) {
1110
+ console.warn('⚠️ Erreur mise à jour cache matter:', cacheError);
1111
+ }
1112
+ }
411
1113
  if (!oldNote) {
412
1114
  return {
413
1115
  hash: commit.commit,
@@ -862,6 +1564,81 @@ async function gitGetBranchHealth(git, branch) {
862
1564
  (health.clean || (health.modifiedFiles === 0 && health.stagedFiles === 0));
863
1565
  return health;
864
1566
  }
1567
+ /**
1568
+ * Version optimisée de gitGetBranchHealth pour les branches de validation
1569
+ *
1570
+ * Cette fonction utilise une approche en cascade pour diagnostiquer rapidement
1571
+ * l'état d'une branche de validation sans effectuer de checkout coûteux.
1572
+ *
1573
+ * Tests effectués dans l'ordre (arrêt au premier échec) :
1574
+ * 1. Branche existe ? (git rev-parse --verify)
1575
+ * 2. Note Git présente sur HEAD ? (gitReadNote avec maxCommit=1)
1576
+ * 3. Note correspond au bon PR ? (note.id === prNumber)
1577
+ *
1578
+ * Avantages :
1579
+ * - ✅ Pas de checkout (gain 80-90% performance)
1580
+ * - ✅ Lecture directe du HEAD sans changer de branche
1581
+ * - ✅ Tests cascade (arrêt dès qu'un problème est détecté)
1582
+ * - ✅ Optimisé pour branches temporaires (validations)
1583
+ *
1584
+ * @param git Instance SimpleGit
1585
+ * @param branch Nom de la branche de validation à diagnostiquer
1586
+ * @param config Configuration Git optionnelle
1587
+ * @returns GitHealthStatus avec diagnostic optimisé
1588
+ */
1589
+ async function gitGetValidationBranchHealth(git, branch, config) {
1590
+ const gitConfig = config || (0, repo_tools_1.gitLoad)();
1591
+ const health = {
1592
+ branch,
1593
+ exists: false,
1594
+ accessible: false,
1595
+ clean: true, // Par défaut OK pour validation (pas de working dir check)
1596
+ mergeInProgress: false,
1597
+ conflictedFiles: 0,
1598
+ modifiedFiles: 0,
1599
+ untrackedFiles: 0,
1600
+ stagedFiles: 0,
1601
+ indexReadable: true,
1602
+ healthy: false,
1603
+ issues: [],
1604
+ recommendations: []
1605
+ };
1606
+ try {
1607
+ // 1. CASCADE: Vérifier existence (très rapide, sans checkout)
1608
+ const branchExists = await git.raw(['rev-parse', '--verify', branch])
1609
+ .then(() => true)
1610
+ .catch(() => false);
1611
+ if (!branchExists) {
1612
+ health.issues.push(`Branche '${branch}' n'existe pas`);
1613
+ health.recommendations.push(`Créer la branche ou vérifier le nom`);
1614
+ return health;
1615
+ }
1616
+ health.exists = true;
1617
+ health.accessible = true; // Si rev-parse OK, branche accessible
1618
+ // 2. CASCADE: Vérifier note sur HEAD (rapide, sans checkout)
1619
+ const headCommit = await git.raw(['rev-parse', branch]).then(s => s.trim());
1620
+ const note = await (0, repo_tools_1.gitReadNote)(git, headCommit, gitConfig.gitNotes.namespace, 1);
1621
+ if (!note) {
1622
+ health.issues.push(`Pas de note Git sur HEAD de '${branch}'`);
1623
+ health.recommendations.push(`Note perdue, utiliser git-notes-list-lost.sh pour la localiser`);
1624
+ return health;
1625
+ }
1626
+ // 3. CASCADE: Vérifier que la note correspond au PR
1627
+ const prNumber = parseInt(branch.split('-').pop() || '0', 10);
1628
+ if (note.id !== prNumber) {
1629
+ health.issues.push(`Note incorrecte: ID=${note.id}, attendu PR=${prNumber}`);
1630
+ health.recommendations.push(`Corriger la note ou supprimer la branche`);
1631
+ return health;
1632
+ }
1633
+ // ✅ Tous les tests essentiels passés
1634
+ health.healthy = true;
1635
+ }
1636
+ catch (error) {
1637
+ health.issues.push(`Erreur diagnostic: ${error.message}`);
1638
+ health.recommendations.push(`Vérifier l'état du dépôt Git`);
1639
+ }
1640
+ return health;
1641
+ }
865
1642
  /**
866
1643
  * Fonction helper pour créer un statut unhealthy
867
1644
  */