@gitgov/core 1.13.0 → 2.0.0
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 +151 -270
- package/dist/src/agent_runner-COAjsdtr.d.ts +2585 -0
- package/dist/src/fs.d.ts +1375 -0
- package/dist/src/fs.js +8483 -0
- package/dist/src/fs.js.map +1 -0
- package/dist/src/index-DMkBFK4C.d.ts +807 -0
- package/dist/src/index.d.ts +1709 -5212
- package/dist/src/index.js +4779 -8956
- package/dist/src/index.js.map +1 -1
- package/dist/src/memory.d.ts +267 -0
- package/dist/src/memory.js +790 -0
- package/dist/src/memory.js.map +1 -0
- package/dist/src/memory_file_lister-D0llxocS.d.ts +221 -0
- package/package.json +19 -8
- package/prompts/gitgov_agent_prompt.md +0 -480
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import picomatch from 'picomatch';
|
|
2
|
+
|
|
3
|
+
// src/record_store/memory/memory_record_store.ts
|
|
4
|
+
var MemoryRecordStore = class {
|
|
5
|
+
data;
|
|
6
|
+
deepClone;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.data = options.initial ?? /* @__PURE__ */ new Map();
|
|
9
|
+
this.deepClone = options.deepClone ?? true;
|
|
10
|
+
}
|
|
11
|
+
clone(value) {
|
|
12
|
+
if (!this.deepClone) return value;
|
|
13
|
+
return JSON.parse(JSON.stringify(value));
|
|
14
|
+
}
|
|
15
|
+
async get(id) {
|
|
16
|
+
const value = this.data.get(id);
|
|
17
|
+
return value !== void 0 ? this.clone(value) : null;
|
|
18
|
+
}
|
|
19
|
+
async put(id, value) {
|
|
20
|
+
this.data.set(id, this.clone(value));
|
|
21
|
+
}
|
|
22
|
+
async delete(id) {
|
|
23
|
+
this.data.delete(id);
|
|
24
|
+
}
|
|
25
|
+
async list() {
|
|
26
|
+
return Array.from(this.data.keys());
|
|
27
|
+
}
|
|
28
|
+
async exists(id) {
|
|
29
|
+
return this.data.has(id);
|
|
30
|
+
}
|
|
31
|
+
// ─────────────────────────────────────────────────────────
|
|
32
|
+
// Test Helpers (not part of RecordStore<T>, only for tests)
|
|
33
|
+
// ─────────────────────────────────────────────────────────
|
|
34
|
+
/** Clears all records from the store */
|
|
35
|
+
clear() {
|
|
36
|
+
this.data.clear();
|
|
37
|
+
}
|
|
38
|
+
/** Returns the number of records */
|
|
39
|
+
size() {
|
|
40
|
+
return this.data.size;
|
|
41
|
+
}
|
|
42
|
+
/** Returns a copy of the internal Map (for assertions) */
|
|
43
|
+
getAll() {
|
|
44
|
+
return new Map(this.data);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/config_store/memory/memory_config_store.ts
|
|
49
|
+
var MemoryConfigStore = class {
|
|
50
|
+
config = null;
|
|
51
|
+
/**
|
|
52
|
+
* Load configuration from memory
|
|
53
|
+
*
|
|
54
|
+
* [EARS-A1] Returns null if no config set
|
|
55
|
+
* [EARS-A2] Returns config set via setConfig
|
|
56
|
+
* [EARS-A3] Returns config saved via saveConfig
|
|
57
|
+
*
|
|
58
|
+
* @returns GitGovConfig or null if not set
|
|
59
|
+
*/
|
|
60
|
+
async loadConfig() {
|
|
61
|
+
return this.config;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Save configuration to memory
|
|
65
|
+
*
|
|
66
|
+
* [EARS-A4] Persists config in memory, accessible via getConfig()
|
|
67
|
+
*/
|
|
68
|
+
async saveConfig(config) {
|
|
69
|
+
this.config = config;
|
|
70
|
+
}
|
|
71
|
+
// ==================== Test Helper Methods ====================
|
|
72
|
+
/**
|
|
73
|
+
* Set configuration directly (for test setup)
|
|
74
|
+
*
|
|
75
|
+
* [EARS-B1] Sets config synchronously, available via getConfig()
|
|
76
|
+
* [EARS-B2] Accepts null to clear config
|
|
77
|
+
*/
|
|
78
|
+
setConfig(config) {
|
|
79
|
+
this.config = config;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get current configuration (for test assertions)
|
|
83
|
+
*/
|
|
84
|
+
getConfig() {
|
|
85
|
+
return this.config;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clear all stored data (for test cleanup)
|
|
89
|
+
*
|
|
90
|
+
* [EARS-B3] Resets store to initial state (config = null)
|
|
91
|
+
*/
|
|
92
|
+
clear() {
|
|
93
|
+
this.config = null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/session_store/memory/memory_session_store.ts
|
|
98
|
+
var MemorySessionStore = class {
|
|
99
|
+
session = null;
|
|
100
|
+
keyFiles = [];
|
|
101
|
+
/**
|
|
102
|
+
* Load session from memory
|
|
103
|
+
* @returns GitGovSession or null if not set
|
|
104
|
+
*/
|
|
105
|
+
async loadSession() {
|
|
106
|
+
return this.session;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Save session to memory
|
|
110
|
+
*/
|
|
111
|
+
async saveSession(session) {
|
|
112
|
+
this.session = session;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Detect actor from simulated .key files
|
|
116
|
+
*
|
|
117
|
+
* In MemorySessionStore, .key files are simulated via setKeyFiles().
|
|
118
|
+
*
|
|
119
|
+
* @returns Actor ID or null if no key files configured
|
|
120
|
+
*/
|
|
121
|
+
async detectActorFromKeyFiles() {
|
|
122
|
+
const firstKeyFile = this.keyFiles[0];
|
|
123
|
+
if (!firstKeyFile) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return firstKeyFile.replace(".key", "");
|
|
127
|
+
}
|
|
128
|
+
// ==================== Test Helper Methods ====================
|
|
129
|
+
/**
|
|
130
|
+
* Set session directly (for test setup)
|
|
131
|
+
*/
|
|
132
|
+
setSession(session) {
|
|
133
|
+
this.session = session;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get current session (for test assertions)
|
|
137
|
+
*/
|
|
138
|
+
getSession() {
|
|
139
|
+
return this.session;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Set simulated .key files (for EARS-B9 testing)
|
|
143
|
+
* @param keyFiles - Array of key filenames (e.g., ["human:camilo.key"])
|
|
144
|
+
*/
|
|
145
|
+
setKeyFiles(keyFiles) {
|
|
146
|
+
this.keyFiles = keyFiles;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Clear all stored data (for test cleanup)
|
|
150
|
+
*/
|
|
151
|
+
clear() {
|
|
152
|
+
this.session = null;
|
|
153
|
+
this.keyFiles = [];
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/key_provider/key_provider.ts
|
|
158
|
+
var KeyProviderError = class extends Error {
|
|
159
|
+
constructor(message, code, actorId) {
|
|
160
|
+
super(message);
|
|
161
|
+
this.code = code;
|
|
162
|
+
this.actorId = actorId;
|
|
163
|
+
this.name = "KeyProviderError";
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/key_provider/memory/env_key_provider.ts
|
|
168
|
+
var EnvKeyProvider = class {
|
|
169
|
+
prefix;
|
|
170
|
+
env;
|
|
171
|
+
allowWrites;
|
|
172
|
+
constructor(options = {}) {
|
|
173
|
+
this.prefix = options.prefix ?? "GITGOV_KEY_";
|
|
174
|
+
this.env = options.env ?? process.env;
|
|
175
|
+
this.allowWrites = options.allowWrites ?? options.env !== void 0;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* [EARS-KP01] Retrieves the private key from environment variable.
|
|
179
|
+
* [EARS-EKP01] Reads from {prefix}{SANITIZED_ACTOR_ID}.
|
|
180
|
+
* [EARS-EKP07] Returns null for empty or whitespace-only value.
|
|
181
|
+
* [EARS-EKP08] Trims whitespace from value.
|
|
182
|
+
*/
|
|
183
|
+
async getPrivateKey(actorId) {
|
|
184
|
+
const varName = this.getEnvVarName(actorId);
|
|
185
|
+
const value = this.env[varName];
|
|
186
|
+
if (!value || value.trim() === "") {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return value.trim();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* [EARS-KP03] Stores a private key in the environment object.
|
|
193
|
+
* [EARS-EKP02] Sets env var in custom env object.
|
|
194
|
+
* [EARS-EKP03] Throws KEY_WRITE_ERROR when writing to process.env.
|
|
195
|
+
*/
|
|
196
|
+
async setPrivateKey(actorId, privateKey) {
|
|
197
|
+
if (!this.allowWrites) {
|
|
198
|
+
throw new KeyProviderError(
|
|
199
|
+
"Cannot write to environment variables in read-only mode. Use a custom env object with allowWrites: true for writable storage.",
|
|
200
|
+
"KEY_WRITE_ERROR",
|
|
201
|
+
actorId
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const varName = this.getEnvVarName(actorId);
|
|
205
|
+
this.env[varName] = privateKey;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Checks if a private key exists in environment variables.
|
|
209
|
+
*/
|
|
210
|
+
async hasPrivateKey(actorId) {
|
|
211
|
+
const varName = this.getEnvVarName(actorId);
|
|
212
|
+
const value = this.env[varName];
|
|
213
|
+
return value !== void 0 && value.trim() !== "";
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* [EARS-KP04] Deletes the private key from environment object.
|
|
217
|
+
* [EARS-EKP10] Throws KEY_DELETE_ERROR in read-only mode.
|
|
218
|
+
*/
|
|
219
|
+
async deletePrivateKey(actorId) {
|
|
220
|
+
if (!this.allowWrites) {
|
|
221
|
+
throw new KeyProviderError(
|
|
222
|
+
"Cannot delete environment variables in read-only mode.",
|
|
223
|
+
"KEY_DELETE_ERROR",
|
|
224
|
+
actorId
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const varName = this.getEnvVarName(actorId);
|
|
228
|
+
const existed = this.env[varName] !== void 0;
|
|
229
|
+
delete this.env[varName];
|
|
230
|
+
return existed;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* [EARS-EKP04] Builds environment variable name from actorId.
|
|
234
|
+
* [EARS-EKP05] Converts to UPPERCASE.
|
|
235
|
+
* [EARS-EKP06] Collapses multiple underscores.
|
|
236
|
+
* [EARS-EKP11] Throws INVALID_ACTOR_ID if empty after sanitization.
|
|
237
|
+
*/
|
|
238
|
+
getEnvVarName(actorId) {
|
|
239
|
+
const sanitized = actorId.toUpperCase().replace(/[^A-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
240
|
+
if (!sanitized) {
|
|
241
|
+
throw new KeyProviderError(
|
|
242
|
+
"Invalid actorId: empty after sanitization",
|
|
243
|
+
"INVALID_ACTOR_ID",
|
|
244
|
+
actorId
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return `${this.prefix}${sanitized}`;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// src/key_provider/memory/mock_key_provider.ts
|
|
252
|
+
var MockKeyProvider = class {
|
|
253
|
+
keys;
|
|
254
|
+
constructor(options = {}) {
|
|
255
|
+
if (options.keys instanceof Map) {
|
|
256
|
+
this.keys = new Map(options.keys);
|
|
257
|
+
} else if (options.keys) {
|
|
258
|
+
this.keys = new Map(Object.entries(options.keys));
|
|
259
|
+
} else {
|
|
260
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* [EARS-KP01] Retrieves the private key for an actor.
|
|
265
|
+
*/
|
|
266
|
+
async getPrivateKey(actorId) {
|
|
267
|
+
return this.keys.get(actorId) ?? null;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* [EARS-KP03] Stores a private key for an actor.
|
|
271
|
+
* [EARS-MKP03] Overwrites existing key if present.
|
|
272
|
+
*/
|
|
273
|
+
async setPrivateKey(actorId, privateKey) {
|
|
274
|
+
this.keys.set(actorId, privateKey);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* [EARS-MKP07] Checks if a private key exists for an actor.
|
|
278
|
+
*/
|
|
279
|
+
async hasPrivateKey(actorId) {
|
|
280
|
+
return this.keys.has(actorId);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* [EARS-KP04] Deletes the private key for an actor.
|
|
284
|
+
*/
|
|
285
|
+
async deletePrivateKey(actorId) {
|
|
286
|
+
return this.keys.delete(actorId);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* [EARS-MKP04] Returns the number of stored keys (useful for testing).
|
|
290
|
+
*/
|
|
291
|
+
size() {
|
|
292
|
+
return this.keys.size;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* [EARS-MKP05] Clears all stored keys (useful for test cleanup).
|
|
296
|
+
*/
|
|
297
|
+
clear() {
|
|
298
|
+
this.keys.clear();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* [EARS-MKP06] Returns all stored actor IDs (useful for testing).
|
|
302
|
+
*/
|
|
303
|
+
listActorIds() {
|
|
304
|
+
return Array.from(this.keys.keys());
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/file_lister/file_lister.errors.ts
|
|
309
|
+
var FileListerError = class extends Error {
|
|
310
|
+
constructor(message, code, filePath) {
|
|
311
|
+
super(message);
|
|
312
|
+
this.code = code;
|
|
313
|
+
this.filePath = filePath;
|
|
314
|
+
this.name = "FileListerError";
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/file_lister/memory/memory_file_lister.ts
|
|
319
|
+
function matchPatterns(patterns, filePaths) {
|
|
320
|
+
const isMatch = picomatch(patterns);
|
|
321
|
+
return filePaths.filter((filePath) => isMatch(filePath));
|
|
322
|
+
}
|
|
323
|
+
function filterIgnored(filePaths, ignorePatterns) {
|
|
324
|
+
if (!ignorePatterns.length) return filePaths;
|
|
325
|
+
const isIgnored = picomatch(ignorePatterns);
|
|
326
|
+
return filePaths.filter((filePath) => !isIgnored(filePath));
|
|
327
|
+
}
|
|
328
|
+
var MemoryFileLister = class {
|
|
329
|
+
files;
|
|
330
|
+
stats;
|
|
331
|
+
/**
|
|
332
|
+
* [EARS-MFL01] Constructs MemoryFileLister with provided files.
|
|
333
|
+
*/
|
|
334
|
+
constructor(options = {}) {
|
|
335
|
+
if (options.files instanceof Map) {
|
|
336
|
+
this.files = new Map(options.files);
|
|
337
|
+
} else if (options.files) {
|
|
338
|
+
this.files = new Map(Object.entries(options.files));
|
|
339
|
+
} else {
|
|
340
|
+
this.files = /* @__PURE__ */ new Map();
|
|
341
|
+
}
|
|
342
|
+
this.stats = options.stats ?? /* @__PURE__ */ new Map();
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* [EARS-FL01] Lists files matching glob patterns.
|
|
346
|
+
* [EARS-MFL02] Filters files using glob patterns.
|
|
347
|
+
*/
|
|
348
|
+
async list(patterns, options) {
|
|
349
|
+
const allPaths = Array.from(this.files.keys());
|
|
350
|
+
let matched = matchPatterns(patterns, allPaths);
|
|
351
|
+
if (options?.ignore?.length) {
|
|
352
|
+
matched = filterIgnored(matched, options.ignore);
|
|
353
|
+
}
|
|
354
|
+
return matched.sort();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* [EARS-FL02] Checks if a file exists.
|
|
358
|
+
*/
|
|
359
|
+
async exists(filePath) {
|
|
360
|
+
return this.files.has(filePath);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* [EARS-FL03] Reads file content as string.
|
|
364
|
+
*/
|
|
365
|
+
async read(filePath) {
|
|
366
|
+
const content = this.files.get(filePath);
|
|
367
|
+
if (content === void 0) {
|
|
368
|
+
throw new FileListerError(
|
|
369
|
+
`File not found: ${filePath}`,
|
|
370
|
+
"FILE_NOT_FOUND",
|
|
371
|
+
filePath
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return content;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* [EARS-FL04] Gets file statistics.
|
|
378
|
+
* [EARS-MFL03] Generates stats from content if not explicitly provided.
|
|
379
|
+
*/
|
|
380
|
+
async stat(filePath) {
|
|
381
|
+
if (!this.files.has(filePath)) {
|
|
382
|
+
throw new FileListerError(
|
|
383
|
+
`File not found: ${filePath}`,
|
|
384
|
+
"FILE_NOT_FOUND",
|
|
385
|
+
filePath
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const explicitStats = this.stats.get(filePath);
|
|
389
|
+
if (explicitStats) {
|
|
390
|
+
return explicitStats;
|
|
391
|
+
}
|
|
392
|
+
const content = this.files.get(filePath);
|
|
393
|
+
return {
|
|
394
|
+
size: content.length,
|
|
395
|
+
mtime: Date.now(),
|
|
396
|
+
isFile: true
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
// ============================================
|
|
400
|
+
// Testing utilities
|
|
401
|
+
// ============================================
|
|
402
|
+
/**
|
|
403
|
+
* [EARS-MFL04] Adds a file to the mock filesystem.
|
|
404
|
+
*/
|
|
405
|
+
addFile(filePath, content) {
|
|
406
|
+
this.files.set(filePath, content);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Removes a file from the mock filesystem.
|
|
410
|
+
*/
|
|
411
|
+
removeFile(filePath) {
|
|
412
|
+
this.stats.delete(filePath);
|
|
413
|
+
return this.files.delete(filePath);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Returns the number of files.
|
|
417
|
+
*/
|
|
418
|
+
size() {
|
|
419
|
+
return this.files.size;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Clears all files.
|
|
423
|
+
*/
|
|
424
|
+
clear() {
|
|
425
|
+
this.files.clear();
|
|
426
|
+
this.stats.clear();
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Returns all file paths.
|
|
430
|
+
*/
|
|
431
|
+
listPaths() {
|
|
432
|
+
return Array.from(this.files.keys());
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/git/errors.ts
|
|
437
|
+
var GitError = class _GitError extends Error {
|
|
438
|
+
constructor(message) {
|
|
439
|
+
super(message);
|
|
440
|
+
this.name = "GitError";
|
|
441
|
+
Object.setPrototypeOf(this, _GitError.prototype);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
var BranchNotFoundError = class _BranchNotFoundError extends GitError {
|
|
445
|
+
branchName;
|
|
446
|
+
constructor(branchName) {
|
|
447
|
+
super(`Branch not found: ${branchName}`);
|
|
448
|
+
this.name = "BranchNotFoundError";
|
|
449
|
+
this.branchName = branchName;
|
|
450
|
+
Object.setPrototypeOf(this, _BranchNotFoundError.prototype);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var FileNotFoundError = class _FileNotFoundError extends GitError {
|
|
454
|
+
filePath;
|
|
455
|
+
commitHash;
|
|
456
|
+
constructor(filePath, commitHash) {
|
|
457
|
+
super(`File not found: ${filePath} in commit ${commitHash}`);
|
|
458
|
+
this.name = "FileNotFoundError";
|
|
459
|
+
this.filePath = filePath;
|
|
460
|
+
this.commitHash = commitHash;
|
|
461
|
+
Object.setPrototypeOf(this, _FileNotFoundError.prototype);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
var RebaseNotInProgressError = class _RebaseNotInProgressError extends GitError {
|
|
465
|
+
constructor() {
|
|
466
|
+
super("No rebase in progress");
|
|
467
|
+
this.name = "RebaseNotInProgressError";
|
|
468
|
+
Object.setPrototypeOf(this, _RebaseNotInProgressError.prototype);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var BranchAlreadyExistsError = class _BranchAlreadyExistsError extends GitError {
|
|
472
|
+
branchName;
|
|
473
|
+
constructor(branchName) {
|
|
474
|
+
super(`Branch already exists: ${branchName}`);
|
|
475
|
+
this.name = "BranchAlreadyExistsError";
|
|
476
|
+
this.branchName = branchName;
|
|
477
|
+
Object.setPrototypeOf(this, _BranchAlreadyExistsError.prototype);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/git/memory/memory_git_module.ts
|
|
482
|
+
var MemoryGitModule = class {
|
|
483
|
+
state;
|
|
484
|
+
constructor(repoRoot = "/test/repo") {
|
|
485
|
+
this.state = {
|
|
486
|
+
repoRoot,
|
|
487
|
+
currentBranch: "main",
|
|
488
|
+
branches: /* @__PURE__ */ new Set(["main"]),
|
|
489
|
+
commits: [],
|
|
490
|
+
stagedFiles: [],
|
|
491
|
+
files: /* @__PURE__ */ new Map(),
|
|
492
|
+
isRebaseInProgress: false,
|
|
493
|
+
conflictedFiles: [],
|
|
494
|
+
remotes: /* @__PURE__ */ new Map([["origin", ["main"]]]),
|
|
495
|
+
stashes: [],
|
|
496
|
+
config: /* @__PURE__ */ new Map()
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
500
|
+
// TEST HELPERS
|
|
501
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
502
|
+
setBranch(name) {
|
|
503
|
+
this.state.currentBranch = name;
|
|
504
|
+
this.state.branches.add(name);
|
|
505
|
+
}
|
|
506
|
+
setBranches(names) {
|
|
507
|
+
this.state.branches = new Set(names);
|
|
508
|
+
if (!this.state.branches.has(this.state.currentBranch)) {
|
|
509
|
+
this.state.currentBranch = names[0] || "main";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
setCommits(commits) {
|
|
513
|
+
this.state.commits = commits.map((c) => ({
|
|
514
|
+
...c,
|
|
515
|
+
files: /* @__PURE__ */ new Map(),
|
|
516
|
+
branch: this.state.currentBranch
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
setFiles(files) {
|
|
520
|
+
this.state.files = new Map(Object.entries(files));
|
|
521
|
+
}
|
|
522
|
+
setFileContent(commitHash, filePath, content) {
|
|
523
|
+
const commit = this.state.commits.find((c) => c.hash === commitHash);
|
|
524
|
+
if (commit) {
|
|
525
|
+
commit.files.set(filePath, content);
|
|
526
|
+
}
|
|
527
|
+
this.state.files.set(filePath, content);
|
|
528
|
+
}
|
|
529
|
+
setStagedFiles(files) {
|
|
530
|
+
this.state.stagedFiles = files;
|
|
531
|
+
}
|
|
532
|
+
setRebaseInProgress(inProgress, conflictedFiles = []) {
|
|
533
|
+
this.state.isRebaseInProgress = inProgress;
|
|
534
|
+
this.state.conflictedFiles = conflictedFiles;
|
|
535
|
+
}
|
|
536
|
+
setRemoteBranches(remote, branches) {
|
|
537
|
+
this.state.remotes.set(remote, branches);
|
|
538
|
+
}
|
|
539
|
+
clear() {
|
|
540
|
+
this.state = {
|
|
541
|
+
repoRoot: this.state.repoRoot,
|
|
542
|
+
currentBranch: "main",
|
|
543
|
+
branches: /* @__PURE__ */ new Set(["main"]),
|
|
544
|
+
commits: [],
|
|
545
|
+
stagedFiles: [],
|
|
546
|
+
files: /* @__PURE__ */ new Map(),
|
|
547
|
+
isRebaseInProgress: false,
|
|
548
|
+
conflictedFiles: [],
|
|
549
|
+
remotes: /* @__PURE__ */ new Map([["origin", ["main"]]]),
|
|
550
|
+
stashes: [],
|
|
551
|
+
config: /* @__PURE__ */ new Map()
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
555
|
+
// IGitModule IMPLEMENTATION
|
|
556
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
557
|
+
async exec(_command, _args, _options) {
|
|
558
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
559
|
+
}
|
|
560
|
+
async init() {
|
|
561
|
+
this.state.branches.add("main");
|
|
562
|
+
this.state.currentBranch = "main";
|
|
563
|
+
}
|
|
564
|
+
async getRepoRoot() {
|
|
565
|
+
return this.state.repoRoot;
|
|
566
|
+
}
|
|
567
|
+
async getCurrentBranch() {
|
|
568
|
+
return this.state.currentBranch;
|
|
569
|
+
}
|
|
570
|
+
async getCommitHash(ref = "HEAD") {
|
|
571
|
+
if (ref === "HEAD") {
|
|
572
|
+
const lastCommit = this.state.commits[this.state.commits.length - 1];
|
|
573
|
+
return lastCommit?.hash || "abc123def456";
|
|
574
|
+
}
|
|
575
|
+
const commit = this.state.commits.find((c) => c.hash.startsWith(ref));
|
|
576
|
+
return commit?.hash || ref;
|
|
577
|
+
}
|
|
578
|
+
async setConfig(key, value, _scope) {
|
|
579
|
+
this.state.config.set(key, value);
|
|
580
|
+
}
|
|
581
|
+
async getMergeBase(branchA, branchB) {
|
|
582
|
+
if (!this.state.branches.has(branchA)) {
|
|
583
|
+
throw new BranchNotFoundError(branchA);
|
|
584
|
+
}
|
|
585
|
+
if (!this.state.branches.has(branchB)) {
|
|
586
|
+
throw new BranchNotFoundError(branchB);
|
|
587
|
+
}
|
|
588
|
+
return this.state.commits[0]?.hash || "merge-base-hash";
|
|
589
|
+
}
|
|
590
|
+
async getChangedFiles(_fromCommit, _toCommit, _pathFilter) {
|
|
591
|
+
return [];
|
|
592
|
+
}
|
|
593
|
+
async getStagedFiles() {
|
|
594
|
+
return this.state.stagedFiles;
|
|
595
|
+
}
|
|
596
|
+
async getFileContent(commitHash, filePath) {
|
|
597
|
+
const commit = this.state.commits.find((c) => c.hash === commitHash);
|
|
598
|
+
if (commit?.files.has(filePath)) {
|
|
599
|
+
return commit.files.get(filePath);
|
|
600
|
+
}
|
|
601
|
+
if (this.state.files.has(filePath)) {
|
|
602
|
+
return this.state.files.get(filePath);
|
|
603
|
+
}
|
|
604
|
+
throw new FileNotFoundError(filePath, commitHash);
|
|
605
|
+
}
|
|
606
|
+
async getCommitHistory(_branch, options) {
|
|
607
|
+
let commits = [...this.state.commits];
|
|
608
|
+
if (options?.maxCount) {
|
|
609
|
+
commits = commits.slice(0, options.maxCount);
|
|
610
|
+
}
|
|
611
|
+
return commits.map((c) => ({
|
|
612
|
+
hash: c.hash,
|
|
613
|
+
message: c.message,
|
|
614
|
+
author: c.author,
|
|
615
|
+
date: c.date
|
|
616
|
+
}));
|
|
617
|
+
}
|
|
618
|
+
async getCommitHistoryRange(fromHash, toHash, options) {
|
|
619
|
+
const fromIndex = this.state.commits.findIndex((c) => c.hash === fromHash);
|
|
620
|
+
const toIndex = this.state.commits.findIndex((c) => c.hash === toHash);
|
|
621
|
+
let commits = fromIndex >= 0 && toIndex >= 0 ? this.state.commits.slice(fromIndex + 1, toIndex + 1) : [];
|
|
622
|
+
if (options?.maxCount) {
|
|
623
|
+
commits = commits.slice(0, options.maxCount);
|
|
624
|
+
}
|
|
625
|
+
return commits.map((c) => ({
|
|
626
|
+
hash: c.hash,
|
|
627
|
+
message: c.message,
|
|
628
|
+
author: c.author,
|
|
629
|
+
date: c.date
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
632
|
+
async getCommitMessage(commitHash) {
|
|
633
|
+
const commit = this.state.commits.find((c) => c.hash === commitHash);
|
|
634
|
+
return commit?.message || "";
|
|
635
|
+
}
|
|
636
|
+
async hasUncommittedChanges(_pathFilter) {
|
|
637
|
+
return this.state.stagedFiles.length > 0;
|
|
638
|
+
}
|
|
639
|
+
async isRebaseInProgress() {
|
|
640
|
+
return this.state.isRebaseInProgress;
|
|
641
|
+
}
|
|
642
|
+
async branchExists(branchName) {
|
|
643
|
+
return this.state.branches.has(branchName);
|
|
644
|
+
}
|
|
645
|
+
async listRemoteBranches(remoteName) {
|
|
646
|
+
return this.state.remotes.get(remoteName) || [];
|
|
647
|
+
}
|
|
648
|
+
async isRemoteConfigured(remoteName) {
|
|
649
|
+
return this.state.remotes.has(remoteName);
|
|
650
|
+
}
|
|
651
|
+
async getBranchRemote(branchName) {
|
|
652
|
+
if (!this.state.branches.has(branchName)) {
|
|
653
|
+
throw new BranchNotFoundError(branchName);
|
|
654
|
+
}
|
|
655
|
+
for (const [remote, branches] of this.state.remotes) {
|
|
656
|
+
if (branches.includes(branchName)) {
|
|
657
|
+
return remote;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
async getConflictedFiles() {
|
|
663
|
+
return this.state.conflictedFiles;
|
|
664
|
+
}
|
|
665
|
+
async checkoutBranch(branchName) {
|
|
666
|
+
if (!this.state.branches.has(branchName)) {
|
|
667
|
+
throw new BranchNotFoundError(branchName);
|
|
668
|
+
}
|
|
669
|
+
this.state.currentBranch = branchName;
|
|
670
|
+
}
|
|
671
|
+
async stash(message) {
|
|
672
|
+
if (this.state.stagedFiles.length === 0) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
const hash = `stash-${Date.now()}`;
|
|
676
|
+
this.state.stashes.push({
|
|
677
|
+
hash,
|
|
678
|
+
message: message || "WIP",
|
|
679
|
+
files: new Map(this.state.files)
|
|
680
|
+
});
|
|
681
|
+
this.state.stagedFiles = [];
|
|
682
|
+
return hash;
|
|
683
|
+
}
|
|
684
|
+
async stashPop() {
|
|
685
|
+
const stash = this.state.stashes.pop();
|
|
686
|
+
if (!stash) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
for (const [path, content] of stash.files) {
|
|
690
|
+
this.state.files.set(path, content);
|
|
691
|
+
}
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
async stashDrop(_stashHash) {
|
|
695
|
+
this.state.stashes.pop();
|
|
696
|
+
}
|
|
697
|
+
async checkoutOrphanBranch(branchName) {
|
|
698
|
+
this.state.branches.add(branchName);
|
|
699
|
+
this.state.currentBranch = branchName;
|
|
700
|
+
}
|
|
701
|
+
async fetch(_remote) {
|
|
702
|
+
}
|
|
703
|
+
async pull(_remote, _branchName) {
|
|
704
|
+
}
|
|
705
|
+
async pullRebase(_remote, _branchName) {
|
|
706
|
+
}
|
|
707
|
+
async resetHard(_target) {
|
|
708
|
+
this.state.stagedFiles = [];
|
|
709
|
+
}
|
|
710
|
+
async checkoutFilesFromBranch(sourceBranch, _filePaths) {
|
|
711
|
+
if (!this.state.branches.has(sourceBranch)) {
|
|
712
|
+
throw new BranchNotFoundError(sourceBranch);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async add(filePaths, _options) {
|
|
716
|
+
for (const path of filePaths) {
|
|
717
|
+
if (!this.state.stagedFiles.includes(path)) {
|
|
718
|
+
this.state.stagedFiles.push(path);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async rm(filePaths) {
|
|
723
|
+
for (const path of filePaths) {
|
|
724
|
+
this.state.files.delete(path);
|
|
725
|
+
this.state.stagedFiles = this.state.stagedFiles.filter((f) => f !== path);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async commit(message, author) {
|
|
729
|
+
const hash = `commit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
730
|
+
this.state.commits.push({
|
|
731
|
+
hash,
|
|
732
|
+
message,
|
|
733
|
+
author: author ? `${author.name} <${author.email}>` : "Test User <test@example.com>",
|
|
734
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
735
|
+
files: new Map(this.state.files),
|
|
736
|
+
branch: this.state.currentBranch
|
|
737
|
+
});
|
|
738
|
+
this.state.stagedFiles = [];
|
|
739
|
+
return hash;
|
|
740
|
+
}
|
|
741
|
+
async commitAllowEmpty(message, author) {
|
|
742
|
+
return this.commit(message, author);
|
|
743
|
+
}
|
|
744
|
+
async push(_remote, branchName) {
|
|
745
|
+
const branches = this.state.remotes.get("origin") || [];
|
|
746
|
+
if (!branches.includes(branchName)) {
|
|
747
|
+
branches.push(branchName);
|
|
748
|
+
this.state.remotes.set("origin", branches);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async pushWithUpstream(_remote, branchName) {
|
|
752
|
+
return this.push("origin", branchName);
|
|
753
|
+
}
|
|
754
|
+
async setUpstream(branchName, _remote, _remoteBranch) {
|
|
755
|
+
if (!this.state.branches.has(branchName)) {
|
|
756
|
+
throw new BranchNotFoundError(branchName);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async rebaseContinue() {
|
|
760
|
+
if (!this.state.isRebaseInProgress) {
|
|
761
|
+
throw new RebaseNotInProgressError();
|
|
762
|
+
}
|
|
763
|
+
this.state.isRebaseInProgress = false;
|
|
764
|
+
this.state.conflictedFiles = [];
|
|
765
|
+
return this.state.commits[this.state.commits.length - 1]?.hash || "rebase-hash";
|
|
766
|
+
}
|
|
767
|
+
async rebaseAbort() {
|
|
768
|
+
if (!this.state.isRebaseInProgress) {
|
|
769
|
+
throw new RebaseNotInProgressError();
|
|
770
|
+
}
|
|
771
|
+
this.state.isRebaseInProgress = false;
|
|
772
|
+
this.state.conflictedFiles = [];
|
|
773
|
+
}
|
|
774
|
+
async createBranch(branchName, _startPoint) {
|
|
775
|
+
if (this.state.branches.has(branchName)) {
|
|
776
|
+
throw new BranchAlreadyExistsError(branchName);
|
|
777
|
+
}
|
|
778
|
+
this.state.branches.add(branchName);
|
|
779
|
+
this.state.currentBranch = branchName;
|
|
780
|
+
}
|
|
781
|
+
async rebase(_targetBranch) {
|
|
782
|
+
if (this.state.conflictedFiles.length > 0) {
|
|
783
|
+
this.state.isRebaseInProgress = true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
export { EnvKeyProvider, MemoryConfigStore, MemoryFileLister, MemoryGitModule, MemoryRecordStore, MemorySessionStore, MockKeyProvider };
|
|
789
|
+
//# sourceMappingURL=memory.js.map
|
|
790
|
+
//# sourceMappingURL=memory.js.map
|