hillclimb 0.9.13 → 0.9.15

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