@jphutchins/code-review 0.1.0-alpha.12 → 0.1.0-alpha.13
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 +220 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/comment.eta +17 -16
package/dist/index.js
CHANGED
|
@@ -178,6 +178,16 @@ ${marker}` : "";
|
|
|
178
178
|
};
|
|
179
179
|
var findingsPointer = (findings, jsonUrl, limit = EMBED_LIMIT) => encodeMarker(findings, jsonUrl, limit);
|
|
180
180
|
var findingPointer = (finding, schemaVersion, jsonUrl, limit = EMBED_LIMIT) => encodeMarker({ schema_version: schemaVersion, findings: [finding] }, jsonUrl, limit);
|
|
181
|
+
var parseFindingsMarker = (body) => {
|
|
182
|
+
const match = /<!-- code-review:findings-json;base64 ([A-Za-z0-9+/=]+) -->/.exec(body);
|
|
183
|
+
const b64 = match?.[1];
|
|
184
|
+
if (b64 === void 0) return null;
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
181
191
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
182
192
|
var projectPatch = (patch) => {
|
|
183
193
|
if (patch === void 0) return { kind: "none" };
|
|
@@ -234,6 +244,7 @@ var render = (input) => {
|
|
|
234
244
|
postedAt: input.postedAt ?? "",
|
|
235
245
|
severityCounts: input.severityCounts ?? computeSeverityCounts(input.findings.findings),
|
|
236
246
|
strays: (input.strays ?? []).map(sanitizeFinding),
|
|
247
|
+
unanchoredCount: input.unanchoredCount ?? 0,
|
|
237
248
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
238
249
|
runUrl: input.runUrl ?? null,
|
|
239
250
|
jsonUrl: input.jsonUrl ?? null,
|
|
@@ -355,7 +366,7 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
355
366
|
}
|
|
356
367
|
return comment;
|
|
357
368
|
});
|
|
358
|
-
return { comments, strays };
|
|
369
|
+
return { comments, strays, inDiff };
|
|
359
370
|
};
|
|
360
371
|
var renderStraysSection = (strays) => {
|
|
361
372
|
if (strays.length === 0) return "";
|
|
@@ -1089,25 +1100,50 @@ var parseHtmlUrl = (raw) => {
|
|
|
1089
1100
|
return void 0;
|
|
1090
1101
|
}
|
|
1091
1102
|
};
|
|
1092
|
-
var
|
|
1103
|
+
var commentPayload = (c) => ({
|
|
1104
|
+
path: c.path,
|
|
1105
|
+
line: c.line,
|
|
1106
|
+
side: c.side,
|
|
1107
|
+
...c.start_line !== void 0 && c.start_side !== void 0 ? { start_line: c.start_line, start_side: c.start_side } : {},
|
|
1108
|
+
body: formatMarkdown(c.body)
|
|
1109
|
+
});
|
|
1110
|
+
var postInlineReview = async (repo, prNumber, headSha, comments, inDiff, stickyUrl, marker, ghApi) => {
|
|
1093
1111
|
const pointer = reviewBodyPointer(headSha, stickyUrl, marker);
|
|
1094
|
-
const
|
|
1112
|
+
const reviewBody = (withComments) => JSON.stringify({
|
|
1095
1113
|
body: pointer,
|
|
1096
1114
|
commit_id: headSha,
|
|
1097
1115
|
event: "COMMENT",
|
|
1098
|
-
comments: comments.map(
|
|
1099
|
-
path: c.path,
|
|
1100
|
-
line: c.line,
|
|
1101
|
-
side: c.side,
|
|
1102
|
-
...c.start_line !== void 0 && c.start_side !== void 0 ? { start_line: c.start_line, start_side: c.start_side } : {},
|
|
1103
|
-
body: formatMarkdown(c.body)
|
|
1104
|
-
}))
|
|
1116
|
+
comments: withComments ? comments.map(commentPayload) : []
|
|
1105
1117
|
});
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1118
|
+
const reviewsEndpoint = [`repos/${repo}/pulls/${String(prNumber)}/reviews`, "--input", "-"];
|
|
1119
|
+
try {
|
|
1120
|
+
const stdout = await ghApi(reviewsEndpoint, reviewBody(true));
|
|
1121
|
+
return { url: parseHtmlUrl(stdout), inlinePosted: comments.length, unposted: [] };
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
if (comments.length === 0) throw err;
|
|
1124
|
+
process.stderr.write(
|
|
1125
|
+
`Warning: the batched inline review on PR #${String(prNumber)} was rejected (${err instanceof Error ? err.message : String(err)}) \u2014 posting the review body-only, then each comment individually to keep the ones GitHub accepts (issue #57)
|
|
1126
|
+
`
|
|
1127
|
+
);
|
|
1128
|
+
const url = parseHtmlUrl(await ghApi(reviewsEndpoint, reviewBody(false)));
|
|
1129
|
+
const commentsEndpoint = [`repos/${repo}/pulls/${String(prNumber)}/comments`, "--input", "-"];
|
|
1130
|
+
const unposted = [];
|
|
1131
|
+
let inlinePosted = 0;
|
|
1132
|
+
for (const [i, c] of comments.entries()) {
|
|
1133
|
+
try {
|
|
1134
|
+
await ghApi(commentsEndpoint, JSON.stringify({ commit_id: headSha, ...commentPayload(c) }));
|
|
1135
|
+
inlinePosted += 1;
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
const finding = inDiff[i];
|
|
1138
|
+
if (finding) unposted.push(finding);
|
|
1139
|
+
process.stderr.write(
|
|
1140
|
+
`Warning: inline comment on ${c.path}:${String(c.line)} rejected (${e instanceof Error ? e.message : String(e)}) \u2014 surfacing that finding in the sticky instead (issue #57)
|
|
1141
|
+
`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return { url, inlinePosted, unposted };
|
|
1146
|
+
}
|
|
1111
1147
|
};
|
|
1112
1148
|
var findBotComment = async (repo, prNumber, botLogin, marker, ghApi) => {
|
|
1113
1149
|
const stdout = await ghApi(
|
|
@@ -1174,10 +1210,7 @@ var fetchBotReviews = async (repo, prNumber, botLogin, ghApi) => {
|
|
|
1174
1210
|
return [];
|
|
1175
1211
|
}
|
|
1176
1212
|
if (!Array.isArray(reviews)) return [];
|
|
1177
|
-
return reviews.filter(isBotReview).filter((r) => r.user.login === botLogin && r.state !== "DISMISSED").map((r) => ({
|
|
1178
|
-
id: r.id,
|
|
1179
|
-
commitId: typeof r.commit_id === "string" ? r.commit_id : ""
|
|
1180
|
-
}));
|
|
1213
|
+
return reviews.filter(isBotReview).filter((r) => r.user.login === botLogin && r.state !== "DISMISSED").map((r) => ({ id: r.id }));
|
|
1181
1214
|
};
|
|
1182
1215
|
var dismissReviews = async (repo, prNumber, ids, ghApi) => {
|
|
1183
1216
|
for (const id of ids) {
|
|
@@ -1200,16 +1233,15 @@ var dismissReviews = async (repo, prNumber, ids, ghApi) => {
|
|
|
1200
1233
|
}
|
|
1201
1234
|
}
|
|
1202
1235
|
};
|
|
1203
|
-
var REVIEW_THREAD_COMMENTS_QUERY = "query($owner:String!,$name:String!,$pr:Int!){repository(owner:$owner,name:$name){pullRequest(number:$pr){reviewThreads(first:100){pageInfo{hasNextPage}nodes{comments(first:100){nodes{id isMinimized author{login}
|
|
1236
|
+
var REVIEW_THREAD_COMMENTS_QUERY = "query($owner:String!,$name:String!,$pr:Int!){repository(owner:$owner,name:$name){pullRequest(number:$pr){reviewThreads(first:100){pageInfo{hasNextPage}nodes{comments(first:100){nodes{id isMinimized author{login}}}}}}}}";
|
|
1204
1237
|
var MINIMIZE_COMMENT_MUTATION = "mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:OUTDATED}){minimizedComment{isMinimized}}}";
|
|
1205
|
-
var
|
|
1238
|
+
var priorBotCommentId = (c, logins) => {
|
|
1206
1239
|
if (typeof c !== "object" || c === null) return null;
|
|
1207
1240
|
const o = c;
|
|
1208
1241
|
const login = o.author?.login;
|
|
1209
|
-
|
|
1210
|
-
return typeof o.id === "string" && o.isMinimized !== true && typeof login === "string" && logins.includes(login) && typeof oid === "string" && oid !== headSha ? o.id : null;
|
|
1242
|
+
return typeof o.id === "string" && o.isMinimized !== true && typeof login === "string" && logins.includes(login) ? o.id : null;
|
|
1211
1243
|
};
|
|
1212
|
-
var
|
|
1244
|
+
var priorBotCommentIds = (raw, botLogin) => {
|
|
1213
1245
|
let parsed;
|
|
1214
1246
|
try {
|
|
1215
1247
|
parsed = JSON.parse(raw);
|
|
@@ -1223,13 +1255,13 @@ var supersededBotCommentIds = (raw, headSha, botLogin) => {
|
|
|
1223
1255
|
const logins = [botLogin.replace(/\[bot\]$/, ""), botLogin];
|
|
1224
1256
|
const ids = nodes.flatMap((t4) => {
|
|
1225
1257
|
const cnodes = t4.comments?.nodes;
|
|
1226
|
-
return Array.isArray(cnodes) ? cnodes.map((c) =>
|
|
1258
|
+
return Array.isArray(cnodes) ? cnodes.map((c) => priorBotCommentId(c, logins)).filter((id) => id !== null) : [];
|
|
1227
1259
|
});
|
|
1228
1260
|
return { ids, truncated };
|
|
1229
1261
|
};
|
|
1230
|
-
var
|
|
1262
|
+
var listPriorBotCommentIds = async (repo, prNumber, botLogin, ghApi) => {
|
|
1231
1263
|
const slash = repo.indexOf("/");
|
|
1232
|
-
if (slash <= 0) return;
|
|
1264
|
+
if (slash <= 0) return [];
|
|
1233
1265
|
const owner = repo.slice(0, slash);
|
|
1234
1266
|
const name = repo.slice(slash + 1);
|
|
1235
1267
|
let raw;
|
|
@@ -1250,15 +1282,18 @@ var minimizeSupersededComments = async (repo, prNumber, headSha, botLogin, ghApi
|
|
|
1250
1282
|
`Warning: could not list review threads to minimize stale comments on PR #${String(prNumber)}: ${err instanceof Error ? err.message : String(err)}
|
|
1251
1283
|
`
|
|
1252
1284
|
);
|
|
1253
|
-
return;
|
|
1285
|
+
return [];
|
|
1254
1286
|
}
|
|
1255
|
-
const { ids, truncated } =
|
|
1287
|
+
const { ids, truncated } = priorBotCommentIds(raw, botLogin);
|
|
1256
1288
|
if (truncated) {
|
|
1257
1289
|
process.stderr.write(
|
|
1258
1290
|
`Note: PR #${String(prNumber)} has more than 100 review threads \u2014 only the first 100 were scanned for stale bot comments
|
|
1259
1291
|
`
|
|
1260
1292
|
);
|
|
1261
1293
|
}
|
|
1294
|
+
return ids;
|
|
1295
|
+
};
|
|
1296
|
+
var minimizeComments = async (prNumber, ids, ghApi) => {
|
|
1262
1297
|
let minimized = 0;
|
|
1263
1298
|
for (const id of ids) {
|
|
1264
1299
|
try {
|
|
@@ -1373,7 +1408,11 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1373
1408
|
process.exit(0);
|
|
1374
1409
|
}
|
|
1375
1410
|
const findingsMarker = findingsPointer(findings, input.jsonUrl);
|
|
1376
|
-
const {
|
|
1411
|
+
const {
|
|
1412
|
+
comments: rawComments,
|
|
1413
|
+
strays,
|
|
1414
|
+
inDiff
|
|
1415
|
+
} = buildInlineComments(findings.findings, diff, {
|
|
1377
1416
|
inlineTemplate,
|
|
1378
1417
|
models: envelope.models.map((m) => m.model),
|
|
1379
1418
|
findings,
|
|
@@ -1387,8 +1426,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1387
1426
|
);
|
|
1388
1427
|
}
|
|
1389
1428
|
const botReviews = await fetchBotReviews(input.repo, prNumber, input.botLogin, ghApi);
|
|
1390
|
-
const
|
|
1391
|
-
const initialDisposition = comments.length > 0 ? alreadyReviewedThisSha ? { kind: "suppressed-existing-review", sha: input.headSha } : void 0 : strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
1429
|
+
const initialDisposition = comments.length === 0 && strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
1392
1430
|
const commonRenderInput = {
|
|
1393
1431
|
findings,
|
|
1394
1432
|
envelope,
|
|
@@ -1412,7 +1450,15 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1412
1450
|
|
|
1413
1451
|
> **Note:** ${String(longFiles.length)} suggestion(s) exceeded GitHub's ~10-line inline suggestion limit and were omitted from the inline comments; the affected findings remain in the review.
|
|
1414
1452
|
` : "";
|
|
1415
|
-
const renderBody = (inlineDisposition, reviewUrl2
|
|
1453
|
+
const renderBody = (inlineDisposition, reviewUrl2, straysOverride, unanchoredCount2) => formatMarkdown(
|
|
1454
|
+
render({
|
|
1455
|
+
...commonRenderInput,
|
|
1456
|
+
...straysOverride ? { strays: straysOverride } : {},
|
|
1457
|
+
...unanchoredCount2 !== void 0 ? { unanchoredCount: unanchoredCount2 } : {},
|
|
1458
|
+
inlineDisposition,
|
|
1459
|
+
reviewUrl: reviewUrl2
|
|
1460
|
+
}) + longFilesNote
|
|
1461
|
+
);
|
|
1416
1462
|
const stickyRef = await upsertSticky(
|
|
1417
1463
|
input.repo,
|
|
1418
1464
|
prNumber,
|
|
@@ -1420,49 +1466,53 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1420
1466
|
renderBody(initialDisposition),
|
|
1421
1467
|
ghApi
|
|
1422
1468
|
);
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
}
|
|
1434
|
-
const reviewUrl = await postInlineReview(
|
|
1469
|
+
const priorInlineComments = await listPriorBotCommentIds(
|
|
1470
|
+
input.repo,
|
|
1471
|
+
prNumber,
|
|
1472
|
+
input.botLogin,
|
|
1473
|
+
ghApi
|
|
1474
|
+
);
|
|
1475
|
+
const {
|
|
1476
|
+
url: reviewUrl,
|
|
1477
|
+
inlinePosted,
|
|
1478
|
+
unposted
|
|
1479
|
+
} = await postInlineReview(
|
|
1435
1480
|
input.repo,
|
|
1436
1481
|
prNumber,
|
|
1437
1482
|
input.headSha,
|
|
1438
1483
|
comments,
|
|
1484
|
+
inDiff,
|
|
1439
1485
|
stickyRef?.url,
|
|
1440
1486
|
findingsMarker,
|
|
1441
1487
|
ghApi
|
|
1442
1488
|
);
|
|
1443
1489
|
process.stderr.write(
|
|
1444
|
-
`Posted a review with ${String(
|
|
1490
|
+
`Posted a review with ${String(inlinePosted)} inline comment(s) on PR #${String(prNumber)}
|
|
1445
1491
|
`
|
|
1446
1492
|
);
|
|
1447
|
-
|
|
1448
|
-
if (
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1493
|
+
const priorReviewIds = botReviews.map((r) => r.id);
|
|
1494
|
+
if (priorReviewIds.length > 0) {
|
|
1495
|
+
await dismissReviews(input.repo, prNumber, priorReviewIds, ghApi);
|
|
1496
|
+
}
|
|
1497
|
+
await minimizeComments(prNumber, priorInlineComments, ghApi);
|
|
1498
|
+
const unanchoredCount = unposted.length;
|
|
1499
|
+
const finalStrays = unanchoredCount > 0 ? [...unposted, ...strays] : strays;
|
|
1500
|
+
if (stickyRef !== null && (inlinePosted > 0 || unanchoredCount > 0)) {
|
|
1501
|
+
const finalDisposition = inlinePosted > 0 ? { kind: "posted", count: inlinePosted, sha: input.headSha } : { kind: "inline-unavailable" };
|
|
1454
1502
|
try {
|
|
1455
1503
|
await patchComment(
|
|
1456
1504
|
input.repo,
|
|
1457
1505
|
stickyRef.id,
|
|
1458
|
-
renderBody(
|
|
1506
|
+
renderBody(finalDisposition, reviewUrl, finalStrays, unanchoredCount),
|
|
1459
1507
|
ghApi
|
|
1460
1508
|
);
|
|
1461
|
-
process.stderr.write(
|
|
1462
|
-
`)
|
|
1509
|
+
process.stderr.write(
|
|
1510
|
+
`Updated sticky comment #${String(stickyRef.id)} to reflect the review
|
|
1511
|
+
`
|
|
1512
|
+
);
|
|
1463
1513
|
} catch (err) {
|
|
1464
1514
|
process.stderr.write(
|
|
1465
|
-
`Warning: failed to
|
|
1515
|
+
`Warning: failed to update the sticky summary after the review: ${err instanceof Error ? err.message : String(err)}
|
|
1466
1516
|
`
|
|
1467
1517
|
);
|
|
1468
1518
|
}
|
|
@@ -1800,18 +1850,18 @@ var withMeta = (base, meta) => ({
|
|
|
1800
1850
|
...meta.effort ? { effort: meta.effort } : {}
|
|
1801
1851
|
});
|
|
1802
1852
|
var resolveTelemetry = (native, meta) => {
|
|
1803
|
-
const fb =
|
|
1804
|
-
|
|
1853
|
+
const fb = (() => {
|
|
1854
|
+
try {
|
|
1855
|
+
return meta.transcriptFallback?.();
|
|
1856
|
+
} catch {
|
|
1857
|
+
return void 0;
|
|
1858
|
+
}
|
|
1859
|
+
})();
|
|
1860
|
+
const wallTurns = fb !== void 0 && fb.durationMs > 0 ? { turns: fb.turns, duration_ms: fb.durationMs } : { turns: native.turns, duration_ms: native.durationMs };
|
|
1805
1861
|
return withMeta(
|
|
1806
|
-
|
|
1807
|
-
models: [...fb.models],
|
|
1808
|
-
|
|
1809
|
-
duration_ms: fb.durationMs,
|
|
1810
|
-
vendor_cost_usd: native.vendorCostUsd
|
|
1811
|
-
} : {
|
|
1812
|
-
models: native.models,
|
|
1813
|
-
turns: native.turns,
|
|
1814
|
-
duration_ms: native.durationMs,
|
|
1862
|
+
{
|
|
1863
|
+
models: native.models.length > 0 ? native.models : fb ? [...fb.models] : native.models,
|
|
1864
|
+
...wallTurns,
|
|
1815
1865
|
vendor_cost_usd: native.vendorCostUsd
|
|
1816
1866
|
},
|
|
1817
1867
|
meta
|
|
@@ -2400,6 +2450,105 @@ ${printableSchema(schemaPath)}
|
|
|
2400
2450
|
}
|
|
2401
2451
|
}
|
|
2402
2452
|
});
|
|
2453
|
+
var seedDraftCmd = defineCommand({
|
|
2454
|
+
meta: {
|
|
2455
|
+
name: "seed-draft",
|
|
2456
|
+
description: "Write a valid findings $DRAFT before the review runs: the decoded findings from a prior review when one exists and still validates (incremental re-review), else an empty-but-valid scaffold \u2014 so a valid draft exists from turn 0 (issues #52, #53). Prints the mode chosen (prior|empty|none \u2014 none when even the scaffold write failed) to stdout; always exits 0"
|
|
2457
|
+
},
|
|
2458
|
+
args: {
|
|
2459
|
+
prior: {
|
|
2460
|
+
type: "string",
|
|
2461
|
+
description: "Path to the prior-review JSON gather staged ({ id, body }, or the literal null); its embedded base64 findings marker is decoded and becomes the seed when it validates against the schema"
|
|
2462
|
+
},
|
|
2463
|
+
out: {
|
|
2464
|
+
type: "string",
|
|
2465
|
+
description: "Path to write the seed $DRAFT to (an absolute path outside the worktree)",
|
|
2466
|
+
required: true
|
|
2467
|
+
},
|
|
2468
|
+
kind: {
|
|
2469
|
+
type: "string",
|
|
2470
|
+
description: "Schema kind to validate the prior findings against (default: findings)"
|
|
2471
|
+
},
|
|
2472
|
+
schema: {
|
|
2473
|
+
type: "string",
|
|
2474
|
+
description: "Path to a schema file (wins over --kind/--schema-version)"
|
|
2475
|
+
},
|
|
2476
|
+
"schema-version": {
|
|
2477
|
+
type: "string",
|
|
2478
|
+
description: "Schema major.minor to validate the prior findings against (default: the kind's latest \u2014 an older-shaped prior review then falls back to the empty scaffold)"
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
run: async ({ args }) => {
|
|
2482
|
+
const outPath = resolve$1(args.out);
|
|
2483
|
+
const kindArg = args.kind || "findings";
|
|
2484
|
+
const kind = isSchemaKind(kindArg) ? kindArg : "findings";
|
|
2485
|
+
if (kind !== kindArg) {
|
|
2486
|
+
process.stderr.write(
|
|
2487
|
+
`Warning: unknown --kind "${kindArg}" \u2014 validating against "findings"
|
|
2488
|
+
`
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
const writeEmptyScaffold = () => {
|
|
2492
|
+
try {
|
|
2493
|
+
writeFileSync(outPath, `${JSON.stringify(noticeFindings(""), null, 2)}
|
|
2494
|
+
`);
|
|
2495
|
+
process.stderr.write(
|
|
2496
|
+
`Seeded ${outPath} with an empty valid scaffold \u2014 no decodable prior findings to build on
|
|
2497
|
+
`
|
|
2498
|
+
);
|
|
2499
|
+
process.stdout.write("empty\n");
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
process.stderr.write(
|
|
2502
|
+
`Warning: could not write the seed scaffold to ${outPath} (${err instanceof Error ? err.message : String(err)}) \u2014 the agent will create $DRAFT itself
|
|
2503
|
+
`
|
|
2504
|
+
);
|
|
2505
|
+
process.stdout.write("none\n");
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
const priorFindings = (() => {
|
|
2509
|
+
if (!args.prior) return null;
|
|
2510
|
+
const raw = (() => {
|
|
2511
|
+
try {
|
|
2512
|
+
return JSON.parse(readFileSync(resolve$1(args.prior), "utf-8"));
|
|
2513
|
+
} catch {
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
})();
|
|
2517
|
+
const body = typeof raw === "object" && raw !== null && "body" in raw && typeof raw.body === "string" ? raw.body : null;
|
|
2518
|
+
return body === null ? null : parseFindingsMarker(body);
|
|
2519
|
+
})();
|
|
2520
|
+
if (priorFindings === null) {
|
|
2521
|
+
writeEmptyScaffold();
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const seededFromPrior = (() => {
|
|
2525
|
+
try {
|
|
2526
|
+
const schemaPath = args.schema ? resolve$1(args.schema) : schemaPathFor(kind, args["schema-version"]);
|
|
2527
|
+
if (!validateAgainstSchema(priorFindings, schemaPath).valid) return false;
|
|
2528
|
+
writeFileSync(outPath, `${JSON.stringify(priorFindings, null, 2)}
|
|
2529
|
+
`);
|
|
2530
|
+
return true;
|
|
2531
|
+
} catch (err) {
|
|
2532
|
+
process.stderr.write(
|
|
2533
|
+
`Warning: could not seed from the prior review (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to the empty scaffold
|
|
2534
|
+
`
|
|
2535
|
+
);
|
|
2536
|
+
return false;
|
|
2537
|
+
}
|
|
2538
|
+
})();
|
|
2539
|
+
if (seededFromPrior) {
|
|
2540
|
+
const priorList = priorFindings.findings;
|
|
2541
|
+
const count = Array.isArray(priorList) ? priorList.length : 0;
|
|
2542
|
+
process.stderr.write(
|
|
2543
|
+
`Seeded ${outPath} from the prior review (${String(count)} finding(s)) \u2014 verify each still holds against the current diff and refine in place
|
|
2544
|
+
`
|
|
2545
|
+
);
|
|
2546
|
+
process.stdout.write("prior\n");
|
|
2547
|
+
} else {
|
|
2548
|
+
writeEmptyScaffold();
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2403
2552
|
var adaptCmd = defineCommand({
|
|
2404
2553
|
meta: {
|
|
2405
2554
|
name: "adapt",
|
|
@@ -2430,7 +2579,7 @@ var adaptCmd = defineCommand({
|
|
|
2430
2579
|
},
|
|
2431
2580
|
transcript: {
|
|
2432
2581
|
type: "string",
|
|
2433
|
-
description: "Path to the session transcript (the main .jsonl)
|
|
2582
|
+
description: "Path to the session transcript (the main .jsonl). Its tree (main + subagents) is the source of the true wall + turn count \u2014 the native envelope only sees the main agent and under-reports a fan-out (issue #59) \u2014 and refills per-model usage too when the native has none (a wall-clock kill leaves it empty, so cost is real not $0.00 \u2014 issues #39/#36)"
|
|
2434
2583
|
}
|
|
2435
2584
|
},
|
|
2436
2585
|
run: async ({ args }) => {
|
|
@@ -2844,7 +2993,7 @@ var main = defineCommand({
|
|
|
2844
2993
|
meta: {
|
|
2845
2994
|
name: "code-review",
|
|
2846
2995
|
version: packageVersion,
|
|
2847
|
-
description: "Deterministic commenter for agentic PR review \u2014 gather, render, inline, post, adapt, extract, validate-patches, cost, check-cost, validate, stop-gate, budget-hook, print-settings, and deadline"
|
|
2996
|
+
description: "Deterministic commenter for agentic PR review \u2014 gather, render, inline, post, adapt, extract, validate-patches, cost, check-cost, validate, seed-draft, stop-gate, budget-hook, print-settings, and deadline"
|
|
2848
2997
|
},
|
|
2849
2998
|
subCommands: {
|
|
2850
2999
|
gather: gatherCmd,
|
|
@@ -2854,6 +3003,7 @@ var main = defineCommand({
|
|
|
2854
3003
|
cost: costCmd,
|
|
2855
3004
|
"check-cost": checkCostCmd,
|
|
2856
3005
|
validate: validateCmd,
|
|
3006
|
+
"seed-draft": seedDraftCmd,
|
|
2857
3007
|
adapt: adaptCmd,
|
|
2858
3008
|
extract: extractCmd,
|
|
2859
3009
|
"validate-patches": validatePatchesCmd,
|