@jphutchins/code-review 0.1.0-alpha.37 → 0.1.0-alpha.39
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/index.js +76 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/comment.eta +7 -2
package/dist/index.js
CHANGED
|
@@ -338,7 +338,8 @@ var parseFindingsMarker = (body) => {
|
|
|
338
338
|
return decodeBase64Json(b64) ?? null;
|
|
339
339
|
};
|
|
340
340
|
var ROUNDS_RE = /<!-- code-review:rounds;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
341
|
-
var
|
|
341
|
+
var SEVERITIES = ["critical", "major", "minor", "nit"];
|
|
342
|
+
var isSeverityCounts = (u) => typeof u === "object" && u !== null && SEVERITIES.every((k) => {
|
|
342
343
|
const v = u[k];
|
|
343
344
|
return typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
344
345
|
});
|
|
@@ -350,7 +351,7 @@ var parseRounds = (body) => {
|
|
|
350
351
|
};
|
|
351
352
|
var roundsMarker = (rounds) => rounds.length === 0 ? "" : `<!-- code-review:rounds;base64 ${Buffer.from(JSON.stringify(rounds), "utf-8").toString("base64")} -->`;
|
|
352
353
|
var roundChip = (c) => {
|
|
353
|
-
const parts =
|
|
354
|
+
const parts = SEVERITIES.filter((k) => c[k] > 0).map((k) => `${severityEmoji(k)}${String(c[k])}`);
|
|
354
355
|
return parts.length === 0 ? "clean" : parts.join(" ");
|
|
355
356
|
};
|
|
356
357
|
var TRAJECTORY_CHIPS = 8;
|
|
@@ -360,6 +361,13 @@ var roundsSummary = (rounds) => {
|
|
|
360
361
|
const trajectory = rounds.length > TRAJECTORY_CHIPS ? `\u2026 \u2192 ${chips.join(" \u2192 ")}` : chips.join(" \u2192 ");
|
|
361
362
|
return `**Round ${String(rounds.length)}** \xB7 ${trajectory}`;
|
|
362
363
|
};
|
|
364
|
+
var CONVERGENCE_WEIGHTS = { critical: 4, major: 2, minor: 1, nit: 0 };
|
|
365
|
+
var DEFAULT_CONVERGENCE_THRESHOLD = 1;
|
|
366
|
+
var convergenceScore = (counts) => SEVERITIES.reduce((sum, k) => sum + counts[k] * CONVERGENCE_WEIGHTS[k], 0);
|
|
367
|
+
var convergenceSummary = (counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
368
|
+
const score = convergenceScore(counts);
|
|
369
|
+
return score <= threshold ? `**Convergence** \u{1F3C1} ${String(score)} \u2264 ${String(threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${String(score)} > ${String(threshold)} \u2014 iterating`;
|
|
370
|
+
};
|
|
363
371
|
var carryForwardMarkers = (body) => {
|
|
364
372
|
const findings = /<!-- code-review:findings-json[^>]*-->/.exec(body)?.[0];
|
|
365
373
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
@@ -402,6 +410,7 @@ var computeSeverityCounts = (findings) => findings.reduce(
|
|
|
402
410
|
(acc, f) => f.severity in acc ? { ...acc, [f.severity]: acc[f.severity] + 1 } : acc,
|
|
403
411
|
emptySeverityCounts()
|
|
404
412
|
);
|
|
413
|
+
var isConvergenceRound = (route, incomplete) => route === "full review" && !incomplete;
|
|
405
414
|
var render = (input) => {
|
|
406
415
|
const eta = new Eta({ autoTrim: false });
|
|
407
416
|
const usageAvailable = input.envelope !== null;
|
|
@@ -412,6 +421,9 @@ var render = (input) => {
|
|
|
412
421
|
const route = input.route ?? input.envelope?.route ?? null;
|
|
413
422
|
const effort = input.effort ?? input.envelope?.effort ?? null;
|
|
414
423
|
const modelNames = input.envelope ? input.envelope.models.map((m) => m.model).join(", ") : "";
|
|
424
|
+
const severityCounts = input.severityCounts ?? computeSeverityCounts(input.findings.findings);
|
|
425
|
+
const rounds = input.rounds ?? [];
|
|
426
|
+
const isFullReviewRound = (input.convergenceRound ?? isConvergenceRound(route, incomplete)) && input.findings.verdict !== "error";
|
|
415
427
|
return eta.renderString(input.template, {
|
|
416
428
|
findings: input.findings,
|
|
417
429
|
envelope: input.envelope,
|
|
@@ -426,15 +438,16 @@ var render = (input) => {
|
|
|
426
438
|
testReport: input.testReport ?? null,
|
|
427
439
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
428
440
|
postedAt: input.postedAt ?? "",
|
|
429
|
-
severityCounts
|
|
441
|
+
severityCounts,
|
|
442
|
+
convergenceSummary: isFullReviewRound ? convergenceSummary(severityCounts, input.convergenceThreshold) : "",
|
|
430
443
|
strays: (input.strays ?? []).map(sanitizeFinding),
|
|
431
444
|
unanchoredCount: input.unanchoredCount ?? 0,
|
|
432
445
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
433
446
|
runUrl: input.runUrl ?? null,
|
|
434
447
|
jsonUrl: input.jsonUrl ?? null,
|
|
435
448
|
findingsPointer: input.findingsPointer ?? findingsPointer(input.findings, input.jsonUrl),
|
|
436
|
-
roundsMarker: roundsMarker(
|
|
437
|
-
roundsSummary: roundsSummary(
|
|
449
|
+
roundsMarker: roundsMarker(rounds),
|
|
450
|
+
roundsSummary: roundsSummary(rounds),
|
|
438
451
|
reviewUrl: input.reviewUrl ?? null,
|
|
439
452
|
formatTokens: (n) => Number.isFinite(n) && n >= 0 ? n.toLocaleString("en-US") : "\u2014",
|
|
440
453
|
// N/A (never a false $0.00) when no real price map was provided — real tokens, no rates to price them.
|
|
@@ -1622,6 +1635,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1622
1635
|
reviewedSha: input.headSha,
|
|
1623
1636
|
effort: input.effort,
|
|
1624
1637
|
rounds: priorRounds,
|
|
1638
|
+
convergenceRound: false,
|
|
1625
1639
|
runUrl: input.runUrl,
|
|
1626
1640
|
jsonUrl: input.jsonUrl,
|
|
1627
1641
|
postedAt: input.postedAt
|
|
@@ -1668,6 +1682,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1668
1682
|
reviewedSha: input.headSha,
|
|
1669
1683
|
effort: input.effort,
|
|
1670
1684
|
rounds: priorRounds,
|
|
1685
|
+
convergenceRound: false,
|
|
1671
1686
|
testReport,
|
|
1672
1687
|
inlineDisposition: { kind: "no-envelope" },
|
|
1673
1688
|
runUrl: input.runUrl,
|
|
@@ -1705,7 +1720,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1705
1720
|
const initialDisposition = comments.length === 0 && strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
1706
1721
|
const currentCounts = computeSeverityCounts(findings.findings);
|
|
1707
1722
|
const effectiveRoute = input.route ?? envelope.route;
|
|
1708
|
-
const
|
|
1723
|
+
const isRound = isConvergenceRound(effectiveRoute, thisIncomplete);
|
|
1724
|
+
const rounds = isRound ? [...priorRounds, currentCounts] : priorRounds;
|
|
1709
1725
|
const commonRenderInput = {
|
|
1710
1726
|
findings,
|
|
1711
1727
|
envelope,
|
|
@@ -1719,6 +1735,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1719
1735
|
testReport,
|
|
1720
1736
|
severityCounts: currentCounts,
|
|
1721
1737
|
rounds,
|
|
1738
|
+
convergenceThreshold: input.convergenceThreshold,
|
|
1739
|
+
convergenceRound: isRound,
|
|
1722
1740
|
strays,
|
|
1723
1741
|
runUrl: input.runUrl,
|
|
1724
1742
|
jsonUrl: input.jsonUrl,
|
|
@@ -2809,6 +2827,12 @@ var deriveModelHost = (apiBaseUrl) => {
|
|
|
2809
2827
|
}
|
|
2810
2828
|
return host;
|
|
2811
2829
|
};
|
|
2830
|
+
var KNOWN_MODEL_HOST_SUFFIXES = ["anthropic.com", "deepseek.com"];
|
|
2831
|
+
var isKnownModelHost = (host, declared = []) => {
|
|
2832
|
+
const normalize = (h) => h.replace(/\.$/, "").toLowerCase();
|
|
2833
|
+
const target = normalize(host);
|
|
2834
|
+
return declared.some((d) => normalize(d) === target) || KNOWN_MODEL_HOST_SUFFIXES.some((suffix) => target.endsWith(`.${suffix}`));
|
|
2835
|
+
};
|
|
2812
2836
|
var parseExtraEndpoints = (extra) => extra.split(/\s+/).map((token) => token.replace(/:\d+$/, "")).filter((token) => token.length > 0);
|
|
2813
2837
|
var buildSandboxConfig = (opts) => ({
|
|
2814
2838
|
network: {
|
|
@@ -2932,6 +2956,7 @@ var resolvePrices = (pricesArg) => {
|
|
|
2932
2956
|
return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
|
|
2933
2957
|
};
|
|
2934
2958
|
var TEST_REPORT_DESCRIPTION = 'Path to a JSON test summary: {"passed": number, "failed": number, "total": number, "failures"?: [{"name": string, "message"?: string}]}';
|
|
2959
|
+
var CONVERGENCE_THRESHOLD_DESCRIPTION = "Advisory convergence tolerance: the weighted-severity score (critical 4 \xB7 major 2 \xB7 minor 1 \xB7 nit 0) at or below which the sticky reads as converged (default: 1 \u2014 unlimited nits plus at most one minor)";
|
|
2935
2960
|
var renderCmd = defineCommand({
|
|
2936
2961
|
meta: {
|
|
2937
2962
|
name: "render",
|
|
@@ -2971,6 +2996,10 @@ var renderCmd = defineCommand({
|
|
|
2971
2996
|
"test-report": {
|
|
2972
2997
|
type: "string",
|
|
2973
2998
|
description: TEST_REPORT_DESCRIPTION
|
|
2999
|
+
},
|
|
3000
|
+
"convergence-threshold": {
|
|
3001
|
+
type: "string",
|
|
3002
|
+
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
2974
3003
|
}
|
|
2975
3004
|
},
|
|
2976
3005
|
run: async ({ args }) => {
|
|
@@ -2991,6 +3020,7 @@ var renderCmd = defineCommand({
|
|
|
2991
3020
|
route: args.route,
|
|
2992
3021
|
effort: args.effort,
|
|
2993
3022
|
testReport,
|
|
3023
|
+
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
2994
3024
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
2995
3025
|
});
|
|
2996
3026
|
process.stdout.write(output2);
|
|
@@ -3111,6 +3141,18 @@ var parseBudgetUsd = (raw) => {
|
|
|
3111
3141
|
const n = Number.parseFloat(raw);
|
|
3112
3142
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
3113
3143
|
};
|
|
3144
|
+
var parseConvergenceThreshold = (raw) => {
|
|
3145
|
+
const trimmed = raw?.trim();
|
|
3146
|
+
if (trimmed === void 0 || trimmed === "") return void 0;
|
|
3147
|
+
if (!/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
3148
|
+
fail(`--convergence-threshold must be a non-negative number; got "${trimmed}"`);
|
|
3149
|
+
}
|
|
3150
|
+
const n = Number.parseFloat(trimmed);
|
|
3151
|
+
if (!Number.isFinite(n)) {
|
|
3152
|
+
fail(`--convergence-threshold is too large to be a meaningful tolerance; got "${trimmed}"`);
|
|
3153
|
+
}
|
|
3154
|
+
return n;
|
|
3155
|
+
};
|
|
3114
3156
|
var mtimeMsOf = (path) => {
|
|
3115
3157
|
try {
|
|
3116
3158
|
return statSync(path).mtimeMs;
|
|
@@ -3957,6 +3999,10 @@ var postCmd = defineCommand({
|
|
|
3957
3999
|
"json-url": {
|
|
3958
4000
|
type: "string",
|
|
3959
4001
|
description: "URL to the machine-readable findings JSON artifact, pointed at from the sticky and each inline comment"
|
|
4002
|
+
},
|
|
4003
|
+
"convergence-threshold": {
|
|
4004
|
+
type: "string",
|
|
4005
|
+
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
3960
4006
|
}
|
|
3961
4007
|
},
|
|
3962
4008
|
run: async ({ args }) => {
|
|
@@ -3977,6 +4023,7 @@ var postCmd = defineCommand({
|
|
|
3977
4023
|
testReportPath: args["test-report"],
|
|
3978
4024
|
runUrl: args["run-url"],
|
|
3979
4025
|
jsonUrl: args["json-url"],
|
|
4026
|
+
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
3980
4027
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
3981
4028
|
});
|
|
3982
4029
|
}
|
|
@@ -4292,9 +4339,32 @@ var sandboxConfigCmd = defineCommand({
|
|
|
4292
4339
|
out: {
|
|
4293
4340
|
type: "string",
|
|
4294
4341
|
description: "Write the settings JSON here instead of stdout"
|
|
4342
|
+
},
|
|
4343
|
+
"known-model-host": {
|
|
4344
|
+
type: "string",
|
|
4345
|
+
description: "Additional model HOST(S) to treat as known (space-separated bare hostnames, not URLs) \u2014 the consumer's declared host(s) for a provider outside the built-in set; a derived host outside the built-ins and this list warns (or fails under --strict-host)"
|
|
4346
|
+
},
|
|
4347
|
+
"strict-host": {
|
|
4348
|
+
type: "boolean",
|
|
4349
|
+
description: "Fail (instead of warning) when the model host derived from api_base_url is not a well-known or declared host \u2014 closes the fail-open-on-typo gap where a mistyped api_base_url would still be allowlisted and sent the key"
|
|
4295
4350
|
}
|
|
4296
4351
|
},
|
|
4297
4352
|
run: ({ args }) => {
|
|
4353
|
+
const modelHost = deriveModelHost(args["api-base-url"]);
|
|
4354
|
+
const declaredHosts = [
|
|
4355
|
+
...args["known-model-host"] ? parseExtraEndpoints(args["known-model-host"]) : [],
|
|
4356
|
+
...args.extra ? parseExtraEndpoints(args.extra) : []
|
|
4357
|
+
];
|
|
4358
|
+
if (!isKnownModelHost(modelHost, declaredHosts)) {
|
|
4359
|
+
const message = `code-review sandbox-config: derived model host "${modelHost}" is not a well-known or declared host \u2014 the jail will allow egress to it and send MODEL_API_KEY there; verify api_base_url is correct`;
|
|
4360
|
+
if (args["strict-host"]) {
|
|
4361
|
+
process.stderr.write(`::error::${annotationSafe(message)}
|
|
4362
|
+
`);
|
|
4363
|
+
process.exit(1);
|
|
4364
|
+
}
|
|
4365
|
+
process.stderr.write(`::warning::${annotationSafe(message)}
|
|
4366
|
+
`);
|
|
4367
|
+
}
|
|
4298
4368
|
const config = buildSandboxConfig({ apiBaseUrl: args["api-base-url"], extra: args.extra });
|
|
4299
4369
|
const json = `${JSON.stringify(config, null, 2)}
|
|
4300
4370
|
`;
|