hillclimb 0.9.13 → 0.9.14
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 +421 -239
- 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
|
);
|
|
@@ -16654,8 +16796,8 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
16654
16796
|
]);
|
|
16655
16797
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
16656
16798
|
function isExcludedSnapshotPath(filePath) {
|
|
16657
|
-
if (EXCLUDED_SNAPSHOT_BASENAMES.has(
|
|
16658
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
16799
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path18.basename(filePath))) return true;
|
|
16800
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path18.extname(filePath).toLowerCase());
|
|
16659
16801
|
}
|
|
16660
16802
|
function isBinaryBuffer(buffer) {
|
|
16661
16803
|
return buffer.includes(0);
|
|
@@ -16676,16 +16818,16 @@ function readTreeBlobHead(repoRoot, sha) {
|
|
|
16676
16818
|
function readWorkingFileHead(absPath) {
|
|
16677
16819
|
let fd = null;
|
|
16678
16820
|
try {
|
|
16679
|
-
fd =
|
|
16821
|
+
fd = fs19.openSync(absPath, "r");
|
|
16680
16822
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
16681
|
-
const bytesRead =
|
|
16823
|
+
const bytesRead = fs19.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
16682
16824
|
return buffer.subarray(0, bytesRead);
|
|
16683
16825
|
} catch {
|
|
16684
16826
|
return null;
|
|
16685
16827
|
} finally {
|
|
16686
16828
|
if (fd !== null) {
|
|
16687
16829
|
try {
|
|
16688
|
-
|
|
16830
|
+
fs19.closeSync(fd);
|
|
16689
16831
|
} catch {
|
|
16690
16832
|
}
|
|
16691
16833
|
}
|
|
@@ -16785,6 +16927,44 @@ function gitBuffer(repoRoot, args, options = {}) {
|
|
|
16785
16927
|
const detached = options.killProcessGroup && process.platform !== "win32";
|
|
16786
16928
|
const started = Date.now();
|
|
16787
16929
|
try {
|
|
16930
|
+
if (options.diagnostics) {
|
|
16931
|
+
const diagnostics = startGitDiagnostics(options.diagnostics.bundleFile);
|
|
16932
|
+
let status = "failed";
|
|
16933
|
+
try {
|
|
16934
|
+
const result = spawnSync2("git", args, {
|
|
16935
|
+
cwd: repoRoot,
|
|
16936
|
+
env: {
|
|
16937
|
+
...options.env ?? process.env,
|
|
16938
|
+
...diagnostics.env
|
|
16939
|
+
},
|
|
16940
|
+
input: options.input,
|
|
16941
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16942
|
+
...EXEC_OPTS,
|
|
16943
|
+
timeout,
|
|
16944
|
+
...detached ? { detached: true } : {}
|
|
16945
|
+
});
|
|
16946
|
+
if (result.error || result.status !== 0) {
|
|
16947
|
+
throw Object.assign(result.error ?? new Error("Git command failed"), {
|
|
16948
|
+
pid: result.pid,
|
|
16949
|
+
status: result.status,
|
|
16950
|
+
signal: result.signal,
|
|
16951
|
+
stdout: result.stdout,
|
|
16952
|
+
stderr: result.stderr
|
|
16953
|
+
});
|
|
16954
|
+
}
|
|
16955
|
+
status = "ok";
|
|
16956
|
+
return result.stdout;
|
|
16957
|
+
} finally {
|
|
16958
|
+
const elapsedMs = Date.now() - started;
|
|
16959
|
+
const summary = diagnostics.finish();
|
|
16960
|
+
if (status === "failed" || elapsedMs >= 1e3) {
|
|
16961
|
+
appendLog(
|
|
16962
|
+
"info",
|
|
16963
|
+
`git-traces: git diagnostics (stage=${options.diagnostics.stage}, status=${status}, elapsedMs=${elapsedMs}, ${summary})`
|
|
16964
|
+
);
|
|
16965
|
+
}
|
|
16966
|
+
}
|
|
16967
|
+
}
|
|
16788
16968
|
return execFileSync2("git", args, {
|
|
16789
16969
|
cwd: repoRoot,
|
|
16790
16970
|
...options.env ? { env: options.env } : {},
|
|
@@ -16850,23 +17030,23 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
16850
17030
|
let tmpDir = null;
|
|
16851
17031
|
let copy = null;
|
|
16852
17032
|
try {
|
|
16853
|
-
const indexPath =
|
|
17033
|
+
const indexPath = path18.resolve(
|
|
16854
17034
|
repoRoot,
|
|
16855
17035
|
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
16856
17036
|
);
|
|
16857
|
-
tmpDir =
|
|
16858
|
-
copy =
|
|
16859
|
-
const fd =
|
|
17037
|
+
tmpDir = fs19.mkdtempSync(path18.join(os8.tmpdir(), "hillclimb-index-"));
|
|
17038
|
+
copy = path18.join(tmpDir, "index");
|
|
17039
|
+
const fd = fs19.openSync(indexPath, "r");
|
|
16860
17040
|
let stat;
|
|
16861
17041
|
let data;
|
|
16862
17042
|
try {
|
|
16863
|
-
stat =
|
|
16864
|
-
data =
|
|
17043
|
+
stat = fs19.fstatSync(fd);
|
|
17044
|
+
data = fs19.readFileSync(fd);
|
|
16865
17045
|
} finally {
|
|
16866
|
-
|
|
17046
|
+
fs19.closeSync(fd);
|
|
16867
17047
|
}
|
|
16868
|
-
|
|
16869
|
-
|
|
17048
|
+
fs19.writeFileSync(copy, data);
|
|
17049
|
+
fs19.utimesSync(copy, stat.atime, stat.mtime);
|
|
16870
17050
|
} catch (err) {
|
|
16871
17051
|
const missing = err.code === "ENOENT";
|
|
16872
17052
|
appendLog(
|
|
@@ -16878,7 +17058,7 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
16878
17058
|
try {
|
|
16879
17059
|
return run(copy ? privateIndexEnv(copy) : null);
|
|
16880
17060
|
} finally {
|
|
16881
|
-
if (tmpDir)
|
|
17061
|
+
if (tmpDir) fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
16882
17062
|
}
|
|
16883
17063
|
}
|
|
16884
17064
|
function privateIndexEnv(indexPath) {
|
|
@@ -17295,8 +17475,8 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
17295
17475
|
options.omittedFiles?.push(...omittedFiles);
|
|
17296
17476
|
return timeCaptureStage(repoRoot, "tracked-tree-filter", () => {
|
|
17297
17477
|
if (omittedFiles.length === 0) return treeSha;
|
|
17298
|
-
const tmpIndex =
|
|
17299
|
-
|
|
17478
|
+
const tmpIndex = path18.join(
|
|
17479
|
+
os8.tmpdir(),
|
|
17300
17480
|
`hillclimb-filter-${Date.now()}-${process.pid}`
|
|
17301
17481
|
);
|
|
17302
17482
|
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
@@ -17310,7 +17490,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
17310
17490
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
17311
17491
|
} finally {
|
|
17312
17492
|
try {
|
|
17313
|
-
|
|
17493
|
+
fs19.unlinkSync(tmpIndex);
|
|
17314
17494
|
} catch {
|
|
17315
17495
|
}
|
|
17316
17496
|
}
|
|
@@ -17335,8 +17515,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17335
17515
|
if (!relPath) continue;
|
|
17336
17516
|
candidates++;
|
|
17337
17517
|
try {
|
|
17338
|
-
const absPath =
|
|
17339
|
-
const stat =
|
|
17518
|
+
const absPath = path18.join(repoRoot, relPath);
|
|
17519
|
+
const stat = fs19.lstatSync(absPath);
|
|
17340
17520
|
const reason = classifyOmission(
|
|
17341
17521
|
relPath,
|
|
17342
17522
|
stat.size,
|
|
@@ -17367,11 +17547,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17367
17547
|
`git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
|
|
17368
17548
|
);
|
|
17369
17549
|
if (kept.length === 0) return null;
|
|
17370
|
-
const tmpDir =
|
|
17550
|
+
const tmpDir = fs19.mkdtempSync(path18.join(os8.tmpdir(), "hillclimb-untracked-"));
|
|
17371
17551
|
const env = {
|
|
17372
17552
|
...process.env,
|
|
17373
17553
|
LC_ALL: "C",
|
|
17374
|
-
GIT_INDEX_FILE:
|
|
17554
|
+
GIT_INDEX_FILE: path18.join(tmpDir, "index")
|
|
17375
17555
|
};
|
|
17376
17556
|
try {
|
|
17377
17557
|
timeCaptureStage(
|
|
@@ -17379,7 +17559,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17379
17559
|
"untracked-hash-and-index",
|
|
17380
17560
|
() => gitBuffer(repoRoot, ["update-index", "--add", "-z", "--stdin"], {
|
|
17381
17561
|
input: `${kept.join("\0")}\0`,
|
|
17382
|
-
env
|
|
17562
|
+
env,
|
|
17563
|
+
diagnostics: { stage: "untracked-hash-and-index" }
|
|
17383
17564
|
})
|
|
17384
17565
|
);
|
|
17385
17566
|
return timeCaptureStage(
|
|
@@ -17399,7 +17580,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
17399
17580
|
return buildUntrackedTree(repoRoot, options, attempt + 1);
|
|
17400
17581
|
} finally {
|
|
17401
17582
|
try {
|
|
17402
|
-
|
|
17583
|
+
fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
17403
17584
|
} catch {
|
|
17404
17585
|
}
|
|
17405
17586
|
}
|
|
@@ -17470,8 +17651,8 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
17470
17651
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
|
|
17471
17652
|
return filteredTrackedTree;
|
|
17472
17653
|
if (filteredTrackedTree === EMPTY_TREE_SHA) return untrackedTree;
|
|
17473
|
-
const tmpIndex =
|
|
17474
|
-
|
|
17654
|
+
const tmpIndex = path18.join(
|
|
17655
|
+
os8.tmpdir(),
|
|
17475
17656
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
17476
17657
|
);
|
|
17477
17658
|
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
@@ -17499,7 +17680,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
17499
17680
|
);
|
|
17500
17681
|
} finally {
|
|
17501
17682
|
try {
|
|
17502
|
-
|
|
17683
|
+
fs19.unlinkSync(tmpIndex);
|
|
17503
17684
|
} catch {
|
|
17504
17685
|
}
|
|
17505
17686
|
}
|
|
@@ -17514,19 +17695,20 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
17514
17695
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
17515
17696
|
let tmpDir;
|
|
17516
17697
|
try {
|
|
17517
|
-
tmpDir =
|
|
17518
|
-
|
|
17698
|
+
tmpDir = fs19.mkdtempSync(
|
|
17699
|
+
path18.join(os8.tmpdir(), `hillclimb-bundle-${process.pid}-`)
|
|
17519
17700
|
);
|
|
17520
|
-
const tmpFile =
|
|
17701
|
+
const tmpFile = path18.join(tmpDir, "baseline.bundle");
|
|
17521
17702
|
timeCaptureStage(
|
|
17522
17703
|
repoRoot,
|
|
17523
17704
|
"bundle-create",
|
|
17524
17705
|
() => gitBuffer(repoRoot, ["bundle", "create", tmpFile, orphanRef], {
|
|
17525
17706
|
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS),
|
|
17526
|
-
killProcessGroup: true
|
|
17707
|
+
killProcessGroup: true,
|
|
17708
|
+
diagnostics: { stage: "bundle-create", bundleFile: tmpFile }
|
|
17527
17709
|
})
|
|
17528
17710
|
);
|
|
17529
|
-
const bundle =
|
|
17711
|
+
const bundle = fs19.readFileSync(tmpFile);
|
|
17530
17712
|
appendLog(
|
|
17531
17713
|
"info",
|
|
17532
17714
|
`git-traces: bundle inventory (repo=${repoRoot}, bytes=${bundle.length})`
|
|
@@ -17534,7 +17716,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
17534
17716
|
return bundle;
|
|
17535
17717
|
} finally {
|
|
17536
17718
|
try {
|
|
17537
|
-
if (tmpDir)
|
|
17719
|
+
if (tmpDir) fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
17538
17720
|
} catch {
|
|
17539
17721
|
}
|
|
17540
17722
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -17753,7 +17935,7 @@ function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersio
|
|
|
17753
17935
|
...omittedFiles.length > 0 ? { omittedFiles } : {}
|
|
17754
17936
|
},
|
|
17755
17937
|
author: { name: authorName, email: authorEmail },
|
|
17756
|
-
hostname:
|
|
17938
|
+
hostname: os8.hostname(),
|
|
17757
17939
|
cliVersion,
|
|
17758
17940
|
commits,
|
|
17759
17941
|
...lineage,
|
|
@@ -17845,9 +18027,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
17845
18027
|
oldPath
|
|
17846
18028
|
});
|
|
17847
18029
|
} else {
|
|
17848
|
-
const
|
|
17849
|
-
indexByPath.set(
|
|
17850
|
-
files.push({ path:
|
|
18030
|
+
const path29 = parts[parts.length - 1];
|
|
18031
|
+
indexByPath.set(path29, files.length);
|
|
18032
|
+
files.push({ path: path29, status, additions: 0, deletions: 0 });
|
|
17851
18033
|
}
|
|
17852
18034
|
}
|
|
17853
18035
|
for (const line of numstat.split("\n")) {
|
|
@@ -17931,11 +18113,11 @@ function countScopedTurnTreeRefs(repoRoot, sessionId, epochPrefix3) {
|
|
|
17931
18113
|
|
|
17932
18114
|
// src/git-traces/session-state.ts
|
|
17933
18115
|
import crypto6 from "crypto";
|
|
17934
|
-
import
|
|
17935
|
-
import
|
|
17936
|
-
import
|
|
18116
|
+
import fs20 from "fs";
|
|
18117
|
+
import os9 from "os";
|
|
18118
|
+
import path19 from "path";
|
|
17937
18119
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
17938
|
-
var DEFAULT_STATE_DIR2 =
|
|
18120
|
+
var DEFAULT_STATE_DIR2 = path19.join(os9.homedir(), ".hillclimb", "git-traces");
|
|
17939
18121
|
var LOCK_RETRIES2 = 120;
|
|
17940
18122
|
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
17941
18123
|
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS2 = 10 * 60 * 1e3;
|
|
@@ -17960,16 +18142,16 @@ function stateDir3() {
|
|
|
17960
18142
|
}
|
|
17961
18143
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
17962
18144
|
const hash = crypto6.createHash("sha256").update(
|
|
17963
|
-
sessionId ? `${
|
|
18145
|
+
sessionId ? `${path19.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path19.resolve(repoRoot)}\0${tool}`
|
|
17964
18146
|
).digest("hex").slice(0, 16);
|
|
17965
|
-
return
|
|
18147
|
+
return path19.join(stateDir3(), `${hash}.json`);
|
|
17966
18148
|
}
|
|
17967
18149
|
function lockFileForRepo(repoRoot, tool, sessionId) {
|
|
17968
18150
|
return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
|
|
17969
18151
|
}
|
|
17970
18152
|
async function readStateFile(file) {
|
|
17971
18153
|
try {
|
|
17972
|
-
const raw = await
|
|
18154
|
+
const raw = await fs20.promises.readFile(file, "utf-8");
|
|
17973
18155
|
const parsed = JSON.parse(raw);
|
|
17974
18156
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
|
|
17975
18157
|
return null;
|
|
@@ -17983,7 +18165,7 @@ async function readStoredStateFile(file) {
|
|
|
17983
18165
|
const state = await readStateFile(file);
|
|
17984
18166
|
if (!state) return null;
|
|
17985
18167
|
try {
|
|
17986
|
-
return { state, mtimeMs: (await
|
|
18168
|
+
return { state, mtimeMs: (await fs20.promises.stat(file)).mtimeMs };
|
|
17987
18169
|
} catch {
|
|
17988
18170
|
return null;
|
|
17989
18171
|
}
|
|
@@ -17991,26 +18173,26 @@ async function readStoredStateFile(file) {
|
|
|
17991
18173
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
17992
18174
|
let entries;
|
|
17993
18175
|
try {
|
|
17994
|
-
entries = await
|
|
18176
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
17995
18177
|
} catch {
|
|
17996
18178
|
return [];
|
|
17997
18179
|
}
|
|
17998
18180
|
const states = [];
|
|
17999
18181
|
for (const entry of entries) {
|
|
18000
18182
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
18001
|
-
const file =
|
|
18183
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18002
18184
|
const state = await readStateFile(file);
|
|
18003
18185
|
if (!state) continue;
|
|
18004
18186
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
18005
18187
|
continue;
|
|
18006
18188
|
}
|
|
18007
|
-
if (
|
|
18008
|
-
if (
|
|
18189
|
+
if (path19.resolve(state.repoRoot) !== path19.resolve(repoRoot)) continue;
|
|
18190
|
+
if (path19.resolve(file) !== path19.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
18009
18191
|
continue;
|
|
18010
18192
|
}
|
|
18011
18193
|
let mtimeMs = 0;
|
|
18012
18194
|
try {
|
|
18013
|
-
mtimeMs = (await
|
|
18195
|
+
mtimeMs = (await fs20.promises.stat(file)).mtimeMs;
|
|
18014
18196
|
} catch {
|
|
18015
18197
|
continue;
|
|
18016
18198
|
}
|
|
@@ -18021,26 +18203,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
18021
18203
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
18022
18204
|
let entries;
|
|
18023
18205
|
try {
|
|
18024
|
-
entries = await
|
|
18206
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
18025
18207
|
} catch {
|
|
18026
18208
|
return [];
|
|
18027
18209
|
}
|
|
18028
18210
|
const states = [];
|
|
18029
18211
|
for (const entry of entries) {
|
|
18030
18212
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
18031
|
-
const file =
|
|
18213
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18032
18214
|
const state = await readStateFile(file);
|
|
18033
18215
|
if (!state) continue;
|
|
18034
18216
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
18035
18217
|
continue;
|
|
18036
18218
|
}
|
|
18037
18219
|
if (state.sessionId !== sessionId) continue;
|
|
18038
|
-
if (
|
|
18220
|
+
if (path19.resolve(file) !== path19.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
18039
18221
|
continue;
|
|
18040
18222
|
}
|
|
18041
18223
|
let mtimeMs = 0;
|
|
18042
18224
|
try {
|
|
18043
|
-
mtimeMs = (await
|
|
18225
|
+
mtimeMs = (await fs20.promises.stat(file)).mtimeMs;
|
|
18044
18226
|
} catch {
|
|
18045
18227
|
continue;
|
|
18046
18228
|
}
|
|
@@ -18089,17 +18271,17 @@ async function writeLegacySessionState(state, tool) {
|
|
|
18089
18271
|
);
|
|
18090
18272
|
}
|
|
18091
18273
|
async function writeStateFile(file, state) {
|
|
18092
|
-
await
|
|
18274
|
+
await fs20.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
18093
18275
|
const tmp = `${file}.tmp`;
|
|
18094
|
-
await
|
|
18276
|
+
await fs20.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
18095
18277
|
mode: 384
|
|
18096
18278
|
});
|
|
18097
|
-
await
|
|
18279
|
+
await fs20.promises.rename(tmp, file);
|
|
18098
18280
|
}
|
|
18099
18281
|
async function touchSessionState(repoRoot, tool, sessionId) {
|
|
18100
18282
|
const now = /* @__PURE__ */ new Date();
|
|
18101
18283
|
try {
|
|
18102
|
-
await
|
|
18284
|
+
await fs20.promises.utimes(
|
|
18103
18285
|
stateFileForRepo(repoRoot, tool, sessionId),
|
|
18104
18286
|
now,
|
|
18105
18287
|
now
|
|
@@ -18109,7 +18291,7 @@ async function touchSessionState(repoRoot, tool, sessionId) {
|
|
|
18109
18291
|
}
|
|
18110
18292
|
async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
18111
18293
|
try {
|
|
18112
|
-
const stat = await
|
|
18294
|
+
const stat = await fs20.promises.stat(
|
|
18113
18295
|
stateFileForRepo(repoRoot, tool, sessionId)
|
|
18114
18296
|
);
|
|
18115
18297
|
return stat.mtimeMs;
|
|
@@ -18119,7 +18301,7 @@ async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
|
18119
18301
|
}
|
|
18120
18302
|
async function deleteStateFile(file) {
|
|
18121
18303
|
try {
|
|
18122
|
-
await
|
|
18304
|
+
await fs20.promises.unlink(file);
|
|
18123
18305
|
} catch {
|
|
18124
18306
|
}
|
|
18125
18307
|
}
|
|
@@ -18158,7 +18340,7 @@ async function acquireLock3(repoRoot, tool, sessionId, retries, delayMs, options
|
|
|
18158
18340
|
}
|
|
18159
18341
|
async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, delayMs, options = {}) {
|
|
18160
18342
|
const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
|
|
18161
|
-
await
|
|
18343
|
+
await fs20.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
18162
18344
|
const bounded = retries !== void 0 || delayMs !== void 0;
|
|
18163
18345
|
const attempts = retries ?? LOCK_RETRIES2;
|
|
18164
18346
|
const delay = delayMs ?? LOCK_RETRY_DELAY_MS2;
|
|
@@ -18168,9 +18350,9 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18168
18350
|
let extensionLogged = false;
|
|
18169
18351
|
for (let i = 0; ; i++) {
|
|
18170
18352
|
try {
|
|
18171
|
-
const fd = await
|
|
18353
|
+
const fd = await fs20.promises.open(
|
|
18172
18354
|
lockPath,
|
|
18173
|
-
|
|
18355
|
+
fs20.constants.O_CREAT | fs20.constants.O_EXCL | fs20.constants.O_WRONLY
|
|
18174
18356
|
);
|
|
18175
18357
|
await fd.write(String(process.pid));
|
|
18176
18358
|
await fd.close();
|
|
@@ -18179,7 +18361,7 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18179
18361
|
if (err.code !== "EEXIST") throw err;
|
|
18180
18362
|
let regularLockFile = false;
|
|
18181
18363
|
try {
|
|
18182
|
-
regularLockFile = (await
|
|
18364
|
+
regularLockFile = (await fs20.promises.lstat(lockPath)).isFile();
|
|
18183
18365
|
} catch (statErr) {
|
|
18184
18366
|
if (statErr.code === "ENOENT") {
|
|
18185
18367
|
i--;
|
|
@@ -18241,21 +18423,21 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
18241
18423
|
}
|
|
18242
18424
|
async function releaseLock3(repoRoot, tool, sessionId) {
|
|
18243
18425
|
try {
|
|
18244
|
-
await
|
|
18426
|
+
await fs20.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
|
|
18245
18427
|
} catch {
|
|
18246
18428
|
}
|
|
18247
18429
|
}
|
|
18248
18430
|
async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now()) {
|
|
18249
18431
|
let entries;
|
|
18250
18432
|
try {
|
|
18251
|
-
entries = await
|
|
18433
|
+
entries = await fs20.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
18252
18434
|
} catch {
|
|
18253
18435
|
return 0;
|
|
18254
18436
|
}
|
|
18255
18437
|
let removed = 0;
|
|
18256
18438
|
for (const entry of entries) {
|
|
18257
18439
|
if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
|
|
18258
|
-
const file =
|
|
18440
|
+
const file = path19.join(stateDir3(), entry.name);
|
|
18259
18441
|
if (await reapLockIfStale(file, {
|
|
18260
18442
|
maxAgeMs: ttlMs,
|
|
18261
18443
|
preserveLiveOwner: true,
|
|
@@ -18317,12 +18499,12 @@ function withPendingCapture(record, work) {
|
|
|
18317
18499
|
return activeCapture.run({ record, save: () => saveCapture(record) }, work);
|
|
18318
18500
|
}
|
|
18319
18501
|
function queueDirectory(repoRoot, tool, sessionId) {
|
|
18320
|
-
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
18321
|
-
const key = crypto7.createHash("sha256").update(`${
|
|
18322
|
-
return
|
|
18502
|
+
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path20.join(os10.homedir(), ".hillclimb", "git-traces");
|
|
18503
|
+
const key = crypto7.createHash("sha256").update(`${path20.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
|
|
18504
|
+
return path20.join(stateDir4, "pending-v1", key);
|
|
18323
18505
|
}
|
|
18324
18506
|
function captureDirectory(record) {
|
|
18325
|
-
return
|
|
18507
|
+
return path20.join(
|
|
18326
18508
|
queueDirectory(record.repoRoot, record.tool, record.sessionId),
|
|
18327
18509
|
record.id
|
|
18328
18510
|
);
|
|
@@ -18330,20 +18512,20 @@ function captureDirectory(record) {
|
|
|
18330
18512
|
function writeDurable(file, body) {
|
|
18331
18513
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
18332
18514
|
try {
|
|
18333
|
-
const descriptor =
|
|
18515
|
+
const descriptor = fs21.openSync(temporary, "w", 384);
|
|
18334
18516
|
try {
|
|
18335
|
-
|
|
18336
|
-
|
|
18517
|
+
fs21.writeFileSync(descriptor, body);
|
|
18518
|
+
fs21.fsyncSync(descriptor);
|
|
18337
18519
|
} finally {
|
|
18338
|
-
|
|
18520
|
+
fs21.closeSync(descriptor);
|
|
18339
18521
|
}
|
|
18340
|
-
|
|
18522
|
+
fs21.renameSync(temporary, file);
|
|
18341
18523
|
try {
|
|
18342
|
-
const directory =
|
|
18524
|
+
const directory = fs21.openSync(path20.dirname(file), "r");
|
|
18343
18525
|
try {
|
|
18344
|
-
|
|
18526
|
+
fs21.fsyncSync(directory);
|
|
18345
18527
|
} finally {
|
|
18346
|
-
|
|
18528
|
+
fs21.closeSync(directory);
|
|
18347
18529
|
}
|
|
18348
18530
|
} catch (err) {
|
|
18349
18531
|
if (!["EINVAL", "ENOTSUP", "EBADF", "EPERM", "EISDIR"].includes(
|
|
@@ -18352,7 +18534,7 @@ function writeDurable(file, body) {
|
|
|
18352
18534
|
throw err;
|
|
18353
18535
|
}
|
|
18354
18536
|
} finally {
|
|
18355
|
-
if (
|
|
18537
|
+
if (fs21.existsSync(temporary)) fs21.unlinkSync(temporary);
|
|
18356
18538
|
}
|
|
18357
18539
|
}
|
|
18358
18540
|
function saveCapture(record) {
|
|
@@ -18363,7 +18545,7 @@ function saveCapture(record) {
|
|
|
18363
18545
|
let status = "failed";
|
|
18364
18546
|
try {
|
|
18365
18547
|
writeDurable(
|
|
18366
|
-
|
|
18548
|
+
path20.join(captureDirectory(record), "capture.json"),
|
|
18367
18549
|
JSON.stringify(record)
|
|
18368
18550
|
);
|
|
18369
18551
|
status = "ok";
|
|
@@ -18375,7 +18557,7 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
18375
18557
|
const directory = queueDirectory(repoRoot, tool, sessionId);
|
|
18376
18558
|
let entries;
|
|
18377
18559
|
try {
|
|
18378
|
-
entries =
|
|
18560
|
+
entries = fs21.readdirSync(directory, { withFileTypes: true });
|
|
18379
18561
|
} catch (err) {
|
|
18380
18562
|
if (err.code === "ENOENT") return [];
|
|
18381
18563
|
throw err;
|
|
@@ -18383,10 +18565,10 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
18383
18565
|
const records = [];
|
|
18384
18566
|
for (const entry of entries) {
|
|
18385
18567
|
if (!entry.isDirectory() || !/^[0-9a-f-]{36}$/.test(entry.name)) continue;
|
|
18386
|
-
const file =
|
|
18387
|
-
if (!
|
|
18568
|
+
const file = path20.join(directory, entry.name, "capture.json");
|
|
18569
|
+
if (!fs21.existsSync(file)) continue;
|
|
18388
18570
|
const record = JSON.parse(
|
|
18389
|
-
|
|
18571
|
+
fs21.readFileSync(file, "utf-8")
|
|
18390
18572
|
);
|
|
18391
18573
|
if (record.version !== 1 || record.id !== entry.name || record.repoRoot !== repoRoot || record.tool !== tool || record.sessionId !== sessionId) {
|
|
18392
18574
|
throw new Error(`Invalid pending Git capture: ${file}`);
|
|
@@ -18399,13 +18581,13 @@ function hasPendingCaptures(repoRoot, tool, sessionId) {
|
|
|
18399
18581
|
return listPendingCaptures2(repoRoot, tool, sessionId).length > 0;
|
|
18400
18582
|
}
|
|
18401
18583
|
function listPendingQueues() {
|
|
18402
|
-
const root =
|
|
18403
|
-
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
18584
|
+
const root = path20.join(
|
|
18585
|
+
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path20.join(os10.homedir(), ".hillclimb", "git-traces"),
|
|
18404
18586
|
"pending-v1"
|
|
18405
18587
|
);
|
|
18406
18588
|
let queueDirs;
|
|
18407
18589
|
try {
|
|
18408
|
-
queueDirs =
|
|
18590
|
+
queueDirs = fs21.readdirSync(root, { withFileTypes: true });
|
|
18409
18591
|
} catch (err) {
|
|
18410
18592
|
if (err.code === "ENOENT") return [];
|
|
18411
18593
|
throw err;
|
|
@@ -18413,15 +18595,15 @@ function listPendingQueues() {
|
|
|
18413
18595
|
const queues = [];
|
|
18414
18596
|
for (const queueDir of queueDirs) {
|
|
18415
18597
|
if (!queueDir.isDirectory()) continue;
|
|
18416
|
-
const directory =
|
|
18598
|
+
const directory = path20.join(root, queueDir.name);
|
|
18417
18599
|
let identity = null;
|
|
18418
18600
|
try {
|
|
18419
|
-
for (const entry of
|
|
18601
|
+
for (const entry of fs21.readdirSync(directory, { withFileTypes: true })) {
|
|
18420
18602
|
if (!entry.isDirectory()) continue;
|
|
18421
|
-
const file =
|
|
18422
|
-
if (!
|
|
18603
|
+
const file = path20.join(directory, entry.name, "capture.json");
|
|
18604
|
+
if (!fs21.existsSync(file)) continue;
|
|
18423
18605
|
identity = JSON.parse(
|
|
18424
|
-
|
|
18606
|
+
fs21.readFileSync(file, "utf-8")
|
|
18425
18607
|
);
|
|
18426
18608
|
break;
|
|
18427
18609
|
}
|
|
@@ -18454,15 +18636,15 @@ function noteReplayFailure(record, error, now = Date.now()) {
|
|
|
18454
18636
|
}
|
|
18455
18637
|
function queueDiskBytes(directory) {
|
|
18456
18638
|
let diskBytes = 0;
|
|
18457
|
-
if (
|
|
18458
|
-
for (const entry of
|
|
18639
|
+
if (fs21.existsSync(directory)) {
|
|
18640
|
+
for (const entry of fs21.readdirSync(directory, { withFileTypes: true })) {
|
|
18459
18641
|
if (!entry.isDirectory()) continue;
|
|
18460
|
-
for (const file of
|
|
18642
|
+
for (const file of fs21.readdirSync(path20.join(directory, entry.name), {
|
|
18461
18643
|
withFileTypes: true
|
|
18462
18644
|
})) {
|
|
18463
18645
|
if (file.isFile())
|
|
18464
|
-
diskBytes +=
|
|
18465
|
-
|
|
18646
|
+
diskBytes += fs21.statSync(
|
|
18647
|
+
path20.join(directory, entry.name, file.name)
|
|
18466
18648
|
).size;
|
|
18467
18649
|
}
|
|
18468
18650
|
}
|
|
@@ -18478,7 +18660,7 @@ function pendingBytes(records) {
|
|
|
18478
18660
|
return queueDiskBytes(
|
|
18479
18661
|
queueDirectory(first.repoRoot, first.tool, first.sessionId)
|
|
18480
18662
|
) + records.reduce(
|
|
18481
|
-
(sum, record) => sum + chargedBytes(record) + (
|
|
18663
|
+
(sum, record) => sum + chargedBytes(record) + (fs21.existsSync(path20.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
|
|
18482
18664
|
0
|
|
18483
18665
|
);
|
|
18484
18666
|
}
|
|
@@ -18643,7 +18825,7 @@ function storePendingCapture(params) {
|
|
|
18643
18825
|
);
|
|
18644
18826
|
}
|
|
18645
18827
|
const directory = captureDirectory(record);
|
|
18646
|
-
|
|
18828
|
+
fs21.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
18647
18829
|
const refRoot = `refs/hillclimb/pending-v1/${record.id}`;
|
|
18648
18830
|
let published = false;
|
|
18649
18831
|
try {
|
|
@@ -18686,7 +18868,7 @@ function storePendingCapture(params) {
|
|
|
18686
18868
|
return record;
|
|
18687
18869
|
} finally {
|
|
18688
18870
|
if (!published) {
|
|
18689
|
-
|
|
18871
|
+
fs21.rmSync(directory, { recursive: true, force: true });
|
|
18690
18872
|
for (const label of [
|
|
18691
18873
|
"snapshot",
|
|
18692
18874
|
"tree",
|
|
@@ -18717,7 +18899,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
18717
18899
|
let status = "failed";
|
|
18718
18900
|
try {
|
|
18719
18901
|
if (saved) {
|
|
18720
|
-
const bytes =
|
|
18902
|
+
const bytes = fs21.readFileSync(path20.join(directory, saved.file));
|
|
18721
18903
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256) {
|
|
18722
18904
|
throw new Error(
|
|
18723
18905
|
`Pending Git artifact is corrupt: ${saved.file}; capture retained`
|
|
@@ -18737,7 +18919,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
18737
18919
|
);
|
|
18738
18920
|
}
|
|
18739
18921
|
const file = `${crypto7.createHash("sha256").update(key).digest("hex")}.artifact`;
|
|
18740
|
-
writeDurable(
|
|
18922
|
+
writeDurable(path20.join(directory, file), buffer);
|
|
18741
18923
|
context.record.uploads[key] = {
|
|
18742
18924
|
file,
|
|
18743
18925
|
bytes: buffer.byteLength,
|
|
@@ -18747,7 +18929,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
18747
18929
|
context.save();
|
|
18748
18930
|
} catch (err) {
|
|
18749
18931
|
delete context.record.uploads[key];
|
|
18750
|
-
|
|
18932
|
+
fs21.rmSync(path20.join(directory, file), { force: true });
|
|
18751
18933
|
throw err;
|
|
18752
18934
|
}
|
|
18753
18935
|
status = "ok";
|
|
@@ -18779,8 +18961,8 @@ function readPreparedCaptureArtifact(context, filename, input) {
|
|
|
18779
18961
|
throw new Error(
|
|
18780
18962
|
`Prepared Git artifact input changed: ${filename}; capture retained`
|
|
18781
18963
|
);
|
|
18782
|
-
const bytes =
|
|
18783
|
-
|
|
18964
|
+
const bytes = fs21.readFileSync(
|
|
18965
|
+
path20.join(captureDirectory(context.record), saved.file)
|
|
18784
18966
|
);
|
|
18785
18967
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256)
|
|
18786
18968
|
throw new Error(
|
|
@@ -18799,7 +18981,7 @@ function completePendingCapture(record) {
|
|
|
18799
18981
|
function removeCompletedCapture(record) {
|
|
18800
18982
|
if (!record.completed)
|
|
18801
18983
|
throw new Error("Cannot remove an unacknowledged Git capture");
|
|
18802
|
-
|
|
18984
|
+
fs21.rmSync(captureDirectory(record), { recursive: true, force: true });
|
|
18803
18985
|
for (const label of ["snapshot", "tree", "head", "baseline", "previous"]) {
|
|
18804
18986
|
deleteRef(
|
|
18805
18987
|
record.repoRoot,
|
|
@@ -18872,7 +19054,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
18872
19054
|
}
|
|
18873
19055
|
|
|
18874
19056
|
// package.json
|
|
18875
|
-
var version = "0.9.
|
|
19057
|
+
var version = "0.9.14";
|
|
18876
19058
|
|
|
18877
19059
|
// src/version.ts
|
|
18878
19060
|
var CLI_VERSION = version;
|
|
@@ -18896,7 +19078,7 @@ function lineHasAssistant(line) {
|
|
|
18896
19078
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
18897
19079
|
}
|
|
18898
19080
|
async function hasAssistantMessage(transcriptPath) {
|
|
18899
|
-
const stream =
|
|
19081
|
+
const stream = fs22.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
18900
19082
|
let buffer = "";
|
|
18901
19083
|
try {
|
|
18902
19084
|
for await (const chunk of stream) {
|
|
@@ -18983,8 +19165,8 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
18983
19165
|
const workspace = payload.workspace_roots?.[0];
|
|
18984
19166
|
if (!id || !workspace) return void 0;
|
|
18985
19167
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
18986
|
-
return
|
|
18987
|
-
|
|
19168
|
+
return path21.join(
|
|
19169
|
+
os11.homedir(),
|
|
18988
19170
|
".cursor",
|
|
18989
19171
|
"projects",
|
|
18990
19172
|
encoded,
|
|
@@ -19039,14 +19221,14 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
19039
19221
|
const cwd = resolveHookCwd(payload);
|
|
19040
19222
|
if (!parentSessionId || !cwd || !payload.transcript_path) return;
|
|
19041
19223
|
try {
|
|
19042
|
-
const directory =
|
|
19043
|
-
|
|
19224
|
+
const directory = path21.join(
|
|
19225
|
+
path21.dirname(path21.resolve(payload.transcript_path)),
|
|
19044
19226
|
parentSessionId,
|
|
19045
19227
|
"subagents"
|
|
19046
19228
|
);
|
|
19047
19229
|
let names;
|
|
19048
19230
|
try {
|
|
19049
|
-
names = await
|
|
19231
|
+
names = await fs22.promises.readdir(directory);
|
|
19050
19232
|
} catch (err) {
|
|
19051
19233
|
if (err.code === "ENOENT") return;
|
|
19052
19234
|
throw err;
|
|
@@ -19055,12 +19237,12 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
19055
19237
|
for (const name of names) {
|
|
19056
19238
|
const match = /^agent-([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
|
|
19057
19239
|
if (!match) continue;
|
|
19058
|
-
const file =
|
|
19240
|
+
const file = path21.join(directory, name);
|
|
19059
19241
|
try {
|
|
19060
19242
|
files.push({
|
|
19061
19243
|
agentId: match[1],
|
|
19062
19244
|
file,
|
|
19063
|
-
modifiedMs: (await
|
|
19245
|
+
modifiedMs: (await fs22.promises.stat(file)).mtimeMs
|
|
19064
19246
|
});
|
|
19065
19247
|
} catch {
|
|
19066
19248
|
}
|
|
@@ -19226,7 +19408,7 @@ async function sweepCodexForkTranscripts(payload) {
|
|
|
19226
19408
|
const parent = await findProjectForCwd(cwd);
|
|
19227
19409
|
if (!parent) return;
|
|
19228
19410
|
const registeredRoots = Object.keys((await loadProjects()).projects).map(
|
|
19229
|
-
(root) =>
|
|
19411
|
+
(root) => path21.resolve(root)
|
|
19230
19412
|
);
|
|
19231
19413
|
const discovery = await discoverCodexForkThreads({
|
|
19232
19414
|
parentThreadId: parentSessionId,
|
|
@@ -19367,9 +19549,9 @@ async function runUploadForSession(payload, options) {
|
|
|
19367
19549
|
);
|
|
19368
19550
|
return "skipped";
|
|
19369
19551
|
}
|
|
19370
|
-
const transcriptResolved =
|
|
19552
|
+
const transcriptResolved = path21.resolve(transcriptPath);
|
|
19371
19553
|
try {
|
|
19372
|
-
const stat = await
|
|
19554
|
+
const stat = await fs22.promises.stat(transcriptResolved);
|
|
19373
19555
|
if (!stat.isFile()) {
|
|
19374
19556
|
appendLog(
|
|
19375
19557
|
"warn",
|
|
@@ -19425,7 +19607,7 @@ function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
|
|
|
19425
19607
|
};
|
|
19426
19608
|
return {
|
|
19427
19609
|
repoPath: repoRoot,
|
|
19428
|
-
label:
|
|
19610
|
+
label: path21.basename(repoRoot),
|
|
19429
19611
|
files: [sourceFile],
|
|
19430
19612
|
sourceNames: [sourceTool],
|
|
19431
19613
|
lastModified: /* @__PURE__ */ new Date()
|
|
@@ -19436,7 +19618,7 @@ async function buildRedactChain(repoRoot, capturedSecrets = []) {
|
|
|
19436
19618
|
"secret-discovery",
|
|
19437
19619
|
() => discoverEnvFiles(repoRoot)
|
|
19438
19620
|
);
|
|
19439
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
19621
|
+
const envFilePaths = envFileNames.map((n) => path21.join(repoRoot, n));
|
|
19440
19622
|
const secretResult = await measureAgentStage(
|
|
19441
19623
|
"secret-collection",
|
|
19442
19624
|
() => collectSecrets(repoRoot, envFilePaths, [])
|
|
@@ -20085,7 +20267,7 @@ async function uploadSession(args) {
|
|
|
20085
20267
|
try {
|
|
20086
20268
|
raw = await measureAgentStage(
|
|
20087
20269
|
"transcript-read",
|
|
20088
|
-
() => captured ? Promise.resolve(captured.bytes) :
|
|
20270
|
+
() => captured ? Promise.resolve(captured.bytes) : fs22.promises.readFile(transcriptPath)
|
|
20089
20271
|
);
|
|
20090
20272
|
} catch (err) {
|
|
20091
20273
|
if (err.code === "ERR_FS_FILE_TOO_LARGE") {
|
|
@@ -20456,8 +20638,8 @@ import crypto9 from "crypto";
|
|
|
20456
20638
|
|
|
20457
20639
|
// src/git-traces/handlers.ts
|
|
20458
20640
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
20459
|
-
import
|
|
20460
|
-
import
|
|
20641
|
+
import fs23 from "fs";
|
|
20642
|
+
import path22 from "path";
|
|
20461
20643
|
var GIT_TRACES_SLUG = "git-traces";
|
|
20462
20644
|
var MAX_FORK_SWEEP_THREADS2 = 8;
|
|
20463
20645
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
@@ -20465,7 +20647,7 @@ var MAX_BASELINE_BUNDLE_UPLOAD_BYTES = 1024 * 1024 * 1024;
|
|
|
20465
20647
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
20466
20648
|
var REBASELINE_AFTER_DIFF_FAILURES = 2;
|
|
20467
20649
|
async function withRepoCaptureLock(repoRoot, capture) {
|
|
20468
|
-
const canonicalRoot =
|
|
20650
|
+
const canonicalRoot = fs23.realpathSync(repoRoot);
|
|
20469
20651
|
await acquireLock3(canonicalRoot, "snapshot-index-v1", null);
|
|
20470
20652
|
try {
|
|
20471
20653
|
return capture();
|
|
@@ -20523,7 +20705,7 @@ var PendingQueueConflictError = class extends Error {
|
|
|
20523
20705
|
async function loadConfiguredRepos() {
|
|
20524
20706
|
const file = await loadProjects();
|
|
20525
20707
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
20526
|
-
repoRoot:
|
|
20708
|
+
repoRoot: path22.resolve(repoRoot),
|
|
20527
20709
|
config
|
|
20528
20710
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
20529
20711
|
}
|
|
@@ -20541,12 +20723,12 @@ async function configuredReposFor(trigger) {
|
|
|
20541
20723
|
return repos;
|
|
20542
20724
|
}
|
|
20543
20725
|
async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
20544
|
-
const source =
|
|
20545
|
-
const target =
|
|
20546
|
-
if (
|
|
20726
|
+
const source = path22.join(parentRoot, ".codex", "hooks.json");
|
|
20727
|
+
const target = path22.join(worktreeRoot, ".codex", "hooks.json");
|
|
20728
|
+
if (fs23.existsSync(target) || !fs23.existsSync(source)) return;
|
|
20547
20729
|
try {
|
|
20548
|
-
|
|
20549
|
-
|
|
20730
|
+
fs23.mkdirSync(path22.dirname(target), { recursive: true });
|
|
20731
|
+
fs23.copyFileSync(source, target, fs23.constants.COPYFILE_EXCL);
|
|
20550
20732
|
} catch (err) {
|
|
20551
20733
|
appendLog(
|
|
20552
20734
|
"warn",
|
|
@@ -20568,7 +20750,7 @@ async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
|
20568
20750
|
}
|
|
20569
20751
|
}
|
|
20570
20752
|
function repoLabel(repoRoot) {
|
|
20571
|
-
return
|
|
20753
|
+
return path22.basename(repoRoot) || repoRoot;
|
|
20572
20754
|
}
|
|
20573
20755
|
function resolveCwd2(payload) {
|
|
20574
20756
|
return resolveHookCwd(payload);
|
|
@@ -22838,7 +23020,7 @@ async function collectStopTargets(tool, sessionId, repos, repoByRoot) {
|
|
|
22838
23020
|
let needsLineageCheck = false;
|
|
22839
23021
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
22840
23022
|
for (const { state } of storedStates) {
|
|
22841
|
-
const repo = repoByRoot.get(
|
|
23023
|
+
const repo = repoByRoot.get(path22.resolve(state.repoRoot));
|
|
22842
23024
|
if (!repo) {
|
|
22843
23025
|
missingConfig++;
|
|
22844
23026
|
appendLog(
|
|
@@ -22870,7 +23052,7 @@ async function lateInitSkipReason(payload, tool) {
|
|
|
22870
23052
|
const transcriptPath = payload.transcript_path;
|
|
22871
23053
|
if (!transcriptPath) return tool === "codex" ? null : "no-transcript-path";
|
|
22872
23054
|
try {
|
|
22873
|
-
const stat = await
|
|
23055
|
+
const stat = await fs23.promises.stat(path22.resolve(transcriptPath));
|
|
22874
23056
|
return stat.isFile() ? null : "transcript-not-a-file";
|
|
22875
23057
|
} catch {
|
|
22876
23058
|
return "transcript-missing";
|
|
@@ -23079,9 +23261,9 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23079
23261
|
if (sessionId) {
|
|
23080
23262
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
23081
23263
|
for (const { state } of states) {
|
|
23082
|
-
const repoRoot =
|
|
23264
|
+
const repoRoot = path22.resolve(state.repoRoot);
|
|
23083
23265
|
repoRoots.add(repoRoot);
|
|
23084
|
-
const canProcess = repoByRoot.has(repoRoot) || project &&
|
|
23266
|
+
const canProcess = repoByRoot.has(repoRoot) || project && path22.resolve(project.repoRoot) === repoRoot;
|
|
23085
23267
|
if (canProcess && tool === "codex" && state.codexLineageChecked !== true) {
|
|
23086
23268
|
needsLineageCheck = true;
|
|
23087
23269
|
}
|
|
@@ -23118,7 +23300,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23118
23300
|
let rejected = 0;
|
|
23119
23301
|
const finalCaptures = /* @__PURE__ */ new Map();
|
|
23120
23302
|
for (const repoRoot of repoRoots) {
|
|
23121
|
-
const repo = repoByRoot.get(
|
|
23303
|
+
const repo = repoByRoot.get(path22.resolve(repoRoot)) ?? (project && path22.resolve(project.repoRoot) === path22.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
23122
23304
|
if (repo)
|
|
23123
23305
|
finalCaptures.set(
|
|
23124
23306
|
repoRoot,
|
|
@@ -23126,7 +23308,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
23126
23308
|
);
|
|
23127
23309
|
}
|
|
23128
23310
|
for (const repoRoot of repoRoots) {
|
|
23129
|
-
const repo = repoByRoot.get(
|
|
23311
|
+
const repo = repoByRoot.get(path22.resolve(repoRoot)) ?? (project && path22.resolve(project.repoRoot) === path22.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
23130
23312
|
if (!repo) {
|
|
23131
23313
|
skipped++;
|
|
23132
23314
|
appendLog(
|
|
@@ -23505,29 +23687,29 @@ ${stack}` : ""}`
|
|
|
23505
23687
|
}
|
|
23506
23688
|
|
|
23507
23689
|
// src/outputs/zip.ts
|
|
23508
|
-
import
|
|
23509
|
-
import
|
|
23690
|
+
import fs25 from "fs";
|
|
23691
|
+
import path24 from "path";
|
|
23510
23692
|
import archiver2 from "archiver";
|
|
23511
23693
|
|
|
23512
23694
|
// src/outputs/downloads.ts
|
|
23513
23695
|
import { execSync as execSync2 } from "child_process";
|
|
23514
|
-
import
|
|
23515
|
-
import
|
|
23516
|
-
import
|
|
23696
|
+
import fs24 from "fs";
|
|
23697
|
+
import os12 from "os";
|
|
23698
|
+
import path23 from "path";
|
|
23517
23699
|
function getDownloadsFolder() {
|
|
23518
|
-
const home =
|
|
23700
|
+
const home = os12.homedir();
|
|
23519
23701
|
if (process.platform === "linux") {
|
|
23520
23702
|
try {
|
|
23521
23703
|
const xdgDir = execSync2("xdg-user-dir DOWNLOAD", {
|
|
23522
23704
|
encoding: "utf-8",
|
|
23523
23705
|
timeout: 3e3
|
|
23524
23706
|
}).trim();
|
|
23525
|
-
if (xdgDir &&
|
|
23707
|
+
if (xdgDir && fs24.existsSync(xdgDir)) return xdgDir;
|
|
23526
23708
|
} catch {
|
|
23527
23709
|
}
|
|
23528
23710
|
}
|
|
23529
|
-
const downloads =
|
|
23530
|
-
if (
|
|
23711
|
+
const downloads = path23.join(home, "Downloads");
|
|
23712
|
+
if (fs24.existsSync(downloads)) return downloads;
|
|
23531
23713
|
return home;
|
|
23532
23714
|
}
|
|
23533
23715
|
|
|
@@ -23536,11 +23718,11 @@ function sanitizeFilename(name) {
|
|
|
23536
23718
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
23537
23719
|
}
|
|
23538
23720
|
function getUniqueFilename(dir, base, ext) {
|
|
23539
|
-
let candidate =
|
|
23540
|
-
if (!
|
|
23721
|
+
let candidate = path24.join(dir, `${base}${ext}`);
|
|
23722
|
+
if (!fs25.existsSync(candidate)) return candidate;
|
|
23541
23723
|
let i = 1;
|
|
23542
|
-
while (
|
|
23543
|
-
candidate =
|
|
23724
|
+
while (fs25.existsSync(candidate)) {
|
|
23725
|
+
candidate = path24.join(dir, `${base}-${i}${ext}`);
|
|
23544
23726
|
i++;
|
|
23545
23727
|
}
|
|
23546
23728
|
return candidate;
|
|
@@ -23550,13 +23732,13 @@ var ZipOutput = class {
|
|
|
23550
23732
|
label = "Save as .zip to Downloads";
|
|
23551
23733
|
async emit(group, options) {
|
|
23552
23734
|
const downloadsDir = getDownloadsFolder();
|
|
23553
|
-
const repoName = sanitizeFilename(
|
|
23735
|
+
const repoName = sanitizeFilename(path24.basename(group.repoPath));
|
|
23554
23736
|
const timeRange = options.timeRange;
|
|
23555
23737
|
const rangePart = timeRange?.label ?? "all";
|
|
23556
23738
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
23557
23739
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
23558
23740
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
23559
|
-
const output =
|
|
23741
|
+
const output = fs25.createWriteStream(outputPath);
|
|
23560
23742
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
23561
23743
|
const done = new Promise((resolve, reject) => {
|
|
23562
23744
|
output.on("close", resolve);
|
|
@@ -23750,15 +23932,15 @@ async function confirmExport(group, output) {
|
|
|
23750
23932
|
}
|
|
23751
23933
|
|
|
23752
23934
|
// src/sources/claude.ts
|
|
23753
|
-
import
|
|
23754
|
-
import
|
|
23755
|
-
import
|
|
23935
|
+
import fs26 from "fs";
|
|
23936
|
+
import os13 from "os";
|
|
23937
|
+
import path25 from "path";
|
|
23756
23938
|
import readline3 from "readline";
|
|
23757
23939
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
23758
23940
|
async function resolveRepoPath(projectDir) {
|
|
23759
|
-
const indexPath =
|
|
23941
|
+
const indexPath = path25.join(projectDir, "sessions-index.json");
|
|
23760
23942
|
try {
|
|
23761
|
-
const raw = await
|
|
23943
|
+
const raw = await fs26.promises.readFile(indexPath, "utf-8");
|
|
23762
23944
|
const data = JSON.parse(raw);
|
|
23763
23945
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
23764
23946
|
return data.originalPath;
|
|
@@ -23766,12 +23948,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
23766
23948
|
} catch {
|
|
23767
23949
|
}
|
|
23768
23950
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
23769
|
-
const entries = await
|
|
23951
|
+
const entries = await fs26.promises.readdir(projectDir, {
|
|
23770
23952
|
withFileTypes: true
|
|
23771
23953
|
});
|
|
23772
23954
|
for (const entry of entries) {
|
|
23773
23955
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
23774
|
-
const cwd = await extractCwdFromJsonl(
|
|
23956
|
+
const cwd = await extractCwdFromJsonl(path25.join(projectDir, entry.name));
|
|
23775
23957
|
if (cwd) {
|
|
23776
23958
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
23777
23959
|
}
|
|
@@ -23790,7 +23972,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
23790
23972
|
return null;
|
|
23791
23973
|
}
|
|
23792
23974
|
async function extractCwdFromJsonl(filePath) {
|
|
23793
|
-
const stream =
|
|
23975
|
+
const stream = fs26.createReadStream(filePath, { encoding: "utf-8" });
|
|
23794
23976
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23795
23977
|
try {
|
|
23796
23978
|
for await (const line of rl) {
|
|
@@ -23812,12 +23994,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
23812
23994
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
23813
23995
|
let entries;
|
|
23814
23996
|
try {
|
|
23815
|
-
entries = await
|
|
23997
|
+
entries = await fs26.promises.readdir(dir, { withFileTypes: true });
|
|
23816
23998
|
} catch {
|
|
23817
23999
|
return;
|
|
23818
24000
|
}
|
|
23819
24001
|
for (const entry of entries) {
|
|
23820
|
-
const fullPath =
|
|
24002
|
+
const fullPath = path25.join(dir, entry.name);
|
|
23821
24003
|
if (entry.isDirectory()) {
|
|
23822
24004
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
23823
24005
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -23839,19 +24021,19 @@ function fallbackDecode(encodedName) {
|
|
|
23839
24021
|
var ClaudeSource = class {
|
|
23840
24022
|
name = "claude";
|
|
23841
24023
|
async scan() {
|
|
23842
|
-
const baseDir =
|
|
24024
|
+
const baseDir = path25.join(os13.homedir(), ".claude", "projects");
|
|
23843
24025
|
try {
|
|
23844
|
-
await
|
|
24026
|
+
await fs26.promises.access(baseDir);
|
|
23845
24027
|
} catch {
|
|
23846
24028
|
return [];
|
|
23847
24029
|
}
|
|
23848
|
-
const projectDirs = await
|
|
24030
|
+
const projectDirs = await fs26.promises.readdir(baseDir, {
|
|
23849
24031
|
withFileTypes: true
|
|
23850
24032
|
});
|
|
23851
24033
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
23852
24034
|
const resultArrays = await Promise.all(
|
|
23853
24035
|
dirEntries.map(async (dir) => {
|
|
23854
|
-
const projectPath =
|
|
24036
|
+
const projectPath = path25.join(baseDir, dir.name);
|
|
23855
24037
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
23856
24038
|
const files = [];
|
|
23857
24039
|
await collectFiles(
|
|
@@ -23869,12 +24051,12 @@ var ClaudeSource = class {
|
|
|
23869
24051
|
};
|
|
23870
24052
|
|
|
23871
24053
|
// src/sources/codex.ts
|
|
23872
|
-
import
|
|
23873
|
-
import
|
|
23874
|
-
import
|
|
24054
|
+
import fs27 from "fs";
|
|
24055
|
+
import os14 from "os";
|
|
24056
|
+
import path26 from "path";
|
|
23875
24057
|
import readline4 from "readline";
|
|
23876
24058
|
async function parseSessionMeta2(filePath) {
|
|
23877
|
-
const stream =
|
|
24059
|
+
const stream = fs27.createReadStream(filePath, { encoding: "utf-8" });
|
|
23878
24060
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23879
24061
|
try {
|
|
23880
24062
|
for await (const line of rl) {
|
|
@@ -23899,12 +24081,12 @@ async function findJsonlFiles(dir) {
|
|
|
23899
24081
|
async function walk(d) {
|
|
23900
24082
|
let entries;
|
|
23901
24083
|
try {
|
|
23902
|
-
entries = await
|
|
24084
|
+
entries = await fs27.promises.readdir(d, { withFileTypes: true });
|
|
23903
24085
|
} catch {
|
|
23904
24086
|
return;
|
|
23905
24087
|
}
|
|
23906
24088
|
for (const entry of entries) {
|
|
23907
|
-
const full =
|
|
24089
|
+
const full = path26.join(d, entry.name);
|
|
23908
24090
|
if (entry.isDirectory()) {
|
|
23909
24091
|
await walk(full);
|
|
23910
24092
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -23918,11 +24100,11 @@ async function findJsonlFiles(dir) {
|
|
|
23918
24100
|
async function loadHistory(historyPath) {
|
|
23919
24101
|
const map = /* @__PURE__ */ new Map();
|
|
23920
24102
|
try {
|
|
23921
|
-
await
|
|
24103
|
+
await fs27.promises.access(historyPath);
|
|
23922
24104
|
} catch {
|
|
23923
24105
|
return map;
|
|
23924
24106
|
}
|
|
23925
|
-
const stream =
|
|
24107
|
+
const stream = fs27.createReadStream(historyPath, { encoding: "utf-8" });
|
|
23926
24108
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
23927
24109
|
try {
|
|
23928
24110
|
for await (const line of rl) {
|
|
@@ -23949,14 +24131,14 @@ async function loadHistory(historyPath) {
|
|
|
23949
24131
|
var CodexSource = class {
|
|
23950
24132
|
name = "codex";
|
|
23951
24133
|
async scan() {
|
|
23952
|
-
const codexDir =
|
|
23953
|
-
const sessionsDir2 =
|
|
24134
|
+
const codexDir = path26.join(os14.homedir(), ".codex");
|
|
24135
|
+
const sessionsDir2 = path26.join(codexDir, "sessions");
|
|
23954
24136
|
try {
|
|
23955
|
-
await
|
|
24137
|
+
await fs27.promises.access(sessionsDir2);
|
|
23956
24138
|
} catch {
|
|
23957
24139
|
return [];
|
|
23958
24140
|
}
|
|
23959
|
-
const historyPath =
|
|
24141
|
+
const historyPath = path26.join(codexDir, "history.jsonl");
|
|
23960
24142
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
23961
24143
|
findJsonlFiles(sessionsDir2),
|
|
23962
24144
|
loadHistory(historyPath)
|
|
@@ -23979,8 +24161,8 @@ var CodexSource = class {
|
|
|
23979
24161
|
});
|
|
23980
24162
|
const historyLines = historyMap.get(meta.sessionId);
|
|
23981
24163
|
if (historyLines) {
|
|
23982
|
-
const sessionDir =
|
|
23983
|
-
const historyAbsPath =
|
|
24164
|
+
const sessionDir = path26.relative(sessionsDir2, path26.dirname(filePath));
|
|
24165
|
+
const historyAbsPath = path26.join(
|
|
23984
24166
|
sessionsDir2,
|
|
23985
24167
|
sessionDir,
|
|
23986
24168
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -24000,18 +24182,18 @@ var CodexSource = class {
|
|
|
24000
24182
|
};
|
|
24001
24183
|
|
|
24002
24184
|
// src/sources/copilotChat.ts
|
|
24003
|
-
import
|
|
24004
|
-
import
|
|
24005
|
-
import
|
|
24185
|
+
import fs28 from "fs";
|
|
24186
|
+
import os15 from "os";
|
|
24187
|
+
import path27 from "path";
|
|
24006
24188
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24007
24189
|
function vsCodeUserDirs() {
|
|
24008
|
-
const home =
|
|
24190
|
+
const home = os15.homedir();
|
|
24009
24191
|
const dirs = [
|
|
24010
|
-
|
|
24011
|
-
|
|
24192
|
+
path27.join(home, "Library", "Application Support", "Code", "User"),
|
|
24193
|
+
path27.join(home, ".config", "Code", "User")
|
|
24012
24194
|
];
|
|
24013
24195
|
if (process.env.APPDATA) {
|
|
24014
|
-
dirs.push(
|
|
24196
|
+
dirs.push(path27.join(process.env.APPDATA, "Code", "User"));
|
|
24015
24197
|
}
|
|
24016
24198
|
return dirs;
|
|
24017
24199
|
}
|
|
@@ -24026,7 +24208,7 @@ function uriToFsPath(uri) {
|
|
|
24026
24208
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
24027
24209
|
let raw;
|
|
24028
24210
|
try {
|
|
24029
|
-
raw = await
|
|
24211
|
+
raw = await fs28.promises.readFile(workspaceJsonPath, "utf-8");
|
|
24030
24212
|
} catch {
|
|
24031
24213
|
return null;
|
|
24032
24214
|
}
|
|
@@ -24048,10 +24230,10 @@ var CopilotChatSource = class {
|
|
|
24048
24230
|
async scan() {
|
|
24049
24231
|
const results = [];
|
|
24050
24232
|
for (const userDir of vsCodeUserDirs()) {
|
|
24051
|
-
const workspaceStorage =
|
|
24233
|
+
const workspaceStorage = path27.join(userDir, "workspaceStorage");
|
|
24052
24234
|
let hashDirs;
|
|
24053
24235
|
try {
|
|
24054
|
-
hashDirs = await
|
|
24236
|
+
hashDirs = await fs28.promises.readdir(workspaceStorage, {
|
|
24055
24237
|
withFileTypes: true
|
|
24056
24238
|
});
|
|
24057
24239
|
} catch {
|
|
@@ -24059,22 +24241,22 @@ var CopilotChatSource = class {
|
|
|
24059
24241
|
}
|
|
24060
24242
|
for (const hash of hashDirs) {
|
|
24061
24243
|
if (!hash.isDirectory()) continue;
|
|
24062
|
-
const wsRoot =
|
|
24063
|
-
const transcriptsDir =
|
|
24244
|
+
const wsRoot = path27.join(workspaceStorage, hash.name);
|
|
24245
|
+
const transcriptsDir = path27.join(
|
|
24064
24246
|
wsRoot,
|
|
24065
24247
|
"GitHub.copilot-chat",
|
|
24066
24248
|
"transcripts"
|
|
24067
24249
|
);
|
|
24068
24250
|
let transcriptEntries;
|
|
24069
24251
|
try {
|
|
24070
|
-
transcriptEntries = await
|
|
24252
|
+
transcriptEntries = await fs28.promises.readdir(transcriptsDir, {
|
|
24071
24253
|
withFileTypes: true
|
|
24072
24254
|
});
|
|
24073
24255
|
} catch {
|
|
24074
24256
|
continue;
|
|
24075
24257
|
}
|
|
24076
24258
|
const repoPath = await readWorkspaceFolder(
|
|
24077
|
-
|
|
24259
|
+
path27.join(wsRoot, "workspace.json")
|
|
24078
24260
|
);
|
|
24079
24261
|
if (!repoPath) continue;
|
|
24080
24262
|
for (const entry of transcriptEntries) {
|
|
@@ -24082,7 +24264,7 @@ var CopilotChatSource = class {
|
|
|
24082
24264
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
24083
24265
|
results.push({
|
|
24084
24266
|
sourceName: this.name,
|
|
24085
|
-
absolutePath:
|
|
24267
|
+
absolutePath: path27.join(transcriptsDir, entry.name),
|
|
24086
24268
|
repoPath,
|
|
24087
24269
|
metadata: { sessionId }
|
|
24088
24270
|
});
|
|
@@ -24122,7 +24304,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
24122
24304
|
async function filterByTimeRange(group, range) {
|
|
24123
24305
|
const results = await Promise.all(
|
|
24124
24306
|
group.files.map(
|
|
24125
|
-
(f) =>
|
|
24307
|
+
(f) => fs29.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
24126
24308
|
)
|
|
24127
24309
|
);
|
|
24128
24310
|
const filtered = [];
|
|
@@ -24149,10 +24331,10 @@ async function runInteractive() {
|
|
|
24149
24331
|
s.start(`Scanning ${source.name} logs...`);
|
|
24150
24332
|
const allFiles = await source.scan();
|
|
24151
24333
|
const allGroups = await mergeByRepo(allFiles);
|
|
24152
|
-
const repoRoot =
|
|
24334
|
+
const repoRoot = path28.resolve(repo.root);
|
|
24153
24335
|
const matching = allGroups.filter((g) => {
|
|
24154
|
-
const resolved =
|
|
24155
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
24336
|
+
const resolved = path28.resolve(g.repoPath);
|
|
24337
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path28.sep);
|
|
24156
24338
|
});
|
|
24157
24339
|
if (matching.length === 0) {
|
|
24158
24340
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -24183,7 +24365,7 @@ async function runInteractive() {
|
|
|
24183
24365
|
}
|
|
24184
24366
|
}
|
|
24185
24367
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
24186
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
24368
|
+
const envFilePaths = envFileNames.map((n) => path28.join(repoRoot, n));
|
|
24187
24369
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
24188
24370
|
const secretResult = await collectSecrets(
|
|
24189
24371
|
repoRoot,
|