hillclimb 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -13,8 +13,8 @@ import {
13
13
  } from "./chunk-UPXMA7RC.js";
14
14
 
15
15
  // src/main.ts
16
- import fs27 from "fs";
17
- import path26 from "path";
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 fs20 from "fs";
2717
+ import fs21 from "fs";
2718
2718
  import os10 from "os";
2719
- import path19 from "path";
2719
+ import path20 from "path";
2720
2720
 
2721
2721
  // src/agent-pending.ts
2722
2722
  import crypto4 from "crypto";
@@ -4733,6 +4733,25 @@ async function readFileContent(file) {
4733
4733
  return { kind: "error" };
4734
4734
  }
4735
4735
  }
4736
+ async function readFileBuffer(file) {
4737
+ if (file.isBinary) {
4738
+ return { kind: "binary" };
4739
+ }
4740
+ if (file.content) {
4741
+ return { kind: "bytes", content: file.content };
4742
+ }
4743
+ if (await checkBinary(file.absolutePath)) {
4744
+ return { kind: "binary" };
4745
+ }
4746
+ try {
4747
+ return {
4748
+ kind: "bytes",
4749
+ content: await fs13.promises.readFile(file.absolutePath)
4750
+ };
4751
+ } catch {
4752
+ return { kind: "error" };
4753
+ }
4754
+ }
4736
4755
 
4737
4756
  // src/middleware/large-file.ts
4738
4757
  import { constants as bufferConstants } from "buffer";
@@ -4743,36 +4762,307 @@ function getLargeFileThreshold() {
4743
4762
  }
4744
4763
  var NEWLINE = 10;
4745
4764
  var OVERSIZED_LINE_PLACEHOLDER = '{"_hillclimb_omitted":"line exceeded max string length; dropped to allow redaction"}';
4746
- function redactBufferByLine(buffer, redactLine, options = {}) {
4765
+ function scanBufferLines(buffer, redactLine, options = {}) {
4747
4766
  const maxLineBytes = options.maxLineBytes ?? MAX_STRINGIFIABLE_BYTES;
4748
4767
  const oversizedPlaceholder = options.oversizedPlaceholder ?? OVERSIZED_LINE_PLACEHOLDER;
4749
- const pieces = [];
4750
- const newlineBuf = Buffer.from([NEWLINE]);
4768
+ const end = options.end ?? buffer.length;
4769
+ const ownsFinalLine = end === buffer.length;
4770
+ const edits = [];
4751
4771
  let totalCount = 0;
4752
4772
  let oversizedLines = 0;
4753
- let start = 0;
4754
- let first = true;
4755
- for (; ; ) {
4773
+ let start = options.start ?? 0;
4774
+ while (ownsFinalLine || start < end) {
4756
4775
  const nl = buffer.indexOf(NEWLINE, start);
4757
- const end = nl === -1 ? buffer.length : nl;
4758
- const slice = buffer.subarray(start, end);
4759
- if (!first) pieces.push(newlineBuf);
4760
- first = false;
4761
- if (slice.length > maxLineBytes) {
4776
+ const lineEnd = nl === -1 ? buffer.length : nl;
4777
+ if (lineEnd - start > maxLineBytes) {
4762
4778
  oversizedLines++;
4763
- pieces.push(Buffer.from(oversizedPlaceholder, "utf-8"));
4779
+ edits.push({ start, end: lineEnd, value: oversizedPlaceholder });
4764
4780
  } else {
4765
- const { value, count } = redactLine(slice.toString("utf-8"));
4766
- totalCount += count;
4767
- pieces.push(Buffer.from(value, "utf-8"));
4781
+ const { value, count } = redactLine(
4782
+ buffer.toString("utf-8", start, lineEnd)
4783
+ );
4784
+ if (count > 0) {
4785
+ totalCount += count;
4786
+ edits.push({ start, end: lineEnd, value });
4787
+ }
4768
4788
  }
4769
4789
  if (nl === -1) break;
4770
4790
  start = nl + 1;
4771
4791
  }
4792
+ return { edits, count: totalCount, oversizedLines };
4793
+ }
4794
+ function applyLineEdits(buffer, edits) {
4795
+ if (edits.length === 0) return buffer;
4796
+ const pieces = [];
4797
+ let copied = 0;
4798
+ for (const edit of edits) {
4799
+ pieces.push(buffer.subarray(copied, edit.start));
4800
+ pieces.push(Buffer.from(edit.value, "utf-8"));
4801
+ copied = edit.end;
4802
+ }
4803
+ pieces.push(buffer.subarray(copied));
4804
+ return Buffer.concat(pieces);
4805
+ }
4806
+ function redactBufferByLine(buffer, redactLine, options = {}) {
4807
+ const { edits, count, oversizedLines } = scanBufferLines(
4808
+ buffer,
4809
+ redactLine,
4810
+ options
4811
+ );
4812
+ return { content: applyLineEdits(buffer, edits), count, oversizedLines };
4813
+ }
4814
+
4815
+ // src/middleware/pattern-prefilter.ts
4816
+ var MIN_LITERAL = 3;
4817
+ function score(req) {
4818
+ if (!req) return -1;
4819
+ let min = Number.POSITIVE_INFINITY;
4820
+ for (const s of req) min = Math.min(min, s.length);
4821
+ return min * 1e3 - req.length;
4822
+ }
4823
+ function better(a, b) {
4824
+ return score(b) > score(a) ? b : a;
4825
+ }
4826
+ function isAsciiLiteral(ch) {
4827
+ const c = ch.charCodeAt(0);
4828
+ return c >= 32 && c < 127;
4829
+ }
4830
+ var UNKNOWN = { literals: null, minLength: 0 };
4831
+ function analyzeRegex(source, flags = "") {
4832
+ if (flags.includes("v")) return UNKNOWN;
4833
+ let i = 0;
4834
+ function skipPast(close) {
4835
+ const at = source.indexOf(close, i);
4836
+ if (at === -1) throw new Error("unterminated escape");
4837
+ i = at + 1;
4838
+ }
4839
+ function flush(seq) {
4840
+ if (seq.run.length > 0) {
4841
+ seq.req = better(seq.req, [seq.run.toLowerCase()]);
4842
+ seq.run = "";
4843
+ }
4844
+ }
4845
+ function quantifier() {
4846
+ const ch = source[i];
4847
+ let min = null;
4848
+ if (ch === "*" || ch === "?") {
4849
+ min = 0;
4850
+ i++;
4851
+ } else if (ch === "+") {
4852
+ min = 1;
4853
+ i++;
4854
+ } else if (ch === "{") {
4855
+ const m = /^\{(\d+)(?:,\d*)?\}/.exec(source.slice(i));
4856
+ if (m) {
4857
+ min = Number(m[1]);
4858
+ i += m[0].length;
4859
+ }
4860
+ }
4861
+ if (min !== null && source[i] === "?") i++;
4862
+ return min;
4863
+ }
4864
+ function skipClass() {
4865
+ i++;
4866
+ while (i < source.length && source[i] !== "]") {
4867
+ if (source[i] === "\\") i++;
4868
+ i++;
4869
+ }
4870
+ i++;
4871
+ }
4872
+ function alternation() {
4873
+ const branches = [];
4874
+ let seq = { req: null, run: "", min: 0 };
4875
+ while (i < source.length && source[i] !== ")") {
4876
+ const ch = source[i];
4877
+ if (ch === "|") {
4878
+ flush(seq);
4879
+ branches.push(seq);
4880
+ seq = { req: null, run: "", min: 0 };
4881
+ i++;
4882
+ continue;
4883
+ }
4884
+ if (ch === "(") {
4885
+ i++;
4886
+ let lookaround = false;
4887
+ if (source[i] === "?") {
4888
+ const m = /^\?(?:[:=!]|<[=!]|<[A-Za-z_$][\w$]*>)/.exec(
4889
+ source.slice(i)
4890
+ );
4891
+ if (!m) throw new Error(`unsupported group at ${i}`);
4892
+ lookaround = m[0] !== "?:" && !m[0].endsWith(">");
4893
+ i += m[0].length;
4894
+ }
4895
+ const inner = alternation();
4896
+ if (source[i] !== ")") throw new Error("unbalanced group");
4897
+ i++;
4898
+ const min3 = quantifier();
4899
+ flush(seq);
4900
+ if (!lookaround) {
4901
+ seq.min += inner.min * (min3 ?? 1);
4902
+ if (min3 === null || min3 >= 1) seq.req = better(seq.req, inner.req);
4903
+ }
4904
+ continue;
4905
+ }
4906
+ if (ch === "[" || ch === ".") {
4907
+ if (ch === "[") skipClass();
4908
+ else i++;
4909
+ seq.min += quantifier() ?? 1;
4910
+ flush(seq);
4911
+ continue;
4912
+ }
4913
+ if (ch === "^" || ch === "$") {
4914
+ i++;
4915
+ quantifier();
4916
+ flush(seq);
4917
+ continue;
4918
+ }
4919
+ let lit;
4920
+ let width = 1;
4921
+ if (ch === "\\") {
4922
+ const e = source[i + 1];
4923
+ i += 2;
4924
+ if (/[A-Za-z0-9]/.test(e)) {
4925
+ if (!/[dDwWsS]/.test(e)) width = 0;
4926
+ if (e === "x") i += 2;
4927
+ else if (e === "u") {
4928
+ if (source[i] === "{") skipPast("}");
4929
+ else i += 4;
4930
+ } else if (e === "c") i += 1;
4931
+ else if (e === "k" && source[i] === "<") skipPast(">");
4932
+ else if ((e === "p" || e === "P") && source[i] === "{") skipPast("}");
4933
+ lit = null;
4934
+ } else {
4935
+ lit = e;
4936
+ }
4937
+ } else {
4938
+ lit = ch;
4939
+ i++;
4940
+ }
4941
+ const min2 = quantifier();
4942
+ seq.min += width * (min2 ?? 1);
4943
+ if (lit === null || !isAsciiLiteral(lit)) {
4944
+ flush(seq);
4945
+ } else if (min2 === null) {
4946
+ seq.run += lit;
4947
+ } else {
4948
+ if (min2 >= 1) seq.run += lit;
4949
+ flush(seq);
4950
+ }
4951
+ }
4952
+ flush(seq);
4953
+ branches.push(seq);
4954
+ let min = Number.POSITIVE_INFINITY;
4955
+ let all = [];
4956
+ for (const b of branches) {
4957
+ min = Math.min(min, b.min);
4958
+ if (!b.req) all = null;
4959
+ else all?.push(...b.req);
4960
+ }
4961
+ return { req: all ? [...new Set(all)] : null, min };
4962
+ }
4963
+ try {
4964
+ const { req, min } = alternation();
4965
+ if (i !== source.length) return UNKNOWN;
4966
+ const foldsBeyondAscii = flags.includes("i") && flags.includes("u");
4967
+ const usable = req && !foldsBeyondAscii && !req.some((s) => s.length < MIN_LITERAL);
4968
+ return { literals: usable ? req : null, minLength: min };
4969
+ } catch {
4970
+ return UNKNOWN;
4971
+ }
4972
+ }
4973
+ function isAscii(text2) {
4974
+ for (let k = 0; k < text2.length; k++) {
4975
+ if (text2.charCodeAt(k) >= 128) return false;
4976
+ }
4977
+ return true;
4978
+ }
4979
+ function buildPrefilter(literalsByRule) {
4980
+ const alwaysRun = [];
4981
+ const goto = [/* @__PURE__ */ new Map()];
4982
+ const outs = [[]];
4983
+ literalsByRule.forEach((given, rule) => {
4984
+ const lits = given?.map((lit) => lit.toLowerCase());
4985
+ if (!lits || lits.some((lit) => lit === "" || !isAscii(lit))) {
4986
+ alwaysRun.push(rule);
4987
+ return;
4988
+ }
4989
+ for (const lit of lits) {
4990
+ let s = 0;
4991
+ for (let k = 0; k < lit.length; k++) {
4992
+ const c = lit.charCodeAt(k);
4993
+ let n = goto[s].get(c);
4994
+ if (n === void 0) {
4995
+ n = goto.length;
4996
+ goto.push(/* @__PURE__ */ new Map());
4997
+ outs.push([]);
4998
+ goto[s].set(c, n);
4999
+ }
5000
+ s = n;
5001
+ }
5002
+ outs[s].push(rule);
5003
+ }
5004
+ });
5005
+ const nStates = goto.length;
5006
+ const next = new Int32Array(nStates << 7);
5007
+ const fail = new Int32Array(nStates);
5008
+ const queue = [];
5009
+ for (const [c, n] of goto[0]) {
5010
+ next[c] = n;
5011
+ queue.push(n);
5012
+ }
5013
+ for (let q = 0; q < queue.length; q++) {
5014
+ const s = queue[q];
5015
+ const f = fail[s];
5016
+ if (outs[f].length) outs[s] = [.../* @__PURE__ */ new Set([...outs[s], ...outs[f]])];
5017
+ const base = s << 7;
5018
+ const fbase = f << 7;
5019
+ for (let c = 0; c < 128; c++) next[base | c] = next[fbase | c];
5020
+ for (const [c, n] of goto[s]) {
5021
+ fail[n] = next[fbase | c];
5022
+ next[base | c] = n;
5023
+ queue.push(n);
5024
+ }
5025
+ }
5026
+ for (let s = 0; s < nStates; s++) {
5027
+ const base = s << 7;
5028
+ for (let c = 65; c <= 90; c++) next[base | c] = next[base | c + 32];
5029
+ }
5030
+ const hasOut = new Uint8Array(nStates);
5031
+ const outRules = outs.map((o, s) => {
5032
+ if (o.length === 0) return null;
5033
+ hasOut[s] = 1;
5034
+ return o;
5035
+ });
5036
+ const ruleStamp = new Uint32Array(literalsByRule.length);
5037
+ const stateStamp = new Uint32Array(nStates);
5038
+ let stamp = 0;
4772
5039
  return {
4773
- content: Buffer.concat(pieces),
4774
- count: totalCount,
4775
- oversizedLines
5040
+ alwaysRun,
5041
+ candidates(value, out) {
5042
+ stamp++;
5043
+ if (stamp === 4294967295) {
5044
+ ruleStamp.fill(0);
5045
+ stateStamp.fill(0);
5046
+ stamp = 1;
5047
+ }
5048
+ let s = 0;
5049
+ let found = false;
5050
+ for (let k = 0, n = value.length; k < n; k++) {
5051
+ const c = value.charCodeAt(k);
5052
+ s = c < 128 ? next[s << 7 | c] : 0;
5053
+ if (hasOut[s] === 1 && stateStamp[s] !== stamp) {
5054
+ stateStamp[s] = stamp;
5055
+ for (const r of outRules[s]) {
5056
+ if (ruleStamp[r] !== stamp) {
5057
+ ruleStamp[r] = stamp;
5058
+ out.push(r);
5059
+ found = true;
5060
+ }
5061
+ }
5062
+ }
5063
+ }
5064
+ if (found) out.sort((a, b) => a - b);
5065
+ }
4776
5066
  };
4777
5067
  }
4778
5068
 
@@ -13258,11 +13548,12 @@ function applyPattern(content, regex, groupCount, validate, prefixRun) {
13258
13548
  result += content.slice(lastIndex);
13259
13549
  return { value: result, count };
13260
13550
  }
13261
- function redactString(value, patterns, owningKey = null) {
13551
+ function redactStringSlow(value, patterns, owningKey = null, from = 0) {
13262
13552
  let result = value;
13263
13553
  let totalCount = 0;
13264
13554
  let lower = null;
13265
- for (const p7 of patterns) {
13555
+ for (let idx = from; idx < patterns.length; idx++) {
13556
+ const p7 = patterns[idx];
13266
13557
  if (p7.precheck) {
13267
13558
  if (lower === null) lower = result.toLowerCase();
13268
13559
  let hit = false;
@@ -13290,6 +13581,69 @@ function redactString(value, patterns, owningKey = null) {
13290
13581
  }
13291
13582
  return { value: result, count: totalCount };
13292
13583
  }
13584
+ var engines = /* @__PURE__ */ new WeakMap();
13585
+ var candidateScratch = [];
13586
+ function engineFor(patterns) {
13587
+ let engine = engines.get(patterns);
13588
+ if (!engine) {
13589
+ const minLength = new Int32Array(patterns.length);
13590
+ const literals = patterns.map((p7, idx) => {
13591
+ const facts = analyzeRegex(p7.regex.source, p7.regex.flags);
13592
+ minLength[idx] = facts.minLength;
13593
+ return p7.precheck ?? facts.literals;
13594
+ });
13595
+ engine = { prefilter: buildPrefilter(literals), minLength };
13596
+ engines.set(patterns, engine);
13597
+ }
13598
+ return engine;
13599
+ }
13600
+ function redactString(value, patterns, owningKey = null) {
13601
+ const { prefilter, minLength } = engineFor(patterns);
13602
+ const hits = candidateScratch;
13603
+ hits.length = 0;
13604
+ prefilter.candidates(value, hits);
13605
+ const always = prefilter.alwaysRun;
13606
+ let lower = null;
13607
+ let h = 0;
13608
+ let a = 0;
13609
+ while (h < hits.length || a < always.length) {
13610
+ const idx = a >= always.length || h < hits.length && hits[h] < always[a] ? hits[h++] : always[a++];
13611
+ if (value.length < minLength[idx]) continue;
13612
+ const p7 = patterns[idx];
13613
+ if (p7.precheck) {
13614
+ if (lower === null) lower = value.toLowerCase();
13615
+ let hit = false;
13616
+ for (const needle of p7.precheck) {
13617
+ if (lower.includes(needle)) {
13618
+ hit = true;
13619
+ break;
13620
+ }
13621
+ }
13622
+ if (!hit) continue;
13623
+ }
13624
+ if (p7.context) {
13625
+ const keyHit = owningKey !== null && p7.context.test(owningKey);
13626
+ if (!keyHit && (p7.keyOnlyContext || !p7.context.test(value))) continue;
13627
+ }
13628
+ const applied = applyPattern(
13629
+ value,
13630
+ p7.regex,
13631
+ p7.groupCount,
13632
+ p7.validate,
13633
+ p7.prefixRun
13634
+ );
13635
+ if (applied.count > 0) {
13636
+ const rest = redactStringSlow(
13637
+ applied.value,
13638
+ patterns,
13639
+ owningKey,
13640
+ idx + 1
13641
+ );
13642
+ return { value: rest.value, count: applied.count + rest.count };
13643
+ }
13644
+ }
13645
+ return { value, count: 0 };
13646
+ }
13293
13647
  var keyMaskCaches = /* @__PURE__ */ new WeakMap();
13294
13648
  var memoStringUnits = /* @__PURE__ */ new WeakMap();
13295
13649
  var MAX_MEMO_STRING_UNITS = 8 * 1024 * 1024;
@@ -13354,6 +13708,16 @@ function walkAndRedactAll(value, patterns, memo, owningKey = null) {
13354
13708
  }
13355
13709
  return { value, count: 0 };
13356
13710
  }
13711
+ function redactJsonlLine(line, patterns, memo) {
13712
+ if (!line.trim()) return { value: line, count: 0 };
13713
+ try {
13714
+ const parsed = JSON.parse(line);
13715
+ const walked = walkAndRedactAll(parsed, patterns, memo);
13716
+ return { value: JSON.stringify(walked.value), count: walked.count };
13717
+ } catch {
13718
+ return redactString(line, patterns);
13719
+ }
13720
+ }
13357
13721
  function processFiles(files) {
13358
13722
  const patterns = compilePatterns();
13359
13723
  const results = [];
@@ -13363,19 +13727,10 @@ function processFiles(files) {
13363
13727
  if (isJsonl) {
13364
13728
  const memo = /* @__PURE__ */ new Map();
13365
13729
  let count = 0;
13366
- const lines = content.split("\n");
13367
- const processed = lines.map((line) => {
13368
- if (!line.trim()) return line;
13369
- try {
13370
- const parsed = JSON.parse(line);
13371
- const walked = walkAndRedactAll(parsed, patterns, memo);
13372
- count += walked.count;
13373
- return JSON.stringify(walked.value);
13374
- } catch {
13375
- const r = redactString(line, patterns);
13376
- count += r.count;
13377
- return r.value;
13378
- }
13730
+ const processed = content.split("\n").map((line) => {
13731
+ const r = redactJsonlLine(line, patterns, memo);
13732
+ count += r.count;
13733
+ return r.count > 0 ? r.value : line;
13379
13734
  });
13380
13735
  totalCount = count;
13381
13736
  output = processed.join("\n");
@@ -13388,21 +13743,25 @@ function processFiles(files) {
13388
13743
  }
13389
13744
  return results;
13390
13745
  }
13746
+ function processRanges(ranges) {
13747
+ const patterns = compilePatterns();
13748
+ return ranges.map(({ index, bytes, start, end }) => {
13749
+ const memo = /* @__PURE__ */ new Map();
13750
+ const buffer = Buffer.from(
13751
+ bytes.buffer,
13752
+ bytes.byteOffset,
13753
+ bytes.byteLength
13754
+ );
13755
+ const scanned = scanBufferLines(
13756
+ buffer,
13757
+ (line) => redactJsonlLine(line, patterns, memo),
13758
+ { start, end }
13759
+ );
13760
+ return { index, ...scanned };
13761
+ });
13762
+ }
13391
13763
 
13392
13764
  // src/middleware/pattern-redact.ts
13393
- function redactPatternLine(line, patterns, memo, isJsonl) {
13394
- if (isJsonl) {
13395
- if (!line.trim()) return { value: line, count: 0 };
13396
- try {
13397
- const parsed = JSON.parse(line);
13398
- const walked = walkAndRedactAll(parsed, patterns, memo);
13399
- return { value: JSON.stringify(walked.value), count: walked.count };
13400
- } catch {
13401
- return redactString(line, patterns);
13402
- }
13403
- }
13404
- return redactString(line, patterns);
13405
- }
13406
13765
  var WORKER_COUNT = Math.min(
13407
13766
  os4.availableParallelism?.() ?? os4.cpus().length,
13408
13767
  4
@@ -13410,6 +13769,31 @@ var WORKER_COUNT = Math.min(
13410
13769
  var WORKER_THRESHOLD = 4;
13411
13770
  var WORKER_STRING_THRESHOLD = 128 * 1024;
13412
13771
  var WORKER_TIMEOUT_MS = 12e4;
13772
+ var RANGE_MIN_BYTES = 8 * 1024 * 1024;
13773
+ var NEWLINE2 = 10;
13774
+ function shareBytes(source) {
13775
+ if (source.buffer instanceof SharedArrayBuffer) return source;
13776
+ const shared = new Uint8Array(new SharedArrayBuffer(source.length));
13777
+ shared.set(source);
13778
+ return shared;
13779
+ }
13780
+ function splitRanges(index, bytes) {
13781
+ const parts = Math.min(
13782
+ WORKER_COUNT,
13783
+ Math.floor(bytes.length / RANGE_MIN_BYTES)
13784
+ );
13785
+ const ranges = [];
13786
+ let start = 0;
13787
+ for (let part = 1; part < parts; part++) {
13788
+ const target = Math.max(start, Math.floor(bytes.length / parts * part));
13789
+ const nl = bytes.indexOf(NEWLINE2, target);
13790
+ if (nl === -1 || nl + 1 >= bytes.length) break;
13791
+ ranges.push({ index, bytes, start, end: nl + 1 });
13792
+ start = nl + 1;
13793
+ }
13794
+ ranges.push({ index, bytes, start, end: bytes.length });
13795
+ return ranges;
13796
+ }
13413
13797
  function resolveWorkerPath() {
13414
13798
  return fileURLToPath(
13415
13799
  new URL(
@@ -13418,7 +13802,7 @@ function resolveWorkerPath() {
13418
13802
  )
13419
13803
  );
13420
13804
  }
13421
- function runWorker(workerPath, files, signal, timeoutMs = WORKER_TIMEOUT_MS) {
13805
+ function runWorker(workerPath, job, signal, timeoutMs = WORKER_TIMEOUT_MS) {
13422
13806
  return new Promise((resolve, reject) => {
13423
13807
  if (signal?.aborted) {
13424
13808
  reject(
@@ -13426,7 +13810,7 @@ function runWorker(workerPath, files, signal, timeoutMs = WORKER_TIMEOUT_MS) {
13426
13810
  );
13427
13811
  return;
13428
13812
  }
13429
- const worker = new Worker(workerPath, { workerData: files });
13813
+ const worker = new Worker(workerPath, { workerData: job });
13430
13814
  let settled = false;
13431
13815
  const finish = (error, results) => {
13432
13816
  if (settled) return;
@@ -13480,11 +13864,25 @@ var PatternRedactMiddleware = class {
13480
13864
  }
13481
13865
  const tasks = [];
13482
13866
  const fileMap = /* @__PURE__ */ new Map();
13483
- const passThrough = [];
13867
+ const passThrough = /* @__PURE__ */ new Map();
13484
13868
  const largeResults = /* @__PURE__ */ new Map();
13869
+ const jsonl = /* @__PURE__ */ new Map();
13485
13870
  for (let i = 0; i < group.files.length; i++) {
13486
13871
  const file = group.files[i];
13487
13872
  this.stats.filesScanned++;
13873
+ if (file.absolutePath.endsWith(".jsonl")) {
13874
+ const read2 = await readFileBuffer(file);
13875
+ if (read2.kind === "binary") {
13876
+ this.stats.binaryFiles++;
13877
+ passThrough.set(i, file);
13878
+ } else if (read2.kind === "error") {
13879
+ this.stats.filesSkipped++;
13880
+ } else {
13881
+ jsonl.set(i, read2.content);
13882
+ fileMap.set(i, file);
13883
+ }
13884
+ continue;
13885
+ }
13488
13886
  if (file.content && !file.isBinary && file.content.length > getLargeFileThreshold()) {
13489
13887
  largeResults.set(i, this.redactLargeFile(file));
13490
13888
  continue;
@@ -13492,45 +13890,67 @@ var PatternRedactMiddleware = class {
13492
13890
  const read = await readFileContent(file);
13493
13891
  if (read.kind === "binary") {
13494
13892
  this.stats.binaryFiles++;
13495
- passThrough.push({ index: i, file });
13893
+ passThrough.set(i, file);
13496
13894
  continue;
13497
13895
  }
13498
13896
  if (read.kind === "error") {
13499
13897
  this.stats.filesSkipped++;
13500
13898
  continue;
13501
13899
  }
13502
- tasks.push({
13503
- index: i,
13504
- content: read.content,
13505
- isJsonl: file.absolutePath.endsWith(".jsonl")
13506
- });
13900
+ tasks.push({ index: i, content: read.content, isJsonl: false });
13507
13901
  fileMap.set(i, file);
13508
13902
  }
13509
- let results;
13510
- if (tasks.length >= WORKER_THRESHOLD || tasks.some((task) => task.content.length >= WORKER_STRING_THRESHOLD)) {
13511
- results = await this.processWithWorkers(tasks);
13903
+ const sources2 = [...jsonl.values()];
13904
+ let output;
13905
+ if (tasks.length + sources2.length >= WORKER_THRESHOLD || tasks.some((task) => task.content.length >= WORKER_STRING_THRESHOLD) || sources2.some((source) => source.length >= WORKER_STRING_THRESHOLD)) {
13906
+ const ranges = [];
13907
+ for (const [index, source] of jsonl) {
13908
+ ranges.push(...splitRanges(index, shareBytes(source)));
13909
+ }
13910
+ output = await this.processWithWorkers(tasks, ranges);
13512
13911
  } else {
13513
- results = processFiles(tasks);
13912
+ output = {
13913
+ files: processFiles(tasks),
13914
+ ranges: processRanges(
13915
+ [...jsonl].map(([index, bytes]) => ({
13916
+ index,
13917
+ bytes,
13918
+ start: 0,
13919
+ end: bytes.length
13920
+ }))
13921
+ )
13922
+ };
13514
13923
  }
13515
13924
  const outputMap = /* @__PURE__ */ new Map();
13516
- for (const r of results) {
13517
- outputMap.set(r.index, { content: r.content, count: r.count });
13925
+ for (const r of output.files) outputMap.set(r.index, r);
13926
+ const scans = /* @__PURE__ */ new Map();
13927
+ for (const { index, edits, count, oversizedLines } of output.ranges) {
13928
+ const scan = scans.get(index);
13929
+ if (!scan) {
13930
+ scans.set(index, { edits, count, oversizedLines });
13931
+ continue;
13932
+ }
13933
+ for (const edit of edits) scan.edits.push(edit);
13934
+ scan.count += count;
13935
+ scan.oversizedLines += oversizedLines;
13518
13936
  }
13519
13937
  const newFiles = [];
13520
13938
  for (let i = 0; i < group.files.length; i++) {
13521
- const large = largeResults.get(i);
13522
- if (large) {
13523
- newFiles.push(large);
13939
+ const unchanged = largeResults.get(i) ?? passThrough.get(i);
13940
+ if (unchanged) {
13941
+ newFiles.push(unchanged);
13524
13942
  continue;
13525
13943
  }
13526
- const pt = passThrough.find((p7) => p7.index === i);
13527
- if (pt) {
13528
- newFiles.push(pt.file);
13944
+ const file = fileMap.get(i);
13945
+ if (!file) continue;
13946
+ const source = jsonl.get(i);
13947
+ const scan = scans.get(i);
13948
+ if (source && scan) {
13949
+ newFiles.push(this.applyScan(file, source, scan));
13529
13950
  continue;
13530
13951
  }
13531
13952
  const result = outputMap.get(i);
13532
- const file = fileMap.get(i);
13533
- if (!result || !file) continue;
13953
+ if (!result) continue;
13534
13954
  if (result.count > 0) {
13535
13955
  this.stats.filesRedacted++;
13536
13956
  this.stats.totalRedactions += result.count;
@@ -13544,17 +13964,31 @@ var PatternRedactMiddleware = class {
13544
13964
  }
13545
13965
  return { ...group, files: newFiles };
13546
13966
  }
13547
- // Stream-redact an oversized file straight from its buffer, one line at a time,
13548
- // so we never build a string larger than a single line. Single-threaded (no
13549
- // worker pool) — it trades parallelism for bounded memory on the one giant file.
13967
+ applyScan(file, source, scan) {
13968
+ if (scan.oversizedLines > 0) {
13969
+ appendLog(
13970
+ "warn",
13971
+ `pattern-redact: dropped ${scan.oversizedLines} oversized line(s) in ${file.absolutePath} (exceeded max string length)`
13972
+ );
13973
+ }
13974
+ if (scan.count > 0) {
13975
+ this.stats.filesRedacted++;
13976
+ this.stats.totalRedactions += scan.count;
13977
+ }
13978
+ if (scan.count === 0 && scan.oversizedLines === 0) return file;
13979
+ scan.edits.sort((a, b) => a.start - b.start);
13980
+ return { ...file, content: applyLineEdits(source, scan.edits) };
13981
+ }
13982
+ // Stream-redact an oversized non-JSONL file straight from its buffer, one line
13983
+ // at a time, so we never build a string larger than a single line.
13984
+ // Single-threaded (no worker pool) — it trades parallelism for bounded memory
13985
+ // on the one giant file.
13550
13986
  redactLargeFile(file) {
13551
13987
  const buffer = file.content;
13552
- const isJsonl = file.absolutePath.endsWith(".jsonl");
13553
13988
  const patterns = compilePatterns();
13554
- const memo = /* @__PURE__ */ new Map();
13555
13989
  const { content, count, oversizedLines } = redactBufferByLine(
13556
13990
  buffer,
13557
- (line) => redactPatternLine(line, patterns, memo, isJsonl)
13991
+ (line) => redactString(line, patterns)
13558
13992
  );
13559
13993
  if (oversizedLines > 0) {
13560
13994
  appendLog(
@@ -13571,25 +14005,32 @@ var PatternRedactMiddleware = class {
13571
14005
  }
13572
14006
  return file;
13573
14007
  }
13574
- async processWithWorkers(tasks) {
14008
+ async processWithWorkers(tasks, ranges) {
13575
14009
  const workerPath = resolveWorkerPath();
13576
14010
  const controller = new AbortController();
13577
14011
  const onAbort = () => controller.abort();
13578
14012
  this.signal?.addEventListener("abort", onAbort, { once: true });
13579
14013
  if (this.signal?.aborted) controller.abort();
13580
- const sorted = [...tasks].sort(
13581
- (a, b) => b.content.length - a.content.length
13582
- );
13583
- const buckets = Array.from(
13584
- { length: WORKER_COUNT },
13585
- () => []
13586
- );
13587
- for (let i = 0; i < sorted.length; i++) {
13588
- buckets[i % WORKER_COUNT].push(sorted[i]);
13589
- }
13590
- const pending = buckets.filter((b) => b.length > 0).map((chunk) => runWorker(workerPath, chunk, controller.signal));
14014
+ const units = [
14015
+ ...tasks.map((task) => ({ task, size: task.content.length })),
14016
+ ...ranges.map((range) => ({ range, size: range.end - range.start }))
14017
+ ].sort((a, b) => b.size - a.size);
14018
+ const jobs = Array.from({ length: WORKER_COUNT }, () => ({
14019
+ files: [],
14020
+ ranges: []
14021
+ }));
14022
+ units.forEach((unit, i) => {
14023
+ const job = jobs[i % WORKER_COUNT];
14024
+ if ("task" in unit) job.files.push(unit.task);
14025
+ else job.ranges.push(unit.range);
14026
+ });
14027
+ const pending = jobs.filter((job) => job.files.length > 0 || job.ranges.length > 0).map((job) => runWorker(workerPath, job, controller.signal));
13591
14028
  try {
13592
- return (await Promise.all(pending)).flat();
14029
+ const outputs2 = await Promise.all(pending);
14030
+ return {
14031
+ files: outputs2.flatMap((o) => o.files),
14032
+ ranges: outputs2.flatMap((o) => o.ranges)
14033
+ };
13593
14034
  } catch (error) {
13594
14035
  controller.abort();
13595
14036
  await Promise.allSettled(pending);
@@ -13661,8 +14102,20 @@ var RedactMiddleware = class {
13661
14102
  for (const file of group.files) {
13662
14103
  this.stats.filesScanned++;
13663
14104
  regex.lastIndex = 0;
14105
+ if (file.absolutePath.endsWith(".jsonl")) {
14106
+ const read2 = await readFileBuffer(file);
14107
+ if (read2.kind === "binary") {
14108
+ this.stats.binaryFiles++;
14109
+ newFiles.push(file);
14110
+ } else if (read2.kind === "error") {
14111
+ this.stats.filesSkipped++;
14112
+ } else {
14113
+ newFiles.push(this.redactBuffer(file, read2.content, regex, true));
14114
+ }
14115
+ continue;
14116
+ }
13664
14117
  if (file.content && !file.isBinary && file.content.length > getLargeFileThreshold()) {
13665
- newFiles.push(this.redactLargeFile(file, regex));
14118
+ newFiles.push(this.redactBuffer(file, file.content, regex, false));
13666
14119
  continue;
13667
14120
  }
13668
14121
  const read = await readFileContent(file);
@@ -13675,18 +14128,7 @@ var RedactMiddleware = class {
13675
14128
  this.stats.filesSkipped++;
13676
14129
  continue;
13677
14130
  }
13678
- const content = read.content;
13679
- let result;
13680
- let count;
13681
- if (file.absolutePath.endsWith(".jsonl")) {
13682
- const processed = this.redactJsonl(content, regex);
13683
- result = processed.result;
13684
- count = processed.count;
13685
- } else {
13686
- const processed = this.redactString(content, regex);
13687
- result = processed.result;
13688
- count = processed.count;
13689
- }
14131
+ const { result, count } = this.redactString(read.content, regex);
13690
14132
  if (count > 0) {
13691
14133
  this.stats.filesRedacted++;
13692
14134
  this.stats.totalRedactions += count;
@@ -13700,13 +14142,9 @@ var RedactMiddleware = class {
13700
14142
  }
13701
14143
  return { ...group, files: newFiles };
13702
14144
  }
13703
- // Stream-redact an oversized file straight from its buffer. Each line is
13704
- // redacted with the same JSON-aware (or raw) logic as redactJsonl, so the
13705
- // output matches the in-memory path exactly for files whose lines all fit in a
13706
- // string — which holds for real transcripts (largest line is a few MiB).
13707
- redactLargeFile(file, regex) {
13708
- const buffer = file.content;
13709
- const isJsonl = file.absolutePath.endsWith(".jsonl");
14145
+ // Redact a file line by line straight from its buffer. A line with no hit
14146
+ // keeps its exact bytes; a JSONL line with one is re-serialized.
14147
+ redactBuffer(file, buffer, regex, isJsonl) {
13710
14148
  const { content, count, oversizedLines } = redactBufferByLine(
13711
14149
  buffer,
13712
14150
  (line) => this.redactLine(line, regex, isJsonl)
@@ -13741,24 +14179,6 @@ var RedactMiddleware = class {
13741
14179
  const { result, count } = this.redactString(line, regex);
13742
14180
  return { value: result, count };
13743
14181
  }
13744
- redactJsonl(content, regex) {
13745
- let totalCount = 0;
13746
- const lines = content.split("\n");
13747
- const processed = lines.map((line) => {
13748
- if (!line.trim()) return line;
13749
- try {
13750
- const parsed = JSON.parse(line);
13751
- const walked = walkAndRedact(parsed, regex);
13752
- totalCount += walked.count;
13753
- return JSON.stringify(walked.value);
13754
- } catch {
13755
- const { result, count } = this.redactString(line, regex);
13756
- totalCount += count;
13757
- return result;
13758
- }
13759
- });
13760
- return { result: processed.join("\n"), count: totalCount };
13761
- }
13762
14182
  redactString(content, regex) {
13763
14183
  let count = 0;
13764
14184
  const result = content.replace(regex, () => {
@@ -14758,16 +15178,144 @@ async function recordDebugLogCompletion(args) {
14758
15178
  // src/git-traces/pending-captures.ts
14759
15179
  import { AsyncLocalStorage } from "async_hooks";
14760
15180
  import crypto7 from "crypto";
14761
- import fs19 from "fs";
15181
+ import fs20 from "fs";
14762
15182
  import os9 from "os";
14763
- import path18 from "path";
15183
+ import path19 from "path";
14764
15184
 
14765
15185
  // src/git-traces/git-ops.ts
14766
15186
  import { execFileSync as execFileSync2, spawn as spawn2, spawnSync } from "child_process";
14767
- import fs17 from "fs";
15187
+ import fs18 from "fs";
14768
15188
  import os7 from "os";
14769
- import path16 from "path";
15189
+ import path17 from "path";
14770
15190
  import { createGzip } from "zlib";
15191
+
15192
+ // src/git-traces/object-store-probe.ts
15193
+ import fs17 from "fs";
15194
+ import path16 from "path";
15195
+ var probedRepos = /* @__PURE__ */ new Set();
15196
+ var SAMPLE_FANOUT_DIRS = 8;
15197
+ var SAMPLE_READS = 20;
15198
+ var SAMPLE_READ_BYTES = 4096;
15199
+ var MAX_NAMED_OBJECTS = 3;
15200
+ function probeObjectStore(repoRoot, trigger, stderr) {
15201
+ if (probedRepos.has(repoRoot)) return;
15202
+ probedRepos.add(repoRoot);
15203
+ const started = Date.now();
15204
+ const fields = [`repo=${repoRoot}`, `trigger=${trigger}`];
15205
+ try {
15206
+ const objectsDir = resolveObjectsDir(repoRoot);
15207
+ if (!objectsDir) {
15208
+ fields.push("objectsDir=unresolved");
15209
+ return;
15210
+ }
15211
+ fields.push(`objectsDir=${objectsDir}`);
15212
+ if (typeof fs17.statfsSync === "function") {
15213
+ const stats = fs17.statfsSync(objectsDir);
15214
+ fields.push(
15215
+ `fsType=${stats.type}`,
15216
+ `freeBytes=${stats.bavail * stats.bsize}`
15217
+ );
15218
+ }
15219
+ const { estimate, sample } = sampleLooseObjects(objectsDir);
15220
+ fields.push(`looseEstimate=${estimate}`);
15221
+ const packDir = path16.join(objectsDir, "pack");
15222
+ const packFiles = fs17.existsSync(packDir) ? fs17.readdirSync(packDir) : [];
15223
+ fields.push(
15224
+ `packs=${packFiles.filter((name) => name.endsWith(".pack")).length}`,
15225
+ `promisorPacks=${packFiles.filter((name) => name.endsWith(".promisor")).length}`,
15226
+ `alternates=${fs17.existsSync(path16.join(objectsDir, "info", "alternates")) ? "yes" : "no"}`
15227
+ );
15228
+ fields.push(...timeSampleReads(sample));
15229
+ for (const sha of namedObjects(stderr)) {
15230
+ const loose = path16.join(objectsDir, sha.slice(0, 2), sha.slice(2));
15231
+ const stats = fs17.statSync(loose, { throwIfNoEntry: false });
15232
+ fields.push(
15233
+ stats ? `named=${sha.slice(0, 8)}(size=${stats.size},blocks=${stats.blocks})` : `named=${sha.slice(0, 8)}(no loose file)`
15234
+ );
15235
+ }
15236
+ } catch (err) {
15237
+ fields.push(
15238
+ `probeError=${err instanceof Error ? err.message : String(err)}`
15239
+ );
15240
+ } finally {
15241
+ fields.push(`probeMs=${Date.now() - started}`);
15242
+ appendLog("info", `git-traces: object store probe (${fields.join(", ")})`);
15243
+ }
15244
+ }
15245
+ function resolveObjectsDir(repoRoot) {
15246
+ let gitDir = path16.join(repoRoot, ".git");
15247
+ const stats = fs17.statSync(gitDir, { throwIfNoEntry: false });
15248
+ if (!stats) return null;
15249
+ if (stats.isFile()) {
15250
+ const match = /^gitdir:\s*(.+)$/m.exec(fs17.readFileSync(gitDir, "utf-8"));
15251
+ if (!match) return null;
15252
+ gitDir = path16.resolve(repoRoot, match[1].trim());
15253
+ }
15254
+ const commonDir = path16.join(gitDir, "commondir");
15255
+ if (fs17.existsSync(commonDir)) {
15256
+ gitDir = path16.resolve(gitDir, fs17.readFileSync(commonDir, "utf-8").trim());
15257
+ }
15258
+ return path16.join(gitDir, "objects");
15259
+ }
15260
+ function sampleLooseObjects(objectsDir) {
15261
+ const fanout = fs17.readdirSync(objectsDir).filter((name) => /^[0-9a-f]{2}$/.test(name)).sort().slice(0, SAMPLE_FANOUT_DIRS);
15262
+ let counted = 0;
15263
+ const sample = [];
15264
+ for (const dir of fanout) {
15265
+ const files = fs17.readdirSync(path16.join(objectsDir, dir));
15266
+ counted += files.length;
15267
+ for (const file of files) {
15268
+ if (sample.length >= SAMPLE_READS) break;
15269
+ sample.push(path16.join(objectsDir, dir, file));
15270
+ }
15271
+ }
15272
+ return {
15273
+ estimate: fanout.length ? Math.round(counted / fanout.length * 256) : 0,
15274
+ sample
15275
+ };
15276
+ }
15277
+ function timeSampleReads(sample) {
15278
+ let dataless = 0;
15279
+ let readErrors = 0;
15280
+ let firstReadError;
15281
+ let totalMs = 0;
15282
+ let maxMs = 0;
15283
+ const buffer = Buffer.alloc(SAMPLE_READ_BYTES);
15284
+ for (const file of sample) {
15285
+ const readStarted = performance.now();
15286
+ try {
15287
+ const stats = fs17.statSync(file);
15288
+ if (stats.size > 0 && stats.blocks === 0) dataless++;
15289
+ const fd = fs17.openSync(file, "r");
15290
+ try {
15291
+ fs17.readSync(fd, buffer, 0, SAMPLE_READ_BYTES, 0);
15292
+ } finally {
15293
+ fs17.closeSync(fd);
15294
+ }
15295
+ } catch (err) {
15296
+ readErrors++;
15297
+ firstReadError ??= err instanceof Error ? err.message : String(err);
15298
+ }
15299
+ const elapsed = performance.now() - readStarted;
15300
+ totalMs += elapsed;
15301
+ maxMs = Math.max(maxMs, elapsed);
15302
+ }
15303
+ const fields = [
15304
+ `sampled=${sample.length}`,
15305
+ `dataless=${dataless}`,
15306
+ `readMsMax=${maxMs.toFixed(1)}`,
15307
+ `readMsTotal=${totalMs.toFixed(1)}`,
15308
+ `readErrors=${readErrors}`
15309
+ ];
15310
+ if (firstReadError) fields.push(`firstReadError=${firstReadError}`);
15311
+ return fields;
15312
+ }
15313
+ function namedObjects(stderr) {
15314
+ const shas = new Set(stderr?.match(/\b[0-9a-f]{40}\b/g) ?? []);
15315
+ return [...shas].slice(0, MAX_NAMED_OBJECTS);
15316
+ }
15317
+
15318
+ // src/git-traces/git-ops.ts
14771
15319
  var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
14772
15320
  var DEFAULT_BUNDLE_TIMEOUT_MS = 3e5;
14773
15321
  var FULL_TREE_SCAN_TIMEOUT_MS = 3e5;
@@ -14958,8 +15506,8 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
14958
15506
  ]);
14959
15507
  var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
14960
15508
  function isExcludedSnapshotPath(filePath) {
14961
- if (EXCLUDED_SNAPSHOT_BASENAMES.has(path16.basename(filePath))) return true;
14962
- return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path16.extname(filePath).toLowerCase());
15509
+ if (EXCLUDED_SNAPSHOT_BASENAMES.has(path17.basename(filePath))) return true;
15510
+ return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path17.extname(filePath).toLowerCase());
14963
15511
  }
14964
15512
  function isBinaryBuffer(buffer) {
14965
15513
  return buffer.includes(0);
@@ -14980,16 +15528,16 @@ function readTreeBlobHead(repoRoot, sha) {
14980
15528
  function readWorkingFileHead(absPath) {
14981
15529
  let fd = null;
14982
15530
  try {
14983
- fd = fs17.openSync(absPath, "r");
15531
+ fd = fs18.openSync(absPath, "r");
14984
15532
  const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
14985
- const bytesRead = fs17.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
15533
+ const bytesRead = fs18.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
14986
15534
  return buffer.subarray(0, bytesRead);
14987
15535
  } catch {
14988
15536
  return null;
14989
15537
  } finally {
14990
15538
  if (fd !== null) {
14991
15539
  try {
14992
- fs17.closeSync(fd);
15540
+ fs18.closeSync(fd);
14993
15541
  } catch {
14994
15542
  }
14995
15543
  }
@@ -15042,11 +15590,19 @@ function formatGitFailure(command, failure, err) {
15042
15590
  }
15043
15591
  return `${command} failed${details.length ? ` (${details.join("; ")})` : ""}`;
15044
15592
  }
15045
- function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS) {
15593
+ function formatGitTimeout(command, failure, timeoutMs, elapsedMs) {
15594
+ const details = [];
15595
+ if (elapsedMs !== void 0) details.push(`elapsed=${elapsedMs}ms`);
15596
+ if (failure.signal) details.push(`signal=${failure.signal}`);
15597
+ const stderr = outputToString(failure.stderr);
15598
+ if (stderr) details.push(`stderr=${truncateOutput(stderr)}`);
15599
+ return `${command} timed out after ${timeoutMs}ms${details.length ? ` (${details.join("; ")})` : ""}`;
15600
+ }
15601
+ function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS, elapsedMs) {
15046
15602
  const failure = err;
15047
15603
  const command = formatGitCommand(args);
15048
15604
  const isTimeout = failure.code === "ETIMEDOUT";
15049
- const message = isTimeout ? `${command} timed out after ${timeoutMs}ms` : failure.code === "ENOBUFS" ? `${command} output exceeded the ${formatSize(failure.maxBufferBytes ?? EXEC_OPTS.maxBuffer)} limit` : formatGitFailure(command, failure, err);
15605
+ const message = isTimeout ? formatGitTimeout(command, failure, timeoutMs, elapsedMs) : failure.code === "ENOBUFS" ? `${command} output exceeded the ${formatSize(failure.maxBufferBytes ?? EXEC_OPTS.maxBuffer)} limit` : formatGitFailure(command, failure, err);
15050
15606
  const wrapped = new Error(message);
15051
15607
  wrapped.isGitTimeout = isTimeout;
15052
15608
  wrapped.gitExitStatus = failure.code || failure.signal ? void 0 : failure.status;
@@ -15078,6 +15634,7 @@ function isGitTimeoutError(err) {
15078
15634
  }
15079
15635
  function gitBuffer(repoRoot, args, options = {}) {
15080
15636
  const timeout = options.timeoutMs ?? EXEC_OPTS.timeout;
15637
+ const started = Date.now();
15081
15638
  try {
15082
15639
  return execFileSync2("git", args, {
15083
15640
  cwd: repoRoot,
@@ -15088,7 +15645,15 @@ function gitBuffer(repoRoot, args, options = {}) {
15088
15645
  timeout
15089
15646
  });
15090
15647
  } catch (err) {
15091
- throw createGitError(args, err, timeout);
15648
+ const wrapped = createGitError(args, err, timeout, Date.now() - started);
15649
+ if (wrapped.isGitTimeout || wrapped.isGitMissingObject) {
15650
+ probeObjectStore(
15651
+ repoRoot,
15652
+ wrapped.isGitTimeout ? "timeout" : "missing-object",
15653
+ wrapped.gitStderr
15654
+ );
15655
+ }
15656
+ throw wrapped;
15092
15657
  }
15093
15658
  }
15094
15659
  function git(repoRoot, args) {
@@ -15126,23 +15691,23 @@ function withPrivateIndex(repoRoot, run) {
15126
15691
  let tmpDir = null;
15127
15692
  let copy = null;
15128
15693
  try {
15129
- const indexPath = path16.resolve(
15694
+ const indexPath = path17.resolve(
15130
15695
  repoRoot,
15131
15696
  git(repoRoot, ["rev-parse", "--git-path", "index"])
15132
15697
  );
15133
- tmpDir = fs17.mkdtempSync(path16.join(os7.tmpdir(), "hillclimb-index-"));
15134
- copy = path16.join(tmpDir, "index");
15135
- const fd = fs17.openSync(indexPath, "r");
15698
+ tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-index-"));
15699
+ copy = path17.join(tmpDir, "index");
15700
+ const fd = fs18.openSync(indexPath, "r");
15136
15701
  let stat;
15137
15702
  let data;
15138
15703
  try {
15139
- stat = fs17.fstatSync(fd);
15140
- data = fs17.readFileSync(fd);
15704
+ stat = fs18.fstatSync(fd);
15705
+ data = fs18.readFileSync(fd);
15141
15706
  } finally {
15142
- fs17.closeSync(fd);
15707
+ fs18.closeSync(fd);
15143
15708
  }
15144
- fs17.writeFileSync(copy, data);
15145
- fs17.utimesSync(copy, stat.atime, stat.mtime);
15709
+ fs18.writeFileSync(copy, data);
15710
+ fs18.utimesSync(copy, stat.atime, stat.mtime);
15146
15711
  } catch (err) {
15147
15712
  const missing = err.code === "ENOENT";
15148
15713
  appendLog(
@@ -15154,7 +15719,7 @@ function withPrivateIndex(repoRoot, run) {
15154
15719
  try {
15155
15720
  return run(copy ? privateIndexEnv(copy) : null);
15156
15721
  } finally {
15157
- if (tmpDir) fs17.rmSync(tmpDir, { recursive: true, force: true });
15722
+ if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
15158
15723
  }
15159
15724
  }
15160
15725
  function privateIndexEnv(indexPath) {
@@ -15484,7 +16049,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
15484
16049
  options.omittedFiles?.push(...omittedFiles);
15485
16050
  return timeCaptureStage(repoRoot, "tracked-tree-filter", () => {
15486
16051
  if (omittedFiles.length === 0) return treeSha;
15487
- const tmpIndex = path16.join(
16052
+ const tmpIndex = path17.join(
15488
16053
  os7.tmpdir(),
15489
16054
  `hillclimb-filter-${Date.now()}-${process.pid}`
15490
16055
  );
@@ -15499,7 +16064,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
15499
16064
  return gitWithEnv(repoRoot, ["write-tree"], env);
15500
16065
  } finally {
15501
16066
  try {
15502
- fs17.unlinkSync(tmpIndex);
16067
+ fs18.unlinkSync(tmpIndex);
15503
16068
  } catch {
15504
16069
  }
15505
16070
  }
@@ -15524,8 +16089,8 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
15524
16089
  if (!relPath) continue;
15525
16090
  candidates++;
15526
16091
  try {
15527
- const absPath = path16.join(repoRoot, relPath);
15528
- const stat = fs17.lstatSync(absPath);
16092
+ const absPath = path17.join(repoRoot, relPath);
16093
+ const stat = fs18.lstatSync(absPath);
15529
16094
  const reason = classifyOmission(
15530
16095
  relPath,
15531
16096
  stat.size,
@@ -15556,11 +16121,11 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
15556
16121
  `git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
15557
16122
  );
15558
16123
  if (kept.length === 0) return null;
15559
- const tmpDir = fs17.mkdtempSync(path16.join(os7.tmpdir(), "hillclimb-untracked-"));
16124
+ const tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-untracked-"));
15560
16125
  const env = {
15561
16126
  ...process.env,
15562
16127
  LC_ALL: "C",
15563
- GIT_INDEX_FILE: path16.join(tmpDir, "index")
16128
+ GIT_INDEX_FILE: path17.join(tmpDir, "index")
15564
16129
  };
15565
16130
  try {
15566
16131
  timeCaptureStage(
@@ -15588,7 +16153,7 @@ function buildUntrackedTree(repoRoot, options = {}, attempt = 1) {
15588
16153
  return buildUntrackedTree(repoRoot, options, attempt + 1);
15589
16154
  } finally {
15590
16155
  try {
15591
- fs17.rmSync(tmpDir, { recursive: true, force: true });
16156
+ fs18.rmSync(tmpDir, { recursive: true, force: true });
15592
16157
  } catch {
15593
16158
  }
15594
16159
  }
@@ -15634,10 +16199,10 @@ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
15634
16199
  `git-traces: tree assembly reused (repo=${repoRoot})`
15635
16200
  );
15636
16201
  return previous.snapshotTreeSha;
15637
- } catch {
16202
+ } catch (err) {
15638
16203
  appendLog(
15639
16204
  "info",
15640
- `git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot})`
16205
+ `git-traces: tree assembly cache unavailable; rebuilding (repo=${repoRoot}): ${err instanceof Error ? err.message : String(err)}`
15641
16206
  );
15642
16207
  }
15643
16208
  }
@@ -15659,7 +16224,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
15659
16224
  if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
15660
16225
  return filteredTrackedTree;
15661
16226
  if (filteredTrackedTree === EMPTY_TREE_SHA) return untrackedTree;
15662
- const tmpIndex = path16.join(
16227
+ const tmpIndex = path17.join(
15663
16228
  os7.tmpdir(),
15664
16229
  `hillclimb-index-${Date.now()}-${process.pid}`
15665
16230
  );
@@ -15688,7 +16253,7 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
15688
16253
  );
15689
16254
  } finally {
15690
16255
  try {
15691
- fs17.unlinkSync(tmpIndex);
16256
+ fs18.unlinkSync(tmpIndex);
15692
16257
  } catch {
15693
16258
  }
15694
16259
  }
@@ -15703,10 +16268,10 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
15703
16268
  pinRef(repoRoot, orphanRef, orphanCommit);
15704
16269
  let tmpDir;
15705
16270
  try {
15706
- tmpDir = fs17.mkdtempSync(
15707
- path16.join(os7.tmpdir(), `hillclimb-bundle-${process.pid}-`)
16271
+ tmpDir = fs18.mkdtempSync(
16272
+ path17.join(os7.tmpdir(), `hillclimb-bundle-${process.pid}-`)
15708
16273
  );
15709
- const tmpFile = path16.join(tmpDir, "baseline.bundle");
16274
+ const tmpFile = path17.join(tmpDir, "baseline.bundle");
15710
16275
  timeCaptureStage(
15711
16276
  repoRoot,
15712
16277
  "bundle-create",
@@ -15714,7 +16279,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
15714
16279
  timeoutMs: resolveGitCommandTimeoutMs(DEFAULT_BUNDLE_TIMEOUT_MS)
15715
16280
  })
15716
16281
  );
15717
- const bundle = fs17.readFileSync(tmpFile);
16282
+ const bundle = fs18.readFileSync(tmpFile);
15718
16283
  appendLog(
15719
16284
  "info",
15720
16285
  `git-traces: bundle inventory (repo=${repoRoot}, bytes=${bundle.length})`
@@ -15722,7 +16287,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
15722
16287
  return bundle;
15723
16288
  } finally {
15724
16289
  try {
15725
- if (tmpDir) fs17.rmSync(tmpDir, { recursive: true, force: true });
16290
+ if (tmpDir) fs18.rmSync(tmpDir, { recursive: true, force: true });
15726
16291
  } catch {
15727
16292
  }
15728
16293
  deleteRef(repoRoot, orphanRef);
@@ -15731,6 +16296,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
15731
16296
  var MAX_PATCH_GZ_BYTES = 500 * 1024 * 1024;
15732
16297
  function gitGzipStream(repoRoot, args, maxGzBytes) {
15733
16298
  return new Promise((resolve, reject) => {
16299
+ const started = Date.now();
15734
16300
  const child = spawn2("git", args, {
15735
16301
  cwd: repoRoot,
15736
16302
  stdio: ["ignore", "pipe", "pipe"],
@@ -15759,7 +16325,18 @@ function gitGzipStream(repoRoot, args, maxGzBytes) {
15759
16325
  settled = true;
15760
16326
  clearTimeout(timer);
15761
16327
  if (timedOut) {
15762
- reject(createGitError(args, { code: "ETIMEDOUT" }));
16328
+ const wrapped = createGitError(
16329
+ args,
16330
+ {
16331
+ code: "ETIMEDOUT",
16332
+ signal: exitSignal,
16333
+ stderr: Buffer.concat(stderrChunks)
16334
+ },
16335
+ GIT_COMMAND_TIMEOUT_MS,
16336
+ Date.now() - started
16337
+ );
16338
+ probeObjectStore(repoRoot, "timeout", wrapped.gitStderr);
16339
+ reject(wrapped);
15763
16340
  } else if (overflowed) {
15764
16341
  reject(
15765
16342
  createGitError(args, { code: "ENOBUFS", maxBufferBytes: maxGzBytes })
@@ -16021,9 +16598,9 @@ function parseCommitFiles(repoRoot, sha) {
16021
16598
  oldPath
16022
16599
  });
16023
16600
  } else {
16024
- const path27 = parts[parts.length - 1];
16025
- indexByPath.set(path27, files.length);
16026
- files.push({ path: path27, status, additions: 0, deletions: 0 });
16601
+ const path28 = parts[parts.length - 1];
16602
+ indexByPath.set(path28, files.length);
16603
+ files.push({ path: path28, status, additions: 0, deletions: 0 });
16027
16604
  }
16028
16605
  }
16029
16606
  for (const line of numstat.split("\n")) {
@@ -16107,11 +16684,11 @@ function countScopedTurnTreeRefs(repoRoot, sessionId, epochPrefix3) {
16107
16684
 
16108
16685
  // src/git-traces/session-state.ts
16109
16686
  import crypto6 from "crypto";
16110
- import fs18 from "fs";
16687
+ import fs19 from "fs";
16111
16688
  import os8 from "os";
16112
- import path17 from "path";
16689
+ import path18 from "path";
16113
16690
  var CURRENT_SCHEMA_VERSION3 = 3;
16114
- var DEFAULT_STATE_DIR2 = path17.join(os8.homedir(), ".hillclimb", "git-traces");
16691
+ var DEFAULT_STATE_DIR2 = path18.join(os8.homedir(), ".hillclimb", "git-traces");
16115
16692
  var LOCK_RETRIES2 = 120;
16116
16693
  var LOCK_RETRY_DELAY_MS2 = 500;
16117
16694
  var DEFAULT_LIVE_OWNER_MAX_WAIT_MS2 = 10 * 60 * 1e3;
@@ -16136,16 +16713,16 @@ function stateDir3() {
16136
16713
  }
16137
16714
  function stateFileForRepo(repoRoot, tool, sessionId) {
16138
16715
  const hash = crypto6.createHash("sha256").update(
16139
- sessionId ? `${path17.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path17.resolve(repoRoot)}\0${tool}`
16716
+ sessionId ? `${path18.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path18.resolve(repoRoot)}\0${tool}`
16140
16717
  ).digest("hex").slice(0, 16);
16141
- return path17.join(stateDir3(), `${hash}.json`);
16718
+ return path18.join(stateDir3(), `${hash}.json`);
16142
16719
  }
16143
16720
  function lockFileForRepo(repoRoot, tool, sessionId) {
16144
16721
  return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
16145
16722
  }
16146
16723
  async function readStateFile(file) {
16147
16724
  try {
16148
- const raw = await fs18.promises.readFile(file, "utf-8");
16725
+ const raw = await fs19.promises.readFile(file, "utf-8");
16149
16726
  const parsed = JSON.parse(raw);
16150
16727
  if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
16151
16728
  return null;
@@ -16159,7 +16736,7 @@ async function readStoredStateFile(file) {
16159
16736
  const state = await readStateFile(file);
16160
16737
  if (!state) return null;
16161
16738
  try {
16162
- return { state, mtimeMs: (await fs18.promises.stat(file)).mtimeMs };
16739
+ return { state, mtimeMs: (await fs19.promises.stat(file)).mtimeMs };
16163
16740
  } catch {
16164
16741
  return null;
16165
16742
  }
@@ -16167,26 +16744,26 @@ async function readStoredStateFile(file) {
16167
16744
  async function listScopedSessionStates(repoRoot, tool) {
16168
16745
  let entries;
16169
16746
  try {
16170
- entries = await fs18.promises.readdir(stateDir3(), { withFileTypes: true });
16747
+ entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
16171
16748
  } catch {
16172
16749
  return [];
16173
16750
  }
16174
16751
  const states = [];
16175
16752
  for (const entry of entries) {
16176
16753
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
16177
- const file = path17.join(stateDir3(), entry.name);
16754
+ const file = path18.join(stateDir3(), entry.name);
16178
16755
  const state = await readStateFile(file);
16179
16756
  if (!state) continue;
16180
16757
  if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
16181
16758
  continue;
16182
16759
  }
16183
- if (path17.resolve(state.repoRoot) !== path17.resolve(repoRoot)) continue;
16184
- if (path17.resolve(file) !== path17.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
16760
+ if (path18.resolve(state.repoRoot) !== path18.resolve(repoRoot)) continue;
16761
+ if (path18.resolve(file) !== path18.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
16185
16762
  continue;
16186
16763
  }
16187
16764
  let mtimeMs = 0;
16188
16765
  try {
16189
- mtimeMs = (await fs18.promises.stat(file)).mtimeMs;
16766
+ mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
16190
16767
  } catch {
16191
16768
  continue;
16192
16769
  }
@@ -16197,26 +16774,26 @@ async function listScopedSessionStates(repoRoot, tool) {
16197
16774
  async function listSessionStatesForSession(tool, sessionId) {
16198
16775
  let entries;
16199
16776
  try {
16200
- entries = await fs18.promises.readdir(stateDir3(), { withFileTypes: true });
16777
+ entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
16201
16778
  } catch {
16202
16779
  return [];
16203
16780
  }
16204
16781
  const states = [];
16205
16782
  for (const entry of entries) {
16206
16783
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
16207
- const file = path17.join(stateDir3(), entry.name);
16784
+ const file = path18.join(stateDir3(), entry.name);
16208
16785
  const state = await readStateFile(file);
16209
16786
  if (!state) continue;
16210
16787
  if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
16211
16788
  continue;
16212
16789
  }
16213
16790
  if (state.sessionId !== sessionId) continue;
16214
- if (path17.resolve(file) !== path17.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
16791
+ if (path18.resolve(file) !== path18.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
16215
16792
  continue;
16216
16793
  }
16217
16794
  let mtimeMs = 0;
16218
16795
  try {
16219
- mtimeMs = (await fs18.promises.stat(file)).mtimeMs;
16796
+ mtimeMs = (await fs19.promises.stat(file)).mtimeMs;
16220
16797
  } catch {
16221
16798
  continue;
16222
16799
  }
@@ -16265,17 +16842,17 @@ async function writeLegacySessionState(state, tool) {
16265
16842
  );
16266
16843
  }
16267
16844
  async function writeStateFile(file, state) {
16268
- await fs18.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
16845
+ await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
16269
16846
  const tmp = `${file}.tmp`;
16270
- await fs18.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
16847
+ await fs19.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
16271
16848
  mode: 384
16272
16849
  });
16273
- await fs18.promises.rename(tmp, file);
16850
+ await fs19.promises.rename(tmp, file);
16274
16851
  }
16275
16852
  async function touchSessionState(repoRoot, tool, sessionId) {
16276
16853
  const now = /* @__PURE__ */ new Date();
16277
16854
  try {
16278
- await fs18.promises.utimes(
16855
+ await fs19.promises.utimes(
16279
16856
  stateFileForRepo(repoRoot, tool, sessionId),
16280
16857
  now,
16281
16858
  now
@@ -16285,7 +16862,7 @@ async function touchSessionState(repoRoot, tool, sessionId) {
16285
16862
  }
16286
16863
  async function statSessionStateMtime(repoRoot, tool, sessionId) {
16287
16864
  try {
16288
- const stat = await fs18.promises.stat(
16865
+ const stat = await fs19.promises.stat(
16289
16866
  stateFileForRepo(repoRoot, tool, sessionId)
16290
16867
  );
16291
16868
  return stat.mtimeMs;
@@ -16295,7 +16872,7 @@ async function statSessionStateMtime(repoRoot, tool, sessionId) {
16295
16872
  }
16296
16873
  async function deleteStateFile(file) {
16297
16874
  try {
16298
- await fs18.promises.unlink(file);
16875
+ await fs19.promises.unlink(file);
16299
16876
  } catch {
16300
16877
  }
16301
16878
  }
@@ -16334,7 +16911,7 @@ async function acquireLock3(repoRoot, tool, sessionId, retries, delayMs, options
16334
16911
  }
16335
16912
  async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, delayMs, options = {}) {
16336
16913
  const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
16337
- await fs18.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
16914
+ await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
16338
16915
  const bounded = retries !== void 0 || delayMs !== void 0;
16339
16916
  const attempts = retries ?? LOCK_RETRIES2;
16340
16917
  const delay = delayMs ?? LOCK_RETRY_DELAY_MS2;
@@ -16344,9 +16921,9 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
16344
16921
  let extensionLogged = false;
16345
16922
  for (let i = 0; ; i++) {
16346
16923
  try {
16347
- const fd = await fs18.promises.open(
16924
+ const fd = await fs19.promises.open(
16348
16925
  lockPath,
16349
- fs18.constants.O_CREAT | fs18.constants.O_EXCL | fs18.constants.O_WRONLY
16926
+ fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
16350
16927
  );
16351
16928
  await fd.write(String(process.pid));
16352
16929
  await fd.close();
@@ -16355,7 +16932,7 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
16355
16932
  if (err.code !== "EEXIST") throw err;
16356
16933
  let regularLockFile = false;
16357
16934
  try {
16358
- regularLockFile = (await fs18.promises.lstat(lockPath)).isFile();
16935
+ regularLockFile = (await fs19.promises.lstat(lockPath)).isFile();
16359
16936
  } catch (statErr) {
16360
16937
  if (statErr.code === "ENOENT") {
16361
16938
  i--;
@@ -16417,21 +16994,21 @@ async function acquireLockWithoutTiming(repoRoot, tool, sessionId, retries, dela
16417
16994
  }
16418
16995
  async function releaseLock3(repoRoot, tool, sessionId) {
16419
16996
  try {
16420
- await fs18.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
16997
+ await fs19.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
16421
16998
  } catch {
16422
16999
  }
16423
17000
  }
16424
17001
  async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS3, now = Date.now()) {
16425
17002
  let entries;
16426
17003
  try {
16427
- entries = await fs18.promises.readdir(stateDir3(), { withFileTypes: true });
17004
+ entries = await fs19.promises.readdir(stateDir3(), { withFileTypes: true });
16428
17005
  } catch {
16429
17006
  return 0;
16430
17007
  }
16431
17008
  let removed = 0;
16432
17009
  for (const entry of entries) {
16433
17010
  if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
16434
- const file = path17.join(stateDir3(), entry.name);
17011
+ const file = path18.join(stateDir3(), entry.name);
16435
17012
  if (await reapLockIfStale(file, {
16436
17013
  maxAgeMs: ttlMs,
16437
17014
  preserveLiveOwner: true,
@@ -16486,12 +17063,12 @@ function withPendingCapture(record, work) {
16486
17063
  return activeCapture.run({ record, save: () => saveCapture(record) }, work);
16487
17064
  }
16488
17065
  function queueDirectory(repoRoot, tool, sessionId) {
16489
- const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path18.join(os9.homedir(), ".hillclimb", "git-traces");
16490
- const key = crypto7.createHash("sha256").update(`${path18.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
16491
- return path18.join(stateDir4, "pending-v1", key);
17066
+ const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces");
17067
+ const key = crypto7.createHash("sha256").update(`${path19.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
17068
+ return path19.join(stateDir4, "pending-v1", key);
16492
17069
  }
16493
17070
  function captureDirectory(record) {
16494
- return path18.join(
17071
+ return path19.join(
16495
17072
  queueDirectory(record.repoRoot, record.tool, record.sessionId),
16496
17073
  record.id
16497
17074
  );
@@ -16499,20 +17076,20 @@ function captureDirectory(record) {
16499
17076
  function writeDurable(file, body) {
16500
17077
  const temporary = `${file}.${process.pid}.tmp`;
16501
17078
  try {
16502
- const descriptor = fs19.openSync(temporary, "w", 384);
17079
+ const descriptor = fs20.openSync(temporary, "w", 384);
16503
17080
  try {
16504
- fs19.writeFileSync(descriptor, body);
16505
- fs19.fsyncSync(descriptor);
17081
+ fs20.writeFileSync(descriptor, body);
17082
+ fs20.fsyncSync(descriptor);
16506
17083
  } finally {
16507
- fs19.closeSync(descriptor);
17084
+ fs20.closeSync(descriptor);
16508
17085
  }
16509
- fs19.renameSync(temporary, file);
17086
+ fs20.renameSync(temporary, file);
16510
17087
  try {
16511
- const directory = fs19.openSync(path18.dirname(file), "r");
17088
+ const directory = fs20.openSync(path19.dirname(file), "r");
16512
17089
  try {
16513
- fs19.fsyncSync(directory);
17090
+ fs20.fsyncSync(directory);
16514
17091
  } finally {
16515
- fs19.closeSync(directory);
17092
+ fs20.closeSync(directory);
16516
17093
  }
16517
17094
  } catch (err) {
16518
17095
  if (!["EINVAL", "ENOTSUP", "EBADF", "EPERM", "EISDIR"].includes(
@@ -16521,7 +17098,7 @@ function writeDurable(file, body) {
16521
17098
  throw err;
16522
17099
  }
16523
17100
  } finally {
16524
- if (fs19.existsSync(temporary)) fs19.unlinkSync(temporary);
17101
+ if (fs20.existsSync(temporary)) fs20.unlinkSync(temporary);
16525
17102
  }
16526
17103
  }
16527
17104
  function saveCapture(record) {
@@ -16532,7 +17109,7 @@ function saveCapture(record) {
16532
17109
  let status = "failed";
16533
17110
  try {
16534
17111
  writeDurable(
16535
- path18.join(captureDirectory(record), "capture.json"),
17112
+ path19.join(captureDirectory(record), "capture.json"),
16536
17113
  JSON.stringify(record)
16537
17114
  );
16538
17115
  status = "ok";
@@ -16544,7 +17121,7 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
16544
17121
  const directory = queueDirectory(repoRoot, tool, sessionId);
16545
17122
  let entries;
16546
17123
  try {
16547
- entries = fs19.readdirSync(directory, { withFileTypes: true });
17124
+ entries = fs20.readdirSync(directory, { withFileTypes: true });
16548
17125
  } catch (err) {
16549
17126
  if (err.code === "ENOENT") return [];
16550
17127
  throw err;
@@ -16552,10 +17129,10 @@ function listPendingCaptures2(repoRoot, tool, sessionId) {
16552
17129
  const records = [];
16553
17130
  for (const entry of entries) {
16554
17131
  if (!entry.isDirectory() || !/^[0-9a-f-]{36}$/.test(entry.name)) continue;
16555
- const file = path18.join(directory, entry.name, "capture.json");
16556
- if (!fs19.existsSync(file)) continue;
17132
+ const file = path19.join(directory, entry.name, "capture.json");
17133
+ if (!fs20.existsSync(file)) continue;
16557
17134
  const record = JSON.parse(
16558
- fs19.readFileSync(file, "utf-8")
17135
+ fs20.readFileSync(file, "utf-8")
16559
17136
  );
16560
17137
  if (record.version !== 1 || record.id !== entry.name || record.repoRoot !== repoRoot || record.tool !== tool || record.sessionId !== sessionId) {
16561
17138
  throw new Error(`Invalid pending Git capture: ${file}`);
@@ -16568,13 +17145,13 @@ function hasPendingCaptures(repoRoot, tool, sessionId) {
16568
17145
  return listPendingCaptures2(repoRoot, tool, sessionId).length > 0;
16569
17146
  }
16570
17147
  function listPendingQueues() {
16571
- const root = path18.join(
16572
- process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path18.join(os9.homedir(), ".hillclimb", "git-traces"),
17148
+ const root = path19.join(
17149
+ process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces"),
16573
17150
  "pending-v1"
16574
17151
  );
16575
17152
  let queueDirs;
16576
17153
  try {
16577
- queueDirs = fs19.readdirSync(root, { withFileTypes: true });
17154
+ queueDirs = fs20.readdirSync(root, { withFileTypes: true });
16578
17155
  } catch (err) {
16579
17156
  if (err.code === "ENOENT") return [];
16580
17157
  throw err;
@@ -16582,15 +17159,15 @@ function listPendingQueues() {
16582
17159
  const queues = [];
16583
17160
  for (const queueDir of queueDirs) {
16584
17161
  if (!queueDir.isDirectory()) continue;
16585
- const directory = path18.join(root, queueDir.name);
17162
+ const directory = path19.join(root, queueDir.name);
16586
17163
  let identity = null;
16587
17164
  try {
16588
- for (const entry of fs19.readdirSync(directory, { withFileTypes: true })) {
17165
+ for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
16589
17166
  if (!entry.isDirectory()) continue;
16590
- const file = path18.join(directory, entry.name, "capture.json");
16591
- if (!fs19.existsSync(file)) continue;
17167
+ const file = path19.join(directory, entry.name, "capture.json");
17168
+ if (!fs20.existsSync(file)) continue;
16592
17169
  identity = JSON.parse(
16593
- fs19.readFileSync(file, "utf-8")
17170
+ fs20.readFileSync(file, "utf-8")
16594
17171
  );
16595
17172
  break;
16596
17173
  }
@@ -16623,15 +17200,15 @@ function noteReplayFailure(record, error, now = Date.now()) {
16623
17200
  }
16624
17201
  function queueDiskBytes(directory) {
16625
17202
  let diskBytes = 0;
16626
- if (fs19.existsSync(directory)) {
16627
- for (const entry of fs19.readdirSync(directory, { withFileTypes: true })) {
17203
+ if (fs20.existsSync(directory)) {
17204
+ for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
16628
17205
  if (!entry.isDirectory()) continue;
16629
- for (const file of fs19.readdirSync(path18.join(directory, entry.name), {
17206
+ for (const file of fs20.readdirSync(path19.join(directory, entry.name), {
16630
17207
  withFileTypes: true
16631
17208
  })) {
16632
17209
  if (file.isFile())
16633
- diskBytes += fs19.statSync(
16634
- path18.join(directory, entry.name, file.name)
17210
+ diskBytes += fs20.statSync(
17211
+ path19.join(directory, entry.name, file.name)
16635
17212
  ).size;
16636
17213
  }
16637
17214
  }
@@ -16647,7 +17224,7 @@ function pendingBytes(records) {
16647
17224
  return queueDiskBytes(
16648
17225
  queueDirectory(first.repoRoot, first.tool, first.sessionId)
16649
17226
  ) + records.reduce(
16650
- (sum, record) => sum + chargedBytes(record) + (fs19.existsSync(path18.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
17227
+ (sum, record) => sum + chargedBytes(record) + (fs20.existsSync(path19.join(captureDirectory(record), "capture.json")) ? 0 : Buffer.byteLength(JSON.stringify(record))),
16651
17228
  0
16652
17229
  );
16653
17230
  }
@@ -16740,7 +17317,7 @@ function storePendingCapture(params) {
16740
17317
  );
16741
17318
  }
16742
17319
  const directory = captureDirectory(record);
16743
- fs19.mkdirSync(directory, { recursive: true, mode: 448 });
17320
+ fs20.mkdirSync(directory, { recursive: true, mode: 448 });
16744
17321
  const refRoot = `refs/hillclimb/pending-v1/${record.id}`;
16745
17322
  let published = false;
16746
17323
  try {
@@ -16783,7 +17360,7 @@ function storePendingCapture(params) {
16783
17360
  return record;
16784
17361
  } finally {
16785
17362
  if (!published) {
16786
- fs19.rmSync(directory, { recursive: true, force: true });
17363
+ fs20.rmSync(directory, { recursive: true, force: true });
16787
17364
  for (const label of [
16788
17365
  "snapshot",
16789
17366
  "tree",
@@ -16814,7 +17391,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
16814
17391
  let status = "failed";
16815
17392
  try {
16816
17393
  if (saved) {
16817
- const bytes = fs19.readFileSync(path18.join(directory, saved.file));
17394
+ const bytes = fs20.readFileSync(path19.join(directory, saved.file));
16818
17395
  if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256) {
16819
17396
  throw new Error(
16820
17397
  `Pending Git artifact is corrupt: ${saved.file}; capture retained`
@@ -16834,7 +17411,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
16834
17411
  );
16835
17412
  }
16836
17413
  const file = `${crypto7.createHash("sha256").update(key).digest("hex")}.artifact`;
16837
- writeDurable(path18.join(directory, file), buffer);
17414
+ writeDurable(path19.join(directory, file), buffer);
16838
17415
  context.record.uploads[key] = {
16839
17416
  file,
16840
17417
  bytes: buffer.byteLength,
@@ -16844,7 +17421,7 @@ async function preparePendingUpload(context, contributionId, filename, buffer) {
16844
17421
  context.save();
16845
17422
  } catch (err) {
16846
17423
  delete context.record.uploads[key];
16847
- fs19.rmSync(path18.join(directory, file), { force: true });
17424
+ fs20.rmSync(path19.join(directory, file), { force: true });
16848
17425
  throw err;
16849
17426
  }
16850
17427
  status = "ok";
@@ -16876,8 +17453,8 @@ function readPreparedCaptureArtifact(context, filename, input) {
16876
17453
  throw new Error(
16877
17454
  `Prepared Git artifact input changed: ${filename}; capture retained`
16878
17455
  );
16879
- const bytes = fs19.readFileSync(
16880
- path18.join(captureDirectory(context.record), saved.file)
17456
+ const bytes = fs20.readFileSync(
17457
+ path19.join(captureDirectory(context.record), saved.file)
16881
17458
  );
16882
17459
  if (bytes.length !== saved.bytes || crypto7.createHash("sha256").update(bytes).digest("hex") !== saved.sha256)
16883
17460
  throw new Error(
@@ -16896,7 +17473,7 @@ function completePendingCapture(record) {
16896
17473
  function removeCompletedCapture(record) {
16897
17474
  if (!record.completed)
16898
17475
  throw new Error("Cannot remove an unacknowledged Git capture");
16899
- fs19.rmSync(captureDirectory(record), { recursive: true, force: true });
17476
+ fs20.rmSync(captureDirectory(record), { recursive: true, force: true });
16900
17477
  for (const label of ["snapshot", "tree", "head", "baseline", "previous"]) {
16901
17478
  deleteRef(
16902
17479
  record.repoRoot,
@@ -16969,7 +17546,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
16969
17546
  }
16970
17547
 
16971
17548
  // package.json
16972
- var version = "0.9.3";
17549
+ var version = "0.9.5";
16973
17550
 
16974
17551
  // src/version.ts
16975
17552
  var CLI_VERSION = version;
@@ -16993,7 +17570,7 @@ function lineHasAssistant(line) {
16993
17570
  return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
16994
17571
  }
16995
17572
  async function hasAssistantMessage(transcriptPath) {
16996
- const stream = fs20.createReadStream(transcriptPath, { encoding: "utf-8" });
17573
+ const stream = fs21.createReadStream(transcriptPath, { encoding: "utf-8" });
16997
17574
  let buffer = "";
16998
17575
  try {
16999
17576
  for await (const chunk of stream) {
@@ -17080,7 +17657,7 @@ function resolveCursorTranscriptPath(payload) {
17080
17657
  const workspace = payload.workspace_roots?.[0];
17081
17658
  if (!id || !workspace) return void 0;
17082
17659
  const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
17083
- return path19.join(
17660
+ return path20.join(
17084
17661
  os10.homedir(),
17085
17662
  ".cursor",
17086
17663
  "projects",
@@ -17136,14 +17713,14 @@ async function sweepClaudeSubagentTranscripts(payload) {
17136
17713
  const cwd = resolveHookCwd(payload);
17137
17714
  if (!parentSessionId || !cwd || !payload.transcript_path) return;
17138
17715
  try {
17139
- const directory = path19.join(
17140
- path19.dirname(path19.resolve(payload.transcript_path)),
17716
+ const directory = path20.join(
17717
+ path20.dirname(path20.resolve(payload.transcript_path)),
17141
17718
  parentSessionId,
17142
17719
  "subagents"
17143
17720
  );
17144
17721
  let names;
17145
17722
  try {
17146
- names = await fs20.promises.readdir(directory);
17723
+ names = await fs21.promises.readdir(directory);
17147
17724
  } catch (err) {
17148
17725
  if (err.code === "ENOENT") return;
17149
17726
  throw err;
@@ -17152,12 +17729,12 @@ async function sweepClaudeSubagentTranscripts(payload) {
17152
17729
  for (const name of names) {
17153
17730
  const match = /^agent-([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
17154
17731
  if (!match) continue;
17155
- const file = path19.join(directory, name);
17732
+ const file = path20.join(directory, name);
17156
17733
  try {
17157
17734
  files.push({
17158
17735
  agentId: match[1],
17159
17736
  file,
17160
- modifiedMs: (await fs20.promises.stat(file)).mtimeMs
17737
+ modifiedMs: (await fs21.promises.stat(file)).mtimeMs
17161
17738
  });
17162
17739
  } catch {
17163
17740
  }
@@ -17323,7 +17900,7 @@ async function sweepCodexForkTranscripts(payload) {
17323
17900
  const parent = await findProjectForCwd(cwd);
17324
17901
  if (!parent) return;
17325
17902
  const registeredRoots = Object.keys((await loadProjects()).projects).map(
17326
- (root) => path19.resolve(root)
17903
+ (root) => path20.resolve(root)
17327
17904
  );
17328
17905
  const discovery = await discoverCodexForkThreads({
17329
17906
  parentThreadId: parentSessionId,
@@ -17464,9 +18041,9 @@ async function runUploadForSession(payload, options) {
17464
18041
  );
17465
18042
  return "skipped";
17466
18043
  }
17467
- const transcriptResolved = path19.resolve(transcriptPath);
18044
+ const transcriptResolved = path20.resolve(transcriptPath);
17468
18045
  try {
17469
- const stat = await fs20.promises.stat(transcriptResolved);
18046
+ const stat = await fs21.promises.stat(transcriptResolved);
17470
18047
  if (!stat.isFile()) {
17471
18048
  appendLog(
17472
18049
  "warn",
@@ -17522,7 +18099,7 @@ function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
17522
18099
  };
17523
18100
  return {
17524
18101
  repoPath: repoRoot,
17525
- label: path19.basename(repoRoot),
18102
+ label: path20.basename(repoRoot),
17526
18103
  files: [sourceFile],
17527
18104
  sourceNames: [sourceTool],
17528
18105
  lastModified: /* @__PURE__ */ new Date()
@@ -17533,7 +18110,7 @@ async function buildRedactChain(repoRoot, capturedSecrets = []) {
17533
18110
  "secret-discovery",
17534
18111
  () => discoverEnvFiles(repoRoot)
17535
18112
  );
17536
- const envFilePaths = envFileNames.map((n) => path19.join(repoRoot, n));
18113
+ const envFilePaths = envFileNames.map((n) => path20.join(repoRoot, n));
17537
18114
  const secretResult = await measureAgentStage(
17538
18115
  "secret-collection",
17539
18116
  () => collectSecrets(repoRoot, envFilePaths, [])
@@ -18187,7 +18764,7 @@ async function uploadSession(args) {
18187
18764
  try {
18188
18765
  raw = await measureAgentStage(
18189
18766
  "transcript-read",
18190
- () => captured ? Promise.resolve(captured.bytes) : fs20.promises.readFile(transcriptPath)
18767
+ () => captured ? Promise.resolve(captured.bytes) : fs21.promises.readFile(transcriptPath)
18191
18768
  );
18192
18769
  } catch (err) {
18193
18770
  if (err.code === "ERR_FS_FILE_TOO_LARGE") {
@@ -18558,15 +19135,15 @@ import crypto9 from "crypto";
18558
19135
 
18559
19136
  // src/git-traces/handlers.ts
18560
19137
  import { execFileSync as execFileSync3 } from "child_process";
18561
- import fs21 from "fs";
18562
- import path20 from "path";
19138
+ import fs22 from "fs";
19139
+ import path21 from "path";
18563
19140
  var GIT_TRACES_SLUG = "git-traces";
18564
19141
  var MAX_FORK_SWEEP_THREADS2 = 8;
18565
19142
  var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
18566
19143
  var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
18567
19144
  var REBASELINE_AFTER_DIFF_FAILURES = 2;
18568
19145
  async function withRepoCaptureLock(repoRoot, capture) {
18569
- const canonicalRoot = fs21.realpathSync(repoRoot);
19146
+ const canonicalRoot = fs22.realpathSync(repoRoot);
18570
19147
  await acquireLock3(canonicalRoot, "snapshot-index-v1", null);
18571
19148
  try {
18572
19149
  return capture();
@@ -18624,7 +19201,7 @@ var PendingQueueConflictError = class extends Error {
18624
19201
  async function loadConfiguredRepos() {
18625
19202
  const file = await loadProjects();
18626
19203
  return Object.entries(file.projects).map(([repoRoot, config]) => ({
18627
- repoRoot: path20.resolve(repoRoot),
19204
+ repoRoot: path21.resolve(repoRoot),
18628
19205
  config
18629
19206
  })).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
18630
19207
  }
@@ -18642,12 +19219,12 @@ async function configuredReposFor(trigger) {
18642
19219
  return repos;
18643
19220
  }
18644
19221
  async function propagateCodexHooks(parentRoot, worktreeRoot) {
18645
- const source = path20.join(parentRoot, ".codex", "hooks.json");
18646
- const target = path20.join(worktreeRoot, ".codex", "hooks.json");
18647
- if (fs21.existsSync(target) || !fs21.existsSync(source)) return;
19222
+ const source = path21.join(parentRoot, ".codex", "hooks.json");
19223
+ const target = path21.join(worktreeRoot, ".codex", "hooks.json");
19224
+ if (fs22.existsSync(target) || !fs22.existsSync(source)) return;
18648
19225
  try {
18649
- fs21.mkdirSync(path20.dirname(target), { recursive: true });
18650
- fs21.copyFileSync(source, target, fs21.constants.COPYFILE_EXCL);
19226
+ fs22.mkdirSync(path21.dirname(target), { recursive: true });
19227
+ fs22.copyFileSync(source, target, fs22.constants.COPYFILE_EXCL);
18651
19228
  } catch (err) {
18652
19229
  appendLog(
18653
19230
  "warn",
@@ -18669,7 +19246,7 @@ async function propagateCodexHooks(parentRoot, worktreeRoot) {
18669
19246
  }
18670
19247
  }
18671
19248
  function repoLabel(repoRoot) {
18672
- return path20.basename(repoRoot) || repoRoot;
19249
+ return path21.basename(repoRoot) || repoRoot;
18673
19250
  }
18674
19251
  function resolveCwd2(payload) {
18675
19252
  return resolveHookCwd(payload);
@@ -20886,7 +21463,7 @@ async function collectStopTargets(tool, sessionId, repos, repoByRoot) {
20886
21463
  let needsLineageCheck = false;
20887
21464
  const storedStates = await listSessionStatesForSession(tool, sessionId);
20888
21465
  for (const { state } of storedStates) {
20889
- const repo = repoByRoot.get(path20.resolve(state.repoRoot));
21466
+ const repo = repoByRoot.get(path21.resolve(state.repoRoot));
20890
21467
  if (!repo) {
20891
21468
  missingConfig++;
20892
21469
  appendLog(
@@ -20918,7 +21495,7 @@ async function lateInitSkipReason(payload, tool) {
20918
21495
  const transcriptPath = payload.transcript_path;
20919
21496
  if (!transcriptPath) return tool === "codex" ? null : "no-transcript-path";
20920
21497
  try {
20921
- const stat = await fs21.promises.stat(path20.resolve(transcriptPath));
21498
+ const stat = await fs22.promises.stat(path21.resolve(transcriptPath));
20922
21499
  return stat.isFile() ? null : "transcript-not-a-file";
20923
21500
  } catch {
20924
21501
  return "transcript-missing";
@@ -21127,9 +21704,9 @@ async function handleSessionEnd(payload, tool) {
21127
21704
  if (sessionId) {
21128
21705
  const states = await listSessionStatesForSession(tool, sessionId);
21129
21706
  for (const { state } of states) {
21130
- const repoRoot = path20.resolve(state.repoRoot);
21707
+ const repoRoot = path21.resolve(state.repoRoot);
21131
21708
  repoRoots.add(repoRoot);
21132
- const canProcess = repoByRoot.has(repoRoot) || project && path20.resolve(project.repoRoot) === repoRoot;
21709
+ const canProcess = repoByRoot.has(repoRoot) || project && path21.resolve(project.repoRoot) === repoRoot;
21133
21710
  if (canProcess && tool === "codex" && state.codexLineageChecked !== true) {
21134
21711
  needsLineageCheck = true;
21135
21712
  }
@@ -21166,7 +21743,7 @@ async function handleSessionEnd(payload, tool) {
21166
21743
  let rejected = 0;
21167
21744
  const finalCaptures = /* @__PURE__ */ new Map();
21168
21745
  for (const repoRoot of repoRoots) {
21169
- const repo = repoByRoot.get(path20.resolve(repoRoot)) ?? (project && path20.resolve(project.repoRoot) === path20.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
21746
+ const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
21170
21747
  if (repo)
21171
21748
  finalCaptures.set(
21172
21749
  repoRoot,
@@ -21174,7 +21751,7 @@ async function handleSessionEnd(payload, tool) {
21174
21751
  );
21175
21752
  }
21176
21753
  for (const repoRoot of repoRoots) {
21177
- const repo = repoByRoot.get(path20.resolve(repoRoot)) ?? (project && path20.resolve(project.repoRoot) === path20.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
21754
+ const repo = repoByRoot.get(path21.resolve(repoRoot)) ?? (project && path21.resolve(project.repoRoot) === path21.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
21178
21755
  if (!repo) {
21179
21756
  skipped++;
21180
21757
  appendLog(
@@ -21553,15 +22130,15 @@ ${stack}` : ""}`
21553
22130
  }
21554
22131
 
21555
22132
  // src/outputs/zip.ts
21556
- import fs23 from "fs";
21557
- import path22 from "path";
22133
+ import fs24 from "fs";
22134
+ import path23 from "path";
21558
22135
  import archiver2 from "archiver";
21559
22136
 
21560
22137
  // src/outputs/downloads.ts
21561
22138
  import { execSync as execSync2 } from "child_process";
21562
- import fs22 from "fs";
22139
+ import fs23 from "fs";
21563
22140
  import os11 from "os";
21564
- import path21 from "path";
22141
+ import path22 from "path";
21565
22142
  function getDownloadsFolder() {
21566
22143
  const home = os11.homedir();
21567
22144
  if (process.platform === "linux") {
@@ -21570,12 +22147,12 @@ function getDownloadsFolder() {
21570
22147
  encoding: "utf-8",
21571
22148
  timeout: 3e3
21572
22149
  }).trim();
21573
- if (xdgDir && fs22.existsSync(xdgDir)) return xdgDir;
22150
+ if (xdgDir && fs23.existsSync(xdgDir)) return xdgDir;
21574
22151
  } catch {
21575
22152
  }
21576
22153
  }
21577
- const downloads = path21.join(home, "Downloads");
21578
- if (fs22.existsSync(downloads)) return downloads;
22154
+ const downloads = path22.join(home, "Downloads");
22155
+ if (fs23.existsSync(downloads)) return downloads;
21579
22156
  return home;
21580
22157
  }
21581
22158
 
@@ -21584,11 +22161,11 @@ function sanitizeFilename(name) {
21584
22161
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
21585
22162
  }
21586
22163
  function getUniqueFilename(dir, base, ext) {
21587
- let candidate = path22.join(dir, `${base}${ext}`);
21588
- if (!fs23.existsSync(candidate)) return candidate;
22164
+ let candidate = path23.join(dir, `${base}${ext}`);
22165
+ if (!fs24.existsSync(candidate)) return candidate;
21589
22166
  let i = 1;
21590
- while (fs23.existsSync(candidate)) {
21591
- candidate = path22.join(dir, `${base}-${i}${ext}`);
22167
+ while (fs24.existsSync(candidate)) {
22168
+ candidate = path23.join(dir, `${base}-${i}${ext}`);
21592
22169
  i++;
21593
22170
  }
21594
22171
  return candidate;
@@ -21598,13 +22175,13 @@ var ZipOutput = class {
21598
22175
  label = "Save as .zip to Downloads";
21599
22176
  async emit(group, options) {
21600
22177
  const downloadsDir = getDownloadsFolder();
21601
- const repoName = sanitizeFilename(path22.basename(group.repoPath));
22178
+ const repoName = sanitizeFilename(path23.basename(group.repoPath));
21602
22179
  const timeRange = options.timeRange;
21603
22180
  const rangePart = timeRange?.label ?? "all";
21604
22181
  const epochSeconds = Math.floor(Date.now() / 1e3);
21605
22182
  const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
21606
22183
  const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
21607
- const output = fs23.createWriteStream(outputPath);
22184
+ const output = fs24.createWriteStream(outputPath);
21608
22185
  const archive = archiver2("zip", { zlib: { level: 6 } });
21609
22186
  const done = new Promise((resolve, reject) => {
21610
22187
  output.on("close", resolve);
@@ -21798,15 +22375,15 @@ async function confirmExport(group, output) {
21798
22375
  }
21799
22376
 
21800
22377
  // src/sources/claude.ts
21801
- import fs24 from "fs";
22378
+ import fs25 from "fs";
21802
22379
  import os12 from "os";
21803
- import path23 from "path";
22380
+ import path24 from "path";
21804
22381
  import readline3 from "readline";
21805
22382
  var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
21806
22383
  async function resolveRepoPath(projectDir) {
21807
- const indexPath = path23.join(projectDir, "sessions-index.json");
22384
+ const indexPath = path24.join(projectDir, "sessions-index.json");
21808
22385
  try {
21809
- const raw = await fs24.promises.readFile(indexPath, "utf-8");
22386
+ const raw = await fs25.promises.readFile(indexPath, "utf-8");
21810
22387
  const data = JSON.parse(raw);
21811
22388
  if (data.originalPath && typeof data.originalPath === "string") {
21812
22389
  return data.originalPath;
@@ -21814,12 +22391,12 @@ async function resolveRepoPath(projectDir) {
21814
22391
  } catch {
21815
22392
  }
21816
22393
  const cwdCounts = /* @__PURE__ */ new Map();
21817
- const entries = await fs24.promises.readdir(projectDir, {
22394
+ const entries = await fs25.promises.readdir(projectDir, {
21818
22395
  withFileTypes: true
21819
22396
  });
21820
22397
  for (const entry of entries) {
21821
22398
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
21822
- const cwd = await extractCwdFromJsonl(path23.join(projectDir, entry.name));
22399
+ const cwd = await extractCwdFromJsonl(path24.join(projectDir, entry.name));
21823
22400
  if (cwd) {
21824
22401
  cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
21825
22402
  }
@@ -21838,7 +22415,7 @@ async function resolveRepoPath(projectDir) {
21838
22415
  return null;
21839
22416
  }
21840
22417
  async function extractCwdFromJsonl(filePath) {
21841
- const stream = fs24.createReadStream(filePath, { encoding: "utf-8" });
22418
+ const stream = fs25.createReadStream(filePath, { encoding: "utf-8" });
21842
22419
  const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
21843
22420
  try {
21844
22421
  for await (const line of rl) {
@@ -21860,12 +22437,12 @@ async function extractCwdFromJsonl(filePath) {
21860
22437
  async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
21861
22438
  let entries;
21862
22439
  try {
21863
- entries = await fs24.promises.readdir(dir, { withFileTypes: true });
22440
+ entries = await fs25.promises.readdir(dir, { withFileTypes: true });
21864
22441
  } catch {
21865
22442
  return;
21866
22443
  }
21867
22444
  for (const entry of entries) {
21868
- const fullPath = path23.join(dir, entry.name);
22445
+ const fullPath = path24.join(dir, entry.name);
21869
22446
  if (entry.isDirectory()) {
21870
22447
  if (SKIP_DIRS.has(entry.name)) continue;
21871
22448
  await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
@@ -21887,19 +22464,19 @@ function fallbackDecode(encodedName) {
21887
22464
  var ClaudeSource = class {
21888
22465
  name = "claude";
21889
22466
  async scan() {
21890
- const baseDir = path23.join(os12.homedir(), ".claude", "projects");
22467
+ const baseDir = path24.join(os12.homedir(), ".claude", "projects");
21891
22468
  try {
21892
- await fs24.promises.access(baseDir);
22469
+ await fs25.promises.access(baseDir);
21893
22470
  } catch {
21894
22471
  return [];
21895
22472
  }
21896
- const projectDirs = await fs24.promises.readdir(baseDir, {
22473
+ const projectDirs = await fs25.promises.readdir(baseDir, {
21897
22474
  withFileTypes: true
21898
22475
  });
21899
22476
  const dirEntries = projectDirs.filter((d) => d.isDirectory());
21900
22477
  const resultArrays = await Promise.all(
21901
22478
  dirEntries.map(async (dir) => {
21902
- const projectPath = path23.join(baseDir, dir.name);
22479
+ const projectPath = path24.join(baseDir, dir.name);
21903
22480
  const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
21904
22481
  const files = [];
21905
22482
  await collectFiles(
@@ -21917,12 +22494,12 @@ var ClaudeSource = class {
21917
22494
  };
21918
22495
 
21919
22496
  // src/sources/codex.ts
21920
- import fs25 from "fs";
22497
+ import fs26 from "fs";
21921
22498
  import os13 from "os";
21922
- import path24 from "path";
22499
+ import path25 from "path";
21923
22500
  import readline4 from "readline";
21924
22501
  async function parseSessionMeta2(filePath) {
21925
- const stream = fs25.createReadStream(filePath, { encoding: "utf-8" });
22502
+ const stream = fs26.createReadStream(filePath, { encoding: "utf-8" });
21926
22503
  const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
21927
22504
  try {
21928
22505
  for await (const line of rl) {
@@ -21947,12 +22524,12 @@ async function findJsonlFiles(dir) {
21947
22524
  async function walk(d) {
21948
22525
  let entries;
21949
22526
  try {
21950
- entries = await fs25.promises.readdir(d, { withFileTypes: true });
22527
+ entries = await fs26.promises.readdir(d, { withFileTypes: true });
21951
22528
  } catch {
21952
22529
  return;
21953
22530
  }
21954
22531
  for (const entry of entries) {
21955
- const full = path24.join(d, entry.name);
22532
+ const full = path25.join(d, entry.name);
21956
22533
  if (entry.isDirectory()) {
21957
22534
  await walk(full);
21958
22535
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -21966,11 +22543,11 @@ async function findJsonlFiles(dir) {
21966
22543
  async function loadHistory(historyPath) {
21967
22544
  const map = /* @__PURE__ */ new Map();
21968
22545
  try {
21969
- await fs25.promises.access(historyPath);
22546
+ await fs26.promises.access(historyPath);
21970
22547
  } catch {
21971
22548
  return map;
21972
22549
  }
21973
- const stream = fs25.createReadStream(historyPath, { encoding: "utf-8" });
22550
+ const stream = fs26.createReadStream(historyPath, { encoding: "utf-8" });
21974
22551
  const rl = readline4.createInterface({ input: stream, crlfDelay: Infinity });
21975
22552
  try {
21976
22553
  for await (const line of rl) {
@@ -21997,14 +22574,14 @@ async function loadHistory(historyPath) {
21997
22574
  var CodexSource = class {
21998
22575
  name = "codex";
21999
22576
  async scan() {
22000
- const codexDir = path24.join(os13.homedir(), ".codex");
22001
- const sessionsDir2 = path24.join(codexDir, "sessions");
22577
+ const codexDir = path25.join(os13.homedir(), ".codex");
22578
+ const sessionsDir2 = path25.join(codexDir, "sessions");
22002
22579
  try {
22003
- await fs25.promises.access(sessionsDir2);
22580
+ await fs26.promises.access(sessionsDir2);
22004
22581
  } catch {
22005
22582
  return [];
22006
22583
  }
22007
- const historyPath = path24.join(codexDir, "history.jsonl");
22584
+ const historyPath = path25.join(codexDir, "history.jsonl");
22008
22585
  const [jsonlFiles, historyMap] = await Promise.all([
22009
22586
  findJsonlFiles(sessionsDir2),
22010
22587
  loadHistory(historyPath)
@@ -22027,8 +22604,8 @@ var CodexSource = class {
22027
22604
  });
22028
22605
  const historyLines = historyMap.get(meta.sessionId);
22029
22606
  if (historyLines) {
22030
- const sessionDir = path24.relative(sessionsDir2, path24.dirname(filePath));
22031
- const historyAbsPath = path24.join(
22607
+ const sessionDir = path25.relative(sessionsDir2, path25.dirname(filePath));
22608
+ const historyAbsPath = path25.join(
22032
22609
  sessionsDir2,
22033
22610
  sessionDir,
22034
22611
  `history-${meta.sessionId}.jsonl`
@@ -22048,18 +22625,18 @@ var CodexSource = class {
22048
22625
  };
22049
22626
 
22050
22627
  // src/sources/copilotChat.ts
22051
- import fs26 from "fs";
22628
+ import fs27 from "fs";
22052
22629
  import os14 from "os";
22053
- import path25 from "path";
22630
+ import path26 from "path";
22054
22631
  import { fileURLToPath as fileURLToPath2 } from "url";
22055
22632
  function vsCodeUserDirs() {
22056
22633
  const home = os14.homedir();
22057
22634
  const dirs = [
22058
- path25.join(home, "Library", "Application Support", "Code", "User"),
22059
- path25.join(home, ".config", "Code", "User")
22635
+ path26.join(home, "Library", "Application Support", "Code", "User"),
22636
+ path26.join(home, ".config", "Code", "User")
22060
22637
  ];
22061
22638
  if (process.env.APPDATA) {
22062
- dirs.push(path25.join(process.env.APPDATA, "Code", "User"));
22639
+ dirs.push(path26.join(process.env.APPDATA, "Code", "User"));
22063
22640
  }
22064
22641
  return dirs;
22065
22642
  }
@@ -22074,7 +22651,7 @@ function uriToFsPath(uri) {
22074
22651
  async function readWorkspaceFolder(workspaceJsonPath) {
22075
22652
  let raw;
22076
22653
  try {
22077
- raw = await fs26.promises.readFile(workspaceJsonPath, "utf-8");
22654
+ raw = await fs27.promises.readFile(workspaceJsonPath, "utf-8");
22078
22655
  } catch {
22079
22656
  return null;
22080
22657
  }
@@ -22096,10 +22673,10 @@ var CopilotChatSource = class {
22096
22673
  async scan() {
22097
22674
  const results = [];
22098
22675
  for (const userDir of vsCodeUserDirs()) {
22099
- const workspaceStorage = path25.join(userDir, "workspaceStorage");
22676
+ const workspaceStorage = path26.join(userDir, "workspaceStorage");
22100
22677
  let hashDirs;
22101
22678
  try {
22102
- hashDirs = await fs26.promises.readdir(workspaceStorage, {
22679
+ hashDirs = await fs27.promises.readdir(workspaceStorage, {
22103
22680
  withFileTypes: true
22104
22681
  });
22105
22682
  } catch {
@@ -22107,22 +22684,22 @@ var CopilotChatSource = class {
22107
22684
  }
22108
22685
  for (const hash of hashDirs) {
22109
22686
  if (!hash.isDirectory()) continue;
22110
- const wsRoot = path25.join(workspaceStorage, hash.name);
22111
- const transcriptsDir = path25.join(
22687
+ const wsRoot = path26.join(workspaceStorage, hash.name);
22688
+ const transcriptsDir = path26.join(
22112
22689
  wsRoot,
22113
22690
  "GitHub.copilot-chat",
22114
22691
  "transcripts"
22115
22692
  );
22116
22693
  let transcriptEntries;
22117
22694
  try {
22118
- transcriptEntries = await fs26.promises.readdir(transcriptsDir, {
22695
+ transcriptEntries = await fs27.promises.readdir(transcriptsDir, {
22119
22696
  withFileTypes: true
22120
22697
  });
22121
22698
  } catch {
22122
22699
  continue;
22123
22700
  }
22124
22701
  const repoPath = await readWorkspaceFolder(
22125
- path25.join(wsRoot, "workspace.json")
22702
+ path26.join(wsRoot, "workspace.json")
22126
22703
  );
22127
22704
  if (!repoPath) continue;
22128
22705
  for (const entry of transcriptEntries) {
@@ -22130,7 +22707,7 @@ var CopilotChatSource = class {
22130
22707
  const sessionId = entry.name.slice(0, -".jsonl".length);
22131
22708
  results.push({
22132
22709
  sourceName: this.name,
22133
- absolutePath: path25.join(transcriptsDir, entry.name),
22710
+ absolutePath: path26.join(transcriptsDir, entry.name),
22134
22711
  repoPath,
22135
22712
  metadata: { sessionId }
22136
22713
  });
@@ -22170,7 +22747,7 @@ function reportRedactionStats(noun, stats) {
22170
22747
  async function filterByTimeRange(group, range) {
22171
22748
  const results = await Promise.all(
22172
22749
  group.files.map(
22173
- (f) => fs27.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
22750
+ (f) => fs28.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
22174
22751
  )
22175
22752
  );
22176
22753
  const filtered = [];
@@ -22197,10 +22774,10 @@ async function runInteractive() {
22197
22774
  s.start(`Scanning ${source.name} logs...`);
22198
22775
  const allFiles = await source.scan();
22199
22776
  const allGroups = await mergeByRepo(allFiles);
22200
- const repoRoot = path26.resolve(repo.root);
22777
+ const repoRoot = path27.resolve(repo.root);
22201
22778
  const matching = allGroups.filter((g) => {
22202
- const resolved = path26.resolve(g.repoPath);
22203
- return resolved === repoRoot || resolved.startsWith(repoRoot + path26.sep);
22779
+ const resolved = path27.resolve(g.repoPath);
22780
+ return resolved === repoRoot || resolved.startsWith(repoRoot + path27.sep);
22204
22781
  });
22205
22782
  if (matching.length === 0) {
22206
22783
  s.stop(`No ${source.name} logs found for ${repo.name}.`);
@@ -22231,7 +22808,7 @@ async function runInteractive() {
22231
22808
  }
22232
22809
  }
22233
22810
  const envFileNames = await discoverEnvFiles(repoRoot);
22234
- const envFilePaths = envFileNames.map((n) => path26.join(repoRoot, n));
22811
+ const envFilePaths = envFileNames.map((n) => path27.join(repoRoot, n));
22235
22812
  const additionalFiles = await promptSecretFiles(envFileNames);
22236
22813
  const secretResult = await collectSecrets(
22237
22814
  repoRoot,