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,900 @@
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.gitEnsureRepositoryConfiguration = gitEnsureRepositoryConfiguration;
37
+ exports.gitEnsureRemoteConfiguration = gitEnsureRemoteConfiguration;
38
+ exports.gitInit = gitInit;
39
+ exports.gitSetupRepository = gitSetupRepository;
40
+ exports.gitShowConfiguration = gitShowConfiguration;
41
+ exports.gitCheckConfiguration = gitCheckConfiguration;
42
+ exports.gitCreateOrEditFile = gitCreateOrEditFile;
43
+ exports.gitEditFile = gitEditFile;
44
+ exports.gitRenameFile_git = gitRenameFile_git;
45
+ exports.gitRenameFile_fs = gitRenameFile_fs;
46
+ exports.gitRenameFile = gitRenameFile;
47
+ exports.gitDeleteFile = gitDeleteFile;
48
+ exports.gitGetBranchHealth = gitGetBranchHealth;
49
+ const fs_1 = require("fs");
50
+ const errors_1 = require("../errors");
51
+ const path_1 = require("path");
52
+ const fs = __importStar(require("fs/promises"));
53
+ const repo_tools_1 = require("./repo.tools");
54
+ /**
55
+ * Vérifie et configure un repository Git existant pour s'assurer qu'il a la configuration requise
56
+ * @param git Instance SimpleGit
57
+ * @param options Options de configuration
58
+ */
59
+ async function gitEnsureRepositoryConfiguration(git, options = {}) {
60
+ const { remoteUrl = (0, repo_tools_1.gitLoad)().remoteUrl, remoteName = 'origin', forceUpdate = false } = options;
61
+ const changes = [];
62
+ try {
63
+ const gitConfig = (0, repo_tools_1.gitLoad)();
64
+ const isRepo = await git.checkIsRepo();
65
+ if (!isRepo) {
66
+ throw new errors_1.GitConfigurationError('Not a Git repository', 'repo_not_found');
67
+ }
68
+ // Vérifier la configuration de la branche par défaut
69
+ try {
70
+ const configs = await git.listConfig();
71
+ const currentDefaultBranch = configs.all['init.defaultbranch'];
72
+ const denyCurrentBranchConfig = configs.all['receive.denyCurrentBranch']?.toString().trim();
73
+ if (currentDefaultBranch !== gitConfig.mainBranch || forceUpdate) {
74
+ await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
75
+ changes.push(`Set default branch to ${gitConfig.mainBranch}`);
76
+ }
77
+ if (denyCurrentBranchConfig !== 'updateInstead' && remoteUrl) {
78
+ await git.addConfig('receive.denyCurrentBranch', 'updateInstead');
79
+ changes.push('✓ Configured receive.denyCurrentBranch = updateInstead for local pushes');
80
+ }
81
+ }
82
+ catch (error) {
83
+ await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
84
+ changes.push(`Set default branch to ${gitConfig.mainBranch}`);
85
+ }
86
+ // Configuration des Git Notes (avec ou sans remote)
87
+ try {
88
+ // Vérifier la configuration actuelle des notes
89
+ const notesConfig = await git.raw(['config', '--get', 'notes.rewriteRef']).catch(() => '');
90
+ if (!notesConfig.includes('refs/notes/commits') || forceUpdate) {
91
+ await git.addConfig('notes.rewriteRef', 'refs/notes/commits');
92
+ changes.push('✓ Configured Git Notes rewriteRef');
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.warn('⚠️ Warning: Could not configure Git Notes:', error);
97
+ }
98
+ return {
99
+ configured: true,
100
+ changes
101
+ };
102
+ }
103
+ catch (error) {
104
+ throw new errors_1.GitOperationError(`Failed to ensure repository configuration: ${error}`, 'repo_config', { error });
105
+ }
106
+ }
107
+ /**
108
+ * Configure le remote origin et les paramètres Git Notes
109
+ * @param git Instance SimpleGit
110
+ * @param config Configuration du remote
111
+ */
112
+ async function gitEnsureRemoteConfiguration(git, config) {
113
+ const { remoteUrl = (0, repo_tools_1.gitLoad)().remoteUrl, remoteName = 'origin', quiet } = config;
114
+ if (!remoteUrl) {
115
+ return;
116
+ }
117
+ try {
118
+ // Vérifier si le remote existe
119
+ const remotes = await git.getRemotes(true);
120
+ const existingRemote = remotes.find(r => r.name === remoteName);
121
+ // Ajouter fetch pour les notes (peut déjà exister)
122
+ try {
123
+ if (!existingRemote) {
124
+ console.log(`🔗 Adding remote ${remoteName} with URL ${remoteUrl}`);
125
+ await git.addRemote(remoteName, remoteUrl);
126
+ await git.addConfig(`remote.${remoteName}.fetch`, '+refs/notes/*:refs/notes/*');
127
+ console.log(`✅ Added Git Notes fetch configuration for ${remoteName}`);
128
+ }
129
+ else {
130
+ await git.addConfig(`remote.${remoteName}.push`, 'refs/notes/*:refs/notes/*');
131
+ console.log(`✅ Added Git Notes push configuration`);
132
+ }
133
+ }
134
+ catch (error) {
135
+ // Ignorer si la configuration existe déjà
136
+ console.log(`⚠️ Git Remote or Notes config may already exist`);
137
+ }
138
+ }
139
+ catch (error) {
140
+ if (!quiet)
141
+ throw new errors_1.GitOperationError(`Failed to configure remote: ${error}`, 'remote_config', { error });
142
+ }
143
+ }
144
+ async function gitInit(git, options = {}) {
145
+ try {
146
+ const gitConfig = (0, repo_tools_1.gitLoad)();
147
+ const { excludePatterns = [
148
+ 'node_modules',
149
+ '.rag',
150
+ '.assets',
151
+ '.storage',
152
+ 'dist',
153
+ '*.log',
154
+ '*.exe',
155
+ '*.dll',
156
+ '*.so',
157
+ '*.dylib',
158
+ '*.a',
159
+ '*.o',
160
+ '*.obj',
161
+ '*.lib',
162
+ '.env',
163
+ '.DS_Store',
164
+ ], remoteUrl = gitConfig?.remoteUrl, remoteName = 'origin' } = options;
165
+ const isRepo = await git.checkIsRepo();
166
+ if (isRepo) {
167
+ // Vérifier la configuration du remote même si le repo existe
168
+ await gitEnsureRemoteConfiguration(git, { remoteUrl, remoteName });
169
+ const existingBranches = await git.branchLocal();
170
+ const hasMainBranch = existingBranches.all.includes(gitConfig.mainBranch);
171
+ const hasDraftBranch = existingBranches.all.includes(gitConfig.draftBranch);
172
+ //
173
+ // missing configuration check
174
+ if (hasMainBranch && hasDraftBranch) {
175
+ if (gitConfig.verbose)
176
+ console.log(`Repository already initialized with correct configuration at ${gitConfig.repoPath}`);
177
+ return;
178
+ }
179
+ if (gitConfig.verbose)
180
+ console.log(`🌶️ Repository already initialized with correct configuration at ${gitConfig.repoPath}`);
181
+ if (gitConfig.verbose)
182
+ console.log(`🌶️ Existing branches: ${existingBranches.all}`);
183
+ // Si le dépôt existe mais n'est pas correctement configuré
184
+ throw new errors_1.GitConfigurationError('Repository exists but is not properly configured. Please check branch names and configuration.', 'repo_config');
185
+ }
186
+ console.log(`🌳 Initializing repository at ${gitConfig.repoPath}`);
187
+ // Créer le répertoire temporaire
188
+ await fs.mkdir(gitConfig.repoPath, { recursive: true });
189
+ // Create upload directory (absolute path)
190
+ await fs.mkdir(gitConfig.uploadPath, { recursive: true });
191
+ console.log(`✅ Created upload directory: ${gitConfig.uploadPath}`);
192
+ // Initialiser le repository Git
193
+ await git.init();
194
+ // Configuration Git de base
195
+ await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
196
+ // Configurer le remote origin et Git Notes si spécifié
197
+ await gitEnsureRemoteConfiguration(git, { remoteUrl, remoteName, quiet: true });
198
+ // Configuration des Git Notes (avec ou sans remote)
199
+ try {
200
+ await git.addConfig('notes.rewriteRef', 'refs/notes/commits');
201
+ console.log('✓ Configured Git Notes rewriteRef');
202
+ }
203
+ catch (error) {
204
+ console.warn('⚠️ Warning: Could not configure Git Notes:', error);
205
+ }
206
+ // Créer .gitignore si des patterns sont spécifiés
207
+ if (excludePatterns.length > 0) {
208
+ const gitignoreContent = excludePatterns.join('\n') + '\n';
209
+ await fs.writeFile((0, path_1.join)(gitConfig.repoPath, '.gitignore'), gitignoreContent, 'utf8');
210
+ await git.add('.gitignore');
211
+ await git.commit('chore: Add .gitignore', ['.gitignore']);
212
+ }
213
+ // Créer la branche main directement
214
+ await git.checkout(['-b', gitConfig.mainBranch]);
215
+ // Créer la branche draft à partir de main
216
+ await git.checkoutBranch(gitConfig.draftBranch, gitConfig.mainBranch);
217
+ console.log(`🌳 Repository initialized successfully at ${gitConfig.repoPath}`);
218
+ }
219
+ catch (error) {
220
+ throw new errors_1.GitOperationError(`Failed to init repository: ${error}`, 'repo_create', { error });
221
+ }
222
+ }
223
+ /**
224
+ * Fonction helper pour initialiser ou configurer un repository avec la configuration Git standard
225
+ * Cette fonction combine gitInit et gitEnsureRepositoryConfiguration pour une utilisation simple
226
+ * @param git Instance SimpleGit
227
+ * @param options Options de configuration
228
+ */
229
+ async function gitSetupRepository(git, options = {}) {
230
+ const { remoteUrl, // Pas de valeur par défaut - peut être undefined pour un repository local
231
+ remoteName = 'origin', enableGitNotes = true, excludePatterns = [], forceReconfig = false } = options;
232
+ let initialized = false;
233
+ let configured = false;
234
+ const changes = [];
235
+ try {
236
+ const isRepo = await git.checkIsRepo();
237
+ if (!isRepo) {
238
+ // Initialiser le repository
239
+ console.log('🚀 Initializing new Git repository with standard configuration...');
240
+ await gitInit(git, {
241
+ excludePatterns,
242
+ remoteUrl,
243
+ remoteName,
244
+ enableGitNotes
245
+ });
246
+ initialized = true;
247
+ configured = true;
248
+ changes.push('Repository initialized with full configuration');
249
+ }
250
+ else {
251
+ console.log('📋 Repository exists, verifying configuration...');
252
+ // Configurer le repository existant
253
+ const result = await gitEnsureRepositoryConfiguration(git, {
254
+ remoteUrl,
255
+ remoteName,
256
+ forceUpdate: forceReconfig
257
+ });
258
+ configured = result.configured;
259
+ changes.push(...result.changes);
260
+ }
261
+ // Vérification finale
262
+ const configCheck = await gitCheckConfiguration(git);
263
+ console.log('📊 Configuration check results:', {
264
+ remoteOrigin: configCheck.remoteOrigin,
265
+ remoteOriginUrl: configCheck.remoteOriginUrl,
266
+ gitNotesRemoteFetch: configCheck.gitNotesRemoteFetch,
267
+ gitNotesRemotePush: configCheck.gitNotesRemotePush,
268
+ defaultBranch: configCheck.defaultBranch,
269
+ isValid: configCheck.isValid
270
+ });
271
+ return {
272
+ initialized,
273
+ configured,
274
+ changes
275
+ };
276
+ }
277
+ catch (error) {
278
+ throw new errors_1.GitOperationError(`Failed to setup repository: ${error}`, 'repo_setup', { error });
279
+ }
280
+ }
281
+ /**
282
+ * Fonction helper pour vérifier et afficher la configuration Git d'un repository
283
+ * @param git Instance SimpleGit
284
+ * @returns Configuration détaillée
285
+ */
286
+ async function gitShowConfiguration(git) {
287
+ try {
288
+ const config = await gitCheckConfiguration(git);
289
+ console.log('\n🔍 === Git Repository Configuration Report ===');
290
+ console.log(`Repository Path: ${config.repoPath ? '✅' : '❌'}`);
291
+ console.log(`Draft Branch: ${config.draftBranch ? '✅' : '❌'}`);
292
+ console.log(`Main Branch: ${config.mainBranch ? '✅' : '❌'}`);
293
+ console.log(`Validation Prefix: ${config.validationPrefix ? '✅' : '❌'}`);
294
+ console.log(`Git Instance: ${config.instance ? '✅' : '❌'}`);
295
+ console.log(`Git Notes Config: ${config.gitNotes ? '✅' : '❌'}`);
296
+ if (config.remoteOrigin !== undefined) {
297
+ console.log(`\n🔗 Remote Configuration:`);
298
+ console.log(`Origin Remote: ${config.remoteOrigin ? '✅' : '❌'}`);
299
+ console.log(`Remote URL: ${config.remoteOriginUrl}`);
300
+ console.log(`Git Notes Fetch: ${config.gitNotesRemoteFetch ? '✅' : '❌'}`);
301
+ console.log(`Git Notes Push: ${config.gitNotesRemotePush ? '✅' : '❌'}`);
302
+ console.log(`Default Branch: ${config.defaultBranch ? '✅' : '❌'} (${config.defaultBranchValue})`);
303
+ }
304
+ console.log(`\n🎯 Overall Status: ${config.isValid ? '✅ VALID' : '❌ NEEDS CONFIGURATION'}`);
305
+ console.log('===============================================\n');
306
+ return config;
307
+ }
308
+ catch (error) {
309
+ throw new errors_1.GitOperationError(`Failed to show configuration: ${error}`, 'config_check', { error });
310
+ }
311
+ }
312
+ /**
313
+ * Vérifie la validité de la configuration Git (utilitaire)
314
+ * @param git Instance Git
315
+ * @returns Promise<boolean> True si la configuration est valide
316
+ */
317
+ async function gitCheckConfiguration(git) {
318
+ const gitConfig = (0, repo_tools_1.gitLoad)();
319
+ const check = {
320
+ repoPath: (0, fs_1.existsSync)(gitConfig.repoPath),
321
+ uploadPath: (0, fs_1.existsSync)(gitConfig.uploadPath),
322
+ draftBranch: gitConfig.draftBranch.length > 0,
323
+ mainBranch: gitConfig.mainBranch.length > 0,
324
+ validationPrefix: gitConfig.validationPrefix.length > 0,
325
+ releasePrefix: gitConfig.releasePrefix.length > 0,
326
+ validationToken: gitConfig.validationToken.length > 0,
327
+ instance: gitConfig.instance && !!gitConfig.instance.add,
328
+ concurrency: gitConfig.concurrency &&
329
+ typeof gitConfig.concurrency.maxRetries === 'number' &&
330
+ typeof gitConfig.concurrency.retryDelayMs === 'number' &&
331
+ typeof gitConfig.concurrency.timeoutMs === 'number',
332
+ gitNotes: gitConfig.gitNotes &&
333
+ typeof gitConfig.gitNotes.namespace === 'string' &&
334
+ gitConfig.gitNotes.namespace.length > 0 &&
335
+ typeof gitConfig.gitNotes.enabled === 'boolean',
336
+ };
337
+ // Vérifications avancées si une instance Git est fournie
338
+ if (git) {
339
+ try {
340
+ check.remoteOrigin = false;
341
+ check.defaultBranch = false;
342
+ check.gitNotesRemoteFetch = false;
343
+ check.gitNotesRemotePush = false;
344
+ const isRepo = await git.checkIsRepo();
345
+ if (isRepo) {
346
+ // Vérifier la configuration du remote origin
347
+ const remotes = await git.getRemotes(true);
348
+ const originRemote = remotes.find(r => r.name === 'origin');
349
+ check.remoteOrigin = !!originRemote;
350
+ check.remoteOriginUrl = originRemote?.refs?.fetch || 'not configured';
351
+ // Vérifier la branche par défaut
352
+ try {
353
+ const configs = await git.listConfig();
354
+ const defaultBranch = configs.all['init.defaultbranch'];
355
+ check.defaultBranch = defaultBranch === gitConfig.mainBranch;
356
+ check.defaultBranchValue = defaultBranch || 'not set';
357
+ }
358
+ catch (error) {
359
+ check.defaultBranchValue = 'error reading config';
360
+ }
361
+ }
362
+ else {
363
+ check.remoteOriginUrl = 'repository not initialized';
364
+ check.defaultBranchValue = 'repository not initialized';
365
+ }
366
+ }
367
+ catch (error) {
368
+ check.remoteOriginUrl = `error: ${error}`;
369
+ check.defaultBranchValue = `error: ${error}`;
370
+ }
371
+ }
372
+ // Calculate isValid based on core properties only (exclude remote-related checks)
373
+ const coreProperties = ['repoPath', 'uploadPath', 'draftBranch', 'mainBranch', 'validationPrefix', 'releasePrefix', 'validationToken', 'instance', 'concurrency', 'gitNotes', 'defaultBranch'];
374
+ check.isValid = coreProperties.every((key) => check[key] === true);
375
+ return check;
376
+ }
377
+ /**
378
+ * Crée un fichier dans la branche draft (opération bas niveau)
379
+ * @param git Instance Git
380
+ * @param filePath Chemin du fichier
381
+ * @param PR Nom de la branche de Pull Request
382
+ * @param content Contenu du fichier
383
+ * @param user Utilisateur qui effectue l'opération
384
+ * @param config Configuration Git (optionnel, utilise la config globale)
385
+ * @throws GitOperationError si la création échoue
386
+ */
387
+ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
388
+ //
389
+ // lock checkout is global
390
+ await (0, repo_tools_1.lock)(`checkout`);
391
+ let currentBranch;
392
+ try {
393
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
394
+ //
395
+ // secure main and draft branches (on production)
396
+ if (!gitConf?.canForce && PR == gitConf.mainBranch) {
397
+ throw new errors_1.GitOperationError('Cannot create files in main branch', 'gitCreateOrEditFile', { filePath, PR });
398
+ }
399
+ if (!gitConf?.canForce && PR == gitConf.draftBranch) {
400
+ throw new errors_1.GitOperationError('Cannot create files in draft branch', 'gitCreateOrEditFile', { filePath, PR });
401
+ }
402
+ currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
403
+ if (currentBranch !== PR) {
404
+ await git.checkout(PR);
405
+ }
406
+ const oldHead = (await git.revparse(['HEAD'])).trim();
407
+ //
408
+ // without note the branche is not a valid PR branch
409
+ const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
410
+ const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
411
+ if (!oldNote) {
412
+ return {
413
+ hash: commit.commit,
414
+ date: new Date(),
415
+ message: `commit: ${filePath}`,
416
+ author: {
417
+ name: user.name,
418
+ email: user.email
419
+ },
420
+ branch: PR
421
+ };
422
+ }
423
+ // Determine the current head after the commit attempt.
424
+ const newHead = (await git.revparse(['HEAD'])).trim();
425
+ const newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, PR, oldNote.mergeBase);
426
+ const updatedNote = {
427
+ ...oldNote,
428
+ files: newFiles,
429
+ };
430
+ // Write the note to the current HEAD of the PR branch.
431
+ // If no commit was made, this overwrites the note on oldHead.
432
+ // If a new commit was made, this writes the note on newHead.
433
+ await (0, repo_tools_1.gitWriteNote)(git, PR, updatedNote, gitConf.gitNotes.namespace);
434
+ // If a new commit was created, the old note on oldHead is now obsolete and should be removed.
435
+ if (newHead !== oldHead) {
436
+ await (0, repo_tools_1.gitDeleteNote)(git, oldHead, gitConf.gitNotes.namespace);
437
+ }
438
+ const log = await git.log(['-n', '1', PR, '--', filePath]);
439
+ //
440
+ // SHOULD NEVER HAPPEN
441
+ if (!log.latest) {
442
+ throw new errors_1.GitOperationError(`Failed to get commit history for ${filePath}`, 'gitCreateOrEditFile', { filePath });
443
+ }
444
+ return {
445
+ hash: log.latest.hash,
446
+ date: new Date(log.latest.date),
447
+ message: log.latest.message,
448
+ author: {
449
+ name: log.latest.author_name,
450
+ email: log.latest.author_email
451
+ },
452
+ branch: PR
453
+ };
454
+ }
455
+ catch (error) {
456
+ throw new errors_1.GitOperationError(`Failed to create or edit file "${filePath}": ${error}`, 'gitCreateOrEditFile', { filePath, error });
457
+ }
458
+ finally {
459
+ if (currentBranch && currentBranch !== PR) {
460
+ await git.checkout(currentBranch);
461
+ }
462
+ (0, repo_tools_1.unlock)(`checkout`);
463
+ }
464
+ }
465
+ /**
466
+ * Modifie un fichier dans la branche draft (opération bas niveau)
467
+ * @param git Instance Git
468
+ * @param filePath Chemin du fichier
469
+ * @param PR Nom de la branche de Pull Request
470
+ * @param content Contenu du fichier
471
+ * @param user Utilisateur qui effectue l'opération
472
+ * @param config Configuration Git (optionnel, utilise la config globale)
473
+ * @throws GitOperationError si la modification échoue
474
+ */
475
+ async function gitEditFile(git, filePath, PR, content, user, config) {
476
+ const exists = await (0, repo_tools_1.gitFileExistsInBranch)(git, filePath, PR);
477
+ if (!exists) {
478
+ throw new errors_1.FileNotFoundError(filePath, PR);
479
+ }
480
+ return await gitCreateOrEditFile(git, filePath, PR, content, user, config);
481
+ }
482
+ async function gitRenameFile_git(git, oldFileName, newFileName, branch, user, config) {
483
+ // Si les noms sont identiques, pas besoin de renommer
484
+ if (oldFileName === newFileName) {
485
+ return {
486
+ hash: '',
487
+ date: new Date(),
488
+ message: 'No rename needed - files are identical',
489
+ author: { name: user.name, email: user.email },
490
+ branch: branch
491
+ };
492
+ }
493
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
494
+ const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
495
+ try {
496
+ await (0, repo_tools_1.lock)(`checkout`);
497
+ // Vérifier que l'ancien fichier existe
498
+ const oldFileExists = await (0, repo_tools_1.gitFileExistsInBranch)(git, oldFileName, branch);
499
+ if (!oldFileExists) {
500
+ throw new errors_1.FileNotFoundError(oldFileName, branch);
501
+ }
502
+ // Vérifier que le nouveau fichier n'existe pas déjà
503
+ const newFileExists = await (0, repo_tools_1.gitFileExistsInBranch)(git, newFileName, branch);
504
+ if (newFileExists) {
505
+ throw new Error(`Le fichier de destination "${newFileName}" existe déjà dans la branche "${branch}"`);
506
+ }
507
+ await git.checkout(branch);
508
+ // Rename file with git mv (atomique dans Git)
509
+ await git.raw(['mv', oldFileName, newFileName]);
510
+ // Commit - git mv automatically stages changes
511
+ const commit = await git.commit(`rename: ${oldFileName} → ${newFileName}`, {
512
+ '--author': `${user.name} <${user.email}>`
513
+ });
514
+ return {
515
+ hash: commit.commit,
516
+ date: new Date(),
517
+ message: `rename: ${oldFileName} → ${newFileName}`,
518
+ author: {
519
+ name: user.name,
520
+ email: user.email
521
+ },
522
+ branch: branch
523
+ };
524
+ }
525
+ catch (error) {
526
+ // En cas d'erreur, essayer de restaurer l'état initial
527
+ try {
528
+ await git.reset(['--hard']);
529
+ }
530
+ catch (resetError) {
531
+ console.warn('Could not reset after failed rename:', resetError);
532
+ }
533
+ throw error;
534
+ }
535
+ finally {
536
+ if (currentBranch) {
537
+ await git.checkout(currentBranch);
538
+ }
539
+ (0, repo_tools_1.unlock)(`checkout`);
540
+ }
541
+ }
542
+ async function gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config) {
543
+ // Si les noms sont identiques, pas besoin de renommer
544
+ if (oldFileName === newFileName) {
545
+ return {
546
+ hash: '',
547
+ date: new Date(),
548
+ message: 'No rename needed - files are identical',
549
+ author: { name: user.name, email: user.email },
550
+ branch: branch
551
+ };
552
+ }
553
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
554
+ try {
555
+ await (0, repo_tools_1.lock)(`checkout`);
556
+ const fullNewPath = (0, path_1.join)(gitConf.uploadPath, newFileName);
557
+ const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
558
+ // Vérifier que l'ancien fichier existe
559
+ const oldFileExists = (0, fs_1.existsSync)(fullOldPath);
560
+ if (!oldFileExists) {
561
+ throw new errors_1.FileNotFoundError(oldFileName, branch);
562
+ }
563
+ // Vérifier que le nouveau fichier n'existe pas déjà
564
+ const newFileExists = (0, fs_1.existsSync)(fullNewPath);
565
+ if (newFileExists) {
566
+ throw new Error(`Le fichier de destination "${newFileName}" existe déjà sur le système de fichiers`);
567
+ }
568
+ // Renommage atomique filesystem
569
+ // Utiliser copyFile + unlink pour plus de sécurité que rename() sur certains systèmes
570
+ try {
571
+ await fs.copyFile(fullOldPath, fullNewPath);
572
+ await fs.unlink(fullOldPath);
573
+ }
574
+ catch (copyError) {
575
+ // En cas d'erreur, nettoyer le nouveau fichier s'il a été créé
576
+ if ((0, fs_1.existsSync)(fullNewPath)) {
577
+ try {
578
+ await fs.unlink(fullNewPath);
579
+ }
580
+ catch (cleanupError) {
581
+ console.warn('Could not cleanup destination file after failed rename:', cleanupError);
582
+ }
583
+ }
584
+ throw copyError;
585
+ }
586
+ return {
587
+ hash: '',
588
+ date: new Date(),
589
+ message: `filesystem rename: ${oldFileName} → ${newFileName}`,
590
+ author: {
591
+ name: user.name,
592
+ email: user.email
593
+ },
594
+ branch: branch
595
+ };
596
+ }
597
+ catch (error) {
598
+ throw error;
599
+ }
600
+ finally {
601
+ (0, repo_tools_1.unlock)(`checkout`);
602
+ }
603
+ }
604
+ /**
605
+ * Renomme un fichier de manière atomique (Git ou filesystem selon le contexte)
606
+ * @param git Instance SimpleGit
607
+ * @param oldFileName Ancien nom du fichier
608
+ * @param newFileName Nouveau nom du fichier
609
+ * @param branch Branche où effectuer le renommage
610
+ * @param user Utilisateur effectuant l'opération
611
+ * @param config Configuration Git optionnelle
612
+ * @returns Historique du commit de renommage
613
+ */
614
+ async function gitRenameFile(git, oldFileName, newFileName, branch, user, config) {
615
+ // Si les noms sont identiques, pas besoin de renommer
616
+ if (oldFileName === newFileName) {
617
+ return {
618
+ hash: '',
619
+ date: new Date(),
620
+ message: 'No rename needed - files are identical',
621
+ author: { name: user.name, email: user.email },
622
+ branch: branch
623
+ };
624
+ }
625
+ // load config
626
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
627
+ // Pour les fichiers dans Git, utiliser Git uniquement (pas de fallback filesystem)
628
+ try {
629
+ //
630
+ //FIXME: add a check about the branch (file can be NEW and on branch)
631
+ const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
632
+ if ((0, fs_1.existsSync)(fullOldPath)) {
633
+ return await gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config);
634
+ }
635
+ else {
636
+ return await gitRenameFile_git(git, oldFileName, newFileName, branch, user, config);
637
+ }
638
+ }
639
+ catch (error) {
640
+ // Pour les vraies branches Git, ne pas faire de fallback filesystem
641
+ // Seules les branches NEW utilisent le filesystem
642
+ throw error;
643
+ }
644
+ }
645
+ /**
646
+ * Supprime un fichier d'une branche Git (opération bas niveau)
647
+ * @param git Instance Git
648
+ * @param filePath Chemin du fichier à supprimer
649
+ * @param branch Nom de la branche où supprimer le fichier
650
+ * @param user Utilisateur qui effectue l'opération
651
+ * @param config Configuration Git (optionnel, utilise la config globale)
652
+ * @throws GitOperationError si la suppression échoue
653
+ */
654
+ async function gitDeleteFile(git, filePath, branch, user, config) {
655
+ await (0, repo_tools_1.lock)(`checkout`);
656
+ let currentBranch;
657
+ try {
658
+ const gitConf = (0, repo_tools_1.gitLoad)(config);
659
+ // Sécurité : interdire la suppression dans les branches de production
660
+ if (!gitConf?.canForce && branch === gitConf.mainBranch) {
661
+ throw new errors_1.GitOperationError('Cannot delete files in main branch', 'gitDeleteFile', { filePath, branch });
662
+ }
663
+ if (!gitConf?.canForce && branch === gitConf.draftBranch) {
664
+ throw new errors_1.GitOperationError('Cannot delete files in draft branch', 'gitDeleteFile', { filePath, branch });
665
+ }
666
+ // Vérifier que le fichier existe avant de le supprimer
667
+ const exists = await (0, repo_tools_1.gitFileExistsInBranch)(git, filePath, branch);
668
+ if (!exists) {
669
+ throw new errors_1.FileNotFoundError(filePath, branch);
670
+ }
671
+ currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
672
+ if (currentBranch !== branch) {
673
+ await git.checkout(branch);
674
+ }
675
+ const oldHead = (await git.revparse(['HEAD'])).trim();
676
+ // Lire l'ancienne note pour la mettre à jour
677
+ const oldNote = await (0, repo_tools_1.gitReadNote)(git, branch, gitConf.gitNotes.namespace, 10);
678
+ // Supprimer le fichier avec git rm
679
+ await git.raw(['rm', filePath]);
680
+ // Commit la suppression
681
+ const commit = await git.commit(`delete: ${filePath}`, {
682
+ '--author': `${user.name} <${user.email}>`
683
+ });
684
+ // Mettre à jour la note si elle existe (pour les branches PR)
685
+ if (oldNote) {
686
+ const newHead = (await git.revparse(['HEAD'])).trim();
687
+ const newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, branch, oldNote.mergeBase);
688
+ const updatedNote = {
689
+ ...oldNote,
690
+ files: newFiles,
691
+ };
692
+ // Écrire la note sur le nouveau HEAD
693
+ await (0, repo_tools_1.gitWriteNote)(git, branch, updatedNote, gitConf.gitNotes.namespace);
694
+ // Supprimer l'ancienne note si un nouveau commit a été créé
695
+ if (newHead !== oldHead) {
696
+ await (0, repo_tools_1.gitDeleteNote)(git, oldHead, gitConf.gitNotes.namespace);
697
+ }
698
+ }
699
+ return {
700
+ hash: commit.commit,
701
+ date: new Date(),
702
+ message: `delete: ${filePath}`,
703
+ author: {
704
+ name: user.name,
705
+ email: user.email
706
+ },
707
+ branch: branch
708
+ };
709
+ }
710
+ catch (error) {
711
+ throw new errors_1.GitOperationError(`Failed to delete file "${filePath}" from branch "${branch}": ${error}`, 'gitDeleteFile', { filePath, branch, error });
712
+ }
713
+ finally {
714
+ if (currentBranch && currentBranch !== branch) {
715
+ await git.checkout(currentBranch);
716
+ }
717
+ (0, repo_tools_1.unlock)(`checkout`);
718
+ }
719
+ }
720
+ /**
721
+ * PRIVATE TOOLS
722
+ */
723
+ const _writeFileAndCommit = async (git, filePath, content, user, config, commitMsg) => {
724
+ const gitConf = config;
725
+ if (!gitConf || !gitConf.repoPath) {
726
+ throw new errors_1.GitOperationError('_writeFileAndCommit requires a config with a valid repoPath.', 'internal_error');
727
+ }
728
+ // Créer les répertoires parents si nécessaire
729
+ const fullPath = (0, path_1.join)(gitConf.repoPath, filePath);
730
+ const dir = (0, path_1.dirname)(fullPath);
731
+ await fs.mkdir(dir, { recursive: true });
732
+ // Écrire le fichier
733
+ await fs.writeFile(fullPath, content, { encoding: 'utf8', flag: 'w' });
734
+ await git.add(filePath);
735
+ const commit = await git.commit(commitMsg || `commit`, [filePath], {
736
+ '--author': `${user.name} <${user.email}>`
737
+ });
738
+ return commit;
739
+ };
740
+ /**
741
+ * Diagnostique l'état de santé d'une branche Git et détecte les problèmes bloquants.
742
+ *
743
+ * **Problèmes bloquants détectés :**
744
+ * - ❌ **Branche inexistante** : La branche spécifiée n'existe pas dans le dépôt
745
+ * - ❌ **Branche inaccessible** : Impossible de faire un checkout vers la branche
746
+ * - ❌ **Merge en cours** : Opération de merge interrompue (fichiers .git/MERGE_HEAD présents)
747
+ * - ❌ **Fichiers en conflit** : Conflits de merge non résolus (status.conflicted)
748
+ * - ❌ **Index Git corrompu** : Index illisible ou corrompu (git status échoue)
749
+ * - ⚠️ **Fichiers modifiés** : Changements non commités dans le working directory
750
+ * - ⚠️ **Fichiers stagés** : Changements en attente de commit dans l'index
751
+ *
752
+ * **Méthode de diagnostic :**
753
+ * 1. Vérification de l'existence de la branche (git branch --list)
754
+ * 2. Test d'accessibilité (git checkout)
755
+ * 3. Analyse de l'état (git status)
756
+ * 4. Détection des merges en cours (fichiers .git/MERGE_*)
757
+ *
758
+ * @param git Instance SimpleGit configurée
759
+ * @param branch Nom de la branche à diagnostiquer (optionnel, utilise la branche courante si non spécifié)
760
+ * @returns Promise<GitHealthStatus> Diagnostic complet avec liste des problèmes et recommandations de réparation
761
+ */
762
+ async function gitGetBranchHealth(git, branch) {
763
+ const gitConfig = (0, repo_tools_1.gitLoad)();
764
+ let currentBranch = branch;
765
+ // Si pas de branche spécifiée, utiliser la branche courante
766
+ if (!currentBranch) {
767
+ try {
768
+ currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
769
+ }
770
+ catch (error) {
771
+ return createUnhealthyStatus('unknown', false, false, false, false, 0, 0, 0, 0, false, ['Impossible de déterminer la branche courante'], ['Vérifier l\'état du dépôt Git']);
772
+ }
773
+ }
774
+ const health = {
775
+ branch: currentBranch,
776
+ exists: false,
777
+ accessible: false,
778
+ clean: false,
779
+ mergeInProgress: false,
780
+ conflictedFiles: 0,
781
+ modifiedFiles: 0,
782
+ untrackedFiles: 0,
783
+ stagedFiles: 0,
784
+ indexReadable: false,
785
+ healthy: false,
786
+ issues: [],
787
+ recommendations: []
788
+ };
789
+ try {
790
+ // 1. Vérifier si la branche existe
791
+ const branches = await git.branchLocal();
792
+ health.exists = branches.all.includes(currentBranch);
793
+ if (!health.exists) {
794
+ health.issues.push(`La branche '${currentBranch}' n'existe pas`);
795
+ health.recommendations.push(`Créer la branche ou vérifier le nom`);
796
+ return health;
797
+ }
798
+ // 2. Tenter un checkout pour vérifier l'accessibilité
799
+ const originalBranch = await git.revparse(['--abbrev-ref', 'HEAD']).catch(() => null);
800
+ try {
801
+ if (originalBranch !== currentBranch) {
802
+ await git.checkout(currentBranch);
803
+ }
804
+ health.accessible = true;
805
+ // 3. Vérifier l'état avec git status
806
+ try {
807
+ const status = await git.status();
808
+ health.indexReadable = true;
809
+ health.clean = status.isClean();
810
+ health.conflictedFiles = status.conflicted.length;
811
+ health.modifiedFiles = status.modified.length;
812
+ health.untrackedFiles = status.not_added.length;
813
+ health.stagedFiles = status.staged.length;
814
+ // 4. Vérifier si un merge est en cours
815
+ health.mergeInProgress = await checkMergeInProgress(gitConfig.repoPath);
816
+ }
817
+ catch (statusError) {
818
+ health.indexReadable = false;
819
+ health.issues.push('Index Git illisible ou corrompu');
820
+ health.recommendations.push('Exécuter git reset --hard HEAD');
821
+ }
822
+ // Restaurer la branche originale si nécessaire
823
+ if (originalBranch && originalBranch !== currentBranch) {
824
+ await git.checkout(originalBranch).catch(() => {
825
+ // Si on ne peut pas revenir, ce n'est pas critique pour le diagnostic
826
+ });
827
+ }
828
+ }
829
+ catch (checkoutError) {
830
+ health.accessible = false;
831
+ health.issues.push(`Impossible d'accéder à la branche '${currentBranch}'`);
832
+ health.recommendations.push('Exécuter git checkout -f ' + currentBranch);
833
+ }
834
+ }
835
+ catch (error) {
836
+ health.issues.push(`Erreur lors du diagnostic : ${error.message}`);
837
+ health.recommendations.push('Vérifier l\'état du dépôt Git');
838
+ }
839
+ // 5. Analyser les problèmes et recommandations
840
+ if (health.mergeInProgress) {
841
+ health.issues.push('Merge en cours');
842
+ health.recommendations.push('Exécuter git merge --abort');
843
+ }
844
+ if (health.conflictedFiles > 0) {
845
+ health.issues.push(`${health.conflictedFiles} fichier(s) en conflit`);
846
+ health.recommendations.push('Résoudre les conflits ou exécuter git reset --hard HEAD');
847
+ }
848
+ if (health.modifiedFiles > 0 || health.stagedFiles > 0) {
849
+ health.issues.push(`${health.modifiedFiles} fichier(s) modifié(s), ${health.stagedFiles} fichier(s) stagé(s)`);
850
+ health.recommendations.push('Committer les changements ou exécuter git reset --hard HEAD');
851
+ }
852
+ if (!health.indexReadable) {
853
+ health.issues.push('Index Git corrompu');
854
+ health.recommendations.push('Exécuter git reset --hard HEAD');
855
+ }
856
+ // 6. Déterminer l'état de santé global
857
+ health.healthy = health.exists &&
858
+ health.accessible &&
859
+ health.indexReadable &&
860
+ !health.mergeInProgress &&
861
+ health.conflictedFiles === 0 &&
862
+ (health.clean || (health.modifiedFiles === 0 && health.stagedFiles === 0));
863
+ return health;
864
+ }
865
+ /**
866
+ * Fonction helper pour créer un statut unhealthy
867
+ */
868
+ function createUnhealthyStatus(branch, exists, accessible, clean, mergeInProgress, conflictedFiles, modifiedFiles, untrackedFiles, stagedFiles, indexReadable, issues, recommendations) {
869
+ return {
870
+ branch,
871
+ exists,
872
+ accessible,
873
+ clean,
874
+ mergeInProgress,
875
+ conflictedFiles,
876
+ modifiedFiles,
877
+ untrackedFiles,
878
+ stagedFiles,
879
+ indexReadable,
880
+ healthy: false,
881
+ issues,
882
+ recommendations
883
+ };
884
+ }
885
+ /**
886
+ * Vérifie si un merge est en cours en regardant les fichiers Git
887
+ */
888
+ async function checkMergeInProgress(repoPath) {
889
+ const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
890
+ const { join } = await Promise.resolve().then(() => __importStar(require('path')));
891
+ const gitDir = join(repoPath, '.git');
892
+ const mergeFiles = [
893
+ 'MERGE_HEAD',
894
+ 'MERGE_MODE',
895
+ 'MERGE_MSG',
896
+ 'CHERRY_PICK_HEAD',
897
+ 'REVERT_HEAD'
898
+ ];
899
+ return mergeFiles.some(file => existsSync(join(gitDir, file)));
900
+ }