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.
- package/README.md +118 -22
- package/dist/src/agents/agents.example.d.ts +3 -0
- package/dist/src/agents/agents.example.js +38 -0
- package/dist/src/agents/authentication.js +2 -0
- package/dist/src/agents/prompts.d.ts +2 -2
- package/dist/src/agents/prompts.js +112 -49
- package/dist/src/agents/reducer.core.d.ts +12 -0
- package/dist/src/agents/reducer.core.js +207 -0
- package/dist/src/agents/reducer.d.ts +3 -0
- package/dist/src/agents/reducer.example.d.ts +28 -0
- package/dist/src/agents/reducer.example.js +118 -0
- package/dist/src/agents/reducer.js +19 -0
- package/dist/src/agents/reducer.loaders.d.ts +34 -0
- package/dist/src/agents/reducer.loaders.js +122 -0
- package/dist/src/agents/reducer.process.d.ts +16 -0
- package/dist/src/agents/reducer.process.js +143 -0
- package/dist/src/agents/reducer.tools.d.ts +29 -0
- package/dist/src/agents/reducer.tools.js +157 -0
- package/dist/src/agents/reducer.types.d.ts +50 -0
- package/dist/src/agents/reducer.types.js +5 -0
- package/dist/src/agents/simulator.d.ts +47 -0
- package/dist/src/agents/simulator.executor.d.ts +26 -0
- package/dist/src/agents/simulator.executor.js +132 -0
- package/dist/src/agents/simulator.js +205 -0
- package/dist/src/agents/simulator.prompts.d.ts +16 -0
- package/dist/src/agents/simulator.prompts.js +108 -0
- package/dist/src/agents/simulator.types.d.ts +42 -0
- package/dist/src/agents/simulator.types.js +2 -0
- package/dist/src/agents/simulator.utils.d.ts +20 -0
- package/dist/src/agents/simulator.utils.js +87 -0
- package/dist/src/execute.d.ts +13 -6
- package/dist/src/execute.js +351 -85
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +14 -0
- package/dist/src/princing.openai.d.ts +9 -2
- package/dist/src/princing.openai.js +15 -11
- package/dist/src/prompts.d.ts +3 -2
- package/dist/src/prompts.js +159 -19
- package/dist/src/rag/embeddings.d.ts +103 -0
- package/dist/src/rag/embeddings.js +466 -0
- package/dist/src/rag/index.d.ts +12 -0
- package/dist/src/rag/index.js +40 -0
- package/dist/src/rag/lucene.d.ts +45 -0
- package/dist/src/rag/lucene.js +227 -0
- package/dist/src/rag/parser.d.ts +68 -0
- package/dist/src/rag/parser.js +192 -0
- package/dist/src/rag/tools.d.ts +76 -0
- package/dist/src/rag/tools.js +196 -0
- package/dist/src/rag/types.d.ts +178 -0
- package/dist/src/rag/types.js +21 -0
- package/dist/src/rag/usecase.d.ts +16 -0
- package/dist/src/rag/usecase.js +79 -0
- package/dist/src/rules/errors.d.ts +60 -0
- package/dist/src/rules/errors.js +97 -0
- package/dist/src/rules/git/git.e2e.helper.d.ts +104 -0
- package/dist/src/rules/git/git.e2e.helper.js +488 -0
- package/dist/src/rules/git/git.health.d.ts +66 -0
- package/dist/src/rules/git/git.health.js +354 -0
- package/dist/src/rules/git/git.helper.d.ts +129 -0
- package/dist/src/rules/git/git.helper.js +53 -0
- package/dist/src/rules/git/index.d.ts +6 -0
- package/dist/src/rules/git/index.js +76 -0
- package/dist/src/rules/git/repo.d.ts +128 -0
- package/dist/src/rules/git/repo.js +900 -0
- package/dist/src/rules/git/repo.pr.d.ts +137 -0
- package/dist/src/rules/git/repo.pr.js +589 -0
- package/dist/src/rules/git/repo.tools.d.ts +134 -0
- package/dist/src/rules/git/repo.tools.js +730 -0
- package/dist/src/rules/index.d.ts +8 -0
- package/dist/src/rules/index.js +25 -0
- package/dist/src/rules/messages.d.ts +17 -0
- package/dist/src/rules/messages.js +21 -0
- package/dist/src/rules/types.ctrl.d.ts +28 -0
- package/dist/src/rules/types.ctrl.js +2 -0
- package/dist/src/rules/types.d.ts +510 -0
- package/dist/src/rules/types.helpers.d.ts +132 -0
- package/dist/src/rules/types.helpers.js +2 -0
- package/dist/src/rules/types.js +33 -0
- package/dist/src/rules/user.mapper.d.ts +61 -0
- package/dist/src/rules/user.mapper.js +160 -0
- package/dist/src/rules/utils/slug.d.ts +22 -0
- package/dist/src/rules/utils/slug.js +35 -0
- package/dist/src/rules/utils.matter.d.ts +66 -0
- package/dist/src/rules/utils.matter.js +208 -0
- package/dist/src/rules/utils.slug.d.ts +22 -0
- package/dist/src/rules/utils.slug.js +35 -0
- package/dist/src/scrapper.d.ts +3 -2
- package/dist/src/scrapper.js +33 -37
- package/dist/src/stategraph/index.d.ts +8 -0
- package/dist/src/stategraph/index.js +21 -0
- package/dist/src/stategraph/stategraph.d.ts +91 -0
- package/dist/src/stategraph/stategraph.js +241 -0
- package/dist/src/stategraph/stategraph.storage.d.ts +41 -0
- package/dist/src/stategraph/stategraph.storage.js +166 -0
- package/dist/src/stategraph/types.d.ts +139 -0
- package/dist/src/stategraph/types.js +19 -0
- package/dist/src/types.d.ts +62 -39
- package/dist/src/types.js +53 -89
- package/dist/src/usecase.d.ts +4 -0
- package/dist/src/usecase.js +44 -0
- package/dist/src/utils.d.ts +12 -5
- package/dist/src/utils.js +30 -13
- 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
|
+
}
|