hillclimb 0.9.4 → 0.9.5
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/main.js +359 -202
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-UPXMA7RC.js";
|
|
14
14
|
|
|
15
15
|
// src/main.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import fs28 from "fs";
|
|
17
|
+
import path27 from "path";
|
|
18
18
|
import * as p6 from "@clack/prompts";
|
|
19
19
|
|
|
20
20
|
// src/commands/init.ts
|
|
@@ -2714,9 +2714,9 @@ async function runStatus(args = []) {
|
|
|
2714
2714
|
// src/commands/upload.ts
|
|
2715
2715
|
import { spawn as spawn3 } from "child_process";
|
|
2716
2716
|
import crypto8 from "crypto";
|
|
2717
|
-
import
|
|
2717
|
+
import fs21 from "fs";
|
|
2718
2718
|
import os10 from "os";
|
|
2719
|
-
import
|
|
2719
|
+
import path20 from "path";
|
|
2720
2720
|
|
|
2721
2721
|
// src/agent-pending.ts
|
|
2722
2722
|
import crypto4 from "crypto";
|
|
@@ -15178,16 +15178,144 @@ async function recordDebugLogCompletion(args) {
|
|
|
15178
15178
|
// src/git-traces/pending-captures.ts
|
|
15179
15179
|
import { AsyncLocalStorage } from "async_hooks";
|
|
15180
15180
|
import crypto7 from "crypto";
|
|
15181
|
-
import
|
|
15181
|
+
import fs20 from "fs";
|
|
15182
15182
|
import os9 from "os";
|
|
15183
|
-
import
|
|
15183
|
+
import path19 from "path";
|
|
15184
15184
|
|
|
15185
15185
|
// src/git-traces/git-ops.ts
|
|
15186
15186
|
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync } from "child_process";
|
|
15187
|
-
import
|
|
15187
|
+
import fs18 from "fs";
|
|
15188
15188
|
import os7 from "os";
|
|
15189
|
-
import
|
|
15189
|
+
import path17 from "path";
|
|
15190
15190
|
import { createGzip } from "zlib";
|
|
15191
|
+
|
|
15192
|
+
// src/git-traces/object-store-probe.ts
|
|
15193
|
+
import fs17 from "fs";
|
|
15194
|
+
import path16 from "path";
|
|
15195
|
+
var probedRepos = /* @__PURE__ */ new Set();
|
|
15196
|
+
var SAMPLE_FANOUT_DIRS = 8;
|
|
15197
|
+
var SAMPLE_READS = 20;
|
|
15198
|
+
var SAMPLE_READ_BYTES = 4096;
|
|
15199
|
+
var MAX_NAMED_OBJECTS = 3;
|
|
15200
|
+
function probeObjectStore(repoRoot, trigger, stderr) {
|
|
15201
|
+
if (probedRepos.has(repoRoot)) return;
|
|
15202
|
+
probedRepos.add(repoRoot);
|
|
15203
|
+
const started = Date.now();
|
|
15204
|
+
const fields = [`repo=${repoRoot}`, `trigger=${trigger}`];
|
|
15205
|
+
try {
|
|
15206
|
+
const objectsDir = resolveObjectsDir(repoRoot);
|
|
15207
|
+
if (!objectsDir) {
|
|
15208
|
+
fields.push("objectsDir=unresolved");
|
|
15209
|
+
return;
|
|
15210
|
+
}
|
|
15211
|
+
fields.push(`objectsDir=${objectsDir}`);
|
|
15212
|
+
if (typeof fs17.statfsSync === "function") {
|
|
15213
|
+
const stats = fs17.statfsSync(objectsDir);
|
|
15214
|
+
fields.push(
|
|
15215
|
+
`fsType=${stats.type}`,
|
|
15216
|
+
`freeBytes=${stats.bavail * stats.bsize}`
|
|
15217
|
+
);
|
|
15218
|
+
}
|
|
15219
|
+
const { estimate, sample } = sampleLooseObjects(objectsDir);
|
|
15220
|
+
fields.push(`looseEstimate=${estimate}`);
|
|
15221
|
+
const packDir = path16.join(objectsDir, "pack");
|
|
15222
|
+
const packFiles = fs17.existsSync(packDir) ? fs17.readdirSync(packDir) : [];
|
|
15223
|
+
fields.push(
|
|
15224
|
+
`packs=${packFiles.filter((name) => name.endsWith(".pack")).length}`,
|
|
15225
|
+
`promisorPacks=${packFiles.filter((name) => name.endsWith(".promisor")).length}`,
|
|
15226
|
+
`alternates=${fs17.existsSync(path16.join(objectsDir, "info", "alternates")) ? "yes" : "no"}`
|
|
15227
|
+
);
|
|
15228
|
+
fields.push(...timeSampleReads(sample));
|
|
15229
|
+
for (const sha of namedObjects(stderr)) {
|
|
15230
|
+
const loose = path16.join(objectsDir, sha.slice(0, 2), sha.slice(2));
|
|
15231
|
+
const stats = fs17.statSync(loose, { throwIfNoEntry: false });
|
|
15232
|
+
fields.push(
|
|
15233
|
+
stats ? `named=${sha.slice(0, 8)}(size=${stats.size},blocks=${stats.blocks})` : `named=${sha.slice(0, 8)}(no loose file)`
|
|
15234
|
+
);
|
|
15235
|
+
}
|
|
15236
|
+
} catch (err) {
|
|
15237
|
+
fields.push(
|
|
15238
|
+
`probeError=${err instanceof Error ? err.message : String(err)}`
|
|
15239
|
+
);
|
|
15240
|
+
} finally {
|
|
15241
|
+
fields.push(`probeMs=${Date.now() - started}`);
|
|
15242
|
+
appendLog("info", `git-traces: object store probe (${fields.join(", ")})`);
|
|
15243
|
+
}
|
|
15244
|
+
}
|
|
15245
|
+
function resolveObjectsDir(repoRoot) {
|
|
15246
|
+
let gitDir = path16.join(repoRoot, ".git");
|
|
15247
|
+
const stats = fs17.statSync(gitDir, { throwIfNoEntry: false });
|
|
15248
|
+
if (!stats) return null;
|
|
15249
|
+
if (stats.isFile()) {
|
|
15250
|
+
const match = /^gitdir:\s*(.+)$/m.exec(fs17.readFileSync(gitDir, "utf-8"));
|
|
15251
|
+
if (!match) return null;
|
|
15252
|
+
gitDir = path16.resolve(repoRoot, match[1].trim());
|
|
15253
|
+
}
|
|
15254
|
+
const commonDir = path16.join(gitDir, "commondir");
|
|
15255
|
+
if (fs17.existsSync(commonDir)) {
|
|
15256
|
+
gitDir = path16.resolve(gitDir, fs17.readFileSync(commonDir, "utf-8").trim());
|
|
15257
|
+
}
|
|
15258
|
+
return path16.join(gitDir, "objects");
|
|
15259
|
+
}
|
|
15260
|
+
function sampleLooseObjects(objectsDir) {
|
|
15261
|
+
const fanout = fs17.readdirSync(objectsDir).filter((name) => /^[0-9a-f]{2}$/.test(name)).sort().slice(0, SAMPLE_FANOUT_DIRS);
|
|
15262
|
+
let counted = 0;
|
|
15263
|
+
const sample = [];
|
|
15264
|
+
for (const dir of fanout) {
|
|
15265
|
+
const files = fs17.readdirSync(path16.join(objectsDir, dir));
|
|
15266
|
+
counted += files.length;
|
|
15267
|
+
for (const file of files) {
|
|
15268
|
+
if (sample.length >= SAMPLE_READS) break;
|
|
15269
|
+
sample.push(path16.join(objectsDir, dir, file));
|
|
15270
|
+
}
|
|
15271
|
+
}
|
|
15272
|
+
return {
|
|
15273
|
+
estimate: fanout.length ? Math.round(counted / fanout.length * 256) : 0,
|
|
15274
|
+
sample
|
|
15275
|
+
};
|
|
15276
|
+
}
|
|
15277
|
+
function timeSampleReads(sample) {
|
|
15278
|
+
let dataless = 0;
|
|
15279
|
+
let readErrors = 0;
|
|
15280
|
+
let firstReadError;
|
|
15281
|
+
let totalMs = 0;
|
|
15282
|
+
let maxMs = 0;
|
|
15283
|
+
const buffer = Buffer.alloc(SAMPLE_READ_BYTES);
|
|
15284
|
+
for (const file of sample) {
|
|
15285
|
+
const readStarted = performance.now();
|
|
15286
|
+
try {
|
|
15287
|
+
const stats = fs17.statSync(file);
|
|
15288
|
+
if (stats.size > 0 && stats.blocks === 0) dataless++;
|
|
15289
|
+
const fd = fs17.openSync(file, "r");
|
|
15290
|
+
try {
|
|
15291
|
+
fs17.readSync(fd, buffer, 0, SAMPLE_READ_BYTES, 0);
|
|
15292
|
+
} finally {
|
|
15293
|
+
fs17.closeSync(fd);
|
|
15294
|
+
}
|
|
15295
|
+
} catch (err) {
|
|
15296
|
+
readErrors++;
|
|
15297
|
+
firstReadError ??= err instanceof Error ? err.message : String(err);
|
|
15298
|
+
}
|
|
15299
|
+
const elapsed = performance.now() - readStarted;
|
|
15300
|
+
totalMs += elapsed;
|
|
15301
|
+
maxMs = Math.max(maxMs, elapsed);
|
|
15302
|
+
}
|
|
15303
|
+
const fields = [
|
|
15304
|
+
`sampled=${sample.length}`,
|
|
15305
|
+
`dataless=${dataless}`,
|
|
15306
|
+
`readMsMax=${maxMs.toFixed(1)}`,
|
|
15307
|
+
`readMsTotal=${totalMs.toFixed(1)}`,
|
|
15308
|
+
`readErrors=${readErrors}`
|
|
15309
|
+
];
|
|
15310
|
+
if (firstReadError) fields.push(`firstReadError=${firstReadError}`);
|
|
15311
|
+
return fields;
|
|
15312
|
+
}
|
|
15313
|
+
function namedObjects(stderr) {
|
|
15314
|
+
const shas = new Set(stderr?.match(/\b[0-9a-f]{40}\b/g) ?? []);
|
|
15315
|
+
return [...shas].slice(0, MAX_NAMED_OBJECTS);
|
|
15316
|
+
}
|
|
15317
|
+
|
|
15318
|
+
// src/git-traces/git-ops.ts
|
|
15191
15319
|
var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
15192
15320
|
var DEFAULT_BUNDLE_TIMEOUT_MS = 3e5;
|
|
15193
15321
|
var FULL_TREE_SCAN_TIMEOUT_MS = 3e5;
|
|
@@ -15378,8 +15506,8 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
15378
15506
|
]);
|
|
15379
15507
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
15380
15508
|
function isExcludedSnapshotPath(filePath) {
|
|
15381
|
-
if (EXCLUDED_SNAPSHOT_BASENAMES.has(
|
|
15382
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
15509
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path17.basename(filePath))) return true;
|
|
15510
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path17.extname(filePath).toLowerCase());
|
|
15383
15511
|
}
|
|
15384
15512
|
function isBinaryBuffer(buffer) {
|
|
15385
15513
|
return buffer.includes(0);
|
|
@@ -15400,16 +15528,16 @@ function readTreeBlobHead(repoRoot, sha) {
|
|
|
15400
15528
|
function readWorkingFileHead(absPath) {
|
|
15401
15529
|
let fd = null;
|
|
15402
15530
|
try {
|
|
15403
|
-
fd =
|
|
15531
|
+
fd = fs18.openSync(absPath, "r");
|
|
15404
15532
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
15405
|
-
const bytesRead =
|
|
15533
|
+
const bytesRead = fs18.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
15406
15534
|
return buffer.subarray(0, bytesRead);
|
|
15407
15535
|
} catch {
|
|
15408
15536
|
return null;
|
|
15409
15537
|
} finally {
|
|
15410
15538
|
if (fd !== null) {
|
|
15411
15539
|
try {
|
|
15412
|
-
|
|
15540
|
+
fs18.closeSync(fd);
|
|
15413
15541
|
} catch {
|
|
15414
15542
|
}
|
|
15415
15543
|
}
|
|
@@ -15462,11 +15590,19 @@ function formatGitFailure(command, failure, err) {
|
|
|
15462
15590
|
}
|
|
15463
15591
|
return `${command} failed${details.length ? ` (${details.join("; ")})` : ""}`;
|
|
15464
15592
|
}
|
|
15465
|
-
function
|
|
15593
|
+
function formatGitTimeout(command, failure, timeoutMs, elapsedMs) {
|
|
15594
|
+
const details = [];
|
|
15595
|
+
if (elapsedMs !== void 0) details.push(`elapsed=${elapsedMs}ms`);
|
|
15596
|
+
if (failure.signal) details.push(`signal=${failure.signal}`);
|
|
15597
|
+
const stderr = outputToString(failure.stderr);
|
|
15598
|
+
if (stderr) details.push(`stderr=${truncateOutput(stderr)}`);
|
|
15599
|
+
return `${command} timed out after ${timeoutMs}ms${details.length ? ` (${details.join("; ")})` : ""}`;
|
|
15600
|
+
}
|
|
15601
|
+
function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS, elapsedMs) {
|
|
15466
15602
|
const failure = err;
|
|
15467
15603
|
const command = formatGitCommand(args);
|
|
15468
15604
|
const isTimeout = failure.code === "ETIMEDOUT";
|
|
15469
|
-
const message = isTimeout ?
|
|
15605
|
+
const message = isTimeout ? formatGitTimeout(command, failure, timeoutMs, elapsedMs) : failure.code === "ENOBUFS" ? `${command} output exceeded the ${formatSize(failure.maxBufferBytes ?? EXEC_OPTS.maxBuffer)} limit` : formatGitFailure(command, failure, err);
|
|
15470
15606
|
const wrapped = new Error(message);
|
|
15471
15607
|
wrapped.isGitTimeout = isTimeout;
|
|
15472
15608
|
wrapped.gitExitStatus = failure.code || failure.signal ? void 0 : failure.status;
|
|
@@ -15498,6 +15634,7 @@ function isGitTimeoutError(err) {
|
|
|
15498
15634
|
}
|
|
15499
15635
|
function gitBuffer(repoRoot, args, options = {}) {
|
|
15500
15636
|
const timeout = options.timeoutMs ?? EXEC_OPTS.timeout;
|
|
15637
|
+
const started = Date.now();
|
|
15501
15638
|
try {
|
|
15502
15639
|
return execFileSync2("git", args, {
|
|
15503
15640
|
cwd: repoRoot,
|
|
@@ -15508,7 +15645,15 @@ function gitBuffer(repoRoot, args, options = {}) {
|
|
|
15508
15645
|
timeout
|
|
15509
15646
|
});
|
|
15510
15647
|
} catch (err) {
|
|
15511
|
-
|
|
15648
|
+
const wrapped = createGitError(args, err, timeout, Date.now() - started);
|
|
15649
|
+
if (wrapped.isGitTimeout || wrapped.isGitMissingObject) {
|
|
15650
|
+
probeObjectStore(
|
|
15651
|
+
repoRoot,
|
|
15652
|
+
wrapped.isGitTimeout ? "timeout" : "missing-object",
|
|
15653
|
+
wrapped.gitStderr
|
|
15654
|
+
);
|
|
15655
|
+
}
|
|
15656
|
+
throw wrapped;
|
|
15512
15657
|
}
|
|
15513
15658
|
}
|
|
15514
15659
|
function git(repoRoot, args) {
|
|
@@ -15546,23 +15691,23 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
15546
15691
|
let tmpDir = null;
|
|
15547
15692
|
let copy = null;
|
|
15548
15693
|
try {
|
|
15549
|
-
const indexPath =
|
|
15694
|
+
const indexPath = path17.resolve(
|
|
15550
15695
|
repoRoot,
|
|
15551
15696
|
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
15552
15697
|
);
|
|
15553
|
-
tmpDir =
|
|
15554
|
-
copy =
|
|
15555
|
-
const fd =
|
|
15698
|
+
tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-index-"));
|
|
15699
|
+
copy = path17.join(tmpDir, "index");
|
|
15700
|
+
const fd = fs18.openSync(indexPath, "r");
|
|
15556
15701
|
let stat;
|
|
15557
15702
|
let data;
|
|
15558
15703
|
try {
|
|
15559
|
-
stat =
|
|
15560
|
-
data =
|
|
15704
|
+
stat = fs18.fstatSync(fd);
|
|
15705
|
+
data = fs18.readFileSync(fd);
|
|
15561
15706
|
} finally {
|
|
15562
|
-
|
|
15707
|
+
fs18.closeSync(fd);
|
|
15563
15708
|
}
|
|
15564
|
-
|
|
15565
|
-
|
|
15709
|
+
fs18.writeFileSync(copy, data);
|
|
15710
|
+
fs18.utimesSync(copy, stat.atime, stat.mtime);
|
|
15566
15711
|
} catch (err) {
|
|
15567
15712
|
const missing = err.code === "ENOENT";
|
|
15568
15713
|
appendLog(
|
|
@@ -15574,7 +15719,7 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
15574
15719
|
try {
|
|
15575
15720
|
return run(copy ? privateIndexEnv(copy) : null);
|
|
15576
15721
|
} finally {
|
|
15577
|
-
if (tmpDir)
|
|
15722
|
+
if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
15578
15723
|
}
|
|
15579
15724
|
}
|
|
15580
15725
|
function privateIndexEnv(indexPath) {
|
|
@@ -15904,7 +16049,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
15904
16049
|
options.omittedFiles?.push(...omittedFiles);
|
|
15905
16050
|
return timeCaptureStage(repoRoot, "tracked-tree-filter", () => {
|
|
15906
16051
|
if (omittedFiles.length === 0) return treeSha;
|
|
15907
|
-
const tmpIndex =
|
|
16052
|
+
const tmpIndex = path17.join(
|
|
15908
16053
|
os7.tmpdir(),
|
|
15909
16054
|
`hillclimb-filter-${Date.now()}-${process.pid}`
|
|
15910
16055
|
);
|
|
@@ -15919,7 +16064,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
15919
16064
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
15920
16065
|
} finally {
|
|
15921
16066
|
try {
|
|
15922
|
-
|
|
16067
|
+
fs18.unlinkSync(tmpIndex);
|
|
15923
16068
|
} catch {
|
|
15924
16069
|
}
|
|
15925
16070
|
}
|
|
@@ -15944,8 +16089,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
15944
16089
|
if (!relPath) continue;
|
|
15945
16090
|
candidates++;
|
|
15946
16091
|
try {
|
|
15947
|
-
const absPath =
|
|
15948
|
-
const stat =
|
|
16092
|
+
const absPath = path17.join(repoRoot, relPath);
|
|
16093
|
+
const stat = fs18.lstatSync(absPath);
|
|
15949
16094
|
const reason = classifyOmission(
|
|
15950
16095
|
relPath,
|
|
15951
16096
|
stat.size,
|
|
@@ -15976,11 +16121,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
15976
16121
|
`git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
|
|
15977
16122
|
);
|
|
15978
16123
|
if (kept.length === 0) return null;
|
|
15979
|
-
const tmpDir =
|
|
16124
|
+
const tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-untracked-"));
|
|
15980
16125
|
const env = {
|
|
15981
16126
|
...process.env,
|
|
15982
16127
|
LC_ALL: "C",
|
|
15983
|
-
GIT_INDEX_FILE:
|
|
16128
|
+
GIT_INDEX_FILE: path17.join(tmpDir, "index")
|
|
15984
16129
|
};
|
|
15985
16130
|
try {
|
|
15986
16131
|
timeCaptureStage(
|
|
@@ -16008,7 +16153,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
16008
16153
|
return buildUntrackedTree(repoRoot, options, attempt + 1);
|
|
16009
16154
|
} finally {
|
|
16010
16155
|
try {
|
|
16011
|
-
|
|
16156
|
+
fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
16012
16157
|
} catch {
|
|
16013
16158
|
}
|
|
16014
16159
|
}
|
|
@@ -16054,10 +16199,10 @@ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
|
|
|
16054
16199
|
`git-traces: tree assembly reused (repo=${repoRoot})`
|
|
16055
16200
|
);
|
|
16056
16201
|
return previous.snapshotTreeSha;
|
|
16057
|
-
} catch {
|
|
16202
|
+
} catch (err) {
|
|
16058
16203
|
appendLog(
|
|
16059
16204
|
"info",
|
|
16060
|
-
`git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot})`
|
|
16205
|
+
`git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot}): ${err instanceof Error ? err.message : String(err)}`
|
|
16061
16206
|
);
|
|
16062
16207
|
}
|
|
16063
16208
|
}
|
|
@@ -16079,7 +16224,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
16079
16224
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
|
|
16080
16225
|
return filteredTrackedTree;
|
|
16081
16226
|
if (filteredTrackedTree === EMPTY_TREE_SHA) return untrackedTree;
|
|
16082
|
-
const tmpIndex =
|
|
16227
|
+
const tmpIndex = path17.join(
|
|
16083
16228
|
os7.tmpdir(),
|
|
16084
16229
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
16085
16230
|
);
|
|
@@ -16108,7 +16253,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
16108
16253
|
);
|
|
16109
16254
|
} finally {
|
|
16110
16255
|
try {
|
|
16111
|
-
|
|
16256
|
+
fs18.unlinkSync(tmpIndex);
|
|
16112
16257
|
} catch {
|
|
16113
16258
|
}
|
|
16114
16259
|
}
|
|
@@ -16123,10 +16268,10 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16123
16268
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
16124
16269
|
let tmpDir;
|
|
16125
16270
|
try {
|
|
16126
|
-
tmpDir =
|
|
16127
|
-
|
|
16271
|
+
tmpDir = fs18.mkdtempSync(
|
|
16272
|
+
path17.join(os7.tmpdir(), `hillclimb-bundle-${process.pid}-`)
|
|
16128
16273
|
);
|
|
16129
|
-
const tmpFile =
|
|
16274
|
+
const tmpFile = path17.join(tmpDir, "baseline.bundle");
|
|
16130
16275
|
timeCaptureStage(
|
|
16131
16276
|
repoRoot,
|
|
16132
16277
|
"bundle-create",
|
|
@@ -16134,7 +16279,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16134
16279
|
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS)
|
|
16135
16280
|
})
|
|
16136
16281
|
);
|
|
16137
|
-
const bundle =
|
|
16282
|
+
const bundle = fs18.readFileSync(tmpFile);
|
|
16138
16283
|
appendLog(
|
|
16139
16284
|
"info",
|
|
16140
16285
|
`git-traces: bundle inventory (repo=${repoRoot}, bytes=${bundle.length})`
|
|
@@ -16142,7 +16287,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16142
16287
|
return bundle;
|
|
16143
16288
|
} finally {
|
|
16144
16289
|
try {
|
|
16145
|
-
if (tmpDir)
|
|
16290
|
+
if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
16146
16291
|
} catch {
|
|
16147
16292
|
}
|
|
16148
16293
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -16151,6 +16296,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16151
16296
|
var MAX_PATCH_GZ_BYTES = 500 * 1024 * 1024;
|
|
16152
16297
|
function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
16153
16298
|
return new Promise((resolve, reject) => {
|
|
16299
|
+
const started = Date.now();
|
|
16154
16300
|
const child = spawn2("git", args, {
|
|
16155
16301
|
cwd: repoRoot,
|
|
16156
16302
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -16179,7 +16325,18 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
16179
16325
|
settled = true;
|
|
16180
16326
|
clearTimeout(timer);
|
|
16181
16327
|
if (timedOut) {
|
|
16182
|
-
|
|
16328
|
+
const wrapped = createGitError(
|
|
16329
|
+
args,
|
|
16330
|
+
{
|
|
16331
|
+
code: "ETIMEDOUT",
|
|
16332
|
+
signal: exitSignal,
|
|
16333
|
+
stderr: Buffer.concat(stderrChunks)
|
|
16334
|
+
},
|
|
16335
|
+
GIT_COMMAND_TIMEOUT_MS,
|
|
16336
|
+
Date.now() - started
|
|
16337
|
+
);
|
|
16338
|
+
probeObjectStore(repoRoot, "timeout", wrapped.gitStderr);
|
|
16339
|
+
reject(wrapped);
|
|
16183
16340
|
} else if (overflowed) {
|
|
16184
16341
|
reject(
|
|
16185
16342
|
createGitError(args, { code: "ENOBUFS", maxBufferBytes: maxGzBytes })
|
|
@@ -16441,9 +16598,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
16441
16598
|
oldPath
|
|
16442
16599
|
});
|
|
16443
16600
|
} else {
|
|
16444
|
-
const
|
|
16445
|
-
indexByPath.set(
|
|
16446
|
-
files.push({ path:
|
|
16601
|
+
const path28 = parts[parts.length - 1];
|
|
16602
|
+
indexByPath.set(path28, files.length);
|
|
16603
|
+
files.push({ path: path28, status, additions: 0, deletions: 0 });
|
|
16447
16604
|
}
|
|
16448
16605
|
}
|
|
16449
16606
|
for (const line of numstat.split("\n")) {
|
|
@@ -16527,11 +16684,11 @@ function countScopedTurnTreeRefs(repoRoot, sessionId, epochPrefix3) {
|
|
|
16527
16684
|
|
|
16528
16685
|
// src/git-traces/session-state.ts
|
|
16529
16686
|
import crypto6 from "crypto";
|
|
16530
|
-
import
|
|
16687
|
+
import fs19 from "fs";
|
|
16531
16688
|
import os8 from "os";
|
|
16532
|
-
import
|
|
16689
|
+
import path18 from "path";
|
|
16533
16690
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
16534
|
-
var DEFAULT_STATE_DIR2 =
|
|
16691
|
+
var DEFAULT_STATE_DIR2 = path18.join(os8.homedir(), ".hillclimb", "git-traces");
|
|
16535
16692
|
var LOCK_RETRIES2 = 120;
|
|
16536
16693
|
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
16537
16694
|
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS2 = 10 * 60 * 1e3;
|
|
@@ -16556,16 +16713,16 @@ function stateDir3() {
|
|
|
16556
16713
|
}
|
|
16557
16714
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
16558
16715
|
const hash = crypto6.createHash("sha256").update(
|
|
16559
|
-
sessionId ? `${
|
|
16716
|
+
sessionId ? `${path18.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path18.resolve(repoRoot)}\0${tool}`
|
|
16560
16717
|
).digest("hex").slice(0, 16);
|
|
16561
|
-
return
|
|
16718
|
+
return path18.join(stateDir3(), `${hash}.json`);
|
|
16562
16719
|
}
|
|
16563
16720
|
function lockFileForRepo(repoRoot, tool, sessionId) {
|
|
16564
16721
|
return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
|
|
16565
16722
|
}
|
|
16566
16723
|
async function readStateFile(file) {
|
|
16567
16724
|
try {
|
|
16568
|
-
const raw = await
|
|
16725
|
+
const raw = await fs19.promises.readFile(file, "utf-8");
|
|
16569
16726
|
const parsed = JSON.parse(raw);
|
|
16570
16727
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
|
|
16571
16728
|
return null;
|
|
@@ -16579,7 +16736,7 @@ async function readStoredStateFile(file) {
|
|
|
16579
16736
|
const state = await readStateFile(file);
|
|
16580
16737
|
if (!state) return null;
|
|
16581
16738
|
try {
|
|
16582
|
-
return { state, mtimeMs: (await
|
|
16739
|
+
return { state, mtimeMs: (await fs19.promises.stat(file)).mtimeMs };
|
|
16583
16740
|
} catch {
|
|
16584
16741
|
return null;
|
|
16585
16742
|
}
|
|
@@ -16587,26 +16744,26 @@ async function readStoredStateFile(file) {
|
|
|
16587
16744
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
16588
16745
|
let entries;
|
|
16589
16746
|
try {
|
|
16590
|
-
entries = await
|
|
16747
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16591
16748
|
} catch {
|
|
16592
16749
|
return [];
|
|
16593
16750
|
}
|
|
16594
16751
|
const states = [];
|
|
16595
16752
|
for (const entry of entries) {
|
|
16596
16753
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
16597
|
-
const file =
|
|
16754
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16598
16755
|
const state = await readStateFile(file);
|
|
16599
16756
|
if (!state) continue;
|
|
16600
16757
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
16601
16758
|
continue;
|
|
16602
16759
|
}
|
|
16603
|
-
if (
|
|
16604
|
-
if (
|
|
16760
|
+
if (path18.resolve(state.repoRoot) !== path18.resolve(repoRoot)) continue;
|
|
16761
|
+
if (path18.resolve(file) !== path18.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
16605
16762
|
continue;
|
|
16606
16763
|
}
|
|
16607
16764
|
let mtimeMs = 0;
|
|
16608
16765
|
try {
|
|
16609
|
-
mtimeMs = (await
|
|
16766
|
+
mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
|
|
16610
16767
|
} catch {
|
|
16611
16768
|
continue;
|
|
16612
16769
|
}
|
|
@@ -16617,26 +16774,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
16617
16774
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
16618
16775
|
let entries;
|
|
16619
16776
|
try {
|
|
16620
|
-
entries = await
|
|
16777
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16621
16778
|
} catch {
|
|
16622
16779
|
return [];
|
|
16623
16780
|
}
|
|
16624
16781
|
const states = [];
|
|
16625
16782
|
for (const entry of entries) {
|
|
16626
16783
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
16627
|
-
const file =
|
|
16784
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16628
16785
|
const state = await readStateFile(file);
|
|
16629
16786
|
if (!state) continue;
|
|
16630
16787
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
16631
16788
|
continue;
|
|
16632
16789
|
}
|
|
16633
16790
|
if (state.sessionId !== sessionId) continue;
|
|
16634
|
-
if (
|
|
16791
|
+
if (path18.resolve(file) !== path18.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
16635
16792
|
continue;
|
|
16636
16793
|
}
|
|
16637
16794
|
let mtimeMs = 0;
|
|
16638
16795
|
try {
|
|
16639
|
-
mtimeMs = (await
|
|
16796
|
+
mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
|
|
16640
16797
|
} catch {
|
|
16641
16798
|
continue;
|
|
16642
16799
|
}
|
|
@@ -16685,17 +16842,17 @@ async function writeLegacySessionState(state, tool) {
|
|
|
16685
16842
|
);
|
|
16686
16843
|
}
|
|
16687
16844
|
async function writeStateFile(file, state) {
|
|
16688
|
-
await
|
|
16845
|
+
await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
16689
16846
|
const tmp = `${file}.tmp`;
|
|
16690
|
-
await
|
|
16847
|
+
await fs19.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
16691
16848
|
mode: 384
|
|
16692
16849
|
});
|
|
16693
|
-
await
|
|
16850
|
+
await fs19.promises.rename(tmp, file);
|
|
16694
16851
|
}
|
|
16695
16852
|
async function touchSessionState(repoRoot, tool, sessionId) {
|
|
16696
16853
|
const now = /* @__PURE__ */ new Date();
|
|
16697
16854
|
try {
|
|
16698
|
-
await
|
|
16855
|
+
await fs19.promises.utimes(
|
|
16699
16856
|
stateFileForRepo(repoRoot, tool, sessionId),
|
|
16700
16857
|
now,
|
|
16701
16858
|
now
|
|
@@ -16705,7 +16862,7 @@ async function touchSessionState(repoRoot, tool, sessionId) {
|
|
|
16705
16862
|
}
|
|
16706
16863
|
async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
16707
16864
|
try {
|
|
16708
|
-
const stat = await
|
|
16865
|
+
const stat = await fs19.promises.stat(
|
|
16709
16866
|
stateFileForRepo(repoRoot, tool, sessionId)
|
|
16710
16867
|
);
|
|
16711
16868
|
return stat.mtimeMs;
|
|
@@ -16715,7 +16872,7 @@ async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
|
16715
16872
|
}
|
|
16716
16873
|
async function deleteStateFile(file) {
|
|
16717
16874
|
try {
|
|
16718
|
-
await
|
|
16875
|
+
await fs19.promises.unlink(file);
|
|
16719
16876
|
} catch {
|
|
16720
16877
|
}
|
|
16721
16878
|
}
|
|
@@ -16754,7 +16911,7 @@ async function acquireLock3(repoRoot, tool, sessionId, retries, delayMs, options
|
|
|
16754
16911
|
}
|
|
16755
16912
|
async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, delayMs, options = {}) {
|
|
16756
16913
|
const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
|
|
16757
|
-
await
|
|
16914
|
+
await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
16758
16915
|
const bounded = retries !== void 0 || delayMs !== void 0;
|
|
16759
16916
|
const attempts = retries ?? LOCK_RETRIES2;
|
|
16760
16917
|
const delay = delayMs ?? LOCK_RETRY_DELAY_MS2;
|
|
@@ -16764,9 +16921,9 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16764
16921
|
let extensionLogged = false;
|
|
16765
16922
|
for (let i = 0; ; i++) {
|
|
16766
16923
|
try {
|
|
16767
|
-
const fd = await
|
|
16924
|
+
const fd = await fs19.promises.open(
|
|
16768
16925
|
lockPath,
|
|
16769
|
-
|
|
16926
|
+
fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
|
|
16770
16927
|
);
|
|
16771
16928
|
await fd.write(String(process.pid));
|
|
16772
16929
|
await fd.close();
|
|
@@ -16775,7 +16932,7 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16775
16932
|
if (err.code !== "EEXIST") throw err;
|
|
16776
16933
|
let regularLockFile = false;
|
|
16777
16934
|
try {
|
|
16778
|
-
regularLockFile = (await
|
|
16935
|
+
regularLockFile = (await fs19.promises.lstat(lockPath)).isFile();
|
|
16779
16936
|
} catch (statErr) {
|
|
16780
16937
|
if (statErr.code === "ENOENT") {
|
|
16781
16938
|
i--;
|
|
@@ -16837,21 +16994,21 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16837
16994
|
}
|
|
16838
16995
|
async function releaseLock3(repoRoot, tool, sessionId) {
|
|
16839
16996
|
try {
|
|
16840
|
-
await
|
|
16997
|
+
await fs19.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
|
|
16841
16998
|
} catch {
|
|
16842
16999
|
}
|
|
16843
17000
|
}
|
|
16844
17001
|
async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now()) {
|
|
16845
17002
|
let entries;
|
|
16846
17003
|
try {
|
|
16847
|
-
entries = await
|
|
17004
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16848
17005
|
} catch {
|
|
16849
17006
|
return 0;
|
|
16850
17007
|
}
|
|
16851
17008
|
let removed = 0;
|
|
16852
17009
|
for (const entry of entries) {
|
|
16853
17010
|
if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
|
|
16854
|
-
const file =
|
|
17011
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16855
17012
|
if (await reapLockIfStale(file, {
|
|
16856
17013
|
maxAgeMs: ttlMs,
|
|
16857
17014
|
preserveLiveOwner: true,
|
|
@@ -16906,12 +17063,12 @@ function withPendingCapture(record, work) {
|
|
|
16906
17063
|
return activeCapture.run({ record, save: () => saveCapture(record) }, work);
|
|
16907
17064
|
}
|
|
16908
17065
|
function queueDirectory(repoRoot, tool, sessionId) {
|
|
16909
|
-
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
16910
|
-
const key = crypto7.createHash("sha256").update(`${
|
|
16911
|
-
return
|
|
17066
|
+
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces");
|
|
17067
|
+
const key = crypto7.createHash("sha256").update(`${path19.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
|
|
17068
|
+
return path19.join(stateDir4, "pending-v1", key);
|
|
16912
17069
|
}
|
|
16913
17070
|
function captureDirectory(record) {
|
|
16914
|
-
return
|
|
17071
|
+
return path19.join(
|
|
16915
17072
|
queueDirectory(record.repoRoot, record.tool, record.sessionId),
|
|
16916
17073
|
record.id
|
|
16917
17074
|
);
|
|
@@ -16919,20 +17076,20 @@ function captureDirectory(record) {
|
|
|
16919
17076
|
function writeDurable(file, body) {
|
|
16920
17077
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
16921
17078
|
try {
|
|
16922
|
-
const descriptor =
|
|
17079
|
+
const descriptor = fs20.openSync(temporary, "w", 384);
|
|
16923
17080
|
try {
|
|
16924
|
-
|
|
16925
|
-
|
|
17081
|
+
fs20.writeFileSync(descriptor, body);
|
|
17082
|
+
fs20.fsyncSync(descriptor);
|
|
16926
17083
|
} finally {
|
|
16927
|
-
|
|
17084
|
+
fs20.closeSync(descriptor);
|
|
16928
17085
|
}
|
|
16929
|
-
|
|
17086
|
+
fs20.renameSync(temporary, file);
|
|
16930
17087
|
try {
|
|
16931
|
-
const directory =
|
|
17088
|
+
const directory = fs20.openSync(path19.dirname(file), "r");
|
|
16932
17089
|
try {
|
|
16933
|
-
|
|
17090
|
+
fs20.fsyncSync(directory);
|
|
16934
17091
|
} finally {
|
|
16935
|
-
|
|
17092
|
+
fs20.closeSync(directory);
|
|
16936
17093
|
}
|
|
16937
17094
|
} catch (err) {
|
|
16938
17095
|
if (!["EINVAL", "ENOTSUP", "EBADF", "EPERM", "EISDIR"].includes(
|
|
@@ -16941,7 +17098,7 @@ function writeDurable(file, body) {
|
|
|
16941
17098
|
throw err;
|
|
16942
17099
|
}
|
|
16943
17100
|
} finally {
|
|
16944
|
-
if (
|
|
17101
|
+
if (fs20.existsSync(temporary)) fs20.unlinkSync(temporary);
|
|
16945
17102
|
}
|
|
16946
17103
|
}
|
|
16947
17104
|
function saveCapture(record) {
|
|
@@ -16952,7 +17109,7 @@ function saveCapture(record) {
|
|
|
16952
17109
|
let status = "failed";
|
|
16953
17110
|
try {
|
|
16954
17111
|
writeDurable(
|
|
16955
|
-
|
|
17112
|
+
path19.join(captureDirectory(record), "capture.json"),
|
|
16956
17113
|
JSON.stringify(record)
|
|
16957
17114
|
);
|
|
16958
17115
|
status = "ok";
|
|
@@ -16964,7 +17121,7 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
16964
17121
|
const directory = queueDirectory(repoRoot, tool, sessionId);
|
|
16965
17122
|
let entries;
|
|
16966
17123
|
try {
|
|
16967
|
-
entries =
|
|
17124
|
+
entries = fs20.readdirSync(directory, { withFileTypes: true });
|
|
16968
17125
|
} catch (err) {
|
|
16969
17126
|
if (err.code === "ENOENT") return [];
|
|
16970
17127
|
throw err;
|
|
@@ -16972,10 +17129,10 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
16972
17129
|
const records = [];
|
|
16973
17130
|
for (const entry of entries) {
|
|
16974
17131
|
if (!entry.isDirectory() || !/^[0-9a-f-]{36}$/.test(entry.name)) continue;
|
|
16975
|
-
const file =
|
|
16976
|
-
if (!
|
|
17132
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17133
|
+
if (!fs20.existsSync(file)) continue;
|
|
16977
17134
|
const record = JSON.parse(
|
|
16978
|
-
|
|
17135
|
+
fs20.readFileSync(file, "utf-8")
|
|
16979
17136
|
);
|
|
16980
17137
|
if (record.version !== 1 || record.id !== entry.name || record.repoRoot !== repoRoot || record.tool !== tool || record.sessionId !== sessionId) {
|
|
16981
17138
|
throw new Error(`Invalid pending Git capture: ${file}`);
|
|
@@ -16988,13 +17145,13 @@ function hasPendingCaptures(repoRoot, tool, sessionId) {
|
|
|
16988
17145
|
return listPendingCaptures2(repoRoot, tool, sessionId).length > 0;
|
|
16989
17146
|
}
|
|
16990
17147
|
function listPendingQueues() {
|
|
16991
|
-
const root =
|
|
16992
|
-
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
17148
|
+
const root = path19.join(
|
|
17149
|
+
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces"),
|
|
16993
17150
|
"pending-v1"
|
|
16994
17151
|
);
|
|
16995
17152
|
let queueDirs;
|
|
16996
17153
|
try {
|
|
16997
|
-
queueDirs =
|
|
17154
|
+
queueDirs = fs20.readdirSync(root, { withFileTypes: true });
|
|
16998
17155
|
} catch (err) {
|
|
16999
17156
|
if (err.code === "ENOENT") return [];
|
|
17000
17157
|
throw err;
|
|
@@ -17002,15 +17159,15 @@ function listPendingQueues() {
|
|
|
17002
17159
|
const queues = [];
|
|
17003
17160
|
for (const queueDir of queueDirs) {
|
|
17004
17161
|
if (!queueDir.isDirectory()) continue;
|
|
17005
|
-
const directory =
|
|
17162
|
+
const directory = path19.join(root, queueDir.name);
|
|
17006
17163
|
let identity = null;
|
|
17007
17164
|
try {
|
|
17008
|
-
for (const entry of
|
|
17165
|
+
for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
|
|
17009
17166
|
if (!entry.isDirectory()) continue;
|
|
17010
|
-
const file =
|
|
17011
|
-
if (!
|
|
17167
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17168
|
+
if (!fs20.existsSync(file)) continue;
|
|
17012
17169
|
identity = JSON.parse(
|
|
17013
|
-
|
|
17170
|
+
fs20.readFileSync(file, "utf-8")
|
|
17014
17171
|
);
|
|
17015
17172
|
break;
|
|
17016
17173
|
}
|
|
@@ -17043,15 +17200,15 @@ function noteReplayFailure(record, error, now = Date.now()) {
|
|
|
17043
17200
|
}
|
|
17044
17201
|
function queueDiskBytes(directory) {
|
|
17045
17202
|
let diskBytes = 0;
|
|
17046
|
-
if (
|
|
17047
|
-
for (const entry of
|
|
17203
|
+
if (fs20.existsSync(directory)) {
|
|
17204
|
+
for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
|
|
17048
17205
|
if (!entry.isDirectory()) continue;
|
|
17049
|
-
for (const file of
|
|
17206
|
+
for (const file of fs20.readdirSync(path19.join(directory, entry.name), {
|
|
17050
17207
|
withFileTypes: true
|
|
17051
17208
|
})) {
|
|
17052
17209
|
if (file.isFile())
|
|
17053
|
-
diskBytes +=
|
|
17054
|
-
|
|
17210
|
+
diskBytes += fs20.statSync(
|
|
17211
|
+
path19.join(directory, entry.name, file.name)
|
|
17055
17212
|
).size;
|
|
17056
17213
|
}
|
|
17057
17214
|
}
|
|
@@ -17067,7 +17224,7 @@ function pendingBytes(records) {
|
|
|
17067
17224
|
return queueDiskBytes(
|
|
17068
17225
|
queueDirectory(first.repoRoot, first.tool, first.sessionId)
|
|
17069
17226
|
) + records.reduce(
|
|
17070
|
-
(sum, record) => sum + chargedBytes(record) + (
|
|
17227
|
+
(sum, record) => sum + chargedBytes(record) + (fs20.existsSync(path19.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
|
|
17071
17228
|
0
|
|
17072
17229
|
);
|
|
17073
17230
|
}
|
|
@@ -17160,7 +17317,7 @@ function storePendingCapture(params) {
|
|
|
17160
17317
|
);
|
|
17161
17318
|
}
|
|
17162
17319
|
const directory = captureDirectory(record);
|
|
17163
|
-
|
|
17320
|
+
fs20.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
17164
17321
|
const refRoot = `refs/hillclimb/pending-v1/${record.id}`;
|
|
17165
17322
|
let published = false;
|
|
17166
17323
|
try {
|
|
@@ -17203,7 +17360,7 @@ function storePendingCapture(params) {
|
|
|
17203
17360
|
return record;
|
|
17204
17361
|
} finally {
|
|
17205
17362
|
if (!published) {
|
|
17206
|
-
|
|
17363
|
+
fs20.rmSync(directory, { recursive: true, force: true });
|
|
17207
17364
|
for (const label of [
|
|
17208
17365
|
"snapshot",
|
|
17209
17366
|
"tree",
|
|
@@ -17234,7 +17391,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17234
17391
|
let status = "failed";
|
|
17235
17392
|
try {
|
|
17236
17393
|
if (saved) {
|
|
17237
|
-
const bytes =
|
|
17394
|
+
const bytes = fs20.readFileSync(path19.join(directory, saved.file));
|
|
17238
17395
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256) {
|
|
17239
17396
|
throw new Error(
|
|
17240
17397
|
`Pending Git artifact is corrupt: ${saved.file}; capture retained`
|
|
@@ -17254,7 +17411,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17254
17411
|
);
|
|
17255
17412
|
}
|
|
17256
17413
|
const file = `${crypto7.createHash("sha256").update(key).digest("hex")}.artifact`;
|
|
17257
|
-
writeDurable(
|
|
17414
|
+
writeDurable(path19.join(directory, file), buffer);
|
|
17258
17415
|
context.record.uploads[key] = {
|
|
17259
17416
|
file,
|
|
17260
17417
|
bytes: buffer.byteLength,
|
|
@@ -17264,7 +17421,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17264
17421
|
context.save();
|
|
17265
17422
|
} catch (err) {
|
|
17266
17423
|
delete context.record.uploads[key];
|
|
17267
|
-
|
|
17424
|
+
fs20.rmSync(path19.join(directory, file), { force: true });
|
|
17268
17425
|
throw err;
|
|
17269
17426
|
}
|
|
17270
17427
|
status = "ok";
|
|
@@ -17296,8 +17453,8 @@ function readPreparedCaptureArtifact(context, filename, input) {
|
|
|
17296
17453
|
throw new Error(
|
|
17297
17454
|
`Prepared Git artifact input changed: ${filename}; capture retained`
|
|
17298
17455
|
);
|
|
17299
|
-
const bytes =
|
|
17300
|
-
|
|
17456
|
+
const bytes = fs20.readFileSync(
|
|
17457
|
+
path19.join(captureDirectory(context.record), saved.file)
|
|
17301
17458
|
);
|
|
17302
17459
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256)
|
|
17303
17460
|
throw new Error(
|
|
@@ -17316,7 +17473,7 @@ function completePendingCapture(record) {
|
|
|
17316
17473
|
function removeCompletedCapture(record) {
|
|
17317
17474
|
if (!record.completed)
|
|
17318
17475
|
throw new Error("Cannot remove an unacknowledged Git capture");
|
|
17319
|
-
|
|
17476
|
+
fs20.rmSync(captureDirectory(record), { recursive: true, force: true });
|
|
17320
17477
|
for (const label of ["snapshot", "tree", "head", "baseline", "previous"]) {
|
|
17321
17478
|
deleteRef(
|
|
17322
17479
|
record.repoRoot,
|
|
@@ -17389,7 +17546,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
17389
17546
|
}
|
|
17390
17547
|
|
|
17391
17548
|
// package.json
|
|
17392
|
-
var version = "0.9.
|
|
17549
|
+
var version = "0.9.5";
|
|
17393
17550
|
|
|
17394
17551
|
// src/version.ts
|
|
17395
17552
|
var CLI_VERSION = version;
|
|
@@ -17413,7 +17570,7 @@ function lineHasAssistant(line) {
|
|
|
17413
17570
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
17414
17571
|
}
|
|
17415
17572
|
async function hasAssistantMessage(transcriptPath) {
|
|
17416
|
-
const stream =
|
|
17573
|
+
const stream = fs21.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
17417
17574
|
let buffer = "";
|
|
17418
17575
|
try {
|
|
17419
17576
|
for await (const chunk of stream) {
|
|
@@ -17500,7 +17657,7 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
17500
17657
|
const workspace = payload.workspace_roots?.[0];
|
|
17501
17658
|
if (!id || !workspace) return void 0;
|
|
17502
17659
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
17503
|
-
return
|
|
17660
|
+
return path20.join(
|
|
17504
17661
|
os10.homedir(),
|
|
17505
17662
|
".cursor",
|
|
17506
17663
|
"projects",
|
|
@@ -17556,14 +17713,14 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
17556
17713
|
const cwd = resolveHookCwd(payload);
|
|
17557
17714
|
if (!parentSessionId || !cwd || !payload.transcript_path) return;
|
|
17558
17715
|
try {
|
|
17559
|
-
const directory =
|
|
17560
|
-
|
|
17716
|
+
const directory = path20.join(
|
|
17717
|
+
path20.dirname(path20.resolve(payload.transcript_path)),
|
|
17561
17718
|
parentSessionId,
|
|
17562
17719
|
"subagents"
|
|
17563
17720
|
);
|
|
17564
17721
|
let names;
|
|
17565
17722
|
try {
|
|
17566
|
-
names = await
|
|
17723
|
+
names = await fs21.promises.readdir(directory);
|
|
17567
17724
|
} catch (err) {
|
|
17568
17725
|
if (err.code === "ENOENT") return;
|
|
17569
17726
|
throw err;
|
|
@@ -17572,12 +17729,12 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
17572
17729
|
for (const name of names) {
|
|
17573
17730
|
const match = /^agent-([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
|
|
17574
17731
|
if (!match) continue;
|
|
17575
|
-
const file =
|
|
17732
|
+
const file = path20.join(directory, name);
|
|
17576
17733
|
try {
|
|
17577
17734
|
files.push({
|
|
17578
17735
|
agentId: match[1],
|
|
17579
17736
|
file,
|
|
17580
|
-
modifiedMs: (await
|
|
17737
|
+
modifiedMs: (await fs21.promises.stat(file)).mtimeMs
|
|
17581
17738
|
});
|
|
17582
17739
|
} catch {
|
|
17583
17740
|
}
|
|
@@ -17743,7 +17900,7 @@ async function sweepCodexForkTranscripts(payload) {
|
|
|
17743
17900
|
const parent = await findProjectForCwd(cwd);
|
|
17744
17901
|
if (!parent) return;
|
|
17745
17902
|
const registeredRoots = Object.keys((await loadProjects()).projects).map(
|
|
17746
|
-
(root) =>
|
|
17903
|
+
(root) => path20.resolve(root)
|
|
17747
17904
|
);
|
|
17748
17905
|
const discovery = await discoverCodexForkThreads({
|
|
17749
17906
|
parentThreadId: parentSessionId,
|
|
@@ -17884,9 +18041,9 @@ async function runUploadForSession(payload, options) {
|
|
|
17884
18041
|
);
|
|
17885
18042
|
return "skipped";
|
|
17886
18043
|
}
|
|
17887
|
-
const transcriptResolved =
|
|
18044
|
+
const transcriptResolved = path20.resolve(transcriptPath);
|
|
17888
18045
|
try {
|
|
17889
|
-
const stat = await
|
|
18046
|
+
const stat = await fs21.promises.stat(transcriptResolved);
|
|
17890
18047
|
if (!stat.isFile()) {
|
|
17891
18048
|
appendLog(
|
|
17892
18049
|
"warn",
|
|
@@ -17942,7 +18099,7 @@ function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
|
|
|
17942
18099
|
};
|
|
17943
18100
|
return {
|
|
17944
18101
|
repoPath: repoRoot,
|
|
17945
|
-
label:
|
|
18102
|
+
label: path20.basename(repoRoot),
|
|
17946
18103
|
files: [sourceFile],
|
|
17947
18104
|
sourceNames: [sourceTool],
|
|
17948
18105
|
lastModified: /* @__PURE__ */ new Date()
|
|
@@ -17953,7 +18110,7 @@ async function buildRedactChain(repoRoot, capturedSecrets = []) {
|
|
|
17953
18110
|
"secret-discovery",
|
|
17954
18111
|
() => discoverEnvFiles(repoRoot)
|
|
17955
18112
|
);
|
|
17956
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
18113
|
+
const envFilePaths = envFileNames.map((n) => path20.join(repoRoot, n));
|
|
17957
18114
|
const secretResult = await measureAgentStage(
|
|
17958
18115
|
"secret-collection",
|
|
17959
18116
|
() => collectSecrets(repoRoot, envFilePaths, [])
|
|
@@ -18607,7 +18764,7 @@ async function uploadSession(args) {
|
|
|
18607
18764
|
try {
|
|
18608
18765
|
raw = await measureAgentStage(
|
|
18609
18766
|
"transcript-read",
|
|
18610
|
-
() => captured ? Promise.resolve(captured.bytes) :
|
|
18767
|
+
() => captured ? Promise.resolve(captured.bytes) : fs21.promises.readFile(transcriptPath)
|
|
18611
18768
|
);
|
|
18612
18769
|
} catch (err) {
|
|
18613
18770
|
if (err.code === "ERR_FS_FILE_TOO_LARGE") {
|
|
@@ -18978,15 +19135,15 @@ import crypto9 from "crypto";
|
|
|
18978
19135
|
|
|
18979
19136
|
// src/git-traces/handlers.ts
|
|
18980
19137
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
18981
|
-
import
|
|
18982
|
-
import
|
|
19138
|
+
import fs22 from "fs";
|
|
19139
|
+
import path21 from "path";
|
|
18983
19140
|
var GIT_TRACES_SLUG = "git-traces";
|
|
18984
19141
|
var MAX_FORK_SWEEP_THREADS2 = 8;
|
|
18985
19142
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
18986
19143
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
18987
19144
|
var REBASELINE_AFTER_DIFF_FAILURES = 2;
|
|
18988
19145
|
async function withRepoCaptureLock(repoRoot, capture) {
|
|
18989
|
-
const canonicalRoot =
|
|
19146
|
+
const canonicalRoot = fs22.realpathSync(repoRoot);
|
|
18990
19147
|
await acquireLock3(canonicalRoot, "snapshot-index-v1", null);
|
|
18991
19148
|
try {
|
|
18992
19149
|
return capture();
|
|
@@ -19044,7 +19201,7 @@ var PendingQueueConflictError = class extends Error {
|
|
|
19044
19201
|
async function loadConfiguredRepos() {
|
|
19045
19202
|
const file = await loadProjects();
|
|
19046
19203
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
19047
|
-
repoRoot:
|
|
19204
|
+
repoRoot: path21.resolve(repoRoot),
|
|
19048
19205
|
config
|
|
19049
19206
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
19050
19207
|
}
|
|
@@ -19062,12 +19219,12 @@ async function configuredReposFor(trigger) {
|
|
|
19062
19219
|
return repos;
|
|
19063
19220
|
}
|
|
19064
19221
|
async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
19065
|
-
const source =
|
|
19066
|
-
const target =
|
|
19067
|
-
if (
|
|
19222
|
+
const source = path21.join(parentRoot, ".codex", "hooks.json");
|
|
19223
|
+
const target = path21.join(worktreeRoot, ".codex", "hooks.json");
|
|
19224
|
+
if (fs22.existsSync(target) || !fs22.existsSync(source)) return;
|
|
19068
19225
|
try {
|
|
19069
|
-
|
|
19070
|
-
|
|
19226
|
+
fs22.mkdirSync(path21.dirname(target), { recursive: true });
|
|
19227
|
+
fs22.copyFileSync(source, target, fs22.constants.COPYFILE_EXCL);
|
|
19071
19228
|
} catch (err) {
|
|
19072
19229
|
appendLog(
|
|
19073
19230
|
"warn",
|
|
@@ -19089,7 +19246,7 @@ async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
|
19089
19246
|
}
|
|
19090
19247
|
}
|
|
19091
19248
|
function repoLabel(repoRoot) {
|
|
19092
|
-
return
|
|
19249
|
+
return path21.basename(repoRoot) || repoRoot;
|
|
19093
19250
|
}
|
|
19094
19251
|
function resolveCwd2(payload) {
|
|
19095
19252
|
return resolveHookCwd(payload);
|
|
@@ -21306,7 +21463,7 @@ async function collectStopTargets(tool, sessionId, repos, repoByRoot) {
|
|
|
21306
21463
|
let needsLineageCheck = false;
|
|
21307
21464
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
21308
21465
|
for (const { state } of storedStates) {
|
|
21309
|
-
const repo = repoByRoot.get(
|
|
21466
|
+
const repo = repoByRoot.get(path21.resolve(state.repoRoot));
|
|
21310
21467
|
if (!repo) {
|
|
21311
21468
|
missingConfig++;
|
|
21312
21469
|
appendLog(
|
|
@@ -21338,7 +21495,7 @@ async function lateInitSkipReason(payload, tool) {
|
|
|
21338
21495
|
const transcriptPath = payload.transcript_path;
|
|
21339
21496
|
if (!transcriptPath) return tool === "codex" ? null : "no-transcript-path";
|
|
21340
21497
|
try {
|
|
21341
|
-
const stat = await
|
|
21498
|
+
const stat = await fs22.promises.stat(path21.resolve(transcriptPath));
|
|
21342
21499
|
return stat.isFile() ? null : "transcript-not-a-file";
|
|
21343
21500
|
} catch {
|
|
21344
21501
|
return "transcript-missing";
|
|
@@ -21547,9 +21704,9 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21547
21704
|
if (sessionId) {
|
|
21548
21705
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
21549
21706
|
for (const { state } of states) {
|
|
21550
|
-
const repoRoot =
|
|
21707
|
+
const repoRoot = path21.resolve(state.repoRoot);
|
|
21551
21708
|
repoRoots.add(repoRoot);
|
|
21552
|
-
const canProcess = repoByRoot.has(repoRoot) || project &&
|
|
21709
|
+
const canProcess = repoByRoot.has(repoRoot) || project && path21.resolve(project.repoRoot) === repoRoot;
|
|
21553
21710
|
if (canProcess && tool === "codex" && state.codexLineageChecked !== true) {
|
|
21554
21711
|
needsLineageCheck = true;
|
|
21555
21712
|
}
|
|
@@ -21586,7 +21743,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21586
21743
|
let rejected = 0;
|
|
21587
21744
|
const finalCaptures = /* @__PURE__ */ new Map();
|
|
21588
21745
|
for (const repoRoot of repoRoots) {
|
|
21589
|
-
const repo = repoByRoot.get(
|
|
21746
|
+
const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
21590
21747
|
if (repo)
|
|
21591
21748
|
finalCaptures.set(
|
|
21592
21749
|
repoRoot,
|
|
@@ -21594,7 +21751,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21594
21751
|
);
|
|
21595
21752
|
}
|
|
21596
21753
|
for (const repoRoot of repoRoots) {
|
|
21597
|
-
const repo = repoByRoot.get(
|
|
21754
|
+
const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
21598
21755
|
if (!repo) {
|
|
21599
21756
|
skipped++;
|
|
21600
21757
|
appendLog(
|
|
@@ -21973,15 +22130,15 @@ ${stack}` : ""}`
|
|
|
21973
22130
|
}
|
|
21974
22131
|
|
|
21975
22132
|
// src/outputs/zip.ts
|
|
21976
|
-
import
|
|
21977
|
-
import
|
|
22133
|
+
import fs24 from "fs";
|
|
22134
|
+
import path23 from "path";
|
|
21978
22135
|
import archiver2 from "archiver";
|
|
21979
22136
|
|
|
21980
22137
|
// src/outputs/downloads.ts
|
|
21981
22138
|
import { execSync as execSync2 } from "child_process";
|
|
21982
|
-
import
|
|
22139
|
+
import fs23 from "fs";
|
|
21983
22140
|
import os11 from "os";
|
|
21984
|
-
import
|
|
22141
|
+
import path22 from "path";
|
|
21985
22142
|
function getDownloadsFolder() {
|
|
21986
22143
|
const home = os11.homedir();
|
|
21987
22144
|
if (process.platform === "linux") {
|
|
@@ -21990,12 +22147,12 @@ function getDownloadsFolder() {
|
|
|
21990
22147
|
encoding: "utf-8",
|
|
21991
22148
|
timeout: 3e3
|
|
21992
22149
|
}).trim();
|
|
21993
|
-
if (xdgDir &&
|
|
22150
|
+
if (xdgDir && fs23.existsSync(xdgDir)) return xdgDir;
|
|
21994
22151
|
} catch {
|
|
21995
22152
|
}
|
|
21996
22153
|
}
|
|
21997
|
-
const downloads =
|
|
21998
|
-
if (
|
|
22154
|
+
const downloads = path22.join(home, "Downloads");
|
|
22155
|
+
if (fs23.existsSync(downloads)) return downloads;
|
|
21999
22156
|
return home;
|
|
22000
22157
|
}
|
|
22001
22158
|
|
|
@@ -22004,11 +22161,11 @@ function sanitizeFilename(name) {
|
|
|
22004
22161
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
22005
22162
|
}
|
|
22006
22163
|
function getUniqueFilename(dir, base, ext) {
|
|
22007
|
-
let candidate =
|
|
22008
|
-
if (!
|
|
22164
|
+
let candidate = path23.join(dir, `${base}${ext}`);
|
|
22165
|
+
if (!fs24.existsSync(candidate)) return candidate;
|
|
22009
22166
|
let i = 1;
|
|
22010
|
-
while (
|
|
22011
|
-
candidate =
|
|
22167
|
+
while (fs24.existsSync(candidate)) {
|
|
22168
|
+
candidate = path23.join(dir, `${base}-${i}${ext}`);
|
|
22012
22169
|
i++;
|
|
22013
22170
|
}
|
|
22014
22171
|
return candidate;
|
|
@@ -22018,13 +22175,13 @@ var ZipOutput = class {
|
|
|
22018
22175
|
label = "Save as .zip to Downloads";
|
|
22019
22176
|
async emit(group, options) {
|
|
22020
22177
|
const downloadsDir = getDownloadsFolder();
|
|
22021
|
-
const repoName = sanitizeFilename(
|
|
22178
|
+
const repoName = sanitizeFilename(path23.basename(group.repoPath));
|
|
22022
22179
|
const timeRange = options.timeRange;
|
|
22023
22180
|
const rangePart = timeRange?.label ?? "all";
|
|
22024
22181
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
22025
22182
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
22026
22183
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
22027
|
-
const output =
|
|
22184
|
+
const output = fs24.createWriteStream(outputPath);
|
|
22028
22185
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
22029
22186
|
const done = new Promise((resolve, reject) => {
|
|
22030
22187
|
output.on("close", resolve);
|
|
@@ -22218,15 +22375,15 @@ async function confirmExport(group, output) {
|
|
|
22218
22375
|
}
|
|
22219
22376
|
|
|
22220
22377
|
// src/sources/claude.ts
|
|
22221
|
-
import
|
|
22378
|
+
import fs25 from "fs";
|
|
22222
22379
|
import os12 from "os";
|
|
22223
|
-
import
|
|
22380
|
+
import path24 from "path";
|
|
22224
22381
|
import readline3 from "readline";
|
|
22225
22382
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
22226
22383
|
async function resolveRepoPath(projectDir) {
|
|
22227
|
-
const indexPath =
|
|
22384
|
+
const indexPath = path24.join(projectDir, "sessions-index.json");
|
|
22228
22385
|
try {
|
|
22229
|
-
const raw = await
|
|
22386
|
+
const raw = await fs25.promises.readFile(indexPath, "utf-8");
|
|
22230
22387
|
const data = JSON.parse(raw);
|
|
22231
22388
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
22232
22389
|
return data.originalPath;
|
|
@@ -22234,12 +22391,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
22234
22391
|
} catch {
|
|
22235
22392
|
}
|
|
22236
22393
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
22237
|
-
const entries = await
|
|
22394
|
+
const entries = await fs25.promises.readdir(projectDir, {
|
|
22238
22395
|
withFileTypes: true
|
|
22239
22396
|
});
|
|
22240
22397
|
for (const entry of entries) {
|
|
22241
22398
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
22242
|
-
const cwd = await extractCwdFromJsonl(
|
|
22399
|
+
const cwd = await extractCwdFromJsonl(path24.join(projectDir, entry.name));
|
|
22243
22400
|
if (cwd) {
|
|
22244
22401
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
22245
22402
|
}
|
|
@@ -22258,7 +22415,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
22258
22415
|
return null;
|
|
22259
22416
|
}
|
|
22260
22417
|
async function extractCwdFromJsonl(filePath) {
|
|
22261
|
-
const stream =
|
|
22418
|
+
const stream = fs25.createReadStream(filePath, { encoding: "utf-8" });
|
|
22262
22419
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22263
22420
|
try {
|
|
22264
22421
|
for await (const line of rl) {
|
|
@@ -22280,12 +22437,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
22280
22437
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
22281
22438
|
let entries;
|
|
22282
22439
|
try {
|
|
22283
|
-
entries = await
|
|
22440
|
+
entries = await fs25.promises.readdir(dir, { withFileTypes: true });
|
|
22284
22441
|
} catch {
|
|
22285
22442
|
return;
|
|
22286
22443
|
}
|
|
22287
22444
|
for (const entry of entries) {
|
|
22288
|
-
const fullPath =
|
|
22445
|
+
const fullPath = path24.join(dir, entry.name);
|
|
22289
22446
|
if (entry.isDirectory()) {
|
|
22290
22447
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
22291
22448
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -22307,19 +22464,19 @@ function fallbackDecode(encodedName) {
|
|
|
22307
22464
|
var ClaudeSource = class {
|
|
22308
22465
|
name = "claude";
|
|
22309
22466
|
async scan() {
|
|
22310
|
-
const baseDir =
|
|
22467
|
+
const baseDir = path24.join(os12.homedir(), ".claude", "projects");
|
|
22311
22468
|
try {
|
|
22312
|
-
await
|
|
22469
|
+
await fs25.promises.access(baseDir);
|
|
22313
22470
|
} catch {
|
|
22314
22471
|
return [];
|
|
22315
22472
|
}
|
|
22316
|
-
const projectDirs = await
|
|
22473
|
+
const projectDirs = await fs25.promises.readdir(baseDir, {
|
|
22317
22474
|
withFileTypes: true
|
|
22318
22475
|
});
|
|
22319
22476
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
22320
22477
|
const resultArrays = await Promise.all(
|
|
22321
22478
|
dirEntries.map(async (dir) => {
|
|
22322
|
-
const projectPath =
|
|
22479
|
+
const projectPath = path24.join(baseDir, dir.name);
|
|
22323
22480
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
22324
22481
|
const files = [];
|
|
22325
22482
|
await collectFiles(
|
|
@@ -22337,12 +22494,12 @@ var ClaudeSource = class {
|
|
|
22337
22494
|
};
|
|
22338
22495
|
|
|
22339
22496
|
// src/sources/codex.ts
|
|
22340
|
-
import
|
|
22497
|
+
import fs26 from "fs";
|
|
22341
22498
|
import os13 from "os";
|
|
22342
|
-
import
|
|
22499
|
+
import path25 from "path";
|
|
22343
22500
|
import readline4 from "readline";
|
|
22344
22501
|
async function parseSessionMeta2(filePath) {
|
|
22345
|
-
const stream =
|
|
22502
|
+
const stream = fs26.createReadStream(filePath, { encoding: "utf-8" });
|
|
22346
22503
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22347
22504
|
try {
|
|
22348
22505
|
for await (const line of rl) {
|
|
@@ -22367,12 +22524,12 @@ async function findJsonlFiles(dir) {
|
|
|
22367
22524
|
async function walk(d) {
|
|
22368
22525
|
let entries;
|
|
22369
22526
|
try {
|
|
22370
|
-
entries = await
|
|
22527
|
+
entries = await fs26.promises.readdir(d, { withFileTypes: true });
|
|
22371
22528
|
} catch {
|
|
22372
22529
|
return;
|
|
22373
22530
|
}
|
|
22374
22531
|
for (const entry of entries) {
|
|
22375
|
-
const full =
|
|
22532
|
+
const full = path25.join(d, entry.name);
|
|
22376
22533
|
if (entry.isDirectory()) {
|
|
22377
22534
|
await walk(full);
|
|
22378
22535
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -22386,11 +22543,11 @@ async function findJsonlFiles(dir) {
|
|
|
22386
22543
|
async function loadHistory(historyPath) {
|
|
22387
22544
|
const map = /* @__PURE__ */ new Map();
|
|
22388
22545
|
try {
|
|
22389
|
-
await
|
|
22546
|
+
await fs26.promises.access(historyPath);
|
|
22390
22547
|
} catch {
|
|
22391
22548
|
return map;
|
|
22392
22549
|
}
|
|
22393
|
-
const stream =
|
|
22550
|
+
const stream = fs26.createReadStream(historyPath, { encoding: "utf-8" });
|
|
22394
22551
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22395
22552
|
try {
|
|
22396
22553
|
for await (const line of rl) {
|
|
@@ -22417,14 +22574,14 @@ async function loadHistory(historyPath) {
|
|
|
22417
22574
|
var CodexSource = class {
|
|
22418
22575
|
name = "codex";
|
|
22419
22576
|
async scan() {
|
|
22420
|
-
const codexDir =
|
|
22421
|
-
const sessionsDir2 =
|
|
22577
|
+
const codexDir = path25.join(os13.homedir(), ".codex");
|
|
22578
|
+
const sessionsDir2 = path25.join(codexDir, "sessions");
|
|
22422
22579
|
try {
|
|
22423
|
-
await
|
|
22580
|
+
await fs26.promises.access(sessionsDir2);
|
|
22424
22581
|
} catch {
|
|
22425
22582
|
return [];
|
|
22426
22583
|
}
|
|
22427
|
-
const historyPath =
|
|
22584
|
+
const historyPath = path25.join(codexDir, "history.jsonl");
|
|
22428
22585
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
22429
22586
|
findJsonlFiles(sessionsDir2),
|
|
22430
22587
|
loadHistory(historyPath)
|
|
@@ -22447,8 +22604,8 @@ var CodexSource = class {
|
|
|
22447
22604
|
});
|
|
22448
22605
|
const historyLines = historyMap.get(meta.sessionId);
|
|
22449
22606
|
if (historyLines) {
|
|
22450
|
-
const sessionDir =
|
|
22451
|
-
const historyAbsPath =
|
|
22607
|
+
const sessionDir = path25.relative(sessionsDir2, path25.dirname(filePath));
|
|
22608
|
+
const historyAbsPath = path25.join(
|
|
22452
22609
|
sessionsDir2,
|
|
22453
22610
|
sessionDir,
|
|
22454
22611
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -22468,18 +22625,18 @@ var CodexSource = class {
|
|
|
22468
22625
|
};
|
|
22469
22626
|
|
|
22470
22627
|
// src/sources/copilotChat.ts
|
|
22471
|
-
import
|
|
22628
|
+
import fs27 from "fs";
|
|
22472
22629
|
import os14 from "os";
|
|
22473
|
-
import
|
|
22630
|
+
import path26 from "path";
|
|
22474
22631
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22475
22632
|
function vsCodeUserDirs() {
|
|
22476
22633
|
const home = os14.homedir();
|
|
22477
22634
|
const dirs = [
|
|
22478
|
-
|
|
22479
|
-
|
|
22635
|
+
path26.join(home, "Library", "Application Support", "Code", "User"),
|
|
22636
|
+
path26.join(home, ".config", "Code", "User")
|
|
22480
22637
|
];
|
|
22481
22638
|
if (process.env.APPDATA) {
|
|
22482
|
-
dirs.push(
|
|
22639
|
+
dirs.push(path26.join(process.env.APPDATA, "Code", "User"));
|
|
22483
22640
|
}
|
|
22484
22641
|
return dirs;
|
|
22485
22642
|
}
|
|
@@ -22494,7 +22651,7 @@ function uriToFsPath(uri) {
|
|
|
22494
22651
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
22495
22652
|
let raw;
|
|
22496
22653
|
try {
|
|
22497
|
-
raw = await
|
|
22654
|
+
raw = await fs27.promises.readFile(workspaceJsonPath, "utf-8");
|
|
22498
22655
|
} catch {
|
|
22499
22656
|
return null;
|
|
22500
22657
|
}
|
|
@@ -22516,10 +22673,10 @@ var CopilotChatSource = class {
|
|
|
22516
22673
|
async scan() {
|
|
22517
22674
|
const results = [];
|
|
22518
22675
|
for (const userDir of vsCodeUserDirs()) {
|
|
22519
|
-
const workspaceStorage =
|
|
22676
|
+
const workspaceStorage = path26.join(userDir, "workspaceStorage");
|
|
22520
22677
|
let hashDirs;
|
|
22521
22678
|
try {
|
|
22522
|
-
hashDirs = await
|
|
22679
|
+
hashDirs = await fs27.promises.readdir(workspaceStorage, {
|
|
22523
22680
|
withFileTypes: true
|
|
22524
22681
|
});
|
|
22525
22682
|
} catch {
|
|
@@ -22527,22 +22684,22 @@ var CopilotChatSource = class {
|
|
|
22527
22684
|
}
|
|
22528
22685
|
for (const hash of hashDirs) {
|
|
22529
22686
|
if (!hash.isDirectory()) continue;
|
|
22530
|
-
const wsRoot =
|
|
22531
|
-
const transcriptsDir =
|
|
22687
|
+
const wsRoot = path26.join(workspaceStorage, hash.name);
|
|
22688
|
+
const transcriptsDir = path26.join(
|
|
22532
22689
|
wsRoot,
|
|
22533
22690
|
"GitHub.copilot-chat",
|
|
22534
22691
|
"transcripts"
|
|
22535
22692
|
);
|
|
22536
22693
|
let transcriptEntries;
|
|
22537
22694
|
try {
|
|
22538
|
-
transcriptEntries = await
|
|
22695
|
+
transcriptEntries = await fs27.promises.readdir(transcriptsDir, {
|
|
22539
22696
|
withFileTypes: true
|
|
22540
22697
|
});
|
|
22541
22698
|
} catch {
|
|
22542
22699
|
continue;
|
|
22543
22700
|
}
|
|
22544
22701
|
const repoPath = await readWorkspaceFolder(
|
|
22545
|
-
|
|
22702
|
+
path26.join(wsRoot, "workspace.json")
|
|
22546
22703
|
);
|
|
22547
22704
|
if (!repoPath) continue;
|
|
22548
22705
|
for (const entry of transcriptEntries) {
|
|
@@ -22550,7 +22707,7 @@ var CopilotChatSource = class {
|
|
|
22550
22707
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
22551
22708
|
results.push({
|
|
22552
22709
|
sourceName: this.name,
|
|
22553
|
-
absolutePath:
|
|
22710
|
+
absolutePath: path26.join(transcriptsDir, entry.name),
|
|
22554
22711
|
repoPath,
|
|
22555
22712
|
metadata: { sessionId }
|
|
22556
22713
|
});
|
|
@@ -22590,7 +22747,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
22590
22747
|
async function filterByTimeRange(group, range) {
|
|
22591
22748
|
const results = await Promise.all(
|
|
22592
22749
|
group.files.map(
|
|
22593
|
-
(f) =>
|
|
22750
|
+
(f) => fs28.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
22594
22751
|
)
|
|
22595
22752
|
);
|
|
22596
22753
|
const filtered = [];
|
|
@@ -22617,10 +22774,10 @@ async function runInteractive() {
|
|
|
22617
22774
|
s.start(`Scanning ${source.name} logs...`);
|
|
22618
22775
|
const allFiles = await source.scan();
|
|
22619
22776
|
const allGroups = await mergeByRepo(allFiles);
|
|
22620
|
-
const repoRoot =
|
|
22777
|
+
const repoRoot = path27.resolve(repo.root);
|
|
22621
22778
|
const matching = allGroups.filter((g) => {
|
|
22622
|
-
const resolved =
|
|
22623
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
22779
|
+
const resolved = path27.resolve(g.repoPath);
|
|
22780
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path27.sep);
|
|
22624
22781
|
});
|
|
22625
22782
|
if (matching.length === 0) {
|
|
22626
22783
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -22651,7 +22808,7 @@ async function runInteractive() {
|
|
|
22651
22808
|
}
|
|
22652
22809
|
}
|
|
22653
22810
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
22654
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
22811
|
+
const envFilePaths = envFileNames.map((n) => path27.join(repoRoot, n));
|
|
22655
22812
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
22656
22813
|
const secretResult = await collectSecrets(
|
|
22657
22814
|
repoRoot,
|