agentic-api 2.0.491 → 2.0.585
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 +37 -34
- package/dist/src/agents/reducer.core.js +2 -2
- package/dist/src/agents/simulator.d.ts +26 -1
- package/dist/src/agents/simulator.dashboard.d.ts +140 -0
- package/dist/src/agents/simulator.dashboard.js +344 -0
- package/dist/src/agents/simulator.js +56 -0
- package/dist/src/agents/simulator.types.d.ts +38 -6
- package/dist/src/agents/simulator.utils.d.ts +22 -1
- package/dist/src/agents/simulator.utils.js +27 -0
- package/dist/src/execute/helpers.js +2 -2
- package/dist/src/execute/modelconfig.d.ts +21 -11
- package/dist/src/execute/modelconfig.js +29 -13
- package/dist/src/execute/responses.js +8 -7
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +20 -1
- package/dist/src/llm/config.d.ts +25 -0
- package/dist/src/llm/config.js +38 -0
- package/dist/src/llm/index.d.ts +48 -0
- package/dist/src/llm/index.js +115 -0
- package/dist/src/llm/openai.d.ts +6 -0
- package/dist/src/llm/openai.js +154 -0
- package/dist/src/llm/pricing.d.ts +26 -0
- package/dist/src/llm/pricing.js +129 -0
- package/dist/src/llm/xai.d.ts +17 -0
- package/dist/src/llm/xai.js +90 -0
- package/dist/src/pricing.llm.d.ts +3 -15
- package/dist/src/pricing.llm.js +10 -251
- package/dist/src/prompts.d.ts +0 -1
- package/dist/src/prompts.js +51 -118
- package/dist/src/rag/embeddings.d.ts +5 -1
- package/dist/src/rag/embeddings.js +15 -5
- package/dist/src/rag/parser.js +1 -1
- package/dist/src/rag/rag.manager.d.ts +33 -2
- package/dist/src/rag/rag.manager.js +132 -46
- package/dist/src/rag/types.d.ts +2 -0
- package/dist/src/rag/usecase.js +8 -11
- package/dist/src/rules/git/git.health.js +59 -4
- package/dist/src/rules/git/repo.d.ts +11 -4
- package/dist/src/rules/git/repo.js +64 -18
- package/dist/src/rules/git/repo.pr.d.ts +8 -0
- package/dist/src/rules/git/repo.pr.js +45 -1
- package/dist/src/rules/git/repo.tools.d.ts +5 -1
- package/dist/src/rules/git/repo.tools.js +54 -7
- package/dist/src/rules/types.d.ts +14 -0
- package/dist/src/rules/utils.matter.d.ts +0 -20
- package/dist/src/rules/utils.matter.js +42 -74
- package/dist/src/scrapper.js +2 -2
- package/dist/src/utils.d.ts +0 -8
- package/dist/src/utils.js +1 -28
- package/package.json +1 -1
|
@@ -53,12 +53,19 @@ class RAGManager {
|
|
|
53
53
|
DEFAULT_RAG_INSTANCE[defaultName] = this;
|
|
54
54
|
}
|
|
55
55
|
static get(config) {
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
// Normaliser le baseDir pour éviter les doublons dus aux trailing slashes
|
|
57
|
+
// ou aux variations de paths (relatif/absolu, symlinks, etc.)
|
|
58
|
+
const normalizedBaseDir = path_1.default.resolve(config.baseDir).replace(/\/+$/, '');
|
|
59
|
+
// Vérifier si une instance existe déjà pour ce baseDir normalisé
|
|
60
|
+
if (DEFAULT_RAG_INSTANCE[normalizedBaseDir]) {
|
|
61
|
+
return DEFAULT_RAG_INSTANCE[normalizedBaseDir];
|
|
62
|
+
}
|
|
63
|
+
// Créer une nouvelle instance avec le baseDir normalisé
|
|
64
|
+
const normalizedConfig = {
|
|
65
|
+
...config,
|
|
66
|
+
baseDir: normalizedBaseDir
|
|
67
|
+
};
|
|
68
|
+
return new RAGManager(normalizedConfig);
|
|
62
69
|
}
|
|
63
70
|
/**
|
|
64
71
|
* Nettoie le cache des instances (utile pour les tests)
|
|
@@ -495,18 +502,31 @@ class RAGManager {
|
|
|
495
502
|
if (!entry) {
|
|
496
503
|
throw new Error(`RAG '${name}' n'existe pas`);
|
|
497
504
|
}
|
|
498
|
-
// if (status.status !== 'building') {
|
|
499
|
-
// throw new Error(`RAG '${name}' n'est pas en construction (status: ${status.status})`);
|
|
500
|
-
// }
|
|
501
505
|
const buildPath = entry.configPath;
|
|
502
506
|
if (!(0, fs_1.existsSync)(buildPath)) {
|
|
503
507
|
throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
|
|
504
508
|
}
|
|
505
509
|
const filePath = path_1.default.join(buildPath, filename);
|
|
506
|
-
|
|
507
|
-
//
|
|
510
|
+
// ✅ Sauvegarder le fichier source uniquement
|
|
511
|
+
// Le .sha est écrit par markDocumentProcessed() APRÈS génération de .enhanced.md et .query.json
|
|
508
512
|
(0, fs_1.writeFileSync)(filePath, content, 'utf8');
|
|
509
|
-
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Marque un document comme entièrement traité (enhanced + query.json générés)
|
|
516
|
+
* Le .sha est écrit APRÈS le traitement complet pour garantir la cohérence
|
|
517
|
+
*
|
|
518
|
+
* @param name Nom du RAG
|
|
519
|
+
* @param filename Nom du fichier (ex: 'procedure.md')
|
|
520
|
+
* @param content Contenu du document traité
|
|
521
|
+
*/
|
|
522
|
+
markDocumentProcessed(name, filename, content) {
|
|
523
|
+
const entry = this.getEntry(name);
|
|
524
|
+
if (!entry) {
|
|
525
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
526
|
+
}
|
|
527
|
+
const buildPath = entry.configPath;
|
|
528
|
+
const shaPath = path_1.default.join(buildPath, filename + '.sha');
|
|
529
|
+
// ✅ Sauvegarder le fingerprint pour marquer le document comme traité
|
|
510
530
|
const sha = (0, usecase_1.calculateFingerprint)(content);
|
|
511
531
|
(0, fs_1.writeFileSync)(shaPath, sha, 'utf8');
|
|
512
532
|
}
|
|
@@ -686,6 +706,72 @@ class RAGManager {
|
|
|
686
706
|
const queryFile = baseName + '.query.json';
|
|
687
707
|
return (0, usecase_1.loadUseCases)(queryFile, { baseDir: configPath });
|
|
688
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Charge tous les use cases de tous les documents d'un RAG
|
|
711
|
+
*
|
|
712
|
+
* Utilise le fichier rag-metadata.json pour obtenir la liste des documents valides
|
|
713
|
+
* puis charge chaque fichier .query.json correspondant
|
|
714
|
+
*
|
|
715
|
+
* @param name Nom du RAG
|
|
716
|
+
* @returns Objet contenant tous les use cases par document
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* ```typescript
|
|
720
|
+
* const allQueries = ragManager.loadAllUseCases('my-rag');
|
|
721
|
+
* console.log(`${Object.keys(allQueries.documents).length} documents avec use cases`);
|
|
722
|
+
* console.log(`${allQueries.totalQueries} questions au total`);
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
725
|
+
loadAllUseCases(name) {
|
|
726
|
+
const entry = this.getEntry(name);
|
|
727
|
+
if (!entry) {
|
|
728
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
729
|
+
}
|
|
730
|
+
//
|
|
731
|
+
// Charger le metadata pour obtenir la liste des documents
|
|
732
|
+
const metadataFile = path_1.default.join(entry.configPath, types_1.RAG_FILES.METADATA);
|
|
733
|
+
if (!(0, fs_1.existsSync)(metadataFile)) {
|
|
734
|
+
throw new Error(`Fichier metadata manquant: ${metadataFile}`);
|
|
735
|
+
}
|
|
736
|
+
let metadata;
|
|
737
|
+
try {
|
|
738
|
+
metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
throw new Error(`Erreur lors du chargement du metadata: ${error}`);
|
|
742
|
+
}
|
|
743
|
+
//
|
|
744
|
+
// Extraire les filenames uniques depuis metadata.documents
|
|
745
|
+
const uniqueFilenames = new Set();
|
|
746
|
+
for (const docRef of Object.values(metadata.documents)) {
|
|
747
|
+
if (docRef.filename) {
|
|
748
|
+
uniqueFilenames.add(docRef.filename);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
//
|
|
752
|
+
// Charger les use cases pour chaque document
|
|
753
|
+
const documents = {};
|
|
754
|
+
let totalQueries = 0;
|
|
755
|
+
const filenames = [];
|
|
756
|
+
for (const filename of uniqueFilenames) {
|
|
757
|
+
const baseName = filename.replace(/\.md$/, '');
|
|
758
|
+
const queryFile = path_1.default.join(entry.configPath, baseName + '.query.json');
|
|
759
|
+
if ((0, fs_1.existsSync)(queryFile)) {
|
|
760
|
+
try {
|
|
761
|
+
const queries = JSON.parse((0, fs_1.readFileSync)(queryFile, 'utf8'));
|
|
762
|
+
documents[filename] = queries;
|
|
763
|
+
totalQueries += queries.queries?.length || 0;
|
|
764
|
+
filenames.push(filename);
|
|
765
|
+
}
|
|
766
|
+
catch (error) {
|
|
767
|
+
//
|
|
768
|
+
// Log l'erreur mais continue avec les autres documents
|
|
769
|
+
console.warn(`⚠️ Erreur chargement use cases pour ${filename}:`, error.message);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return { documents, totalQueries };
|
|
774
|
+
}
|
|
689
775
|
/**
|
|
690
776
|
* Construit un RAG de manière atomique
|
|
691
777
|
*
|
|
@@ -881,28 +967,34 @@ class RAGManager {
|
|
|
881
967
|
*/
|
|
882
968
|
async delete(name, archiveFlag = true) {
|
|
883
969
|
if (!this.exists(name)) {
|
|
884
|
-
|
|
970
|
+
console.warn(`⚠️ RAG '${name}' n'existe pas, skip delete`);
|
|
971
|
+
return new Error(`RAG '${name}' n'existe pas`);
|
|
885
972
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
(0, fs_1.
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
973
|
+
try {
|
|
974
|
+
const entry = this.getEntry(name);
|
|
975
|
+
const registry = this.getRegistry();
|
|
976
|
+
// Archiver si demandé
|
|
977
|
+
if (archiveFlag) {
|
|
978
|
+
await this.archive(name);
|
|
979
|
+
}
|
|
980
|
+
else if ((0, fs_1.existsSync)(entry.configPath)) {
|
|
981
|
+
(0, fs_1.rmSync)(entry.configPath, { recursive: true, force: true });
|
|
982
|
+
}
|
|
983
|
+
// Supprimer du registre
|
|
984
|
+
delete registry.registries[name];
|
|
985
|
+
// Supprimer de la map des embeddings chargés
|
|
986
|
+
this.loadedEmbeddings.delete(name);
|
|
987
|
+
// Changer le défaut si nécessaire
|
|
988
|
+
if (registry.defaultName === name) {
|
|
989
|
+
const remainingRAGs = Object.keys(registry.registries);
|
|
990
|
+
registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
|
|
991
|
+
}
|
|
992
|
+
this.saveRegistry(registry);
|
|
993
|
+
console.log(`✅ RAG supprimé: ${name}`);
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
return error;
|
|
903
997
|
}
|
|
904
|
-
this.saveRegistry(registry);
|
|
905
|
-
console.log(`✅ RAG supprimé: ${name}`);
|
|
906
998
|
}
|
|
907
999
|
/**
|
|
908
1000
|
* Renomme un document dans un RAG existant
|
|
@@ -1041,7 +1133,8 @@ class RAGManager {
|
|
|
1041
1133
|
}
|
|
1042
1134
|
}
|
|
1043
1135
|
/**
|
|
1044
|
-
* Renomme un RAG existant
|
|
1136
|
+
* Renomme un RAG existant (nom dans le registre uniquement)
|
|
1137
|
+
* Le dossier physique n'est PAS modifié, seul le nom dans la config change.
|
|
1045
1138
|
*
|
|
1046
1139
|
* @param from Nom actuel du RAG
|
|
1047
1140
|
* @param to Nouveau nom du RAG
|
|
@@ -1051,6 +1144,7 @@ class RAGManager {
|
|
|
1051
1144
|
* @example
|
|
1052
1145
|
* ```typescript
|
|
1053
1146
|
* await ragManager.rename('old-name', 'new-name', 'Description mise à jour');
|
|
1147
|
+
* // Le dossier reste /path/to/old-name mais le RAG s'appelle 'new-name'
|
|
1054
1148
|
* ```
|
|
1055
1149
|
*/
|
|
1056
1150
|
async rename(from, to, description) {
|
|
@@ -1073,20 +1167,14 @@ class RAGManager {
|
|
|
1073
1167
|
throw new Error(`Le nom '${to}' est réservé et ne peut pas être utilisé`);
|
|
1074
1168
|
}
|
|
1075
1169
|
const sourceEntry = this.getEntry(from);
|
|
1076
|
-
const sourcePath = sourceEntry.configPath;
|
|
1077
|
-
const destPath = path_1.default.join(this.config.baseDir, to);
|
|
1078
1170
|
try {
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (this.config.verbose)
|
|
1083
|
-
console.log(`📁 Dossier renommé: ${sourcePath} → ${destPath}`);
|
|
1084
|
-
}
|
|
1085
|
-
// Mettre à jour le registre
|
|
1171
|
+
//
|
|
1172
|
+
// Ne PAS renommer le dossier physique - juste le nom dans le registre
|
|
1173
|
+
// Le configPath reste inchangé (pointe vers le même dossier)
|
|
1086
1174
|
const registry = this.getRegistry();
|
|
1087
1175
|
registry.registries[to] = {
|
|
1088
1176
|
name: to,
|
|
1089
|
-
configPath:
|
|
1177
|
+
configPath: sourceEntry.configPath, // ✅ Garde le même chemin
|
|
1090
1178
|
status: sourceEntry.status,
|
|
1091
1179
|
description: description || sourceEntry.description
|
|
1092
1180
|
};
|
|
@@ -1100,12 +1188,10 @@ class RAGManager {
|
|
|
1100
1188
|
if (embeddingData) {
|
|
1101
1189
|
this.loadedEmbeddings.set(to, embeddingData);
|
|
1102
1190
|
this.loadedEmbeddings.delete(from);
|
|
1103
|
-
// Notifier l'embedding de se mettre à jour avec le nouveau chemin
|
|
1104
|
-
this.notifyUpdate(to);
|
|
1105
1191
|
}
|
|
1106
1192
|
// Mettre à jour le cache
|
|
1107
1193
|
this.saveRegistry(registry);
|
|
1108
|
-
console.log(`✅ RAG renommé: ${from} → ${to}`);
|
|
1194
|
+
console.log(`✅ RAG renommé: ${from} → ${to} (dossier inchangé: ${sourceEntry.configPath})`);
|
|
1109
1195
|
}
|
|
1110
1196
|
catch (error) {
|
|
1111
1197
|
console.error(`❌ Erreur lors du renommage de '${from}' en '${to}':`, error);
|
package/dist/src/rag/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare const RAG_FILES: {
|
|
|
5
5
|
readonly QUERIES: "rag-queries.json";
|
|
6
6
|
};
|
|
7
7
|
export interface RAGConfig {
|
|
8
|
+
/** Provider de l'embedding (default openai) */
|
|
9
|
+
provider?: string;
|
|
8
10
|
/** Répertoire de base pour les documents et index */
|
|
9
11
|
baseDir: string;
|
|
10
12
|
/** Nom du fichier de vecteurs (sans extension) */
|
package/dist/src/rag/usecase.js
CHANGED
|
@@ -132,25 +132,22 @@ async function extractAndSaveDocumentUseCases(document, baseDir, version, tag, o
|
|
|
132
132
|
const existingQueries = JSON.parse((0, fs_1.readFileSync)(queryFile, 'utf8'));
|
|
133
133
|
// Comparer le fingerprint
|
|
134
134
|
if (existingQueries.fingerprint === fingerprint) {
|
|
135
|
-
|
|
136
|
-
console.log(`⏭️ Use cases à jour (fingerprint identique): ${document.filename}`);
|
|
137
|
-
}
|
|
135
|
+
console.log(`⏭️ Skip queries: ${document.filename}`);
|
|
138
136
|
return existingQueries;
|
|
139
137
|
}
|
|
140
|
-
|
|
141
|
-
console.log(`🔄 Contenu modifié (fingerprint différent): ${document.filename}`);
|
|
142
|
-
}
|
|
138
|
+
console.log(`🔄 Source modifiée queries: ${document.filename}`);
|
|
143
139
|
}
|
|
144
140
|
catch (error) {
|
|
145
141
|
// Si erreur de lecture, on continue avec l'extraction
|
|
146
|
-
|
|
147
|
-
console.warn(`⚠️ Erreur lecture use cases existants: ${error}`);
|
|
148
|
-
}
|
|
142
|
+
console.warn(`⚠️ Erreur lecture use cases: ${error}`);
|
|
149
143
|
}
|
|
150
144
|
}
|
|
151
145
|
// Extraction nécessaire (fichier inexistant, fingerprint différent, ou force=true)
|
|
152
|
-
if (
|
|
153
|
-
console.log(`🔄
|
|
146
|
+
if (force) {
|
|
147
|
+
console.log(`🔄 Force queries: ${document.filename}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(`✨ Nouveau queries: ${document.filename}`);
|
|
154
151
|
}
|
|
155
152
|
const { queries, cost } = await extractDocumentUseCases(document);
|
|
156
153
|
const docQueries = {
|
|
@@ -551,6 +551,44 @@ class GitHealthManager {
|
|
|
551
551
|
];
|
|
552
552
|
let fixed = 0;
|
|
553
553
|
const fixedFiles = new Set();
|
|
554
|
+
// ✅ ÉTAPE CRITIQUE: Charger le cache pour toutes les branches avant la réparation
|
|
555
|
+
// Cela permet à getFileID() de trouver l'ID existant du fichier
|
|
556
|
+
if (!dryRun && filesToFix.length > 0) {
|
|
557
|
+
console.log(` 🔍 Chargement du cache pour toutes les branches...`);
|
|
558
|
+
const allBranches = await (0, git_1.gitGetAllBranches)(this.git);
|
|
559
|
+
const uniqueFiles = new Set();
|
|
560
|
+
for (const fileRef of filesToFix) {
|
|
561
|
+
const [, ...fileParts] = fileRef.split(':');
|
|
562
|
+
const file = fileParts.join(':');
|
|
563
|
+
uniqueFiles.add(file);
|
|
564
|
+
}
|
|
565
|
+
// Mettre en cache la liste des fichiers par branche pour éviter les appels répétés
|
|
566
|
+
const branchFilesCache = new Map();
|
|
567
|
+
for (const branch of allBranches) {
|
|
568
|
+
try {
|
|
569
|
+
const filesInBranch = await (0, git_1.gitListFilesInBranch)(this.git, branch);
|
|
570
|
+
branchFilesCache.set(branch, filesInBranch);
|
|
571
|
+
}
|
|
572
|
+
catch (e) {
|
|
573
|
+
// Ignorer silencieusement les erreurs (branche peut être inaccessible)
|
|
574
|
+
branchFilesCache.set(branch, []);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Charger le cache pour chaque fichier dans les branches où il existe
|
|
578
|
+
for (const file of uniqueFiles) {
|
|
579
|
+
for (const branch of allBranches) {
|
|
580
|
+
try {
|
|
581
|
+
const filesInBranch = branchFilesCache.get(branch) || [];
|
|
582
|
+
if (filesInBranch.includes(file)) {
|
|
583
|
+
await (0, git_1.gitFileStrictMatter)(this.git, file, branch, this.config);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
catch (e) {
|
|
587
|
+
// Ignorer silencieusement les erreurs (fichier peut ne pas exister)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
554
592
|
for (const fileRef of filesToFix) {
|
|
555
593
|
// Éviter de réparer deux fois le même fichier
|
|
556
594
|
if (fixedFiles.has(fileRef)) {
|
|
@@ -570,11 +608,19 @@ class GitHealthManager {
|
|
|
570
608
|
// Parser le matter complet
|
|
571
609
|
const parsed = (0, utils_matter_1.matterParse)(fileData.content);
|
|
572
610
|
const { matter: fullMatter, content } = parsed;
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
611
|
+
// ✅ CORRECTION: Utiliser gitEnsureMatterID() pour réutiliser l'ID existant du fichier
|
|
612
|
+
// Si le fichier existe déjà dans une autre branche, son ID sera réutilisé
|
|
613
|
+
// Sinon, un nouvel ID sera généré automatiquement
|
|
614
|
+
const matterWithID = (0, git_1.gitEnsureMatterID)(fullMatter, this.config, branchName, file);
|
|
615
|
+
if (matterWithID.id !== fullMatter.id) {
|
|
616
|
+
if (fullMatter.id && fullMatter.id > 999) {
|
|
617
|
+
console.log(` 🔧 ${file}: ID ${fullMatter.id} remplacé par ID existant ${matterWithID.id}`);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
console.log(` 🔧 ${file}: Nouvel ID assigné: ${matterWithID.id}`);
|
|
621
|
+
}
|
|
577
622
|
}
|
|
623
|
+
fullMatter.id = matterWithID.id;
|
|
578
624
|
// Générer un titre si manquant (basé sur le nom du fichier ou le premier titre de section)
|
|
579
625
|
if (!fullMatter.title || fullMatter.title.trim() === '') {
|
|
580
626
|
// Essayer d'extraire le premier titre de section (# Title)
|
|
@@ -591,6 +637,15 @@ class GitHealthManager {
|
|
|
591
637
|
}
|
|
592
638
|
// Re-sérialiser et sauvegarder
|
|
593
639
|
const updatedContent = (0, utils_matter_1.matterSerialize)(content, fullMatter);
|
|
640
|
+
// ✅ CRITIQUE: Vérifier si le contenu a réellement changé avant de créer un commit
|
|
641
|
+
// Si le contenu n'a pas changé, ne pas créer de commit pour éviter de déplacer les notes Git
|
|
642
|
+
if (updatedContent === fileData.content) {
|
|
643
|
+
// Le contenu n'a pas changé, juste mettre à jour le cache sans créer de commit
|
|
644
|
+
console.log(` ℹ️ ${file}: Aucun changement nécessaire (ID ${fullMatter.id} déjà correct)`);
|
|
645
|
+
fixed++;
|
|
646
|
+
fixedFiles.add(fileRef);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
594
649
|
// ✅ IMPORTANT: Désactiver withID car l'ID est déjà généré et enregistré
|
|
595
650
|
// Évite la double validation qui cause "ID déjà utilisé"
|
|
596
651
|
await (0, repo_1.gitCreateOrEditFile)(this.git, file, branchName, updatedContent, { name: 'GitHealthManager', email: 'health@system' }, { ...this.config, canForce: true, withID: false });
|
|
@@ -195,13 +195,20 @@ export declare function gitShowConfiguration(git: SimpleGit): Promise<any>;
|
|
|
195
195
|
*/
|
|
196
196
|
export declare function gitCheckConfiguration(git?: SimpleGit): Promise<any>;
|
|
197
197
|
/**
|
|
198
|
-
* Crée un fichier dans
|
|
199
|
-
*
|
|
198
|
+
* Crée ou modifie un fichier dans une branche Git et fait un commit automatique
|
|
199
|
+
*
|
|
200
|
+
* @param git Instance SimpleGit
|
|
200
201
|
* @param filePath Chemin du fichier
|
|
201
202
|
* @param PR Nom de la branche de Pull Request
|
|
202
203
|
* @param content Contenu du fichier
|
|
203
|
-
* @param user Utilisateur qui
|
|
204
|
-
* @param config Configuration Git
|
|
204
|
+
* @param user Utilisateur qui fait le commit
|
|
205
|
+
* @param config Configuration Git optionnelle
|
|
206
|
+
* @returns Historique du commit créé
|
|
207
|
+
*
|
|
208
|
+
* @fixme Ajouter un paramètre optionnel `commitMessage?: string` pour permettre de personnaliser
|
|
209
|
+
* le message de commit au lieu d'utiliser toujours `commit: ${filePath}` par défaut.
|
|
210
|
+
* Cela permettrait aux callers de spécifier des messages plus descriptifs.
|
|
211
|
+
*
|
|
205
212
|
* @throws GitOperationError si la création échoue
|
|
206
213
|
*/
|
|
207
214
|
export declare function gitCreateOrEditFile(git: SimpleGit, filePath: string, PR: string, content: string, user: RuleUser, config?: RulesGitConfig): Promise<GitCommitHistory>;
|
|
@@ -59,6 +59,8 @@ const errors_1 = require("../errors");
|
|
|
59
59
|
const path_1 = require("path");
|
|
60
60
|
const fs = __importStar(require("fs/promises"));
|
|
61
61
|
const repo_tools_1 = require("./repo.tools");
|
|
62
|
+
const repo_pr_1 = require("./repo.pr");
|
|
63
|
+
const utils_matter_1 = require("../utils.matter");
|
|
62
64
|
/**
|
|
63
65
|
* Service singleton pour gérer le registre d'IDs en mémoire
|
|
64
66
|
*/
|
|
@@ -561,7 +563,7 @@ function gitEnsureMatterID(matter, config, branch, file) {
|
|
|
561
563
|
// (Si même fichier, registerExistingID ne lève pas d'erreur)
|
|
562
564
|
}
|
|
563
565
|
}
|
|
564
|
-
// Générer un nouvel ID
|
|
566
|
+
// Générer un nouvel ID (si aucun ID fourni ou ID invalide)
|
|
565
567
|
matter.id = gitGenerateNextID(config);
|
|
566
568
|
// FIXME: Double save ici - gitGenerateNextID() fait save() puis gitRegisterExistingID() fait save() aussi
|
|
567
569
|
// Refactorisation future: Retirer save() de gitGenerateNextID() et le faire uniquement dans gitRegisterExistingID()
|
|
@@ -999,13 +1001,20 @@ async function gitCheckConfiguration(git) {
|
|
|
999
1001
|
return check;
|
|
1000
1002
|
}
|
|
1001
1003
|
/**
|
|
1002
|
-
* Crée un fichier dans
|
|
1003
|
-
*
|
|
1004
|
+
* Crée ou modifie un fichier dans une branche Git et fait un commit automatique
|
|
1005
|
+
*
|
|
1006
|
+
* @param git Instance SimpleGit
|
|
1004
1007
|
* @param filePath Chemin du fichier
|
|
1005
1008
|
* @param PR Nom de la branche de Pull Request
|
|
1006
1009
|
* @param content Contenu du fichier
|
|
1007
|
-
* @param user Utilisateur qui
|
|
1008
|
-
* @param config Configuration Git
|
|
1010
|
+
* @param user Utilisateur qui fait le commit
|
|
1011
|
+
* @param config Configuration Git optionnelle
|
|
1012
|
+
* @returns Historique du commit créé
|
|
1013
|
+
*
|
|
1014
|
+
* @fixme Ajouter un paramètre optionnel `commitMessage?: string` pour permettre de personnaliser
|
|
1015
|
+
* le message de commit au lieu d'utiliser toujours `commit: ${filePath}` par défaut.
|
|
1016
|
+
* Cela permettrait aux callers de spécifier des messages plus descriptifs.
|
|
1017
|
+
*
|
|
1009
1018
|
* @throws GitOperationError si la création échoue
|
|
1010
1019
|
*/
|
|
1011
1020
|
async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
@@ -1019,8 +1028,7 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
|
1019
1028
|
if (gitConf.withID) {
|
|
1020
1029
|
try {
|
|
1021
1030
|
// Import dynamique pour éviter les dépendances circulaires
|
|
1022
|
-
const
|
|
1023
|
-
const parsed = matterParse(content);
|
|
1031
|
+
const parsed = (0, utils_matter_1.matterParse)(content);
|
|
1024
1032
|
if (gitConf.strictID) {
|
|
1025
1033
|
// Mode strict : valider que l'ID existe et est correct
|
|
1026
1034
|
validateMatter(parsed.matter);
|
|
@@ -1040,7 +1048,7 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
|
1040
1048
|
// Seulement si l'ID est manquant ou invalide
|
|
1041
1049
|
parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
|
|
1042
1050
|
// Re-sérialiser avec l'ID mis à jour
|
|
1043
|
-
content = matterSerialize(parsed.content, parsed.matter);
|
|
1051
|
+
content = (0, utils_matter_1.matterSerialize)(parsed.content, parsed.matter);
|
|
1044
1052
|
}
|
|
1045
1053
|
else {
|
|
1046
1054
|
// ✅ L'ID existe déjà : gitRegisterExistingID() appelle maintenant setMatterCache() automatiquement
|
|
@@ -1052,7 +1060,7 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
|
1052
1060
|
if (error.code === 'id_already_used') {
|
|
1053
1061
|
// En mode non-strict, générer un nouvel ID si doublon détecté
|
|
1054
1062
|
parsed.matter = gitEnsureMatterID(parsed.matter, gitConf, PR, filePath);
|
|
1055
|
-
content = matterSerialize(parsed.content, parsed.matter);
|
|
1063
|
+
content = (0, utils_matter_1.matterSerialize)(parsed.content, parsed.matter);
|
|
1056
1064
|
// ✅ Ne pas propager l'erreur, continuer avec le nouvel ID généré
|
|
1057
1065
|
}
|
|
1058
1066
|
else {
|
|
@@ -1089,13 +1097,15 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
|
1089
1097
|
//
|
|
1090
1098
|
// without note the branche is not a valid PR branch
|
|
1091
1099
|
const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
|
|
1100
|
+
if (oldNote && gitConf.verbose) {
|
|
1101
|
+
console.debug('[gitCreateOrEditFile] metadata.files before update:', oldNote.files);
|
|
1102
|
+
}
|
|
1092
1103
|
const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
|
|
1093
1104
|
// ✅ Mettre à jour le cache du matter après sauvegarde
|
|
1094
1105
|
// NOTE: Le cache est TOUJOURS mis à jour, indépendamment de withID
|
|
1095
1106
|
// C'est une opération de cache, pas une validation
|
|
1096
1107
|
try {
|
|
1097
|
-
const
|
|
1098
|
-
const parsed = matterParse(content);
|
|
1108
|
+
const parsed = (0, utils_matter_1.matterParse)(content);
|
|
1099
1109
|
idRegistryService.init(gitConf.repoPath);
|
|
1100
1110
|
idRegistryService.setMatterCache(PR, filePath, {
|
|
1101
1111
|
id: parsed.matter.id,
|
|
@@ -1124,11 +1134,15 @@ async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
|
1124
1134
|
}
|
|
1125
1135
|
// Determine the current head after the commit attempt.
|
|
1126
1136
|
const newHead = (await git.revparse(['HEAD'])).trim();
|
|
1127
|
-
|
|
1137
|
+
let newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, PR, oldNote.mergeBase);
|
|
1138
|
+
newFiles = await sanitizePRFiles(git, newFiles, PR);
|
|
1128
1139
|
const updatedNote = {
|
|
1129
1140
|
...oldNote,
|
|
1130
1141
|
files: newFiles,
|
|
1131
1142
|
};
|
|
1143
|
+
if (gitConf.verbose) {
|
|
1144
|
+
console.debug('[gitCreateOrEditFile] metadata.files after update:', updatedNote.files);
|
|
1145
|
+
}
|
|
1132
1146
|
// Write the note to the current HEAD of the PR branch.
|
|
1133
1147
|
// If no commit was made, this overwrites the note on oldHead.
|
|
1134
1148
|
// If a new commit was made, this writes the note on newHead.
|
|
@@ -1331,12 +1345,24 @@ async function gitRenameFile(git, oldFileName, newFileName, branch, user, config
|
|
|
1331
1345
|
//
|
|
1332
1346
|
//FIXME: add a check about the branch (file can be NEW and on branch)
|
|
1333
1347
|
const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
|
|
1348
|
+
let result;
|
|
1334
1349
|
if ((0, fs_1.existsSync)(fullOldPath)) {
|
|
1335
|
-
|
|
1350
|
+
result = await gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config);
|
|
1336
1351
|
}
|
|
1337
1352
|
else {
|
|
1338
|
-
|
|
1353
|
+
result = await gitRenameFile_git(git, oldFileName, newFileName, branch, user, config);
|
|
1354
|
+
if (branch !== 'NEW' && branch.startsWith(gitConf.validationPrefix)) {
|
|
1355
|
+
try {
|
|
1356
|
+
await (0, repo_pr_1.gitPRReplaceFile)(git, branch, { remove: oldFileName, add: newFileName }, gitConf);
|
|
1357
|
+
}
|
|
1358
|
+
catch (metadataError) {
|
|
1359
|
+
if (gitConf.verbose) {
|
|
1360
|
+
console.warn('⚠️ Failed to update PR metadata after rename:', metadataError);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1339
1364
|
}
|
|
1365
|
+
return result;
|
|
1340
1366
|
}
|
|
1341
1367
|
catch (error) {
|
|
1342
1368
|
// Pour les vraies branches Git, ne pas faire de fallback filesystem
|
|
@@ -1663,9 +1689,7 @@ function createUnhealthyStatus(branch, exists, accessible, clean, mergeInProgres
|
|
|
1663
1689
|
* Vérifie si un merge est en cours en regardant les fichiers Git
|
|
1664
1690
|
*/
|
|
1665
1691
|
async function checkMergeInProgress(repoPath) {
|
|
1666
|
-
const
|
|
1667
|
-
const { join } = await Promise.resolve().then(() => __importStar(require('path')));
|
|
1668
|
-
const gitDir = join(repoPath, '.git');
|
|
1692
|
+
const gitDir = (0, path_1.join)(repoPath, '.git');
|
|
1669
1693
|
const mergeFiles = [
|
|
1670
1694
|
'MERGE_HEAD',
|
|
1671
1695
|
'MERGE_MODE',
|
|
@@ -1673,5 +1697,27 @@ async function checkMergeInProgress(repoPath) {
|
|
|
1673
1697
|
'CHERRY_PICK_HEAD',
|
|
1674
1698
|
'REVERT_HEAD'
|
|
1675
1699
|
];
|
|
1676
|
-
return mergeFiles.some(file => existsSync(join(gitDir, file)));
|
|
1700
|
+
return mergeFiles.some(file => (0, fs_1.existsSync)((0, path_1.join)(gitDir, file)));
|
|
1701
|
+
}
|
|
1702
|
+
async function sanitizePRFiles(git, files, branch) {
|
|
1703
|
+
if (!files || files.length === 0) {
|
|
1704
|
+
return [];
|
|
1705
|
+
}
|
|
1706
|
+
const checks = await Promise.all(files.map(async (file) => ({
|
|
1707
|
+
file,
|
|
1708
|
+
exists: await (0, repo_tools_1.gitFileExistsInBranch)(git, file, branch)
|
|
1709
|
+
})));
|
|
1710
|
+
const sanitized = [];
|
|
1711
|
+
const seen = new Set();
|
|
1712
|
+
for (const { file, exists } of checks) {
|
|
1713
|
+
if (!exists) {
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
if (seen.has(file)) {
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
seen.add(file);
|
|
1720
|
+
sanitized.push(file);
|
|
1721
|
+
}
|
|
1722
|
+
return sanitized;
|
|
1677
1723
|
}
|
|
@@ -79,6 +79,14 @@ export declare function gitGetAllPR(git: SimpleGit, options?: {
|
|
|
79
79
|
*/
|
|
80
80
|
export declare function gitGetClosedPRs(git: SimpleGit, gitConfig: RulesGitConfig): Promise<PRInfo[]>;
|
|
81
81
|
export declare function gitLoadPR(git: SimpleGit, branch: string): Promise<PRInfo>;
|
|
82
|
+
/**
|
|
83
|
+
* Remplace explicitement un nom de fichier dans les métadonnées d'une PR.
|
|
84
|
+
* Utilisé pour refléter immédiatement les renames sans dépendre de git diff.
|
|
85
|
+
*/
|
|
86
|
+
export declare function gitPRReplaceFile(git: SimpleGit, branch: string, options?: {
|
|
87
|
+
remove?: string;
|
|
88
|
+
add?: string;
|
|
89
|
+
}, config?: RulesGitConfig): Promise<void>;
|
|
82
90
|
export declare function gitPRUpdateComments(git: SimpleGit, branch: string, details: RulePullRequestDetails, config?: RulesGitConfig): Promise<PRInfo>;
|
|
83
91
|
/**
|
|
84
92
|
* DEPRECATED: use gitClosePRRobust instead
|
|
@@ -40,6 +40,7 @@ exports.gitGetPRMetadata = gitGetPRMetadata;
|
|
|
40
40
|
exports.gitGetAllPR = gitGetAllPR;
|
|
41
41
|
exports.gitGetClosedPRs = gitGetClosedPRs;
|
|
42
42
|
exports.gitLoadPR = gitLoadPR;
|
|
43
|
+
exports.gitPRReplaceFile = gitPRReplaceFile;
|
|
43
44
|
exports.gitPRUpdateComments = gitPRUpdateComments;
|
|
44
45
|
exports.gitClosePR = gitClosePR;
|
|
45
46
|
exports.gitClosePRRobust = gitClosePRRobust;
|
|
@@ -321,7 +322,7 @@ async function gitLoadPR(git, branch) {
|
|
|
321
322
|
if (!metadata) {
|
|
322
323
|
throw new errors_1.GitOperationError(`PR not found for branch ${branch}`, 'pr_load', { branch });
|
|
323
324
|
}
|
|
324
|
-
const files = await (0, repo_tools_1.gitGetDiffFiles)(git, branch, metadata?.mergeBase, '.md');
|
|
325
|
+
const files = metadata.files || (await (0, repo_tools_1.gitGetDiffFiles)(git, branch, metadata?.mergeBase, '.md'));
|
|
325
326
|
// Récupérer les infos du dernier commit de la branche
|
|
326
327
|
const log = await git.log({ from: branch, to: branch, maxCount: 1 });
|
|
327
328
|
const lastCommit = log.latest;
|
|
@@ -341,6 +342,49 @@ async function gitLoadPR(git, branch) {
|
|
|
341
342
|
throw new errors_1.GitOperationError(`Failed to retrieve PR: ${error}`, 'pr_load');
|
|
342
343
|
}
|
|
343
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Remplace explicitement un nom de fichier dans les métadonnées d'une PR.
|
|
347
|
+
* Utilisé pour refléter immédiatement les renames sans dépendre de git diff.
|
|
348
|
+
*/
|
|
349
|
+
async function gitPRReplaceFile(git, branch, options = {}, config) {
|
|
350
|
+
const { remove, add } = options;
|
|
351
|
+
if (!remove && !add) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
355
|
+
const metadata = await gitGetPRMetadata(git, branch, config);
|
|
356
|
+
if (!metadata) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const currentFiles = Array.isArray(metadata.files) ? metadata.files : [];
|
|
360
|
+
const seen = new Set();
|
|
361
|
+
const nextFiles = [];
|
|
362
|
+
for (const file of currentFiles) {
|
|
363
|
+
if (!file) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (remove && file === remove) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (seen.has(file)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
seen.add(file);
|
|
373
|
+
nextFiles.push(file);
|
|
374
|
+
}
|
|
375
|
+
if (add && !seen.has(add)) {
|
|
376
|
+
nextFiles.push(add);
|
|
377
|
+
seen.add(add);
|
|
378
|
+
}
|
|
379
|
+
const changed = currentFiles.length !== nextFiles.length ||
|
|
380
|
+
currentFiles.some((file, idx) => file !== nextFiles[idx]);
|
|
381
|
+
if (!changed) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
metadata.files = nextFiles;
|
|
385
|
+
const headCommitHash = (await git.revparse([branch])).trim();
|
|
386
|
+
await (0, repo_tools_1.gitWriteNote)(git, headCommitHash, metadata, gitConf.gitNotes.namespace);
|
|
387
|
+
}
|
|
344
388
|
async function gitPRUpdateComments(git, branch, details, config) {
|
|
345
389
|
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
346
390
|
const metadata = await gitGetPRMetadata(git, branch, config);
|