hillclimb 0.9.13 → 0.9.15
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/dist/git-diagnostics-worker.js +205 -0
- package/dist/main.js +590 -265
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-YBBDXGEY.js";
|
|
14
14
|
|
|
15
15
|
// src/main.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import fs29 from "fs";
|
|
17
|
+
import path28 from "path";
|
|
18
18
|
import * as p6 from "@clack/prompts";
|
|
19
19
|
|
|
20
20
|
// src/commands/init.ts
|
|
@@ -2937,9 +2937,9 @@ async function runStatus(args = []) {
|
|
|
2937
2937
|
// src/commands/upload.ts
|
|
2938
2938
|
import { spawn as spawn3 } from "child_process";
|
|
2939
2939
|
import crypto8 from "crypto";
|
|
2940
|
-
import
|
|
2941
|
-
import
|
|
2942
|
-
import
|
|
2940
|
+
import fs22 from "fs";
|
|
2941
|
+
import os11 from "os";
|
|
2942
|
+
import path21 from "path";
|
|
2943
2943
|
|
|
2944
2944
|
// src/agent-pending.ts
|
|
2945
2945
|
import crypto4 from "crypto";
|
|
@@ -16194,21 +16194,163 @@ async function recordDebugLogCompletion(args) {
|
|
|
16194
16194
|
// src/git-traces/pending-captures.ts
|
|
16195
16195
|
import { AsyncLocalStorage } from "async_hooks";
|
|
16196
16196
|
import crypto7 from "crypto";
|
|
16197
|
-
import
|
|
16198
|
-
import
|
|
16199
|
-
import
|
|
16197
|
+
import fs21 from "fs";
|
|
16198
|
+
import os10 from "os";
|
|
16199
|
+
import path20 from "path";
|
|
16200
16200
|
|
|
16201
16201
|
// src/git-traces/git-ops.ts
|
|
16202
16202
|
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
16203
|
-
import
|
|
16204
|
-
import
|
|
16205
|
-
import
|
|
16203
|
+
import fs19 from "fs";
|
|
16204
|
+
import os8 from "os";
|
|
16205
|
+
import path18 from "path";
|
|
16206
16206
|
import { createGzip } from "zlib";
|
|
16207
16207
|
|
|
16208
|
-
// src/git-traces/
|
|
16209
|
-
import { spawnSync } from "child_process";
|
|
16208
|
+
// src/git-traces/git-diagnostics.ts
|
|
16210
16209
|
import fs17 from "fs";
|
|
16210
|
+
import os7 from "os";
|
|
16211
16211
|
import path16 from "path";
|
|
16212
|
+
import { Worker as Worker2 } from "worker_threads";
|
|
16213
|
+
var PHASES = /* @__PURE__ */ new Set([
|
|
16214
|
+
"pack-objects/enumerate-objects",
|
|
16215
|
+
"pack-objects/prepare-pack",
|
|
16216
|
+
"pack-objects/write-pack-file",
|
|
16217
|
+
"index/do_read_index",
|
|
16218
|
+
"index/do_write_index"
|
|
16219
|
+
]);
|
|
16220
|
+
var COUNTERS = /* @__PURE__ */ new Set([
|
|
16221
|
+
"pack-objects/write_pack_file/wrote",
|
|
16222
|
+
"pack-objects/written",
|
|
16223
|
+
"index/read/cache_nr",
|
|
16224
|
+
"index/write/cache_nr"
|
|
16225
|
+
]);
|
|
16226
|
+
function summarizeGitTrace(trace, partial = false) {
|
|
16227
|
+
const phases = /* @__PURE__ */ new Map();
|
|
16228
|
+
const active = /* @__PURE__ */ new Set();
|
|
16229
|
+
const counts = /* @__PURE__ */ new Map();
|
|
16230
|
+
const text2 = trace?.subarray(0, 1024 * 1024).toString("utf8") ?? "";
|
|
16231
|
+
const lines = text2.split("\n");
|
|
16232
|
+
partial ||= (trace?.length ?? 0) > 1024 * 1024 || lines.length > 4096;
|
|
16233
|
+
for (const line of lines.slice(0, 4096)) {
|
|
16234
|
+
if (!line) continue;
|
|
16235
|
+
if (line.length > 16384) {
|
|
16236
|
+
partial = true;
|
|
16237
|
+
continue;
|
|
16238
|
+
}
|
|
16239
|
+
try {
|
|
16240
|
+
const e = JSON.parse(line);
|
|
16241
|
+
if (!e || typeof e !== "object") continue;
|
|
16242
|
+
const phase = `${e.category}/${e.label}`;
|
|
16243
|
+
if (e.thread === "main" && PHASES.has(phase)) {
|
|
16244
|
+
if (e.event === "region_enter") active.add(phase);
|
|
16245
|
+
if (e.event === "region_leave" && typeof e.t_rel === "number" && Number.isFinite(e.t_rel) && e.t_rel >= 0 && e.t_rel < 86400) {
|
|
16246
|
+
active.delete(phase);
|
|
16247
|
+
phases.set(phase, Math.round(e.t_rel * 1e3));
|
|
16248
|
+
}
|
|
16249
|
+
}
|
|
16250
|
+
const key = `${e.category}/${e.key}`;
|
|
16251
|
+
if (e.event === "data" && COUNTERS.has(key) && /^\d{1,15}$/.test(String(e.value))) {
|
|
16252
|
+
counts.set(key, Number(e.value));
|
|
16253
|
+
}
|
|
16254
|
+
} catch {
|
|
16255
|
+
partial = true;
|
|
16256
|
+
}
|
|
16257
|
+
}
|
|
16258
|
+
return `traceState=${!text2 ? "unavailable" : partial ? "partial" : "available"}, phasesMs=${JSON.stringify(Object.fromEntries(phases))}, activePhases=${JSON.stringify([...active])}, counts=${JSON.stringify(Object.fromEntries(counts))}`;
|
|
16259
|
+
}
|
|
16260
|
+
var SAMPLE = {
|
|
16261
|
+
stop: 0,
|
|
16262
|
+
count: 1,
|
|
16263
|
+
cpuMs: 2,
|
|
16264
|
+
elapsedMs: 3,
|
|
16265
|
+
bytes: 4,
|
|
16266
|
+
growthMs: 5
|
|
16267
|
+
};
|
|
16268
|
+
var TRACE = { ready: 0, length: 1, partial: 2, done: 3 };
|
|
16269
|
+
function bundleBytes(file) {
|
|
16270
|
+
let bytes = 0;
|
|
16271
|
+
if (!file) return bytes;
|
|
16272
|
+
for (const candidate of [file, `${file}.lock`]) {
|
|
16273
|
+
try {
|
|
16274
|
+
bytes = Math.max(bytes, fs17.statSync(candidate).size);
|
|
16275
|
+
} catch {
|
|
16276
|
+
}
|
|
16277
|
+
}
|
|
16278
|
+
return bytes;
|
|
16279
|
+
}
|
|
16280
|
+
function startGitDiagnostics(bundleFile) {
|
|
16281
|
+
const samples = new BigInt64Array(new SharedArrayBuffer(6 * 8));
|
|
16282
|
+
samples[SAMPLE.cpuMs] = -1n;
|
|
16283
|
+
const control = new Int32Array(new SharedArrayBuffer(4 * 4));
|
|
16284
|
+
const trace = Buffer.from(new SharedArrayBuffer(1024 * 1024));
|
|
16285
|
+
let worker;
|
|
16286
|
+
let directory;
|
|
16287
|
+
let socketPath;
|
|
16288
|
+
try {
|
|
16289
|
+
if (process.platform !== "win32") {
|
|
16290
|
+
directory = fs17.mkdtempSync(path16.join(os7.tmpdir(), "hc-git-"));
|
|
16291
|
+
fs17.chmodSync(directory, 448);
|
|
16292
|
+
const candidate = path16.join(directory, "trace.sock");
|
|
16293
|
+
if (Buffer.byteLength(candidate) < 100) socketPath = candidate;
|
|
16294
|
+
}
|
|
16295
|
+
worker = new Worker2(
|
|
16296
|
+
new URL(
|
|
16297
|
+
import.meta.url.endsWith(".ts") ? "./git-diagnostics-worker.ts" : "./git-diagnostics-worker.js",
|
|
16298
|
+
import.meta.url
|
|
16299
|
+
),
|
|
16300
|
+
{
|
|
16301
|
+
workerData: {
|
|
16302
|
+
samples: samples.buffer,
|
|
16303
|
+
control: control.buffer,
|
|
16304
|
+
trace: trace.buffer,
|
|
16305
|
+
socketPath,
|
|
16306
|
+
parentPid: process.pid,
|
|
16307
|
+
bundleFile,
|
|
16308
|
+
started: Date.now()
|
|
16309
|
+
}
|
|
16310
|
+
}
|
|
16311
|
+
);
|
|
16312
|
+
worker.on("error", () => {
|
|
16313
|
+
});
|
|
16314
|
+
worker.unref();
|
|
16315
|
+
Atomics.wait(control, TRACE.ready, 0, 100);
|
|
16316
|
+
} catch {
|
|
16317
|
+
}
|
|
16318
|
+
return {
|
|
16319
|
+
env: {
|
|
16320
|
+
GIT_TRACE2_EVENT: socketPath && Atomics.load(control, TRACE.ready) === 1 ? `af_unix:stream:${socketPath}` : "0",
|
|
16321
|
+
GIT_TRACE2_EVENT_NESTING: "2",
|
|
16322
|
+
GIT_TRACE2_CONFIG_PARAMS: "",
|
|
16323
|
+
GIT_TRACE2_ENV_VARS: "",
|
|
16324
|
+
GIT_TRACE2_DST_DEBUG: "0"
|
|
16325
|
+
},
|
|
16326
|
+
finish: () => {
|
|
16327
|
+
Atomics.store(samples, SAMPLE.stop, 1n);
|
|
16328
|
+
try {
|
|
16329
|
+
worker?.postMessage("stop");
|
|
16330
|
+
if (worker) Atomics.wait(control, TRACE.done, 0, 100);
|
|
16331
|
+
} catch {
|
|
16332
|
+
}
|
|
16333
|
+
void worker?.terminate().catch(() => {
|
|
16334
|
+
});
|
|
16335
|
+
if (directory) {
|
|
16336
|
+
try {
|
|
16337
|
+
fs17.rmSync(directory, { recursive: true, force: true });
|
|
16338
|
+
} catch {
|
|
16339
|
+
}
|
|
16340
|
+
}
|
|
16341
|
+
const summary = summarizeGitTrace(
|
|
16342
|
+
trace.subarray(0, Atomics.load(control, TRACE.length)),
|
|
16343
|
+
!!Atomics.load(control, TRACE.partial) || !Atomics.load(control, TRACE.done)
|
|
16344
|
+
);
|
|
16345
|
+
return `${summary}, cpuSamples=${Atomics.load(samples, SAMPLE.count)}, sampledCpuMs=${Atomics.load(samples, SAMPLE.cpuMs)}, sampledThroughMs=${Atomics.load(samples, SAMPLE.elapsedMs)}${bundleFile ? `, sampledBundleBytes=${Atomics.load(samples, SAMPLE.bytes)}, lastBundleGrowthMs=${Atomics.load(samples, SAMPLE.growthMs)}, bundleBytes=${bundleBytes(bundleFile)}` : ""}`;
|
|
16346
|
+
}
|
|
16347
|
+
};
|
|
16348
|
+
}
|
|
16349
|
+
|
|
16350
|
+
// src/git-traces/object-store-probe.ts
|
|
16351
|
+
import { spawnSync } from "child_process";
|
|
16352
|
+
import fs18 from "fs";
|
|
16353
|
+
import path17 from "path";
|
|
16212
16354
|
var probedRepos = /* @__PURE__ */ new Set();
|
|
16213
16355
|
var SAMPLE_FANOUT_DIRS = 8;
|
|
16214
16356
|
var SAMPLE_READS = 20;
|
|
@@ -16235,8 +16377,8 @@ function probeObjectStore(repoRoot, trigger, stderr, args = []) {
|
|
|
16235
16377
|
return;
|
|
16236
16378
|
}
|
|
16237
16379
|
fields.push(`objectsDir=${objectsDir}`);
|
|
16238
|
-
if (typeof
|
|
16239
|
-
const stats =
|
|
16380
|
+
if (typeof fs18.statfsSync === "function") {
|
|
16381
|
+
const stats = fs18.statfsSync(objectsDir);
|
|
16240
16382
|
fields.push(
|
|
16241
16383
|
`fsType=${stats.type}`,
|
|
16242
16384
|
`freeBytes=${stats.bavail * stats.bsize}`
|
|
@@ -16244,18 +16386,18 @@ function probeObjectStore(repoRoot, trigger, stderr, args = []) {
|
|
|
16244
16386
|
}
|
|
16245
16387
|
const { estimate, sample } = sampleLooseObjects(objectsDir);
|
|
16246
16388
|
fields.push(`looseEstimate=${estimate}`);
|
|
16247
|
-
const packDir =
|
|
16248
|
-
const packFiles =
|
|
16389
|
+
const packDir = path17.join(objectsDir, "pack");
|
|
16390
|
+
const packFiles = fs18.existsSync(packDir) ? fs18.readdirSync(packDir) : [];
|
|
16249
16391
|
fields.push(
|
|
16250
16392
|
`packs=${packFiles.filter((name) => name.endsWith(".pack")).length}`,
|
|
16251
16393
|
`promisorPacks=${packFiles.filter((name) => name.endsWith(".promisor")).length}`,
|
|
16252
|
-
`alternates=${
|
|
16394
|
+
`alternates=${fs18.existsSync(path17.join(objectsDir, "info", "alternates")) ? "yes" : "no"}`
|
|
16253
16395
|
);
|
|
16254
16396
|
fields.push(...timeSampleReads(sample));
|
|
16255
16397
|
const named = namedObjects(stderr);
|
|
16256
16398
|
for (const sha of named) {
|
|
16257
|
-
const loose =
|
|
16258
|
-
const stats =
|
|
16399
|
+
const loose = path17.join(objectsDir, sha.slice(0, 2), sha.slice(2));
|
|
16400
|
+
const stats = fs18.statSync(loose, { throwIfNoEntry: false });
|
|
16259
16401
|
fields.push(
|
|
16260
16402
|
stats ? `named=${sha.slice(0, 8)}(size=${stats.size},blocks=${stats.blocks})` : `named=${sha.slice(0, 8)}(no loose file)`
|
|
16261
16403
|
);
|
|
@@ -16275,30 +16417,30 @@ function probeObjectStore(repoRoot, trigger, stderr, args = []) {
|
|
|
16275
16417
|
}
|
|
16276
16418
|
}
|
|
16277
16419
|
function resolveObjectsDir(repoRoot) {
|
|
16278
|
-
let gitDir =
|
|
16279
|
-
const stats =
|
|
16420
|
+
let gitDir = path17.join(repoRoot, ".git");
|
|
16421
|
+
const stats = fs18.statSync(gitDir, { throwIfNoEntry: false });
|
|
16280
16422
|
if (!stats) return null;
|
|
16281
16423
|
if (stats.isFile()) {
|
|
16282
|
-
const match = /^gitdir:\s*(.+)$/m.exec(
|
|
16424
|
+
const match = /^gitdir:\s*(.+)$/m.exec(fs18.readFileSync(gitDir, "utf-8"));
|
|
16283
16425
|
if (!match) return null;
|
|
16284
|
-
gitDir =
|
|
16426
|
+
gitDir = path17.resolve(repoRoot, match[1].trim());
|
|
16285
16427
|
}
|
|
16286
|
-
const commonDir =
|
|
16287
|
-
if (
|
|
16288
|
-
gitDir =
|
|
16428
|
+
const commonDir = path17.join(gitDir, "commondir");
|
|
16429
|
+
if (fs18.existsSync(commonDir)) {
|
|
16430
|
+
gitDir = path17.resolve(gitDir, fs18.readFileSync(commonDir, "utf-8").trim());
|
|
16289
16431
|
}
|
|
16290
|
-
return
|
|
16432
|
+
return path17.join(gitDir, "objects");
|
|
16291
16433
|
}
|
|
16292
16434
|
function sampleLooseObjects(objectsDir) {
|
|
16293
|
-
const fanout =
|
|
16435
|
+
const fanout = fs18.readdirSync(objectsDir).filter((name) => /^[0-9a-f]{2}$/.test(name)).sort().slice(0, SAMPLE_FANOUT_DIRS);
|
|
16294
16436
|
let counted = 0;
|
|
16295
16437
|
const sample = [];
|
|
16296
16438
|
for (const dir of fanout) {
|
|
16297
|
-
const files =
|
|
16439
|
+
const files = fs18.readdirSync(path17.join(objectsDir, dir));
|
|
16298
16440
|
counted += files.length;
|
|
16299
16441
|
for (const file of files) {
|
|
16300
16442
|
if (sample.length >= SAMPLE_READS) break;
|
|
16301
|
-
sample.push(
|
|
16443
|
+
sample.push(path17.join(objectsDir, dir, file));
|
|
16302
16444
|
}
|
|
16303
16445
|
}
|
|
16304
16446
|
return {
|
|
@@ -16316,14 +16458,14 @@ function timeSampleReads(sample) {
|
|
|
16316
16458
|
for (const file of sample) {
|
|
16317
16459
|
const readStarted = performance.now();
|
|
16318
16460
|
try {
|
|
16319
|
-
const stats =
|
|
16461
|
+
const stats = fs18.statSync(file);
|
|
16320
16462
|
if (!stats.isFile()) continue;
|
|
16321
16463
|
if (stats.size > 0 && stats.blocks === 0) dataless++;
|
|
16322
|
-
const fd =
|
|
16464
|
+
const fd = fs18.openSync(file, "r");
|
|
16323
16465
|
try {
|
|
16324
|
-
|
|
16466
|
+
fs18.readSync(fd, buffer, 0, SAMPLE_READ_BYTES, 0);
|
|
16325
16467
|
} finally {
|
|
16326
|
-
|
|
16468
|
+
fs18.closeSync(fd);
|
|
16327
16469
|
}
|
|
16328
16470
|
} catch (err) {
|
|
16329
16471
|
readErrors++;
|
|
@@ -16348,7 +16490,7 @@ function namedObjects(stderr) {
|
|
|
16348
16490
|
return [...shas].slice(0, MAX_NAMED_OBJECTS);
|
|
16349
16491
|
}
|
|
16350
16492
|
function sampleSha(file) {
|
|
16351
|
-
const sha =
|
|
16493
|
+
const sha = path17.basename(path17.dirname(file)) + path17.basename(file);
|
|
16352
16494
|
return OBJECT_SHA.test(sha) ? sha : null;
|
|
16353
16495
|
}
|
|
16354
16496
|
function failedTreeish(args) {
|
|
@@ -16425,12 +16567,12 @@ function locateGitRead(fields, repoRoot, objectsDir, treeish, fallback) {
|
|
|
16425
16567
|
fields.push(
|
|
16426
16568
|
`stalledPath=${JSON.stringify(stalled.path.slice(0, MAX_STALLED_PATH_CHARS))}`
|
|
16427
16569
|
);
|
|
16428
|
-
const loose =
|
|
16570
|
+
const loose = path17.join(
|
|
16429
16571
|
objectsDir,
|
|
16430
16572
|
stalled.sha.slice(0, 2),
|
|
16431
16573
|
stalled.sha.slice(2)
|
|
16432
16574
|
);
|
|
16433
|
-
const stats =
|
|
16575
|
+
const stats = fs18.statSync(loose, { throwIfNoEntry: false });
|
|
16434
16576
|
fields.push(
|
|
16435
16577
|
stats ? `stalledLoose=size=${stats.size},blocks=${stats.blocks}` : "stalledLoose=no"
|
|
16436
16578
|
);
|
|
@@ -16466,6 +16608,8 @@ function failureText(result) {
|
|
|
16466
16608
|
// src/git-traces/git-ops.ts
|
|
16467
16609
|
var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
16468
16610
|
var DEFAULT_BUNDLE_TIMEOUT_MS = 3e5;
|
|
16611
|
+
var MAX_BASELINE_BUNDLE_BYTES = 1024 * 1024 * 1024;
|
|
16612
|
+
var DEFAULT_UNTRACKED_INDEX_TIMEOUT_MS = 3e5;
|
|
16469
16613
|
var FULL_TREE_SCAN_TIMEOUT_MS = 3e5;
|
|
16470
16614
|
function resolveGitCommandTimeoutMs(fallback = DEFAULT_GIT_COMMAND_TIMEOUT_MS) {
|
|
16471
16615
|
const raw = process.env.HILLCLIMB_GIT_COMMAND_TIMEOUT_MS;
|
|
@@ -16654,8 +16798,8 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
16654
16798
|
]);
|
|
16655
16799
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
16656
16800
|
function isExcludedSnapshotPath(filePath) {
|
|
16657
|
-
if (EXCLUDED_SNAPSHOT_BASENAMES.has(
|
|
16658
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
16801
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path18.basename(filePath))) return true;
|
|
16802
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path18.extname(filePath).toLowerCase());
|
|
16659
16803
|
}
|
|
16660
16804
|
function isBinaryBuffer(buffer) {
|
|
16661
16805
|
return buffer.includes(0);
|
|
@@ -16676,16 +16820,16 @@ function readTreeBlobHead(repoRoot, sha) {
|
|
|
16676
16820
|
function readWorkingFileHead(absPath) {
|
|
16677
16821
|
let fd = null;
|
|
16678
16822
|
try {
|
|
16679
|
-
fd =
|
|
16823
|
+
fd = fs19.openSync(absPath, "r");
|
|
16680
16824
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
16681
|
-
const bytesRead =
|
|
16825
|
+
const bytesRead = fs19.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
16682
16826
|
return buffer.subarray(0, bytesRead);
|
|
16683
16827
|
} catch {
|
|
16684
16828
|
return null;
|
|
16685
16829
|
} finally {
|
|
16686
16830
|
if (fd !== null) {
|
|
16687
16831
|
try {
|
|
16688
|
-
|
|
16832
|
+
fs19.closeSync(fd);
|
|
16689
16833
|
} catch {
|
|
16690
16834
|
}
|
|
16691
16835
|
}
|
|
@@ -16759,6 +16903,7 @@ function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS, elapsedMs
|
|
|
16759
16903
|
const stdout = outputToString(failure.stdout);
|
|
16760
16904
|
wrapped.gitStdout = stdout ? truncateOutput(stdout) : void 0;
|
|
16761
16905
|
wrapped.isGitMissingObject = !isTimeout && failure.code !== "ENOBUFS" && matchesMissingObject(stderr);
|
|
16906
|
+
wrapped.isGitMissingWorktreeFile = args[0] === "update-index" && failure.status === 128 && !failure.signal && (!failure.code || failure.code === "EPIPE") && !!stderr?.includes("does not exist and --remove not passed");
|
|
16762
16907
|
return wrapped;
|
|
16763
16908
|
}
|
|
16764
16909
|
function commitTree(repoRoot, treeSha, message) {
|
|
@@ -16785,6 +16930,44 @@ function gitBuffer(repoRoot, args, options = {}) {
|
|
|
16785
16930
|
const detached = options.killProcessGroup && process.platform !== "win32";
|
|
16786
16931
|
const started = Date.now();
|
|
16787
16932
|
try {
|
|
16933
|
+
if (options.diagnostics) {
|
|
16934
|
+
const diagnostics = startGitDiagnostics(options.diagnostics.bundleFile);
|
|
16935
|
+
let status = "failed";
|
|
16936
|
+
try {
|
|
16937
|
+
const result = spawnSync2("git", args, {
|
|
16938
|
+
cwd: repoRoot,
|
|
16939
|
+
env: {
|
|
16940
|
+
...options.env ?? process.env,
|
|
16941
|
+
...diagnostics.env
|
|
16942
|
+
},
|
|
16943
|
+
input: options.input,
|
|
16944
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16945
|
+
...EXEC_OPTS,
|
|
16946
|
+
timeout,
|
|
16947
|
+
...detached ? { detached: true } : {}
|
|
16948
|
+
});
|
|
16949
|
+
if (result.error || result.status !== 0) {
|
|
16950
|
+
throw Object.assign(result.error ?? new Error("Git command failed"), {
|
|
16951
|
+
pid: result.pid,
|
|
16952
|
+
status: result.status,
|
|
16953
|
+
signal: result.signal,
|
|
16954
|
+
stdout: result.stdout,
|
|
16955
|
+
stderr: result.stderr
|
|
16956
|
+
});
|
|
16957
|
+
}
|
|
16958
|
+
status = "ok";
|
|
16959
|
+
return result.stdout;
|
|
16960
|
+
} finally {
|
|
16961
|
+
const elapsedMs = Date.now() - started;
|
|
16962
|
+
const summary = diagnostics.finish();
|
|
16963
|
+
if (status === "failed" || elapsedMs >= 1e3) {
|
|
16964
|
+
appendLog(
|
|
16965
|
+
"info",
|
|
16966
|
+
`git-traces: git diagnostics (stage=${options.diagnostics.stage}, status=${status}, elapsedMs=${elapsedMs}, ${summary})`
|
|
16967
|
+
);
|
|
16968
|
+
}
|
|
16969
|
+
}
|
|
16970
|
+
}
|
|
16788
16971
|
return execFileSync2("git", args, {
|
|
16789
16972
|
cwd: repoRoot,
|
|
16790
16973
|
...options.env ? { env: options.env } : {},
|
|
@@ -16850,23 +17033,23 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
16850
17033
|
let tmpDir = null;
|
|
16851
17034
|
let copy = null;
|
|
16852
17035
|
try {
|
|
16853
|
-
const indexPath =
|
|
17036
|
+
const indexPath = path18.resolve(
|
|
16854
17037
|
repoRoot,
|
|
16855
17038
|
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
16856
17039
|
);
|
|
16857
|
-
tmpDir =
|
|
16858
|
-
copy =
|
|
16859
|
-
const fd =
|
|
17040
|
+
tmpDir = fs19.mkdtempSync(path18.join(os8.tmpdir(), "hillclimb-index-"));
|
|
17041
|
+
copy = path18.join(tmpDir, "index");
|
|
17042
|
+
const fd = fs19.openSync(indexPath, "r");
|
|
16860
17043
|
let stat;
|
|
16861
17044
|
let data;
|
|
16862
17045
|
try {
|
|
16863
|
-
stat =
|
|
16864
|
-
data =
|
|
17046
|
+
stat = fs19.fstatSync(fd);
|
|
17047
|
+
data = fs19.readFileSync(fd);
|
|
16865
17048
|
} finally {
|
|
16866
|
-
|
|
17049
|
+
fs19.closeSync(fd);
|
|
16867
17050
|
}
|
|
16868
|
-
|
|
16869
|
-
|
|
17051
|
+
fs19.writeFileSync(copy, data);
|
|
17052
|
+
fs19.utimesSync(copy, stat.atime, stat.mtime);
|
|
16870
17053
|
} catch (err) {
|
|
16871
17054
|
const missing = err.code === "ENOENT";
|
|
16872
17055
|
appendLog(
|
|
@@ -16878,7 +17061,7 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
16878
17061
|
try {
|
|
16879
17062
|
return run(copy ? privateIndexEnv(copy) : null);
|
|
16880
17063
|
} finally {
|
|
16881
|
-
if (tmpDir)
|
|
17064
|
+
if (tmpDir) fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
16882
17065
|
}
|
|
16883
17066
|
}
|
|
16884
17067
|
function privateIndexEnv(indexPath) {
|
|
@@ -17295,8 +17478,8 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
17295
17478
|
options.omittedFiles?.push(...omittedFiles);
|
|
17296
17479
|
return timeCaptureStage(repoRoot, "tracked-tree-filter", () => {
|
|
17297
17480
|
if (omittedFiles.length === 0) return treeSha;
|
|
17298
|
-
const tmpIndex =
|
|
17299
|
-
|
|
17481
|
+
const tmpIndex = path18.join(
|
|
17482
|
+
os8.tmpdir(),
|
|
17300
17483
|
`hillclimb-filter-${Date.now()}-${process.pid}`
|
|
17301
17484
|
);
|
|
17302
17485
|
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
@@ -17310,7 +17493,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
17310
17493
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
17311
17494
|
} finally {
|
|
17312
17495
|
try {
|
|
17313
|
-
|
|
17496
|
+
fs19.unlinkSync(tmpIndex);
|
|
17314
17497
|
} catch {
|
|
17315
17498
|
}
|
|
17316
17499
|
}
|
|
@@ -17335,8 +17518,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17335
17518
|
if (!relPath) continue;
|
|
17336
17519
|
candidates++;
|
|
17337
17520
|
try {
|
|
17338
|
-
const absPath =
|
|
17339
|
-
const stat =
|
|
17521
|
+
const absPath = path18.join(repoRoot, relPath);
|
|
17522
|
+
const stat = fs19.lstatSync(absPath);
|
|
17340
17523
|
const reason = classifyOmission(
|
|
17341
17524
|
relPath,
|
|
17342
17525
|
stat.size,
|
|
@@ -17367,11 +17550,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17367
17550
|
`git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
|
|
17368
17551
|
);
|
|
17369
17552
|
if (kept.length === 0) return null;
|
|
17370
|
-
const tmpDir =
|
|
17553
|
+
const tmpDir = fs19.mkdtempSync(path18.join(os8.tmpdir(), "hillclimb-untracked-"));
|
|
17371
17554
|
const env = {
|
|
17372
17555
|
...process.env,
|
|
17373
17556
|
LC_ALL: "C",
|
|
17374
|
-
GIT_INDEX_FILE:
|
|
17557
|
+
GIT_INDEX_FILE: path18.join(tmpDir, "index")
|
|
17375
17558
|
};
|
|
17376
17559
|
try {
|
|
17377
17560
|
timeCaptureStage(
|
|
@@ -17379,7 +17562,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17379
17562
|
"untracked-hash-and-index",
|
|
17380
17563
|
() => gitBuffer(repoRoot, ["update-index", "--add", "-z", "--stdin"], {
|
|
17381
17564
|
input: `${kept.join("\0")}\0`,
|
|
17382
|
-
env
|
|
17565
|
+
env,
|
|
17566
|
+
timeoutMs: resolveGitCommandTimeoutMs(
|
|
17567
|
+
DEFAULT_UNTRACKED_INDEX_TIMEOUT_MS
|
|
17568
|
+
),
|
|
17569
|
+
diagnostics: { stage: "untracked-hash-and-index" }
|
|
17383
17570
|
})
|
|
17384
17571
|
);
|
|
17385
17572
|
return timeCaptureStage(
|
|
@@ -17389,8 +17576,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17389
17576
|
);
|
|
17390
17577
|
} catch (err) {
|
|
17391
17578
|
const failure = err;
|
|
17392
|
-
if (attempt !== 1 ||
|
|
17393
|
-
throw err;
|
|
17579
|
+
if (attempt !== 1 || !failure.isGitMissingWorktreeFile) throw err;
|
|
17394
17580
|
appendLog(
|
|
17395
17581
|
"warn",
|
|
17396
17582
|
`git-traces: retrying untracked capture after files disappeared (repo=${repoRoot}, attempt=${attempt}, maxAttempts=2)`
|
|
@@ -17399,7 +17585,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17399
17585
|
return buildUntrackedTree(repoRoot, options, attempt + 1);
|
|
17400
17586
|
} finally {
|
|
17401
17587
|
try {
|
|
17402
|
-
|
|
17588
|
+
fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
17403
17589
|
} catch {
|
|
17404
17590
|
}
|
|
17405
17591
|
}
|
|
@@ -17470,8 +17656,8 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
17470
17656
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
|
|
17471
17657
|
return filteredTrackedTree;
|
|
17472
17658
|
if (filteredTrackedTree === EMPTY_TREE_SHA) return untrackedTree;
|
|
17473
|
-
const tmpIndex =
|
|
17474
|
-
|
|
17659
|
+
const tmpIndex = path18.join(
|
|
17660
|
+
os8.tmpdir(),
|
|
17475
17661
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
17476
17662
|
);
|
|
17477
17663
|
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
@@ -17499,7 +17685,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
17499
17685
|
);
|
|
17500
17686
|
} finally {
|
|
17501
17687
|
try {
|
|
17502
|
-
|
|
17688
|
+
fs19.unlinkSync(tmpIndex);
|
|
17503
17689
|
} catch {
|
|
17504
17690
|
}
|
|
17505
17691
|
}
|
|
@@ -17514,19 +17700,26 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
17514
17700
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
17515
17701
|
let tmpDir;
|
|
17516
17702
|
try {
|
|
17517
|
-
tmpDir =
|
|
17518
|
-
|
|
17703
|
+
tmpDir = fs19.mkdtempSync(
|
|
17704
|
+
path18.join(os8.tmpdir(), `hillclimb-bundle-${process.pid}-`)
|
|
17519
17705
|
);
|
|
17520
|
-
const tmpFile =
|
|
17706
|
+
const tmpFile = path18.join(tmpDir, "baseline.bundle");
|
|
17521
17707
|
timeCaptureStage(
|
|
17522
17708
|
repoRoot,
|
|
17523
17709
|
"bundle-create",
|
|
17524
17710
|
() => gitBuffer(repoRoot, ["bundle", "create", tmpFile, orphanRef], {
|
|
17525
17711
|
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS),
|
|
17526
|
-
killProcessGroup: true
|
|
17712
|
+
killProcessGroup: true,
|
|
17713
|
+
diagnostics: { stage: "bundle-create", bundleFile: tmpFile }
|
|
17527
17714
|
})
|
|
17528
17715
|
);
|
|
17529
|
-
const
|
|
17716
|
+
const bytes = fs19.statSync(tmpFile).size;
|
|
17717
|
+
if (bytes > MAX_BASELINE_BUNDLE_BYTES) {
|
|
17718
|
+
throw new Error(
|
|
17719
|
+
`Git baseline bundle exceeds the upload limit (bytes=${bytes}, maxBytes=${MAX_BASELINE_BUNDLE_BYTES}); capture retained`
|
|
17720
|
+
);
|
|
17721
|
+
}
|
|
17722
|
+
const bundle = fs19.readFileSync(tmpFile);
|
|
17530
17723
|
appendLog(
|
|
17531
17724
|
"info",
|
|
17532
17725
|
`git-traces: bundle inventory (repo=${repoRoot}, bytes=${bundle.length})`
|
|
@@ -17534,14 +17727,28 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
17534
17727
|
return bundle;
|
|
17535
17728
|
} finally {
|
|
17536
17729
|
try {
|
|
17537
|
-
if (tmpDir)
|
|
17730
|
+
if (tmpDir) fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
17538
17731
|
} catch {
|
|
17539
17732
|
}
|
|
17540
17733
|
deleteRef(repoRoot, orphanRef);
|
|
17541
17734
|
}
|
|
17542
17735
|
}
|
|
17543
17736
|
var MAX_PATCH_GZ_BYTES = 500 * 1024 * 1024;
|
|
17544
|
-
|
|
17737
|
+
var MAX_PATCH_RAW_BYTES = 1023 * 1024 * 1024 - 1;
|
|
17738
|
+
var GitPatchTooLargeError = class extends Error {
|
|
17739
|
+
constructor(rawBytes, maxRawBytes) {
|
|
17740
|
+
super(
|
|
17741
|
+
`git diff raw output exceeded the ${maxRawBytes}-byte replay limit (observedBytes=${rawBytes}); a new baseline is required`
|
|
17742
|
+
);
|
|
17743
|
+
this.rawBytes = rawBytes;
|
|
17744
|
+
this.maxRawBytes = maxRawBytes;
|
|
17745
|
+
this.name = "GitPatchTooLargeError";
|
|
17746
|
+
}
|
|
17747
|
+
};
|
|
17748
|
+
function resolveMaxPatchRawBytes(value) {
|
|
17749
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? Math.min(value, MAX_PATCH_RAW_BYTES) : MAX_PATCH_RAW_BYTES;
|
|
17750
|
+
}
|
|
17751
|
+
function gitGzipStream(repoRoot, args, maxGzBytes, maxRawBytes) {
|
|
17545
17752
|
return new Promise((resolve, reject) => {
|
|
17546
17753
|
const started = Date.now();
|
|
17547
17754
|
const child = spawn2("git", args, {
|
|
@@ -17558,6 +17765,7 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
17558
17765
|
let settled = false;
|
|
17559
17766
|
let timedOut = false;
|
|
17560
17767
|
let overflowed = false;
|
|
17768
|
+
let rawOverflowed = false;
|
|
17561
17769
|
let streamError = null;
|
|
17562
17770
|
let exitCode = null;
|
|
17563
17771
|
let exitSignal = null;
|
|
@@ -17584,6 +17792,8 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
17584
17792
|
);
|
|
17585
17793
|
probeObjectStore(repoRoot, "timeout", wrapped.gitStderr, args);
|
|
17586
17794
|
reject(wrapped);
|
|
17795
|
+
} else if (rawOverflowed) {
|
|
17796
|
+
reject(new GitPatchTooLargeError(rawBytes, maxRawBytes));
|
|
17587
17797
|
} else if (overflowed) {
|
|
17588
17798
|
reject(
|
|
17589
17799
|
createGitError(args, { code: "ENOBUFS", maxBufferBytes: maxGzBytes })
|
|
@@ -17602,8 +17812,22 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
17602
17812
|
resolve({ gz: Buffer.concat(gzChunks), rawBytes });
|
|
17603
17813
|
}
|
|
17604
17814
|
};
|
|
17815
|
+
const stopForOverflow = () => {
|
|
17816
|
+
child.stdout.unpipe(gzip);
|
|
17817
|
+
child.stdout.resume();
|
|
17818
|
+
child.kill("SIGTERM");
|
|
17819
|
+
gzip.destroy();
|
|
17820
|
+
gzChunks.length = 0;
|
|
17821
|
+
gzipDone = true;
|
|
17822
|
+
finish();
|
|
17823
|
+
};
|
|
17605
17824
|
child.stdout.on("data", (chunk) => {
|
|
17825
|
+
if (rawOverflowed || overflowed) return;
|
|
17606
17826
|
rawBytes += chunk.length;
|
|
17827
|
+
if (rawBytes > maxRawBytes) {
|
|
17828
|
+
rawOverflowed = true;
|
|
17829
|
+
stopForOverflow();
|
|
17830
|
+
}
|
|
17607
17831
|
});
|
|
17608
17832
|
child.stdout.on("error", (err) => {
|
|
17609
17833
|
streamError ??= err;
|
|
@@ -17612,17 +17836,11 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
17612
17836
|
});
|
|
17613
17837
|
child.stdout.pipe(gzip);
|
|
17614
17838
|
gzip.on("data", (chunk) => {
|
|
17839
|
+
if (rawOverflowed || overflowed) return;
|
|
17615
17840
|
gzBytes += chunk.length;
|
|
17616
17841
|
if (gzBytes > maxGzBytes) {
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
child.stdout.unpipe(gzip);
|
|
17620
|
-
child.stdout.resume();
|
|
17621
|
-
child.kill("SIGTERM");
|
|
17622
|
-
gzip.destroy();
|
|
17623
|
-
gzipDone = true;
|
|
17624
|
-
finish();
|
|
17625
|
-
}
|
|
17842
|
+
overflowed = true;
|
|
17843
|
+
stopForOverflow();
|
|
17626
17844
|
return;
|
|
17627
17845
|
}
|
|
17628
17846
|
gzChunks.push(chunk);
|
|
@@ -17632,7 +17850,7 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
17632
17850
|
finish();
|
|
17633
17851
|
});
|
|
17634
17852
|
gzip.on("error", (err) => {
|
|
17635
|
-
if (!overflowed) streamError ??= err;
|
|
17853
|
+
if (!overflowed && !rawOverflowed) streamError ??= err;
|
|
17636
17854
|
gzipDone = true;
|
|
17637
17855
|
finish();
|
|
17638
17856
|
});
|
|
@@ -17673,7 +17891,8 @@ async function createTreeDiffPatchGz(repoRoot, fromTreeSha, toTreeSha, options =
|
|
|
17673
17891
|
const { gz, rawBytes } = await gitGzipStream(
|
|
17674
17892
|
repoRoot,
|
|
17675
17893
|
["diff", "--binary", filteredFromTreeSha, filteredToTreeSha],
|
|
17676
|
-
options.maxPatchGzBytes ?? MAX_PATCH_GZ_BYTES
|
|
17894
|
+
options.maxPatchGzBytes ?? MAX_PATCH_GZ_BYTES,
|
|
17895
|
+
resolveMaxPatchRawBytes(options.maxPatchRawBytes)
|
|
17677
17896
|
);
|
|
17678
17897
|
if (rawBytes === 0) return null;
|
|
17679
17898
|
return gz;
|
|
@@ -17753,7 +17972,7 @@ function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersio
|
|
|
17753
17972
|
...omittedFiles.length > 0 ? { omittedFiles } : {}
|
|
17754
17973
|
},
|
|
17755
17974
|
author: { name: authorName, email: authorEmail },
|
|
17756
|
-
hostname:
|
|
17975
|
+
hostname: os8.hostname(),
|
|
17757
17976
|
cliVersion,
|
|
17758
17977
|
commits,
|
|
17759
17978
|
...lineage,
|
|
@@ -17845,9 +18064,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
17845
18064
|
oldPath
|
|
17846
18065
|
});
|
|
17847
18066
|
} else {
|
|
17848
|
-
const
|
|
17849
|
-
indexByPath.set(
|
|
17850
|
-
files.push({ path:
|
|
18067
|
+
const path29 = parts[parts.length - 1];
|
|
18068
|
+
indexByPath.set(path29, files.length);
|
|
18069
|
+
files.push({ path: path29, status, additions: 0, deletions: 0 });
|
|
17851
18070
|
}
|
|
17852
18071
|
}
|
|
17853
18072
|
for (const line of numstat.split("\n")) {
|
|
@@ -17931,11 +18150,11 @@ function countScopedTurnTreeRefs(repoRoot, sessionId, epochPrefix3) {
|
|
|
17931
18150
|
|
|
17932
18151
|
// src/git-traces/session-state.ts
|
|
17933
18152
|
import crypto6 from "crypto";
|
|
17934
|
-
import
|
|
17935
|
-
import
|
|
17936
|
-
import
|
|
18153
|
+
import fs20 from "fs";
|
|
18154
|
+
import os9 from "os";
|
|
18155
|
+
import path19 from "path";
|
|
17937
18156
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
17938
|
-
var DEFAULT_STATE_DIR2 =
|
|
18157
|
+
var DEFAULT_STATE_DIR2 = path19.join(os9.homedir(), ".hillclimb", "git-traces");
|
|
17939
18158
|
var LOCK_RETRIES2 = 120;
|
|
17940
18159
|
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
17941
18160
|
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS2 = 10 * 60 * 1e3;
|
|
@@ -17960,16 +18179,16 @@ function stateDir3() {
|
|
|
17960
18179
|
}
|
|
17961
18180
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
17962
18181
|
const hash = crypto6.createHash("sha256").update(
|
|
17963
|
-
sessionId ? `${
|
|
18182
|
+
sessionId ? `${path19.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path19.resolve(repoRoot)}\0${tool}`
|
|
17964
18183
|
).digest("hex").slice(0, 16);
|
|
17965
|
-
return
|
|
18184
|
+
return path19.join(stateDir3(), `${hash}.json`);
|
|
17966
18185
|
}
|
|
17967
18186
|
function lockFileForRepo(repoRoot, tool, sessionId) {
|
|
17968
18187
|
return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
|
|
17969
18188
|
}
|
|
17970
18189
|
async function readStateFile(file) {
|
|
17971
18190
|
try {
|
|
17972
|
-
const raw = await
|
|
18191
|
+
const raw = await fs20.promises.readFile(file, "utf-8");
|
|
17973
18192
|
const parsed = JSON.parse(raw);
|
|
17974
18193
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
|
|
17975
18194
|
return null;
|
|
@@ -17983,7 +18202,7 @@ async function readStoredStateFile(file) {
|
|
|
17983
18202
|
const state = await readStateFile(file);
|
|
17984
18203
|
if (!state) return null;
|
|
17985
18204
|
try {
|
|
17986
|
-
return { state, mtimeMs: (await
|
|
18205
|
+
return { state, mtimeMs: (await fs20.promises.stat(file)).mtimeMs };
|
|
17987
18206
|
} catch {
|
|
17988
18207
|
return null;
|
|
17989
18208
|
}
|
|
@@ -17991,26 +18210,26 @@ async function readStoredStateFile(file) {
|
|
|
17991
18210
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
17992
18211
|
let entries;
|
|
17993
18212
|
try {
|
|
17994
|
-
entries = await
|
|
18213
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
17995
18214
|
} catch {
|
|
17996
18215
|
return [];
|
|
17997
18216
|
}
|
|
17998
18217
|
const states = [];
|
|
17999
18218
|
for (const entry of entries) {
|
|
18000
18219
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
18001
|
-
const file =
|
|
18220
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18002
18221
|
const state = await readStateFile(file);
|
|
18003
18222
|
if (!state) continue;
|
|
18004
18223
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
18005
18224
|
continue;
|
|
18006
18225
|
}
|
|
18007
|
-
if (
|
|
18008
|
-
if (
|
|
18226
|
+
if (path19.resolve(state.repoRoot) !== path19.resolve(repoRoot)) continue;
|
|
18227
|
+
if (path19.resolve(file) !== path19.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
18009
18228
|
continue;
|
|
18010
18229
|
}
|
|
18011
18230
|
let mtimeMs = 0;
|
|
18012
18231
|
try {
|
|
18013
|
-
mtimeMs = (await
|
|
18232
|
+
mtimeMs = (await fs20.promises.stat(file)).mtimeMs;
|
|
18014
18233
|
} catch {
|
|
18015
18234
|
continue;
|
|
18016
18235
|
}
|
|
@@ -18021,26 +18240,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
18021
18240
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
18022
18241
|
let entries;
|
|
18023
18242
|
try {
|
|
18024
|
-
entries = await
|
|
18243
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
18025
18244
|
} catch {
|
|
18026
18245
|
return [];
|
|
18027
18246
|
}
|
|
18028
18247
|
const states = [];
|
|
18029
18248
|
for (const entry of entries) {
|
|
18030
18249
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
18031
|
-
const file =
|
|
18250
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18032
18251
|
const state = await readStateFile(file);
|
|
18033
18252
|
if (!state) continue;
|
|
18034
18253
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
18035
18254
|
continue;
|
|
18036
18255
|
}
|
|
18037
18256
|
if (state.sessionId !== sessionId) continue;
|
|
18038
|
-
if (
|
|
18257
|
+
if (path19.resolve(file) !== path19.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
18039
18258
|
continue;
|
|
18040
18259
|
}
|
|
18041
18260
|
let mtimeMs = 0;
|
|
18042
18261
|
try {
|
|
18043
|
-
mtimeMs = (await
|
|
18262
|
+
mtimeMs = (await fs20.promises.stat(file)).mtimeMs;
|
|
18044
18263
|
} catch {
|
|
18045
18264
|
continue;
|
|
18046
18265
|
}
|
|
@@ -18089,17 +18308,17 @@ async function writeLegacySessionState(state, tool) {
|
|
|
18089
18308
|
);
|
|
18090
18309
|
}
|
|
18091
18310
|
async function writeStateFile(file, state) {
|
|
18092
|
-
await
|
|
18311
|
+
await fs20.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
18093
18312
|
const tmp = `${file}.tmp`;
|
|
18094
|
-
await
|
|
18313
|
+
await fs20.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
18095
18314
|
mode: 384
|
|
18096
18315
|
});
|
|
18097
|
-
await
|
|
18316
|
+
await fs20.promises.rename(tmp, file);
|
|
18098
18317
|
}
|
|
18099
18318
|
async function touchSessionState(repoRoot, tool, sessionId) {
|
|
18100
18319
|
const now = /* @__PURE__ */ new Date();
|
|
18101
18320
|
try {
|
|
18102
|
-
await
|
|
18321
|
+
await fs20.promises.utimes(
|
|
18103
18322
|
stateFileForRepo(repoRoot, tool, sessionId),
|
|
18104
18323
|
now,
|
|
18105
18324
|
now
|
|
@@ -18109,7 +18328,7 @@ async function touchSessionState(repoRoot, tool, sessionId) {
|
|
|
18109
18328
|
}
|
|
18110
18329
|
async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
18111
18330
|
try {
|
|
18112
|
-
const stat = await
|
|
18331
|
+
const stat = await fs20.promises.stat(
|
|
18113
18332
|
stateFileForRepo(repoRoot, tool, sessionId)
|
|
18114
18333
|
);
|
|
18115
18334
|
return stat.mtimeMs;
|
|
@@ -18119,7 +18338,7 @@ async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
|
18119
18338
|
}
|
|
18120
18339
|
async function deleteStateFile(file) {
|
|
18121
18340
|
try {
|
|
18122
|
-
await
|
|
18341
|
+
await fs20.promises.unlink(file);
|
|
18123
18342
|
} catch {
|
|
18124
18343
|
}
|
|
18125
18344
|
}
|
|
@@ -18158,7 +18377,7 @@ async function acquireLock3(repoRoot, tool, sessionId, retries, delayMs, options
|
|
|
18158
18377
|
}
|
|
18159
18378
|
async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, delayMs, options = {}) {
|
|
18160
18379
|
const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
|
|
18161
|
-
await
|
|
18380
|
+
await fs20.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
18162
18381
|
const bounded = retries !== void 0 || delayMs !== void 0;
|
|
18163
18382
|
const attempts = retries ?? LOCK_RETRIES2;
|
|
18164
18383
|
const delay = delayMs ?? LOCK_RETRY_DELAY_MS2;
|
|
@@ -18168,9 +18387,9 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18168
18387
|
let extensionLogged = false;
|
|
18169
18388
|
for (let i = 0; ; i++) {
|
|
18170
18389
|
try {
|
|
18171
|
-
const fd = await
|
|
18390
|
+
const fd = await fs20.promises.open(
|
|
18172
18391
|
lockPath,
|
|
18173
|
-
|
|
18392
|
+
fs20.constants.O_CREAT | fs20.constants.O_EXCL | fs20.constants.O_WRONLY
|
|
18174
18393
|
);
|
|
18175
18394
|
await fd.write(String(process.pid));
|
|
18176
18395
|
await fd.close();
|
|
@@ -18179,7 +18398,7 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18179
18398
|
if (err.code !== "EEXIST") throw err;
|
|
18180
18399
|
let regularLockFile = false;
|
|
18181
18400
|
try {
|
|
18182
|
-
regularLockFile = (await
|
|
18401
|
+
regularLockFile = (await fs20.promises.lstat(lockPath)).isFile();
|
|
18183
18402
|
} catch (statErr) {
|
|
18184
18403
|
if (statErr.code === "ENOENT") {
|
|
18185
18404
|
i--;
|
|
@@ -18241,21 +18460,21 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18241
18460
|
}
|
|
18242
18461
|
async function releaseLock3(repoRoot, tool, sessionId) {
|
|
18243
18462
|
try {
|
|
18244
|
-
await
|
|
18463
|
+
await fs20.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
|
|
18245
18464
|
} catch {
|
|
18246
18465
|
}
|
|
18247
18466
|
}
|
|
18248
18467
|
async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now()) {
|
|
18249
18468
|
let entries;
|
|
18250
18469
|
try {
|
|
18251
|
-
entries = await
|
|
18470
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
18252
18471
|
} catch {
|
|
18253
18472
|
return 0;
|
|
18254
18473
|
}
|
|
18255
18474
|
let removed = 0;
|
|
18256
18475
|
for (const entry of entries) {
|
|
18257
18476
|
if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
|
|
18258
|
-
const file =
|
|
18477
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18259
18478
|
if (await reapLockIfStale(file, {
|
|
18260
18479
|
maxAgeMs: ttlMs,
|
|
18261
18480
|
preserveLiveOwner: true,
|
|
@@ -18270,6 +18489,8 @@ async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now())
|
|
|
18270
18489
|
// src/git-traces/pending-captures.ts
|
|
18271
18490
|
var MAX_CAPTURES = 128;
|
|
18272
18491
|
var MAX_PENDING_BYTES2 = 2 * 1024 * 1024 * 1024;
|
|
18492
|
+
var MAX_OVERSIZED_CAPTURE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
18493
|
+
var MAX_OVERSIZED_ARTIFACT_BYTES = 4 * 1024 * 1024 * 1024;
|
|
18273
18494
|
var PendingCaptureLimitError = class extends Error {
|
|
18274
18495
|
};
|
|
18275
18496
|
var STRANDED_MIN_AGE_MS = 10 * 60 * 1e3;
|
|
@@ -18317,12 +18538,12 @@ function withPendingCapture(record, work) {
|
|
|
18317
18538
|
return activeCapture.run({ record, save: () => saveCapture(record) }, work);
|
|
18318
18539
|
}
|
|
18319
18540
|
function queueDirectory(repoRoot, tool, sessionId) {
|
|
18320
|
-
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
18321
|
-
const key = crypto7.createHash("sha256").update(`${
|
|
18322
|
-
return
|
|
18541
|
+
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path20.join(os10.homedir(), ".hillclimb", "git-traces");
|
|
18542
|
+
const key = crypto7.createHash("sha256").update(`${path20.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
|
|
18543
|
+
return path20.join(stateDir4, "pending-v1", key);
|
|
18323
18544
|
}
|
|
18324
18545
|
function captureDirectory(record) {
|
|
18325
|
-
return
|
|
18546
|
+
return path20.join(
|
|
18326
18547
|
queueDirectory(record.repoRoot, record.tool, record.sessionId),
|
|
18327
18548
|
record.id
|
|
18328
18549
|
);
|
|
@@ -18330,20 +18551,20 @@ function captureDirectory(record) {
|
|
|
18330
18551
|
function writeDurable(file, body) {
|
|
18331
18552
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
18332
18553
|
try {
|
|
18333
|
-
const descriptor =
|
|
18554
|
+
const descriptor = fs21.openSync(temporary, "w", 384);
|
|
18334
18555
|
try {
|
|
18335
|
-
|
|
18336
|
-
|
|
18556
|
+
fs21.writeFileSync(descriptor, body);
|
|
18557
|
+
fs21.fsyncSync(descriptor);
|
|
18337
18558
|
} finally {
|
|
18338
|
-
|
|
18559
|
+
fs21.closeSync(descriptor);
|
|
18339
18560
|
}
|
|
18340
|
-
|
|
18561
|
+
fs21.renameSync(temporary, file);
|
|
18341
18562
|
try {
|
|
18342
|
-
const directory =
|
|
18563
|
+
const directory = fs21.openSync(path20.dirname(file), "r");
|
|
18343
18564
|
try {
|
|
18344
|
-
|
|
18565
|
+
fs21.fsyncSync(directory);
|
|
18345
18566
|
} finally {
|
|
18346
|
-
|
|
18567
|
+
fs21.closeSync(directory);
|
|
18347
18568
|
}
|
|
18348
18569
|
} catch (err) {
|
|
18349
18570
|
if (!["EINVAL", "ENOTSUP", "EBADF", "EPERM", "EISDIR"].includes(
|
|
@@ -18352,7 +18573,7 @@ function writeDurable(file, body) {
|
|
|
18352
18573
|
throw err;
|
|
18353
18574
|
}
|
|
18354
18575
|
} finally {
|
|
18355
|
-
if (
|
|
18576
|
+
if (fs21.existsSync(temporary)) fs21.unlinkSync(temporary);
|
|
18356
18577
|
}
|
|
18357
18578
|
}
|
|
18358
18579
|
function saveCapture(record) {
|
|
@@ -18363,7 +18584,7 @@ function saveCapture(record) {
|
|
|
18363
18584
|
let status = "failed";
|
|
18364
18585
|
try {
|
|
18365
18586
|
writeDurable(
|
|
18366
|
-
|
|
18587
|
+
path20.join(captureDirectory(record), "capture.json"),
|
|
18367
18588
|
JSON.stringify(record)
|
|
18368
18589
|
);
|
|
18369
18590
|
status = "ok";
|
|
@@ -18375,7 +18596,7 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
18375
18596
|
const directory = queueDirectory(repoRoot, tool, sessionId);
|
|
18376
18597
|
let entries;
|
|
18377
18598
|
try {
|
|
18378
|
-
entries =
|
|
18599
|
+
entries = fs21.readdirSync(directory, { withFileTypes: true });
|
|
18379
18600
|
} catch (err) {
|
|
18380
18601
|
if (err.code === "ENOENT") return [];
|
|
18381
18602
|
throw err;
|
|
@@ -18383,10 +18604,10 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
18383
18604
|
const records = [];
|
|
18384
18605
|
for (const entry of entries) {
|
|
18385
18606
|
if (!entry.isDirectory() || !/^[0-9a-f-]{36}$/.test(entry.name)) continue;
|
|
18386
|
-
const file =
|
|
18387
|
-
if (!
|
|
18607
|
+
const file = path20.join(directory, entry.name, "capture.json");
|
|
18608
|
+
if (!fs21.existsSync(file)) continue;
|
|
18388
18609
|
const record = JSON.parse(
|
|
18389
|
-
|
|
18610
|
+
fs21.readFileSync(file, "utf-8")
|
|
18390
18611
|
);
|
|
18391
18612
|
if (record.version !== 1 || record.id !== entry.name || record.repoRoot !== repoRoot || record.tool !== tool || record.sessionId !== sessionId) {
|
|
18392
18613
|
throw new Error(`Invalid pending Git capture: ${file}`);
|
|
@@ -18399,13 +18620,13 @@ function hasPendingCaptures(repoRoot, tool, sessionId) {
|
|
|
18399
18620
|
return listPendingCaptures2(repoRoot, tool, sessionId).length > 0;
|
|
18400
18621
|
}
|
|
18401
18622
|
function listPendingQueues() {
|
|
18402
|
-
const root =
|
|
18403
|
-
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
18623
|
+
const root = path20.join(
|
|
18624
|
+
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path20.join(os10.homedir(), ".hillclimb", "git-traces"),
|
|
18404
18625
|
"pending-v1"
|
|
18405
18626
|
);
|
|
18406
18627
|
let queueDirs;
|
|
18407
18628
|
try {
|
|
18408
|
-
queueDirs =
|
|
18629
|
+
queueDirs = fs21.readdirSync(root, { withFileTypes: true });
|
|
18409
18630
|
} catch (err) {
|
|
18410
18631
|
if (err.code === "ENOENT") return [];
|
|
18411
18632
|
throw err;
|
|
@@ -18413,15 +18634,15 @@ function listPendingQueues() {
|
|
|
18413
18634
|
const queues = [];
|
|
18414
18635
|
for (const queueDir of queueDirs) {
|
|
18415
18636
|
if (!queueDir.isDirectory()) continue;
|
|
18416
|
-
const directory =
|
|
18637
|
+
const directory = path20.join(root, queueDir.name);
|
|
18417
18638
|
let identity = null;
|
|
18418
18639
|
try {
|
|
18419
|
-
for (const entry of
|
|
18640
|
+
for (const entry of fs21.readdirSync(directory, { withFileTypes: true })) {
|
|
18420
18641
|
if (!entry.isDirectory()) continue;
|
|
18421
|
-
const file =
|
|
18422
|
-
if (!
|
|
18642
|
+
const file = path20.join(directory, entry.name, "capture.json");
|
|
18643
|
+
if (!fs21.existsSync(file)) continue;
|
|
18423
18644
|
identity = JSON.parse(
|
|
18424
|
-
|
|
18645
|
+
fs21.readFileSync(file, "utf-8")
|
|
18425
18646
|
);
|
|
18426
18647
|
break;
|
|
18427
18648
|
}
|
|
@@ -18454,15 +18675,15 @@ function noteReplayFailure(record, error, now = Date.now()) {
|
|
|
18454
18675
|
}
|
|
18455
18676
|
function queueDiskBytes(directory) {
|
|
18456
18677
|
let diskBytes = 0;
|
|
18457
|
-
if (
|
|
18458
|
-
for (const entry of
|
|
18678
|
+
if (fs21.existsSync(directory)) {
|
|
18679
|
+
for (const entry of fs21.readdirSync(directory, { withFileTypes: true })) {
|
|
18459
18680
|
if (!entry.isDirectory()) continue;
|
|
18460
|
-
for (const file of
|
|
18681
|
+
for (const file of fs21.readdirSync(path20.join(directory, entry.name), {
|
|
18461
18682
|
withFileTypes: true
|
|
18462
18683
|
})) {
|
|
18463
18684
|
if (file.isFile())
|
|
18464
|
-
diskBytes +=
|
|
18465
|
-
|
|
18685
|
+
diskBytes += fs21.statSync(
|
|
18686
|
+
path20.join(directory, entry.name, file.name)
|
|
18466
18687
|
).size;
|
|
18467
18688
|
}
|
|
18468
18689
|
}
|
|
@@ -18478,10 +18699,15 @@ function pendingBytes(records) {
|
|
|
18478
18699
|
return queueDiskBytes(
|
|
18479
18700
|
queueDirectory(first.repoRoot, first.tool, first.sessionId)
|
|
18480
18701
|
) + records.reduce(
|
|
18481
|
-
(sum, record) => sum + chargedBytes(record) + (
|
|
18702
|
+
(sum, record) => sum + chargedBytes(record) + (fs21.existsSync(path20.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
|
|
18482
18703
|
0
|
|
18483
18704
|
);
|
|
18484
18705
|
}
|
|
18706
|
+
function artifactByteLimit(records, captureId) {
|
|
18707
|
+
const record = records.length === 1 ? records[0] : void 0;
|
|
18708
|
+
const retained = record ? chargedBytes(record) : 0;
|
|
18709
|
+
return record?.id === captureId && record.oversized === true && retained >= 0 && retained <= MAX_OVERSIZED_CAPTURE_BYTES ? retained + MAX_OVERSIZED_ARTIFACT_BYTES : MAX_PENDING_BYTES2;
|
|
18710
|
+
}
|
|
18485
18711
|
function warnMissingBase(repoRoot, base) {
|
|
18486
18712
|
appendLog(
|
|
18487
18713
|
"warn",
|
|
@@ -18636,14 +18862,19 @@ function storePendingCapture(params) {
|
|
|
18636
18862
|
const pendingBefore = queueDiskBytes(
|
|
18637
18863
|
queueDirectory(params.repoRoot, params.tool, params.sessionId)
|
|
18638
18864
|
) + existing.reduce((sum, item) => sum + chargedBytes(item), 0);
|
|
18639
|
-
|
|
18640
|
-
if (existing.length
|
|
18865
|
+
let incomingBytes = retainedBytes + Buffer.byteLength(JSON.stringify(record));
|
|
18866
|
+
if (existing.length === 0 && pendingBefore === 0 && incomingBytes > MAX_PENDING_BYTES2) {
|
|
18867
|
+
record.oversized = true;
|
|
18868
|
+
incomingBytes = retainedBytes + Buffer.byteLength(JSON.stringify(record));
|
|
18869
|
+
}
|
|
18870
|
+
const maxBytes = record.oversized ? MAX_OVERSIZED_CAPTURE_BYTES : MAX_PENDING_BYTES2;
|
|
18871
|
+
if (existing.some((item) => item.oversized === true) || existing.length >= MAX_CAPTURES || pendingBefore + incomingBytes > maxBytes) {
|
|
18641
18872
|
throw new PendingCaptureLimitError(
|
|
18642
|
-
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes}, retainedBytes=${retainedBytes}, maxBytes=${
|
|
18873
|
+
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes}, retainedBytes=${retainedBytes}, maxBytes=${maxBytes}); existing captures retained`
|
|
18643
18874
|
);
|
|
18644
18875
|
}
|
|
18645
18876
|
const directory = captureDirectory(record);
|
|
18646
|
-
|
|
18877
|
+
fs21.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
18647
18878
|
const refRoot = `refs/hillclimb/pending-v1/${record.id}`;
|
|
18648
18879
|
let published = false;
|
|
18649
18880
|
try {
|
|
@@ -18681,12 +18912,12 @@ function storePendingCapture(params) {
|
|
|
18681
18912
|
durableDetails = `capture=${record.id}, durableCapturedAt=${durableCapturedAt}`;
|
|
18682
18913
|
appendLog(
|
|
18683
18914
|
"info",
|
|
18684
|
-
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)}, retainedBytes=${record.retainedBytes}, pendingBytes=${pendingBefore + incomingBytes})`
|
|
18915
|
+
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)}, retainedBytes=${record.retainedBytes}, pendingBytes=${pendingBefore + incomingBytes}, oversized=${record.oversized === true})`
|
|
18685
18916
|
);
|
|
18686
18917
|
return record;
|
|
18687
18918
|
} finally {
|
|
18688
18919
|
if (!published) {
|
|
18689
|
-
|
|
18920
|
+
fs21.rmSync(directory, { recursive: true, force: true });
|
|
18690
18921
|
for (const label of [
|
|
18691
18922
|
"snapshot",
|
|
18692
18923
|
"tree",
|
|
@@ -18717,7 +18948,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
18717
18948
|
let status = "failed";
|
|
18718
18949
|
try {
|
|
18719
18950
|
if (saved) {
|
|
18720
|
-
const bytes =
|
|
18951
|
+
const bytes = fs21.readFileSync(path20.join(directory, saved.file));
|
|
18721
18952
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256) {
|
|
18722
18953
|
throw new Error(
|
|
18723
18954
|
`Pending Git artifact is corrupt: ${saved.file}; capture retained`
|
|
@@ -18731,23 +18962,35 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
18731
18962
|
context.record.tool,
|
|
18732
18963
|
context.record.sessionId
|
|
18733
18964
|
);
|
|
18734
|
-
if (pendingBytes(records) + buffer.byteLength > MAX_PENDING_BYTES2) {
|
|
18735
|
-
throw new Error(
|
|
18736
|
-
`Pending Git artifact byte limit reached (${MAX_PENDING_BYTES2}); capture retained`
|
|
18737
|
-
);
|
|
18738
|
-
}
|
|
18739
18965
|
const file = `${crypto7.createHash("sha256").update(key).digest("hex")}.artifact`;
|
|
18740
|
-
|
|
18741
|
-
context.record.uploads[key] = {
|
|
18966
|
+
const upload = {
|
|
18742
18967
|
file,
|
|
18743
18968
|
bytes: buffer.byteLength,
|
|
18744
18969
|
sha256: crypto7.createHash("sha256").update(buffer).digest("hex")
|
|
18745
18970
|
};
|
|
18971
|
+
const nextRecordBytes = Buffer.byteLength(
|
|
18972
|
+
JSON.stringify({
|
|
18973
|
+
...context.record,
|
|
18974
|
+
uploads: { ...context.record.uploads, [key]: upload }
|
|
18975
|
+
})
|
|
18976
|
+
);
|
|
18977
|
+
const recordGrowth = Math.max(
|
|
18978
|
+
0,
|
|
18979
|
+
nextRecordBytes - fs21.statSync(path20.join(directory, "capture.json")).size
|
|
18980
|
+
);
|
|
18981
|
+
const maxBytes = artifactByteLimit(records, context.record.id);
|
|
18982
|
+
if (pendingBytes(records) + buffer.byteLength + recordGrowth > maxBytes) {
|
|
18983
|
+
throw new Error(
|
|
18984
|
+
`Pending Git artifact byte limit reached (${maxBytes}); capture retained`
|
|
18985
|
+
);
|
|
18986
|
+
}
|
|
18987
|
+
writeDurable(path20.join(directory, file), buffer);
|
|
18988
|
+
context.record.uploads[key] = upload;
|
|
18746
18989
|
try {
|
|
18747
18990
|
context.save();
|
|
18748
18991
|
} catch (err) {
|
|
18749
18992
|
delete context.record.uploads[key];
|
|
18750
|
-
|
|
18993
|
+
fs21.rmSync(path20.join(directory, file), { force: true });
|
|
18751
18994
|
throw err;
|
|
18752
18995
|
}
|
|
18753
18996
|
status = "ok";
|
|
@@ -18779,8 +19022,8 @@ function readPreparedCaptureArtifact(context, filename, input) {
|
|
|
18779
19022
|
throw new Error(
|
|
18780
19023
|
`Prepared Git artifact input changed: ${filename}; capture retained`
|
|
18781
19024
|
);
|
|
18782
|
-
const bytes =
|
|
18783
|
-
|
|
19025
|
+
const bytes = fs21.readFileSync(
|
|
19026
|
+
path20.join(captureDirectory(context.record), saved.file)
|
|
18784
19027
|
);
|
|
18785
19028
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256)
|
|
18786
19029
|
throw new Error(
|
|
@@ -18799,7 +19042,7 @@ function completePendingCapture(record) {
|
|
|
18799
19042
|
function removeCompletedCapture(record) {
|
|
18800
19043
|
if (!record.completed)
|
|
18801
19044
|
throw new Error("Cannot remove an unacknowledged Git capture");
|
|
18802
|
-
|
|
19045
|
+
fs21.rmSync(captureDirectory(record), { recursive: true, force: true });
|
|
18803
19046
|
for (const label of ["snapshot", "tree", "head", "baseline", "previous"]) {
|
|
18804
19047
|
deleteRef(
|
|
18805
19048
|
record.repoRoot,
|
|
@@ -18872,7 +19115,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
18872
19115
|
}
|
|
18873
19116
|
|
|
18874
19117
|
// package.json
|
|
18875
|
-
var version = "0.9.
|
|
19118
|
+
var version = "0.9.15";
|
|
18876
19119
|
|
|
18877
19120
|
// src/version.ts
|
|
18878
19121
|
var CLI_VERSION = version;
|
|
@@ -18896,7 +19139,7 @@ function lineHasAssistant(line) {
|
|
|
18896
19139
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
18897
19140
|
}
|
|
18898
19141
|
async function hasAssistantMessage(transcriptPath) {
|
|
18899
|
-
const stream =
|
|
19142
|
+
const stream = fs22.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
18900
19143
|
let buffer = "";
|
|
18901
19144
|
try {
|
|
18902
19145
|
for await (const chunk of stream) {
|
|
@@ -18983,8 +19226,8 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
18983
19226
|
const workspace = payload.workspace_roots?.[0];
|
|
18984
19227
|
if (!id || !workspace) return void 0;
|
|
18985
19228
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
18986
|
-
return
|
|
18987
|
-
|
|
19229
|
+
return path21.join(
|
|
19230
|
+
os11.homedir(),
|
|
18988
19231
|
".cursor",
|
|
18989
19232
|
"projects",
|
|
18990
19233
|
encoded,
|
|
@@ -19039,14 +19282,14 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
19039
19282
|
const cwd = resolveHookCwd(payload);
|
|
19040
19283
|
if (!parentSessionId || !cwd || !payload.transcript_path) return;
|
|
19041
19284
|
try {
|
|
19042
|
-
const directory =
|
|
19043
|
-
|
|
19285
|
+
const directory = path21.join(
|
|
19286
|
+
path21.dirname(path21.resolve(payload.transcript_path)),
|
|
19044
19287
|
parentSessionId,
|
|
19045
19288
|
"subagents"
|
|
19046
19289
|
);
|
|
19047
19290
|
let names;
|
|
19048
19291
|
try {
|
|
19049
|
-
names = await
|
|
19292
|
+
names = await fs22.promises.readdir(directory);
|
|
19050
19293
|
} catch (err) {
|
|
19051
19294
|
if (err.code === "ENOENT") return;
|
|
19052
19295
|
throw err;
|
|
@@ -19055,12 +19298,12 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
19055
19298
|
for (const name of names) {
|
|
19056
19299
|
const match = /^agent-([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
|
|
19057
19300
|
if (!match) continue;
|
|
19058
|
-
const file =
|
|
19301
|
+
const file = path21.join(directory, name);
|
|
19059
19302
|
try {
|
|
19060
19303
|
files.push({
|
|
19061
19304
|
agentId: match[1],
|
|
19062
19305
|
file,
|
|
19063
|
-
modifiedMs: (await
|
|
19306
|
+
modifiedMs: (await fs22.promises.stat(file)).mtimeMs
|
|
19064
19307
|
});
|
|
19065
19308
|
} catch {
|
|
19066
19309
|
}
|
|
@@ -19226,7 +19469,7 @@ async function sweepCodexForkTranscripts(payload) {
|
|
|
19226
19469
|
const parent = await findProjectForCwd(cwd);
|
|
19227
19470
|
if (!parent) return;
|
|
19228
19471
|
const registeredRoots = Object.keys((await loadProjects()).projects).map(
|
|
19229
|
-
(root) =>
|
|
19472
|
+
(root) => path21.resolve(root)
|
|
19230
19473
|
);
|
|
19231
19474
|
const discovery = await discoverCodexForkThreads({
|
|
19232
19475
|
parentThreadId: parentSessionId,
|
|
@@ -19367,9 +19610,9 @@ async function runUploadForSession(payload, options) {
|
|
|
19367
19610
|
);
|
|
19368
19611
|
return "skipped";
|
|
19369
19612
|
}
|
|
19370
|
-
const transcriptResolved =
|
|
19613
|
+
const transcriptResolved = path21.resolve(transcriptPath);
|
|
19371
19614
|
try {
|
|
19372
|
-
const stat = await
|
|
19615
|
+
const stat = await fs22.promises.stat(transcriptResolved);
|
|
19373
19616
|
if (!stat.isFile()) {
|
|
19374
19617
|
appendLog(
|
|
19375
19618
|
"warn",
|
|
@@ -19425,7 +19668,7 @@ function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
|
|
|
19425
19668
|
};
|
|
19426
19669
|
return {
|
|
19427
19670
|
repoPath: repoRoot,
|
|
19428
|
-
label:
|
|
19671
|
+
label: path21.basename(repoRoot),
|
|
19429
19672
|
files: [sourceFile],
|
|
19430
19673
|
sourceNames: [sourceTool],
|
|
19431
19674
|
lastModified: /* @__PURE__ */ new Date()
|
|
@@ -19436,7 +19679,7 @@ async function buildRedactChain(repoRoot, capturedSecrets = []) {
|
|
|
19436
19679
|
"secret-discovery",
|
|
19437
19680
|
() => discoverEnvFiles(repoRoot)
|
|
19438
19681
|
);
|
|
19439
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
19682
|
+
const envFilePaths = envFileNames.map((n) => path21.join(repoRoot, n));
|
|
19440
19683
|
const secretResult = await measureAgentStage(
|
|
19441
19684
|
"secret-collection",
|
|
19442
19685
|
() => collectSecrets(repoRoot, envFilePaths, [])
|
|
@@ -20085,7 +20328,7 @@ async function uploadSession(args) {
|
|
|
20085
20328
|
try {
|
|
20086
20329
|
raw = await measureAgentStage(
|
|
20087
20330
|
"transcript-read",
|
|
20088
|
-
() => captured ? Promise.resolve(captured.bytes) :
|
|
20331
|
+
() => captured ? Promise.resolve(captured.bytes) : fs22.promises.readFile(transcriptPath)
|
|
20089
20332
|
);
|
|
20090
20333
|
} catch (err) {
|
|
20091
20334
|
if (err.code === "ERR_FS_FILE_TOO_LARGE") {
|
|
@@ -20456,16 +20699,15 @@ import crypto9 from "crypto";
|
|
|
20456
20699
|
|
|
20457
20700
|
// src/git-traces/handlers.ts
|
|
20458
20701
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
20459
|
-
import
|
|
20460
|
-
import
|
|
20702
|
+
import fs23 from "fs";
|
|
20703
|
+
import path22 from "path";
|
|
20461
20704
|
var GIT_TRACES_SLUG = "git-traces";
|
|
20462
20705
|
var MAX_FORK_SWEEP_THREADS2 = 8;
|
|
20463
20706
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
20464
|
-
var MAX_BASELINE_BUNDLE_UPLOAD_BYTES = 1024 * 1024 * 1024;
|
|
20465
20707
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
20466
20708
|
var REBASELINE_AFTER_DIFF_FAILURES = 2;
|
|
20467
20709
|
async function withRepoCaptureLock(repoRoot, capture) {
|
|
20468
|
-
const canonicalRoot =
|
|
20710
|
+
const canonicalRoot = fs23.realpathSync(repoRoot);
|
|
20469
20711
|
await acquireLock3(canonicalRoot, "snapshot-index-v1", null);
|
|
20470
20712
|
try {
|
|
20471
20713
|
return capture();
|
|
@@ -20523,7 +20765,7 @@ var PendingQueueConflictError = class extends Error {
|
|
|
20523
20765
|
async function loadConfiguredRepos() {
|
|
20524
20766
|
const file = await loadProjects();
|
|
20525
20767
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
20526
|
-
repoRoot:
|
|
20768
|
+
repoRoot: path22.resolve(repoRoot),
|
|
20527
20769
|
config
|
|
20528
20770
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
20529
20771
|
}
|
|
@@ -20541,12 +20783,12 @@ async function configuredReposFor(trigger) {
|
|
|
20541
20783
|
return repos;
|
|
20542
20784
|
}
|
|
20543
20785
|
async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
20544
|
-
const source =
|
|
20545
|
-
const target =
|
|
20546
|
-
if (
|
|
20786
|
+
const source = path22.join(parentRoot, ".codex", "hooks.json");
|
|
20787
|
+
const target = path22.join(worktreeRoot, ".codex", "hooks.json");
|
|
20788
|
+
if (fs23.existsSync(target) || !fs23.existsSync(source)) return;
|
|
20547
20789
|
try {
|
|
20548
|
-
|
|
20549
|
-
|
|
20790
|
+
fs23.mkdirSync(path22.dirname(target), { recursive: true });
|
|
20791
|
+
fs23.copyFileSync(source, target, fs23.constants.COPYFILE_EXCL);
|
|
20550
20792
|
} catch (err) {
|
|
20551
20793
|
appendLog(
|
|
20552
20794
|
"warn",
|
|
@@ -20568,7 +20810,7 @@ async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
|
20568
20810
|
}
|
|
20569
20811
|
}
|
|
20570
20812
|
function repoLabel(repoRoot) {
|
|
20571
|
-
return
|
|
20813
|
+
return path22.basename(repoRoot) || repoRoot;
|
|
20572
20814
|
}
|
|
20573
20815
|
function resolveCwd2(payload) {
|
|
20574
20816
|
return resolveHookCwd(payload);
|
|
@@ -20606,7 +20848,7 @@ function execGit(cwd, args) {
|
|
|
20606
20848
|
}).toString("utf-8").trim();
|
|
20607
20849
|
}
|
|
20608
20850
|
function canUploadFile(filename, buffer) {
|
|
20609
|
-
const limit = filename.endsWith("-baseline.bundle") ?
|
|
20851
|
+
const limit = filename.endsWith("-baseline.bundle") ? MAX_BASELINE_BUNDLE_BYTES : MAX_UPLOAD_BYTES;
|
|
20610
20852
|
if (buffer.byteLength <= limit) return true;
|
|
20611
20853
|
appendLog(
|
|
20612
20854
|
"warn",
|
|
@@ -21689,6 +21931,75 @@ async function refreshLineageBaseline(params) {
|
|
|
21689
21931
|
return false;
|
|
21690
21932
|
}
|
|
21691
21933
|
}
|
|
21934
|
+
async function uploadLargeCaptureBaseline(params) {
|
|
21935
|
+
const { repo, state, tool, persistState, limits } = params;
|
|
21936
|
+
const pending = currentPendingCapture(repo.repoRoot);
|
|
21937
|
+
if (!pending) return "failed";
|
|
21938
|
+
const client = await loadRepoClient(repo);
|
|
21939
|
+
if (!client) return "skipped";
|
|
21940
|
+
if (state.contributionId === null || !state.baselineUploaded) {
|
|
21941
|
+
const initial = buildInitialBaselineArtifactsForState(
|
|
21942
|
+
repo.repoRoot,
|
|
21943
|
+
tool,
|
|
21944
|
+
state,
|
|
21945
|
+
limits
|
|
21946
|
+
);
|
|
21947
|
+
if (!initial || !canUploadEpochBaselineArtifacts(state.epoch, initial))
|
|
21948
|
+
return "failed";
|
|
21949
|
+
if (!await registerInitialContribution({
|
|
21950
|
+
...params,
|
|
21951
|
+
client,
|
|
21952
|
+
artifacts: initial
|
|
21953
|
+
}))
|
|
21954
|
+
return "failed";
|
|
21955
|
+
}
|
|
21956
|
+
if (state.contributionId === null) return "failed";
|
|
21957
|
+
const nextEpoch = await reserveEpoch(state, persistState);
|
|
21958
|
+
const artifacts = await openEpoch({
|
|
21959
|
+
repoRoot: repo.repoRoot,
|
|
21960
|
+
client,
|
|
21961
|
+
contributionId: state.contributionId,
|
|
21962
|
+
sessionId: state.sessionId,
|
|
21963
|
+
tool,
|
|
21964
|
+
epoch: nextEpoch,
|
|
21965
|
+
prevHeadSha: state.headSha || null,
|
|
21966
|
+
transitionKind: "initial",
|
|
21967
|
+
startedAt: state.startedAt,
|
|
21968
|
+
limits,
|
|
21969
|
+
lineage: state.codexLineage,
|
|
21970
|
+
previousScan: pending.record.scan,
|
|
21971
|
+
provenance: {
|
|
21972
|
+
recovery: {
|
|
21973
|
+
reason: "patch-too-large",
|
|
21974
|
+
trigger: "stop-diff",
|
|
21975
|
+
detectedAt: new Date(pending.record.capturedAt).toISOString(),
|
|
21976
|
+
abandonedEpoch: state.epoch,
|
|
21977
|
+
abandonedBaselineSha: state.baselineSha,
|
|
21978
|
+
abandonedUploadedTurns: state.turnCount
|
|
21979
|
+
}
|
|
21980
|
+
}
|
|
21981
|
+
});
|
|
21982
|
+
if (!artifacts) return "failed";
|
|
21983
|
+
Object.assign(state, {
|
|
21984
|
+
baselineSha: artifacts.baselineSha,
|
|
21985
|
+
baselineTreeSha: artifacts.baselineTreeSha,
|
|
21986
|
+
lastSnapshotSha: artifacts.baselineSha,
|
|
21987
|
+
lastSnapshotTreeSha: artifacts.baselineTreeSha,
|
|
21988
|
+
epoch: nextEpoch,
|
|
21989
|
+
turnCount: 0,
|
|
21990
|
+
headSha: artifacts.headSha,
|
|
21991
|
+
omittedKeys: artifacts.omittedKeys,
|
|
21992
|
+
omissionScan: artifacts.scan,
|
|
21993
|
+
codexLineageStamped: artifacts.uploadedLineage
|
|
21994
|
+
});
|
|
21995
|
+
delete state.diffFailureCount;
|
|
21996
|
+
await persistState(state);
|
|
21997
|
+
appendLog(
|
|
21998
|
+
"info",
|
|
21999
|
+
`git-traces: large patch captured as baseline (repo=${repo.repoRoot}, session=${state.sessionId}, capture=${pending.record.id}, epoch=${nextEpoch}, tree=${artifacts.baselineTreeSha})`
|
|
22000
|
+
);
|
|
22001
|
+
return "uploaded";
|
|
22002
|
+
}
|
|
21692
22003
|
async function recoverFromWedgedDiff(params) {
|
|
21693
22004
|
const {
|
|
21694
22005
|
repo,
|
|
@@ -22180,6 +22491,20 @@ async function processStopRepo(repo, tool, sessionId, recordedAt, lineageCheck,
|
|
|
22180
22491
|
}
|
|
22181
22492
|
);
|
|
22182
22493
|
} catch (err) {
|
|
22494
|
+
if (err instanceof GitPatchTooLargeError) {
|
|
22495
|
+
appendLog(
|
|
22496
|
+
"warn",
|
|
22497
|
+
`git-traces: turn patch exceeds Git apply limit; attempting frozen baseline fallback (repo=${repoRoot}, session=${state.sessionId}, rawBytes=${err.rawBytes}, maxRawBytes=${err.maxRawBytes})`
|
|
22498
|
+
);
|
|
22499
|
+
const outcome = await uploadLargeCaptureBaseline({
|
|
22500
|
+
repo,
|
|
22501
|
+
state,
|
|
22502
|
+
tool,
|
|
22503
|
+
persistState,
|
|
22504
|
+
limits
|
|
22505
|
+
});
|
|
22506
|
+
return outcome === "uploaded" ? await finishSuccessfulStop("uploaded") : outcome;
|
|
22507
|
+
}
|
|
22183
22508
|
if (isMissingObjectError(err)) {
|
|
22184
22509
|
logMissingBaselineObjects(repoRoot, state, "stop-diff", err);
|
|
22185
22510
|
state.diffFailureCount = REBASELINE_AFTER_DIFF_FAILURES;
|
|
@@ -22838,7 +23163,7 @@ async function collectStopTargets(tool, sessionId, repos, repoByRoot) {
|
|
|
22838
23163
|
let needsLineageCheck = false;
|
|
22839
23164
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
22840
23165
|
for (const { state } of storedStates) {
|
|
22841
|
-
const repo = repoByRoot.get(
|
|
23166
|
+
const repo = repoByRoot.get(path22.resolve(state.repoRoot));
|
|
22842
23167
|
if (!repo) {
|
|
22843
23168
|
missingConfig++;
|
|
22844
23169
|
appendLog(
|
|
@@ -22870,7 +23195,7 @@ async function lateInitSkipReason(payload, tool) {
|
|
|
22870
23195
|
const transcriptPath = payload.transcript_path;
|
|
22871
23196
|
if (!transcriptPath) return tool === "codex" ? null : "no-transcript-path";
|
|
22872
23197
|
try {
|
|
22873
|
-
const stat = await
|
|
23198
|
+
const stat = await fs23.promises.stat(path22.resolve(transcriptPath));
|
|
22874
23199
|
return stat.isFile() ? null : "transcript-not-a-file";
|
|
22875
23200
|
} catch {
|
|
22876
23201
|
return "transcript-missing";
|
|
@@ -23079,9 +23404,9 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23079
23404
|
if (sessionId) {
|
|
23080
23405
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
23081
23406
|
for (const { state } of states) {
|
|
23082
|
-
const repoRoot =
|
|
23407
|
+
const repoRoot = path22.resolve(state.repoRoot);
|
|
23083
23408
|
repoRoots.add(repoRoot);
|
|
23084
|
-
const canProcess = repoByRoot.has(repoRoot) || project &&
|
|
23409
|
+
const canProcess = repoByRoot.has(repoRoot) || project && path22.resolve(project.repoRoot) === repoRoot;
|
|
23085
23410
|
if (canProcess && tool === "codex" && state.codexLineageChecked !== true) {
|
|
23086
23411
|
needsLineageCheck = true;
|
|
23087
23412
|
}
|
|
@@ -23118,7 +23443,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23118
23443
|
let rejected = 0;
|
|
23119
23444
|
const finalCaptures = /* @__PURE__ */ new Map();
|
|
23120
23445
|
for (const repoRoot of repoRoots) {
|
|
23121
|
-
const repo = repoByRoot.get(
|
|
23446
|
+
const repo = repoByRoot.get(path22.resolve(repoRoot)) ?? (project && path22.resolve(project.repoRoot) === path22.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
23122
23447
|
if (repo)
|
|
23123
23448
|
finalCaptures.set(
|
|
23124
23449
|
repoRoot,
|
|
@@ -23126,7 +23451,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23126
23451
|
);
|
|
23127
23452
|
}
|
|
23128
23453
|
for (const repoRoot of repoRoots) {
|
|
23129
|
-
const repo = repoByRoot.get(
|
|
23454
|
+
const repo = repoByRoot.get(path22.resolve(repoRoot)) ?? (project && path22.resolve(project.repoRoot) === path22.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
23130
23455
|
if (!repo) {
|
|
23131
23456
|
skipped++;
|
|
23132
23457
|
appendLog(
|
|
@@ -23505,29 +23830,29 @@ ${stack}` : ""}`
|
|
|
23505
23830
|
}
|
|
23506
23831
|
|
|
23507
23832
|
// src/outputs/zip.ts
|
|
23508
|
-
import
|
|
23509
|
-
import
|
|
23833
|
+
import fs25 from "fs";
|
|
23834
|
+
import path24 from "path";
|
|
23510
23835
|
import archiver2 from "archiver";
|
|
23511
23836
|
|
|
23512
23837
|
// src/outputs/downloads.ts
|
|
23513
23838
|
import { execSync as execSync2 } from "child_process";
|
|
23514
|
-
import
|
|
23515
|
-
import
|
|
23516
|
-
import
|
|
23839
|
+
import fs24 from "fs";
|
|
23840
|
+
import os12 from "os";
|
|
23841
|
+
import path23 from "path";
|
|
23517
23842
|
function getDownloadsFolder() {
|
|
23518
|
-
const home =
|
|
23843
|
+
const home = os12.homedir();
|
|
23519
23844
|
if (process.platform === "linux") {
|
|
23520
23845
|
try {
|
|
23521
23846
|
const xdgDir = execSync2("xdg-user-dir DOWNLOAD", {
|
|
23522
23847
|
encoding: "utf-8",
|
|
23523
23848
|
timeout: 3e3
|
|
23524
23849
|
}).trim();
|
|
23525
|
-
if (xdgDir &&
|
|
23850
|
+
if (xdgDir && fs24.existsSync(xdgDir)) return xdgDir;
|
|
23526
23851
|
} catch {
|
|
23527
23852
|
}
|
|
23528
23853
|
}
|
|
23529
|
-
const downloads =
|
|
23530
|
-
if (
|
|
23854
|
+
const downloads = path23.join(home, "Downloads");
|
|
23855
|
+
if (fs24.existsSync(downloads)) return downloads;
|
|
23531
23856
|
return home;
|
|
23532
23857
|
}
|
|
23533
23858
|
|
|
@@ -23536,11 +23861,11 @@ function sanitizeFilename(name) {
|
|
|
23536
23861
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
23537
23862
|
}
|
|
23538
23863
|
function getUniqueFilename(dir, base, ext) {
|
|
23539
|
-
let candidate =
|
|
23540
|
-
if (!
|
|
23864
|
+
let candidate = path24.join(dir, `${base}${ext}`);
|
|
23865
|
+
if (!fs25.existsSync(candidate)) return candidate;
|
|
23541
23866
|
let i = 1;
|
|
23542
|
-
while (
|
|
23543
|
-
candidate =
|
|
23867
|
+
while (fs25.existsSync(candidate)) {
|
|
23868
|
+
candidate = path24.join(dir, `${base}-${i}${ext}`);
|
|
23544
23869
|
i++;
|
|
23545
23870
|
}
|
|
23546
23871
|
return candidate;
|
|
@@ -23550,13 +23875,13 @@ var ZipOutput = class {
|
|
|
23550
23875
|
label = "Save as .zip to Downloads";
|
|
23551
23876
|
async emit(group, options) {
|
|
23552
23877
|
const downloadsDir = getDownloadsFolder();
|
|
23553
|
-
const repoName = sanitizeFilename(
|
|
23878
|
+
const repoName = sanitizeFilename(path24.basename(group.repoPath));
|
|
23554
23879
|
const timeRange = options.timeRange;
|
|
23555
23880
|
const rangePart = timeRange?.label ?? "all";
|
|
23556
23881
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
23557
23882
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
23558
23883
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
23559
|
-
const output =
|
|
23884
|
+
const output = fs25.createWriteStream(outputPath);
|
|
23560
23885
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
23561
23886
|
const done = new Promise((resolve, reject) => {
|
|
23562
23887
|
output.on("close", resolve);
|
|
@@ -23750,15 +24075,15 @@ async function confirmExport(group, output) {
|
|
|
23750
24075
|
}
|
|
23751
24076
|
|
|
23752
24077
|
// src/sources/claude.ts
|
|
23753
|
-
import
|
|
23754
|
-
import
|
|
23755
|
-
import
|
|
24078
|
+
import fs26 from "fs";
|
|
24079
|
+
import os13 from "os";
|
|
24080
|
+
import path25 from "path";
|
|
23756
24081
|
import readline3 from "readline";
|
|
23757
24082
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
23758
24083
|
async function resolveRepoPath(projectDir) {
|
|
23759
|
-
const indexPath =
|
|
24084
|
+
const indexPath = path25.join(projectDir, "sessions-index.json");
|
|
23760
24085
|
try {
|
|
23761
|
-
const raw = await
|
|
24086
|
+
const raw = await fs26.promises.readFile(indexPath, "utf-8");
|
|
23762
24087
|
const data = JSON.parse(raw);
|
|
23763
24088
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
23764
24089
|
return data.originalPath;
|
|
@@ -23766,12 +24091,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
23766
24091
|
} catch {
|
|
23767
24092
|
}
|
|
23768
24093
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
23769
|
-
const entries = await
|
|
24094
|
+
const entries = await fs26.promises.readdir(projectDir, {
|
|
23770
24095
|
withFileTypes: true
|
|
23771
24096
|
});
|
|
23772
24097
|
for (const entry of entries) {
|
|
23773
24098
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
23774
|
-
const cwd = await extractCwdFromJsonl(
|
|
24099
|
+
const cwd = await extractCwdFromJsonl(path25.join(projectDir, entry.name));
|
|
23775
24100
|
if (cwd) {
|
|
23776
24101
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
23777
24102
|
}
|
|
@@ -23790,7 +24115,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
23790
24115
|
return null;
|
|
23791
24116
|
}
|
|
23792
24117
|
async function extractCwdFromJsonl(filePath) {
|
|
23793
|
-
const stream =
|
|
24118
|
+
const stream = fs26.createReadStream(filePath, { encoding: "utf-8" });
|
|
23794
24119
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23795
24120
|
try {
|
|
23796
24121
|
for await (const line of rl) {
|
|
@@ -23812,12 +24137,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
23812
24137
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
23813
24138
|
let entries;
|
|
23814
24139
|
try {
|
|
23815
|
-
entries = await
|
|
24140
|
+
entries = await fs26.promises.readdir(dir, { withFileTypes: true });
|
|
23816
24141
|
} catch {
|
|
23817
24142
|
return;
|
|
23818
24143
|
}
|
|
23819
24144
|
for (const entry of entries) {
|
|
23820
|
-
const fullPath =
|
|
24145
|
+
const fullPath = path25.join(dir, entry.name);
|
|
23821
24146
|
if (entry.isDirectory()) {
|
|
23822
24147
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
23823
24148
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -23839,19 +24164,19 @@ function fallbackDecode(encodedName) {
|
|
|
23839
24164
|
var ClaudeSource = class {
|
|
23840
24165
|
name = "claude";
|
|
23841
24166
|
async scan() {
|
|
23842
|
-
const baseDir =
|
|
24167
|
+
const baseDir = path25.join(os13.homedir(), ".claude", "projects");
|
|
23843
24168
|
try {
|
|
23844
|
-
await
|
|
24169
|
+
await fs26.promises.access(baseDir);
|
|
23845
24170
|
} catch {
|
|
23846
24171
|
return [];
|
|
23847
24172
|
}
|
|
23848
|
-
const projectDirs = await
|
|
24173
|
+
const projectDirs = await fs26.promises.readdir(baseDir, {
|
|
23849
24174
|
withFileTypes: true
|
|
23850
24175
|
});
|
|
23851
24176
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
23852
24177
|
const resultArrays = await Promise.all(
|
|
23853
24178
|
dirEntries.map(async (dir) => {
|
|
23854
|
-
const projectPath =
|
|
24179
|
+
const projectPath = path25.join(baseDir, dir.name);
|
|
23855
24180
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
23856
24181
|
const files = [];
|
|
23857
24182
|
await collectFiles(
|
|
@@ -23869,12 +24194,12 @@ var ClaudeSource = class {
|
|
|
23869
24194
|
};
|
|
23870
24195
|
|
|
23871
24196
|
// src/sources/codex.ts
|
|
23872
|
-
import
|
|
23873
|
-
import
|
|
23874
|
-
import
|
|
24197
|
+
import fs27 from "fs";
|
|
24198
|
+
import os14 from "os";
|
|
24199
|
+
import path26 from "path";
|
|
23875
24200
|
import readline4 from "readline";
|
|
23876
24201
|
async function parseSessionMeta2(filePath) {
|
|
23877
|
-
const stream =
|
|
24202
|
+
const stream = fs27.createReadStream(filePath, { encoding: "utf-8" });
|
|
23878
24203
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23879
24204
|
try {
|
|
23880
24205
|
for await (const line of rl) {
|
|
@@ -23899,12 +24224,12 @@ async function findJsonlFiles(dir) {
|
|
|
23899
24224
|
async function walk(d) {
|
|
23900
24225
|
let entries;
|
|
23901
24226
|
try {
|
|
23902
|
-
entries = await
|
|
24227
|
+
entries = await fs27.promises.readdir(d, { withFileTypes: true });
|
|
23903
24228
|
} catch {
|
|
23904
24229
|
return;
|
|
23905
24230
|
}
|
|
23906
24231
|
for (const entry of entries) {
|
|
23907
|
-
const full =
|
|
24232
|
+
const full = path26.join(d, entry.name);
|
|
23908
24233
|
if (entry.isDirectory()) {
|
|
23909
24234
|
await walk(full);
|
|
23910
24235
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -23918,11 +24243,11 @@ async function findJsonlFiles(dir) {
|
|
|
23918
24243
|
async function loadHistory(historyPath) {
|
|
23919
24244
|
const map = /* @__PURE__ */ new Map();
|
|
23920
24245
|
try {
|
|
23921
|
-
await
|
|
24246
|
+
await fs27.promises.access(historyPath);
|
|
23922
24247
|
} catch {
|
|
23923
24248
|
return map;
|
|
23924
24249
|
}
|
|
23925
|
-
const stream =
|
|
24250
|
+
const stream = fs27.createReadStream(historyPath, { encoding: "utf-8" });
|
|
23926
24251
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23927
24252
|
try {
|
|
23928
24253
|
for await (const line of rl) {
|
|
@@ -23949,14 +24274,14 @@ async function loadHistory(historyPath) {
|
|
|
23949
24274
|
var CodexSource = class {
|
|
23950
24275
|
name = "codex";
|
|
23951
24276
|
async scan() {
|
|
23952
|
-
const codexDir =
|
|
23953
|
-
const sessionsDir2 =
|
|
24277
|
+
const codexDir = path26.join(os14.homedir(), ".codex");
|
|
24278
|
+
const sessionsDir2 = path26.join(codexDir, "sessions");
|
|
23954
24279
|
try {
|
|
23955
|
-
await
|
|
24280
|
+
await fs27.promises.access(sessionsDir2);
|
|
23956
24281
|
} catch {
|
|
23957
24282
|
return [];
|
|
23958
24283
|
}
|
|
23959
|
-
const historyPath =
|
|
24284
|
+
const historyPath = path26.join(codexDir, "history.jsonl");
|
|
23960
24285
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
23961
24286
|
findJsonlFiles(sessionsDir2),
|
|
23962
24287
|
loadHistory(historyPath)
|
|
@@ -23979,8 +24304,8 @@ var CodexSource = class {
|
|
|
23979
24304
|
});
|
|
23980
24305
|
const historyLines = historyMap.get(meta.sessionId);
|
|
23981
24306
|
if (historyLines) {
|
|
23982
|
-
const sessionDir =
|
|
23983
|
-
const historyAbsPath =
|
|
24307
|
+
const sessionDir = path26.relative(sessionsDir2, path26.dirname(filePath));
|
|
24308
|
+
const historyAbsPath = path26.join(
|
|
23984
24309
|
sessionsDir2,
|
|
23985
24310
|
sessionDir,
|
|
23986
24311
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -24000,18 +24325,18 @@ var CodexSource = class {
|
|
|
24000
24325
|
};
|
|
24001
24326
|
|
|
24002
24327
|
// src/sources/copilotChat.ts
|
|
24003
|
-
import
|
|
24004
|
-
import
|
|
24005
|
-
import
|
|
24328
|
+
import fs28 from "fs";
|
|
24329
|
+
import os15 from "os";
|
|
24330
|
+
import path27 from "path";
|
|
24006
24331
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24007
24332
|
function vsCodeUserDirs() {
|
|
24008
|
-
const home =
|
|
24333
|
+
const home = os15.homedir();
|
|
24009
24334
|
const dirs = [
|
|
24010
|
-
|
|
24011
|
-
|
|
24335
|
+
path27.join(home, "Library", "Application Support", "Code", "User"),
|
|
24336
|
+
path27.join(home, ".config", "Code", "User")
|
|
24012
24337
|
];
|
|
24013
24338
|
if (process.env.APPDATA) {
|
|
24014
|
-
dirs.push(
|
|
24339
|
+
dirs.push(path27.join(process.env.APPDATA, "Code", "User"));
|
|
24015
24340
|
}
|
|
24016
24341
|
return dirs;
|
|
24017
24342
|
}
|
|
@@ -24026,7 +24351,7 @@ function uriToFsPath(uri) {
|
|
|
24026
24351
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
24027
24352
|
let raw;
|
|
24028
24353
|
try {
|
|
24029
|
-
raw = await
|
|
24354
|
+
raw = await fs28.promises.readFile(workspaceJsonPath, "utf-8");
|
|
24030
24355
|
} catch {
|
|
24031
24356
|
return null;
|
|
24032
24357
|
}
|
|
@@ -24048,10 +24373,10 @@ var CopilotChatSource = class {
|
|
|
24048
24373
|
async scan() {
|
|
24049
24374
|
const results = [];
|
|
24050
24375
|
for (const userDir of vsCodeUserDirs()) {
|
|
24051
|
-
const workspaceStorage =
|
|
24376
|
+
const workspaceStorage = path27.join(userDir, "workspaceStorage");
|
|
24052
24377
|
let hashDirs;
|
|
24053
24378
|
try {
|
|
24054
|
-
hashDirs = await
|
|
24379
|
+
hashDirs = await fs28.promises.readdir(workspaceStorage, {
|
|
24055
24380
|
withFileTypes: true
|
|
24056
24381
|
});
|
|
24057
24382
|
} catch {
|
|
@@ -24059,22 +24384,22 @@ var CopilotChatSource = class {
|
|
|
24059
24384
|
}
|
|
24060
24385
|
for (const hash of hashDirs) {
|
|
24061
24386
|
if (!hash.isDirectory()) continue;
|
|
24062
|
-
const wsRoot =
|
|
24063
|
-
const transcriptsDir =
|
|
24387
|
+
const wsRoot = path27.join(workspaceStorage, hash.name);
|
|
24388
|
+
const transcriptsDir = path27.join(
|
|
24064
24389
|
wsRoot,
|
|
24065
24390
|
"GitHub.copilot-chat",
|
|
24066
24391
|
"transcripts"
|
|
24067
24392
|
);
|
|
24068
24393
|
let transcriptEntries;
|
|
24069
24394
|
try {
|
|
24070
|
-
transcriptEntries = await
|
|
24395
|
+
transcriptEntries = await fs28.promises.readdir(transcriptsDir, {
|
|
24071
24396
|
withFileTypes: true
|
|
24072
24397
|
});
|
|
24073
24398
|
} catch {
|
|
24074
24399
|
continue;
|
|
24075
24400
|
}
|
|
24076
24401
|
const repoPath = await readWorkspaceFolder(
|
|
24077
|
-
|
|
24402
|
+
path27.join(wsRoot, "workspace.json")
|
|
24078
24403
|
);
|
|
24079
24404
|
if (!repoPath) continue;
|
|
24080
24405
|
for (const entry of transcriptEntries) {
|
|
@@ -24082,7 +24407,7 @@ var CopilotChatSource = class {
|
|
|
24082
24407
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
24083
24408
|
results.push({
|
|
24084
24409
|
sourceName: this.name,
|
|
24085
|
-
absolutePath:
|
|
24410
|
+
absolutePath: path27.join(transcriptsDir, entry.name),
|
|
24086
24411
|
repoPath,
|
|
24087
24412
|
metadata: { sessionId }
|
|
24088
24413
|
});
|
|
@@ -24122,7 +24447,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
24122
24447
|
async function filterByTimeRange(group, range) {
|
|
24123
24448
|
const results = await Promise.all(
|
|
24124
24449
|
group.files.map(
|
|
24125
|
-
(f) =>
|
|
24450
|
+
(f) => fs29.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
24126
24451
|
)
|
|
24127
24452
|
);
|
|
24128
24453
|
const filtered = [];
|
|
@@ -24149,10 +24474,10 @@ async function runInteractive() {
|
|
|
24149
24474
|
s.start(`Scanning ${source.name} logs...`);
|
|
24150
24475
|
const allFiles = await source.scan();
|
|
24151
24476
|
const allGroups = await mergeByRepo(allFiles);
|
|
24152
|
-
const repoRoot =
|
|
24477
|
+
const repoRoot = path28.resolve(repo.root);
|
|
24153
24478
|
const matching = allGroups.filter((g) => {
|
|
24154
|
-
const resolved =
|
|
24155
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
24479
|
+
const resolved = path28.resolve(g.repoPath);
|
|
24480
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path28.sep);
|
|
24156
24481
|
});
|
|
24157
24482
|
if (matching.length === 0) {
|
|
24158
24483
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -24183,7 +24508,7 @@ async function runInteractive() {
|
|
|
24183
24508
|
}
|
|
24184
24509
|
}
|
|
24185
24510
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
24186
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
24511
|
+
const envFilePaths = envFileNames.map((n) => path28.join(repoRoot, n));
|
|
24187
24512
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
24188
24513
|
const secretResult = await collectSecrets(
|
|
24189
24514
|
repoRoot,
|