@weareikko/code-review 0.8.4 → 0.8.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/{cli-FDpPZaNn.js → cli-DmzA9PxS.js} +326 -53
- package/dist/cli-DmzA9PxS.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +9 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/git-tool.d.ts +29 -0
- package/dist/git-tool.d.ts.map +1 -0
- package/dist/gitlab-review.d.ts +29 -1
- package/dist/gitlab-review.d.ts.map +1 -1
- package/dist/review.js +1 -1
- package/dist/skipped-retrieval.d.ts +8 -3
- package/dist/skipped-retrieval.d.ts.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.d.ts +2 -1
- package/dist/verify.d.ts.map +1 -1
- package/package.json +4 -1
- package/dist/cli-FDpPZaNn.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import nodeFs, { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { getEnvApiKey, getModel } from "@earendil-works/pi-ai";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { tracingChannel } from "node:diagnostics_channel";
|
|
@@ -10,6 +10,9 @@ import { execFile } from "node:child_process";
|
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
12
12
|
import { createReadOnlyTools } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { createTwoFilesPatch } from "diff";
|
|
14
|
+
import * as git from "isomorphic-git";
|
|
15
|
+
import { Type } from "typebox";
|
|
13
16
|
import { homedir } from "node:os";
|
|
14
17
|
import { parse } from "yaml";
|
|
15
18
|
import { SpanKind, SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
@@ -548,7 +551,7 @@ function buildSummaryBody(summary, costFooter, options = {}) {
|
|
|
548
551
|
return `${withFooter}\n\n${buildSummaryHistoryBlock(historyEntries)}`;
|
|
549
552
|
}
|
|
550
553
|
function buildReviewedCommitFooter(commitSha) {
|
|
551
|
-
return `Reviewed by ${PRODUCT_LINK} v0.8.
|
|
554
|
+
return `Reviewed by ${PRODUCT_LINK} v0.8.5 for commit ${commitSha}.`;
|
|
552
555
|
}
|
|
553
556
|
function extractReviewedCommitSha(body) {
|
|
554
557
|
return REVIEWED_COMMIT_FOOTER_PATTERN.exec(body)?.[1] ?? null;
|
|
@@ -800,6 +803,12 @@ var REVIEW_DEPTHS = [
|
|
|
800
803
|
"verify",
|
|
801
804
|
"full"
|
|
802
805
|
];
|
|
806
|
+
var REVIEW_INPUT_MODES = [
|
|
807
|
+
"auto",
|
|
808
|
+
"inline",
|
|
809
|
+
"disk",
|
|
810
|
+
"commits"
|
|
811
|
+
];
|
|
803
812
|
var THINKING_LEVELS = [
|
|
804
813
|
"off",
|
|
805
814
|
"minimal",
|
|
@@ -1211,6 +1220,7 @@ function resolveConfig(argv = process.argv.slice(2), env = process.env) {
|
|
|
1211
1220
|
minSeverity: normalizeChoice(args.minSeverity ?? env.CODE_REVIEW_MIN_SEVERITY ?? "info"),
|
|
1212
1221
|
thinkingLevel: normalizeChoice(args.thinking ?? env.CODE_REVIEW_THINKING_LEVEL ?? "off"),
|
|
1213
1222
|
reviewDepth: normalizeChoice(args.reviewDepth ?? env.CODE_REVIEW_DEPTH ?? "single"),
|
|
1223
|
+
inputMode: normalizeChoice(args.inputMode ?? env.CODE_REVIEW_INPUT_MODE ?? "auto"),
|
|
1214
1224
|
verifyModel: String(args.verifyModel ?? env.CODE_REVIEW_VERIFY_MODEL ?? ""),
|
|
1215
1225
|
postingMode: normalizeChoice(args.postingMode ?? env.CODE_REVIEW_POSTING_MODE ?? "direct"),
|
|
1216
1226
|
apiKey,
|
|
@@ -1268,6 +1278,7 @@ function validateConfig(config) {
|
|
|
1268
1278
|
].includes(config.minSeverity)) throw new ConfigError("--min-severity must be one of: info, warn, critical");
|
|
1269
1279
|
if (!THINKING_LEVELS.includes(config.thinkingLevel)) throw new ConfigError(`--thinking must be one of: ${THINKING_LEVELS.join(", ")}`);
|
|
1270
1280
|
if (!REVIEW_DEPTHS.includes(config.reviewDepth)) throw new ConfigError(`--review-depth must be one of: ${REVIEW_DEPTHS.join(", ")}`);
|
|
1281
|
+
if (config.inputMode !== void 0 && !REVIEW_INPUT_MODES.includes(config.inputMode)) throw new ConfigError(`--input-mode must be one of: ${REVIEW_INPUT_MODES.join(", ")}`);
|
|
1271
1282
|
if (!POSTING_MODES.includes(config.postingMode)) throw new ConfigError(`--posting-mode must be one of: ${POSTING_MODES.join(", ")}`);
|
|
1272
1283
|
}
|
|
1273
1284
|
//#endregion
|
|
@@ -1401,7 +1412,7 @@ function gitErrorMessage(error) {
|
|
|
1401
1412
|
err.stdout
|
|
1402
1413
|
].filter(Boolean).join("\n").trim();
|
|
1403
1414
|
}
|
|
1404
|
-
async function git(args, options = {}) {
|
|
1415
|
+
async function git$1(args, options = {}) {
|
|
1405
1416
|
try {
|
|
1406
1417
|
const { stdout } = await exec$1("git", args, {
|
|
1407
1418
|
cwd: options.cwd,
|
|
@@ -1443,7 +1454,7 @@ function getMergeDiffCommand(targetBranch, options = {}) {
|
|
|
1443
1454
|
];
|
|
1444
1455
|
}
|
|
1445
1456
|
async function fetchBranch(remote, branch, options) {
|
|
1446
|
-
await git([
|
|
1457
|
+
await git$1([
|
|
1447
1458
|
"fetch",
|
|
1448
1459
|
"--no-tags",
|
|
1449
1460
|
remote,
|
|
@@ -1452,7 +1463,7 @@ async function fetchBranch(remote, branch, options) {
|
|
|
1452
1463
|
}
|
|
1453
1464
|
async function isTracked(path, options) {
|
|
1454
1465
|
try {
|
|
1455
|
-
await git([
|
|
1466
|
+
await git$1([
|
|
1456
1467
|
"ls-files",
|
|
1457
1468
|
"--error-unmatch",
|
|
1458
1469
|
"--",
|
|
@@ -1479,7 +1490,7 @@ async function removeGeneratedCodeQualityArtifacts(paths = DEFAULT_CODEQUALITY_A
|
|
|
1479
1490
|
async function prepareGitHistory(sourceBranch, targetBranch, options = {}) {
|
|
1480
1491
|
const remote = options.remote ?? "origin";
|
|
1481
1492
|
await removeGeneratedCodeQualityArtifacts(options.codeQualityArtifacts, options);
|
|
1482
|
-
await git([
|
|
1493
|
+
await git$1([
|
|
1483
1494
|
"fetch",
|
|
1484
1495
|
"--unshallow",
|
|
1485
1496
|
"--no-tags",
|
|
@@ -1493,7 +1504,7 @@ async function prepareGitHistory(sourceBranch, targetBranch, options = {}) {
|
|
|
1493
1504
|
}
|
|
1494
1505
|
if (fetchErrors.length === 2) throw new GitError(`Unable to fetch MR source/target branches from ${remote}.`, { hint: fetchErrors.join("\n") });
|
|
1495
1506
|
try {
|
|
1496
|
-
await git([
|
|
1507
|
+
await git$1([
|
|
1497
1508
|
"merge-base",
|
|
1498
1509
|
remoteRef(remote, targetBranch),
|
|
1499
1510
|
"HEAD"
|
|
@@ -1507,7 +1518,7 @@ async function prepareGitHistory(sourceBranch, targetBranch, options = {}) {
|
|
|
1507
1518
|
}
|
|
1508
1519
|
}
|
|
1509
1520
|
async function getMergeDiff(targetBranch, options = {}) {
|
|
1510
|
-
return git(getMergeDiffCommand(targetBranch, options), options);
|
|
1521
|
+
return git$1(getMergeDiffCommand(targetBranch, options), options);
|
|
1511
1522
|
}
|
|
1512
1523
|
function getMergeCommitLogArguments(targetBranch, options = {}) {
|
|
1513
1524
|
return [
|
|
@@ -1518,7 +1529,7 @@ function getMergeCommitLogArguments(targetBranch, options = {}) {
|
|
|
1518
1529
|
];
|
|
1519
1530
|
}
|
|
1520
1531
|
async function getMergeCommitLog(targetBranch, options = {}) {
|
|
1521
|
-
return git(["log", ...getMergeCommitLogArguments(targetBranch, options)], options);
|
|
1532
|
+
return git$1(["log", ...getMergeCommitLogArguments(targetBranch, options)], options);
|
|
1522
1533
|
}
|
|
1523
1534
|
/**
|
|
1524
1535
|
* Summarize a unified diff into file/line counts for telemetry. Counts one file
|
|
@@ -1546,6 +1557,171 @@ function summarizeDiff(diff) {
|
|
|
1546
1557
|
};
|
|
1547
1558
|
}
|
|
1548
1559
|
//#endregion
|
|
1560
|
+
//#region src/git-tool.ts
|
|
1561
|
+
/**
|
|
1562
|
+
* Custom read-only git tools for the reviewer agent (Mode C: commit exploration).
|
|
1563
|
+
*
|
|
1564
|
+
* The reviewer's default toolbox (`createReadOnlyTools`) has no git access, and a
|
|
1565
|
+
* general bash tool is unsafe here — the reviewer processes attacker-controlled
|
|
1566
|
+
* MR content with CI credentials in the environment. These tools expose only
|
|
1567
|
+
* three read-only operations, backed by isomorphic-git (pure JS, no shell-out,
|
|
1568
|
+
* so there is no command-injection surface) and scoped to a single repo dir:
|
|
1569
|
+
*
|
|
1570
|
+
* - `git_log`: list commits (optionally the range since a base ref).
|
|
1571
|
+
* - `git_show`: a commit's message + unified diff against its first parent.
|
|
1572
|
+
* - `git_diff`: unified diff between two refs.
|
|
1573
|
+
*
|
|
1574
|
+
* Ref arguments are validated to sha/ref shapes; nothing is passed to a shell.
|
|
1575
|
+
*/
|
|
1576
|
+
/** Accept 4-40 hex sha prefixes or conservative ref names (branch/tag/HEAD~n). */
|
|
1577
|
+
var REF_RE = /^[0-9a-zA-Z._/~^-]{1,120}$/;
|
|
1578
|
+
var MAX_PATCH_BYTES = 2e5;
|
|
1579
|
+
function assertRef(ref) {
|
|
1580
|
+
if (!REF_RE.test(ref)) throw new Error(`Invalid git ref: ${JSON.stringify(ref)}`);
|
|
1581
|
+
}
|
|
1582
|
+
function text(body) {
|
|
1583
|
+
return {
|
|
1584
|
+
content: [{
|
|
1585
|
+
type: "text",
|
|
1586
|
+
text: body
|
|
1587
|
+
}],
|
|
1588
|
+
details: void 0
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
async function resolveOid(dir, fs, ref) {
|
|
1592
|
+
assertRef(ref);
|
|
1593
|
+
return git.resolveRef({
|
|
1594
|
+
fs,
|
|
1595
|
+
dir,
|
|
1596
|
+
ref
|
|
1597
|
+
}).catch(() => git.expandOid({
|
|
1598
|
+
fs,
|
|
1599
|
+
dir,
|
|
1600
|
+
oid: ref
|
|
1601
|
+
}));
|
|
1602
|
+
}
|
|
1603
|
+
function decode(data) {
|
|
1604
|
+
if (data.includes(0)) return null;
|
|
1605
|
+
return Buffer.from(data).toString("utf8");
|
|
1606
|
+
}
|
|
1607
|
+
async function readFileAt(dir, fs, oid, filepath) {
|
|
1608
|
+
try {
|
|
1609
|
+
const { blob } = await git.readBlob({
|
|
1610
|
+
fs,
|
|
1611
|
+
dir,
|
|
1612
|
+
oid,
|
|
1613
|
+
filepath
|
|
1614
|
+
});
|
|
1615
|
+
return decode(blob);
|
|
1616
|
+
} catch {
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
/** Unified diff of every changed file between two commit oids. */
|
|
1621
|
+
async function diffCommits(dir, fs, oldOid, newOid) {
|
|
1622
|
+
const trees = oldOid ? [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })] : [git.TREE({ ref: newOid })];
|
|
1623
|
+
const changed = await git.walk({
|
|
1624
|
+
fs,
|
|
1625
|
+
dir,
|
|
1626
|
+
trees,
|
|
1627
|
+
map: async (filepath, entries) => {
|
|
1628
|
+
if (filepath === ".") return void 0;
|
|
1629
|
+
if (!oldOid) {
|
|
1630
|
+
const [b] = entries;
|
|
1631
|
+
return b && await b.type() === "blob" ? filepath : void 0;
|
|
1632
|
+
}
|
|
1633
|
+
const [a, b] = entries;
|
|
1634
|
+
if ((a ? await a.oid() : void 0) === (b ? await b.oid() : void 0)) return void 0;
|
|
1635
|
+
const aType = a ? await a.type() : void 0;
|
|
1636
|
+
const bType = b ? await b.type() : void 0;
|
|
1637
|
+
if (aType === "tree" || bType === "tree") return void 0;
|
|
1638
|
+
return filepath;
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
const patches = [];
|
|
1642
|
+
let patchBytes = 0;
|
|
1643
|
+
for (const filepath of changed.sort()) {
|
|
1644
|
+
const before = oldOid ? await readFileAt(dir, fs, oldOid, filepath) ?? "" : "";
|
|
1645
|
+
const after = await readFileAt(dir, fs, newOid, filepath) ?? "";
|
|
1646
|
+
if (before === after) continue;
|
|
1647
|
+
const patch = createTwoFilesPatch(`a/${filepath}`, `b/${filepath}`, before, after);
|
|
1648
|
+
patches.push(patch);
|
|
1649
|
+
patchBytes += patch.length + 1;
|
|
1650
|
+
if (patchBytes > MAX_PATCH_BYTES) {
|
|
1651
|
+
patches.push(`\n[diff truncated at ${MAX_PATCH_BYTES} bytes — open the file directly for the rest]`);
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
return patches.join("\n") || "(no textual changes)";
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Build the read-only git tools scoped to `dir` (the repo root). Returns an empty
|
|
1659
|
+
* array when `dir` is not a git repository, so callers can wire it
|
|
1660
|
+
* unconditionally.
|
|
1661
|
+
*/
|
|
1662
|
+
function createGitTools(dir, options = {}) {
|
|
1663
|
+
const fs = options.fs ?? nodeFs;
|
|
1664
|
+
if (!fs.existsSync(join(dir, ".git"))) return [];
|
|
1665
|
+
return [
|
|
1666
|
+
{
|
|
1667
|
+
name: "git_log",
|
|
1668
|
+
label: "git log",
|
|
1669
|
+
description: "List commits in this repository (newest first). Optionally pass `since` (a ref/sha) to list only commits after it — the range you have not reviewed yet.",
|
|
1670
|
+
parameters: Type.Object({
|
|
1671
|
+
since: Type.Optional(Type.String({ description: "Only list commits after this ref/sha (exclusive)." })),
|
|
1672
|
+
maxCount: Type.Optional(Type.Number({ description: "Maximum number of commits to return (default 50)." }))
|
|
1673
|
+
}),
|
|
1674
|
+
async execute(_id, params) {
|
|
1675
|
+
const depth = Math.min(Math.max(params.maxCount ?? 50, 1), 500);
|
|
1676
|
+
const commits = await git.log({
|
|
1677
|
+
fs,
|
|
1678
|
+
dir,
|
|
1679
|
+
depth
|
|
1680
|
+
});
|
|
1681
|
+
let stop = -1;
|
|
1682
|
+
if (params.since) {
|
|
1683
|
+
const sinceOid = await resolveOid(dir, fs, params.since);
|
|
1684
|
+
stop = commits.findIndex((c) => c.oid === sinceOid);
|
|
1685
|
+
if (stop < 0) return text(`(base ref ${params.since} is not within the ${depth} most recent commits — re-run git_log with a larger maxCount to cover the full unreviewed range)`);
|
|
1686
|
+
}
|
|
1687
|
+
const list = (stop >= 0 ? commits.slice(0, stop) : commits).map((c) => {
|
|
1688
|
+
const subject = c.commit.message.split("\n", 1)[0];
|
|
1689
|
+
return `${c.oid.slice(0, 10)} ${subject} (${c.commit.author.name})`;
|
|
1690
|
+
});
|
|
1691
|
+
return text(list.length ? list.join("\n") : "(no commits in range)");
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
name: "git_show",
|
|
1696
|
+
label: "git show",
|
|
1697
|
+
description: "Show a commit's message and its unified diff against its first parent. Pass `ref` (a sha or ref).",
|
|
1698
|
+
parameters: Type.Object({ ref: Type.String({ description: "The commit sha or ref to show." }) }),
|
|
1699
|
+
async execute(_id, params) {
|
|
1700
|
+
const oid = await resolveOid(dir, fs, params.ref);
|
|
1701
|
+
const { commit } = await git.readCommit({
|
|
1702
|
+
fs,
|
|
1703
|
+
dir,
|
|
1704
|
+
oid
|
|
1705
|
+
});
|
|
1706
|
+
const diff = await diffCommits(dir, fs, commit.parent[0] ?? null, oid);
|
|
1707
|
+
return text(`commit ${oid}\nAuthor: ${commit.author.name} <${commit.author.email}>\n\n${commit.message.trim()}\n\n${diff}`);
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
name: "git_diff",
|
|
1712
|
+
label: "git diff",
|
|
1713
|
+
description: "Unified diff between two refs/shas. Pass `from` and `to`.",
|
|
1714
|
+
parameters: Type.Object({
|
|
1715
|
+
from: Type.String({ description: "Base ref/sha." }),
|
|
1716
|
+
to: Type.String({ description: "Target ref/sha." })
|
|
1717
|
+
}),
|
|
1718
|
+
async execute(_id, params) {
|
|
1719
|
+
return text(await diffCommits(dir, fs, await resolveOid(dir, fs, params.from), await resolveOid(dir, fs, params.to)));
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
];
|
|
1723
|
+
}
|
|
1724
|
+
//#endregion
|
|
1549
1725
|
//#region src/logger.ts
|
|
1550
1726
|
var LEVELS = {
|
|
1551
1727
|
debug: 0,
|
|
@@ -2228,11 +2404,12 @@ function atEndOfBlockComment(text, i) {
|
|
|
2228
2404
|
}
|
|
2229
2405
|
//#endregion
|
|
2230
2406
|
//#region src/verify.ts
|
|
2231
|
-
function buildVerifySystemPrompt(diff, commitLog) {
|
|
2407
|
+
function buildVerifySystemPrompt(diff, commitLog, staged) {
|
|
2408
|
+
const diskMode = staged !== void 0 && staged.length > 0;
|
|
2232
2409
|
const parts = [
|
|
2233
2410
|
"You are a strict, adversarial verifier of a SINGLE code-review finding. Your job is to REFUTE the finding, not to agree with it.",
|
|
2234
2411
|
"",
|
|
2235
|
-
"Each request gives you one proposed finding (file, line, severity, confidence, and body) to check against the diff below. You may read referenced files to confirm reachability. Decide whether the finding survives scrutiny.",
|
|
2412
|
+
diskMode ? "Each request gives you one proposed finding (file, line, severity, confidence, and body). The change is NOT inline below — every file diff is staged on disk (see <staged_files>). Open the finding's file (and any others you need) with your file-read tool to check reachability. Decide whether the finding survives scrutiny." : "Each request gives you one proposed finding (file, line, severity, confidence, and body) to check against the diff below. You may read referenced files to confirm reachability. Decide whether the finding survives scrutiny.",
|
|
2236
2413
|
"",
|
|
2237
2414
|
"Apply this bar:",
|
|
2238
2415
|
"- The finding must point to a concrete defect demonstrable from the diff (and any file you read): a specific input, state, or execution path triggers it, and a violated contract is visible.",
|
|
@@ -2248,7 +2425,10 @@ function buildVerifySystemPrompt(diff, commitLog) {
|
|
|
2248
2425
|
"- \"drop\": not a real defect — speculative, stylistic, contradicted by the code/comments, or based on external state not visible in the diff."
|
|
2249
2426
|
];
|
|
2250
2427
|
if (commitLog?.trim()) parts.push("", `Commit messages for this change (oldest first):\n<commits>\n${commitLog.trim()}\n</commits>`);
|
|
2251
|
-
|
|
2428
|
+
if (diskMode) {
|
|
2429
|
+
const list = staged.map((f) => `- ${f.path} → ${f.diskPath}`).join("\n");
|
|
2430
|
+
parts.push("", `The change is staged on disk — open the relevant file(s) to verify:\n<staged_files>\n${list}\n</staged_files>`);
|
|
2431
|
+
} else parts.push("", `Verify each finding against this diff:\n<diff>\n${diff}\n</diff>`);
|
|
2252
2432
|
return parts.join("\n");
|
|
2253
2433
|
}
|
|
2254
2434
|
function buildVerifyUserPrompt(comment) {
|
|
@@ -3037,18 +3217,18 @@ function gitSkillCacheKey(url, ref) {
|
|
|
3037
3217
|
* remote's default branch via `HEAD`.
|
|
3038
3218
|
*/
|
|
3039
3219
|
async function gitShallowClone(url, ref, dir) {
|
|
3040
|
-
await git([
|
|
3220
|
+
await git$1([
|
|
3041
3221
|
"init",
|
|
3042
3222
|
"--quiet",
|
|
3043
3223
|
dir
|
|
3044
3224
|
]);
|
|
3045
|
-
await git([
|
|
3225
|
+
await git$1([
|
|
3046
3226
|
"remote",
|
|
3047
3227
|
"add",
|
|
3048
3228
|
"origin",
|
|
3049
3229
|
url
|
|
3050
3230
|
], { cwd: dir });
|
|
3051
|
-
await git([
|
|
3231
|
+
await git$1([
|
|
3052
3232
|
"fetch",
|
|
3053
3233
|
"--depth",
|
|
3054
3234
|
"1",
|
|
@@ -3056,7 +3236,7 @@ async function gitShallowClone(url, ref, dir) {
|
|
|
3056
3236
|
"origin",
|
|
3057
3237
|
ref || "HEAD"
|
|
3058
3238
|
], { cwd: dir });
|
|
3059
|
-
await git([
|
|
3239
|
+
await git$1([
|
|
3060
3240
|
"checkout",
|
|
3061
3241
|
"--quiet",
|
|
3062
3242
|
"FETCH_HEAD"
|
|
@@ -3198,11 +3378,18 @@ async function cleanupSkippedDiffs(cwd) {
|
|
|
3198
3378
|
});
|
|
3199
3379
|
}
|
|
3200
3380
|
/**
|
|
3201
|
-
* Render the `<skipped_files>` block
|
|
3202
|
-
*
|
|
3381
|
+
* Render the `<skipped_files>` block: each file with its on-disk diff path and an
|
|
3382
|
+
* instruction to read them.
|
|
3383
|
+
*
|
|
3384
|
+
* - `partial` (retrieval mode): only budget-dropped files are staged; the rest
|
|
3385
|
+
* of the diff is inline, so the agent reads the highest-risk staged files.
|
|
3386
|
+
* - `full` (disk input mode): the ENTIRE change is staged — nothing is inline —
|
|
3387
|
+
* so reading is not optional; the review is only as good as what gets opened.
|
|
3203
3388
|
*/
|
|
3204
|
-
function renderRetrievableSkippedBlock(files) {
|
|
3205
|
-
|
|
3389
|
+
function renderRetrievableSkippedBlock(files, mode = "partial") {
|
|
3390
|
+
const list = files.map((f) => `- ${f.path} → ${f.diskPath}`).join("\n");
|
|
3391
|
+
if (mode === "full") return `<skipped_files>\n${list}\n</skipped_files>\nThis review has NO diff inline — every changed file's diff is staged on disk at the path shown, and the list above is the complete change. Use your file-read tool to open and review them; a file you do not open is a file you did not review. Prioritise source files over config/tests/generated, but aim to cover the whole change. In your summary, state explicitly which files you reviewed and which you did not open.`;
|
|
3392
|
+
return `<skipped_files>\n${list}\n</skipped_files>\nThese files exceeded the inline size budget, so their diffs are NOT in the prompt above — but each is staged on disk at the path shown. Use your file-read tool to open the diffs most likely to contain defects (start with source files over config/tests) and review them as if they were inline. You may not have budget to read them all; say in your summary which you reviewed and which you did not.`;
|
|
3206
3393
|
}
|
|
3207
3394
|
//#endregion
|
|
3208
3395
|
//#region src/triage.ts
|
|
@@ -3591,13 +3778,22 @@ function filterDiff(raw, maxChars = DEFAULT_MAX_DIFF_CHARS) {
|
|
|
3591
3778
|
totalChars += section.length;
|
|
3592
3779
|
reviewedChangedLines += changedLines;
|
|
3593
3780
|
}
|
|
3781
|
+
const allSections = kept.map((section) => {
|
|
3782
|
+
const filePath = parseFilePath(section.split("\n", 1)[0] ?? "");
|
|
3783
|
+
return filePath ? {
|
|
3784
|
+
path: filePath,
|
|
3785
|
+
section,
|
|
3786
|
+
changedLines: countChangedLines(section)
|
|
3787
|
+
} : null;
|
|
3788
|
+
}).filter((entry) => entry !== null);
|
|
3594
3789
|
return {
|
|
3595
3790
|
diff: included.join(""),
|
|
3596
3791
|
noiseSkippedFiles,
|
|
3597
3792
|
sizeSkippedFiles,
|
|
3598
3793
|
reviewedChangedLines,
|
|
3599
3794
|
skippedChangedLines,
|
|
3600
|
-
sizeSkippedSections
|
|
3795
|
+
sizeSkippedSections,
|
|
3796
|
+
allSections
|
|
3601
3797
|
};
|
|
3602
3798
|
}
|
|
3603
3799
|
function mergeContent(files) {
|
|
@@ -3803,13 +3999,23 @@ function buildAngleSystemPrompt(context, minSeverity, angle) {
|
|
|
3803
3999
|
"</review_angle>"
|
|
3804
4000
|
].join("\n");
|
|
3805
4001
|
}
|
|
3806
|
-
function buildUserPrompt(diff, skippedFiles = [], commitLog, priorThreads, intent, coverage, retrievableSkipped) {
|
|
4002
|
+
function buildUserPrompt(diff, skippedFiles = [], commitLog, priorThreads, intent, coverage, retrievableSkipped, omitInlineDiff = false, commitExploration) {
|
|
3807
4003
|
const parts = [];
|
|
3808
4004
|
const intentBlock = renderIntentBlock(intent);
|
|
3809
4005
|
if (intentBlock) parts.push(`The author described the purpose of this change below. Use it as context for reading the diff. If the code omits something promised or adds something never claimed, note it in one line of the summary — do not raise inline findings on the description text itself:\n${intentBlock}`);
|
|
3810
4006
|
if (commitLog?.trim()) parts.push(`Commits in this MR (oldest first):\n<commits>\n${commitLog.trim()}\n</commits>`);
|
|
3811
|
-
|
|
3812
|
-
|
|
4007
|
+
if (commitExploration) {
|
|
4008
|
+
const since = commitExploration.sinceRef;
|
|
4009
|
+
parts.push([
|
|
4010
|
+
"This review has NO diff inline. Explore the change with the read-only git tools:",
|
|
4011
|
+
since ? `- \`git_log\` with since="${since}" lists the commits you have NOT reviewed yet (everything after ${since}). Review exactly those commits.` : "- `git_log` lists the commits in this change. Review all of them.",
|
|
4012
|
+
"- `git_show <sha>` shows a commit's message and full diff. Open every commit in scope.",
|
|
4013
|
+
"- `git_diff <from> <to>` shows the combined diff between two points if you prefer to review the range at once.",
|
|
4014
|
+
"A commit you do not open is code you did not review. In your summary, state which commits you reviewed."
|
|
4015
|
+
].join("\n"));
|
|
4016
|
+
}
|
|
4017
|
+
if (!omitInlineDiff && !commitExploration) parts.push(`Review this diff:\n<diff>\n${diff}\n</diff>`);
|
|
4018
|
+
if (retrievableSkipped && retrievableSkipped.length > 0) parts.push(renderRetrievableSkippedBlock(retrievableSkipped, omitInlineDiff ? "full" : "partial"));
|
|
3813
4019
|
else if (skippedFiles.length > 0) parts.push(`<skipped_files>\n${skippedFiles.map((file) => `- ${file}`).join("\n")}\n</skipped_files>\nThe above files were not included because the diff exceeded the size limit. They are already surfaced to the reader in the MR summary, so do not re-list them; just do not assume they are clean, since you did not see them.`);
|
|
3814
4020
|
if (coverage && coverage.totalLines > 0 && coverage.reviewedLines < coverage.totalLines) {
|
|
3815
4021
|
const pct = Math.round(coverage.reviewedLines / coverage.totalLines * 100);
|
|
@@ -4029,33 +4235,86 @@ function accumulateUsage(target, message, modelId) {
|
|
|
4029
4235
|
addUsageToBucket(bucket, message);
|
|
4030
4236
|
}
|
|
4031
4237
|
}
|
|
4238
|
+
/**
|
|
4239
|
+
* The "consider decomposing this MR" hint: present when a threshold is set
|
|
4240
|
+
* (`> 0`) and the change exceeds it. `changedLines` is the count the caller
|
|
4241
|
+
* deems reviewed — the budget-fitted subset for inline mode, the whole change
|
|
4242
|
+
* for disk/commits mode.
|
|
4243
|
+
*/
|
|
4244
|
+
function resolveDecomposeHint(threshold, reviewedLines) {
|
|
4245
|
+
return threshold > 0 && reviewedLines > threshold ? {
|
|
4246
|
+
lines: reviewedLines,
|
|
4247
|
+
threshold
|
|
4248
|
+
} : void 0;
|
|
4249
|
+
}
|
|
4032
4250
|
async function runReview(config, options) {
|
|
4033
4251
|
const cwd = options.cwd ?? config.cwd;
|
|
4034
4252
|
const minSeverity = toGitLabReviewSeverity(config.minSeverity);
|
|
4035
4253
|
const logger = options.logger ?? noopLogger;
|
|
4254
|
+
const inputMode = config.inputMode ?? "auto";
|
|
4255
|
+
const gitTools = createGitTools(cwd);
|
|
4256
|
+
let commitsMode = inputMode === "commits";
|
|
4257
|
+
if (commitsMode && gitTools.length === 0) {
|
|
4258
|
+
logger.warn("Commit-exploration input mode requires a git checkout, but none was found in cwd; falling back to auto input mode.");
|
|
4259
|
+
commitsMode = false;
|
|
4260
|
+
}
|
|
4261
|
+
const effectiveMode = commitsMode ? "commits" : inputMode === "commits" ? "auto" : inputMode;
|
|
4036
4262
|
const maxDiffChars = config.maxDiffChars > 0 ? config.maxDiffChars : DEFAULT_MAX_DIFF_CHARS;
|
|
4037
|
-
const { diff, noiseSkippedFiles, sizeSkippedFiles, reviewedChangedLines, skippedChangedLines, sizeSkippedSections } = filterDiff(options.diff, maxDiffChars);
|
|
4038
|
-
|
|
4039
|
-
const
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4263
|
+
const { diff, noiseSkippedFiles, sizeSkippedFiles, reviewedChangedLines, skippedChangedLines, sizeSkippedSections, allSections } = filterDiff(options.diff, maxDiffChars);
|
|
4264
|
+
const overflowed = sizeSkippedSections.length > 0;
|
|
4265
|
+
const diskMode = effectiveMode === "disk" || effectiveMode === "auto" && overflowed;
|
|
4266
|
+
if (effectiveMode === "auto") logger.info(diskMode ? `Auto input mode: diff exceeds the ${maxDiffChars}-char budget — staging all files on disk for the agent to read.` : "Auto input mode: diff fits the budget — reviewing inline.");
|
|
4267
|
+
if (!(diskMode || commitsMode ? allSections.length > 0 : diff.trim().length > 0)) throw new ReviewerError("No reviewable diff content after filtering noise files.", { hint: "Ensure the merge request introduces changes outside of generated/lock files." });
|
|
4268
|
+
const decomposeHint = resolveDecomposeHint(config.decomposeHintLines, diskMode || commitsMode ? reviewedChangedLines + skippedChangedLines : reviewedChangedLines);
|
|
4269
|
+
let promptDiff;
|
|
4270
|
+
let promptSkippedFiles;
|
|
4271
|
+
let promptCoverage;
|
|
4272
|
+
let retrievableSkipped;
|
|
4273
|
+
let sizeNotice;
|
|
4274
|
+
if (commitsMode) {
|
|
4275
|
+
retrievableSkipped = [];
|
|
4276
|
+
promptDiff = "";
|
|
4277
|
+
promptSkippedFiles = [];
|
|
4278
|
+
promptCoverage = void 0;
|
|
4279
|
+
sizeNotice = {
|
|
4280
|
+
sizeSkippedFiles: [],
|
|
4281
|
+
decomposeHint,
|
|
4282
|
+
retrieved: false
|
|
4283
|
+
};
|
|
4284
|
+
logger.info(options.sinceRef ? `Commit-exploration input mode: reviewing commits since ${options.sinceRef}.` : "Commit-exploration input mode: reviewing the full commit range.");
|
|
4285
|
+
} else if (diskMode) {
|
|
4286
|
+
retrievableSkipped = allSections.length > 0 ? await writeSkippedDiffs(cwd, allSections.map(({ path, section }) => ({
|
|
4287
|
+
path,
|
|
4288
|
+
section
|
|
4289
|
+
}))) : [];
|
|
4290
|
+
if (retrievableSkipped.length > 0) logger.info(`Disk input mode: staged ${retrievableSkipped.length} file diff(s) for on-demand review.`);
|
|
4291
|
+
promptDiff = "";
|
|
4292
|
+
promptSkippedFiles = [];
|
|
4293
|
+
promptCoverage = void 0;
|
|
4294
|
+
sizeNotice = {
|
|
4295
|
+
sizeSkippedFiles: [],
|
|
4296
|
+
decomposeHint,
|
|
4297
|
+
retrieved: false
|
|
4298
|
+
};
|
|
4299
|
+
} else {
|
|
4300
|
+
promptSkippedFiles = [...sizeSkippedFiles.map((f) => f.path), ...noiseSkippedFiles];
|
|
4301
|
+
promptCoverage = sizeSkippedFiles.length > 0 ? {
|
|
4302
|
+
reviewedLines: reviewedChangedLines,
|
|
4303
|
+
totalLines: reviewedChangedLines + skippedChangedLines
|
|
4304
|
+
} : void 0;
|
|
4305
|
+
retrievableSkipped = config.retrieveSkipped && sizeSkippedSections.length > 0 ? await writeSkippedDiffs(cwd, sizeSkippedSections) : [];
|
|
4306
|
+
if (retrievableSkipped.length > 0) logger.info(`Staged ${retrievableSkipped.length} dropped-file diff(s) on disk for retrieval.`);
|
|
4307
|
+
promptDiff = diff;
|
|
4308
|
+
sizeNotice = {
|
|
4309
|
+
sizeSkippedFiles,
|
|
4310
|
+
decomposeHint,
|
|
4311
|
+
coverage: promptCoverage,
|
|
4312
|
+
retrieved: retrievableSkipped.length > 0
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4056
4315
|
const context = await loadReviewContext(cwd, config.skills, (msg) => logger.warn(msg), { refreshGitSkills: config.refreshGitSkills });
|
|
4057
4316
|
const systemPrompt = buildJSONSystemPrompt(context, minSeverity);
|
|
4058
|
-
const userPrompt = buildUserPrompt(
|
|
4317
|
+
const userPrompt = buildUserPrompt(promptDiff, promptSkippedFiles, options.commitLog, options.priorThreads, options.intent, promptCoverage, retrievableSkipped, diskMode, commitsMode ? { sinceRef: options.sinceRef } : void 0);
|
|
4059
4318
|
const skillNames = context.skills.map((s) => s.name);
|
|
4060
4319
|
if (skillNames.length > 0) logger.debug(`Skills loaded: ${skillNames.join(", ")}`);
|
|
4061
4320
|
if (context.conventions.length > 0) logger.debug(`Conventions: ${context.conventions.map((f) => f.path).join(", ")}`);
|
|
@@ -4063,7 +4322,7 @@ async function runReview(config, options) {
|
|
|
4063
4322
|
const pool = buildEffectivePool(config, logger);
|
|
4064
4323
|
if (pool.length > 1) logger.info(`Model pool: ${pool.map((m) => m.id).join(", ")}.`);
|
|
4065
4324
|
const primary = pool[0];
|
|
4066
|
-
const tools = createReadOnlyTools(cwd);
|
|
4325
|
+
const tools = [...createReadOnlyTools(cwd), ...gitTools];
|
|
4067
4326
|
const createAgent = options.createAgent ?? defaultCreateAgent;
|
|
4068
4327
|
const timeoutMs = options.timeoutMs ?? DEFAULT_REVIEW_TIMEOUT_MS;
|
|
4069
4328
|
const aggregated = emptyUsage();
|
|
@@ -4075,7 +4334,9 @@ async function runReview(config, options) {
|
|
|
4075
4334
|
timeoutMs,
|
|
4076
4335
|
logger,
|
|
4077
4336
|
aggregated,
|
|
4078
|
-
verifyMember: resolveVerifyMember(config, primary, logger)
|
|
4337
|
+
verifyMember: resolveVerifyMember(config, primary, logger),
|
|
4338
|
+
attachTelemetry: options.attachTelemetry,
|
|
4339
|
+
verifyStaged: diskMode ? retrievableSkipped : void 0
|
|
4079
4340
|
};
|
|
4080
4341
|
let outputText;
|
|
4081
4342
|
if (config.reviewDepth === "full") {
|
|
@@ -4241,6 +4502,7 @@ async function runMultiAngleFind(context, minSeverity, userPrompt, deps) {
|
|
|
4241
4502
|
thinkingLevel: deps.thinkingLevel,
|
|
4242
4503
|
getApiKey: member.getApiKey
|
|
4243
4504
|
});
|
|
4505
|
+
const detachTelemetry = deps.attachTelemetry?.(agent);
|
|
4244
4506
|
try {
|
|
4245
4507
|
const parsed = parseReviewMarkdownWithWarnings(await runAgentToCompletion(agent, userPrompt, {
|
|
4246
4508
|
timeoutMs: deps.timeoutMs,
|
|
@@ -4254,6 +4516,8 @@ async function runMultiAngleFind(context, minSeverity, userPrompt, deps) {
|
|
|
4254
4516
|
summaries[index] = parsed.summary;
|
|
4255
4517
|
} catch (error) {
|
|
4256
4518
|
deps.logger.warn(`Find angle "${angle.key}" failed: ${error.message}; skipping.`);
|
|
4519
|
+
} finally {
|
|
4520
|
+
detachTelemetry?.();
|
|
4257
4521
|
}
|
|
4258
4522
|
}), FIND_CONCURRENCY);
|
|
4259
4523
|
const raw = groups.reduce((total, group) => total + group.length, 0);
|
|
@@ -4279,7 +4543,7 @@ async function verifyAndSynthesize(findings, summary, diff, commitLog, deps) {
|
|
|
4279
4543
|
})).filter(({ finding }) => finding.comment.severity === "critical" || finding.comment.severity === "warn");
|
|
4280
4544
|
const verdicts = /* @__PURE__ */ new Map();
|
|
4281
4545
|
if (severe.length > 0) {
|
|
4282
|
-
const verifySystemPrompt = buildVerifySystemPrompt(diff, commitLog);
|
|
4546
|
+
const verifySystemPrompt = buildVerifySystemPrompt(diff, commitLog, deps.verifyStaged);
|
|
4283
4547
|
await runBounded(severe.map(({ finding, index }) => async () => {
|
|
4284
4548
|
const comment = finding.comment;
|
|
4285
4549
|
const verifierMember = deps.verifyMember ?? pickVerifier(deps.pool, finding.authorModel);
|
|
@@ -4290,6 +4554,7 @@ async function verifyAndSynthesize(findings, summary, diff, commitLog, deps) {
|
|
|
4290
4554
|
thinkingLevel: deps.thinkingLevel,
|
|
4291
4555
|
getApiKey: verifierMember.getApiKey
|
|
4292
4556
|
});
|
|
4557
|
+
const detachTelemetry = deps.attachTelemetry?.(verifier);
|
|
4293
4558
|
try {
|
|
4294
4559
|
const text = await runAgentToCompletion(verifier, buildVerifyUserPrompt(comment), {
|
|
4295
4560
|
timeoutMs: deps.timeoutMs,
|
|
@@ -4302,6 +4567,8 @@ async function verifyAndSynthesize(findings, summary, diff, commitLog, deps) {
|
|
|
4302
4567
|
decision: "keep",
|
|
4303
4568
|
reason: "verifier error; finding kept"
|
|
4304
4569
|
});
|
|
4570
|
+
} finally {
|
|
4571
|
+
detachTelemetry?.();
|
|
4305
4572
|
}
|
|
4306
4573
|
}), VERIFY_CONCURRENCY);
|
|
4307
4574
|
}
|
|
@@ -4932,7 +5199,7 @@ async function loadDefaultRuntime() {
|
|
|
4932
5199
|
const [sdkNode, resources, semconv] = modules;
|
|
4933
5200
|
const serviceResource = resources.resourceFromAttributes({
|
|
4934
5201
|
[semconv.ATTR_SERVICE_NAME ?? "service.name"]: SERVICE_NAME,
|
|
4935
|
-
[semconv.ATTR_SERVICE_VERSION ?? "service.version"]: "0.8.
|
|
5202
|
+
[semconv.ATTR_SERVICE_VERSION ?? "service.version"]: "0.8.5"
|
|
4936
5203
|
});
|
|
4937
5204
|
process.env.OTEL_METRICS_EXPORTER = process.env.OTEL_METRICS_EXPORTER ?? "otlp";
|
|
4938
5205
|
process.env.OTEL_LOGS_EXPORTER = process.env.OTEL_LOGS_EXPORTER ?? "otlp";
|
|
@@ -5364,7 +5631,7 @@ function boldCommentTitle(body) {
|
|
|
5364
5631
|
*/
|
|
5365
5632
|
function buildCommentBody(body, commitSha, confidence) {
|
|
5366
5633
|
const confidenceLine = `_Confidence: ${confidence}._`;
|
|
5367
|
-
const footer = `<sub>Reviewed by ${PRODUCT_LINK} v0.8.
|
|
5634
|
+
const footer = `<sub>Reviewed by ${PRODUCT_LINK} v0.8.5 for commit ${commitSha}.</sub>`;
|
|
5368
5635
|
return `${boldCommentTitle(body.trim())}\n\n${confidenceLine}\n\n---\n\n${footer}`;
|
|
5369
5636
|
}
|
|
5370
5637
|
function buildPayload(comment, body, refs, resolved) {
|
|
@@ -6099,6 +6366,11 @@ Options:
|
|
|
6099
6366
|
--review-depth <depth> single (one pass), verify (adversarial re-check of each
|
|
6100
6367
|
severe finding), or full (multi-angle finders → triage →
|
|
6101
6368
|
verify). (default: single; env: CODE_REVIEW_DEPTH)
|
|
6369
|
+
--input-mode <mode> How the change is fed to the reviewer: auto (inline while it
|
|
6370
|
+
fits the budget, disk on overflow), inline (diff in the prompt),
|
|
6371
|
+
disk (every file diff staged on disk; the agent reads on demand),
|
|
6372
|
+
or commits (agent explores via read-only git tools).
|
|
6373
|
+
(default: auto; env: CODE_REVIEW_INPUT_MODE)
|
|
6102
6374
|
--verify-model <p/id> Model for the Verify stage (verify/full depth). Pairs a cheap
|
|
6103
6375
|
finder with a strong, high-precision verifier. Warns if it looks
|
|
6104
6376
|
cheaper than --model. Empty (default) = pool selection.
|
|
@@ -6205,7 +6477,8 @@ async function run(config, bridges) {
|
|
|
6205
6477
|
description: mr.description
|
|
6206
6478
|
},
|
|
6207
6479
|
logger,
|
|
6208
|
-
attachTelemetry: bridges?.otel?.createAgentTelemetry(runId)
|
|
6480
|
+
attachTelemetry: bridges?.otel?.createAgentTelemetry(runId),
|
|
6481
|
+
sinceRef: config.inputMode === "commits" && reviewedCommitSha && reviewedCommitSha !== refs.head_sha ? reviewedCommitSha : void 0
|
|
6209
6482
|
});
|
|
6210
6483
|
context.usage = result;
|
|
6211
6484
|
return result;
|
|
@@ -6452,10 +6725,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
6452
6725
|
return;
|
|
6453
6726
|
}
|
|
6454
6727
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
6455
|
-
console.log("0.8.
|
|
6728
|
+
console.log("0.8.5");
|
|
6456
6729
|
return;
|
|
6457
6730
|
}
|
|
6458
|
-
process.stderr.write(`[code-review] @weareikko/code-review v0.8.
|
|
6731
|
+
process.stderr.write(`[code-review] @weareikko/code-review v0.8.5\n`);
|
|
6459
6732
|
assertNodeVersion();
|
|
6460
6733
|
applyCodeReviewEnvPrefix();
|
|
6461
6734
|
applyDefaultCacheRetention();
|
|
@@ -6478,4 +6751,4 @@ if (isDirectRun()) main().catch((error) => {
|
|
|
6478
6751
|
//#endregion
|
|
6479
6752
|
export { normalizeBody as $, SUMMARY_HISTORY_END as A, buildSummaryHistoryEntries as B, createDiagnosticContext as C, traceDiagnosticPhase as D, traceDiagnostic as E, SUMMARY_MARKER as F, findExistingSummaryNoteId as G, extractSummaryHistoryEntries as H, buildArchivedSummaryEntry as I, upsertSummaryNote as J, stripSummaryHistory as K, buildReviewedCommitFooter as L, SUMMARY_HISTORY_ENTRY_START as M, SUMMARY_HISTORY_LIMIT as N, normalizeSeverity as O, SUMMARY_HISTORY_START as P, fingerprints as Q, buildSizeNoticeBlock as R, DIAGNOSTIC_CHANNEL_PREFIX as S, diagnosticChannels as T, findExistingReviewedCommitSha as U, extractReviewedCommitSha as V, findExistingSummaryNote as W, extractDiffHunkContext as X, appendFingerprintMarkers as Y, extractExistingFingerprints as Z, resolveNpmSkillDir as _, main as a, parseReviewMarkdownWithWarnings as b, buildGeneratedComments as c, startOtelBridge as d, sha256 as et, filterDiff as f, parseSkillSpec as g, loadNamedSkill as h, formatUsageLine as i, SUMMARY_HISTORY_ENTRY_END as j, toGitLabReviewSeverity as k, buildPayload as l, gitSkillCacheKey as m, formatPerModelUsage as n, run as o, runReview as p, stripSummaryMarker as q, formatSkillsFooter as r, withHttpStamping as s, countPostedBySeverity as t, isOtelEnabled as u, resolveSkillCacheDir as v, createDiagnosticRunId as w, DIAGNOSTIC_CHANNEL_NAMES as x, parseReviewMarkdown as y, buildSummaryBody as z };
|
|
6480
6753
|
|
|
6481
|
-
//# sourceMappingURL=cli-
|
|
6754
|
+
//# sourceMappingURL=cli-DmzA9PxS.js.map
|