hillclimb 0.9.4 → 0.9.6
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 +774 -255
- 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,276 @@ 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
|
-
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync } from "child_process";
|
|
15187
|
-
import
|
|
15186
|
+
import { execFileSync as execFileSync2, spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
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 { spawnSync } from "child_process";
|
|
15194
|
+
import fs17 from "fs";
|
|
15195
|
+
import path16 from "path";
|
|
15196
|
+
var probedRepos = /* @__PURE__ */ new Set();
|
|
15197
|
+
var SAMPLE_FANOUT_DIRS = 8;
|
|
15198
|
+
var SAMPLE_READS = 20;
|
|
15199
|
+
var SAMPLE_READ_BYTES = 4096;
|
|
15200
|
+
var MAX_NAMED_OBJECTS = 3;
|
|
15201
|
+
var MAX_LOCATE_BLOBS = 2e4;
|
|
15202
|
+
var MAX_STALLED_PATH_CHARS = 120;
|
|
15203
|
+
var OBJECT_SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
15204
|
+
var gitLimits = {
|
|
15205
|
+
versionMs: 2e3,
|
|
15206
|
+
listMs: 1e4,
|
|
15207
|
+
readMs: 15e3,
|
|
15208
|
+
headMs: 5e3
|
|
15209
|
+
};
|
|
15210
|
+
function probeObjectStore(repoRoot, trigger, stderr, args = []) {
|
|
15211
|
+
if (probedRepos.has(repoRoot)) return;
|
|
15212
|
+
probedRepos.add(repoRoot);
|
|
15213
|
+
const started = Date.now();
|
|
15214
|
+
const fields = [`repo=${repoRoot}`, `trigger=${trigger}`];
|
|
15215
|
+
try {
|
|
15216
|
+
const objectsDir = resolveObjectsDir(repoRoot);
|
|
15217
|
+
if (!objectsDir) {
|
|
15218
|
+
fields.push("objectsDir=unresolved");
|
|
15219
|
+
return;
|
|
15220
|
+
}
|
|
15221
|
+
fields.push(`objectsDir=${objectsDir}`);
|
|
15222
|
+
if (typeof fs17.statfsSync === "function") {
|
|
15223
|
+
const stats = fs17.statfsSync(objectsDir);
|
|
15224
|
+
fields.push(
|
|
15225
|
+
`fsType=${stats.type}`,
|
|
15226
|
+
`freeBytes=${stats.bavail * stats.bsize}`
|
|
15227
|
+
);
|
|
15228
|
+
}
|
|
15229
|
+
const { estimate, sample } = sampleLooseObjects(objectsDir);
|
|
15230
|
+
fields.push(`looseEstimate=${estimate}`);
|
|
15231
|
+
const packDir = path16.join(objectsDir, "pack");
|
|
15232
|
+
const packFiles = fs17.existsSync(packDir) ? fs17.readdirSync(packDir) : [];
|
|
15233
|
+
fields.push(
|
|
15234
|
+
`packs=${packFiles.filter((name) => name.endsWith(".pack")).length}`,
|
|
15235
|
+
`promisorPacks=${packFiles.filter((name) => name.endsWith(".promisor")).length}`,
|
|
15236
|
+
`alternates=${fs17.existsSync(path16.join(objectsDir, "info", "alternates")) ? "yes" : "no"}`
|
|
15237
|
+
);
|
|
15238
|
+
fields.push(...timeSampleReads(sample));
|
|
15239
|
+
const named = namedObjects(stderr);
|
|
15240
|
+
for (const sha of named) {
|
|
15241
|
+
const loose = path16.join(objectsDir, sha.slice(0, 2), sha.slice(2));
|
|
15242
|
+
const stats = fs17.statSync(loose, { throwIfNoEntry: false });
|
|
15243
|
+
fields.push(
|
|
15244
|
+
stats ? `named=${sha.slice(0, 8)}(size=${stats.size},blocks=${stats.blocks})` : `named=${sha.slice(0, 8)}(no loose file)`
|
|
15245
|
+
);
|
|
15246
|
+
}
|
|
15247
|
+
fields.push(...gitVersion(repoRoot));
|
|
15248
|
+
locateGitRead(fields, repoRoot, objectsDir, failedTreeish(args), [
|
|
15249
|
+
...sample.map(sampleSha).filter((sha) => !!sha),
|
|
15250
|
+
...named
|
|
15251
|
+
]);
|
|
15252
|
+
} catch (err) {
|
|
15253
|
+
fields.push(
|
|
15254
|
+
`probeError=${err instanceof Error ? err.message : String(err)}`
|
|
15255
|
+
);
|
|
15256
|
+
} finally {
|
|
15257
|
+
fields.push(`probeMs=${Date.now() - started}`);
|
|
15258
|
+
appendLog("info", `git-traces: object store probe (${fields.join(", ")})`);
|
|
15259
|
+
}
|
|
15260
|
+
}
|
|
15261
|
+
function resolveObjectsDir(repoRoot) {
|
|
15262
|
+
let gitDir = path16.join(repoRoot, ".git");
|
|
15263
|
+
const stats = fs17.statSync(gitDir, { throwIfNoEntry: false });
|
|
15264
|
+
if (!stats) return null;
|
|
15265
|
+
if (stats.isFile()) {
|
|
15266
|
+
const match = /^gitdir:\s*(.+)$/m.exec(fs17.readFileSync(gitDir, "utf-8"));
|
|
15267
|
+
if (!match) return null;
|
|
15268
|
+
gitDir = path16.resolve(repoRoot, match[1].trim());
|
|
15269
|
+
}
|
|
15270
|
+
const commonDir = path16.join(gitDir, "commondir");
|
|
15271
|
+
if (fs17.existsSync(commonDir)) {
|
|
15272
|
+
gitDir = path16.resolve(gitDir, fs17.readFileSync(commonDir, "utf-8").trim());
|
|
15273
|
+
}
|
|
15274
|
+
return path16.join(gitDir, "objects");
|
|
15275
|
+
}
|
|
15276
|
+
function sampleLooseObjects(objectsDir) {
|
|
15277
|
+
const fanout = fs17.readdirSync(objectsDir).filter((name) => /^[0-9a-f]{2}$/.test(name)).sort().slice(0, SAMPLE_FANOUT_DIRS);
|
|
15278
|
+
let counted = 0;
|
|
15279
|
+
const sample = [];
|
|
15280
|
+
for (const dir of fanout) {
|
|
15281
|
+
const files = fs17.readdirSync(path16.join(objectsDir, dir));
|
|
15282
|
+
counted += files.length;
|
|
15283
|
+
for (const file of files) {
|
|
15284
|
+
if (sample.length >= SAMPLE_READS) break;
|
|
15285
|
+
sample.push(path16.join(objectsDir, dir, file));
|
|
15286
|
+
}
|
|
15287
|
+
}
|
|
15288
|
+
return {
|
|
15289
|
+
estimate: fanout.length ? Math.round(counted / fanout.length * 256) : 0,
|
|
15290
|
+
sample
|
|
15291
|
+
};
|
|
15292
|
+
}
|
|
15293
|
+
function timeSampleReads(sample) {
|
|
15294
|
+
let dataless = 0;
|
|
15295
|
+
let readErrors = 0;
|
|
15296
|
+
let firstReadError;
|
|
15297
|
+
let totalMs = 0;
|
|
15298
|
+
let maxMs = 0;
|
|
15299
|
+
const buffer = Buffer.alloc(SAMPLE_READ_BYTES);
|
|
15300
|
+
for (const file of sample) {
|
|
15301
|
+
const readStarted = performance.now();
|
|
15302
|
+
try {
|
|
15303
|
+
const stats = fs17.statSync(file);
|
|
15304
|
+
if (!stats.isFile()) continue;
|
|
15305
|
+
if (stats.size > 0 && stats.blocks === 0) dataless++;
|
|
15306
|
+
const fd = fs17.openSync(file, "r");
|
|
15307
|
+
try {
|
|
15308
|
+
fs17.readSync(fd, buffer, 0, SAMPLE_READ_BYTES, 0);
|
|
15309
|
+
} finally {
|
|
15310
|
+
fs17.closeSync(fd);
|
|
15311
|
+
}
|
|
15312
|
+
} catch (err) {
|
|
15313
|
+
readErrors++;
|
|
15314
|
+
firstReadError ??= err instanceof Error ? err.message : String(err);
|
|
15315
|
+
}
|
|
15316
|
+
const elapsed = performance.now() - readStarted;
|
|
15317
|
+
totalMs += elapsed;
|
|
15318
|
+
maxMs = Math.max(maxMs, elapsed);
|
|
15319
|
+
}
|
|
15320
|
+
const fields = [
|
|
15321
|
+
`sampled=${sample.length}`,
|
|
15322
|
+
`dataless=${dataless}`,
|
|
15323
|
+
`readMsMax=${maxMs.toFixed(1)}`,
|
|
15324
|
+
`readMsTotal=${totalMs.toFixed(1)}`,
|
|
15325
|
+
`readErrors=${readErrors}`
|
|
15326
|
+
];
|
|
15327
|
+
if (firstReadError) fields.push(`firstReadError=${firstReadError}`);
|
|
15328
|
+
return fields;
|
|
15329
|
+
}
|
|
15330
|
+
function namedObjects(stderr) {
|
|
15331
|
+
const shas = new Set(stderr?.match(/\b[0-9a-f]{40}\b/g) ?? []);
|
|
15332
|
+
return [...shas].slice(0, MAX_NAMED_OBJECTS);
|
|
15333
|
+
}
|
|
15334
|
+
function sampleSha(file) {
|
|
15335
|
+
const sha = path16.basename(path16.dirname(file)) + path16.basename(file);
|
|
15336
|
+
return OBJECT_SHA.test(sha) ? sha : null;
|
|
15337
|
+
}
|
|
15338
|
+
function failedTreeish(args) {
|
|
15339
|
+
for (const arg of args.slice(1).reverse()) {
|
|
15340
|
+
if (OBJECT_SHA.test(arg) || arg.startsWith("refs/")) return arg;
|
|
15341
|
+
}
|
|
15342
|
+
return null;
|
|
15343
|
+
}
|
|
15344
|
+
function probeGit(repoRoot, args, timeout, input) {
|
|
15345
|
+
return spawnSync("git", args, {
|
|
15346
|
+
cwd: repoRoot,
|
|
15347
|
+
input,
|
|
15348
|
+
timeout,
|
|
15349
|
+
killSignal: "SIGKILL",
|
|
15350
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
15351
|
+
windowsHide: true,
|
|
15352
|
+
// Never prompt or lazily fetch from inside a diagnostic.
|
|
15353
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_NO_LAZY_FETCH: "1" }
|
|
15354
|
+
});
|
|
15355
|
+
}
|
|
15356
|
+
function gitVersion(repoRoot) {
|
|
15357
|
+
const started = performance.now();
|
|
15358
|
+
const result = probeGit(repoRoot, ["--version"], gitLimits.versionMs);
|
|
15359
|
+
const version2 = result.stdout?.toString().trim().replace(/^git version /, "");
|
|
15360
|
+
return [
|
|
15361
|
+
`gitVersion=${result.status === 0 && version2 ? version2 : `unavailable(${result.error ? result.error.code : result.status})`}`,
|
|
15362
|
+
`gitSpawnMs=${(performance.now() - started).toFixed(0)}`
|
|
15363
|
+
];
|
|
15364
|
+
}
|
|
15365
|
+
function locateGitRead(fields, repoRoot, objectsDir, treeish, fallback) {
|
|
15366
|
+
let targets = null;
|
|
15367
|
+
if (treeish) {
|
|
15368
|
+
const listed = probeGit(
|
|
15369
|
+
repoRoot,
|
|
15370
|
+
["ls-tree", "-r", "-z", treeish],
|
|
15371
|
+
gitLimits.listMs
|
|
15372
|
+
);
|
|
15373
|
+
if (listed.status === 0 && listed.stdout) {
|
|
15374
|
+
targets = treeBlobs(listed.stdout.toString());
|
|
15375
|
+
fields.push(
|
|
15376
|
+
`gitReadTarget=tree:${OBJECT_SHA.test(treeish) ? treeish.slice(0, 8) : treeish}`,
|
|
15377
|
+
`treeBlobs=${targets.length}`
|
|
15378
|
+
);
|
|
15379
|
+
targets = targets.slice(0, MAX_LOCATE_BLOBS);
|
|
15380
|
+
} else {
|
|
15381
|
+
fields.push("gitReadTarget=sample", `lsTreeError=${failureText(listed)}`);
|
|
15382
|
+
}
|
|
15383
|
+
} else {
|
|
15384
|
+
fields.push("gitReadTarget=sample");
|
|
15385
|
+
}
|
|
15386
|
+
targets ??= [...new Set(fallback)].map((sha) => ({ sha }));
|
|
15387
|
+
if (targets.length === 0) return;
|
|
15388
|
+
const started = performance.now();
|
|
15389
|
+
const read = probeGit(
|
|
15390
|
+
repoRoot,
|
|
15391
|
+
["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
15392
|
+
gitLimits.readMs,
|
|
15393
|
+
`${targets.map((target) => target.sha).join("\n")}
|
|
15394
|
+
`
|
|
15395
|
+
);
|
|
15396
|
+
const answered = (read.stdout?.toString() ?? "").split("\n").slice(0, -1);
|
|
15397
|
+
fields.push(
|
|
15398
|
+
`gitRead=${answered.length}/${targets.length}`,
|
|
15399
|
+
`gitReadMs=${(performance.now() - started).toFixed(1)}`,
|
|
15400
|
+
`gitMissing=${answered.filter((line) => line.endsWith(" missing")).length}`
|
|
15401
|
+
);
|
|
15402
|
+
const stalled = targets[answered.length];
|
|
15403
|
+
if (!stalled) return;
|
|
15404
|
+
fields.push(
|
|
15405
|
+
read.error?.code === "ETIMEDOUT" ? `gitReadTimeout=${gitLimits.readMs}` : `gitReadError=${failureText(read)}`,
|
|
15406
|
+
`gitStalledOn=${stalled.sha.slice(0, 8)}`
|
|
15407
|
+
);
|
|
15408
|
+
if (stalled.path)
|
|
15409
|
+
fields.push(
|
|
15410
|
+
`stalledPath=${JSON.stringify(stalled.path.slice(0, MAX_STALLED_PATH_CHARS))}`
|
|
15411
|
+
);
|
|
15412
|
+
const loose = path16.join(
|
|
15413
|
+
objectsDir,
|
|
15414
|
+
stalled.sha.slice(0, 2),
|
|
15415
|
+
stalled.sha.slice(2)
|
|
15416
|
+
);
|
|
15417
|
+
const stats = fs17.statSync(loose, { throwIfNoEntry: false });
|
|
15418
|
+
fields.push(
|
|
15419
|
+
stats ? `stalledLoose=size=${stats.size},blocks=${stats.blocks}` : "stalledLoose=no"
|
|
15420
|
+
);
|
|
15421
|
+
if (stats && process.platform !== "win32") {
|
|
15422
|
+
const headStarted = performance.now();
|
|
15423
|
+
const head = spawnSync("head", ["-c", "4096", loose], {
|
|
15424
|
+
timeout: gitLimits.headMs,
|
|
15425
|
+
killSignal: "SIGKILL"
|
|
15426
|
+
});
|
|
15427
|
+
fields.push(
|
|
15428
|
+
head.error?.code === "ETIMEDOUT" ? `headTimeout=${gitLimits.headMs}` : `headMs=${(performance.now() - headStarted).toFixed(0)}`
|
|
15429
|
+
);
|
|
15430
|
+
}
|
|
15431
|
+
}
|
|
15432
|
+
function treeBlobs(listing) {
|
|
15433
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
15434
|
+
for (const entry of listing.split("\0")) {
|
|
15435
|
+
const tab = entry.indexOf(" ");
|
|
15436
|
+
if (tab < 0) continue;
|
|
15437
|
+
const [, type, sha] = entry.slice(0, tab).split(" ");
|
|
15438
|
+
if (type === "blob" && sha && !blobs.has(sha))
|
|
15439
|
+
blobs.set(sha, entry.slice(tab + 1));
|
|
15440
|
+
}
|
|
15441
|
+
return [...blobs].map(([sha, blobPath]) => ({ sha, path: blobPath }));
|
|
15442
|
+
}
|
|
15443
|
+
function failureText(result) {
|
|
15444
|
+
const error = result.error;
|
|
15445
|
+
if (error) return error.code ?? error.message;
|
|
15446
|
+
const stderr = result.stderr?.toString().trim().split("\n")[0];
|
|
15447
|
+
return stderr || `exit=${result.status}`;
|
|
15448
|
+
}
|
|
15449
|
+
|
|
15450
|
+
// src/git-traces/git-ops.ts
|
|
15191
15451
|
var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
15192
15452
|
var DEFAULT_BUNDLE_TIMEOUT_MS = 3e5;
|
|
15193
15453
|
var FULL_TREE_SCAN_TIMEOUT_MS = 3e5;
|
|
@@ -15378,15 +15638,15 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
15378
15638
|
]);
|
|
15379
15639
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
15380
15640
|
function isExcludedSnapshotPath(filePath) {
|
|
15381
|
-
if (EXCLUDED_SNAPSHOT_BASENAMES.has(
|
|
15382
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
15641
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path17.basename(filePath))) return true;
|
|
15642
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path17.extname(filePath).toLowerCase());
|
|
15383
15643
|
}
|
|
15384
15644
|
function isBinaryBuffer(buffer) {
|
|
15385
15645
|
return buffer.includes(0);
|
|
15386
15646
|
}
|
|
15387
15647
|
function readTreeBlobHead(repoRoot, sha) {
|
|
15388
15648
|
try {
|
|
15389
|
-
const { stdout } =
|
|
15649
|
+
const { stdout } = spawnSync2("git", ["cat-file", "blob", sha], {
|
|
15390
15650
|
cwd: repoRoot,
|
|
15391
15651
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
15392
15652
|
maxBuffer: BINARY_SNIFF_BYTES,
|
|
@@ -15400,16 +15660,16 @@ function readTreeBlobHead(repoRoot, sha) {
|
|
|
15400
15660
|
function readWorkingFileHead(absPath) {
|
|
15401
15661
|
let fd = null;
|
|
15402
15662
|
try {
|
|
15403
|
-
fd =
|
|
15663
|
+
fd = fs18.openSync(absPath, "r");
|
|
15404
15664
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
15405
|
-
const bytesRead =
|
|
15665
|
+
const bytesRead = fs18.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
15406
15666
|
return buffer.subarray(0, bytesRead);
|
|
15407
15667
|
} catch {
|
|
15408
15668
|
return null;
|
|
15409
15669
|
} finally {
|
|
15410
15670
|
if (fd !== null) {
|
|
15411
15671
|
try {
|
|
15412
|
-
|
|
15672
|
+
fs18.closeSync(fd);
|
|
15413
15673
|
} catch {
|
|
15414
15674
|
}
|
|
15415
15675
|
}
|
|
@@ -15462,11 +15722,19 @@ function formatGitFailure(command, failure, err) {
|
|
|
15462
15722
|
}
|
|
15463
15723
|
return `${command} failed${details.length ? ` (${details.join("; ")})` : ""}`;
|
|
15464
15724
|
}
|
|
15465
|
-
function
|
|
15725
|
+
function formatGitTimeout(command, failure, timeoutMs, elapsedMs) {
|
|
15726
|
+
const details = [];
|
|
15727
|
+
if (elapsedMs !== void 0) details.push(`elapsed=${elapsedMs}ms`);
|
|
15728
|
+
if (failure.signal) details.push(`signal=${failure.signal}`);
|
|
15729
|
+
const stderr = outputToString(failure.stderr);
|
|
15730
|
+
if (stderr) details.push(`stderr=${truncateOutput(stderr)}`);
|
|
15731
|
+
return `${command} timed out after ${timeoutMs}ms${details.length ? ` (${details.join("; ")})` : ""}`;
|
|
15732
|
+
}
|
|
15733
|
+
function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS, elapsedMs) {
|
|
15466
15734
|
const failure = err;
|
|
15467
15735
|
const command = formatGitCommand(args);
|
|
15468
15736
|
const isTimeout = failure.code === "ETIMEDOUT";
|
|
15469
|
-
const message = isTimeout ?
|
|
15737
|
+
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
15738
|
const wrapped = new Error(message);
|
|
15471
15739
|
wrapped.isGitTimeout = isTimeout;
|
|
15472
15740
|
wrapped.gitExitStatus = failure.code || failure.signal ? void 0 : failure.status;
|
|
@@ -15498,6 +15766,8 @@ function isGitTimeoutError(err) {
|
|
|
15498
15766
|
}
|
|
15499
15767
|
function gitBuffer(repoRoot, args, options = {}) {
|
|
15500
15768
|
const timeout = options.timeoutMs ?? EXEC_OPTS.timeout;
|
|
15769
|
+
const detached = options.killProcessGroup && process.platform !== "win32";
|
|
15770
|
+
const started = Date.now();
|
|
15501
15771
|
try {
|
|
15502
15772
|
return execFileSync2("git", args, {
|
|
15503
15773
|
cwd: repoRoot,
|
|
@@ -15505,10 +15775,28 @@ function gitBuffer(repoRoot, args, options = {}) {
|
|
|
15505
15775
|
...options.input !== void 0 ? { input: options.input } : {},
|
|
15506
15776
|
stdio: ["pipe", "pipe", "pipe"],
|
|
15507
15777
|
...EXEC_OPTS,
|
|
15508
|
-
timeout
|
|
15778
|
+
timeout,
|
|
15779
|
+
// Not in the sync typings, but spawnSync honors it (setsid).
|
|
15780
|
+
...detached ? { detached: true } : {}
|
|
15509
15781
|
});
|
|
15510
15782
|
} catch (err) {
|
|
15511
|
-
|
|
15783
|
+
const pid = err.pid;
|
|
15784
|
+
if (detached && pid) {
|
|
15785
|
+
try {
|
|
15786
|
+
process.kill(-pid, "SIGKILL");
|
|
15787
|
+
} catch {
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15790
|
+
const wrapped = createGitError(args, err, timeout, Date.now() - started);
|
|
15791
|
+
if (wrapped.isGitTimeout || wrapped.isGitMissingObject && !options.expectMissingObjects) {
|
|
15792
|
+
probeObjectStore(
|
|
15793
|
+
repoRoot,
|
|
15794
|
+
wrapped.isGitTimeout ? "timeout" : "missing-object",
|
|
15795
|
+
wrapped.gitStderr,
|
|
15796
|
+
args
|
|
15797
|
+
);
|
|
15798
|
+
}
|
|
15799
|
+
throw wrapped;
|
|
15512
15800
|
}
|
|
15513
15801
|
}
|
|
15514
15802
|
function git(repoRoot, args) {
|
|
@@ -15546,23 +15834,23 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
15546
15834
|
let tmpDir = null;
|
|
15547
15835
|
let copy = null;
|
|
15548
15836
|
try {
|
|
15549
|
-
const indexPath =
|
|
15837
|
+
const indexPath = path17.resolve(
|
|
15550
15838
|
repoRoot,
|
|
15551
15839
|
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
15552
15840
|
);
|
|
15553
|
-
tmpDir =
|
|
15554
|
-
copy =
|
|
15555
|
-
const fd =
|
|
15841
|
+
tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-index-"));
|
|
15842
|
+
copy = path17.join(tmpDir, "index");
|
|
15843
|
+
const fd = fs18.openSync(indexPath, "r");
|
|
15556
15844
|
let stat;
|
|
15557
15845
|
let data;
|
|
15558
15846
|
try {
|
|
15559
|
-
stat =
|
|
15560
|
-
data =
|
|
15847
|
+
stat = fs18.fstatSync(fd);
|
|
15848
|
+
data = fs18.readFileSync(fd);
|
|
15561
15849
|
} finally {
|
|
15562
|
-
|
|
15850
|
+
fs18.closeSync(fd);
|
|
15563
15851
|
}
|
|
15564
|
-
|
|
15565
|
-
|
|
15852
|
+
fs18.writeFileSync(copy, data);
|
|
15853
|
+
fs18.utimesSync(copy, stat.atime, stat.mtime);
|
|
15566
15854
|
} catch (err) {
|
|
15567
15855
|
const missing = err.code === "ENOENT";
|
|
15568
15856
|
appendLog(
|
|
@@ -15574,7 +15862,7 @@ function withPrivateIndex(repoRoot, run) {
|
|
|
15574
15862
|
try {
|
|
15575
15863
|
return run(copy ? privateIndexEnv(copy) : null);
|
|
15576
15864
|
} finally {
|
|
15577
|
-
if (tmpDir)
|
|
15865
|
+
if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
15578
15866
|
}
|
|
15579
15867
|
}
|
|
15580
15868
|
function privateIndexEnv(indexPath) {
|
|
@@ -15638,7 +15926,7 @@ function captureWorkingCommitShaOnIndex(repoRoot, indexEnv) {
|
|
|
15638
15926
|
function hasUnbornHead(repoRoot) {
|
|
15639
15927
|
const ref = git(repoRoot, ["symbolic-ref", "--quiet", "HEAD"]);
|
|
15640
15928
|
const args = ["show-ref", "--verify", "--quiet", ref];
|
|
15641
|
-
const result =
|
|
15929
|
+
const result = spawnSync2("git", args, { cwd: repoRoot, ...EXEC_OPTS });
|
|
15642
15930
|
if (result.error) throw createGitError(args, result.error);
|
|
15643
15931
|
return result.status === 1;
|
|
15644
15932
|
}
|
|
@@ -15657,20 +15945,99 @@ function deleteRef(repoRoot, refName) {
|
|
|
15657
15945
|
function captureSnapshotSha(repoRoot) {
|
|
15658
15946
|
return captureWorkingCommitSha(repoRoot);
|
|
15659
15947
|
}
|
|
15660
|
-
|
|
15661
|
-
|
|
15948
|
+
var OBJECT_SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
15949
|
+
function gitExpectingMissing(repoRoot, args) {
|
|
15950
|
+
return gitBuffer(repoRoot, args, { expectMissingObjects: true }).toString("utf-8").trim();
|
|
15951
|
+
}
|
|
15952
|
+
function commitTreeSha(repoRoot, commit) {
|
|
15953
|
+
const tree = gitExpectingMissing(repoRoot, [
|
|
15954
|
+
"rev-list",
|
|
15955
|
+
"--no-walk",
|
|
15956
|
+
"--format=%T",
|
|
15957
|
+
commit,
|
|
15958
|
+
"--"
|
|
15959
|
+
]).split("\n").at(-1);
|
|
15960
|
+
return tree && OBJECT_SHA2.test(tree) ? tree : null;
|
|
15961
|
+
}
|
|
15962
|
+
function listUnsharedObjects(repoRoot, treeSha, baseTrees) {
|
|
15963
|
+
const out = gitExpectingMissing(repoRoot, [
|
|
15964
|
+
"rev-list",
|
|
15965
|
+
"--objects",
|
|
15966
|
+
treeSha,
|
|
15967
|
+
...baseTrees.map((base) => `^${base}`),
|
|
15968
|
+
"--"
|
|
15969
|
+
]);
|
|
15970
|
+
const shas = /* @__PURE__ */ new Set();
|
|
15971
|
+
for (const line of out.split("\n")) {
|
|
15972
|
+
const sha = line.split(" ", 1)[0];
|
|
15973
|
+
if (OBJECT_SHA2.test(sha)) shas.add(sha);
|
|
15974
|
+
}
|
|
15975
|
+
return [...shas];
|
|
15976
|
+
}
|
|
15977
|
+
function walkBaseTree(repoRoot, tree) {
|
|
15978
|
+
gitExpectingMissing(repoRoot, ["rev-list", "--objects", `^${tree}`, "--"]);
|
|
15979
|
+
}
|
|
15980
|
+
function blobSizeTotals(repoRoot, shas) {
|
|
15981
|
+
const totals = { bytes: 0, blobs: 0, unreadable: 0 };
|
|
15982
|
+
for (const object of batchCheckObjects(repoRoot, shas)) {
|
|
15983
|
+
if (object.type === "missing") totals.unreadable++;
|
|
15984
|
+
else if (object.type === "blob" && object.size !== null) {
|
|
15985
|
+
totals.bytes += object.size;
|
|
15986
|
+
totals.blobs++;
|
|
15987
|
+
}
|
|
15988
|
+
}
|
|
15989
|
+
return totals;
|
|
15990
|
+
}
|
|
15991
|
+
var gitVersionCache;
|
|
15992
|
+
function gitVersion2(repoRoot) {
|
|
15993
|
+
if (gitVersionCache !== void 0) return gitVersionCache;
|
|
15994
|
+
try {
|
|
15995
|
+
const match = /git version (\d+)\.(\d+)(?:\.(\d+))?/.exec(
|
|
15996
|
+
git(repoRoot, ["--version"])
|
|
15997
|
+
);
|
|
15998
|
+
gitVersionCache = match ? [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)] : null;
|
|
15999
|
+
} catch {
|
|
16000
|
+
gitVersionCache = null;
|
|
16001
|
+
}
|
|
16002
|
+
return gitVersionCache;
|
|
16003
|
+
}
|
|
16004
|
+
function gitExcludesNegativeTrees(repoRoot) {
|
|
16005
|
+
const version2 = gitVersion2(repoRoot);
|
|
16006
|
+
if (!version2) return true;
|
|
16007
|
+
const [major, minor, patch] = version2;
|
|
16008
|
+
return major > 1 || minor > 8 || minor === 8 && patch >= 5;
|
|
16009
|
+
}
|
|
16010
|
+
function treeBlobSizes(repoRoot, treeSha) {
|
|
16011
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
16012
|
+
for (const entry of lsTreeEntries(repoRoot, ["-l"], treeSha)) {
|
|
16013
|
+
if (entry.type === "blob" && !sizes.has(entry.sha))
|
|
16014
|
+
sizes.set(
|
|
16015
|
+
entry.sha,
|
|
16016
|
+
/^\d+$/.test(entry.size) ? Number(entry.size) : null
|
|
16017
|
+
);
|
|
16018
|
+
}
|
|
16019
|
+
return sizes;
|
|
16020
|
+
}
|
|
16021
|
+
function treeBlobShas(repoRoot, treeSha) {
|
|
16022
|
+
const shas = /* @__PURE__ */ new Set();
|
|
16023
|
+
for (const entry of lsTreeEntries(repoRoot, [], treeSha)) {
|
|
16024
|
+
if (entry.type === "blob") shas.add(entry.sha);
|
|
16025
|
+
}
|
|
16026
|
+
return shas;
|
|
16027
|
+
}
|
|
16028
|
+
function lsTreeEntries(repoRoot, flags, treeSha) {
|
|
16029
|
+
const entries = [];
|
|
15662
16030
|
for (const entry of gitBuffer(repoRoot, [
|
|
15663
16031
|
"ls-tree",
|
|
15664
16032
|
"-r",
|
|
15665
|
-
|
|
16033
|
+
...flags,
|
|
15666
16034
|
"-z",
|
|
15667
|
-
|
|
16035
|
+
treeSha
|
|
15668
16036
|
]).toString("utf-8").split("\0")) {
|
|
15669
|
-
const [, type, sha, size] = entry.slice(0, entry.indexOf(" ")).trim().split(/\s+/);
|
|
15670
|
-
if (type
|
|
15671
|
-
blobs.set(sha, Number(size));
|
|
16037
|
+
const [, type, sha, size = ""] = entry.slice(0, entry.indexOf(" ")).trim().split(/\s+/);
|
|
16038
|
+
if (type && sha) entries.push({ type, sha, size });
|
|
15672
16039
|
}
|
|
15673
|
-
return
|
|
16040
|
+
return entries;
|
|
15674
16041
|
}
|
|
15675
16042
|
function formatSize(bytes) {
|
|
15676
16043
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -15742,20 +16109,28 @@ function parseDiffTreeRawZ(output) {
|
|
|
15742
16109
|
}
|
|
15743
16110
|
function lookupBlobSizes(repoRoot, shas) {
|
|
15744
16111
|
const sizes = /* @__PURE__ */ new Map();
|
|
15745
|
-
|
|
16112
|
+
for (const object of batchCheckObjects(repoRoot, shas)) {
|
|
16113
|
+
if (object.type === "blob" && object.size !== null)
|
|
16114
|
+
sizes.set(object.sha, object.size);
|
|
16115
|
+
}
|
|
16116
|
+
return sizes;
|
|
16117
|
+
}
|
|
16118
|
+
function batchCheckObjects(repoRoot, shas) {
|
|
16119
|
+
if (shas.length === 0) return [];
|
|
15746
16120
|
const out = gitBuffer(
|
|
15747
16121
|
repoRoot,
|
|
15748
16122
|
["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
15749
16123
|
{ input: `${shas.join("\n")}
|
|
15750
16124
|
` }
|
|
15751
16125
|
).toString("utf-8");
|
|
16126
|
+
const objects = [];
|
|
15752
16127
|
for (const line of out.split("\n")) {
|
|
15753
16128
|
const [sha, type, sizeRaw] = line.split(" ");
|
|
15754
|
-
if (
|
|
16129
|
+
if (!sha || !type) continue;
|
|
15755
16130
|
const size = Number.parseInt(sizeRaw, 10);
|
|
15756
|
-
|
|
16131
|
+
objects.push({ sha, type, size: Number.isFinite(size) ? size : null });
|
|
15757
16132
|
}
|
|
15758
|
-
return
|
|
16133
|
+
return objects;
|
|
15759
16134
|
}
|
|
15760
16135
|
function sortOmittedByPath(files) {
|
|
15761
16136
|
return [...files].sort(
|
|
@@ -15904,7 +16279,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
15904
16279
|
options.omittedFiles?.push(...omittedFiles);
|
|
15905
16280
|
return timeCaptureStage(repoRoot, "tracked-tree-filter", () => {
|
|
15906
16281
|
if (omittedFiles.length === 0) return treeSha;
|
|
15907
|
-
const tmpIndex =
|
|
16282
|
+
const tmpIndex = path17.join(
|
|
15908
16283
|
os7.tmpdir(),
|
|
15909
16284
|
`hillclimb-filter-${Date.now()}-${process.pid}`
|
|
15910
16285
|
);
|
|
@@ -15919,7 +16294,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
15919
16294
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
15920
16295
|
} finally {
|
|
15921
16296
|
try {
|
|
15922
|
-
|
|
16297
|
+
fs18.unlinkSync(tmpIndex);
|
|
15923
16298
|
} catch {
|
|
15924
16299
|
}
|
|
15925
16300
|
}
|
|
@@ -15944,8 +16319,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
15944
16319
|
if (!relPath) continue;
|
|
15945
16320
|
candidates++;
|
|
15946
16321
|
try {
|
|
15947
|
-
const absPath =
|
|
15948
|
-
const stat =
|
|
16322
|
+
const absPath = path17.join(repoRoot, relPath);
|
|
16323
|
+
const stat = fs18.lstatSync(absPath);
|
|
15949
16324
|
const reason = classifyOmission(
|
|
15950
16325
|
relPath,
|
|
15951
16326
|
stat.size,
|
|
@@ -15976,11 +16351,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
15976
16351
|
`git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
|
|
15977
16352
|
);
|
|
15978
16353
|
if (kept.length === 0) return null;
|
|
15979
|
-
const tmpDir =
|
|
16354
|
+
const tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-untracked-"));
|
|
15980
16355
|
const env = {
|
|
15981
16356
|
...process.env,
|
|
15982
16357
|
LC_ALL: "C",
|
|
15983
|
-
GIT_INDEX_FILE:
|
|
16358
|
+
GIT_INDEX_FILE: path17.join(tmpDir, "index")
|
|
15984
16359
|
};
|
|
15985
16360
|
try {
|
|
15986
16361
|
timeCaptureStage(
|
|
@@ -16008,7 +16383,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
|
|
|
16008
16383
|
return buildUntrackedTree(repoRoot, options, attempt + 1);
|
|
16009
16384
|
} finally {
|
|
16010
16385
|
try {
|
|
16011
|
-
|
|
16386
|
+
fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
16012
16387
|
} catch {
|
|
16013
16388
|
}
|
|
16014
16389
|
}
|
|
@@ -16054,10 +16429,10 @@ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
|
|
|
16054
16429
|
`git-traces: tree assembly reused (repo=${repoRoot})`
|
|
16055
16430
|
);
|
|
16056
16431
|
return previous.snapshotTreeSha;
|
|
16057
|
-
} catch {
|
|
16432
|
+
} catch (err) {
|
|
16058
16433
|
appendLog(
|
|
16059
16434
|
"info",
|
|
16060
|
-
`git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot})`
|
|
16435
|
+
`git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot}): ${err instanceof Error ? err.message : String(err)}`
|
|
16061
16436
|
);
|
|
16062
16437
|
}
|
|
16063
16438
|
}
|
|
@@ -16079,7 +16454,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
16079
16454
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
|
|
16080
16455
|
return filteredTrackedTree;
|
|
16081
16456
|
if (filteredTrackedTree === EMPTY_TREE_SHA) return untrackedTree;
|
|
16082
|
-
const tmpIndex =
|
|
16457
|
+
const tmpIndex = path17.join(
|
|
16083
16458
|
os7.tmpdir(),
|
|
16084
16459
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
16085
16460
|
);
|
|
@@ -16108,7 +16483,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
|
|
|
16108
16483
|
);
|
|
16109
16484
|
} finally {
|
|
16110
16485
|
try {
|
|
16111
|
-
|
|
16486
|
+
fs18.unlinkSync(tmpIndex);
|
|
16112
16487
|
} catch {
|
|
16113
16488
|
}
|
|
16114
16489
|
}
|
|
@@ -16123,18 +16498,19 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16123
16498
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
16124
16499
|
let tmpDir;
|
|
16125
16500
|
try {
|
|
16126
|
-
tmpDir =
|
|
16127
|
-
|
|
16501
|
+
tmpDir = fs18.mkdtempSync(
|
|
16502
|
+
path17.join(os7.tmpdir(), `hillclimb-bundle-${process.pid}-`)
|
|
16128
16503
|
);
|
|
16129
|
-
const tmpFile =
|
|
16504
|
+
const tmpFile = path17.join(tmpDir, "baseline.bundle");
|
|
16130
16505
|
timeCaptureStage(
|
|
16131
16506
|
repoRoot,
|
|
16132
16507
|
"bundle-create",
|
|
16133
16508
|
() => gitBuffer(repoRoot, ["bundle", "create", tmpFile, orphanRef], {
|
|
16134
|
-
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS)
|
|
16509
|
+
timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS),
|
|
16510
|
+
killProcessGroup: true
|
|
16135
16511
|
})
|
|
16136
16512
|
);
|
|
16137
|
-
const bundle =
|
|
16513
|
+
const bundle = fs18.readFileSync(tmpFile);
|
|
16138
16514
|
appendLog(
|
|
16139
16515
|
"info",
|
|
16140
16516
|
`git-traces: bundle inventory (repo=${repoRoot}, bytes=${bundle.length})`
|
|
@@ -16142,7 +16518,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16142
16518
|
return bundle;
|
|
16143
16519
|
} finally {
|
|
16144
16520
|
try {
|
|
16145
|
-
if (tmpDir)
|
|
16521
|
+
if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
|
|
16146
16522
|
} catch {
|
|
16147
16523
|
}
|
|
16148
16524
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -16151,6 +16527,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
16151
16527
|
var MAX_PATCH_GZ_BYTES = 500 * 1024 * 1024;
|
|
16152
16528
|
function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
16153
16529
|
return new Promise((resolve, reject) => {
|
|
16530
|
+
const started = Date.now();
|
|
16154
16531
|
const child = spawn2("git", args, {
|
|
16155
16532
|
cwd: repoRoot,
|
|
16156
16533
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -16179,7 +16556,18 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
|
16179
16556
|
settled = true;
|
|
16180
16557
|
clearTimeout(timer);
|
|
16181
16558
|
if (timedOut) {
|
|
16182
|
-
|
|
16559
|
+
const wrapped = createGitError(
|
|
16560
|
+
args,
|
|
16561
|
+
{
|
|
16562
|
+
code: "ETIMEDOUT",
|
|
16563
|
+
signal: exitSignal,
|
|
16564
|
+
stderr: Buffer.concat(stderrChunks)
|
|
16565
|
+
},
|
|
16566
|
+
GIT_COMMAND_TIMEOUT_MS,
|
|
16567
|
+
Date.now() - started
|
|
16568
|
+
);
|
|
16569
|
+
probeObjectStore(repoRoot, "timeout", wrapped.gitStderr, args);
|
|
16570
|
+
reject(wrapped);
|
|
16183
16571
|
} else if (overflowed) {
|
|
16184
16572
|
reject(
|
|
16185
16573
|
createGitError(args, { code: "ENOBUFS", maxBufferBytes: maxGzBytes })
|
|
@@ -16441,9 +16829,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
16441
16829
|
oldPath
|
|
16442
16830
|
});
|
|
16443
16831
|
} else {
|
|
16444
|
-
const
|
|
16445
|
-
indexByPath.set(
|
|
16446
|
-
files.push({ path:
|
|
16832
|
+
const path28 = parts[parts.length - 1];
|
|
16833
|
+
indexByPath.set(path28, files.length);
|
|
16834
|
+
files.push({ path: path28, status, additions: 0, deletions: 0 });
|
|
16447
16835
|
}
|
|
16448
16836
|
}
|
|
16449
16837
|
for (const line of numstat.split("\n")) {
|
|
@@ -16527,11 +16915,11 @@ function countScopedTurnTreeRefs(repoRoot, sessionId, epochPrefix3) {
|
|
|
16527
16915
|
|
|
16528
16916
|
// src/git-traces/session-state.ts
|
|
16529
16917
|
import crypto6 from "crypto";
|
|
16530
|
-
import
|
|
16918
|
+
import fs19 from "fs";
|
|
16531
16919
|
import os8 from "os";
|
|
16532
|
-
import
|
|
16920
|
+
import path18 from "path";
|
|
16533
16921
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
16534
|
-
var DEFAULT_STATE_DIR2 =
|
|
16922
|
+
var DEFAULT_STATE_DIR2 = path18.join(os8.homedir(), ".hillclimb", "git-traces");
|
|
16535
16923
|
var LOCK_RETRIES2 = 120;
|
|
16536
16924
|
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
16537
16925
|
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS2 = 10 * 60 * 1e3;
|
|
@@ -16556,16 +16944,16 @@ function stateDir3() {
|
|
|
16556
16944
|
}
|
|
16557
16945
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
16558
16946
|
const hash = crypto6.createHash("sha256").update(
|
|
16559
|
-
sessionId ? `${
|
|
16947
|
+
sessionId ? `${path18.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path18.resolve(repoRoot)}\0${tool}`
|
|
16560
16948
|
).digest("hex").slice(0, 16);
|
|
16561
|
-
return
|
|
16949
|
+
return path18.join(stateDir3(), `${hash}.json`);
|
|
16562
16950
|
}
|
|
16563
16951
|
function lockFileForRepo(repoRoot, tool, sessionId) {
|
|
16564
16952
|
return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
|
|
16565
16953
|
}
|
|
16566
16954
|
async function readStateFile(file) {
|
|
16567
16955
|
try {
|
|
16568
|
-
const raw = await
|
|
16956
|
+
const raw = await fs19.promises.readFile(file, "utf-8");
|
|
16569
16957
|
const parsed = JSON.parse(raw);
|
|
16570
16958
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
|
|
16571
16959
|
return null;
|
|
@@ -16579,7 +16967,7 @@ async function readStoredStateFile(file) {
|
|
|
16579
16967
|
const state = await readStateFile(file);
|
|
16580
16968
|
if (!state) return null;
|
|
16581
16969
|
try {
|
|
16582
|
-
return { state, mtimeMs: (await
|
|
16970
|
+
return { state, mtimeMs: (await fs19.promises.stat(file)).mtimeMs };
|
|
16583
16971
|
} catch {
|
|
16584
16972
|
return null;
|
|
16585
16973
|
}
|
|
@@ -16587,26 +16975,26 @@ async function readStoredStateFile(file) {
|
|
|
16587
16975
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
16588
16976
|
let entries;
|
|
16589
16977
|
try {
|
|
16590
|
-
entries = await
|
|
16978
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16591
16979
|
} catch {
|
|
16592
16980
|
return [];
|
|
16593
16981
|
}
|
|
16594
16982
|
const states = [];
|
|
16595
16983
|
for (const entry of entries) {
|
|
16596
16984
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
16597
|
-
const file =
|
|
16985
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16598
16986
|
const state = await readStateFile(file);
|
|
16599
16987
|
if (!state) continue;
|
|
16600
16988
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
16601
16989
|
continue;
|
|
16602
16990
|
}
|
|
16603
|
-
if (
|
|
16604
|
-
if (
|
|
16991
|
+
if (path18.resolve(state.repoRoot) !== path18.resolve(repoRoot)) continue;
|
|
16992
|
+
if (path18.resolve(file) !== path18.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
16605
16993
|
continue;
|
|
16606
16994
|
}
|
|
16607
16995
|
let mtimeMs = 0;
|
|
16608
16996
|
try {
|
|
16609
|
-
mtimeMs = (await
|
|
16997
|
+
mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
|
|
16610
16998
|
} catch {
|
|
16611
16999
|
continue;
|
|
16612
17000
|
}
|
|
@@ -16617,26 +17005,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
16617
17005
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
16618
17006
|
let entries;
|
|
16619
17007
|
try {
|
|
16620
|
-
entries = await
|
|
17008
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16621
17009
|
} catch {
|
|
16622
17010
|
return [];
|
|
16623
17011
|
}
|
|
16624
17012
|
const states = [];
|
|
16625
17013
|
for (const entry of entries) {
|
|
16626
17014
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
16627
|
-
const file =
|
|
17015
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16628
17016
|
const state = await readStateFile(file);
|
|
16629
17017
|
if (!state) continue;
|
|
16630
17018
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
16631
17019
|
continue;
|
|
16632
17020
|
}
|
|
16633
17021
|
if (state.sessionId !== sessionId) continue;
|
|
16634
|
-
if (
|
|
17022
|
+
if (path18.resolve(file) !== path18.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
16635
17023
|
continue;
|
|
16636
17024
|
}
|
|
16637
17025
|
let mtimeMs = 0;
|
|
16638
17026
|
try {
|
|
16639
|
-
mtimeMs = (await
|
|
17027
|
+
mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
|
|
16640
17028
|
} catch {
|
|
16641
17029
|
continue;
|
|
16642
17030
|
}
|
|
@@ -16685,17 +17073,17 @@ async function writeLegacySessionState(state, tool) {
|
|
|
16685
17073
|
);
|
|
16686
17074
|
}
|
|
16687
17075
|
async function writeStateFile(file, state) {
|
|
16688
|
-
await
|
|
17076
|
+
await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
16689
17077
|
const tmp = `${file}.tmp`;
|
|
16690
|
-
await
|
|
17078
|
+
await fs19.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
16691
17079
|
mode: 384
|
|
16692
17080
|
});
|
|
16693
|
-
await
|
|
17081
|
+
await fs19.promises.rename(tmp, file);
|
|
16694
17082
|
}
|
|
16695
17083
|
async function touchSessionState(repoRoot, tool, sessionId) {
|
|
16696
17084
|
const now = /* @__PURE__ */ new Date();
|
|
16697
17085
|
try {
|
|
16698
|
-
await
|
|
17086
|
+
await fs19.promises.utimes(
|
|
16699
17087
|
stateFileForRepo(repoRoot, tool, sessionId),
|
|
16700
17088
|
now,
|
|
16701
17089
|
now
|
|
@@ -16705,7 +17093,7 @@ async function touchSessionState(repoRoot, tool, sessionId) {
|
|
|
16705
17093
|
}
|
|
16706
17094
|
async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
16707
17095
|
try {
|
|
16708
|
-
const stat = await
|
|
17096
|
+
const stat = await fs19.promises.stat(
|
|
16709
17097
|
stateFileForRepo(repoRoot, tool, sessionId)
|
|
16710
17098
|
);
|
|
16711
17099
|
return stat.mtimeMs;
|
|
@@ -16715,7 +17103,7 @@ async function statSessionStateMtime(repoRoot, tool, sessionId) {
|
|
|
16715
17103
|
}
|
|
16716
17104
|
async function deleteStateFile(file) {
|
|
16717
17105
|
try {
|
|
16718
|
-
await
|
|
17106
|
+
await fs19.promises.unlink(file);
|
|
16719
17107
|
} catch {
|
|
16720
17108
|
}
|
|
16721
17109
|
}
|
|
@@ -16754,7 +17142,7 @@ async function acquireLock3(repoRoot, tool, sessionId, retries, delayMs, options
|
|
|
16754
17142
|
}
|
|
16755
17143
|
async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, delayMs, options = {}) {
|
|
16756
17144
|
const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
|
|
16757
|
-
await
|
|
17145
|
+
await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
16758
17146
|
const bounded = retries !== void 0 || delayMs !== void 0;
|
|
16759
17147
|
const attempts = retries ?? LOCK_RETRIES2;
|
|
16760
17148
|
const delay = delayMs ?? LOCK_RETRY_DELAY_MS2;
|
|
@@ -16764,9 +17152,9 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16764
17152
|
let extensionLogged = false;
|
|
16765
17153
|
for (let i = 0; ; i++) {
|
|
16766
17154
|
try {
|
|
16767
|
-
const fd = await
|
|
17155
|
+
const fd = await fs19.promises.open(
|
|
16768
17156
|
lockPath,
|
|
16769
|
-
|
|
17157
|
+
fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
|
|
16770
17158
|
);
|
|
16771
17159
|
await fd.write(String(process.pid));
|
|
16772
17160
|
await fd.close();
|
|
@@ -16775,7 +17163,7 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16775
17163
|
if (err.code !== "EEXIST") throw err;
|
|
16776
17164
|
let regularLockFile = false;
|
|
16777
17165
|
try {
|
|
16778
|
-
regularLockFile = (await
|
|
17166
|
+
regularLockFile = (await fs19.promises.lstat(lockPath)).isFile();
|
|
16779
17167
|
} catch (statErr) {
|
|
16780
17168
|
if (statErr.code === "ENOENT") {
|
|
16781
17169
|
i--;
|
|
@@ -16837,21 +17225,21 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
|
|
|
16837
17225
|
}
|
|
16838
17226
|
async function releaseLock3(repoRoot, tool, sessionId) {
|
|
16839
17227
|
try {
|
|
16840
|
-
await
|
|
17228
|
+
await fs19.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
|
|
16841
17229
|
} catch {
|
|
16842
17230
|
}
|
|
16843
17231
|
}
|
|
16844
17232
|
async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now()) {
|
|
16845
17233
|
let entries;
|
|
16846
17234
|
try {
|
|
16847
|
-
entries = await
|
|
17235
|
+
entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
16848
17236
|
} catch {
|
|
16849
17237
|
return 0;
|
|
16850
17238
|
}
|
|
16851
17239
|
let removed = 0;
|
|
16852
17240
|
for (const entry of entries) {
|
|
16853
17241
|
if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
|
|
16854
|
-
const file =
|
|
17242
|
+
const file = path18.join(stateDir3(), entry.name);
|
|
16855
17243
|
if (await reapLockIfStale(file, {
|
|
16856
17244
|
maxAgeMs: ttlMs,
|
|
16857
17245
|
preserveLiveOwner: true,
|
|
@@ -16878,6 +17266,13 @@ function replayBackoffMs(attempts) {
|
|
|
16878
17266
|
REPLAY_BACKOFF_MAX_MS
|
|
16879
17267
|
);
|
|
16880
17268
|
}
|
|
17269
|
+
function bundleBackoffRemainingMs(timeout, now) {
|
|
17270
|
+
if (!timeout) return 0;
|
|
17271
|
+
return Math.max(
|
|
17272
|
+
0,
|
|
17273
|
+
timeout.lastAttemptAt + replayBackoffMs(timeout.attempts) - now
|
|
17274
|
+
);
|
|
17275
|
+
}
|
|
16881
17276
|
function replayEligibility(oldestCapturedAt, replay, now) {
|
|
16882
17277
|
if (now - oldestCapturedAt < STRANDED_MIN_AGE_MS)
|
|
16883
17278
|
return { eligible: false, reason: "young" };
|
|
@@ -16906,12 +17301,12 @@ function withPendingCapture(record, work) {
|
|
|
16906
17301
|
return activeCapture.run({ record, save: () => saveCapture(record) }, work);
|
|
16907
17302
|
}
|
|
16908
17303
|
function queueDirectory(repoRoot, tool, sessionId) {
|
|
16909
|
-
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
16910
|
-
const key = crypto7.createHash("sha256").update(`${
|
|
16911
|
-
return
|
|
17304
|
+
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces");
|
|
17305
|
+
const key = crypto7.createHash("sha256").update(`${path19.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
|
|
17306
|
+
return path19.join(stateDir4, "pending-v1", key);
|
|
16912
17307
|
}
|
|
16913
17308
|
function captureDirectory(record) {
|
|
16914
|
-
return
|
|
17309
|
+
return path19.join(
|
|
16915
17310
|
queueDirectory(record.repoRoot, record.tool, record.sessionId),
|
|
16916
17311
|
record.id
|
|
16917
17312
|
);
|
|
@@ -16919,20 +17314,20 @@ function captureDirectory(record) {
|
|
|
16919
17314
|
function writeDurable(file, body) {
|
|
16920
17315
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
16921
17316
|
try {
|
|
16922
|
-
const descriptor =
|
|
17317
|
+
const descriptor = fs20.openSync(temporary, "w", 384);
|
|
16923
17318
|
try {
|
|
16924
|
-
|
|
16925
|
-
|
|
17319
|
+
fs20.writeFileSync(descriptor, body);
|
|
17320
|
+
fs20.fsyncSync(descriptor);
|
|
16926
17321
|
} finally {
|
|
16927
|
-
|
|
17322
|
+
fs20.closeSync(descriptor);
|
|
16928
17323
|
}
|
|
16929
|
-
|
|
17324
|
+
fs20.renameSync(temporary, file);
|
|
16930
17325
|
try {
|
|
16931
|
-
const directory =
|
|
17326
|
+
const directory = fs20.openSync(path19.dirname(file), "r");
|
|
16932
17327
|
try {
|
|
16933
|
-
|
|
17328
|
+
fs20.fsyncSync(directory);
|
|
16934
17329
|
} finally {
|
|
16935
|
-
|
|
17330
|
+
fs20.closeSync(directory);
|
|
16936
17331
|
}
|
|
16937
17332
|
} catch (err) {
|
|
16938
17333
|
if (!["EINVAL", "ENOTSUP", "EBADF", "EPERM", "EISDIR"].includes(
|
|
@@ -16941,7 +17336,7 @@ function writeDurable(file, body) {
|
|
|
16941
17336
|
throw err;
|
|
16942
17337
|
}
|
|
16943
17338
|
} finally {
|
|
16944
|
-
if (
|
|
17339
|
+
if (fs20.existsSync(temporary)) fs20.unlinkSync(temporary);
|
|
16945
17340
|
}
|
|
16946
17341
|
}
|
|
16947
17342
|
function saveCapture(record) {
|
|
@@ -16952,7 +17347,7 @@ function saveCapture(record) {
|
|
|
16952
17347
|
let status = "failed";
|
|
16953
17348
|
try {
|
|
16954
17349
|
writeDurable(
|
|
16955
|
-
|
|
17350
|
+
path19.join(captureDirectory(record), "capture.json"),
|
|
16956
17351
|
JSON.stringify(record)
|
|
16957
17352
|
);
|
|
16958
17353
|
status = "ok";
|
|
@@ -16964,7 +17359,7 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
16964
17359
|
const directory = queueDirectory(repoRoot, tool, sessionId);
|
|
16965
17360
|
let entries;
|
|
16966
17361
|
try {
|
|
16967
|
-
entries =
|
|
17362
|
+
entries = fs20.readdirSync(directory, { withFileTypes: true });
|
|
16968
17363
|
} catch (err) {
|
|
16969
17364
|
if (err.code === "ENOENT") return [];
|
|
16970
17365
|
throw err;
|
|
@@ -16972,10 +17367,10 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
|
|
|
16972
17367
|
const records = [];
|
|
16973
17368
|
for (const entry of entries) {
|
|
16974
17369
|
if (!entry.isDirectory() || !/^[0-9a-f-]{36}$/.test(entry.name)) continue;
|
|
16975
|
-
const file =
|
|
16976
|
-
if (!
|
|
17370
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17371
|
+
if (!fs20.existsSync(file)) continue;
|
|
16977
17372
|
const record = JSON.parse(
|
|
16978
|
-
|
|
17373
|
+
fs20.readFileSync(file, "utf-8")
|
|
16979
17374
|
);
|
|
16980
17375
|
if (record.version !== 1 || record.id !== entry.name || record.repoRoot !== repoRoot || record.tool !== tool || record.sessionId !== sessionId) {
|
|
16981
17376
|
throw new Error(`Invalid pending Git capture: ${file}`);
|
|
@@ -16988,13 +17383,13 @@ function hasPendingCaptures(repoRoot, tool, sessionId) {
|
|
|
16988
17383
|
return listPendingCaptures2(repoRoot, tool, sessionId).length > 0;
|
|
16989
17384
|
}
|
|
16990
17385
|
function listPendingQueues() {
|
|
16991
|
-
const root =
|
|
16992
|
-
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
17386
|
+
const root = path19.join(
|
|
17387
|
+
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces"),
|
|
16993
17388
|
"pending-v1"
|
|
16994
17389
|
);
|
|
16995
17390
|
let queueDirs;
|
|
16996
17391
|
try {
|
|
16997
|
-
queueDirs =
|
|
17392
|
+
queueDirs = fs20.readdirSync(root, { withFileTypes: true });
|
|
16998
17393
|
} catch (err) {
|
|
16999
17394
|
if (err.code === "ENOENT") return [];
|
|
17000
17395
|
throw err;
|
|
@@ -17002,15 +17397,15 @@ function listPendingQueues() {
|
|
|
17002
17397
|
const queues = [];
|
|
17003
17398
|
for (const queueDir of queueDirs) {
|
|
17004
17399
|
if (!queueDir.isDirectory()) continue;
|
|
17005
|
-
const directory =
|
|
17400
|
+
const directory = path19.join(root, queueDir.name);
|
|
17006
17401
|
let identity = null;
|
|
17007
17402
|
try {
|
|
17008
|
-
for (const entry of
|
|
17403
|
+
for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
|
|
17009
17404
|
if (!entry.isDirectory()) continue;
|
|
17010
|
-
const file =
|
|
17011
|
-
if (!
|
|
17405
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17406
|
+
if (!fs20.existsSync(file)) continue;
|
|
17012
17407
|
identity = JSON.parse(
|
|
17013
|
-
|
|
17408
|
+
fs20.readFileSync(file, "utf-8")
|
|
17014
17409
|
);
|
|
17015
17410
|
break;
|
|
17016
17411
|
}
|
|
@@ -17043,15 +17438,15 @@ function noteReplayFailure(record, error, now = Date.now()) {
|
|
|
17043
17438
|
}
|
|
17044
17439
|
function queueDiskBytes(directory) {
|
|
17045
17440
|
let diskBytes = 0;
|
|
17046
|
-
if (
|
|
17047
|
-
for (const entry of
|
|
17441
|
+
if (fs20.existsSync(directory)) {
|
|
17442
|
+
for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
|
|
17048
17443
|
if (!entry.isDirectory()) continue;
|
|
17049
|
-
for (const file of
|
|
17444
|
+
for (const file of fs20.readdirSync(path19.join(directory, entry.name), {
|
|
17050
17445
|
withFileTypes: true
|
|
17051
17446
|
})) {
|
|
17052
17447
|
if (file.isFile())
|
|
17053
|
-
diskBytes +=
|
|
17054
|
-
|
|
17448
|
+
diskBytes += fs20.statSync(
|
|
17449
|
+
path19.join(directory, entry.name, file.name)
|
|
17055
17450
|
).size;
|
|
17056
17451
|
}
|
|
17057
17452
|
}
|
|
@@ -17059,7 +17454,7 @@ function queueDiskBytes(directory) {
|
|
|
17059
17454
|
return diskBytes;
|
|
17060
17455
|
}
|
|
17061
17456
|
function chargedBytes(record) {
|
|
17062
|
-
return record.retainedBytes ?? record.treeBytes;
|
|
17457
|
+
return record.retainedBytes ?? record.treeBytes ?? 0;
|
|
17063
17458
|
}
|
|
17064
17459
|
function pendingBytes(records) {
|
|
17065
17460
|
const first = records[0];
|
|
@@ -17067,46 +17462,113 @@ function pendingBytes(records) {
|
|
|
17067
17462
|
return queueDiskBytes(
|
|
17068
17463
|
queueDirectory(first.repoRoot, first.tool, first.sessionId)
|
|
17069
17464
|
) + records.reduce(
|
|
17070
|
-
(sum, record) => sum + chargedBytes(record) + (
|
|
17465
|
+
(sum, record) => sum + chargedBytes(record) + (fs20.existsSync(path19.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
|
|
17071
17466
|
0
|
|
17072
17467
|
);
|
|
17073
17468
|
}
|
|
17469
|
+
function warnMissingBase(repoRoot, base) {
|
|
17470
|
+
appendLog(
|
|
17471
|
+
"warn",
|
|
17472
|
+
`git-traces: capture base is missing for retention accounting; charging unshared content (repo=${repoRoot}, base=${base})`
|
|
17473
|
+
);
|
|
17474
|
+
}
|
|
17074
17475
|
function measureTreeRetention(repoRoot, treeSha, bases, context) {
|
|
17075
17476
|
const finish = startCaptureStage("tree-inventory", context);
|
|
17076
17477
|
let status = "failed";
|
|
17077
17478
|
let details;
|
|
17078
17479
|
try {
|
|
17079
|
-
const
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
bases.filter((base2) => !!base2)
|
|
17084
|
-
)) {
|
|
17480
|
+
const trees = [
|
|
17481
|
+
...new Set(bases.trees.filter((base) => !!base))
|
|
17482
|
+
];
|
|
17483
|
+
if (!trees.includes(treeSha) && bases.headSha) {
|
|
17085
17484
|
try {
|
|
17086
|
-
const
|
|
17087
|
-
|
|
17088
|
-
|
|
17485
|
+
const headTree = commitTreeSha(repoRoot, bases.headSha);
|
|
17486
|
+
if (headTree && !trees.includes(headTree)) trees.push(headTree);
|
|
17487
|
+
if (!headTree) warnMissingBase(repoRoot, bases.headSha);
|
|
17089
17488
|
} catch (err) {
|
|
17090
17489
|
if (!isMissingObjectError(err)) throw err;
|
|
17091
|
-
|
|
17092
|
-
"warn",
|
|
17093
|
-
`git-traces: capture base is missing for retention accounting; charging unshared content (repo=${repoRoot}, base=${base})`
|
|
17094
|
-
);
|
|
17490
|
+
warnMissingBase(repoRoot, bases.headSha);
|
|
17095
17491
|
}
|
|
17096
17492
|
}
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17493
|
+
if (trees.includes(treeSha)) {
|
|
17494
|
+
status = "ok";
|
|
17495
|
+
details = "retainedBytes=0, sameAsBase=yes";
|
|
17496
|
+
return 0;
|
|
17497
|
+
}
|
|
17498
|
+
let totals;
|
|
17499
|
+
if (gitExcludesNegativeTrees(repoRoot)) {
|
|
17500
|
+
const { objects, usable } = unsharedObjects(repoRoot, treeSha, trees);
|
|
17501
|
+
totals = { ...blobSizeTotals(repoRoot, objects), used: usable };
|
|
17502
|
+
} else {
|
|
17503
|
+
totals = legacyRetention(repoRoot, treeSha, trees);
|
|
17102
17504
|
}
|
|
17505
|
+
const { used } = totals;
|
|
17103
17506
|
status = "ok";
|
|
17104
|
-
details = `
|
|
17105
|
-
|
|
17507
|
+
details = `retainedBytes=${totals.bytes}, retainedBlobs=${totals.blobs}, bases=${used}`;
|
|
17508
|
+
if (totals.unreadable > 0)
|
|
17509
|
+
details += `, unreadableBlobs=${totals.unreadable}`;
|
|
17510
|
+
if (used < trees.length) details += `, droppedBases=${trees.length - used}`;
|
|
17511
|
+
return totals.bytes;
|
|
17106
17512
|
} finally {
|
|
17107
17513
|
finish(status, details);
|
|
17108
17514
|
}
|
|
17109
17515
|
}
|
|
17516
|
+
function legacyRetention(repoRoot, treeSha, bases) {
|
|
17517
|
+
appendLog(
|
|
17518
|
+
"info",
|
|
17519
|
+
`git-traces: git before 1.8.5 ignores negative trees; measuring retention from ls-tree (repo=${repoRoot})`
|
|
17520
|
+
);
|
|
17521
|
+
const shared = /* @__PURE__ */ new Set();
|
|
17522
|
+
let used = 0;
|
|
17523
|
+
for (const base of bases) {
|
|
17524
|
+
try {
|
|
17525
|
+
for (const sha of treeBlobShas(repoRoot, base)) shared.add(sha);
|
|
17526
|
+
used++;
|
|
17527
|
+
} catch (err) {
|
|
17528
|
+
if (!isMissingObjectError(err)) throw err;
|
|
17529
|
+
warnMissingBase(repoRoot, base);
|
|
17530
|
+
}
|
|
17531
|
+
}
|
|
17532
|
+
const totals = { bytes: 0, blobs: 0, unreadable: 0, used };
|
|
17533
|
+
for (const [sha, size] of treeBlobSizes(repoRoot, treeSha)) {
|
|
17534
|
+
if (shared.has(sha)) continue;
|
|
17535
|
+
if (size === null) totals.unreadable++;
|
|
17536
|
+
else {
|
|
17537
|
+
totals.bytes += size;
|
|
17538
|
+
totals.blobs++;
|
|
17539
|
+
}
|
|
17540
|
+
}
|
|
17541
|
+
return totals;
|
|
17542
|
+
}
|
|
17543
|
+
function unsharedObjects(repoRoot, treeSha, bases) {
|
|
17544
|
+
try {
|
|
17545
|
+
return {
|
|
17546
|
+
objects: listUnsharedObjects(repoRoot, treeSha, bases),
|
|
17547
|
+
usable: bases.length
|
|
17548
|
+
};
|
|
17549
|
+
} catch (err) {
|
|
17550
|
+
if (!isMissingObjectError(err) || bases.length === 0) throw err;
|
|
17551
|
+
const usable = bases.filter((base) => {
|
|
17552
|
+
try {
|
|
17553
|
+
walkBaseTree(repoRoot, base);
|
|
17554
|
+
return true;
|
|
17555
|
+
} catch (baseErr) {
|
|
17556
|
+
if (!isMissingObjectError(baseErr)) throw baseErr;
|
|
17557
|
+
warnMissingBase(repoRoot, base);
|
|
17558
|
+
return false;
|
|
17559
|
+
}
|
|
17560
|
+
});
|
|
17561
|
+
if (usable.length === bases.length)
|
|
17562
|
+
return {
|
|
17563
|
+
objects: listUnsharedObjects(repoRoot, treeSha, bases),
|
|
17564
|
+
usable: bases.length
|
|
17565
|
+
};
|
|
17566
|
+
return {
|
|
17567
|
+
objects: listUnsharedObjects(repoRoot, treeSha, usable),
|
|
17568
|
+
usable: usable.length
|
|
17569
|
+
};
|
|
17570
|
+
}
|
|
17571
|
+
}
|
|
17110
17572
|
async function withCaptureQueueLock(repoRoot, tool, sessionId, work) {
|
|
17111
17573
|
const lockTool = `${tool}:capture-queue-v1`;
|
|
17112
17574
|
await acquireLock3(repoRoot, lockTool, sessionId);
|
|
@@ -17130,21 +17592,26 @@ function storePendingCapture(params) {
|
|
|
17130
17592
|
params.tool,
|
|
17131
17593
|
params.sessionId
|
|
17132
17594
|
);
|
|
17133
|
-
const
|
|
17595
|
+
const retainedBytes = measureTreeRetention(
|
|
17134
17596
|
params.repoRoot,
|
|
17135
17597
|
input.treeSha,
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17598
|
+
{
|
|
17599
|
+
trees: [
|
|
17600
|
+
existing.at(-1)?.treeSha,
|
|
17601
|
+
params.seed.lastSnapshotTreeSha,
|
|
17602
|
+
params.seed.baselineTreeSha
|
|
17603
|
+
],
|
|
17604
|
+
headSha: input.headSha
|
|
17605
|
+
},
|
|
17142
17606
|
`repo=${params.repoRoot}, tool=${params.tool}, session=${params.sessionId}, recordedAt=${params.recordedAt}`
|
|
17143
17607
|
);
|
|
17144
17608
|
const record = {
|
|
17145
17609
|
...identity,
|
|
17146
17610
|
...input,
|
|
17147
|
-
|
|
17611
|
+
retainedBytes,
|
|
17612
|
+
// 0.8.12 sums treeBytes with no fallback; a missing value would
|
|
17613
|
+
// disable its byte limit for the whole queue.
|
|
17614
|
+
treeBytes: retainedBytes,
|
|
17148
17615
|
version: 1,
|
|
17149
17616
|
id: crypto7.randomUUID(),
|
|
17150
17617
|
sequence: Math.max(Date.now(), (existing.at(-1)?.sequence ?? 0) + 1),
|
|
@@ -17153,14 +17620,14 @@ function storePendingCapture(params) {
|
|
|
17153
17620
|
const pendingBefore = queueDiskBytes(
|
|
17154
17621
|
queueDirectory(params.repoRoot, params.tool, params.sessionId)
|
|
17155
17622
|
) + existing.reduce((sum, item) => sum + chargedBytes(item), 0);
|
|
17156
|
-
const incomingBytes =
|
|
17623
|
+
const incomingBytes = retainedBytes + Buffer.byteLength(JSON.stringify(record));
|
|
17157
17624
|
if (existing.length >= MAX_CAPTURES || pendingBefore + incomingBytes > MAX_PENDING_BYTES2) {
|
|
17158
17625
|
throw new PendingCaptureLimitError(
|
|
17159
|
-
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes},
|
|
17626
|
+
`Pending Git capture limit reached (count=${existing.length}, maxCount=${MAX_CAPTURES}, pendingBytes=${pendingBefore}, incomingBytes=${incomingBytes}, retainedBytes=${retainedBytes}, maxBytes=${MAX_PENDING_BYTES2}); existing captures retained`
|
|
17160
17627
|
);
|
|
17161
17628
|
}
|
|
17162
17629
|
const directory = captureDirectory(record);
|
|
17163
|
-
|
|
17630
|
+
fs20.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
17164
17631
|
const refRoot = `refs/hillclimb/pending-v1/${record.id}`;
|
|
17165
17632
|
let published = false;
|
|
17166
17633
|
try {
|
|
@@ -17198,12 +17665,12 @@ function storePendingCapture(params) {
|
|
|
17198
17665
|
durableDetails = `capture=${record.id}, durableCapturedAt=${durableCapturedAt}`;
|
|
17199
17666
|
appendLog(
|
|
17200
17667
|
"info",
|
|
17201
|
-
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)},
|
|
17668
|
+
`git-traces: capture queued (repo=${record.repoRoot}, session=${record.sessionId}, capture=${record.id}, recordedAt=${record.recordedAt}, captureStartedAt=${record.captureStartedAt}, capturedAt=${record.capturedAt}, sourceCapturedAt=${record.capturedAt}, durableCapturedAt=${durableCapturedAt}, pending=${existing.length + 1}, oldestPendingAgeMs=${Date.now() - (existing[0]?.capturedAt ?? record.capturedAt)}, retainedBytes=${record.retainedBytes}, pendingBytes=${pendingBefore + incomingBytes})`
|
|
17202
17669
|
);
|
|
17203
17670
|
return record;
|
|
17204
17671
|
} finally {
|
|
17205
17672
|
if (!published) {
|
|
17206
|
-
|
|
17673
|
+
fs20.rmSync(directory, { recursive: true, force: true });
|
|
17207
17674
|
for (const label of [
|
|
17208
17675
|
"snapshot",
|
|
17209
17676
|
"tree",
|
|
@@ -17234,7 +17701,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17234
17701
|
let status = "failed";
|
|
17235
17702
|
try {
|
|
17236
17703
|
if (saved) {
|
|
17237
|
-
const bytes =
|
|
17704
|
+
const bytes = fs20.readFileSync(path19.join(directory, saved.file));
|
|
17238
17705
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256) {
|
|
17239
17706
|
throw new Error(
|
|
17240
17707
|
`Pending Git artifact is corrupt: ${saved.file}; capture retained`
|
|
@@ -17254,7 +17721,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17254
17721
|
);
|
|
17255
17722
|
}
|
|
17256
17723
|
const file = `${crypto7.createHash("sha256").update(key).digest("hex")}.artifact`;
|
|
17257
|
-
writeDurable(
|
|
17724
|
+
writeDurable(path19.join(directory, file), buffer);
|
|
17258
17725
|
context.record.uploads[key] = {
|
|
17259
17726
|
file,
|
|
17260
17727
|
bytes: buffer.byteLength,
|
|
@@ -17264,7 +17731,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
|
|
|
17264
17731
|
context.save();
|
|
17265
17732
|
} catch (err) {
|
|
17266
17733
|
delete context.record.uploads[key];
|
|
17267
|
-
|
|
17734
|
+
fs20.rmSync(path19.join(directory, file), { force: true });
|
|
17268
17735
|
throw err;
|
|
17269
17736
|
}
|
|
17270
17737
|
status = "ok";
|
|
@@ -17296,8 +17763,8 @@ function readPreparedCaptureArtifact(context, filename, input) {
|
|
|
17296
17763
|
throw new Error(
|
|
17297
17764
|
`Prepared Git artifact input changed: ${filename}; capture retained`
|
|
17298
17765
|
);
|
|
17299
|
-
const bytes =
|
|
17300
|
-
|
|
17766
|
+
const bytes = fs20.readFileSync(
|
|
17767
|
+
path19.join(captureDirectory(context.record), saved.file)
|
|
17301
17768
|
);
|
|
17302
17769
|
if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256)
|
|
17303
17770
|
throw new Error(
|
|
@@ -17316,7 +17783,7 @@ function completePendingCapture(record) {
|
|
|
17316
17783
|
function removeCompletedCapture(record) {
|
|
17317
17784
|
if (!record.completed)
|
|
17318
17785
|
throw new Error("Cannot remove an unacknowledged Git capture");
|
|
17319
|
-
|
|
17786
|
+
fs20.rmSync(captureDirectory(record), { recursive: true, force: true });
|
|
17320
17787
|
for (const label of ["snapshot", "tree", "head", "baseline", "previous"]) {
|
|
17321
17788
|
deleteRef(
|
|
17322
17789
|
record.repoRoot,
|
|
@@ -17389,7 +17856,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
17389
17856
|
}
|
|
17390
17857
|
|
|
17391
17858
|
// package.json
|
|
17392
|
-
var version = "0.9.
|
|
17859
|
+
var version = "0.9.6";
|
|
17393
17860
|
|
|
17394
17861
|
// src/version.ts
|
|
17395
17862
|
var CLI_VERSION = version;
|
|
@@ -17413,7 +17880,7 @@ function lineHasAssistant(line) {
|
|
|
17413
17880
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
17414
17881
|
}
|
|
17415
17882
|
async function hasAssistantMessage(transcriptPath) {
|
|
17416
|
-
const stream =
|
|
17883
|
+
const stream = fs21.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
17417
17884
|
let buffer = "";
|
|
17418
17885
|
try {
|
|
17419
17886
|
for await (const chunk of stream) {
|
|
@@ -17500,7 +17967,7 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
17500
17967
|
const workspace = payload.workspace_roots?.[0];
|
|
17501
17968
|
if (!id || !workspace) return void 0;
|
|
17502
17969
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
17503
|
-
return
|
|
17970
|
+
return path20.join(
|
|
17504
17971
|
os10.homedir(),
|
|
17505
17972
|
".cursor",
|
|
17506
17973
|
"projects",
|
|
@@ -17556,14 +18023,14 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
17556
18023
|
const cwd = resolveHookCwd(payload);
|
|
17557
18024
|
if (!parentSessionId || !cwd || !payload.transcript_path) return;
|
|
17558
18025
|
try {
|
|
17559
|
-
const directory =
|
|
17560
|
-
|
|
18026
|
+
const directory = path20.join(
|
|
18027
|
+
path20.dirname(path20.resolve(payload.transcript_path)),
|
|
17561
18028
|
parentSessionId,
|
|
17562
18029
|
"subagents"
|
|
17563
18030
|
);
|
|
17564
18031
|
let names;
|
|
17565
18032
|
try {
|
|
17566
|
-
names = await
|
|
18033
|
+
names = await fs21.promises.readdir(directory);
|
|
17567
18034
|
} catch (err) {
|
|
17568
18035
|
if (err.code === "ENOENT") return;
|
|
17569
18036
|
throw err;
|
|
@@ -17572,12 +18039,12 @@ async function sweepClaudeSubagentTranscripts(payload) {
|
|
|
17572
18039
|
for (const name of names) {
|
|
17573
18040
|
const match = /^agent-([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
|
|
17574
18041
|
if (!match) continue;
|
|
17575
|
-
const file =
|
|
18042
|
+
const file = path20.join(directory, name);
|
|
17576
18043
|
try {
|
|
17577
18044
|
files.push({
|
|
17578
18045
|
agentId: match[1],
|
|
17579
18046
|
file,
|
|
17580
|
-
modifiedMs: (await
|
|
18047
|
+
modifiedMs: (await fs21.promises.stat(file)).mtimeMs
|
|
17581
18048
|
});
|
|
17582
18049
|
} catch {
|
|
17583
18050
|
}
|
|
@@ -17743,7 +18210,7 @@ async function sweepCodexForkTranscripts(payload) {
|
|
|
17743
18210
|
const parent = await findProjectForCwd(cwd);
|
|
17744
18211
|
if (!parent) return;
|
|
17745
18212
|
const registeredRoots = Object.keys((await loadProjects()).projects).map(
|
|
17746
|
-
(root) =>
|
|
18213
|
+
(root) => path20.resolve(root)
|
|
17747
18214
|
);
|
|
17748
18215
|
const discovery = await discoverCodexForkThreads({
|
|
17749
18216
|
parentThreadId: parentSessionId,
|
|
@@ -17884,9 +18351,9 @@ async function runUploadForSession(payload, options) {
|
|
|
17884
18351
|
);
|
|
17885
18352
|
return "skipped";
|
|
17886
18353
|
}
|
|
17887
|
-
const transcriptResolved =
|
|
18354
|
+
const transcriptResolved = path20.resolve(transcriptPath);
|
|
17888
18355
|
try {
|
|
17889
|
-
const stat = await
|
|
18356
|
+
const stat = await fs21.promises.stat(transcriptResolved);
|
|
17890
18357
|
if (!stat.isFile()) {
|
|
17891
18358
|
appendLog(
|
|
17892
18359
|
"warn",
|
|
@@ -17942,7 +18409,7 @@ function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
|
|
|
17942
18409
|
};
|
|
17943
18410
|
return {
|
|
17944
18411
|
repoPath: repoRoot,
|
|
17945
|
-
label:
|
|
18412
|
+
label: path20.basename(repoRoot),
|
|
17946
18413
|
files: [sourceFile],
|
|
17947
18414
|
sourceNames: [sourceTool],
|
|
17948
18415
|
lastModified: /* @__PURE__ */ new Date()
|
|
@@ -17953,7 +18420,7 @@ async function buildRedactChain(repoRoot, capturedSecrets = []) {
|
|
|
17953
18420
|
"secret-discovery",
|
|
17954
18421
|
() => discoverEnvFiles(repoRoot)
|
|
17955
18422
|
);
|
|
17956
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
18423
|
+
const envFilePaths = envFileNames.map((n) => path20.join(repoRoot, n));
|
|
17957
18424
|
const secretResult = await measureAgentStage(
|
|
17958
18425
|
"secret-collection",
|
|
17959
18426
|
() => collectSecrets(repoRoot, envFilePaths, [])
|
|
@@ -18607,7 +19074,7 @@ async function uploadSession(args) {
|
|
|
18607
19074
|
try {
|
|
18608
19075
|
raw = await measureAgentStage(
|
|
18609
19076
|
"transcript-read",
|
|
18610
|
-
() => captured ? Promise.resolve(captured.bytes) :
|
|
19077
|
+
() => captured ? Promise.resolve(captured.bytes) : fs21.promises.readFile(transcriptPath)
|
|
18611
19078
|
);
|
|
18612
19079
|
} catch (err) {
|
|
18613
19080
|
if (err.code === "ERR_FS_FILE_TOO_LARGE") {
|
|
@@ -18978,15 +19445,15 @@ import crypto9 from "crypto";
|
|
|
18978
19445
|
|
|
18979
19446
|
// src/git-traces/handlers.ts
|
|
18980
19447
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
18981
|
-
import
|
|
18982
|
-
import
|
|
19448
|
+
import fs22 from "fs";
|
|
19449
|
+
import path21 from "path";
|
|
18983
19450
|
var GIT_TRACES_SLUG = "git-traces";
|
|
18984
19451
|
var MAX_FORK_SWEEP_THREADS2 = 8;
|
|
18985
19452
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
18986
19453
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
18987
19454
|
var REBASELINE_AFTER_DIFF_FAILURES = 2;
|
|
18988
19455
|
async function withRepoCaptureLock(repoRoot, capture) {
|
|
18989
|
-
const canonicalRoot =
|
|
19456
|
+
const canonicalRoot = fs22.realpathSync(repoRoot);
|
|
18990
19457
|
await acquireLock3(canonicalRoot, "snapshot-index-v1", null);
|
|
18991
19458
|
try {
|
|
18992
19459
|
return capture();
|
|
@@ -19044,7 +19511,7 @@ var PendingQueueConflictError = class extends Error {
|
|
|
19044
19511
|
async function loadConfiguredRepos() {
|
|
19045
19512
|
const file = await loadProjects();
|
|
19046
19513
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
19047
|
-
repoRoot:
|
|
19514
|
+
repoRoot: path21.resolve(repoRoot),
|
|
19048
19515
|
config
|
|
19049
19516
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
19050
19517
|
}
|
|
@@ -19062,12 +19529,12 @@ async function configuredReposFor(trigger) {
|
|
|
19062
19529
|
return repos;
|
|
19063
19530
|
}
|
|
19064
19531
|
async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
19065
|
-
const source =
|
|
19066
|
-
const target =
|
|
19067
|
-
if (
|
|
19532
|
+
const source = path21.join(parentRoot, ".codex", "hooks.json");
|
|
19533
|
+
const target = path21.join(worktreeRoot, ".codex", "hooks.json");
|
|
19534
|
+
if (fs22.existsSync(target) || !fs22.existsSync(source)) return;
|
|
19068
19535
|
try {
|
|
19069
|
-
|
|
19070
|
-
|
|
19536
|
+
fs22.mkdirSync(path21.dirname(target), { recursive: true });
|
|
19537
|
+
fs22.copyFileSync(source, target, fs22.constants.COPYFILE_EXCL);
|
|
19071
19538
|
} catch (err) {
|
|
19072
19539
|
appendLog(
|
|
19073
19540
|
"warn",
|
|
@@ -19089,7 +19556,7 @@ async function propagateCodexHooks(parentRoot, worktreeRoot) {
|
|
|
19089
19556
|
}
|
|
19090
19557
|
}
|
|
19091
19558
|
function repoLabel(repoRoot) {
|
|
19092
|
-
return
|
|
19559
|
+
return path21.basename(repoRoot) || repoRoot;
|
|
19093
19560
|
}
|
|
19094
19561
|
function resolveCwd2(payload) {
|
|
19095
19562
|
return resolveHookCwd(payload);
|
|
@@ -19331,6 +19798,53 @@ function freezeEpochBaseline(params) {
|
|
|
19331
19798
|
return null;
|
|
19332
19799
|
}
|
|
19333
19800
|
}
|
|
19801
|
+
function createBundleWithBackoff(pending, params) {
|
|
19802
|
+
const { repoRoot, baselineTreeSha } = params;
|
|
19803
|
+
const previous = pending?.record.bundleTimeout?.input === baselineTreeSha ? pending.record.bundleTimeout : void 0;
|
|
19804
|
+
if (pending && previous) {
|
|
19805
|
+
const retryInMs = bundleBackoffRemainingMs(previous, Date.now());
|
|
19806
|
+
if (retryInMs > 0) {
|
|
19807
|
+
appendLog(
|
|
19808
|
+
"warn",
|
|
19809
|
+
`git-traces: baseline bundle deferred after timeout (repo=${repoRoot}, session=${pending.record.sessionId}, capture=${pending.record.id}, attempts=${previous.attempts}, retryInMs=${retryInMs})`
|
|
19810
|
+
);
|
|
19811
|
+
return null;
|
|
19812
|
+
}
|
|
19813
|
+
}
|
|
19814
|
+
try {
|
|
19815
|
+
const bundle = createBundleFromTree(
|
|
19816
|
+
repoRoot,
|
|
19817
|
+
baselineTreeSha,
|
|
19818
|
+
params.bundleKey,
|
|
19819
|
+
params.label
|
|
19820
|
+
);
|
|
19821
|
+
if (pending?.record.bundleTimeout) {
|
|
19822
|
+
delete pending.record.bundleTimeout;
|
|
19823
|
+
saveBundleBackoff(pending);
|
|
19824
|
+
}
|
|
19825
|
+
return bundle;
|
|
19826
|
+
} catch (err) {
|
|
19827
|
+
if (pending && isGitTimeoutError(err)) {
|
|
19828
|
+
pending.record.bundleTimeout = {
|
|
19829
|
+
input: baselineTreeSha,
|
|
19830
|
+
attempts: (previous?.attempts ?? 0) + 1,
|
|
19831
|
+
lastAttemptAt: Date.now()
|
|
19832
|
+
};
|
|
19833
|
+
saveBundleBackoff(pending);
|
|
19834
|
+
}
|
|
19835
|
+
throw err;
|
|
19836
|
+
}
|
|
19837
|
+
}
|
|
19838
|
+
function saveBundleBackoff(pending) {
|
|
19839
|
+
try {
|
|
19840
|
+
pending.save();
|
|
19841
|
+
} catch (err) {
|
|
19842
|
+
appendLog(
|
|
19843
|
+
"warn",
|
|
19844
|
+
`git-traces: could not save baseline bundle backoff (capture=${pending.record.id}): ${formatError(err)}`
|
|
19845
|
+
);
|
|
19846
|
+
}
|
|
19847
|
+
}
|
|
19334
19848
|
function buildFrozenEpochBaselineArtifacts(params) {
|
|
19335
19849
|
const { repoRoot, sessionId, epoch, baselineTreeSha, baselineMetadata } = params;
|
|
19336
19850
|
const prefix = epochPrefix2(epoch);
|
|
@@ -19340,12 +19854,13 @@ function buildFrozenEpochBaselineArtifacts(params) {
|
|
|
19340
19854
|
pending,
|
|
19341
19855
|
`${prefix}-baseline.bundle`,
|
|
19342
19856
|
baselineTreeSha
|
|
19343
|
-
)) ??
|
|
19857
|
+
)) ?? createBundleWithBackoff(pending, {
|
|
19344
19858
|
repoRoot,
|
|
19345
19859
|
baselineTreeSha,
|
|
19346
|
-
`${sessionId}-${prefix}`,
|
|
19347
|
-
`baseline ${prefix}`
|
|
19348
|
-
);
|
|
19860
|
+
bundleKey: `${sessionId}-${prefix}`,
|
|
19861
|
+
label: `baseline ${prefix}`
|
|
19862
|
+
});
|
|
19863
|
+
if (!bundleBuffer) return null;
|
|
19349
19864
|
const metadataBuffer = Buffer.from(
|
|
19350
19865
|
JSON.stringify(baselineMetadata, null, 2)
|
|
19351
19866
|
);
|
|
@@ -21250,6 +21765,10 @@ async function sweepStrandedCaptures(params) {
|
|
|
21250
21765
|
tally[state.reason]++;
|
|
21251
21766
|
continue;
|
|
21252
21767
|
}
|
|
21768
|
+
if (bundleBackoffRemainingMs(head.bundleTimeout, now) > 0) {
|
|
21769
|
+
tally.backoff++;
|
|
21770
|
+
continue;
|
|
21771
|
+
}
|
|
21253
21772
|
eligible.push({
|
|
21254
21773
|
queue,
|
|
21255
21774
|
oldestCapturedAt,
|
|
@@ -21306,7 +21825,7 @@ async function collectStopTargets(tool, sessionId, repos, repoByRoot) {
|
|
|
21306
21825
|
let needsLineageCheck = false;
|
|
21307
21826
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
21308
21827
|
for (const { state } of storedStates) {
|
|
21309
|
-
const repo = repoByRoot.get(
|
|
21828
|
+
const repo = repoByRoot.get(path21.resolve(state.repoRoot));
|
|
21310
21829
|
if (!repo) {
|
|
21311
21830
|
missingConfig++;
|
|
21312
21831
|
appendLog(
|
|
@@ -21338,7 +21857,7 @@ async function lateInitSkipReason(payload, tool) {
|
|
|
21338
21857
|
const transcriptPath = payload.transcript_path;
|
|
21339
21858
|
if (!transcriptPath) return tool === "codex" ? null : "no-transcript-path";
|
|
21340
21859
|
try {
|
|
21341
|
-
const stat = await
|
|
21860
|
+
const stat = await fs22.promises.stat(path21.resolve(transcriptPath));
|
|
21342
21861
|
return stat.isFile() ? null : "transcript-not-a-file";
|
|
21343
21862
|
} catch {
|
|
21344
21863
|
return "transcript-missing";
|
|
@@ -21547,9 +22066,9 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21547
22066
|
if (sessionId) {
|
|
21548
22067
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
21549
22068
|
for (const { state } of states) {
|
|
21550
|
-
const repoRoot =
|
|
22069
|
+
const repoRoot = path21.resolve(state.repoRoot);
|
|
21551
22070
|
repoRoots.add(repoRoot);
|
|
21552
|
-
const canProcess = repoByRoot.has(repoRoot) || project &&
|
|
22071
|
+
const canProcess = repoByRoot.has(repoRoot) || project && path21.resolve(project.repoRoot) === repoRoot;
|
|
21553
22072
|
if (canProcess && tool === "codex" && state.codexLineageChecked !== true) {
|
|
21554
22073
|
needsLineageCheck = true;
|
|
21555
22074
|
}
|
|
@@ -21586,7 +22105,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21586
22105
|
let rejected = 0;
|
|
21587
22106
|
const finalCaptures = /* @__PURE__ */ new Map();
|
|
21588
22107
|
for (const repoRoot of repoRoots) {
|
|
21589
|
-
const repo = repoByRoot.get(
|
|
22108
|
+
const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
21590
22109
|
if (repo)
|
|
21591
22110
|
finalCaptures.set(
|
|
21592
22111
|
repoRoot,
|
|
@@ -21594,7 +22113,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
21594
22113
|
);
|
|
21595
22114
|
}
|
|
21596
22115
|
for (const repoRoot of repoRoots) {
|
|
21597
|
-
const repo = repoByRoot.get(
|
|
22116
|
+
const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
21598
22117
|
if (!repo) {
|
|
21599
22118
|
skipped++;
|
|
21600
22119
|
appendLog(
|
|
@@ -21973,15 +22492,15 @@ ${stack}` : ""}`
|
|
|
21973
22492
|
}
|
|
21974
22493
|
|
|
21975
22494
|
// src/outputs/zip.ts
|
|
21976
|
-
import
|
|
21977
|
-
import
|
|
22495
|
+
import fs24 from "fs";
|
|
22496
|
+
import path23 from "path";
|
|
21978
22497
|
import archiver2 from "archiver";
|
|
21979
22498
|
|
|
21980
22499
|
// src/outputs/downloads.ts
|
|
21981
22500
|
import { execSync as execSync2 } from "child_process";
|
|
21982
|
-
import
|
|
22501
|
+
import fs23 from "fs";
|
|
21983
22502
|
import os11 from "os";
|
|
21984
|
-
import
|
|
22503
|
+
import path22 from "path";
|
|
21985
22504
|
function getDownloadsFolder() {
|
|
21986
22505
|
const home = os11.homedir();
|
|
21987
22506
|
if (process.platform === "linux") {
|
|
@@ -21990,12 +22509,12 @@ function getDownloadsFolder() {
|
|
|
21990
22509
|
encoding: "utf-8",
|
|
21991
22510
|
timeout: 3e3
|
|
21992
22511
|
}).trim();
|
|
21993
|
-
if (xdgDir &&
|
|
22512
|
+
if (xdgDir && fs23.existsSync(xdgDir)) return xdgDir;
|
|
21994
22513
|
} catch {
|
|
21995
22514
|
}
|
|
21996
22515
|
}
|
|
21997
|
-
const downloads =
|
|
21998
|
-
if (
|
|
22516
|
+
const downloads = path22.join(home, "Downloads");
|
|
22517
|
+
if (fs23.existsSync(downloads)) return downloads;
|
|
21999
22518
|
return home;
|
|
22000
22519
|
}
|
|
22001
22520
|
|
|
@@ -22004,11 +22523,11 @@ function sanitizeFilename(name) {
|
|
|
22004
22523
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
22005
22524
|
}
|
|
22006
22525
|
function getUniqueFilename(dir, base, ext) {
|
|
22007
|
-
let candidate =
|
|
22008
|
-
if (!
|
|
22526
|
+
let candidate = path23.join(dir, `${base}${ext}`);
|
|
22527
|
+
if (!fs24.existsSync(candidate)) return candidate;
|
|
22009
22528
|
let i = 1;
|
|
22010
|
-
while (
|
|
22011
|
-
candidate =
|
|
22529
|
+
while (fs24.existsSync(candidate)) {
|
|
22530
|
+
candidate = path23.join(dir, `${base}-${i}${ext}`);
|
|
22012
22531
|
i++;
|
|
22013
22532
|
}
|
|
22014
22533
|
return candidate;
|
|
@@ -22018,13 +22537,13 @@ var ZipOutput = class {
|
|
|
22018
22537
|
label = "Save as .zip to Downloads";
|
|
22019
22538
|
async emit(group, options) {
|
|
22020
22539
|
const downloadsDir = getDownloadsFolder();
|
|
22021
|
-
const repoName = sanitizeFilename(
|
|
22540
|
+
const repoName = sanitizeFilename(path23.basename(group.repoPath));
|
|
22022
22541
|
const timeRange = options.timeRange;
|
|
22023
22542
|
const rangePart = timeRange?.label ?? "all";
|
|
22024
22543
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
22025
22544
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
22026
22545
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
22027
|
-
const output =
|
|
22546
|
+
const output = fs24.createWriteStream(outputPath);
|
|
22028
22547
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
22029
22548
|
const done = new Promise((resolve, reject) => {
|
|
22030
22549
|
output.on("close", resolve);
|
|
@@ -22218,15 +22737,15 @@ async function confirmExport(group, output) {
|
|
|
22218
22737
|
}
|
|
22219
22738
|
|
|
22220
22739
|
// src/sources/claude.ts
|
|
22221
|
-
import
|
|
22740
|
+
import fs25 from "fs";
|
|
22222
22741
|
import os12 from "os";
|
|
22223
|
-
import
|
|
22742
|
+
import path24 from "path";
|
|
22224
22743
|
import readline3 from "readline";
|
|
22225
22744
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
22226
22745
|
async function resolveRepoPath(projectDir) {
|
|
22227
|
-
const indexPath =
|
|
22746
|
+
const indexPath = path24.join(projectDir, "sessions-index.json");
|
|
22228
22747
|
try {
|
|
22229
|
-
const raw = await
|
|
22748
|
+
const raw = await fs25.promises.readFile(indexPath, "utf-8");
|
|
22230
22749
|
const data = JSON.parse(raw);
|
|
22231
22750
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
22232
22751
|
return data.originalPath;
|
|
@@ -22234,12 +22753,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
22234
22753
|
} catch {
|
|
22235
22754
|
}
|
|
22236
22755
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
22237
|
-
const entries = await
|
|
22756
|
+
const entries = await fs25.promises.readdir(projectDir, {
|
|
22238
22757
|
withFileTypes: true
|
|
22239
22758
|
});
|
|
22240
22759
|
for (const entry of entries) {
|
|
22241
22760
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
22242
|
-
const cwd = await extractCwdFromJsonl(
|
|
22761
|
+
const cwd = await extractCwdFromJsonl(path24.join(projectDir, entry.name));
|
|
22243
22762
|
if (cwd) {
|
|
22244
22763
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
22245
22764
|
}
|
|
@@ -22258,7 +22777,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
22258
22777
|
return null;
|
|
22259
22778
|
}
|
|
22260
22779
|
async function extractCwdFromJsonl(filePath) {
|
|
22261
|
-
const stream =
|
|
22780
|
+
const stream = fs25.createReadStream(filePath, { encoding: "utf-8" });
|
|
22262
22781
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22263
22782
|
try {
|
|
22264
22783
|
for await (const line of rl) {
|
|
@@ -22280,12 +22799,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
22280
22799
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
22281
22800
|
let entries;
|
|
22282
22801
|
try {
|
|
22283
|
-
entries = await
|
|
22802
|
+
entries = await fs25.promises.readdir(dir, { withFileTypes: true });
|
|
22284
22803
|
} catch {
|
|
22285
22804
|
return;
|
|
22286
22805
|
}
|
|
22287
22806
|
for (const entry of entries) {
|
|
22288
|
-
const fullPath =
|
|
22807
|
+
const fullPath = path24.join(dir, entry.name);
|
|
22289
22808
|
if (entry.isDirectory()) {
|
|
22290
22809
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
22291
22810
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -22307,19 +22826,19 @@ function fallbackDecode(encodedName) {
|
|
|
22307
22826
|
var ClaudeSource = class {
|
|
22308
22827
|
name = "claude";
|
|
22309
22828
|
async scan() {
|
|
22310
|
-
const baseDir =
|
|
22829
|
+
const baseDir = path24.join(os12.homedir(), ".claude", "projects");
|
|
22311
22830
|
try {
|
|
22312
|
-
await
|
|
22831
|
+
await fs25.promises.access(baseDir);
|
|
22313
22832
|
} catch {
|
|
22314
22833
|
return [];
|
|
22315
22834
|
}
|
|
22316
|
-
const projectDirs = await
|
|
22835
|
+
const projectDirs = await fs25.promises.readdir(baseDir, {
|
|
22317
22836
|
withFileTypes: true
|
|
22318
22837
|
});
|
|
22319
22838
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
22320
22839
|
const resultArrays = await Promise.all(
|
|
22321
22840
|
dirEntries.map(async (dir) => {
|
|
22322
|
-
const projectPath =
|
|
22841
|
+
const projectPath = path24.join(baseDir, dir.name);
|
|
22323
22842
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
22324
22843
|
const files = [];
|
|
22325
22844
|
await collectFiles(
|
|
@@ -22337,12 +22856,12 @@ var ClaudeSource = class {
|
|
|
22337
22856
|
};
|
|
22338
22857
|
|
|
22339
22858
|
// src/sources/codex.ts
|
|
22340
|
-
import
|
|
22859
|
+
import fs26 from "fs";
|
|
22341
22860
|
import os13 from "os";
|
|
22342
|
-
import
|
|
22861
|
+
import path25 from "path";
|
|
22343
22862
|
import readline4 from "readline";
|
|
22344
22863
|
async function parseSessionMeta2(filePath) {
|
|
22345
|
-
const stream =
|
|
22864
|
+
const stream = fs26.createReadStream(filePath, { encoding: "utf-8" });
|
|
22346
22865
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22347
22866
|
try {
|
|
22348
22867
|
for await (const line of rl) {
|
|
@@ -22367,12 +22886,12 @@ async function findJsonlFiles(dir) {
|
|
|
22367
22886
|
async function walk(d) {
|
|
22368
22887
|
let entries;
|
|
22369
22888
|
try {
|
|
22370
|
-
entries = await
|
|
22889
|
+
entries = await fs26.promises.readdir(d, { withFileTypes: true });
|
|
22371
22890
|
} catch {
|
|
22372
22891
|
return;
|
|
22373
22892
|
}
|
|
22374
22893
|
for (const entry of entries) {
|
|
22375
|
-
const full =
|
|
22894
|
+
const full = path25.join(d, entry.name);
|
|
22376
22895
|
if (entry.isDirectory()) {
|
|
22377
22896
|
await walk(full);
|
|
22378
22897
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -22386,11 +22905,11 @@ async function findJsonlFiles(dir) {
|
|
|
22386
22905
|
async function loadHistory(historyPath) {
|
|
22387
22906
|
const map = /* @__PURE__ */ new Map();
|
|
22388
22907
|
try {
|
|
22389
|
-
await
|
|
22908
|
+
await fs26.promises.access(historyPath);
|
|
22390
22909
|
} catch {
|
|
22391
22910
|
return map;
|
|
22392
22911
|
}
|
|
22393
|
-
const stream =
|
|
22912
|
+
const stream = fs26.createReadStream(historyPath, { encoding: "utf-8" });
|
|
22394
22913
|
const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
|
|
22395
22914
|
try {
|
|
22396
22915
|
for await (const line of rl) {
|
|
@@ -22417,14 +22936,14 @@ async function loadHistory(historyPath) {
|
|
|
22417
22936
|
var CodexSource = class {
|
|
22418
22937
|
name = "codex";
|
|
22419
22938
|
async scan() {
|
|
22420
|
-
const codexDir =
|
|
22421
|
-
const sessionsDir2 =
|
|
22939
|
+
const codexDir = path25.join(os13.homedir(), ".codex");
|
|
22940
|
+
const sessionsDir2 = path25.join(codexDir, "sessions");
|
|
22422
22941
|
try {
|
|
22423
|
-
await
|
|
22942
|
+
await fs26.promises.access(sessionsDir2);
|
|
22424
22943
|
} catch {
|
|
22425
22944
|
return [];
|
|
22426
22945
|
}
|
|
22427
|
-
const historyPath =
|
|
22946
|
+
const historyPath = path25.join(codexDir, "history.jsonl");
|
|
22428
22947
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
22429
22948
|
findJsonlFiles(sessionsDir2),
|
|
22430
22949
|
loadHistory(historyPath)
|
|
@@ -22447,8 +22966,8 @@ var CodexSource = class {
|
|
|
22447
22966
|
});
|
|
22448
22967
|
const historyLines = historyMap.get(meta.sessionId);
|
|
22449
22968
|
if (historyLines) {
|
|
22450
|
-
const sessionDir =
|
|
22451
|
-
const historyAbsPath =
|
|
22969
|
+
const sessionDir = path25.relative(sessionsDir2, path25.dirname(filePath));
|
|
22970
|
+
const historyAbsPath = path25.join(
|
|
22452
22971
|
sessionsDir2,
|
|
22453
22972
|
sessionDir,
|
|
22454
22973
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -22468,18 +22987,18 @@ var CodexSource = class {
|
|
|
22468
22987
|
};
|
|
22469
22988
|
|
|
22470
22989
|
// src/sources/copilotChat.ts
|
|
22471
|
-
import
|
|
22990
|
+
import fs27 from "fs";
|
|
22472
22991
|
import os14 from "os";
|
|
22473
|
-
import
|
|
22992
|
+
import path26 from "path";
|
|
22474
22993
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22475
22994
|
function vsCodeUserDirs() {
|
|
22476
22995
|
const home = os14.homedir();
|
|
22477
22996
|
const dirs = [
|
|
22478
|
-
|
|
22479
|
-
|
|
22997
|
+
path26.join(home, "Library", "Application Support", "Code", "User"),
|
|
22998
|
+
path26.join(home, ".config", "Code", "User")
|
|
22480
22999
|
];
|
|
22481
23000
|
if (process.env.APPDATA) {
|
|
22482
|
-
dirs.push(
|
|
23001
|
+
dirs.push(path26.join(process.env.APPDATA, "Code", "User"));
|
|
22483
23002
|
}
|
|
22484
23003
|
return dirs;
|
|
22485
23004
|
}
|
|
@@ -22494,7 +23013,7 @@ function uriToFsPath(uri) {
|
|
|
22494
23013
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
22495
23014
|
let raw;
|
|
22496
23015
|
try {
|
|
22497
|
-
raw = await
|
|
23016
|
+
raw = await fs27.promises.readFile(workspaceJsonPath, "utf-8");
|
|
22498
23017
|
} catch {
|
|
22499
23018
|
return null;
|
|
22500
23019
|
}
|
|
@@ -22516,10 +23035,10 @@ var CopilotChatSource = class {
|
|
|
22516
23035
|
async scan() {
|
|
22517
23036
|
const results = [];
|
|
22518
23037
|
for (const userDir of vsCodeUserDirs()) {
|
|
22519
|
-
const workspaceStorage =
|
|
23038
|
+
const workspaceStorage = path26.join(userDir, "workspaceStorage");
|
|
22520
23039
|
let hashDirs;
|
|
22521
23040
|
try {
|
|
22522
|
-
hashDirs = await
|
|
23041
|
+
hashDirs = await fs27.promises.readdir(workspaceStorage, {
|
|
22523
23042
|
withFileTypes: true
|
|
22524
23043
|
});
|
|
22525
23044
|
} catch {
|
|
@@ -22527,22 +23046,22 @@ var CopilotChatSource = class {
|
|
|
22527
23046
|
}
|
|
22528
23047
|
for (const hash of hashDirs) {
|
|
22529
23048
|
if (!hash.isDirectory()) continue;
|
|
22530
|
-
const wsRoot =
|
|
22531
|
-
const transcriptsDir =
|
|
23049
|
+
const wsRoot = path26.join(workspaceStorage, hash.name);
|
|
23050
|
+
const transcriptsDir = path26.join(
|
|
22532
23051
|
wsRoot,
|
|
22533
23052
|
"GitHub.copilot-chat",
|
|
22534
23053
|
"transcripts"
|
|
22535
23054
|
);
|
|
22536
23055
|
let transcriptEntries;
|
|
22537
23056
|
try {
|
|
22538
|
-
transcriptEntries = await
|
|
23057
|
+
transcriptEntries = await fs27.promises.readdir(transcriptsDir, {
|
|
22539
23058
|
withFileTypes: true
|
|
22540
23059
|
});
|
|
22541
23060
|
} catch {
|
|
22542
23061
|
continue;
|
|
22543
23062
|
}
|
|
22544
23063
|
const repoPath = await readWorkspaceFolder(
|
|
22545
|
-
|
|
23064
|
+
path26.join(wsRoot, "workspace.json")
|
|
22546
23065
|
);
|
|
22547
23066
|
if (!repoPath) continue;
|
|
22548
23067
|
for (const entry of transcriptEntries) {
|
|
@@ -22550,7 +23069,7 @@ var CopilotChatSource = class {
|
|
|
22550
23069
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
22551
23070
|
results.push({
|
|
22552
23071
|
sourceName: this.name,
|
|
22553
|
-
absolutePath:
|
|
23072
|
+
absolutePath: path26.join(transcriptsDir, entry.name),
|
|
22554
23073
|
repoPath,
|
|
22555
23074
|
metadata: { sessionId }
|
|
22556
23075
|
});
|
|
@@ -22590,7 +23109,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
22590
23109
|
async function filterByTimeRange(group, range) {
|
|
22591
23110
|
const results = await Promise.all(
|
|
22592
23111
|
group.files.map(
|
|
22593
|
-
(f) =>
|
|
23112
|
+
(f) => fs28.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
22594
23113
|
)
|
|
22595
23114
|
);
|
|
22596
23115
|
const filtered = [];
|
|
@@ -22617,10 +23136,10 @@ async function runInteractive() {
|
|
|
22617
23136
|
s.start(`Scanning ${source.name} logs...`);
|
|
22618
23137
|
const allFiles = await source.scan();
|
|
22619
23138
|
const allGroups = await mergeByRepo(allFiles);
|
|
22620
|
-
const repoRoot =
|
|
23139
|
+
const repoRoot = path27.resolve(repo.root);
|
|
22621
23140
|
const matching = allGroups.filter((g) => {
|
|
22622
|
-
const resolved =
|
|
22623
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
23141
|
+
const resolved = path27.resolve(g.repoPath);
|
|
23142
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path27.sep);
|
|
22624
23143
|
});
|
|
22625
23144
|
if (matching.length === 0) {
|
|
22626
23145
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -22651,7 +23170,7 @@ async function runInteractive() {
|
|
|
22651
23170
|
}
|
|
22652
23171
|
}
|
|
22653
23172
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
22654
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
23173
|
+
const envFilePaths = envFileNames.map((n) => path27.join(repoRoot, n));
|
|
22655
23174
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
22656
23175
|
const secretResult = await collectSecrets(
|
|
22657
23176
|
repoRoot,
|