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
@@ -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;
@@ -51,6 +58,486 @@ const errors_1 = require("../errors");
51
58
  const path_1 = require("path");
52
59
  const fs = __importStar(require("fs/promises"));
53
60
  const repo_tools_1 = require("./repo.tools");
61
+ /**
62
+ * Service singleton pour gérer le registre d'IDs en mémoire
63
+ */
64
+ class IDRegistryService {
65
+ constructor() {
66
+ this.registry = null;
67
+ this.repoPath = '';
68
+ this.isDirty = false;
69
+ }
70
+ static get() {
71
+ return IDRegistryService.instance || (IDRegistryService.instance = new IDRegistryService());
72
+ }
73
+ /**
74
+ * Initialise le service avec le chemin du repository
75
+ */
76
+ init(repoPath) {
77
+ // Skip if already initialized
78
+ if (this.registry) {
79
+ return;
80
+ }
81
+ if (this.repoPath !== repoPath || !this.registry) {
82
+ this.repoPath = repoPath;
83
+ this.registry = this.loadFromDisk();
84
+ this.isDirty = false;
85
+ }
86
+ }
87
+ /**
88
+ * Force le rechargement du registre depuis le disque
89
+ * Utilisé principalement pour les tests qui simulent un redémarrage
90
+ */
91
+ forceReload() {
92
+ if (!this.repoPath) {
93
+ throw new Error('IDRegistryService not initialized. Call init() first.');
94
+ }
95
+ this.registry = this.loadFromDisk();
96
+ this.isDirty = false;
97
+ }
98
+ /**
99
+ * Charge le registre depuis le disque
100
+ */
101
+ loadFromDisk() {
102
+ const registryPath = (0, path_1.join)(this.repoPath, '.git', 'with-ids.json');
103
+ if (!(0, fs_1.existsSync)(registryPath)) {
104
+ return {
105
+ last: 980,
106
+ used: [],
107
+ updated: new Date().toISOString(),
108
+ matters: {}
109
+ };
110
+ }
111
+ try {
112
+ const data = JSON.parse((0, fs_1.readFileSync)(registryPath, 'utf8'));
113
+ // Migration : ajouter matters si absent (compatibilité anciens registres)
114
+ if (!data.matters) {
115
+ data.matters = {};
116
+ }
117
+ return data;
118
+ }
119
+ catch (error) {
120
+ console.warn('⚠️ Registre d\'IDs corrompu, création d\'un nouveau');
121
+ return {
122
+ last: 980,
123
+ used: [],
124
+ updated: new Date().toISOString(),
125
+ matters: {}
126
+ };
127
+ }
128
+ }
129
+ /**
130
+ * Sauvegarde le registre sur le disque (seulement si modifié)
131
+ */
132
+ save() {
133
+ if (!this.registry || !this.isDirty) {
134
+ return;
135
+ }
136
+ const gitDir = (0, path_1.join)(this.repoPath, '.git');
137
+ const registryPath = (0, path_1.join)(gitDir, 'with-ids.json');
138
+ // S'assurer que le répertoire .git existe
139
+ if (!(0, fs_1.existsSync)(gitDir)) {
140
+ console.warn(`⚠️ Cannot save ID registry: .git directory not found at ${this.repoPath}`);
141
+ return; // Ne pas sauvegarder si le repo n'est pas initialisé
142
+ }
143
+ try {
144
+ this.registry.updated = new Date().toISOString();
145
+ (0, fs_1.writeFileSync)(registryPath, JSON.stringify(this.registry, null, 2), 'utf8');
146
+ this.isDirty = false;
147
+ }
148
+ catch (error) {
149
+ console.warn(`⚠️ Failed to save ID registry:`, error);
150
+ // Ne pas propager l'erreur, le registre est une optimisation
151
+ }
152
+ }
153
+ /**
154
+ * Récupère le registre (lecture seule)
155
+ */
156
+ getRegistry() {
157
+ if (!this.registry) {
158
+ throw new Error('IDRegistryService not initialized. Call init() first.');
159
+ }
160
+ return this.registry;
161
+ }
162
+ /**
163
+ * Génère un nouvel ID unique
164
+ */
165
+ generateNextID() {
166
+ if (!this.registry) {
167
+ throw new Error('IDRegistryService not initialized. Call init() first.');
168
+ }
169
+ const newID = this.registry.last + 20;
170
+ this.registry.last = newID;
171
+ this.registry.used.push(newID);
172
+ this.isDirty = true;
173
+ return newID;
174
+ }
175
+ /**
176
+ * Vérifie si un ID appartient déjà à un fichier spécifique
177
+ */
178
+ isIDOwnedByFile(id, branch, file) {
179
+ if (!this.registry) {
180
+ return false;
181
+ }
182
+ const cacheKey = `${branch}:${file}`;
183
+ const cachedMatter = this.registry.matters[cacheKey];
184
+ return cachedMatter?.id === id;
185
+ }
186
+ /**
187
+ * Enregistre un ID existant
188
+ *
189
+ * @param id L'ID à enregistrer
190
+ * @param branch Branche du fichier (optionnel, pour vérification de propriété)
191
+ * @param file Nom du fichier (optionnel, pour vérification de propriété)
192
+ * @throws Error si l'ID est déjà utilisé par un autre fichier
193
+ */
194
+ registerExistingID(id, branch, file) {
195
+ if (!this.registry) {
196
+ throw new Error('IDRegistryService not initialized. Call init() first.');
197
+ }
198
+ // Si l'ID est déjà utilisé
199
+ if (this.registry.used.includes(id)) {
200
+ // Vérifier si c'est le même fichier (pas un doublon)
201
+ if (branch && file && this.isIDOwnedByFile(id, branch, file)) {
202
+ // Même fichier → OK, pas d'erreur
203
+ return;
204
+ }
205
+ // ID utilisé par un autre fichier → Erreur
206
+ const error = new Error(`ID ${id} est déjà utilisé`);
207
+ error.code = 'id_already_used';
208
+ throw error;
209
+ }
210
+ // Nouvel ID → Enregistrer
211
+ this.registry.used.push(id);
212
+ if (id > this.registry.last) {
213
+ this.registry.last = id;
214
+ }
215
+ this.isDirty = true;
216
+ }
217
+ /**
218
+ * Récupère un matter depuis le cache
219
+ * Note: Le champ 'updated' est filtré pour ne pas être exposé à l'extérieur
220
+ */
221
+ getMatterCache(branch, file) {
222
+ if (!this.registry) {
223
+ throw new Error('IDRegistryService not initialized. Call init() first.');
224
+ }
225
+ const cacheKey = `${branch}:${file}`;
226
+ const cached = this.registry.matters[cacheKey];
227
+ if (!cached) {
228
+ return undefined;
229
+ }
230
+ // Retourner uniquement les champs publics (sans 'updated')
231
+ return {
232
+ id: cached.id,
233
+ title: cached.title,
234
+ service: cached.service,
235
+ oldfile: cached.oldfile // FIXME (check if needed) ✅ Inclure oldfile temporaire si présent
236
+ };
237
+ }
238
+ /**
239
+ * Met à jour le cache d'un matter
240
+ */
241
+ setMatterCache(branch, file, matter) {
242
+ if (!this.registry) {
243
+ throw new Error('IDRegistryService not initialized. Call init() first.');
244
+ }
245
+ const cacheKey = `${branch}:${file}`;
246
+ this.registry.matters[cacheKey] = {
247
+ id: matter.id,
248
+ title: matter.title,
249
+ service: matter.service,
250
+ oldfile: matter.oldfile, // ✅ Inclure oldfile temporaire
251
+ updated: new Date().toISOString()
252
+ };
253
+ this.isDirty = true;
254
+ }
255
+ /**
256
+ * Supprime une entrée du cache
257
+ */
258
+ deleteMatterCache(branch, file) {
259
+ if (!this.registry) {
260
+ throw new Error('IDRegistryService not initialized. Call init() first.');
261
+ }
262
+ const cacheKey = `${branch}:${file}`;
263
+ delete this.registry.matters[cacheKey];
264
+ this.isDirty = true;
265
+ }
266
+ /**
267
+ * Renomme une entrée dans le cache du registre
268
+ *
269
+ * Cette méthode gère le renommage d'un fichier dans le cache:
270
+ * - Supprime l'ancienne clé `${branch}:${oldFile}`
271
+ * - Crée une nouvelle clé `${branch}:${newFile}` avec les mêmes données
272
+ * - Préserve l'ID et les métadonnées
273
+ *
274
+ * @param oldFile Ancien nom du fichier
275
+ * @param newFile Nouveau nom du fichier
276
+ * @param branch Branche concernée
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * // Après un rename Git
281
+ * idRegistryService.rename('old-name.md', 'new-name.md', 'rule-validation-1');
282
+ * ```
283
+ */
284
+ rename(oldFile, newFile, branch) {
285
+ if (!this.registry) {
286
+ throw new Error('IDRegistryService not initialized. Call init() first.');
287
+ }
288
+ const oldKey = `${branch}:${oldFile}`;
289
+ const newKey = `${branch}:${newFile}`;
290
+ // Récupérer le cache existant
291
+ const cached = this.registry.matters[oldKey];
292
+ if (!cached) {
293
+ // Pas de cache pour l'ancien fichier, rien à renommer
294
+ return;
295
+ }
296
+ // Supprimer l'ancienne clé
297
+ delete this.registry.matters[oldKey];
298
+ //
299
+ // Créer la nouvelle clé avec les mêmes données
300
+ // ⚠️ NE PAS inclure oldfile - c'est temporaire uniquement pour notification client
301
+ this.registry.matters[newKey] = {
302
+ id: cached.id,
303
+ title: cached.title,
304
+ service: cached.service,
305
+ updated: new Date().toISOString()
306
+ };
307
+ this.isDirty = true;
308
+ }
309
+ }
310
+ IDRegistryService.instance = null;
311
+ // ✅ Instance singleton du service
312
+ const idRegistryService = IDRegistryService.get();
313
+ /**
314
+ * Vérifie si le fichier de registre d'IDs existe dans le repository Git
315
+ *
316
+ * Cette fonction est utile pour détecter si le système d'ID natif a été initialisé
317
+ * dans le repository. Le fichier `with-ids.json` est créé automatiquement lors du
318
+ * premier appel à `gitGenerateNextID()` ou `gitEnsureMatterID()`.
319
+ *
320
+ * @param config Configuration Git optionnelle (utilise la config par défaut si non fournie)
321
+ * @returns `true` si le fichier `.git/with-ids.json` existe, `false` sinon
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const config = gitLoad({ repoPath: './my-repo' });
326
+ *
327
+ * if (gitIDRegistryExists(config)) {
328
+ * console.log('Le registre d\'IDs existe déjà');
329
+ * // On peut charger le registre existant
330
+ * gitReloadIDRegistry(config);
331
+ * } else {
332
+ * console.log('Première utilisation du système d\'IDs');
333
+ * // Le registre sera créé au premier gitGenerateNextID()
334
+ * }
335
+ * ```
336
+ */
337
+ function gitIDRegistryExists(config) {
338
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
339
+ const registryPath = (0, path_1.join)(gitConf.repoPath, '.git', 'with-ids.json');
340
+ return (0, fs_1.existsSync)(registryPath);
341
+ }
342
+ /**
343
+ * Génère un nouvel ID unique en incrémentant de +20
344
+ * @param config Configuration Git optionnelle
345
+ * @returns Un nouvel ID unique
346
+ */
347
+ function gitGenerateNextID(config) {
348
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
349
+ idRegistryService.init(gitConf.repoPath);
350
+ const newID = idRegistryService.generateNextID();
351
+ idRegistryService.save();
352
+ return newID;
353
+ }
354
+ /**
355
+ * Force le rechargement du registre d'IDs depuis le disque
356
+ *
357
+ * **Utilisé principalement pour les tests qui simulent un redémarrage du serveur.**
358
+ *
359
+ * En production, cette fonction n'est pas nécessaire car le singleton est
360
+ * réinitialisé à chaque redémarrage du process Node.js.
361
+ *
362
+ * @param config Configuration Git optionnelle
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * // Dans un test : simuler un redémarrage
367
+ * await gitCreateOrEditFile(...); // Modifie le registre
368
+ * gitReloadIDRegistry(); // Force le rechargement depuis le disque
369
+ * ```
370
+ */
371
+ function gitReloadIDRegistry(config) {
372
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
373
+ idRegistryService.init(gitConf.repoPath);
374
+ idRegistryService.forceReload();
375
+ }
376
+ /**
377
+ * Enregistre un ID existant dans le registre
378
+ *
379
+ * @param id L'ID à enregistrer
380
+ * @param branch Branche du fichier (optionnel, pour vérification de propriété)
381
+ * @param file Nom du fichier (optionnel, pour vérification de propriété)
382
+ * @param config Configuration Git optionnelle
383
+ * @throws GitOperationError si l'ID est déjà utilisé par un autre fichier
384
+ *
385
+ * **Note:** Si `branch` et `file` sont fournis, la fonction vérifie si l'ID appartient
386
+ * déjà au même fichier. Si oui, aucune erreur n'est levée (cas de re-scan).
387
+ */
388
+ function gitRegisterExistingID(id, branch, file, config) {
389
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
390
+ idRegistryService.init(gitConf.repoPath);
391
+ idRegistryService.registerExistingID(id, branch, file);
392
+ idRegistryService.save();
393
+ }
394
+ /**
395
+ * Renomme un fichier dans le cache du registre d'IDs
396
+ *
397
+ * Cette fonction met à jour le cache lorsqu'un fichier est renommé:
398
+ * - Supprime l'entrée avec l'ancien nom
399
+ * - Crée une nouvelle entrée avec le nouveau nom
400
+ * - Préserve l'ID et les métadonnées
401
+ *
402
+ * @param oldFile Ancien nom du fichier
403
+ * @param newFile Nouveau nom du fichier
404
+ * @param branch Branche concernée
405
+ * @param config Configuration Git optionnelle
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * // Après un rename dans Git
410
+ * gitIDRegistryRename('procedure-old.md', 'procedure-new.md', 'rule-validation-1');
411
+ * ```
412
+ */
413
+ function gitIDRegistryRename(oldFile, newFile, branch, config) {
414
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
415
+ idRegistryService.init(gitConf.repoPath);
416
+ idRegistryService.rename(oldFile, newFile, branch);
417
+ idRegistryService.save();
418
+ }
419
+ /**
420
+ * Valide et assure qu'un matter a un ID valide
421
+ * @param matter Le matter à valider/compléter
422
+ * @param config Configuration Git optionnelle
423
+ * @returns Le matter mis à jour avec un ID valide
424
+ */
425
+ function gitEnsureMatterID(matter, config) {
426
+ // Si ID existe et est valide
427
+ // FIXME 999 should be a constant in config
428
+ if (matter.id && typeof matter.id === 'number' && matter.id > 999) {
429
+ try {
430
+ // Pas de branch/file car utilisé pour nouveaux fichiers ou contexte inconnu
431
+ gitRegisterExistingID(matter.id, undefined, undefined, config);
432
+ return matter;
433
+ }
434
+ catch (error) {
435
+ // ID déjà utilisé → générer un nouveau (silencieux, géré par le caller)
436
+ }
437
+ }
438
+ // Générer un nouvel ID
439
+ matter.id = gitGenerateNextID(config);
440
+ return matter;
441
+ }
442
+ /**
443
+ * Valide le format du matter (ID et title obligatoires)
444
+ * @param matter Le matter à valider
445
+ * @throws GitOperationError si le matter est invalide
446
+ */
447
+ function validateMatter(matter) {
448
+ if (!matter || typeof matter !== 'object') {
449
+ throw new errors_1.GitOperationError('Le matter est invalide', 'invalid_matter');
450
+ }
451
+ //
452
+ // ✅ FIX: Vérifier que l'ID existe et est un nombre
453
+ if (!matter.id || typeof matter.id !== 'number') {
454
+ throw new errors_1.GitOperationError('Le matter doit contenir un champ "id" de type number', 'missing_id', { matter });
455
+ }
456
+ if (matter.id <= 999) {
457
+ throw new errors_1.GitOperationError(`L'ID ${matter.id} est réservé (doit être >= 1000)`, 'invalid_id', { id: matter.id });
458
+ }
459
+ if (!matter.title || typeof matter.title !== 'string') {
460
+ throw new errors_1.GitOperationError('Le matter doit contenir un champ "title" de type string', 'missing_title', { matter });
461
+ }
462
+ }
463
+ /**
464
+ * Extraction rapide du matter depuis une chaîne de contenu (helper interne)
465
+ * @internal
466
+ */
467
+ function extractMatterFromContent(content) {
468
+ if (!content) {
469
+ return { id: 0, title: undefined, service: undefined, oldfile: undefined };
470
+ }
471
+ // Regex pour extraire id (format: "id: 1234" ou "id: '1234'")
472
+ const idMatch = content.match(/^id:\s*['"]?(\d+)['"]?/m);
473
+ const id = idMatch ? parseInt(idMatch[1], 10) : undefined;
474
+ // Regex pour extraire title (format: "title: Mon Titre" ou "title: 'Mon Titre'")
475
+ const titleMatch = content.match(/^title:\s*["']?([^"'\n]+)["']?/m);
476
+ const title = titleMatch ? titleMatch[1].trim() : undefined;
477
+ const serviceMatch = content.match(/^service:\s*["']?([^"'\n]+)["']?/m);
478
+ const service = serviceMatch ? serviceMatch[1].trim() : undefined;
479
+ //
480
+ // ✅ Extraction de oldfile (champ temporaire pour notifier les renames)
481
+ const oldfileMatch = content.match(/^oldfile:\s*["']?([^"'\n]+)["']?/m);
482
+ const oldfile = oldfileMatch ? oldfileMatch[1].trim() : undefined;
483
+ return { id, title, service, oldfile };
484
+ }
485
+ /**
486
+ * Lecture rapide et stricte du matter d'un fichier (id + title uniquement)
487
+ *
488
+ * **Optimisation : Utilise le cache du registre d'IDs pour éviter de lire le fichier.**
489
+ *
490
+ * Cette fonction :
491
+ * 1. Vérifie d'abord le cache dans `.git/with-ids.json`
492
+ * 2. Si absent ou invalidé, lit le fichier avec regex (pas de parsing complet)
493
+ * 3. Met à jour le cache automatiquement
494
+ *
495
+ * @param git Instance SimpleGit
496
+ * @param filePath Chemin du fichier
497
+ * @param branch Nom de la branche
498
+ * @param config Configuration Git optionnelle
499
+ * @returns { id, title } ou { id: undefined, title: undefined } si absent
500
+ *
501
+ * @example
502
+ * ```typescript
503
+ * const matter = await gitFileStrictMatter(git, 'doc.md', 'main');
504
+ * console.log(matter.id); // 1234
505
+ * console.log(matter.title); // "Mon Document"
506
+ * ```
507
+ */
508
+ async function gitFileStrictMatter(git, filePath, branch, config) {
509
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
510
+ idRegistryService.init(gitConf.repoPath);
511
+ try {
512
+ // ✅ ÉTAPE 1: Vérifier le cache
513
+ const cached = idRegistryService.getMatterCache(branch, filePath);
514
+ if (cached) {
515
+ // Cache trouvé, retourner directement
516
+ return {
517
+ id: cached.id,
518
+ title: cached.title,
519
+ oldfile: cached.oldfile // ✅ Inclure oldfile temporaire
520
+ };
521
+ }
522
+ // ❌ ÉTAPE 2: Cache absent, lire le fichier
523
+ const content = await git.show([`${branch}:${filePath}`]);
524
+ if (!content) {
525
+ return { id: undefined, title: undefined, oldfile: undefined };
526
+ }
527
+ // ✅ ÉTAPE 3: Extraction rapide avec regex (sans matterParse complet)
528
+ const matter = extractMatterFromContent(content);
529
+ // ✅ ÉTAPE 4: Mettre à jour le cache
530
+ idRegistryService.setMatterCache(branch, filePath, matter);
531
+ idRegistryService.save();
532
+ return matter;
533
+ }
534
+ catch (error) {
535
+ if (gitConf.verbose) {
536
+ console.warn(`⚠️ Erreur lecture matter de ${filePath}:`, error);
537
+ }
538
+ return { id: undefined, title: undefined, oldfile: undefined };
539
+ }
540
+ }
54
541
  /**
55
542
  * Vérifie et configure un repository Git existant pour s'assurer qu'il a la configuration requise
56
543
  * @param git Instance SimpleGit
@@ -391,6 +878,35 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
391
878
  let currentBranch;
392
879
  try {
393
880
  const gitConf = (0, repo_tools_1.gitLoad)(config);
881
+ // ===== VALIDATION DES IDs (si activée) =====
882
+ if (gitConf.withID) {
883
+ try {
884
+ // Import dynamique pour éviter les dépendances circulaires
885
+ const { matterParse, matterSerialize } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
886
+ const parsed = matterParse(content);
887
+ if (gitConf.strictID) {
888
+ // Mode strict : valider que l'ID existe et est correct
889
+ validateMatter(parsed.matter);
890
+ // Enregistrer l'ID existant
891
+ // ✅ Passer branch (PR) et file (filePath) pour vérifier propriété
892
+ gitRegisterExistingID(parsed.matter.id, PR, filePath, gitConf);
893
+ }
894
+ else {
895
+ // Mode permissif : générer l'ID si manquant
896
+ parsed.matter = gitEnsureMatterID(parsed.matter, gitConf);
897
+ // Re-sérialiser avec l'ID mis à jour
898
+ content = matterSerialize(parsed.content, parsed.matter);
899
+ }
900
+ }
901
+ catch (error) {
902
+ if (error.code === 'id_already_used') {
903
+ throw new errors_1.GitOperationError(`Impossible de créer le fichier: ${error.message}`, 'duplicate_id', { filePath, error });
904
+ }
905
+ // Propager les autres erreurs de validation
906
+ throw error;
907
+ }
908
+ }
909
+ // ===== FIN VALIDATION IDs =====
394
910
  //
395
911
  // secure main and draft branches (on production)
396
912
  if (!gitConf?.canForce && PR == gitConf.mainBranch) {
@@ -408,6 +924,26 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
408
924
  // without note the branche is not a valid PR branch
409
925
  const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
410
926
  const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
927
+ // ✅ Mettre à jour le cache du matter après sauvegarde
928
+ // NOTE: Le cache est TOUJOURS mis à jour, indépendamment de withID
929
+ // C'est une opération de cache, pas une validation
930
+ try {
931
+ const { matterParse } = await Promise.resolve().then(() => __importStar(require('../utils.matter')));
932
+ const parsed = matterParse(content);
933
+ idRegistryService.init(gitConf.repoPath);
934
+ idRegistryService.setMatterCache(PR, filePath, {
935
+ id: parsed.matter.id,
936
+ title: parsed.matter.title
937
+ // ⚠️ Ne PAS persister oldfile dans le cache - temporaire uniquement pour client
938
+ });
939
+ idRegistryService.save();
940
+ }
941
+ catch (cacheError) {
942
+ // Erreur non critique, ne pas bloquer la sauvegarde
943
+ if (gitConf.verbose) {
944
+ console.warn('⚠️ Erreur mise à jour cache matter:', cacheError);
945
+ }
946
+ }
411
947
  if (!oldNote) {
412
948
  return {
413
949
  hash: commit.commit,
@@ -2,11 +2,32 @@ import { SimpleGit } from 'simple-git';
2
2
  import { RulesGitConfig, PRMetadata, GitCommitHistory, GitFileSummary } from '../types';
3
3
  export declare function lock(key: string): Promise<void>;
4
4
  export declare function unlock(key: string): void;
5
+ /**
6
+ * Valide si une valeur est un entier valide pour un ID
7
+ *
8
+ * Un ID valide doit être:
9
+ * - Un nombre (type number)
10
+ * - Un entier (pas de décimales)
11
+ * - Supérieur à 999 (réservé pour les IDs système)
12
+ * - Fini (pas NaN, pas Infinity)
13
+ *
14
+ * @param id Valeur à valider
15
+ * @returns true si l'ID est valide, false sinon
16
+ *
17
+ * @example
18
+ * isValidInt(1234) // true
19
+ * isValidInt(999) // false (trop petit)
20
+ * isValidInt(1.5) // false (pas un entier)
21
+ * isValidInt(NaN) // false
22
+ * isValidInt("1234") // false (pas un nombre)
23
+ */
24
+ export declare function isValidInt(id: any): boolean;
5
25
  /**
6
26
  * Configuration et initialisation du client Git
27
+ * @param defaultConfig Configuration partielle optionnelle pour override
7
28
  * @returns Configuration Git avec l'instance et les chemins
8
29
  */
9
- export declare function gitLoad(defaultConfig?: RulesGitConfig): RulesGitConfig;
30
+ export declare function gitLoad(defaultConfig?: Partial<RulesGitConfig>): RulesGitConfig;
10
31
  /**
11
32
  * Récupère la prévisualisation d'un fichier depuis Git (opération bas niveau atomique),
12
33
  * sans les métadonnées.
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.lock = lock;
37
37
  exports.unlock = unlock;
38
+ exports.isValidInt = isValidInt;
38
39
  exports.gitLoad = gitLoad;
39
40
  exports.gitGetFilePreview = gitGetFilePreview;
40
41
  exports.gitGetFileContent = gitGetFileContent;
@@ -76,11 +77,50 @@ function unlock(key) {
76
77
  releasers.delete(key);
77
78
  }
78
79
  }
80
+ /**
81
+ * Valide si une valeur est un entier valide pour un ID
82
+ *
83
+ * Un ID valide doit être:
84
+ * - Un nombre (type number)
85
+ * - Un entier (pas de décimales)
86
+ * - Supérieur à 999 (réservé pour les IDs système)
87
+ * - Fini (pas NaN, pas Infinity)
88
+ *
89
+ * @param id Valeur à valider
90
+ * @returns true si l'ID est valide, false sinon
91
+ *
92
+ * @example
93
+ * isValidInt(1234) // true
94
+ * isValidInt(999) // false (trop petit)
95
+ * isValidInt(1.5) // false (pas un entier)
96
+ * isValidInt(NaN) // false
97
+ * isValidInt("1234") // false (pas un nombre)
98
+ */
99
+ function isValidInt(id) {
100
+ // Vérifier que c'est un nombre
101
+ if (typeof id !== 'number') {
102
+ return false;
103
+ }
104
+ // Vérifier que c'est un nombre fini (pas NaN, pas Infinity)
105
+ if (!Number.isFinite(id)) {
106
+ return false;
107
+ }
108
+ // Vérifier que c'est un entier
109
+ if (!Number.isInteger(id)) {
110
+ return false;
111
+ }
112
+ // Vérifier que c'est supérieur à 999
113
+ if (id <= 999) {
114
+ return false;
115
+ }
116
+ return true;
117
+ }
79
118
  // Global variables for git config and instance
80
119
  let gitConfig;
81
120
  let git;
82
121
  /**
83
122
  * Configuration et initialisation du client Git
123
+ * @param defaultConfig Configuration partielle optionnelle pour override
84
124
  * @returns Configuration Git avec l'instance et les chemins
85
125
  */
86
126
  function gitLoad(defaultConfig) {
@@ -99,6 +139,13 @@ function gitLoad(defaultConfig) {
99
139
  const validationToken = defaultConfig?.validationToken || process.env.DEFAULT_VALIDATION_TOKEN;
100
140
  const sshKeyPath = process.env.GIT_SSH_KEY_PATH;
101
141
  const sshPassphrase = process.env.GIT_SSH_PASSPHRASE;
142
+ // Configuration de la validation des IDs
143
+ const withID = defaultConfig?.withID !== undefined
144
+ ? defaultConfig.withID
145
+ : process.env.GIT_WITH_ID == 'true';
146
+ const strictID = defaultConfig?.strictID !== undefined
147
+ ? defaultConfig.strictID
148
+ : process.env.GIT_STRICT_ID == 'true';
102
149
  if (!repoPath || !draftBranch || !uploadPath || !mainBranch || !validationToken) {
103
150
  throw new errors_1.GitConfigurationError(messages_1.i18nRules.git_config_incomplete, 'repoPath|draftBranch|uploadPath|mainBranch|validationToken');
104
151
  }
@@ -152,7 +199,9 @@ function gitLoad(defaultConfig) {
152
199
  remoteUrl,
153
200
  verbose,
154
201
  canForce: defaultConfig?.canForce,
155
- reset: defaultConfig?.reset
202
+ reset: defaultConfig?.reset,
203
+ withID,
204
+ strictID
156
205
  };
157
206
  git = gitInstance;
158
207
  return gitConfig;