agentic-api 1.0.6 → 2.0.31

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 (103) hide show
  1. package/README.md +336 -76
  2. package/dist/src/agents/agents.example.d.ts +3 -0
  3. package/dist/src/agents/agents.example.js +38 -0
  4. package/dist/src/agents/authentication.js +2 -0
  5. package/dist/src/agents/prompts.d.ts +2 -2
  6. package/dist/src/agents/prompts.js +112 -49
  7. package/dist/src/agents/reducer.core.d.ts +12 -0
  8. package/dist/src/agents/reducer.core.js +207 -0
  9. package/dist/src/agents/reducer.d.ts +3 -0
  10. package/dist/src/agents/reducer.example.d.ts +28 -0
  11. package/dist/src/agents/reducer.example.js +118 -0
  12. package/dist/src/agents/reducer.js +19 -0
  13. package/dist/src/agents/reducer.loaders.d.ts +34 -0
  14. package/dist/src/agents/reducer.loaders.js +122 -0
  15. package/dist/src/agents/reducer.process.d.ts +16 -0
  16. package/dist/src/agents/reducer.process.js +143 -0
  17. package/dist/src/agents/reducer.tools.d.ts +29 -0
  18. package/dist/src/agents/reducer.tools.js +157 -0
  19. package/dist/src/agents/reducer.types.d.ts +50 -0
  20. package/dist/src/agents/reducer.types.js +5 -0
  21. package/dist/src/agents/simulator.d.ts +47 -0
  22. package/dist/src/agents/simulator.executor.d.ts +26 -0
  23. package/dist/src/agents/simulator.executor.js +132 -0
  24. package/dist/src/agents/simulator.js +205 -0
  25. package/dist/src/agents/simulator.prompts.d.ts +16 -0
  26. package/dist/src/agents/simulator.prompts.js +108 -0
  27. package/dist/src/agents/simulator.types.d.ts +42 -0
  28. package/dist/src/agents/simulator.types.js +2 -0
  29. package/dist/src/agents/simulator.utils.d.ts +20 -0
  30. package/dist/src/agents/simulator.utils.js +87 -0
  31. package/dist/src/execute.d.ts +13 -6
  32. package/dist/src/execute.js +351 -85
  33. package/dist/src/index.d.ts +9 -0
  34. package/dist/src/index.js +14 -0
  35. package/dist/src/princing.openai.d.ts +9 -2
  36. package/dist/src/princing.openai.js +15 -11
  37. package/dist/src/prompts.d.ts +3 -2
  38. package/dist/src/prompts.js +159 -19
  39. package/dist/src/rag/embeddings.d.ts +103 -0
  40. package/dist/src/rag/embeddings.js +466 -0
  41. package/dist/src/rag/index.d.ts +12 -0
  42. package/dist/src/rag/index.js +40 -0
  43. package/dist/src/rag/lucene.d.ts +45 -0
  44. package/dist/src/rag/lucene.js +227 -0
  45. package/dist/src/rag/parser.d.ts +68 -0
  46. package/dist/src/rag/parser.js +192 -0
  47. package/dist/src/rag/tools.d.ts +76 -0
  48. package/dist/src/rag/tools.js +196 -0
  49. package/dist/src/rag/types.d.ts +178 -0
  50. package/dist/src/rag/types.js +21 -0
  51. package/dist/src/rag/usecase.d.ts +16 -0
  52. package/dist/src/rag/usecase.js +79 -0
  53. package/dist/src/rules/errors.d.ts +60 -0
  54. package/dist/src/rules/errors.js +97 -0
  55. package/dist/src/rules/git/git.e2e.helper.d.ts +104 -0
  56. package/dist/src/rules/git/git.e2e.helper.js +488 -0
  57. package/dist/src/rules/git/git.health.d.ts +66 -0
  58. package/dist/src/rules/git/git.health.js +354 -0
  59. package/dist/src/rules/git/git.helper.d.ts +129 -0
  60. package/dist/src/rules/git/git.helper.js +53 -0
  61. package/dist/src/rules/git/index.d.ts +6 -0
  62. package/dist/src/rules/git/index.js +76 -0
  63. package/dist/src/rules/git/repo.d.ts +128 -0
  64. package/dist/src/rules/git/repo.js +900 -0
  65. package/dist/src/rules/git/repo.pr.d.ts +137 -0
  66. package/dist/src/rules/git/repo.pr.js +589 -0
  67. package/dist/src/rules/git/repo.tools.d.ts +134 -0
  68. package/dist/src/rules/git/repo.tools.js +730 -0
  69. package/dist/src/rules/index.d.ts +8 -0
  70. package/dist/src/rules/index.js +25 -0
  71. package/dist/src/rules/messages.d.ts +17 -0
  72. package/dist/src/rules/messages.js +21 -0
  73. package/dist/src/rules/types.ctrl.d.ts +28 -0
  74. package/dist/src/rules/types.ctrl.js +2 -0
  75. package/dist/src/rules/types.d.ts +510 -0
  76. package/dist/src/rules/types.helpers.d.ts +132 -0
  77. package/dist/src/rules/types.helpers.js +2 -0
  78. package/dist/src/rules/types.js +33 -0
  79. package/dist/src/rules/user.mapper.d.ts +61 -0
  80. package/dist/src/rules/user.mapper.js +160 -0
  81. package/dist/src/rules/utils/slug.d.ts +22 -0
  82. package/dist/src/rules/utils/slug.js +35 -0
  83. package/dist/src/rules/utils.matter.d.ts +66 -0
  84. package/dist/src/rules/utils.matter.js +208 -0
  85. package/dist/src/rules/utils.slug.d.ts +22 -0
  86. package/dist/src/rules/utils.slug.js +35 -0
  87. package/dist/src/scrapper.d.ts +3 -2
  88. package/dist/src/scrapper.js +33 -37
  89. package/dist/src/stategraph/index.d.ts +8 -0
  90. package/dist/src/stategraph/index.js +21 -0
  91. package/dist/src/stategraph/stategraph.d.ts +91 -0
  92. package/dist/src/stategraph/stategraph.js +241 -0
  93. package/dist/src/stategraph/stategraph.storage.d.ts +41 -0
  94. package/dist/src/stategraph/stategraph.storage.js +166 -0
  95. package/dist/src/stategraph/types.d.ts +139 -0
  96. package/dist/src/stategraph/types.js +19 -0
  97. package/dist/src/types.d.ts +62 -39
  98. package/dist/src/types.js +53 -89
  99. package/dist/src/usecase.d.ts +4 -0
  100. package/dist/src/usecase.js +44 -0
  101. package/dist/src/utils.d.ts +12 -5
  102. package/dist/src/utils.js +30 -13
  103. package/package.json +9 -3
@@ -0,0 +1,730 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.lock = lock;
37
+ exports.unlock = unlock;
38
+ exports.gitLoad = gitLoad;
39
+ exports.gitGetFilePreview = gitGetFilePreview;
40
+ exports.gitGetFileContent = gitGetFileContent;
41
+ exports.gitReadFileOutsideRepo = gitReadFileOutsideRepo;
42
+ exports.gitGetFileHistory = gitGetFileHistory;
43
+ exports.gitLastCommit = gitLastCommit;
44
+ exports.gitListFilesInBranch = gitListFilesInBranch;
45
+ exports.gitListFilesOutsideRepo = gitListFilesOutsideRepo;
46
+ exports.gitFileExistsInBranch = gitFileExistsInBranch;
47
+ exports.gitGetUnmergedBranchesForFile = gitGetUnmergedBranchesForFile;
48
+ exports.gitIsFileMerged = gitIsFileMerged;
49
+ exports.gitGetAllBranches = gitGetAllBranches;
50
+ exports.gitGetDiffFiles = gitGetDiffFiles;
51
+ exports.gitReadNote = gitReadNote;
52
+ exports.gitWriteNote = gitWriteNote;
53
+ exports.gitDeleteNote = gitDeleteNote;
54
+ exports.gitGetFilesSummary = gitGetFilesSummary;
55
+ const simple_git_1 = require("simple-git");
56
+ const messages_1 = require("../messages");
57
+ const fs_1 = require("fs");
58
+ const errors_1 = require("../errors");
59
+ const path_1 = require("path");
60
+ const fs = __importStar(require("fs/promises"));
61
+ const async_mutex_1 = require("async-mutex");
62
+ // Global map to hold mutexes for different resources
63
+ const mutexes = new Map();
64
+ const releasers = new Map();
65
+ async function lock(key) {
66
+ if (!mutexes.has(key)) {
67
+ mutexes.set(key, new async_mutex_1.Mutex());
68
+ }
69
+ const mutex = mutexes.get(key);
70
+ const releaser = await mutex.acquire();
71
+ releasers.set(key, releaser);
72
+ }
73
+ function unlock(key) {
74
+ if (releasers.has(key)) {
75
+ releasers.get(key)(); // Execute the release function
76
+ releasers.delete(key);
77
+ }
78
+ }
79
+ // Global variables for git config and instance
80
+ let gitConfig;
81
+ let git;
82
+ /**
83
+ * Configuration et initialisation du client Git
84
+ * @returns Configuration Git avec l'instance et les chemins
85
+ */
86
+ function gitLoad(defaultConfig) {
87
+ if (gitConfig && !defaultConfig?.reset) {
88
+ return { ...gitConfig, ...(defaultConfig || {}) };
89
+ }
90
+ // console.log(`🌶️ gitLoad: First Loading git config`,defaultConfig);
91
+ const verbose = defaultConfig?.verbose || process.env.GIT_VERBOSE === 'true';
92
+ const remoteUrl = defaultConfig?.remoteUrl || process.env.GIT_REMOTE_URL;
93
+ const repoPath = defaultConfig?.repoPath || process.env.GIT_REPO_PATH;
94
+ const uploadPath = defaultConfig?.uploadPath || process.env.GIT_UPLOAD_PATH;
95
+ const draftBranch = defaultConfig?.draftBranch || process.env.DEFAULT_BRANCH_DRAFT;
96
+ const mainBranch = defaultConfig?.mainBranch || process.env.DEFAULT_BRANCH_MAIN;
97
+ const validationPrefix = defaultConfig?.validationPrefix || process.env.DEFAULT_BRANCH_VALIDATION_PREFIX;
98
+ const releasePrefix = defaultConfig?.releasePrefix || process.env.DEFAULT_BRANCH_RELEASE_PREFIX;
99
+ const validationToken = defaultConfig?.validationToken || process.env.DEFAULT_VALIDATION_TOKEN;
100
+ const sshKeyPath = process.env.GIT_SSH_KEY_PATH;
101
+ const sshPassphrase = process.env.GIT_SSH_PASSPHRASE;
102
+ if (!repoPath || !draftBranch || !uploadPath || !mainBranch || !validationToken) {
103
+ throw new errors_1.GitConfigurationError(messages_1.i18nRules.git_config_incomplete, 'repoPath|draftBranch|uploadPath|mainBranch|validationToken');
104
+ }
105
+ // Ensure the repository directory exists
106
+ if (!(0, fs_1.existsSync)(repoPath)) {
107
+ if (defaultConfig?.verbose)
108
+ console.log(`Creating repository directory: ${repoPath}`);
109
+ (0, fs_1.mkdirSync)(repoPath, { recursive: true });
110
+ }
111
+ // Ensure the repository directory exists
112
+ if (!(0, fs_1.existsSync)(uploadPath)) {
113
+ if (defaultConfig?.verbose)
114
+ console.log(`Creating upload directory: ${uploadPath}`);
115
+ (0, fs_1.mkdirSync)(uploadPath, { recursive: true });
116
+ }
117
+ // Configuration SSH si nécessaire
118
+ const gitOptions = { baseDir: repoPath, binary: 'git' };
119
+ if (sshKeyPath) {
120
+ gitOptions.config = {
121
+ 'core.sshCommand': `ssh -i ${sshKeyPath}${sshPassphrase ? ` -N` : ''}`
122
+ };
123
+ }
124
+ const gitInstance = (0, simple_git_1.simpleGit)(gitOptions);
125
+ // Configuration de la branche principale par défaut
126
+ if (gitInstance?.addConfig) {
127
+ gitInstance.addConfig('init.defaultBranch', mainBranch);
128
+ }
129
+ // Configuration de la concurrence
130
+ const concurrency = {
131
+ maxRetries: parseInt(process.env.GIT_CONCURRENCY_MAX_RETRIES || '3', 10),
132
+ retryDelayMs: parseInt(process.env.GIT_CONCURRENCY_RETRY_DELAY_MS || '100', 10),
133
+ timeoutMs: parseInt(process.env.GIT_CONCURRENCY_TIMEOUT_MS || '5000', 10)
134
+ };
135
+ // Configuration des Git Notes
136
+ const gitNotes = {
137
+ namespace: process.env.GIT_NOTES_NAMESPACE || 'refs/notes/pr-status',
138
+ enabled: process.env.GIT_NOTES_ENABLED !== 'false', // Activé par défaut
139
+ fallbackToCommit: process.env.GIT_NOTES_FALLBACK_TO_COMMIT !== 'false' // Activé par défaut
140
+ };
141
+ gitConfig = {
142
+ instance: gitInstance,
143
+ repoPath,
144
+ uploadPath,
145
+ draftBranch,
146
+ mainBranch,
147
+ validationPrefix: validationPrefix || 'rule-validation-',
148
+ releasePrefix: releasePrefix || 'rule-release-',
149
+ validationToken: validationToken || 'closed',
150
+ concurrency,
151
+ gitNotes,
152
+ remoteUrl,
153
+ verbose,
154
+ canForce: defaultConfig?.canForce,
155
+ reset: defaultConfig?.reset
156
+ };
157
+ git = gitInstance;
158
+ return gitConfig;
159
+ }
160
+ /**
161
+ * Récupère la prévisualisation d'un fichier depuis Git (opération bas niveau atomique),
162
+ * sans les métadonnées.
163
+ * @param git Instance SimpleGit
164
+ * @param filename Nom du fichier
165
+ * @param branch Branche optionnelle (par défaut: rule-editor)
166
+ * @returns Contenu du fichier ou null si non trouvé
167
+ */
168
+ async function gitGetFilePreview(git, filename, branch = 'rule-editor', fallback = false) {
169
+ const gitConfig = gitLoad();
170
+ // FIXME missing test for branch NEW
171
+ // Fast-path: NEW files are stored outside the repo. Read directly from filesystem.
172
+ if (branch?.toLowerCase() === 'new') {
173
+ try {
174
+ const { content } = await gitReadFileOutsideRepo(git, filename);
175
+ return content;
176
+ }
177
+ catch (error) {
178
+ if (!fallback)
179
+ return null;
180
+ }
181
+ }
182
+ if (branch.startsWith(gitConfig.validationPrefix)) {
183
+ try {
184
+ const parse = (await git.raw(['rev-list', '--reverse', branch, '--', filename])).split('\n').map(hash => hash.trim()).filter(hash => hash.length);
185
+ // Vérifier si branch est un hash de commit (40 caractères hexadécimaux) ou une branche
186
+ const hash = parse[0];
187
+ const content = await git.show([`${hash}:${filename}`]);
188
+ return content;
189
+ }
190
+ catch (error) {
191
+ console.log('🌶️ gitGetFilePreview', `${branch}:${filename}`, error.message);
192
+ // try other solutions
193
+ if (!fallback) {
194
+ return null;
195
+ }
196
+ }
197
+ }
198
+ try {
199
+ // 1. Récupération atomique du contenu
200
+ const showRef = `${branch}:${filename}`;
201
+ const contents = await git.show([showRef]);
202
+ return contents;
203
+ }
204
+ catch (error) {
205
+ }
206
+ try {
207
+ // 0. Check if branch is NEW
208
+ const { content, date } = await gitReadFileOutsideRepo(git, filename);
209
+ return content;
210
+ }
211
+ catch (error) {
212
+ }
213
+ return null;
214
+ }
215
+ /**
216
+ * Récupère le contenu d'un fichier depuis Git (opération bas niveau atomique),
217
+ * ainsi que les métadonnées du dernier commit qui a touché ce fichier dans la même branche.
218
+ * @param git Instance SimpleGit
219
+ * @param filename Chemin du fichier
220
+ * @param branch Branche optionnelle (par défaut: HEAD)
221
+ * @returns Contenu du fichier avec métadonnées ou null si non trouvé
222
+ */
223
+ async function gitGetFileContent(git, filename, branch = 'HEAD') {
224
+ try {
225
+ // 0. Check if branch is NEW
226
+ if (branch?.toLocaleLowerCase() === 'new') {
227
+ const { content, date } = await gitReadFileOutsideRepo(git, filename);
228
+ return {
229
+ hash: 'NEW',
230
+ date,
231
+ message: '',
232
+ author: { name: 'system', email: 'system' },
233
+ content,
234
+ branch: 'NEW'
235
+ };
236
+ }
237
+ // 1. Récupération atomique du contenu
238
+ const showRef = `${branch}:${filename}`;
239
+ const content = await git.show([showRef]);
240
+ const log = await git.log(['-n', '1', branch, '--', filename]);
241
+ const commit = log.latest;
242
+ //console.log('🌶️ gitGetFileContent', {filename, branch, commit});
243
+ if (!commit) {
244
+ return null;
245
+ }
246
+ return {
247
+ hash: commit.hash,
248
+ date: new Date(commit.date),
249
+ message: commit.message,
250
+ author: {
251
+ name: commit.author_name,
252
+ email: commit.author_email
253
+ },
254
+ content: content,
255
+ branch
256
+ };
257
+ }
258
+ catch (error) {
259
+ // Si le fichier n'existe pas dans la branche donnée, git.show ou git.log lève une erreur
260
+ const msg = error.message;
261
+ if (msg.includes('does not exist') || msg.includes('fatal: Path')) {
262
+ return null;
263
+ }
264
+ // Autres erreurs : on remonte (TODO for next release)
265
+ // throw error;
266
+ return null;
267
+ }
268
+ }
269
+ /**
270
+ * FIXME:missing return type
271
+ * Lit le contenu d'un fichier depuis le système de fichiers (hors repo ou dans uploadPath)
272
+ * @param filePath Chemin du fichier
273
+ * @returns Contenu du fichier ou null si non trouvé
274
+ */
275
+ async function gitReadFileOutsideRepo(git, filePath) {
276
+ const gitConfig = gitLoad();
277
+ const uploadPath = gitConfig.uploadPath;
278
+ const fullPath = (0, path_1.join)(uploadPath, filePath);
279
+ try {
280
+ const stats = await fs.stat(fullPath);
281
+ const date = stats.mtime;
282
+ const content = await fs.readFile(fullPath, 'utf-8');
283
+ return { content, date };
284
+ }
285
+ catch (error) {
286
+ // if (error.code === 'ENOENT') {
287
+ // return null;
288
+ // }
289
+ throw error;
290
+ }
291
+ }
292
+ async function gitGetFileHistory(git, filename, hash) {
293
+ try {
294
+ const log = await git.log({ file: filename });
295
+ const all = log.all.map(commit => ({
296
+ hash: commit.hash,
297
+ date: new Date(commit.date),
298
+ message: commit.message,
299
+ author: {
300
+ name: commit.author_name,
301
+ email: commit.author_email
302
+ }
303
+ }));
304
+ // Si un hash est fourni, retourner le contenu du fichier à ce hash
305
+ if (hash) {
306
+ const fileContent = await gitGetFileContent(git, filename, hash);
307
+ const index = all.findIndex(commit => commit.hash === hash);
308
+ if (index !== -1 && fileContent) {
309
+ all[index] = fileContent;
310
+ }
311
+ }
312
+ return all;
313
+ }
314
+ catch (error) {
315
+ throw new errors_1.GitOperationError(`Failed to get file history for ${filename}: ${error}`, 'file_history_get', { filename, error });
316
+ }
317
+ }
318
+ /**
319
+ * Récupère le dernier commit d'une branche (opération bas niveau atomique)
320
+ * @param git Instance SimpleGit
321
+ * @param branch Nom de la branche
322
+ * @returns Dernier commit de la branche ou null si la branche n'existe pas
323
+ */
324
+ async function gitLastCommit(git, branch) {
325
+ try {
326
+ const log = await git.log({ from: branch, to: branch, maxCount: 1 });
327
+ const commit = log.latest;
328
+ if (!commit) {
329
+ return null;
330
+ }
331
+ return {
332
+ hash: commit.hash,
333
+ date: new Date(commit.date),
334
+ message: commit.message,
335
+ author: {
336
+ name: commit.author_name,
337
+ email: commit.author_email
338
+ },
339
+ branch
340
+ };
341
+ }
342
+ catch (error) {
343
+ throw new errors_1.GitOperationError(`Failed to get last commit for branch ${branch}: ${error}`, 'last_commit_get', { branch, error });
344
+ }
345
+ }
346
+ /**
347
+ * Liste tous les fichiers d'un type donné dans une branche (opération bas niveau atomique)
348
+ * @param git Instance Git
349
+ * @param branch Nom de la branche à lister
350
+ * @param pattern Pattern de fichiers (ex:'.md')
351
+ * @returns Liste des chemins de fichiers
352
+ */
353
+ async function gitListFilesInBranch(git, branch, ext = '.md') {
354
+ try {
355
+ // Utilisation atomique de ls-tree sans checkout
356
+ const output = await git.raw(['ls-tree', '-r', '--name-only', branch]);
357
+ const allFiles = output.split('\n').filter(Boolean);
358
+ // Filtrer selon le pattern (simple matching pour *.md)
359
+ return allFiles.filter(file => file.endsWith(ext));
360
+ }
361
+ catch (error) {
362
+ throw new errors_1.BranchNotFoundError(branch);
363
+ }
364
+ }
365
+ async function gitListFilesOutsideRepo(git, options = {}) {
366
+ try {
367
+ const gitConfig = gitLoad();
368
+ const { ext = '.md' } = options;
369
+ const uploadPath = gitConfig.uploadPath; // Chemin absolu
370
+ const files = [];
371
+ // List files from upload directory using fs.readdir
372
+ try {
373
+ const uploadFiles = await fs.readdir(uploadPath);
374
+ for (const fileName of uploadFiles) {
375
+ if (!ext || fileName.endsWith(ext)) {
376
+ try {
377
+ const fullPath = (0, path_1.join)(uploadPath, fileName);
378
+ const stats = await fs.stat(fullPath);
379
+ files.push({ file: fileName, path: fullPath, date: stats.mtime });
380
+ }
381
+ catch (statError) {
382
+ if (gitConfig.verbose) {
383
+ console.warn(`🌶️ DEBUG: gitListFilesOutsideRepo -- Impossible de lire les stats du fichier ${fileName} dans ${uploadPath}, ignoré. Erreur: ${statError}`);
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ catch (error) {
390
+ if (gitConfig.verbose) {
391
+ console.warn(`🌶️ DEBUG: gitListFilesOutsideRepo -- Impossible d'accéder au répertoire ${uploadPath}: ${error}`);
392
+ }
393
+ }
394
+ return files;
395
+ }
396
+ catch (error) {
397
+ throw new errors_1.GitOperationError(`Failed to list files outside repo: ${error}`, 'gitListFilesOutsideRepo', { error });
398
+ }
399
+ }
400
+ /**
401
+ * Vérifie si un fichier existe dans une branche donnée et s'il est plus récent que la version dans main
402
+ * @param git Instance Git
403
+ * @param filePath Chemin du fichier
404
+ * @param branch Branche à vérifier
405
+ * @param config Configuration Git (optionnel, utilise la config globale)
406
+ * @returns true si le fichier existe et est plus récent que main
407
+ */
408
+ async function gitFileExistsInBranch(git, filePath, branch = 'HEAD', config) {
409
+ const gitConfig = gitLoad();
410
+ try {
411
+ //
412
+ // If file does not exist, git.raw will throw an error
413
+ const showRef = `${branch.toString()}:${filePath}`;
414
+ await git.raw(['cat-file', '-e', showRef]);
415
+ // const out = await git.raw(['ls-tree', '-r', '--name-only', branch, '--', filePath]);
416
+ // console.log('🌶️ DEBUG: gitFileExistsInBranch:', { showRef, out });
417
+ return true;
418
+ }
419
+ catch (error) {
420
+ if (/does not exist/.test(error.message)) {
421
+ return false;
422
+ }
423
+ if (/exists on disk, but not in/.test(error.message)) {
424
+ return false;
425
+ }
426
+ if (gitConfig.verbose)
427
+ console.log('🌶️ DEBUG: gitFileExistsInBranch:', error);
428
+ return false;
429
+ }
430
+ }
431
+ /**
432
+ * Retourne la liste des branches non mergées contenant un fichier spécifique
433
+ * @param git Instance Git
434
+ * @param filePath Chemin du fichier
435
+ * @param targetBranch Branche cible (par défaut: main)
436
+ * @returns Liste des branches non mergées contenant le fichier
437
+ */
438
+ async function gitGetUnmergedBranchesForFile(git, filePath, targetBranch = 'main') {
439
+ try {
440
+ // 1. Obtenir toutes les branches non mergées
441
+ const unmergedBranches = await gitGetAllBranches(git, {
442
+ unmergedOnly: true,
443
+ mainBranch: targetBranch
444
+ });
445
+ // 2. Filtrer les branches qui contiennent le fichier
446
+ const branchesWithFile = [];
447
+ for (const branch of unmergedBranches) {
448
+ const exists = await gitFileExistsInBranch(git, filePath, branch);
449
+ if (exists) {
450
+ branchesWithFile.push(branch);
451
+ }
452
+ }
453
+ return branchesWithFile;
454
+ }
455
+ catch (error) {
456
+ throw new errors_1.GitOperationError(`Failed to get unmerged branches for file ${filePath}: ${error}`, 'unmerged_branches_for_file', { filePath, targetBranch, error });
457
+ }
458
+ }
459
+ /**
460
+ * Vérifie si la dernière version du fichier (sur sourceBranch) a été mergée dans targetBranch.
461
+ * - Recherche du dernier commit modifiant filePath sur sourceBranch
462
+ * - Test de l'ancêtre (merge-base --is-ancestor)
463
+ */
464
+ async function gitIsFileMerged(git, filePath, from, target = 'main') {
465
+ const gitConfig = gitLoad();
466
+ try {
467
+ // 0. Vérification rapide : si la branche source est mergée, le fichier l'est aussi
468
+ const unmergedBranches = await gitGetAllBranches(git, { unmergedOnly: true, mainBranch: target });
469
+ if (!unmergedBranches.includes(from)) {
470
+ return true; // La branche est mergée, donc le fichier aussi
471
+ }
472
+ // 1. Récupérer le dernier commit qui a touché le fichier dans la branche source
473
+ const commitHash = await git.raw(['log', from, '--', filePath, '-1', '--format=%H']);
474
+ const hash = commitHash.trim();
475
+ if (!hash) {
476
+ return false;
477
+ }
478
+ // 2. Récupérer le dernier commit qui a touché le fichier dans la branche cible
479
+ const lastMain = await git.raw(['log', target, '--', filePath, '-1', '--format=%H']);
480
+ const mainHash = lastMain.trim();
481
+ // 3. Si les commits sont identiques, c'est un merge fast-forward réussi
482
+ if (hash === mainHash) {
483
+ return true;
484
+ }
485
+ // 4. Sinon, vérifier si le commit est ancestor de targetBranch
486
+ try {
487
+ await git.raw(['merge-base', '--is-ancestor', hash, target]);
488
+ return true; // code de sortie 0 => mergé
489
+ }
490
+ catch (error) {
491
+ return false; // non-ancêtre => pas mergé
492
+ }
493
+ }
494
+ catch (error) {
495
+ if (gitConfig.verbose)
496
+ console.warn(`🌶️ DBG ---- Erreur lors de la vérification du merge de ${filePath}:`, error);
497
+ return false;
498
+ }
499
+ }
500
+ /**
501
+ * Récupère la liste de toutes les branches (opération bas niveau)
502
+ * @param git Instance Git
503
+ * @param options Options de filtrage des branches
504
+ * @returns Liste des noms de branches
505
+ */
506
+ async function gitGetAllBranches(git, options = {}) {
507
+ try {
508
+ const gitConfig = gitLoad();
509
+ if (options.unmergedOnly) {
510
+ const main = options.mainBranch || gitConfig.mainBranch;
511
+ const output = await git.raw(['branch', '--no-merged', main]);
512
+ return output
513
+ .split('\n')
514
+ .map((b) => b.trim().replace('* ', ''))
515
+ .filter(Boolean);
516
+ }
517
+ // Sinon, retourner toutes les branches
518
+ const branches = await git.branchLocal();
519
+ return branches.all;
520
+ }
521
+ catch (error) {
522
+ throw new errors_1.GitOperationError(`Failed to retrieve branches: ${error}`, 'branch_listing', { originalError: error });
523
+ }
524
+ }
525
+ /**
526
+ * Récupère les fichiers modifiés entre deux branches (sans état)
527
+ * @param git Instance SimpleGit
528
+ * @param targetBranch Branche à comparer
529
+ * @param baseBranch Branche de base (par défaut: main)
530
+ * @param filter Filtre optionnel (ex: '.md')
531
+ * @returns Liste des fichiers modifiés
532
+ */
533
+ async function gitGetDiffFiles(git, targetBranch, baseBranch, filter) {
534
+ const gitConfig = gitLoad();
535
+ const base = baseBranch || gitConfig.mainBranch;
536
+ try {
537
+ // Utilise git diff --name-only pour obtenir les fichiers modifiés
538
+ const diffResult = await git.raw(['diff', '--name-only', `${base}...${targetBranch}`]);
539
+ const files = diffResult
540
+ .split('\n')
541
+ .filter((file) => file.trim()) // Supprimer les lignes vides
542
+ .filter((file) => !filter || file.endsWith(filter)); // Appliquer le filtre si fourni
543
+ return files;
544
+ }
545
+ catch (error) {
546
+ if (gitConfig.verbose)
547
+ console.error(`[gitGetDiffFiles] Error getting diff between ${base} and ${targetBranch}:`, error);
548
+ return [];
549
+ }
550
+ }
551
+ /**
552
+ * Lit une note Git pour une branche donnée
553
+ * @param git Instance Git
554
+ * @param branchOrCommit Hash du commit ou nom de la branche
555
+ * @param namespace Namespace des notes
556
+ * @param maxCommits Nombre maximum de commits à examiner (défaut: 10)
557
+ * @returns Contenu de la note ou null si inexistante
558
+ */
559
+ async function gitReadNote(git, branchOrCommit, namespace, maxCommits = 10) {
560
+ try {
561
+ // D'abord, essayer de lire directement la note sur la branche/commit
562
+ try {
563
+ const noteContent = await git.raw(['notes', '--ref', namespace, 'show', branchOrCommit]);
564
+ if (noteContent) {
565
+ return JSON.parse(noteContent.trim());
566
+ }
567
+ }
568
+ catch (error) {
569
+ // Si pas de note directement sur la branche, continuer avec l'historique
570
+ }
571
+ // Récupérer les N derniers commits de la branche
572
+ const log = await git.log(['-n', `${maxCommits}`, branchOrCommit]);
573
+ // const log = await git.log({
574
+ // from: branchOrCommit,
575
+ // to: branchOrCommit
576
+ // });
577
+ // Chercher une note dans chaque commit, du plus récent au plus ancien
578
+ for (const commit of log.all) {
579
+ try {
580
+ const noteContent = await git.raw(['notes', '--ref', namespace, 'show', commit.hash]);
581
+ if (noteContent) {
582
+ return JSON.parse(noteContent.trim());
583
+ }
584
+ }
585
+ catch (error) {
586
+ // Continue avec le commit suivant si aucune note n'est trouvée
587
+ continue;
588
+ }
589
+ }
590
+ //
591
+ // Si aucune note trouvée dans l'historique, essayer de lister toutes les notes disponibles
592
+ // try {
593
+ // const allNotes = await git.raw(['notes', '--ref', namespace, 'list']);
594
+ // if (allNotes.trim()) {
595
+ // const noteLines = allNotes.trim().split('\n');
596
+ // for (const line of noteLines) {
597
+ // const [noteHash, commitHash] = line.split(' ');
598
+ // if (noteHash && commitHash) {
599
+ // try {
600
+ // const noteContent = await git.raw(['notes', '--ref', namespace, 'show', commitHash]);
601
+ // if (noteContent) {
602
+ // const metadata = JSON.parse(noteContent.trim());
603
+ // // Vérifier si cette note correspond à notre branche
604
+ // if (metadata && typeof metadata === 'object' && metadata.id) {
605
+ // return metadata;
606
+ // }
607
+ // }
608
+ // } catch(error) {
609
+ // continue;
610
+ // }
611
+ // }
612
+ // }
613
+ // }
614
+ // } catch(error) {
615
+ // // Ignorer les erreurs de listing des notes
616
+ // }
617
+ return null; // Aucune note trouvée
618
+ }
619
+ catch (error) {
620
+ // En cas d'erreur (branche inexistante, etc.), retourner null
621
+ return null;
622
+ }
623
+ }
624
+ /**
625
+ * Écrit une note Git pour une branche donnée
626
+ * @param git Instance Git
627
+ * @param commitHash Hash du commit
628
+ * @param author la note
629
+ * @param content Contenu de la note
630
+ * @param namespace Namespace des notes
631
+ */
632
+ async function gitWriteNote(git, commitHash, metadata, namespace) {
633
+ await lock(`gitWriteNote:${commitHash}`);
634
+ try {
635
+ // Ajouter ou remplacer la note
636
+ const noteMetadata = {
637
+ ...metadata,
638
+ ...{ timestamp: new Date().toISOString() }
639
+ };
640
+ const content = JSON.stringify(noteMetadata, null, 2);
641
+ await git.raw(['notes', '--ref', namespace, 'add', '-f', '-m', content, commitHash]);
642
+ // console.log(`[DEBUG][gitWriteNote] Note written successfully for ${branch}. Content snippet: ${content.substring(0, 50)}...`);
643
+ }
644
+ catch (error) {
645
+ console.error(`[DEBUG][gitWriteNote] Error writing note for ${commitHash}:`, error);
646
+ throw new errors_1.GitOperationError(`Failed to write note to branch "${commitHash}": ${error}`, 'note_write', { branch: commitHash, error });
647
+ }
648
+ finally {
649
+ unlock(`gitWriteNote:${commitHash}`);
650
+ }
651
+ }
652
+ /**
653
+ * Modifie un fichier dans la branche draft (opération bas niveau)
654
+ * @param namespace Namespace des notes
655
+ */
656
+ async function gitDeleteNote(git, commitHash, namespace) {
657
+ try {
658
+ await git.raw(['notes', '--ref', namespace, 'remove', commitHash]);
659
+ }
660
+ catch (e) {
661
+ // Ignore error if note does not exist
662
+ }
663
+ }
664
+ /**
665
+ * FIXME: GitFileSummary must be cached in memory (and refreshed on change)
666
+ * Retrieves a summary (content and last commit) for multiple files in a branch efficiently.
667
+ * It uses a single `git log` command for all files to improve performance.
668
+ * @param git SimpleGit instance
669
+ * @param branch The branch to inspect
670
+ * @returns An array of GitFileSummary objects.
671
+ */
672
+ async function gitGetFilesSummary(git, branch) {
673
+ const files = await gitListFilesInBranch(git, branch, '.md');
674
+ if (files.length === 0) {
675
+ return [];
676
+ }
677
+ // 1. Get content for all files in parallel
678
+ const contents = await Promise.all(files.map((file) => git.show([`${branch}:${file}`]).catch(() => null)));
679
+ // 2. Get last commit for all files in one go using a raw log command
680
+ const logOutput = await git.raw([
681
+ 'log',
682
+ branch,
683
+ '--name-only',
684
+ '--pretty=format:---COMMIT---%n%H%n%aN%n%aE%n%aI',
685
+ '--',
686
+ ...files
687
+ ]);
688
+ const commitsByFile = new Map();
689
+ if (logOutput) {
690
+ const commitChunks = logOutput.split('---COMMIT---\n');
691
+ for (const chunk of commitChunks) {
692
+ if (chunk.trim() === '')
693
+ continue;
694
+ const lines = chunk.trim().split('\n');
695
+ const [hash, authorName, authorEmail, date] = lines.slice(0, 4);
696
+ const changedFiles = lines.slice(4).filter(Boolean);
697
+ for (const file of changedFiles) {
698
+ if (!commitsByFile.has(file)) {
699
+ commitsByFile.set(file, {
700
+ hash,
701
+ author: { name: authorName, email: authorEmail },
702
+ date,
703
+ });
704
+ }
705
+ }
706
+ }
707
+ }
708
+ // 3. Combine results
709
+ const summaries = [];
710
+ for (let i = 0; i < files.length; i++) {
711
+ const filePath = files[i];
712
+ const content = contents[i];
713
+ const commitInfo = commitsByFile.get(filePath);
714
+ if (content !== null && commitInfo) {
715
+ summaries.push({
716
+ filePath,
717
+ content,
718
+ commit: {
719
+ hash: commitInfo.hash,
720
+ date: new Date(commitInfo.date),
721
+ message: '', // Not retrieved for performance, can be added if needed
722
+ author: commitInfo.author,
723
+ branch,
724
+ content,
725
+ },
726
+ });
727
+ }
728
+ }
729
+ return summaries;
730
+ }