@gitgov/core 2.5.0 → 2.7.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/LICENSE +374 -0
- package/README.md +2 -1
- package/dist/src/agent_runner-CHDkBfPZ.d.ts +610 -0
- package/dist/src/fs.d.ts +9 -25
- package/dist/src/fs.js +3 -0
- package/dist/src/fs.js.map +1 -1
- package/dist/src/github.d.ts +117 -3
- package/dist/src/github.js +651 -10
- package/dist/src/github.js.map +1 -1
- package/dist/src/{index-Bhc341pf.d.ts → index-LULVRsCZ.d.ts} +180 -1
- package/dist/src/index.d.ts +16 -11
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/memory.d.ts +5 -6
- package/dist/src/{memory_file_lister-BIIcVXLw.d.ts → memory_file_lister-BCY4ZYGW.d.ts} +1 -2
- package/dist/src/prisma.d.ts +108 -26
- package/dist/src/prisma.js +268 -32
- package/dist/src/prisma.js.map +1 -1
- package/dist/src/{record_projection.types-B8AM7u8U.d.ts → record_projection.types-Dz9YU3r9.d.ts} +64 -4
- package/dist/src/session_store-I4Z6PW2c.d.ts +50 -0
- package/dist/src/{agent_runner-pr7h-9cV.d.ts → sync_state-Bn_LogJ2.d.ts} +5 -592
- package/package.json +7 -1
- package/dist/src/key_provider-jjWek3w1.d.ts +0 -227
- package/dist/src/record_store-BXKWqon5.d.ts +0 -64
package/dist/src/github.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import picomatch from 'picomatch';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
|
|
3
4
|
// src/file_lister/github/github_file_lister.ts
|
|
4
5
|
|
|
@@ -935,10 +936,10 @@ var GitHubGitModule = class {
|
|
|
935
936
|
});
|
|
936
937
|
const treeSha = commitData.tree.sha;
|
|
937
938
|
const treeEntries = [];
|
|
938
|
-
for (const [
|
|
939
|
+
for (const [path2, content] of this.stagingBuffer) {
|
|
939
940
|
if (content === null) {
|
|
940
941
|
treeEntries.push({
|
|
941
|
-
path,
|
|
942
|
+
path: path2,
|
|
942
943
|
mode: "100644",
|
|
943
944
|
type: "blob",
|
|
944
945
|
sha: null
|
|
@@ -951,7 +952,7 @@ var GitHubGitModule = class {
|
|
|
951
952
|
encoding: "base64"
|
|
952
953
|
});
|
|
953
954
|
treeEntries.push({
|
|
954
|
-
path,
|
|
955
|
+
path: path2,
|
|
955
956
|
mode: "100644",
|
|
956
957
|
type: "blob",
|
|
957
958
|
sha: blobData.sha
|
|
@@ -1154,12 +1155,12 @@ var GitHubConfigStore = class {
|
|
|
1154
1155
|
* [EARS-B2] Caches SHA from response for subsequent saveConfig.
|
|
1155
1156
|
*/
|
|
1156
1157
|
async loadConfig() {
|
|
1157
|
-
const
|
|
1158
|
+
const path2 = `${this.basePath}/config.json`;
|
|
1158
1159
|
try {
|
|
1159
1160
|
const { data } = await this.octokit.rest.repos.getContent({
|
|
1160
1161
|
owner: this.owner,
|
|
1161
1162
|
repo: this.repo,
|
|
1162
|
-
path,
|
|
1163
|
+
path: path2,
|
|
1163
1164
|
ref: this.ref
|
|
1164
1165
|
});
|
|
1165
1166
|
if (Array.isArray(data) || data.type !== "file") {
|
|
@@ -1179,7 +1180,7 @@ var GitHubConfigStore = class {
|
|
|
1179
1180
|
if (isOctokitRequestError(error) && error.status === 404) {
|
|
1180
1181
|
return null;
|
|
1181
1182
|
}
|
|
1182
|
-
throw mapOctokitError(error, `loadConfig ${this.owner}/${this.repo}/${
|
|
1183
|
+
throw mapOctokitError(error, `loadConfig ${this.owner}/${this.repo}/${path2}`);
|
|
1183
1184
|
}
|
|
1184
1185
|
}
|
|
1185
1186
|
/**
|
|
@@ -1193,13 +1194,13 @@ var GitHubConfigStore = class {
|
|
|
1193
1194
|
* [EARS-C3] Throws SERVER_ERROR on 5xx.
|
|
1194
1195
|
*/
|
|
1195
1196
|
async saveConfig(config) {
|
|
1196
|
-
const
|
|
1197
|
+
const path2 = `${this.basePath}/config.json`;
|
|
1197
1198
|
const content = Buffer.from(JSON.stringify(config, null, 2)).toString("base64");
|
|
1198
1199
|
try {
|
|
1199
1200
|
const { data } = await this.octokit.rest.repos.createOrUpdateFileContents({
|
|
1200
1201
|
owner: this.owner,
|
|
1201
1202
|
repo: this.repo,
|
|
1202
|
-
path,
|
|
1203
|
+
path: path2,
|
|
1203
1204
|
message: "chore(config): update gitgov config.json",
|
|
1204
1205
|
content,
|
|
1205
1206
|
branch: this.ref,
|
|
@@ -1210,11 +1211,651 @@ var GitHubConfigStore = class {
|
|
|
1210
1211
|
}
|
|
1211
1212
|
return { commitSha: data.commit.sha };
|
|
1212
1213
|
} catch (error) {
|
|
1213
|
-
throw mapOctokitError(error, `saveConfig ${this.owner}/${this.repo}/${
|
|
1214
|
+
throw mapOctokitError(error, `saveConfig ${this.owner}/${this.repo}/${path2}`);
|
|
1214
1215
|
}
|
|
1215
1216
|
}
|
|
1216
1217
|
};
|
|
1217
1218
|
|
|
1219
|
+
// src/sync_state/sync_state.types.ts
|
|
1220
|
+
var SYNC_DIRECTORIES = [
|
|
1221
|
+
"tasks",
|
|
1222
|
+
"cycles",
|
|
1223
|
+
"actors",
|
|
1224
|
+
"agents",
|
|
1225
|
+
"feedbacks",
|
|
1226
|
+
"executions",
|
|
1227
|
+
"changelogs",
|
|
1228
|
+
"workflows"
|
|
1229
|
+
];
|
|
1230
|
+
var SYNC_ROOT_FILES = [
|
|
1231
|
+
"config.json"
|
|
1232
|
+
];
|
|
1233
|
+
var SYNC_ALLOWED_EXTENSIONS = [".json"];
|
|
1234
|
+
var SYNC_EXCLUDED_PATTERNS = [
|
|
1235
|
+
/\.key$/,
|
|
1236
|
+
// Private keys (e.g., keys/*.key)
|
|
1237
|
+
/\.backup$/,
|
|
1238
|
+
// Backup files from lint
|
|
1239
|
+
/\.backup-\d+$/,
|
|
1240
|
+
// Numbered backup files
|
|
1241
|
+
/\.tmp$/,
|
|
1242
|
+
// Temporary files
|
|
1243
|
+
/\.bak$/
|
|
1244
|
+
// Backup files
|
|
1245
|
+
];
|
|
1246
|
+
var LOCAL_ONLY_FILES = [
|
|
1247
|
+
"index.json",
|
|
1248
|
+
// Generated index, rebuilt on each machine
|
|
1249
|
+
".session.json",
|
|
1250
|
+
// Local session state for current user/agent
|
|
1251
|
+
"gitgov"
|
|
1252
|
+
// Local binary/script
|
|
1253
|
+
];
|
|
1254
|
+
|
|
1255
|
+
// src/sync_state/sync_state.utils.ts
|
|
1256
|
+
function shouldSyncFile(filePath) {
|
|
1257
|
+
const fileName = path.basename(filePath);
|
|
1258
|
+
const ext = path.extname(filePath);
|
|
1259
|
+
if (!SYNC_ALLOWED_EXTENSIONS.includes(ext)) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
for (const pattern of SYNC_EXCLUDED_PATTERNS) {
|
|
1263
|
+
if (pattern.test(fileName)) {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (LOCAL_ONLY_FILES.includes(fileName)) {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
1271
|
+
const parts = normalizedPath.split("/");
|
|
1272
|
+
const gitgovIndex = parts.findIndex((p) => p === ".gitgov");
|
|
1273
|
+
let relativeParts;
|
|
1274
|
+
if (gitgovIndex !== -1) {
|
|
1275
|
+
relativeParts = parts.slice(gitgovIndex + 1);
|
|
1276
|
+
} else {
|
|
1277
|
+
const syncDirIndex = parts.findIndex(
|
|
1278
|
+
(p) => SYNC_DIRECTORIES.includes(p)
|
|
1279
|
+
);
|
|
1280
|
+
if (syncDirIndex !== -1) {
|
|
1281
|
+
relativeParts = parts.slice(syncDirIndex);
|
|
1282
|
+
} else if (SYNC_ROOT_FILES.includes(fileName)) {
|
|
1283
|
+
return true;
|
|
1284
|
+
} else {
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (relativeParts.length === 1) {
|
|
1289
|
+
return SYNC_ROOT_FILES.includes(relativeParts[0]);
|
|
1290
|
+
} else if (relativeParts.length >= 2) {
|
|
1291
|
+
const dirName = relativeParts[0];
|
|
1292
|
+
return SYNC_DIRECTORIES.includes(dirName);
|
|
1293
|
+
}
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/sync_state/github_sync_state/github_sync_state.ts
|
|
1298
|
+
var GithubSyncStateModule = class {
|
|
1299
|
+
deps;
|
|
1300
|
+
lastKnownSha = null;
|
|
1301
|
+
constructor(deps) {
|
|
1302
|
+
this.deps = deps;
|
|
1303
|
+
}
|
|
1304
|
+
// ==================== Block A: Branch Management ====================
|
|
1305
|
+
/**
|
|
1306
|
+
* [EARS-GS-A3] Returns the configured state branch name.
|
|
1307
|
+
*/
|
|
1308
|
+
async getStateBranchName() {
|
|
1309
|
+
return "gitgov-state";
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* [EARS-GS-A1] Creates gitgov-state branch if it does not exist.
|
|
1313
|
+
* [EARS-GS-A2] Idempotent — no-op if branch already exists.
|
|
1314
|
+
*/
|
|
1315
|
+
async ensureStateBranch() {
|
|
1316
|
+
const branchName = await this.getStateBranchName();
|
|
1317
|
+
try {
|
|
1318
|
+
await this.deps.octokit.rest.repos.getBranch({
|
|
1319
|
+
owner: this.deps.owner,
|
|
1320
|
+
repo: this.deps.repo,
|
|
1321
|
+
branch: branchName
|
|
1322
|
+
});
|
|
1323
|
+
return;
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
if (!isOctokitRequestError(error) || error.status !== 404) {
|
|
1326
|
+
throw error;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const { data: repoData } = await this.deps.octokit.rest.repos.get({
|
|
1330
|
+
owner: this.deps.owner,
|
|
1331
|
+
repo: this.deps.repo
|
|
1332
|
+
});
|
|
1333
|
+
const defaultBranch = repoData.default_branch;
|
|
1334
|
+
const { data: refData } = await this.deps.octokit.rest.git.getRef({
|
|
1335
|
+
owner: this.deps.owner,
|
|
1336
|
+
repo: this.deps.repo,
|
|
1337
|
+
ref: `heads/${defaultBranch}`
|
|
1338
|
+
});
|
|
1339
|
+
await this.deps.octokit.rest.git.createRef({
|
|
1340
|
+
owner: this.deps.owner,
|
|
1341
|
+
repo: this.deps.repo,
|
|
1342
|
+
ref: `refs/heads/${branchName}`,
|
|
1343
|
+
sha: refData.object.sha
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
// ==================== Block B: Push State ====================
|
|
1347
|
+
/**
|
|
1348
|
+
* [EARS-GS-B1..B5] Push local .gitgov/ state to gitgov-state branch via API.
|
|
1349
|
+
*
|
|
1350
|
+
* Uses the 6-step atomic commit pattern:
|
|
1351
|
+
* getRef → getCommit → createBlob → createTree → createCommit → updateRef
|
|
1352
|
+
*
|
|
1353
|
+
* Optimistic concurrency: if remote ref advanced since our read, updateRef
|
|
1354
|
+
* fails with 422 → return conflictDetected: true.
|
|
1355
|
+
*/
|
|
1356
|
+
async pushState(options) {
|
|
1357
|
+
const branchName = await this.getStateBranchName();
|
|
1358
|
+
const sourceBranch = options.sourceBranch ?? "main";
|
|
1359
|
+
try {
|
|
1360
|
+
const { data: stateRefData } = await this.deps.octokit.rest.git.getRef({
|
|
1361
|
+
owner: this.deps.owner,
|
|
1362
|
+
repo: this.deps.repo,
|
|
1363
|
+
ref: `heads/${branchName}`
|
|
1364
|
+
});
|
|
1365
|
+
const currentSha = stateRefData.object.sha;
|
|
1366
|
+
const { data: sourceTree } = await this.deps.octokit.rest.git.getTree({
|
|
1367
|
+
owner: this.deps.owner,
|
|
1368
|
+
repo: this.deps.repo,
|
|
1369
|
+
tree_sha: sourceBranch,
|
|
1370
|
+
recursive: "true"
|
|
1371
|
+
});
|
|
1372
|
+
const sourceFiles = (sourceTree.tree ?? []).filter(
|
|
1373
|
+
(item) => item.type === "blob" && item.path?.startsWith(".gitgov/") && shouldSyncFile(item.path)
|
|
1374
|
+
);
|
|
1375
|
+
const { data: targetCommit } = await this.deps.octokit.rest.git.getCommit({
|
|
1376
|
+
owner: this.deps.owner,
|
|
1377
|
+
repo: this.deps.repo,
|
|
1378
|
+
commit_sha: currentSha
|
|
1379
|
+
});
|
|
1380
|
+
const { data: targetTree } = await this.deps.octokit.rest.git.getTree({
|
|
1381
|
+
owner: this.deps.owner,
|
|
1382
|
+
repo: this.deps.repo,
|
|
1383
|
+
tree_sha: targetCommit.tree.sha,
|
|
1384
|
+
recursive: "true"
|
|
1385
|
+
});
|
|
1386
|
+
const targetFileMap = /* @__PURE__ */ new Map();
|
|
1387
|
+
for (const item of targetTree.tree ?? []) {
|
|
1388
|
+
if (item.type === "blob" && item.path && item.sha) {
|
|
1389
|
+
targetFileMap.set(item.path, item.sha);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
const delta = [];
|
|
1393
|
+
const treeEntries = [];
|
|
1394
|
+
for (const sourceFile of sourceFiles) {
|
|
1395
|
+
if (!sourceFile.path || !sourceFile.sha) continue;
|
|
1396
|
+
const statePath = sourceFile.path.replace(/^\.gitgov\//, "");
|
|
1397
|
+
const targetSha = targetFileMap.get(statePath);
|
|
1398
|
+
if (targetSha !== sourceFile.sha) {
|
|
1399
|
+
delta.push({
|
|
1400
|
+
status: targetSha ? "M" : "A",
|
|
1401
|
+
file: statePath
|
|
1402
|
+
});
|
|
1403
|
+
treeEntries.push({
|
|
1404
|
+
path: statePath,
|
|
1405
|
+
mode: "100644",
|
|
1406
|
+
type: "blob",
|
|
1407
|
+
sha: sourceFile.sha
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
targetFileMap.delete(statePath);
|
|
1411
|
+
}
|
|
1412
|
+
for (const [deletedPath] of targetFileMap) {
|
|
1413
|
+
if (shouldSyncFile(deletedPath)) {
|
|
1414
|
+
delta.push({ status: "D", file: deletedPath });
|
|
1415
|
+
treeEntries.push({
|
|
1416
|
+
path: deletedPath,
|
|
1417
|
+
mode: "100644",
|
|
1418
|
+
type: "blob",
|
|
1419
|
+
sha: null
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
if (delta.length === 0) {
|
|
1424
|
+
return {
|
|
1425
|
+
success: true,
|
|
1426
|
+
filesSynced: 0,
|
|
1427
|
+
sourceBranch,
|
|
1428
|
+
commitHash: null,
|
|
1429
|
+
commitMessage: null,
|
|
1430
|
+
conflictDetected: false
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
if (options.dryRun) {
|
|
1434
|
+
return {
|
|
1435
|
+
success: true,
|
|
1436
|
+
filesSynced: delta.length,
|
|
1437
|
+
sourceBranch,
|
|
1438
|
+
commitHash: null,
|
|
1439
|
+
commitMessage: `[dry-run] gitgov sync: ${delta.length} files`,
|
|
1440
|
+
conflictDetected: false
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
const { data: newTreeData } = await this.deps.octokit.rest.git.createTree({
|
|
1444
|
+
owner: this.deps.owner,
|
|
1445
|
+
repo: this.deps.repo,
|
|
1446
|
+
base_tree: targetCommit.tree.sha,
|
|
1447
|
+
tree: treeEntries
|
|
1448
|
+
});
|
|
1449
|
+
const commitMessage = `gitgov sync: ${delta.length} files from ${sourceBranch}`;
|
|
1450
|
+
const { data: newCommitData } = await this.deps.octokit.rest.git.createCommit({
|
|
1451
|
+
owner: this.deps.owner,
|
|
1452
|
+
repo: this.deps.repo,
|
|
1453
|
+
message: commitMessage,
|
|
1454
|
+
tree: newTreeData.sha,
|
|
1455
|
+
parents: [currentSha]
|
|
1456
|
+
});
|
|
1457
|
+
try {
|
|
1458
|
+
await this.deps.octokit.rest.git.updateRef({
|
|
1459
|
+
owner: this.deps.owner,
|
|
1460
|
+
repo: this.deps.repo,
|
|
1461
|
+
ref: `heads/${branchName}`,
|
|
1462
|
+
sha: newCommitData.sha
|
|
1463
|
+
});
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
if (isOctokitRequestError(error) && (error.status === 422 || error.status === 409)) {
|
|
1466
|
+
return {
|
|
1467
|
+
success: false,
|
|
1468
|
+
filesSynced: 0,
|
|
1469
|
+
sourceBranch,
|
|
1470
|
+
commitHash: null,
|
|
1471
|
+
commitMessage: null,
|
|
1472
|
+
conflictDetected: true,
|
|
1473
|
+
conflictInfo: {
|
|
1474
|
+
type: "rebase_conflict",
|
|
1475
|
+
affectedFiles: delta.map((d) => d.file),
|
|
1476
|
+
message: "Remote gitgov-state ref has advanced since last read. Pull and retry.",
|
|
1477
|
+
resolutionSteps: [
|
|
1478
|
+
"Call pullState() to fetch latest remote state",
|
|
1479
|
+
"Retry pushState() with updated parent SHA"
|
|
1480
|
+
]
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
throw error;
|
|
1485
|
+
}
|
|
1486
|
+
this.lastKnownSha = newCommitData.sha;
|
|
1487
|
+
return {
|
|
1488
|
+
success: true,
|
|
1489
|
+
filesSynced: delta.length,
|
|
1490
|
+
sourceBranch,
|
|
1491
|
+
commitHash: newCommitData.sha,
|
|
1492
|
+
commitMessage,
|
|
1493
|
+
conflictDetected: false
|
|
1494
|
+
};
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
if (isOctokitRequestError(error)) {
|
|
1497
|
+
return {
|
|
1498
|
+
success: false,
|
|
1499
|
+
filesSynced: 0,
|
|
1500
|
+
sourceBranch,
|
|
1501
|
+
commitHash: null,
|
|
1502
|
+
commitMessage: null,
|
|
1503
|
+
conflictDetected: false,
|
|
1504
|
+
error: `GitHub API error (${error.status}): ${error.message}`
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1508
|
+
return {
|
|
1509
|
+
success: false,
|
|
1510
|
+
filesSynced: 0,
|
|
1511
|
+
sourceBranch,
|
|
1512
|
+
commitHash: null,
|
|
1513
|
+
commitMessage: null,
|
|
1514
|
+
conflictDetected: false,
|
|
1515
|
+
error: msg
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
// ==================== Block C: Pull State ====================
|
|
1520
|
+
/**
|
|
1521
|
+
* [EARS-GS-C1..C4] Pull remote state from gitgov-state branch.
|
|
1522
|
+
*
|
|
1523
|
+
* Fetches tree + blobs, updates lastKnownSha, triggers re-indexing.
|
|
1524
|
+
*/
|
|
1525
|
+
async pullState(options) {
|
|
1526
|
+
const branchName = await this.getStateBranchName();
|
|
1527
|
+
let remoteSha;
|
|
1528
|
+
try {
|
|
1529
|
+
const { data: refData } = await this.deps.octokit.rest.git.getRef({
|
|
1530
|
+
owner: this.deps.owner,
|
|
1531
|
+
repo: this.deps.repo,
|
|
1532
|
+
ref: `heads/${branchName}`
|
|
1533
|
+
});
|
|
1534
|
+
remoteSha = refData.object.sha;
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
if (isOctokitRequestError(error) && error.status === 404) {
|
|
1537
|
+
return {
|
|
1538
|
+
success: true,
|
|
1539
|
+
hasChanges: false,
|
|
1540
|
+
filesUpdated: 0,
|
|
1541
|
+
reindexed: false,
|
|
1542
|
+
conflictDetected: false
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
throw error;
|
|
1546
|
+
}
|
|
1547
|
+
if (this.lastKnownSha === remoteSha && !options?.forceReindex) {
|
|
1548
|
+
return {
|
|
1549
|
+
success: true,
|
|
1550
|
+
hasChanges: false,
|
|
1551
|
+
filesUpdated: 0,
|
|
1552
|
+
reindexed: false,
|
|
1553
|
+
conflictDetected: false
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
const { data: commitData } = await this.deps.octokit.rest.git.getCommit({
|
|
1557
|
+
owner: this.deps.owner,
|
|
1558
|
+
repo: this.deps.repo,
|
|
1559
|
+
commit_sha: remoteSha
|
|
1560
|
+
});
|
|
1561
|
+
const { data: treeData } = await this.deps.octokit.rest.git.getTree({
|
|
1562
|
+
owner: this.deps.owner,
|
|
1563
|
+
repo: this.deps.repo,
|
|
1564
|
+
tree_sha: commitData.tree.sha,
|
|
1565
|
+
recursive: "true"
|
|
1566
|
+
});
|
|
1567
|
+
const syncableFiles = (treeData.tree ?? []).filter(
|
|
1568
|
+
(item) => item.type === "blob" && item.path && shouldSyncFile(item.path)
|
|
1569
|
+
);
|
|
1570
|
+
const filesUpdated = syncableFiles.length;
|
|
1571
|
+
this.lastKnownSha = remoteSha;
|
|
1572
|
+
let reindexed = false;
|
|
1573
|
+
if (filesUpdated > 0 || options?.forceReindex) {
|
|
1574
|
+
try {
|
|
1575
|
+
await this.deps.indexer.computeProjection();
|
|
1576
|
+
reindexed = true;
|
|
1577
|
+
} catch {
|
|
1578
|
+
reindexed = false;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
success: true,
|
|
1583
|
+
hasChanges: filesUpdated > 0,
|
|
1584
|
+
filesUpdated,
|
|
1585
|
+
reindexed,
|
|
1586
|
+
conflictDetected: false
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
// ==================== Block D: Change Detection ====================
|
|
1590
|
+
/**
|
|
1591
|
+
* [EARS-GS-D1..D3] Calculate file delta between known state and current remote.
|
|
1592
|
+
*/
|
|
1593
|
+
async calculateStateDelta(_sourceBranch) {
|
|
1594
|
+
const branchName = await this.getStateBranchName();
|
|
1595
|
+
let currentSha;
|
|
1596
|
+
try {
|
|
1597
|
+
const { data: refData } = await this.deps.octokit.rest.git.getRef({
|
|
1598
|
+
owner: this.deps.owner,
|
|
1599
|
+
repo: this.deps.repo,
|
|
1600
|
+
ref: `heads/${branchName}`
|
|
1601
|
+
});
|
|
1602
|
+
currentSha = refData.object.sha;
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
if (isOctokitRequestError(error) && error.status === 404) {
|
|
1605
|
+
return [];
|
|
1606
|
+
}
|
|
1607
|
+
throw error;
|
|
1608
|
+
}
|
|
1609
|
+
if (this.lastKnownSha === currentSha) {
|
|
1610
|
+
return [];
|
|
1611
|
+
}
|
|
1612
|
+
if (this.lastKnownSha === null) {
|
|
1613
|
+
const { data: commitData } = await this.deps.octokit.rest.git.getCommit({
|
|
1614
|
+
owner: this.deps.owner,
|
|
1615
|
+
repo: this.deps.repo,
|
|
1616
|
+
commit_sha: currentSha
|
|
1617
|
+
});
|
|
1618
|
+
const { data: treeData } = await this.deps.octokit.rest.git.getTree({
|
|
1619
|
+
owner: this.deps.owner,
|
|
1620
|
+
repo: this.deps.repo,
|
|
1621
|
+
tree_sha: commitData.tree.sha,
|
|
1622
|
+
recursive: "true"
|
|
1623
|
+
});
|
|
1624
|
+
return (treeData.tree ?? []).filter((item) => item.type === "blob" && item.path && shouldSyncFile(item.path)).map((item) => ({
|
|
1625
|
+
status: "A",
|
|
1626
|
+
file: item.path
|
|
1627
|
+
}));
|
|
1628
|
+
}
|
|
1629
|
+
const { data: comparison } = await this.deps.octokit.rest.repos.compareCommits({
|
|
1630
|
+
owner: this.deps.owner,
|
|
1631
|
+
repo: this.deps.repo,
|
|
1632
|
+
base: this.lastKnownSha,
|
|
1633
|
+
head: currentSha
|
|
1634
|
+
});
|
|
1635
|
+
return (comparison.files ?? []).filter((file) => shouldSyncFile(file.filename)).map((file) => ({
|
|
1636
|
+
status: file.status === "added" ? "A" : file.status === "removed" ? "D" : "M",
|
|
1637
|
+
file: file.filename
|
|
1638
|
+
}));
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Always empty — no local pending changes in API mode.
|
|
1642
|
+
* In API mode there is no local filesystem; all state is remote.
|
|
1643
|
+
*/
|
|
1644
|
+
async getPendingChanges() {
|
|
1645
|
+
return [];
|
|
1646
|
+
}
|
|
1647
|
+
// ==================== Block E: Conflict Handling ====================
|
|
1648
|
+
/**
|
|
1649
|
+
* Always false — no rebase in API mode.
|
|
1650
|
+
*/
|
|
1651
|
+
async isRebaseInProgress() {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Always empty — no conflict markers in API mode.
|
|
1656
|
+
*/
|
|
1657
|
+
async checkConflictMarkers(_filePaths) {
|
|
1658
|
+
return [];
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Empty diff — no git-level conflict markers in API mode.
|
|
1662
|
+
*/
|
|
1663
|
+
async getConflictDiff(_filePaths) {
|
|
1664
|
+
return {
|
|
1665
|
+
files: [],
|
|
1666
|
+
message: "No conflict markers in API mode. Conflicts are SHA-based.",
|
|
1667
|
+
resolutionSteps: [
|
|
1668
|
+
"Call pullState() to fetch latest remote state",
|
|
1669
|
+
"Retry pushState() with updated records"
|
|
1670
|
+
]
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* [EARS-GS-E1..E2] Resolve conflict by pulling latest and retrying push.
|
|
1675
|
+
*/
|
|
1676
|
+
async resolveConflict(options) {
|
|
1677
|
+
const pullResult = await this.pullState({ forceReindex: false });
|
|
1678
|
+
if (!pullResult.success) {
|
|
1679
|
+
return {
|
|
1680
|
+
success: false,
|
|
1681
|
+
rebaseCommitHash: "",
|
|
1682
|
+
resolutionCommitHash: "",
|
|
1683
|
+
conflictsResolved: 0,
|
|
1684
|
+
resolvedBy: options.actorId,
|
|
1685
|
+
reason: options.reason,
|
|
1686
|
+
error: `Pull failed during conflict resolution: ${pullResult.error}`
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
const pushResult = await this.pushState({
|
|
1690
|
+
actorId: options.actorId
|
|
1691
|
+
});
|
|
1692
|
+
if (!pushResult.success || pushResult.conflictDetected) {
|
|
1693
|
+
const errorMsg = pushResult.conflictDetected ? "Content conflict: same file modified by both sides. Manual resolution required." : pushResult.error ?? "Unknown push error";
|
|
1694
|
+
return {
|
|
1695
|
+
success: false,
|
|
1696
|
+
rebaseCommitHash: "",
|
|
1697
|
+
resolutionCommitHash: "",
|
|
1698
|
+
conflictsResolved: 0,
|
|
1699
|
+
resolvedBy: options.actorId,
|
|
1700
|
+
reason: options.reason,
|
|
1701
|
+
error: errorMsg
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
return {
|
|
1705
|
+
success: true,
|
|
1706
|
+
rebaseCommitHash: this.lastKnownSha ?? "",
|
|
1707
|
+
resolutionCommitHash: pushResult.commitHash ?? "",
|
|
1708
|
+
conflictsResolved: pushResult.filesSynced,
|
|
1709
|
+
resolvedBy: options.actorId,
|
|
1710
|
+
reason: options.reason
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* No integrity violations in API mode (no rebase commits).
|
|
1715
|
+
*/
|
|
1716
|
+
async verifyResolutionIntegrity() {
|
|
1717
|
+
return [];
|
|
1718
|
+
}
|
|
1719
|
+
// ==================== Block F: Audit ====================
|
|
1720
|
+
/**
|
|
1721
|
+
* [EARS-GS-F1..F2] Audit the remote gitgov-state branch.
|
|
1722
|
+
*/
|
|
1723
|
+
async auditState(options) {
|
|
1724
|
+
const branchName = await this.getStateBranchName();
|
|
1725
|
+
const scope = options?.scope ?? "all";
|
|
1726
|
+
let totalCommits = 0;
|
|
1727
|
+
try {
|
|
1728
|
+
const { data: commits } = await this.deps.octokit.rest.repos.listCommits({
|
|
1729
|
+
owner: this.deps.owner,
|
|
1730
|
+
repo: this.deps.repo,
|
|
1731
|
+
sha: branchName,
|
|
1732
|
+
per_page: 100
|
|
1733
|
+
});
|
|
1734
|
+
totalCommits = commits.length;
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
if (isOctokitRequestError(error) && error.status === 404) {
|
|
1737
|
+
return {
|
|
1738
|
+
passed: true,
|
|
1739
|
+
scope,
|
|
1740
|
+
totalCommits: 0,
|
|
1741
|
+
rebaseCommits: 0,
|
|
1742
|
+
resolutionCommits: 0,
|
|
1743
|
+
integrityViolations: [],
|
|
1744
|
+
summary: "Branch gitgov-state does not exist. No audit needed."
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
throw error;
|
|
1748
|
+
}
|
|
1749
|
+
const rebaseCommits = 0;
|
|
1750
|
+
const resolutionCommits = 0;
|
|
1751
|
+
const integrityViolations = [];
|
|
1752
|
+
let lintReport;
|
|
1753
|
+
if (options?.verifySignatures !== false || options?.verifyChecksums !== false) {
|
|
1754
|
+
try {
|
|
1755
|
+
const { data: refData } = await this.deps.octokit.rest.git.getRef({
|
|
1756
|
+
owner: this.deps.owner,
|
|
1757
|
+
repo: this.deps.repo,
|
|
1758
|
+
ref: `heads/${branchName}`
|
|
1759
|
+
});
|
|
1760
|
+
const { data: commitData } = await this.deps.octokit.rest.git.getCommit({
|
|
1761
|
+
owner: this.deps.owner,
|
|
1762
|
+
repo: this.deps.repo,
|
|
1763
|
+
commit_sha: refData.object.sha
|
|
1764
|
+
});
|
|
1765
|
+
const { data: treeData } = await this.deps.octokit.rest.git.getTree({
|
|
1766
|
+
owner: this.deps.owner,
|
|
1767
|
+
repo: this.deps.repo,
|
|
1768
|
+
tree_sha: commitData.tree.sha,
|
|
1769
|
+
recursive: "true"
|
|
1770
|
+
});
|
|
1771
|
+
const treeItems = (treeData.tree ?? []).filter((item) => item.type === "blob" && item.path && item.sha && shouldSyncFile(item.path));
|
|
1772
|
+
const startTime = Date.now();
|
|
1773
|
+
const allResults = [];
|
|
1774
|
+
let filesChecked = 0;
|
|
1775
|
+
for (const item of treeItems) {
|
|
1776
|
+
try {
|
|
1777
|
+
const { data: blobData } = await this.deps.octokit.rest.git.getBlob({
|
|
1778
|
+
owner: this.deps.owner,
|
|
1779
|
+
repo: this.deps.repo,
|
|
1780
|
+
file_sha: item.sha
|
|
1781
|
+
});
|
|
1782
|
+
const content = Buffer.from(blobData.content, "base64").toString("utf-8");
|
|
1783
|
+
const record = JSON.parse(content);
|
|
1784
|
+
const entityType = pathToEntityType(item.path);
|
|
1785
|
+
if (entityType) {
|
|
1786
|
+
const results = this.deps.lint.lintRecord(record, {
|
|
1787
|
+
recordId: item.path.split("/").pop()?.replace(".json", "") ?? item.path,
|
|
1788
|
+
entityType,
|
|
1789
|
+
filePath: item.path
|
|
1790
|
+
});
|
|
1791
|
+
allResults.push(...results);
|
|
1792
|
+
}
|
|
1793
|
+
filesChecked++;
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (filesChecked > 0) {
|
|
1798
|
+
lintReport = {
|
|
1799
|
+
summary: {
|
|
1800
|
+
filesChecked,
|
|
1801
|
+
errors: allResults.filter((r) => r.level === "error").length,
|
|
1802
|
+
warnings: allResults.filter((r) => r.level === "warning").length,
|
|
1803
|
+
fixable: allResults.filter((r) => r.fixable).length,
|
|
1804
|
+
executionTime: Date.now() - startTime
|
|
1805
|
+
},
|
|
1806
|
+
results: allResults,
|
|
1807
|
+
metadata: {
|
|
1808
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1809
|
+
options: {},
|
|
1810
|
+
version: "1.0.0"
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
} catch {
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
const lintPassed = !lintReport || lintReport.summary.errors === 0;
|
|
1818
|
+
const passed = integrityViolations.length === 0 && lintPassed;
|
|
1819
|
+
const lintErrors = lintReport?.summary.errors ?? 0;
|
|
1820
|
+
let summary;
|
|
1821
|
+
if (passed) {
|
|
1822
|
+
summary = `Audit passed. ${totalCommits} commits analyzed, 0 violations.`;
|
|
1823
|
+
} else if (integrityViolations.length > 0 && lintErrors > 0) {
|
|
1824
|
+
summary = `Audit failed. ${integrityViolations.length} integrity violations, ${lintErrors} lint errors.`;
|
|
1825
|
+
} else if (lintErrors > 0) {
|
|
1826
|
+
summary = `Audit failed. ${lintErrors} lint errors found.`;
|
|
1827
|
+
} else {
|
|
1828
|
+
summary = `Audit failed. ${integrityViolations.length} integrity violations found.`;
|
|
1829
|
+
}
|
|
1830
|
+
const report = {
|
|
1831
|
+
passed,
|
|
1832
|
+
scope,
|
|
1833
|
+
totalCommits,
|
|
1834
|
+
rebaseCommits,
|
|
1835
|
+
resolutionCommits,
|
|
1836
|
+
integrityViolations,
|
|
1837
|
+
summary
|
|
1838
|
+
};
|
|
1839
|
+
if (lintReport) {
|
|
1840
|
+
report.lintReport = lintReport;
|
|
1841
|
+
}
|
|
1842
|
+
return report;
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
function pathToEntityType(filePath) {
|
|
1846
|
+
const dirMap = {
|
|
1847
|
+
tasks: "task",
|
|
1848
|
+
cycles: "cycle",
|
|
1849
|
+
actors: "actor",
|
|
1850
|
+
agents: "agent",
|
|
1851
|
+
feedbacks: "feedback",
|
|
1852
|
+
executions: "execution",
|
|
1853
|
+
changelogs: "changelog"
|
|
1854
|
+
};
|
|
1855
|
+
const firstSegment = filePath.split("/")[0] ?? "";
|
|
1856
|
+
return dirMap[firstSegment];
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1218
1859
|
// src/github.ts
|
|
1219
1860
|
var GitHubApiError = class _GitHubApiError extends Error {
|
|
1220
1861
|
constructor(message, code, statusCode) {
|
|
@@ -1276,6 +1917,6 @@ function mapOctokitError(error, context) {
|
|
|
1276
1917
|
return new GitHubApiError(`Network error: ${message}`, "NETWORK_ERROR");
|
|
1277
1918
|
}
|
|
1278
1919
|
|
|
1279
|
-
export { GitHubApiError, GitHubConfigStore, GitHubFileLister, GitHubGitModule, GitHubRecordStore, isOctokitRequestError, mapOctokitError };
|
|
1920
|
+
export { GitHubApiError, GitHubConfigStore, GitHubFileLister, GitHubGitModule, GitHubRecordStore, GithubSyncStateModule, isOctokitRequestError, mapOctokitError };
|
|
1280
1921
|
//# sourceMappingURL=github.js.map
|
|
1281
1922
|
//# sourceMappingURL=github.js.map
|