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 +905 -328
- package/dist/pattern-worker.js +400 -26
- package/package.json +2 -1
package/dist/main.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-UPXMA7RC.js";
|
|
14
14
|
|
|
15
15
|
// src/main.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import fs28 from "fs";
|
|
17
|
+
import path27 from "path";
|
|
18
18
|
import * as p6 from "@clack/prompts";
|
|
19
19
|
|
|
20
20
|
// src/commands/init.ts
|
|
@@ -2714,9 +2714,9 @@ async function runStatus(args = []) {
|
|
|
2714
2714
|
// src/commands/upload.ts
|
|
2715
2715
|
import { spawn as spawn3 } from "child_process";
|
|
2716
2716
|
import crypto8 from "crypto";
|
|
2717
|
-
import
|
|
2717
|
+
import fs21 from "fs";
|
|
2718
2718
|
import os10 from "os";
|
|
2719
|
-
import
|
|
2719
|
+
import path20 from "path";
|
|
2720
2720
|
|
|
2721
2721
|
// src/agent-pending.ts
|
|
2722
2722
|
import crypto4 from "crypto";
|
|
@@ -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
|
|
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
|
|
4750
|
-
const
|
|
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
|
-
|
|
4755
|
-
for (; ; ) {
|
|
4773
|
+
let start = options.start ?? 0;
|
|
4774
|
+
while (ownsFinalLine || start < end) {
|
|
4756
4775
|
const nl = buffer.indexOf(NEWLINE, start);
|
|
4757
|
-
const
|
|
4758
|
-
|
|
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
|
-
|
|
4779
|
+
edits.push({ start, end: lineEnd, value: oversizedPlaceholder });
|
|
4764
4780
|
} else {
|
|
4765
|
-
const { value, count } = redactLine(
|
|
4766
|
-
|
|
4767
|
-
|
|
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
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
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
|
|
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 (
|
|
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
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
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,
|
|
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:
|
|
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.
|
|
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
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
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
|
-
|
|
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
|
|
13517
|
-
|
|
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
|
|
13522
|
-
if (
|
|
13523
|
-
newFiles.push(
|
|
13939
|
+
const unchanged = largeResults.get(i) ?? passThrough.get(i);
|
|
13940
|
+
if (unchanged) {
|
|
13941
|
+
newFiles.push(unchanged);
|
|
13524
13942
|
continue;
|
|
13525
13943
|
}
|
|
13526
|
-
const
|
|
13527
|
-
if (
|
|
13528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
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) =>
|
|
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
|
|
13581
|
-
(
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
13704
|
-
//
|
|
13705
|
-
|
|
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
|
|
15181
|
+
import fs20 from "fs";
|
|
14762
15182
|
import os9 from "os";
|
|
14763
|
-
import
|
|
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
|
|
15187
|
+
import fs18 from "fs";
|
|
14768
15188
|
import os7 from "os";
|
|
14769
|
-
import
|
|
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(
|
|
14962
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
15509
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path17.basename(filePath))) return true;
|
|
15510
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path17.extname(filePath).toLowerCase());
|
|
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 =
|
|
15531
|
+
fd = fs18.openSync(absPath, "r");
|
|
14984
15532
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
14985
|
-
const bytesRead =
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
-
|
|
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 =
|
|
15694
|
+
const indexPath = path17.resolve(
|
|
15130
15695
|
repoRoot,
|
|
15131
15696
|
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
15132
15697
|
);
|
|
15133
|
-
tmpDir =
|
|
15134
|
-
copy =
|
|
15135
|
-
const fd =
|
|
15698
|
+
tmpDir = fs18.mkdtempSync(path17.join(os7.tmpdir(), "hillclimb-index-"));
|
|
15699
|
+
copy = path17.join(tmpDir, "index");
|
|
15700
|
+
const fd = fs18.openSync(indexPath, "r");
|
|
15136
15701
|
let stat;
|
|
15137
15702
|
let data;
|
|
15138
15703
|
try {
|
|
15139
|
-
stat =
|
|
15140
|
-
data =
|
|
15704
|
+
stat = fs18.fstatSync(fd);
|
|
15705
|
+
data = fs18.readFileSync(fd);
|
|
15141
15706
|
} finally {
|
|
15142
|
-
|
|
15707
|
+
fs18.closeSync(fd);
|
|
15143
15708
|
}
|
|
15144
|
-
|
|
15145
|
-
|
|
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)
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
15528
|
-
const stat =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
15707
|
-
|
|
16271
|
+
tmpDir = fs18.mkdtempSync(
|
|
16272
|
+
path17.join(os7.tmpdir(), `hillclimb-bundle-${process.pid}-`)
|
|
15708
16273
|
);
|
|
15709
|
-
const tmpFile =
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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
|
|
16025
|
-
indexByPath.set(
|
|
16026
|
-
files.push({ path:
|
|
16601
|
+
const path28 = parts[parts.length - 1];
|
|
16602
|
+
indexByPath.set(path28, files.length);
|
|
16603
|
+
files.push({ path: path28, status, additions: 0, deletions: 0 });
|
|
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
|
|
16687
|
+
import fs19 from "fs";
|
|
16111
16688
|
import os8 from "os";
|
|
16112
|
-
import
|
|
16689
|
+
import path18 from "path";
|
|
16113
16690
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
16114
|
-
var DEFAULT_STATE_DIR2 =
|
|
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 ? `${
|
|
16716
|
+
sessionId ? `${path18.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path18.resolve(repoRoot)}\0${tool}`
|
|
16140
16717
|
).digest("hex").slice(0, 16);
|
|
16141
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
16184
|
-
if (
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
|
16845
|
+
await fs19.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
16269
16846
|
const tmp = `${file}.tmp`;
|
|
16270
|
-
await
|
|
16847
|
+
await fs19.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
16271
16848
|
mode: 384
|
|
16272
16849
|
});
|
|
16273
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16924
|
+
const fd = await fs19.promises.open(
|
|
16348
16925
|
lockPath,
|
|
16349
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 ??
|
|
16490
|
-
const key = crypto7.createHash("sha256").update(`${
|
|
16491
|
-
return
|
|
17066
|
+
const stateDir4 = process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces");
|
|
17067
|
+
const key = crypto7.createHash("sha256").update(`${path19.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex");
|
|
17068
|
+
return path19.join(stateDir4, "pending-v1", key);
|
|
16492
17069
|
}
|
|
16493
17070
|
function captureDirectory(record) {
|
|
16494
|
-
return
|
|
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 =
|
|
17079
|
+
const descriptor = fs20.openSync(temporary, "w", 384);
|
|
16503
17080
|
try {
|
|
16504
|
-
|
|
16505
|
-
|
|
17081
|
+
fs20.writeFileSync(descriptor, body);
|
|
17082
|
+
fs20.fsyncSync(descriptor);
|
|
16506
17083
|
} finally {
|
|
16507
|
-
|
|
17084
|
+
fs20.closeSync(descriptor);
|
|
16508
17085
|
}
|
|
16509
|
-
|
|
17086
|
+
fs20.renameSync(temporary, file);
|
|
16510
17087
|
try {
|
|
16511
|
-
const directory =
|
|
17088
|
+
const directory = fs20.openSync(path19.dirname(file), "r");
|
|
16512
17089
|
try {
|
|
16513
|
-
|
|
17090
|
+
fs20.fsyncSync(directory);
|
|
16514
17091
|
} finally {
|
|
16515
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
16556
|
-
if (!
|
|
17132
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17133
|
+
if (!fs20.existsSync(file)) continue;
|
|
16557
17134
|
const record = JSON.parse(
|
|
16558
|
-
|
|
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 =
|
|
16572
|
-
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ??
|
|
17148
|
+
const root = path19.join(
|
|
17149
|
+
process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? path19.join(os9.homedir(), ".hillclimb", "git-traces"),
|
|
16573
17150
|
"pending-v1"
|
|
16574
17151
|
);
|
|
16575
17152
|
let queueDirs;
|
|
16576
17153
|
try {
|
|
16577
|
-
queueDirs =
|
|
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 =
|
|
17162
|
+
const directory = path19.join(root, queueDir.name);
|
|
16586
17163
|
let identity = null;
|
|
16587
17164
|
try {
|
|
16588
|
-
for (const entry of
|
|
17165
|
+
for (const entry of fs20.readdirSync(directory, { withFileTypes: true })) {
|
|
16589
17166
|
if (!entry.isDirectory()) continue;
|
|
16590
|
-
const file =
|
|
16591
|
-
if (!
|
|
17167
|
+
const file = path19.join(directory, entry.name, "capture.json");
|
|
17168
|
+
if (!fs20.existsSync(file)) continue;
|
|
16592
17169
|
identity = JSON.parse(
|
|
16593
|
-
|
|
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 (
|
|
16627
|
-
for (const entry of
|
|
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
|
|
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 +=
|
|
16634
|
-
|
|
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) + (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
16880
|
-
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
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 =
|
|
17140
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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) =>
|
|
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 =
|
|
18044
|
+
const transcriptResolved = path20.resolve(transcriptPath);
|
|
17468
18045
|
try {
|
|
17469
|
-
const stat = await
|
|
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:
|
|
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) =>
|
|
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) :
|
|
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
|
|
18562
|
-
import
|
|
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 =
|
|
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:
|
|
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 =
|
|
18646
|
-
const target =
|
|
18647
|
-
if (
|
|
19222
|
+
const source = path21.join(parentRoot, ".codex", "hooks.json");
|
|
19223
|
+
const target = path21.join(worktreeRoot, ".codex", "hooks.json");
|
|
19224
|
+
if (fs22.existsSync(target) || !fs22.existsSync(source)) return;
|
|
18648
19225
|
try {
|
|
18649
|
-
|
|
18650
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
21707
|
+
const repoRoot = path21.resolve(state.repoRoot);
|
|
21131
21708
|
repoRoots.add(repoRoot);
|
|
21132
|
-
const canProcess = repoByRoot.has(repoRoot) || project &&
|
|
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(
|
|
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(
|
|
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
|
|
21557
|
-
import
|
|
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
|
|
22139
|
+
import fs23 from "fs";
|
|
21563
22140
|
import os11 from "os";
|
|
21564
|
-
import
|
|
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 &&
|
|
22150
|
+
if (xdgDir && fs23.existsSync(xdgDir)) return xdgDir;
|
|
21574
22151
|
} catch {
|
|
21575
22152
|
}
|
|
21576
22153
|
}
|
|
21577
|
-
const downloads =
|
|
21578
|
-
if (
|
|
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 =
|
|
21588
|
-
if (!
|
|
22164
|
+
let candidate = path23.join(dir, `${base}${ext}`);
|
|
22165
|
+
if (!fs24.existsSync(candidate)) return candidate;
|
|
21589
22166
|
let i = 1;
|
|
21590
|
-
while (
|
|
21591
|
-
candidate =
|
|
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(
|
|
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 =
|
|
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
|
|
22378
|
+
import fs25 from "fs";
|
|
21802
22379
|
import os12 from "os";
|
|
21803
|
-
import
|
|
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 =
|
|
22384
|
+
const indexPath = path24.join(projectDir, "sessions-index.json");
|
|
21808
22385
|
try {
|
|
21809
|
-
const raw = await
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
22467
|
+
const baseDir = path24.join(os12.homedir(), ".claude", "projects");
|
|
21891
22468
|
try {
|
|
21892
|
-
await
|
|
22469
|
+
await fs25.promises.access(baseDir);
|
|
21893
22470
|
} catch {
|
|
21894
22471
|
return [];
|
|
21895
22472
|
}
|
|
21896
|
-
const projectDirs = await
|
|
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 =
|
|
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
|
|
22497
|
+
import fs26 from "fs";
|
|
21921
22498
|
import os13 from "os";
|
|
21922
|
-
import
|
|
22499
|
+
import path25 from "path";
|
|
21923
22500
|
import readline4 from "readline";
|
|
21924
22501
|
async function parseSessionMeta2(filePath) {
|
|
21925
|
-
const stream =
|
|
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
|
|
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 =
|
|
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
|
|
22546
|
+
await fs26.promises.access(historyPath);
|
|
21970
22547
|
} catch {
|
|
21971
22548
|
return map;
|
|
21972
22549
|
}
|
|
21973
|
-
const stream =
|
|
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 =
|
|
22001
|
-
const sessionsDir2 =
|
|
22577
|
+
const codexDir = path25.join(os13.homedir(), ".codex");
|
|
22578
|
+
const sessionsDir2 = path25.join(codexDir, "sessions");
|
|
22002
22579
|
try {
|
|
22003
|
-
await
|
|
22580
|
+
await fs26.promises.access(sessionsDir2);
|
|
22004
22581
|
} catch {
|
|
22005
22582
|
return [];
|
|
22006
22583
|
}
|
|
22007
|
-
const historyPath =
|
|
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 =
|
|
22031
|
-
const historyAbsPath =
|
|
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
|
|
22628
|
+
import fs27 from "fs";
|
|
22052
22629
|
import os14 from "os";
|
|
22053
|
-
import
|
|
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
|
-
|
|
22059
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
22676
|
+
const workspaceStorage = path26.join(userDir, "workspaceStorage");
|
|
22100
22677
|
let hashDirs;
|
|
22101
22678
|
try {
|
|
22102
|
-
hashDirs = await
|
|
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 =
|
|
22111
|
-
const transcriptsDir =
|
|
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
|
|
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
|
-
|
|
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:
|
|
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) =>
|
|
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 =
|
|
22777
|
+
const repoRoot = path27.resolve(repo.root);
|
|
22201
22778
|
const matching = allGroups.filter((g) => {
|
|
22202
|
-
const resolved =
|
|
22203
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
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) =>
|
|
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,
|