agentic-api 1.0.6 → 2.0.26

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 +118 -22
  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,104 @@
1
+ import { SimpleGit } from 'simple-git';
2
+ import { RulesGitConfig, GitCommitHistory, RuleUser, PRInfo, PRMergeResult, RulePullRequestDetails } from '../types';
3
+ import { IE2ETestOperations, E2ETestConfig, E2ETestResult, IRulesE2EAssertions } from './git.helper';
4
+ import { IConcurrencyManager, IGitNotesOperations } from '../types.helpers';
5
+ /**
6
+ * Helper E2E pour les tests d'intégration avec un vrai repository Git
7
+ * Implémente toutes les interfaces pour permettre des tests complets
8
+ */
9
+ export declare class GitE2EHelper implements IE2ETestOperations, IRulesE2EAssertions {
10
+ readonly git: SimpleGit;
11
+ readonly config: RulesGitConfig;
12
+ readonly concurrency: IConcurrencyManager;
13
+ readonly notes: IGitNotesOperations;
14
+ private tempRepoPath;
15
+ private isInitialized;
16
+ constructor(config: E2ETestConfig);
17
+ initTemporaryRepo(): Promise<void>;
18
+ cleanup(): Promise<void>;
19
+ getFileContent(filePath: string, branch?: string): Promise<GitCommitHistory | null>;
20
+ listFilesInBranch(branch: string, pattern?: string): Promise<string[]>;
21
+ fileExistsInBranch(filePath: string, branch: string): Promise<boolean>;
22
+ isFileMerged(filePath: string, from: string, target?: string): Promise<boolean>;
23
+ getAllBranches(options?: {
24
+ unmergedOnly?: boolean;
25
+ }): Promise<string[]>;
26
+ validateConfiguration(): Promise<any>;
27
+ isPRClosed(branch: string): Promise<boolean>;
28
+ isPRClosedRobust(branch: string, config?: RulesGitConfig): Promise<boolean>;
29
+ closePR(branch: string, author: RuleUser, message?: string): Promise<PRMergeResult>;
30
+ closePRRobust(branch: string, closedBy: RuleUser, message: string, config?: RulesGitConfig): Promise<PRMergeResult>;
31
+ getAllPR(options?: {
32
+ closed?: boolean;
33
+ validationPrefix?: string;
34
+ }): Promise<PRInfo[]>;
35
+ getNextPRNumber(validationPrefix: string): Promise<number>;
36
+ newPR(files: string[], description: string, author: RuleUser, options?: {
37
+ content?: string[];
38
+ editorBranch?: string;
39
+ validationPrefix?: string;
40
+ rulesGitConfig?: RulesGitConfig;
41
+ }): Promise<PRInfo>;
42
+ getPRInfo(branch: string, config?: RulesGitConfig): Promise<PRInfo>;
43
+ updatePRComments(branch: string, details: RulePullRequestDetails): Promise<PRInfo>;
44
+ syncPR(branch: string, user: RuleUser): Promise<void>;
45
+ /**
46
+ * Crée un fichier dans le repository
47
+ * @param filePath Chemin du fichier
48
+ * @param content Contenu du fichier
49
+ * @param branch Branche optionnelle (par défaut: draft)
50
+ */
51
+ createOrEditFile(filePath: string, content: string, branch?: string, config?: RulesGitConfig): Promise<GitCommitHistory>;
52
+ /**
53
+ * Modifie un fichier existant
54
+ * @param filePath Chemin du fichier
55
+ * @param content Contenu du fichier
56
+ * @param branch Branche optionnelle (par défaut: draft)
57
+ */
58
+ editFile(filePath: string, content: string, branch?: string): Promise<GitCommitHistory>;
59
+ validateRepositoryState(): Promise<{
60
+ isValid: boolean;
61
+ branches: string[];
62
+ files: Record<string, string[]>;
63
+ errors: string[];
64
+ }>;
65
+ runCompleteWorkflow(files: {
66
+ path: string;
67
+ content: string;
68
+ }[], description: string): Promise<{
69
+ prBranch: string;
70
+ commitHash: string;
71
+ mergeCommitHash?: string;
72
+ }>;
73
+ assertFileContent(filePath: string, expectedContent: string, branch?: string): Promise<void>;
74
+ assertPRState(branch: string, expectedState: 'open' | 'closed'): Promise<void>;
75
+ assertFilemerged(filePath: string, fromBranch: string, toBranch?: string): Promise<void>;
76
+ assertBranchStructure(expectedBranches: string[]): Promise<void>;
77
+ assertGitHistory(expectedCommits: {
78
+ message: string;
79
+ author?: string;
80
+ }[]): Promise<void>;
81
+ /**
82
+ * Retourne le chemin du repository temporaire
83
+ */
84
+ getRepositoryPath(): string;
85
+ }
86
+ /**
87
+ * Factory pour créer des helpers E2E facilement
88
+ */
89
+ export declare class GitE2EFactory {
90
+ static getDefaultHelper(): GitE2EHelper;
91
+ /**
92
+ * Crée un helper E2E avec un repository temporaire
93
+ */
94
+ static createE2EHelper(options?: {
95
+ autoCleanup?: boolean;
96
+ customConfig?: Partial<RulesGitConfig>;
97
+ }): Promise<GitE2EHelper>;
98
+ /**
99
+ * Crée un helper E2E et exécute un test complet
100
+ */
101
+ static runE2ETest(testName: string, testFunction: (helper: GitE2EHelper) => Promise<void>, options?: {
102
+ autoCleanup?: boolean;
103
+ }): Promise<E2ETestResult>;
104
+ }
@@ -0,0 +1,488 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GitE2EFactory = exports.GitE2EHelper = void 0;
7
+ const simple_git_1 = __importDefault(require("simple-git"));
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ const os_1 = require("os");
11
+ const index_1 = require("./index");
12
+ /**
13
+ * Helper E2E pour les tests d'intégration avec un vrai repository Git
14
+ * Implémente toutes les interfaces pour permettre des tests complets
15
+ */
16
+ class GitE2EHelper {
17
+ constructor(config) {
18
+ this.isInitialized = false;
19
+ this.tempRepoPath = config.tempDir;
20
+ this.config = config.gitConfig;
21
+ this.config.instance = this.git = (0, simple_git_1.default)({ baseDir: this.tempRepoPath, binary: 'git' });
22
+ delete this.config.reset;
23
+ // Concurrency manager réel (pas de mock)
24
+ this.concurrency = {
25
+ async withRetry(operation, options, operationName = 'unknown') {
26
+ let attempts = 0;
27
+ let lastError;
28
+ while (attempts < options.maxRetries) {
29
+ attempts++;
30
+ try {
31
+ const result = await Promise.race([
32
+ operation(),
33
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timeout')), options.timeoutMs))
34
+ ]);
35
+ return { success: true, result, attempts };
36
+ }
37
+ catch (error) {
38
+ lastError = error instanceof Error ? error : new Error(String(error));
39
+ if (attempts < options.maxRetries) {
40
+ const delay = options.retryDelayMs * Math.pow(2, attempts - 1) + Math.random() * 50;
41
+ await new Promise(resolve => setTimeout(resolve, delay));
42
+ }
43
+ }
44
+ }
45
+ return {
46
+ success: false,
47
+ attempts,
48
+ error: lastError
49
+ };
50
+ }
51
+ };
52
+ // Capture reference to git instance for closures
53
+ const gitInstance = this.git;
54
+ // Git Notes operations réelles
55
+ this.notes = {
56
+ async readNote(branch, namespace) {
57
+ return (0, index_1.gitReadNote)(gitInstance, branch, namespace);
58
+ },
59
+ async writeNote(branch, metadata, namespace) {
60
+ return (0, index_1.gitWriteNote)(gitInstance, branch, metadata, namespace);
61
+ },
62
+ async deleteNote(branch, author, namespace) {
63
+ console.log(`[DEBUG][E2EHelper.notes.deleteNote] Deleting note for ${branch} by ${author.name}`);
64
+ try {
65
+ await gitInstance.raw(['notes', '--ref', namespace, 'remove', branch]);
66
+ }
67
+ catch {
68
+ // Ignorer si la note n'existe pas
69
+ }
70
+ },
71
+ };
72
+ }
73
+ async initTemporaryRepo() {
74
+ if (this.isInitialized) {
75
+ return;
76
+ }
77
+ try {
78
+ // Créer le répertoire temporaire
79
+ await fs_1.promises.mkdir(this.tempRepoPath, { recursive: true });
80
+ // Create upload directory
81
+ const uploadDir = this.config.uploadPath;
82
+ await fs_1.promises.mkdir(uploadDir, { recursive: true });
83
+ // Initialiser le repository Git
84
+ await this.git.init();
85
+ // Configuration Git de base
86
+ await this.git.addConfig('user.name', 'E2E Test');
87
+ await this.git.addConfig('user.email', 'e2e@test.com');
88
+ await this.git.addConfig('init.defaultBranch', this.config.mainBranch);
89
+ // Créer la branche main directement
90
+ await this.git.checkout(['-b', this.config.mainBranch]);
91
+ // Créer un commit initial
92
+ const readmePath = (0, path_1.join)(this.tempRepoPath, 'README.md');
93
+ await fs_1.promises.writeFile(readmePath, '# E2E Test Repository\n\nTemporary repository for E2E testing.');
94
+ await this.git.add('README.md');
95
+ await this.git.commit('Initial commit');
96
+ // Créer la branche draft à partir de main
97
+ await this.git.checkoutBranch(this.config.draftBranch, this.config.mainBranch);
98
+ // force reload of git config
99
+ this.config.reset = true;
100
+ await (0, index_1.gitLoad)(this.config);
101
+ // console.log(`🌶️ Helper.initTemporaryRepo: gitConfig: `,this.config.repoPath, 'reset:',this.config.reset);
102
+ delete this.config.reset;
103
+ this.isInitialized = true;
104
+ }
105
+ catch (error) {
106
+ throw new Error(`Échec de l'initialisation du repository E2E: ${error}`);
107
+ }
108
+ }
109
+ async cleanup() {
110
+ if (this.tempRepoPath) {
111
+ try {
112
+ await fs_1.promises.rm(this.tempRepoPath, { recursive: true, force: true });
113
+ }
114
+ catch (error) {
115
+ console.warn(`Erreur lors du nettoyage du repository E2E: ${error}`);
116
+ }
117
+ }
118
+ }
119
+ // ===== DÉLÉGATION VERS LES FONCTIONS REPO.TS =====
120
+ async getFileContent(filePath, branch) {
121
+ return (0, index_1.gitGetFileContent)(this.git, filePath, branch);
122
+ }
123
+ async listFilesInBranch(branch, pattern) {
124
+ // return gitGetDiffFiles(this.git, branch, this.config.draftBranch, pattern);
125
+ return (0, index_1.gitListFilesInBranch)(this.git, branch, pattern || '.md');
126
+ }
127
+ async fileExistsInBranch(filePath, branch) {
128
+ return (0, index_1.gitFileExistsInBranch)(this.git, filePath, branch);
129
+ }
130
+ async isFileMerged(filePath, from, target) {
131
+ return (0, index_1.gitIsFileMerged)(this.git, filePath, from, target);
132
+ }
133
+ async getAllBranches(options) {
134
+ return (0, index_1.gitGetAllBranches)(this.git, options);
135
+ }
136
+ async validateConfiguration() {
137
+ return (0, index_1.gitCheckConfiguration)(this.git);
138
+ }
139
+ async isPRClosed(branch) {
140
+ return (0, index_1.gitIsPRClosed)(this.git, branch);
141
+ }
142
+ async isPRClosedRobust(branch, config) {
143
+ return (0, index_1.gitIsPRClosedRobust)(this.git, branch, config || this.config);
144
+ }
145
+ async closePR(branch, author, message) {
146
+ console.log(`[DEBUG][E2EHelper.closePR] Closing PR: ${branch} by ${author.name}`);
147
+ return (0, index_1.gitClosePR)(this.git, branch, author, message);
148
+ }
149
+ async closePRRobust(branch, closedBy, message, config) {
150
+ return (0, index_1.gitClosePRRobust)(this.git, branch, closedBy, message, config || this.config);
151
+ }
152
+ async getAllPR(options) {
153
+ // Underlying gitGetAllPR uses global gitLoad(), so passing specific config here has no effect yet.
154
+ // Keeping rulesGitConfig in signature for potential future refactor of gitGetAllPR.
155
+ return (0, index_1.gitGetAllPR)(this.git, options);
156
+ }
157
+ async getNextPRNumber(validationPrefix) {
158
+ return (0, index_1.gitGetNextPRNumber)(this.git, validationPrefix);
159
+ }
160
+ async newPR(files, description, author, options) {
161
+ //console.log(`[DEBUG][E2EHelper.newPR] Creating new PR by ${author.name} for files: ${files.join(', ')}`);
162
+ const passOptions = { ...options, rulesGitConfig: options?.rulesGitConfig || this.config };
163
+ return (0, index_1.gitNewValidationRequest)(this.git, files, description, author, passOptions);
164
+ }
165
+ async getPRInfo(branch, config) {
166
+ const effectiveConfig = config || this.config;
167
+ // First, determine if the PR is closed using the robust method and effectiveConfig
168
+ // Then, load the PR info, passing the determined closed status
169
+ const prInfo = await (0, index_1.gitLoadPR)(this.git, branch);
170
+ if (!prInfo) {
171
+ throw new Error(`PR info not found for branch ${branch}`); // Should not happen if branch exists
172
+ }
173
+ prInfo.open = prInfo.metadata.status == 'open';
174
+ return prInfo;
175
+ }
176
+ async updatePRComments(branch, details) {
177
+ return (0, index_1.gitPRUpdateComments)(this.git, branch, details, this.config);
178
+ }
179
+ // FIXME: Cette fonction a un problème de gestion des métadonnées manquantes. Voir gitSyncPR pour les détails.
180
+ async syncPR(branch, user) {
181
+ return (0, index_1.gitSyncPR)(this.git, branch, user);
182
+ }
183
+ // ===== MÉTHODES E2E SPÉCIFIQUES =====
184
+ /**
185
+ * Crée un fichier dans le repository
186
+ * @param filePath Chemin du fichier
187
+ * @param content Contenu du fichier
188
+ * @param branch Branche optionnelle (par défaut: draft)
189
+ */
190
+ async createOrEditFile(filePath, content, branch, config) {
191
+ const user = { name: 'E2E Test', email: 'e2e@test.com' };
192
+ const targetBranch = branch || config?.draftBranch || this.config.draftBranch;
193
+ return (0, index_1.gitCreateOrEditFile)(this.git, filePath, targetBranch, content, user, config || this.config);
194
+ }
195
+ /**
196
+ * Modifie un fichier existant
197
+ * @param filePath Chemin du fichier
198
+ * @param content Contenu du fichier
199
+ * @param branch Branche optionnelle (par défaut: draft)
200
+ */
201
+ async editFile(filePath, content, branch) {
202
+ const user = { name: 'E2E Test', email: 'e2e@test.com' };
203
+ const targetBranch = branch || this.config.draftBranch;
204
+ return (0, index_1.gitEditFile)(this.git, filePath, targetBranch, content, user, this.config);
205
+ }
206
+ async validateRepositoryState() {
207
+ const errors = [];
208
+ let isValid = true;
209
+ try {
210
+ // Vérifier que c'est un repository Git valide
211
+ const isRepo = await this.git.checkIsRepo();
212
+ if (!isRepo) {
213
+ errors.push('Le répertoire n\'est pas un repository Git valide');
214
+ isValid = false;
215
+ }
216
+ // Lister les branches
217
+ const branches = await this.getAllBranches();
218
+ // Vérifier les branches obligatoires
219
+ if (!branches.includes(this.config.mainBranch)) {
220
+ errors.push(`Branche principale manquante: ${this.config.mainBranch}`);
221
+ isValid = false;
222
+ }
223
+ if (!branches.includes(this.config.draftBranch)) {
224
+ errors.push(`Branche draft manquante: ${this.config.draftBranch}`);
225
+ isValid = false;
226
+ }
227
+ // Lister les fichiers par branche
228
+ const files = {};
229
+ for (const branch of branches) {
230
+ try {
231
+ files[branch] = await this.listFilesInBranch(branch);
232
+ }
233
+ catch (error) {
234
+ errors.push(`Erreur lors de la lecture de la branche ${branch}: ${error}`);
235
+ isValid = false;
236
+ }
237
+ }
238
+ return { isValid, branches, files, errors };
239
+ }
240
+ catch (error) {
241
+ errors.push(`Erreur lors de la validation: ${error}`);
242
+ return { isValid: false, branches: [], files: {}, errors };
243
+ }
244
+ }
245
+ async runCompleteWorkflow(files, description) {
246
+ const user = { name: 'E2E Workflow User', email: 'e2e.workflow@example.com' };
247
+ const filePaths = files.map(f => f.path);
248
+ const contents = files.map(f => f.content);
249
+ // 1. Create a new PR (contribution branch) with the initial content
250
+ const pr = await this.newPR(filePaths, description, user, {
251
+ content: contents,
252
+ });
253
+ const prBranch = pr.branch;
254
+ // Capture the initial commit hash on the PR branch
255
+ // newPR makes a commit, so we get its hash.
256
+ // If newPR makes multiple commits for multiple files, this gets the latest one.
257
+ let commitHash = '';
258
+ try {
259
+ await this.git.checkout(prBranch); // Ensure we are on the prBranch
260
+ const log = await this.git.log({ maxCount: 1 });
261
+ commitHash = log.latest?.hash || 'unknown-initial-commit-hash';
262
+ await this.git.checkout(this.config.draftBranch); // Go back to draftBranch or main for safety before next ops
263
+ }
264
+ catch (e) {
265
+ console.warn(`[E2EHelper.runCompleteWorkflow] Could not get initial commit hash for ${prBranch}: ${e}`);
266
+ commitHash = 'error-getting-initial-commit-hash';
267
+ }
268
+ // Verify files are on the PR branch
269
+ for (const file of files) {
270
+ await this.assertFileContent(file.path, file.content, prBranch);
271
+ }
272
+ // Verify files are NOT YET on rule-editor
273
+ for (const file of files) {
274
+ const existsOnRuleEditor = await this.fileExistsInBranch(file.path, this.config.draftBranch);
275
+ expect(existsOnRuleEditor).toBe(false);
276
+ }
277
+ // 2. Close the PR (merge the contribution branch into rule-editor)
278
+ /* const closureHash = */ await this.closePRRobust(// We don't need to store closureHash anymore
279
+ prBranch, user, `Validation E2E terminée for: ${description}`, this.config);
280
+ // 3. Verify files are now on rule-editor with correct content
281
+ await this.git.checkout(this.config.draftBranch);
282
+ for (const file of files) {
283
+ await this.assertFileContent(file.path, file.content, this.config.draftBranch);
284
+ }
285
+ // 4. Verify the contribution branch was merged into rule-editor (content check serves as primary)
286
+ // Get the hash of the merge commit on rule-editor
287
+ let mergeCommitHash;
288
+ try {
289
+ // Ensure we are on draftBranch to get its latest log
290
+ await this.git.checkout(this.config.draftBranch);
291
+ const log = await this.git.log({ maxCount: 1 }); // Get the latest commit on rule-editor
292
+ mergeCommitHash = log.latest?.hash;
293
+ }
294
+ catch (e) {
295
+ console.warn(`[E2EHelper.runCompleteWorkflow] Could not get merge commit hash on ${this.config.draftBranch}: ${e}`);
296
+ }
297
+ return {
298
+ prBranch,
299
+ commitHash, // Added initial commit hash
300
+ mergeCommitHash // Renamed from ruleEditorCommitHash
301
+ };
302
+ }
303
+ // ===== ASSERTIONS E2E =====
304
+ async assertFileContent(filePath, expectedContent, branch) {
305
+ const commit = await this.getFileContent(filePath, branch);
306
+ if (commit?.content !== expectedContent) {
307
+ throw new Error(`Contenu du fichier incorrect.\n` +
308
+ `Attendu: ${expectedContent}\n` +
309
+ `Reçu: ${commit?.content}`);
310
+ }
311
+ }
312
+ async assertPRState(branch, expectedState) {
313
+ const isClosed = await this.isPRClosedRobust(branch, this.config);
314
+ const actualState = isClosed ? 'closed' : 'open';
315
+ if (actualState !== expectedState) {
316
+ throw new Error(`État du PR incorrect. Attendu: ${expectedState}, Reçu: ${actualState}`);
317
+ }
318
+ }
319
+ async assertFilemerged(filePath, fromBranch, toBranch) {
320
+ const target = toBranch || this.config.mainBranch;
321
+ const isMerged = await this.isFileMerged(filePath, fromBranch, target);
322
+ if (!isMerged) {
323
+ throw new Error(`Le fichier ${filePath} n'a pas été mergé de ${fromBranch} vers ${target}`);
324
+ }
325
+ }
326
+ async assertBranchStructure(expectedBranches) {
327
+ const actualBranches = await this.getAllBranches();
328
+ for (const expectedBranch of expectedBranches) {
329
+ if (!actualBranches.includes(expectedBranch)) {
330
+ throw new Error(`Branche manquante: ${expectedBranch}`);
331
+ }
332
+ }
333
+ }
334
+ async assertGitHistory(expectedCommits) {
335
+ const log = await this.git.log({ maxCount: expectedCommits.length });
336
+ const actualCommits = log.all.slice(0, expectedCommits.length);
337
+ for (let i = 0; i < expectedCommits.length; i++) {
338
+ const expected = expectedCommits[i];
339
+ const actual = actualCommits[i];
340
+ if (expected.author && !actual.author_name?.includes(expected.author)) {
341
+ throw new Error(`Auteur de commit incorrect à l'index ${i}.\n` +
342
+ `Attendu: ${expected.author}\n` +
343
+ `Reçu: ${actual.author_name}`);
344
+ }
345
+ }
346
+ }
347
+ /**
348
+ * Retourne le chemin du repository temporaire
349
+ */
350
+ getRepositoryPath() {
351
+ return this.tempRepoPath;
352
+ }
353
+ }
354
+ exports.GitE2EHelper = GitE2EHelper;
355
+ const defaultConfig = (tempDir) => {
356
+ if (!tempDir) {
357
+ throw new Error('Default should be initialized from tests/setup.after.env.ts');
358
+ }
359
+ const config = {
360
+ instance: (0, simple_git_1.default)({ baseDir: tempDir, binary: 'git' }),
361
+ repoPath: tempDir,
362
+ uploadPath: (0, path_1.join)(tempDir, '.assets'), // Add uploadPath to default config
363
+ draftBranch: 'rule-editor',
364
+ mainBranch: 'main',
365
+ validationPrefix: 'rule-validation-',
366
+ releasePrefix: 'rule-release-',
367
+ validationToken: 'closed',
368
+ concurrency: {
369
+ maxRetries: 3,
370
+ retryDelayMs: 100,
371
+ timeoutMs: 5000
372
+ },
373
+ gitNotes: {
374
+ namespace: 'refs/notes/pr-status',
375
+ enabled: true,
376
+ fallbackToCommit: true
377
+ },
378
+ canForce: true,
379
+ verbose: false
380
+ };
381
+ return config;
382
+ };
383
+ let defaultHelper;
384
+ /**
385
+ * Factory pour créer des helpers E2E facilement
386
+ */
387
+ class GitE2EFactory {
388
+ static getDefaultHelper() {
389
+ if (!defaultHelper) {
390
+ throw new Error('Default should be initialized from tests/setup.after.env.ts');
391
+ }
392
+ return defaultHelper;
393
+ }
394
+ /**
395
+ * Crée un helper E2E avec un repository temporaire
396
+ */
397
+ static async createE2EHelper(options) {
398
+ const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `git-e2e-test-${Math.random() * 10000 | 0}-${Math.random() * 10000 | 0}`);
399
+ (0, fs_1.mkdirSync)(tempDir, { recursive: true });
400
+ const gitConfig = { ...defaultConfig(tempDir), ...options?.customConfig };
401
+ gitConfig.repoPath = tempDir;
402
+ const testConfig = {
403
+ tempDir,
404
+ gitConfig,
405
+ autoCleanup: options?.autoCleanup ?? true
406
+ };
407
+ const helper = new GitE2EHelper(testConfig);
408
+ // Set test environment
409
+ process.env.NODE_ENV = 'test';
410
+ // Set default test values for required environment variables
411
+ const defaultTestEnv = {
412
+ DEFAULT_BRANCH_DRAFT: helper.config.draftBranch,
413
+ DEFAULT_BRANCH_MAIN: helper.config.mainBranch,
414
+ DEFAULT_BRANCH_VALIDATION_PREFIX: helper.config.validationPrefix,
415
+ DEFAULT_VALIDATION_TOKEN: helper.config.validationToken,
416
+ GIT_CONCURRENCY_MAX_RETRIES: helper.config.concurrency.maxRetries.toString(),
417
+ GIT_CONCURRENCY_RETRY_DELAY_MS: helper.config.concurrency.retryDelayMs.toString(),
418
+ GIT_CONCURRENCY_TIMEOUT_MS: helper.config.concurrency.timeoutMs.toString(),
419
+ GIT_NOTES_NAMESPACE: helper.config.gitNotes.namespace,
420
+ GIT_NOTES_ENABLED: helper.config.gitNotes.enabled.toString(),
421
+ GIT_NOTES_FALLBACK_TO_COMMIT: helper.config.gitNotes.fallbackToCommit.toString()
422
+ };
423
+ // Set default values if not already set
424
+ Object.entries(defaultTestEnv).forEach(([key, value]) => {
425
+ if (!process.env[key]) {
426
+ process.env[key] = value;
427
+ }
428
+ });
429
+ await helper.initTemporaryRepo();
430
+ defaultHelper = helper;
431
+ return helper;
432
+ }
433
+ /**
434
+ * Crée un helper E2E et exécute un test complet
435
+ */
436
+ static async runE2ETest(testName, testFunction, options) {
437
+ const startTime = Date.now();
438
+ const result = {
439
+ success: false,
440
+ duration: 0,
441
+ details: {
442
+ repositoryValid: false,
443
+ filesCreated: [],
444
+ prCreated: undefined,
445
+ prClosed: false,
446
+ merged: false
447
+ },
448
+ errors: []
449
+ };
450
+ let helper;
451
+ try {
452
+ // console.log(`🧪 Démarrage du test E2E: ${testName}`);
453
+ // Créer le helper
454
+ helper = await GitE2EFactory.createE2EHelper({
455
+ autoCleanup: options?.autoCleanup ?? true
456
+ });
457
+ // Valider l'état initial du repository
458
+ // Exécuter le test
459
+ await testFunction(helper);
460
+ // Marquer le test comme réussi
461
+ result.success = true;
462
+ result.duration = Date.now() - startTime;
463
+ // console.log(`✅ Test E2E réussi: ${testName} (${result.duration}ms)`);
464
+ return result;
465
+ }
466
+ catch (error) {
467
+ result.success = false;
468
+ result.duration = Date.now() - startTime;
469
+ result.errors.push(error instanceof Error ? error.message : String(error));
470
+ console.error(`❌ Test E2E échoué: ${testName}`);
471
+ console.error('Erreur:', error);
472
+ return result;
473
+ }
474
+ finally {
475
+ // Nettoyage si autoCleanup est activé
476
+ if (helper && (options?.autoCleanup ?? true)) {
477
+ try {
478
+ await helper.cleanup();
479
+ console.log(`🧹 Repository E2E nettoyé: ${helper.getRepositoryPath()}`);
480
+ }
481
+ catch (error) {
482
+ console.warn('Erreur lors du nettoyage:', error);
483
+ }
484
+ }
485
+ }
486
+ }
487
+ }
488
+ exports.GitE2EFactory = GitE2EFactory;
@@ -0,0 +1,66 @@
1
+ import { RulesGitConfig, GitHealthStatus } from '../types';
2
+ /**
3
+ * Git Repository Health Manager
4
+ *
5
+ * Manages repository health diagnostics, repairs, and maintenance operations.
6
+ * Provides safe operations that protect user work by only allowing destructive
7
+ * operations on temporary validation branches.
8
+ */
9
+ export declare class GitHealthManager {
10
+ private git;
11
+ private config;
12
+ constructor(config?: Partial<RulesGitConfig>);
13
+ /**
14
+ * Detects and resolves any ongoing merge states in the repository
15
+ * This should be called before any migration operations
16
+ *
17
+ * Uses the robust gitGetBranchHealth function to diagnose issues:
18
+ * - git merge --abort → abandonne tout merge en cours
19
+ * - git reset --hard HEAD → nettoie index + working directory
20
+ */
21
+ repairRepository(branch?: string): Promise<void>;
22
+ /**
23
+ * Ensures we're on a safe branch before performing destructive operations
24
+ * Protects user work by refusing operations on main branches
25
+ */
26
+ private ensureSafeBranch;
27
+ /**
28
+ * Force repair when git status fails
29
+ * PROTECTED: Only works on validation branches to protect user work
30
+ */
31
+ private forceRepair;
32
+ /**
33
+ * Comprehensive health check for all important branches in the system
34
+ * Provides a complete overview of repository health
35
+ */
36
+ diagnosticRepository(): Promise<GitHealthStatus[]>;
37
+ /**
38
+ * Display a concise health summary for a branch
39
+ */
40
+ private displayHealthSummary;
41
+ /**
42
+ * Synchronizes and repairs all validation branches with the editor branch
43
+ *
44
+ * This function updates existing validation branches to stay in sync with rule-editor,
45
+ * not merging rule-editor into validation branches.
46
+ *
47
+ * Handles:
48
+ * - Échecs de synchronisation → tentative de réparation par branche
49
+ * - Branches inaccessibles → git checkout -f + git reset --hard HEAD (protected)
50
+ * - Working directory sale → git reset --hard HEAD (protected)
51
+ * - Retry automatique après réparation
52
+ * - Branches irréparables → log d'intervention manuelle
53
+ */
54
+ syncValidationBranches(): Promise<void>;
55
+ /**
56
+ * Migrates Git notes from the first commit of validation branches to the last one.
57
+ *
58
+ * Handles:
59
+ * - Notes déjà présentes sur HEAD → skip migration
60
+ * - Merge-base introuvable → skip branche avec warning
61
+ * - Notes orphelines sur anciens commits → copie vers HEAD + suppression
62
+ * - Branches sans historique de notes → ignore silencieusement
63
+ * - Erreurs de traitement par branche → continue avec les autres
64
+ */
65
+ migrateNotes(): Promise<void>;
66
+ }