mergejudge-runner 0.4.0 → 0.4.1
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/README.md +7 -1
- package/dist/cli.js +249 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ mergejudge-runner run --concurrency 2 # capped by the concurrency registered wit
|
|
|
63
63
|
- Network errors, 5xx and 429 back off exponentially with jitter; `Retry-After` is honoured.
|
|
64
64
|
- `SIGINT`/`SIGTERM`: stop polling, give in-flight jobs 30 s to finish, then kill the engine and nack them ("runner shutting down") and exit. A second signal exits immediately.
|
|
65
65
|
- The access token is rotated through `/token/rotate` when it has < 14 days left (or when a response carries an `x-mergejudge-rotate` header). The new token is written to the config file (atomic rename) **before** it is used.
|
|
66
|
-
- Modes: `diff`, `context` and `deep`. **Deep on a runner is a single pass**: the Worker builds a temporary index of the repo (nothing stored, no Cloud AI), looks up callers, importers and imports of the changed code, and sends them as context with the job. The engine still runs with no tools and no checkout, so it cannot browse further. If the repo is too big to index, the review falls back to `context` and says so. Runners older than 0.3.0 do not advertise `deep`, so they are never offered such jobs; upgrade and restart to receive them. With `runner.code_source: local` the runner reads its own clone instead; see below.
|
|
66
|
+
- Modes: `diff`, `context` and `deep`. **Deep on a runner is a single pass**: the Worker builds a temporary index of the repo (nothing stored, no Cloud AI), looks up callers, importers and imports of the changed code (two hops out, hop 1 first), plus files that reference strings, object keys and env var names the diff changed or removed, and sends them as context with the job. The engine still runs with no tools and no checkout, so it cannot browse further. If the repo is too big to index, the review falls back to `context` and says so. Runners older than 0.3.0 do not advertise `deep`, so they are never offered such jobs; upgrade and restart to receive them. With `runner.code_source: local` the runner reads its own clone instead; see below.
|
|
67
67
|
|
|
68
68
|
## Local code (deep mode without code leaving your machine) — runner 0.4.0
|
|
69
69
|
|
|
@@ -121,3 +121,9 @@ Logs go to stderr.
|
|
|
121
121
|
- **Token storage.** The runner token (`mjr_a_...`) is a bearer credential for your installation's review jobs. It lives only in the 0600 config file; it is never logged (logs hold job ids, repo/PR numbers, durations and sizes; a redaction filter masks token shapes as a safety net) and never passed to the engine. Revoke or rotate it in the panel if a machine is lost. The runner follows no redirects, so the token cannot be forwarded to another origin.
|
|
122
122
|
- **What the runner sees.** Job payloads contain the PR diff, title/body, the repo's policy files and (context mode) related files. Nothing is written to disk by the runner except the config file.
|
|
123
123
|
- The `claude` process runs as you. Run the runner under a dedicated OS user if you do not want PR text processed in your main account.
|
|
124
|
+
|
|
125
|
+
## Changelog
|
|
126
|
+
|
|
127
|
+
- **0.4.1** (2026-10-16): local-code deep reviews use the new retrieval from MergeJudge #82: two-hop importers/imports and a repo search for changed string literals, keys and env names, within fixed budgets. No config or protocol changes.
|
|
128
|
+
- **0.4.0**: local code source (`runner.code_source: local`).
|
|
129
|
+
- **0.3.0**: deep mode on the runner (single pass, Worker-built context).
|
package/dist/cli.js
CHANGED
|
@@ -28065,7 +28065,18 @@ ${c.text}`.slice(0, 1500);
|
|
|
28065
28065
|
|
|
28066
28066
|
// ../../src/deepretrieve.ts
|
|
28067
28067
|
var DEEP_RETRIEVE_DEFAULTS = { max_bytes: 8e4, max_files: 60, max_symbols: 8, max_callers: 5, max_file_bytes: 12e3 };
|
|
28068
|
-
var
|
|
28068
|
+
var DEEP_HOP_DEFAULTS = {
|
|
28069
|
+
max_importers: 6,
|
|
28070
|
+
max_hop2_importers: 6,
|
|
28071
|
+
max_hop2_imports: 4,
|
|
28072
|
+
max_examine: 40,
|
|
28073
|
+
max_literals: 12,
|
|
28074
|
+
max_literal_files: 8,
|
|
28075
|
+
literal_common_files: 25,
|
|
28076
|
+
max_scan_bytes: 8e6,
|
|
28077
|
+
literal_piece_bytes: 6e3,
|
|
28078
|
+
max_ms: 3e3
|
|
28079
|
+
};
|
|
28069
28080
|
function snapshotText(snap, path2) {
|
|
28070
28081
|
const f = snap.files[path2];
|
|
28071
28082
|
if (!f) return null;
|
|
@@ -28084,8 +28095,109 @@ function snapshotSource(snap) {
|
|
|
28084
28095
|
searchSymbol: async (sym) => [...new Set(findReferences(snap, sym, 80).map((h) => h.path))]
|
|
28085
28096
|
};
|
|
28086
28097
|
}
|
|
28098
|
+
var baseName = (p) => p.replace(/\.[^./]+$/, "").split("/").pop();
|
|
28099
|
+
var escapeRe2 = (x) => x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28100
|
+
var wholeToken = (lit) => new RegExp(`(?<![\\w$])${escapeRe2(lit)}(?![\\w$])`);
|
|
28101
|
+
var COMMON_LITERALS = /* @__PURE__ */ new Set([
|
|
28102
|
+
"use strict",
|
|
28103
|
+
"utf8",
|
|
28104
|
+
"utf-8",
|
|
28105
|
+
"string",
|
|
28106
|
+
"number",
|
|
28107
|
+
"object",
|
|
28108
|
+
"boolean",
|
|
28109
|
+
"function",
|
|
28110
|
+
"undefined",
|
|
28111
|
+
"default",
|
|
28112
|
+
"error",
|
|
28113
|
+
"data",
|
|
28114
|
+
"json",
|
|
28115
|
+
"true",
|
|
28116
|
+
"false",
|
|
28117
|
+
"null",
|
|
28118
|
+
"none",
|
|
28119
|
+
"type",
|
|
28120
|
+
"name",
|
|
28121
|
+
"value",
|
|
28122
|
+
"values",
|
|
28123
|
+
"test",
|
|
28124
|
+
"index",
|
|
28125
|
+
"main",
|
|
28126
|
+
"node",
|
|
28127
|
+
"content-type",
|
|
28128
|
+
"application/json",
|
|
28129
|
+
"text/plain",
|
|
28130
|
+
"text/html",
|
|
28131
|
+
"GET",
|
|
28132
|
+
"POST",
|
|
28133
|
+
"PUT",
|
|
28134
|
+
"DELETE",
|
|
28135
|
+
"PATCH",
|
|
28136
|
+
"HEAD",
|
|
28137
|
+
"message",
|
|
28138
|
+
"status",
|
|
28139
|
+
"result",
|
|
28140
|
+
"results",
|
|
28141
|
+
"items",
|
|
28142
|
+
"length",
|
|
28143
|
+
"key",
|
|
28144
|
+
"keys",
|
|
28145
|
+
"path",
|
|
28146
|
+
"file",
|
|
28147
|
+
"text",
|
|
28148
|
+
"info",
|
|
28149
|
+
"warn",
|
|
28150
|
+
"debug",
|
|
28151
|
+
"require",
|
|
28152
|
+
"exports",
|
|
28153
|
+
"module",
|
|
28154
|
+
"string",
|
|
28155
|
+
"return",
|
|
28156
|
+
"async",
|
|
28157
|
+
"await",
|
|
28158
|
+
"const",
|
|
28159
|
+
"this",
|
|
28160
|
+
"self",
|
|
28161
|
+
"class"
|
|
28162
|
+
]);
|
|
28163
|
+
function distinct(t) {
|
|
28164
|
+
let sc = Math.min(t.length, 24) / 4;
|
|
28165
|
+
if (/[._:/-]/.test(t)) sc += 3;
|
|
28166
|
+
if (/[a-z][A-Z]/.test(t) || /^[A-Z0-9_]{4,}$/.test(t)) sc += 2;
|
|
28167
|
+
if (/\d/.test(t)) sc += 1;
|
|
28168
|
+
return sc;
|
|
28169
|
+
}
|
|
28170
|
+
function patchLiterals(patch, max = DEEP_HOP_DEFAULTS.max_literals) {
|
|
28171
|
+
if (!patch) return [];
|
|
28172
|
+
const seen = /* @__PURE__ */ new Map();
|
|
28173
|
+
const note = (text, kind, removed) => {
|
|
28174
|
+
if (text.length < 3 || text.length > 80 || /\s/.test(text) || COMMON_LITERALS.has(text) || /^[\d._-]+$/.test(text)) return;
|
|
28175
|
+
if (/^[./]|^@[\w-]+\/|^node:/.test(text)) return;
|
|
28176
|
+
const e = seen.get(text) ?? { removed: false, added: false, kind };
|
|
28177
|
+
if (removed) e.removed = true;
|
|
28178
|
+
else e.added = true;
|
|
28179
|
+
if (kind === "env") e.kind = "env";
|
|
28180
|
+
seen.set(text, e);
|
|
28181
|
+
};
|
|
28182
|
+
for (const line of patch.split("\n")) {
|
|
28183
|
+
if (!/^[+-]/.test(line) || /^(\+\+\+|---)/.test(line) || line.length > 600) continue;
|
|
28184
|
+
const removed = line[0] === "-";
|
|
28185
|
+
const l = line.slice(1);
|
|
28186
|
+
if (/^\s*(?:import\b|export\s[^'"]*\sfrom\b|from\s+\S+\s+import\b)|\brequire\s*\(/.test(l)) continue;
|
|
28187
|
+
for (const m of l.matchAll(/process\.env\.([A-Za-z_]\w*)|import\.meta\.env\.([A-Za-z_]\w*)|\benv\.([A-Z_][A-Z0-9_]*)|(?:process\.env|os\.environ|env)\[\s*['"]([A-Za-z_]\w*)['"]\s*\]|os\.(?:getenv|environ\.get)\(\s*['"]([A-Za-z_]\w*)['"]/g))
|
|
28188
|
+
note(m[1] ?? m[2] ?? m[3] ?? m[4] ?? m[5], "env", removed);
|
|
28189
|
+
for (const m of l.matchAll(/(["'`])((?:\\.|(?!\1)[^\\$])*?)\1/g)) note(m[2], "string", removed);
|
|
28190
|
+
for (const m of l.matchAll(/(?:^|[{,(])\s*["']?([A-Za-z_$][\w$-]{4,})["']?\s*:(?!:)/g)) note(m[1], "key", removed);
|
|
28191
|
+
for (const m of l.matchAll(/\.([a-z][\w$]*[A-Z][\w$]*)\b/g)) note(m[1], "key", removed);
|
|
28192
|
+
}
|
|
28193
|
+
const out = [...seen].map(([text, e]) => ({ text, kind: e.kind, side: e.removed && e.added ? "both" : e.removed ? "removed" : "added" }));
|
|
28194
|
+
const rank = (x) => (x.side === "removed" ? 4 : x.side === "added" ? 2 : 0) + (x.kind === "env" ? 3 : 0) + distinct(x.text);
|
|
28195
|
+
return out.sort((a, b) => rank(b) - rank(a) || (a.text < b.text ? -1 : 1)).slice(0, max);
|
|
28196
|
+
}
|
|
28087
28197
|
async function retrieveDeepContext(snap, files, opts = {}, log = () => {
|
|
28088
28198
|
}) {
|
|
28199
|
+
const t0 = Date.now();
|
|
28200
|
+
const { max_importers, max_hop2_importers, max_hop2_imports, max_examine, max_literals, max_literal_files, literal_common_files, max_scan_bytes, literal_piece_bytes, max_ms } = { ...DEEP_HOP_DEFAULTS, ...opts };
|
|
28089
28201
|
const o = { ...DEEP_RETRIEVE_DEFAULTS, ...opts };
|
|
28090
28202
|
const src = snapshotSource(snap);
|
|
28091
28203
|
const g = await gatherContext(files, src, o, log);
|
|
@@ -28093,26 +28205,138 @@ async function retrieveDeepContext(snap, files, opts = {}, log = () => {
|
|
|
28093
28205
|
let bytes = g.bytes;
|
|
28094
28206
|
const seen = /* @__PURE__ */ new Set([...pieces.map((p) => p.path), ...files.map((f) => f.path)]);
|
|
28095
28207
|
const symbols = [...new Set(files.flatMap((f) => changedSymbols(f.patch)))];
|
|
28096
|
-
|
|
28208
|
+
const timeUp = () => Date.now() - t0 > max_ms;
|
|
28209
|
+
const full = () => o.max_bytes - bytes < 512 || pieces.length >= o.max_files;
|
|
28210
|
+
const stats = { hop1: 0, literal: 0, hop2: 0, scanned: 0, timedOut: false };
|
|
28211
|
+
const addPiece = (path2, text, kind, why, re, cap0 = o.max_file_bytes) => {
|
|
28212
|
+
const cap = Math.min(o.max_bytes - bytes, cap0);
|
|
28213
|
+
const truncated = text.length > cap;
|
|
28214
|
+
const lines = text.split("\n");
|
|
28215
|
+
const hit = re ? lines.flatMap((l, i) => re.test(l) ? [[i + 1, i + 1]] : []) : [];
|
|
28216
|
+
const content = truncated ? excerpt(text, hit.length ? hit : [[1, 40]], cap) : text;
|
|
28217
|
+
pieces.push({ path: path2, kind, why: why + (truncated ? ", excerpt around relevant lines" : ""), content, truncated });
|
|
28218
|
+
bytes += content.length;
|
|
28219
|
+
seen.add(path2);
|
|
28220
|
+
return true;
|
|
28221
|
+
};
|
|
28222
|
+
const symRe = (names) => new RegExp(names.map((n) => escapeRe2(n)).join("|"));
|
|
28223
|
+
const rankCandidates = (cands, needles) => {
|
|
28224
|
+
const out = [];
|
|
28225
|
+
const uniq = [...new Set(cands)].filter((c) => !seen.has(c) && !o.skip?.(c) && !isSensitivePath(c)).sort();
|
|
28226
|
+
const re = needles.length ? symRe(needles) : null;
|
|
28227
|
+
for (const c of uniq.slice(0, max_examine)) {
|
|
28228
|
+
if (timeUp()) {
|
|
28229
|
+
stats.timedOut = true;
|
|
28230
|
+
break;
|
|
28231
|
+
}
|
|
28232
|
+
const text = snapshotText(snap, c);
|
|
28233
|
+
if (text !== null) out.push({ path: c, text, rel: re ? re.test(text) : false });
|
|
28234
|
+
}
|
|
28235
|
+
return out.sort((a, b) => Number(b.rel) - Number(a.rel));
|
|
28236
|
+
};
|
|
28237
|
+
const hop1 = g.pieces.filter((x) => x.kind === "caller").map((x) => ({
|
|
28238
|
+
path: x.path,
|
|
28239
|
+
via: files.find((f) => importersOf(snap, f.path).includes(x.path))?.path ?? files[0]?.path ?? "a changed file"
|
|
28240
|
+
}));
|
|
28097
28241
|
for (const f of files) {
|
|
28098
|
-
if (f.status === "removed") continue;
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28109
|
-
|
|
28110
|
-
|
|
28111
|
-
|
|
28112
|
-
|
|
28113
|
-
|
|
28114
|
-
|
|
28115
|
-
|
|
28242
|
+
if (f.status === "removed" || full() || timeUp()) continue;
|
|
28243
|
+
const needles = [baseName(f.path), ...symbols];
|
|
28244
|
+
for (const c of rankCandidates(importersOf(snap, f.path), needles)) {
|
|
28245
|
+
if (stats.hop1 >= max_importers || full() || seen.has(c.path)) continue;
|
|
28246
|
+
addPiece(c.path, c.text, "caller", `imports ${f.path}`, symRe(needles));
|
|
28247
|
+
hop1.push({ path: c.path, via: f.path });
|
|
28248
|
+
stats.hop1++;
|
|
28249
|
+
}
|
|
28250
|
+
}
|
|
28251
|
+
if (!full() && !timeUp() && max_literals > 0 && max_literal_files > 0) {
|
|
28252
|
+
const lits = /* @__PURE__ */ new Map();
|
|
28253
|
+
for (const f of files) for (const l of patchLiterals(f.patch, max_literals)) {
|
|
28254
|
+
const e = lits.get(l.text);
|
|
28255
|
+
if (e) {
|
|
28256
|
+
e.from.push(f.path);
|
|
28257
|
+
if (e.side !== l.side) e.side = "both";
|
|
28258
|
+
} else lits.set(l.text, { ...l, from: [f.path] });
|
|
28259
|
+
}
|
|
28260
|
+
const list = [...lits.values()].slice(0, max_literals);
|
|
28261
|
+
if (list.length) {
|
|
28262
|
+
const changed = new Set(files.map((f) => f.path));
|
|
28263
|
+
const res = list.map((l) => wholeToken(l.text));
|
|
28264
|
+
const hits = list.map(() => /* @__PURE__ */ new Map());
|
|
28265
|
+
const common = list.map(() => false);
|
|
28266
|
+
let scanned = 0;
|
|
28267
|
+
scan: for (const [path2, fe] of Object.entries(snap.files)) {
|
|
28268
|
+
if (changed.has(path2) || o.skip?.(path2) || isSensitivePath(path2)) continue;
|
|
28269
|
+
if (timeUp()) {
|
|
28270
|
+
stats.timedOut = true;
|
|
28271
|
+
break;
|
|
28272
|
+
}
|
|
28273
|
+
for (const c of fe.chunks) {
|
|
28274
|
+
scanned += c.text.length;
|
|
28275
|
+
if (scanned > max_scan_bytes) break scan;
|
|
28276
|
+
for (let i = 0; i < list.length; i++) {
|
|
28277
|
+
if (common[i] || !c.text.includes(list[i].text) || !res[i].test(c.text)) continue;
|
|
28278
|
+
const m = hits[i];
|
|
28279
|
+
if (!m.has(path2) && m.size >= literal_common_files) {
|
|
28280
|
+
common[i] = true;
|
|
28281
|
+
m.clear();
|
|
28282
|
+
continue;
|
|
28283
|
+
}
|
|
28284
|
+
const lines = m.get(path2) ?? [];
|
|
28285
|
+
c.text.split("\n").forEach((l, j) => {
|
|
28286
|
+
if (l.includes(list[i].text) && res[i].test(l)) lines.push(c.start + j);
|
|
28287
|
+
});
|
|
28288
|
+
m.set(path2, lines);
|
|
28289
|
+
}
|
|
28290
|
+
}
|
|
28291
|
+
}
|
|
28292
|
+
stats.scanned = scanned;
|
|
28293
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
28294
|
+
list.forEach((l, i) => {
|
|
28295
|
+
for (const [path2, lines] of hits[i]) {
|
|
28296
|
+
const e = byFile.get(path2) ?? { score: 0, lits: [], lines: [] };
|
|
28297
|
+
e.score += (l.side === "removed" ? 3 : l.side === "both" ? 1 : 2) + distinct(l.text) / 10;
|
|
28298
|
+
e.lits.push(i);
|
|
28299
|
+
e.lines.push(...lines);
|
|
28300
|
+
byFile.set(path2, e);
|
|
28301
|
+
}
|
|
28302
|
+
});
|
|
28303
|
+
const near = new Set(hop1.map((h) => h.path));
|
|
28304
|
+
const ranked = [...byFile].filter(([p]) => !seen.has(p)).sort((a, b) => b[1].score - a[1].score || Number(near.has(b[0])) - Number(near.has(a[0])) || (a[0] < b[0] ? -1 : 1));
|
|
28305
|
+
for (const [path2, e] of ranked) {
|
|
28306
|
+
if (stats.literal >= max_literal_files || full()) break;
|
|
28307
|
+
const text = snapshotText(snap, path2);
|
|
28308
|
+
if (text === null) continue;
|
|
28309
|
+
const names = e.lits.slice(0, 3).map((i) => `${JSON.stringify(list[i].text)} (${list[i].side === "removed" ? "removed" : list[i].side === "added" ? "added" : "changed"} in ${list[i].from[0]})`);
|
|
28310
|
+
const re = symRe(e.lits.map((i) => list[i].text));
|
|
28311
|
+
addPiece(path2, text, "caller", `references literal ${names.join(", ")} changed in this PR; may depend on the old or new value`, re, literal_piece_bytes);
|
|
28312
|
+
stats.literal++;
|
|
28313
|
+
}
|
|
28314
|
+
}
|
|
28315
|
+
}
|
|
28316
|
+
if (!full() && !timeUp() && (max_hop2_importers > 0 || max_hop2_imports > 0)) {
|
|
28317
|
+
for (const h of hop1) {
|
|
28318
|
+
if (stats.hop2 >= max_hop2_importers || full()) break;
|
|
28319
|
+
const needles = [baseName(h.path), ...symbols];
|
|
28320
|
+
for (const c of rankCandidates(importersOf(snap, h.path), needles)) {
|
|
28321
|
+
if (stats.hop2 >= max_hop2_importers || full()) break;
|
|
28322
|
+
addPiece(c.path, c.text, "caller", `imports ${h.path}, which imports ${h.via} (2 hops from the changed file)`, symRe(needles));
|
|
28323
|
+
stats.hop2++;
|
|
28324
|
+
}
|
|
28325
|
+
}
|
|
28326
|
+
let imp2 = 0;
|
|
28327
|
+
const pathSet = new Set(Object.keys(snap.files));
|
|
28328
|
+
for (const p of g.pieces.filter((x) => x.kind === "import")) {
|
|
28329
|
+
if (imp2 >= max_hop2_imports || full() || timeUp()) break;
|
|
28330
|
+
const targets = (snap.files[p.path]?.imports ?? []).map((spec) => resolveImport(p.path, spec, pathSet)).filter((t) => !!t);
|
|
28331
|
+
for (const c of rankCandidates(targets, symbols)) {
|
|
28332
|
+
if (imp2 >= max_hop2_imports || full()) break;
|
|
28333
|
+
addPiece(c.path, c.text, "import", `imported by ${p.path}, which a changed file imports (2 hops)`, symbols.length ? symRe(symbols) : null);
|
|
28334
|
+
imp2++;
|
|
28335
|
+
stats.hop2++;
|
|
28336
|
+
}
|
|
28337
|
+
}
|
|
28338
|
+
}
|
|
28339
|
+
log(`deep: ${pieces.length} piece(s), ${bytes} bytes retrieved from the index (hop1 importers ${stats.hop1}, literal refs ${stats.literal}, hop2 ${stats.hop2}, ${stats.scanned} bytes scanned, ${Date.now() - t0} ms${stats.timedOut ? ", time budget hit" : ""})`);
|
|
28116
28340
|
return { pieces, bytes };
|
|
28117
28341
|
}
|
|
28118
28342
|
|
|
@@ -30172,13 +30396,14 @@ function scanSecrets(patch, path2 = "") {
|
|
|
30172
30396
|
function findSecrets(patch, path2 = "") {
|
|
30173
30397
|
return scanSecrets(patch, path2).blocked;
|
|
30174
30398
|
}
|
|
30175
|
-
var CODE_AUTO_NEVER_GLOBS = [".github/**", "**/.env*"];
|
|
30399
|
+
var CODE_AUTO_NEVER_GLOBS = [".github/**", "**/.env*", "_headers", "**/_headers"];
|
|
30400
|
+
var ALWAYS_REVIEW_GLOBS = ["_headers", "**/_headers"];
|
|
30176
30401
|
var CODE_AUTO_NEVER_EXT = /* @__PURE__ */ new Set(["sql", "sh", "bash", "zsh", "ps1", "wasm"]);
|
|
30177
30402
|
function codeAutoBlocker(file2, paths, changed, ca) {
|
|
30178
30403
|
if (file2.status === "removed" || file2.status === "renamed" || file2.previousPath) return "code file deleted or renamed (needs human review)";
|
|
30179
30404
|
if (file2.status !== "added" && file2.status !== "modified") return `code file with status ${file2.status} (needs human review)`;
|
|
30180
30405
|
if (paths.some((p) => matchesAny(p, CODE_AUTO_NEVER_GLOBS) || CODE_AUTO_NEVER_EXT.has(p.split(".").pop().toLowerCase())))
|
|
30181
|
-
return "code file type never eligible for lanes.code_auto (CI, env, shell, SQL, wasm)";
|
|
30406
|
+
return "code file type never eligible for lanes.code_auto (CI, env, headers, shell, SQL, wasm)";
|
|
30182
30407
|
if (ca.paths.length && !matchesAny(file2.path, ca.paths)) return "code file outside lanes.code_auto.paths";
|
|
30183
30408
|
if (changed > ca.max_lines) return `code file with ${changed} changed lines (lanes.code_auto.max_lines ${ca.max_lines})`;
|
|
30184
30409
|
return void 0;
|
|
@@ -30199,6 +30424,7 @@ function classifyFileInner(file2, cfg) {
|
|
|
30199
30424
|
const secrets = findSecrets(file2.patch, file2.path);
|
|
30200
30425
|
if (secrets.length) raise("block", `possible secret in added lines (${secrets.join(", ")})`);
|
|
30201
30426
|
if (paths.some((p) => matchesAny(p, cfg.lanes.protected))) raise("review", "protected path");
|
|
30427
|
+
else if (paths.some((p) => matchesAny(p, ALWAYS_REVIEW_GLOBS))) raise("review", "protected path (hosting headers file)");
|
|
30202
30428
|
const ca = cfg.lanes.code_auto;
|
|
30203
30429
|
const codeCandidate = !!ca?.enabled && paths.some(isCodeFile);
|
|
30204
30430
|
if (!codeCandidate && paths.some(isCodeFile)) raise("review", "code file (always needs human review)");
|
|
@@ -32284,7 +32510,7 @@ function installAutostart(d, bundle) {
|
|
|
32284
32510
|
}
|
|
32285
32511
|
|
|
32286
32512
|
// src/version.ts
|
|
32287
|
-
var VERSION = "0.4.
|
|
32513
|
+
var VERSION = "0.4.1";
|
|
32288
32514
|
var MODES = ["diff", "context", "deep"];
|
|
32289
32515
|
var FEATURES = ["local-code"];
|
|
32290
32516
|
|
package/package.json
CHANGED