hillclimb 0.9.3 → 0.9.4
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 +547 -127
- package/dist/pattern-worker.js +400 -26
- package/package.json +2 -1
package/dist/main.js
CHANGED
|
@@ -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, () => {
|
|
@@ -16969,7 +17389,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
16969
17389
|
}
|
|
16970
17390
|
|
|
16971
17391
|
// package.json
|
|
16972
|
-
var version = "0.9.
|
|
17392
|
+
var version = "0.9.4";
|
|
16973
17393
|
|
|
16974
17394
|
// src/version.ts
|
|
16975
17395
|
var CLI_VERSION = version;
|
package/dist/pattern-worker.js
CHANGED
|
@@ -1,6 +1,295 @@
|
|
|
1
1
|
// src/middleware/pattern-worker.ts
|
|
2
2
|
import { parentPort, workerData } from "worker_threads";
|
|
3
3
|
|
|
4
|
+
// src/middleware/large-file.ts
|
|
5
|
+
import { constants as bufferConstants } from "buffer";
|
|
6
|
+
var MAX_STRINGIFIABLE_BYTES = bufferConstants.MAX_STRING_LENGTH;
|
|
7
|
+
var NEWLINE = 10;
|
|
8
|
+
var OVERSIZED_LINE_PLACEHOLDER = '{"_hillclimb_omitted":"line exceeded max string length; dropped to allow redaction"}';
|
|
9
|
+
function scanBufferLines(buffer, redactLine, options = {}) {
|
|
10
|
+
const maxLineBytes = options.maxLineBytes ?? MAX_STRINGIFIABLE_BYTES;
|
|
11
|
+
const oversizedPlaceholder = options.oversizedPlaceholder ?? OVERSIZED_LINE_PLACEHOLDER;
|
|
12
|
+
const end = options.end ?? buffer.length;
|
|
13
|
+
const ownsFinalLine = end === buffer.length;
|
|
14
|
+
const edits = [];
|
|
15
|
+
let totalCount = 0;
|
|
16
|
+
let oversizedLines = 0;
|
|
17
|
+
let start = options.start ?? 0;
|
|
18
|
+
while (ownsFinalLine || start < end) {
|
|
19
|
+
const nl = buffer.indexOf(NEWLINE, start);
|
|
20
|
+
const lineEnd = nl === -1 ? buffer.length : nl;
|
|
21
|
+
if (lineEnd - start > maxLineBytes) {
|
|
22
|
+
oversizedLines++;
|
|
23
|
+
edits.push({ start, end: lineEnd, value: oversizedPlaceholder });
|
|
24
|
+
} else {
|
|
25
|
+
const { value, count } = redactLine(
|
|
26
|
+
buffer.toString("utf-8", start, lineEnd)
|
|
27
|
+
);
|
|
28
|
+
if (count > 0) {
|
|
29
|
+
totalCount += count;
|
|
30
|
+
edits.push({ start, end: lineEnd, value });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (nl === -1) break;
|
|
34
|
+
start = nl + 1;
|
|
35
|
+
}
|
|
36
|
+
return { edits, count: totalCount, oversizedLines };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/middleware/pattern-prefilter.ts
|
|
40
|
+
var MIN_LITERAL = 3;
|
|
41
|
+
function score(req) {
|
|
42
|
+
if (!req) return -1;
|
|
43
|
+
let min = Number.POSITIVE_INFINITY;
|
|
44
|
+
for (const s of req) min = Math.min(min, s.length);
|
|
45
|
+
return min * 1e3 - req.length;
|
|
46
|
+
}
|
|
47
|
+
function better(a, b) {
|
|
48
|
+
return score(b) > score(a) ? b : a;
|
|
49
|
+
}
|
|
50
|
+
function isAsciiLiteral(ch) {
|
|
51
|
+
const c = ch.charCodeAt(0);
|
|
52
|
+
return c >= 32 && c < 127;
|
|
53
|
+
}
|
|
54
|
+
var UNKNOWN = { literals: null, minLength: 0 };
|
|
55
|
+
function analyzeRegex(source, flags = "") {
|
|
56
|
+
if (flags.includes("v")) return UNKNOWN;
|
|
57
|
+
let i = 0;
|
|
58
|
+
function skipPast(close) {
|
|
59
|
+
const at = source.indexOf(close, i);
|
|
60
|
+
if (at === -1) throw new Error("unterminated escape");
|
|
61
|
+
i = at + 1;
|
|
62
|
+
}
|
|
63
|
+
function flush(seq) {
|
|
64
|
+
if (seq.run.length > 0) {
|
|
65
|
+
seq.req = better(seq.req, [seq.run.toLowerCase()]);
|
|
66
|
+
seq.run = "";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function quantifier() {
|
|
70
|
+
const ch = source[i];
|
|
71
|
+
let min = null;
|
|
72
|
+
if (ch === "*" || ch === "?") {
|
|
73
|
+
min = 0;
|
|
74
|
+
i++;
|
|
75
|
+
} else if (ch === "+") {
|
|
76
|
+
min = 1;
|
|
77
|
+
i++;
|
|
78
|
+
} else if (ch === "{") {
|
|
79
|
+
const m = /^\{(\d+)(?:,\d*)?\}/.exec(source.slice(i));
|
|
80
|
+
if (m) {
|
|
81
|
+
min = Number(m[1]);
|
|
82
|
+
i += m[0].length;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (min !== null && source[i] === "?") i++;
|
|
86
|
+
return min;
|
|
87
|
+
}
|
|
88
|
+
function skipClass() {
|
|
89
|
+
i++;
|
|
90
|
+
while (i < source.length && source[i] !== "]") {
|
|
91
|
+
if (source[i] === "\\") i++;
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
function alternation() {
|
|
97
|
+
const branches = [];
|
|
98
|
+
let seq = { req: null, run: "", min: 0 };
|
|
99
|
+
while (i < source.length && source[i] !== ")") {
|
|
100
|
+
const ch = source[i];
|
|
101
|
+
if (ch === "|") {
|
|
102
|
+
flush(seq);
|
|
103
|
+
branches.push(seq);
|
|
104
|
+
seq = { req: null, run: "", min: 0 };
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (ch === "(") {
|
|
109
|
+
i++;
|
|
110
|
+
let lookaround = false;
|
|
111
|
+
if (source[i] === "?") {
|
|
112
|
+
const m = /^\?(?:[:=!]|<[=!]|<[A-Za-z_$][\w$]*>)/.exec(
|
|
113
|
+
source.slice(i)
|
|
114
|
+
);
|
|
115
|
+
if (!m) throw new Error(`unsupported group at ${i}`);
|
|
116
|
+
lookaround = m[0] !== "?:" && !m[0].endsWith(">");
|
|
117
|
+
i += m[0].length;
|
|
118
|
+
}
|
|
119
|
+
const inner = alternation();
|
|
120
|
+
if (source[i] !== ")") throw new Error("unbalanced group");
|
|
121
|
+
i++;
|
|
122
|
+
const min3 = quantifier();
|
|
123
|
+
flush(seq);
|
|
124
|
+
if (!lookaround) {
|
|
125
|
+
seq.min += inner.min * (min3 ?? 1);
|
|
126
|
+
if (min3 === null || min3 >= 1) seq.req = better(seq.req, inner.req);
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ch === "[" || ch === ".") {
|
|
131
|
+
if (ch === "[") skipClass();
|
|
132
|
+
else i++;
|
|
133
|
+
seq.min += quantifier() ?? 1;
|
|
134
|
+
flush(seq);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (ch === "^" || ch === "$") {
|
|
138
|
+
i++;
|
|
139
|
+
quantifier();
|
|
140
|
+
flush(seq);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
let lit;
|
|
144
|
+
let width = 1;
|
|
145
|
+
if (ch === "\\") {
|
|
146
|
+
const e = source[i + 1];
|
|
147
|
+
i += 2;
|
|
148
|
+
if (/[A-Za-z0-9]/.test(e)) {
|
|
149
|
+
if (!/[dDwWsS]/.test(e)) width = 0;
|
|
150
|
+
if (e === "x") i += 2;
|
|
151
|
+
else if (e === "u") {
|
|
152
|
+
if (source[i] === "{") skipPast("}");
|
|
153
|
+
else i += 4;
|
|
154
|
+
} else if (e === "c") i += 1;
|
|
155
|
+
else if (e === "k" && source[i] === "<") skipPast(">");
|
|
156
|
+
else if ((e === "p" || e === "P") && source[i] === "{") skipPast("}");
|
|
157
|
+
lit = null;
|
|
158
|
+
} else {
|
|
159
|
+
lit = e;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
lit = ch;
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
const min2 = quantifier();
|
|
166
|
+
seq.min += width * (min2 ?? 1);
|
|
167
|
+
if (lit === null || !isAsciiLiteral(lit)) {
|
|
168
|
+
flush(seq);
|
|
169
|
+
} else if (min2 === null) {
|
|
170
|
+
seq.run += lit;
|
|
171
|
+
} else {
|
|
172
|
+
if (min2 >= 1) seq.run += lit;
|
|
173
|
+
flush(seq);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
flush(seq);
|
|
177
|
+
branches.push(seq);
|
|
178
|
+
let min = Number.POSITIVE_INFINITY;
|
|
179
|
+
let all = [];
|
|
180
|
+
for (const b of branches) {
|
|
181
|
+
min = Math.min(min, b.min);
|
|
182
|
+
if (!b.req) all = null;
|
|
183
|
+
else all?.push(...b.req);
|
|
184
|
+
}
|
|
185
|
+
return { req: all ? [...new Set(all)] : null, min };
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const { req, min } = alternation();
|
|
189
|
+
if (i !== source.length) return UNKNOWN;
|
|
190
|
+
const foldsBeyondAscii = flags.includes("i") && flags.includes("u");
|
|
191
|
+
const usable = req && !foldsBeyondAscii && !req.some((s) => s.length < MIN_LITERAL);
|
|
192
|
+
return { literals: usable ? req : null, minLength: min };
|
|
193
|
+
} catch {
|
|
194
|
+
return UNKNOWN;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function isAscii(text) {
|
|
198
|
+
for (let k = 0; k < text.length; k++) {
|
|
199
|
+
if (text.charCodeAt(k) >= 128) return false;
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
function buildPrefilter(literalsByRule) {
|
|
204
|
+
const alwaysRun = [];
|
|
205
|
+
const goto = [/* @__PURE__ */ new Map()];
|
|
206
|
+
const outs = [[]];
|
|
207
|
+
literalsByRule.forEach((given, rule) => {
|
|
208
|
+
const lits = given?.map((lit) => lit.toLowerCase());
|
|
209
|
+
if (!lits || lits.some((lit) => lit === "" || !isAscii(lit))) {
|
|
210
|
+
alwaysRun.push(rule);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
for (const lit of lits) {
|
|
214
|
+
let s = 0;
|
|
215
|
+
for (let k = 0; k < lit.length; k++) {
|
|
216
|
+
const c = lit.charCodeAt(k);
|
|
217
|
+
let n = goto[s].get(c);
|
|
218
|
+
if (n === void 0) {
|
|
219
|
+
n = goto.length;
|
|
220
|
+
goto.push(/* @__PURE__ */ new Map());
|
|
221
|
+
outs.push([]);
|
|
222
|
+
goto[s].set(c, n);
|
|
223
|
+
}
|
|
224
|
+
s = n;
|
|
225
|
+
}
|
|
226
|
+
outs[s].push(rule);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
const nStates = goto.length;
|
|
230
|
+
const next = new Int32Array(nStates << 7);
|
|
231
|
+
const fail = new Int32Array(nStates);
|
|
232
|
+
const queue = [];
|
|
233
|
+
for (const [c, n] of goto[0]) {
|
|
234
|
+
next[c] = n;
|
|
235
|
+
queue.push(n);
|
|
236
|
+
}
|
|
237
|
+
for (let q = 0; q < queue.length; q++) {
|
|
238
|
+
const s = queue[q];
|
|
239
|
+
const f = fail[s];
|
|
240
|
+
if (outs[f].length) outs[s] = [.../* @__PURE__ */ new Set([...outs[s], ...outs[f]])];
|
|
241
|
+
const base = s << 7;
|
|
242
|
+
const fbase = f << 7;
|
|
243
|
+
for (let c = 0; c < 128; c++) next[base | c] = next[fbase | c];
|
|
244
|
+
for (const [c, n] of goto[s]) {
|
|
245
|
+
fail[n] = next[fbase | c];
|
|
246
|
+
next[base | c] = n;
|
|
247
|
+
queue.push(n);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (let s = 0; s < nStates; s++) {
|
|
251
|
+
const base = s << 7;
|
|
252
|
+
for (let c = 65; c <= 90; c++) next[base | c] = next[base | c + 32];
|
|
253
|
+
}
|
|
254
|
+
const hasOut = new Uint8Array(nStates);
|
|
255
|
+
const outRules = outs.map((o, s) => {
|
|
256
|
+
if (o.length === 0) return null;
|
|
257
|
+
hasOut[s] = 1;
|
|
258
|
+
return o;
|
|
259
|
+
});
|
|
260
|
+
const ruleStamp = new Uint32Array(literalsByRule.length);
|
|
261
|
+
const stateStamp = new Uint32Array(nStates);
|
|
262
|
+
let stamp = 0;
|
|
263
|
+
return {
|
|
264
|
+
alwaysRun,
|
|
265
|
+
candidates(value, out) {
|
|
266
|
+
stamp++;
|
|
267
|
+
if (stamp === 4294967295) {
|
|
268
|
+
ruleStamp.fill(0);
|
|
269
|
+
stateStamp.fill(0);
|
|
270
|
+
stamp = 1;
|
|
271
|
+
}
|
|
272
|
+
let s = 0;
|
|
273
|
+
let found = false;
|
|
274
|
+
for (let k = 0, n = value.length; k < n; k++) {
|
|
275
|
+
const c = value.charCodeAt(k);
|
|
276
|
+
s = c < 128 ? next[s << 7 | c] : 0;
|
|
277
|
+
if (hasOut[s] === 1 && stateStamp[s] !== stamp) {
|
|
278
|
+
stateStamp[s] = stamp;
|
|
279
|
+
for (const r of outRules[s]) {
|
|
280
|
+
if (ruleStamp[r] !== stamp) {
|
|
281
|
+
ruleStamp[r] = stamp;
|
|
282
|
+
out.push(r);
|
|
283
|
+
found = true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (found) out.sort((a, b) => a - b);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
4
293
|
// src/middleware/patterns.json
|
|
5
294
|
var patterns_default = {
|
|
6
295
|
patterns: [
|
|
@@ -8483,11 +8772,12 @@ function applyPattern(content, regex, groupCount, validate, prefixRun) {
|
|
|
8483
8772
|
result += content.slice(lastIndex);
|
|
8484
8773
|
return { value: result, count };
|
|
8485
8774
|
}
|
|
8486
|
-
function
|
|
8775
|
+
function redactStringSlow(value, patterns, owningKey = null, from = 0) {
|
|
8487
8776
|
let result = value;
|
|
8488
8777
|
let totalCount = 0;
|
|
8489
8778
|
let lower = null;
|
|
8490
|
-
for (
|
|
8779
|
+
for (let idx = from; idx < patterns.length; idx++) {
|
|
8780
|
+
const p = patterns[idx];
|
|
8491
8781
|
if (p.precheck) {
|
|
8492
8782
|
if (lower === null) lower = result.toLowerCase();
|
|
8493
8783
|
let hit = false;
|
|
@@ -8515,6 +8805,69 @@ function redactString(value, patterns, owningKey = null) {
|
|
|
8515
8805
|
}
|
|
8516
8806
|
return { value: result, count: totalCount };
|
|
8517
8807
|
}
|
|
8808
|
+
var engines = /* @__PURE__ */ new WeakMap();
|
|
8809
|
+
var candidateScratch = [];
|
|
8810
|
+
function engineFor(patterns) {
|
|
8811
|
+
let engine = engines.get(patterns);
|
|
8812
|
+
if (!engine) {
|
|
8813
|
+
const minLength = new Int32Array(patterns.length);
|
|
8814
|
+
const literals = patterns.map((p, idx) => {
|
|
8815
|
+
const facts = analyzeRegex(p.regex.source, p.regex.flags);
|
|
8816
|
+
minLength[idx] = facts.minLength;
|
|
8817
|
+
return p.precheck ?? facts.literals;
|
|
8818
|
+
});
|
|
8819
|
+
engine = { prefilter: buildPrefilter(literals), minLength };
|
|
8820
|
+
engines.set(patterns, engine);
|
|
8821
|
+
}
|
|
8822
|
+
return engine;
|
|
8823
|
+
}
|
|
8824
|
+
function redactString(value, patterns, owningKey = null) {
|
|
8825
|
+
const { prefilter, minLength } = engineFor(patterns);
|
|
8826
|
+
const hits = candidateScratch;
|
|
8827
|
+
hits.length = 0;
|
|
8828
|
+
prefilter.candidates(value, hits);
|
|
8829
|
+
const always = prefilter.alwaysRun;
|
|
8830
|
+
let lower = null;
|
|
8831
|
+
let h = 0;
|
|
8832
|
+
let a = 0;
|
|
8833
|
+
while (h < hits.length || a < always.length) {
|
|
8834
|
+
const idx = a >= always.length || h < hits.length && hits[h] < always[a] ? hits[h++] : always[a++];
|
|
8835
|
+
if (value.length < minLength[idx]) continue;
|
|
8836
|
+
const p = patterns[idx];
|
|
8837
|
+
if (p.precheck) {
|
|
8838
|
+
if (lower === null) lower = value.toLowerCase();
|
|
8839
|
+
let hit = false;
|
|
8840
|
+
for (const needle of p.precheck) {
|
|
8841
|
+
if (lower.includes(needle)) {
|
|
8842
|
+
hit = true;
|
|
8843
|
+
break;
|
|
8844
|
+
}
|
|
8845
|
+
}
|
|
8846
|
+
if (!hit) continue;
|
|
8847
|
+
}
|
|
8848
|
+
if (p.context) {
|
|
8849
|
+
const keyHit = owningKey !== null && p.context.test(owningKey);
|
|
8850
|
+
if (!keyHit && (p.keyOnlyContext || !p.context.test(value))) continue;
|
|
8851
|
+
}
|
|
8852
|
+
const applied = applyPattern(
|
|
8853
|
+
value,
|
|
8854
|
+
p.regex,
|
|
8855
|
+
p.groupCount,
|
|
8856
|
+
p.validate,
|
|
8857
|
+
p.prefixRun
|
|
8858
|
+
);
|
|
8859
|
+
if (applied.count > 0) {
|
|
8860
|
+
const rest = redactStringSlow(
|
|
8861
|
+
applied.value,
|
|
8862
|
+
patterns,
|
|
8863
|
+
owningKey,
|
|
8864
|
+
idx + 1
|
|
8865
|
+
);
|
|
8866
|
+
return { value: rest.value, count: applied.count + rest.count };
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
return { value, count: 0 };
|
|
8870
|
+
}
|
|
8518
8871
|
var keyMaskCaches = /* @__PURE__ */ new WeakMap();
|
|
8519
8872
|
var memoStringUnits = /* @__PURE__ */ new WeakMap();
|
|
8520
8873
|
var MAX_MEMO_STRING_UNITS = 8 * 1024 * 1024;
|
|
@@ -8579,42 +8932,63 @@ function walkAndRedactAll(value, patterns, memo, owningKey = null) {
|
|
|
8579
8932
|
}
|
|
8580
8933
|
return { value, count: 0 };
|
|
8581
8934
|
}
|
|
8582
|
-
function
|
|
8935
|
+
function redactJsonlLine(line, patterns, memo) {
|
|
8936
|
+
if (!line.trim()) return { value: line, count: 0 };
|
|
8937
|
+
try {
|
|
8938
|
+
const parsed = JSON.parse(line);
|
|
8939
|
+
const walked = walkAndRedactAll(parsed, patterns, memo);
|
|
8940
|
+
return { value: JSON.stringify(walked.value), count: walked.count };
|
|
8941
|
+
} catch {
|
|
8942
|
+
return redactString(line, patterns);
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
function processFiles(files) {
|
|
8583
8946
|
const patterns = compilePatterns();
|
|
8584
|
-
const
|
|
8585
|
-
for (const { index, content, isJsonl } of
|
|
8947
|
+
const results = [];
|
|
8948
|
+
for (const { index, content, isJsonl } of files) {
|
|
8586
8949
|
let totalCount;
|
|
8587
|
-
let
|
|
8950
|
+
let output2;
|
|
8588
8951
|
if (isJsonl) {
|
|
8589
8952
|
const memo = /* @__PURE__ */ new Map();
|
|
8590
8953
|
let count = 0;
|
|
8591
|
-
const
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
const parsed = JSON.parse(line);
|
|
8596
|
-
const walked = walkAndRedactAll(parsed, patterns, memo);
|
|
8597
|
-
count += walked.count;
|
|
8598
|
-
return JSON.stringify(walked.value);
|
|
8599
|
-
} catch {
|
|
8600
|
-
const r = redactString(line, patterns);
|
|
8601
|
-
count += r.count;
|
|
8602
|
-
return r.value;
|
|
8603
|
-
}
|
|
8954
|
+
const processed = content.split("\n").map((line) => {
|
|
8955
|
+
const r = redactJsonlLine(line, patterns, memo);
|
|
8956
|
+
count += r.count;
|
|
8957
|
+
return r.count > 0 ? r.value : line;
|
|
8604
8958
|
});
|
|
8605
8959
|
totalCount = count;
|
|
8606
|
-
|
|
8960
|
+
output2 = processed.join("\n");
|
|
8607
8961
|
} else {
|
|
8608
8962
|
const r = redactString(content, patterns);
|
|
8609
8963
|
totalCount = r.count;
|
|
8610
|
-
|
|
8964
|
+
output2 = r.value;
|
|
8611
8965
|
}
|
|
8612
|
-
|
|
8966
|
+
results.push({ index, content: output2, count: totalCount });
|
|
8613
8967
|
}
|
|
8614
|
-
return
|
|
8968
|
+
return results;
|
|
8969
|
+
}
|
|
8970
|
+
function processRanges(ranges) {
|
|
8971
|
+
const patterns = compilePatterns();
|
|
8972
|
+
return ranges.map(({ index, bytes, start, end }) => {
|
|
8973
|
+
const memo = /* @__PURE__ */ new Map();
|
|
8974
|
+
const buffer = Buffer.from(
|
|
8975
|
+
bytes.buffer,
|
|
8976
|
+
bytes.byteOffset,
|
|
8977
|
+
bytes.byteLength
|
|
8978
|
+
);
|
|
8979
|
+
const scanned = scanBufferLines(
|
|
8980
|
+
buffer,
|
|
8981
|
+
(line) => redactJsonlLine(line, patterns, memo),
|
|
8982
|
+
{ start, end }
|
|
8983
|
+
);
|
|
8984
|
+
return { index, ...scanned };
|
|
8985
|
+
});
|
|
8615
8986
|
}
|
|
8616
8987
|
|
|
8617
8988
|
// src/middleware/pattern-worker.ts
|
|
8618
|
-
var
|
|
8619
|
-
var
|
|
8620
|
-
|
|
8989
|
+
var job = workerData;
|
|
8990
|
+
var output = {
|
|
8991
|
+
files: processFiles(job.files),
|
|
8992
|
+
ranges: processRanges(job.ranges)
|
|
8993
|
+
};
|
|
8994
|
+
parentPort?.postMessage(output);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hillclimb",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Extract and export AI coding tool logs grouped by repo",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@types/archiver": "^6.0.3",
|
|
45
45
|
"@types/node": "^22.0.0",
|
|
46
46
|
"@typescript/native-preview": "^7.0.0-dev.20260330.1",
|
|
47
|
+
"randexp": "^0.5.3",
|
|
47
48
|
"tsup": "^8.5.1",
|
|
48
49
|
"typescript": "^5.9.3",
|
|
49
50
|
"yaml": "^2.8.3"
|