@jphutchins/code-review 0.1.0-alpha.64 → 0.1.0-alpha.65
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 +3 -3
- package/dist/index.js +579 -405
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +2 -1
- package/schema/findings.schema.json +9 -1
- package/templates/comment.eta +13 -1
- package/templates/inline.eta +5 -1
package/dist/index.js
CHANGED
|
@@ -109,7 +109,8 @@ var FindingIdRequired = t.type({ id: t.string });
|
|
|
109
109
|
var FindingOptional = t.partial({
|
|
110
110
|
side: SideCodec,
|
|
111
111
|
recommendation: t.string,
|
|
112
|
-
patch: t.string
|
|
112
|
+
patch: t.string,
|
|
113
|
+
rebuttal: t.string
|
|
113
114
|
});
|
|
114
115
|
var EndGeStart = (shape) => t.refinement(shape, (f) => f.end_line >= f.start_line, "EndGeStart");
|
|
115
116
|
var FindingShape = t.intersection([
|
|
@@ -135,7 +136,8 @@ var SystemicRequired = t.type({
|
|
|
135
136
|
var SystemicOptional = t.partial({
|
|
136
137
|
id: t.string,
|
|
137
138
|
finding_ids: t.array(t.string),
|
|
138
|
-
paths: t.array(t.string)
|
|
139
|
+
paths: t.array(t.string),
|
|
140
|
+
rebuttal: t.string
|
|
139
141
|
});
|
|
140
142
|
var SystemicProblemShape = t.intersection([SystemicRequired, RuleUrlCodec, SystemicOptional]);
|
|
141
143
|
var SystemicProblemCodec = strictExact("SystemicProblemStrict", SystemicProblemShape, [
|
|
@@ -245,6 +247,31 @@ var RECOVERABLE_OPTIONAL_FIELDS = /* @__PURE__ */ new Set([
|
|
|
245
247
|
...PIPELINE_STAMPED_FIELDS,
|
|
246
248
|
"change_size"
|
|
247
249
|
]);
|
|
250
|
+
var ID_SHAPE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
251
|
+
var priorIdsFrom = (doc) => {
|
|
252
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return [];
|
|
253
|
+
const rec = doc;
|
|
254
|
+
const idsOf = (field, systemic) => (Array.isArray(rec[field]) ? rec[field] : []).flatMap((raw) => {
|
|
255
|
+
const item = typeof raw === "object" && raw !== null ? asRecord(raw) : null;
|
|
256
|
+
if (item === null) return [];
|
|
257
|
+
const title = typeof item["title"] === "string" ? item["title"] : "";
|
|
258
|
+
const resolved = resolveRuleId({
|
|
259
|
+
...typeof item["id"] === "string" ? { id: item["id"] } : {},
|
|
260
|
+
...typeof item["code"] === "string" ? { code: item["code"] } : {},
|
|
261
|
+
...typeof item["path"] === "string" ? { path: item["path"] } : {},
|
|
262
|
+
title
|
|
263
|
+
});
|
|
264
|
+
const id = resolved ?? (systemic && title !== "" ? synthesizedSystemicId(title) : void 0);
|
|
265
|
+
return id !== void 0 ? [id] : [];
|
|
266
|
+
});
|
|
267
|
+
return [...idsOf("findings", false), ...idsOf("systemic_problems", true)];
|
|
268
|
+
};
|
|
269
|
+
var hasRebuttal = (item) => (item.rebuttal ?? "").trim() !== "";
|
|
270
|
+
var withoutRebuttals = (doc) => ({
|
|
271
|
+
...doc,
|
|
272
|
+
findings: doc.findings.map(({ rebuttal: _rebuttal, ...f }) => f),
|
|
273
|
+
...doc.systemic_problems !== void 0 ? { systemic_problems: doc.systemic_problems.map(({ rebuttal: _rebuttal, ...s }) => s) } : {}
|
|
274
|
+
});
|
|
248
275
|
var synthesizedId = (parts, prefix) => `${prefix}${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 12)}`;
|
|
249
276
|
var synthesizedFindingId = (path, title) => synthesizedId([path, title], "f-");
|
|
250
277
|
var synthesizedSystemicId = (title) => synthesizedId([title], "s-");
|
|
@@ -270,12 +297,18 @@ var LegacyRuleCodec = t.partial({
|
|
|
270
297
|
id: t.string,
|
|
271
298
|
code_url: UriString
|
|
272
299
|
});
|
|
273
|
-
var
|
|
300
|
+
var FindingOptionalV09 = t.partial({
|
|
301
|
+
side: SideCodec,
|
|
302
|
+
recommendation: t.string,
|
|
303
|
+
patch: t.string
|
|
304
|
+
});
|
|
305
|
+
var FindingShapeV09 = t.intersection([FindingCoreRequired, LegacyRuleCodec, FindingOptionalV09]);
|
|
274
306
|
var FindingCodecV09 = t.exact(EndGeStart(FindingShapeV09));
|
|
275
307
|
var SystemicV09Optional = t.partial({
|
|
276
308
|
finding_codes: t.array(t.string),
|
|
277
309
|
finding_ids: t.array(t.string),
|
|
278
|
-
paths: t.array(t.string)
|
|
310
|
+
paths: t.array(t.string),
|
|
311
|
+
rebuttal: t.unknown
|
|
279
312
|
});
|
|
280
313
|
var SystemicV09Shape = t.intersection([SystemicRequired, LegacyRuleCodec, SystemicV09Optional]);
|
|
281
314
|
var SystemicProblemCodecV09 = strictExact("SystemicV09Strict", SystemicV09Shape, [
|
|
@@ -343,7 +376,7 @@ var normalizeV09 = (doc) => {
|
|
|
343
376
|
id: resolveRuleId({ id, code, path: f.path, title: f.title }) ?? synthesizedFindingId(f.path, f.title)
|
|
344
377
|
}));
|
|
345
378
|
const systemic_problems = doc.systemic_problems?.map(
|
|
346
|
-
({ code, id, finding_codes, finding_ids, ...s }) => ({
|
|
379
|
+
({ code, id, finding_codes, finding_ids, rebuttal: _rebuttal, ...s }) => ({
|
|
347
380
|
...s,
|
|
348
381
|
id: resolveRuleId({ id, code, title: s.title }) ?? synthesizedSystemicId(s.title),
|
|
349
382
|
...finding_ids !== void 0 && finding_ids.length > 0 ? { finding_ids } : finding_codes !== void 0 ? { finding_ids: finding_codes } : {}
|
|
@@ -484,7 +517,7 @@ var TestSummaryCodec = t.intersection([
|
|
|
484
517
|
failures: t.array(TestFailureCodec)
|
|
485
518
|
})
|
|
486
519
|
]);
|
|
487
|
-
var DEFAULT_SCHEMA_VERSION = "0.
|
|
520
|
+
var DEFAULT_SCHEMA_VERSION = "0.11.0";
|
|
488
521
|
var incompleteFindings = (summary) => ({
|
|
489
522
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
490
523
|
summary,
|
|
@@ -494,7 +527,7 @@ var incompleteFindings = (summary) => ({
|
|
|
494
527
|
var isIncompleteFindings = (findings) => findings.verdict === "error" && findings.findings.length === 0;
|
|
495
528
|
|
|
496
529
|
// src/released.ts
|
|
497
|
-
var RELEASED = "2026-10-
|
|
530
|
+
var RELEASED = "2026-10-10";
|
|
498
531
|
|
|
499
532
|
// src/cost.ts
|
|
500
533
|
var defaultWarn = (message) => {
|
|
@@ -824,6 +857,8 @@ var isSeverityCounts = (u) => typeof u === "object" && u !== null && SEVERITIES.
|
|
|
824
857
|
var MAX_IDS_PER_ROUND = 8;
|
|
825
858
|
var hasId = (ids, code) => ids !== void 0 && Object.prototype.hasOwnProperty.call(ids, code);
|
|
826
859
|
var escapeCodeBackticks = (code) => code.replace(/`/g, "-").replace(/\r?\n/g, " ");
|
|
860
|
+
var encodeAutolinkParens = (url) => url.replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
861
|
+
var linkSafeUrl = (url) => encodeAutolinkParens(escapeCodeBackticks(url));
|
|
827
862
|
var normalizeIdCounts = (ids, priorCodes, preferredKeys, preValidated = false) => {
|
|
828
863
|
const entries = Object.entries((preValidated ? ids : usableCountsMap(ids)) ?? {});
|
|
829
864
|
if (entries.length === 0) return void 0;
|
|
@@ -1356,270 +1391,32 @@ var resolvePriorFindings = async (body, read) => {
|
|
|
1356
1391
|
}
|
|
1357
1392
|
};
|
|
1358
1393
|
|
|
1359
|
-
// src/comment-chain.ts
|
|
1360
|
-
function* ancestors(start, parentOf) {
|
|
1361
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1362
|
-
let current = start;
|
|
1363
|
-
while (current !== null && !seen.has(current)) {
|
|
1364
|
-
yield current;
|
|
1365
|
-
seen.add(current);
|
|
1366
|
-
current = parentOf(current);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
var numField = (rec, key2) => {
|
|
1370
|
-
const v = rec[key2];
|
|
1371
|
-
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
1372
|
-
};
|
|
1373
|
-
var messageUsage = (entry) => {
|
|
1374
|
-
const rec = asRecord(entry);
|
|
1375
|
-
if (rec === null || rec["type"] !== "assistant") return null;
|
|
1376
|
-
const msg = asRecord(rec["message"]);
|
|
1377
|
-
if (msg === null) return null;
|
|
1378
|
-
const model = msg["model"];
|
|
1379
|
-
const usage = asRecord(msg["usage"]);
|
|
1380
|
-
if (typeof model !== "string" || usage === null) return null;
|
|
1381
|
-
const id = msg["id"];
|
|
1382
|
-
return {
|
|
1383
|
-
id: typeof id === "string" ? id : null,
|
|
1384
|
-
model: modelIdentity(model),
|
|
1385
|
-
input: numField(usage, "input_tokens"),
|
|
1386
|
-
output: numField(usage, "output_tokens"),
|
|
1387
|
-
cacheRead: numField(usage, "cache_read_input_tokens"),
|
|
1388
|
-
cacheWrite: numField(usage, "cache_creation_input_tokens")
|
|
1389
|
-
};
|
|
1390
|
-
};
|
|
1391
|
-
var tsMsOf = (entry) => {
|
|
1392
|
-
const rec = asRecord(entry);
|
|
1393
|
-
const ts = rec?.["timestamp"];
|
|
1394
|
-
if (typeof ts !== "string") return null;
|
|
1395
|
-
const ms = Date.parse(ts);
|
|
1396
|
-
return Number.isNaN(ms) ? null : ms;
|
|
1397
|
-
};
|
|
1398
|
-
var EMPTY_TOTALS = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
1399
|
-
var parseJsonl = (text) => text.split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
1400
|
-
try {
|
|
1401
|
-
return [JSON.parse(line)];
|
|
1402
|
-
} catch {
|
|
1403
|
-
return [];
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
var sumTranscriptUsage = (entries) => {
|
|
1407
|
-
const summed = entries.reduce(
|
|
1408
|
-
(acc, entry) => {
|
|
1409
|
-
const u = messageUsage(entry);
|
|
1410
|
-
if (u === null) return acc;
|
|
1411
|
-
if (u.id !== null && acc.seen.has(u.id)) return acc;
|
|
1412
|
-
if (u.id !== null) acc.seen.add(u.id);
|
|
1413
|
-
const prev = acc.totals.get(u.model) ?? EMPTY_TOTALS;
|
|
1414
|
-
acc.totals.set(u.model, {
|
|
1415
|
-
input: prev.input + u.input,
|
|
1416
|
-
output: prev.output + u.output,
|
|
1417
|
-
cacheRead: prev.cacheRead + u.cacheRead,
|
|
1418
|
-
cacheWrite: prev.cacheWrite + u.cacheWrite
|
|
1419
|
-
});
|
|
1420
|
-
return { totals: acc.totals, turns: acc.turns + 1, seen: acc.seen };
|
|
1421
|
-
},
|
|
1422
|
-
{ totals: /* @__PURE__ */ new Map(), turns: 0, seen: /* @__PURE__ */ new Set() }
|
|
1423
|
-
);
|
|
1424
|
-
const models = [...summed.totals].map(([model, t8]) => ({
|
|
1425
|
-
model,
|
|
1426
|
-
input_tokens: t8.input,
|
|
1427
|
-
output_tokens: t8.output,
|
|
1428
|
-
cache_read_tokens: t8.cacheRead,
|
|
1429
|
-
cache_write_tokens: t8.cacheWrite
|
|
1430
|
-
}));
|
|
1431
|
-
const bounds = entries.reduce(
|
|
1432
|
-
(acc, entry) => {
|
|
1433
|
-
const ms = tsMsOf(entry);
|
|
1434
|
-
if (ms === null) return acc;
|
|
1435
|
-
return {
|
|
1436
|
-
min: acc.min === null || ms < acc.min ? ms : acc.min,
|
|
1437
|
-
max: acc.max === null || ms > acc.max ? ms : acc.max
|
|
1438
|
-
};
|
|
1439
|
-
},
|
|
1440
|
-
{ min: null, max: null }
|
|
1441
|
-
);
|
|
1442
|
-
return {
|
|
1443
|
-
models,
|
|
1444
|
-
turns: summed.turns,
|
|
1445
|
-
durationMs: bounds.min !== null && bounds.max !== null ? bounds.max - bounds.min : 0,
|
|
1446
|
-
firstTsMs: bounds.min,
|
|
1447
|
-
lastTsMs: bounds.max
|
|
1448
|
-
};
|
|
1449
|
-
};
|
|
1450
|
-
var subagentFiles = (mainPath) => {
|
|
1451
|
-
const dir = join(dirname(mainPath), basename(mainPath, ".jsonl"), "subagents");
|
|
1452
|
-
try {
|
|
1453
|
-
return readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => join(dir, name));
|
|
1454
|
-
} catch {
|
|
1455
|
-
return [];
|
|
1456
|
-
}
|
|
1457
|
-
};
|
|
1458
|
-
var readTranscriptTree = (mainPath) => {
|
|
1459
|
-
const mainText = readFileOrNull(mainPath);
|
|
1460
|
-
const mainEntries = mainText === null ? [] : parseJsonl(mainText);
|
|
1461
|
-
const inlineSidechains = mainEntries.some((e) => asRecord(e)?.["isSidechain"] === true);
|
|
1462
|
-
const siblings = inlineSidechains ? [] : subagentFiles(mainPath);
|
|
1463
|
-
const siblingReads = siblings.flatMap((path) => {
|
|
1464
|
-
const text = readFileOrNull(path);
|
|
1465
|
-
return text === null ? [] : [{ path, entries: parseJsonl(text) }];
|
|
1466
|
-
});
|
|
1467
|
-
return {
|
|
1468
|
-
entries: [...mainEntries, ...siblingReads.flatMap((r) => r.entries)],
|
|
1469
|
-
files: [...mainText === null ? [] : [mainPath], ...siblingReads.map((r) => r.path)],
|
|
1470
|
-
missing: mainText === null
|
|
1471
|
-
};
|
|
1472
|
-
};
|
|
1473
|
-
|
|
1474
1394
|
// src/answered.ts
|
|
1475
|
-
var
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
description: t.string,
|
|
1491
|
-
reasoning: t.string,
|
|
1492
|
-
severity: t.union([
|
|
1493
|
-
t.literal("critical"),
|
|
1494
|
-
t.literal("major"),
|
|
1495
|
-
t.literal("minor"),
|
|
1496
|
-
t.literal("nit")
|
|
1497
|
-
]),
|
|
1498
|
-
path: t.string,
|
|
1499
|
-
patch: t.union([t.string, t.null]),
|
|
1500
|
-
replied_at: t.union([t.string, t.null]),
|
|
1501
|
-
reply_id: t.number,
|
|
1502
|
-
thread_url: t.string,
|
|
1503
|
-
reply_url: t.string,
|
|
1504
|
-
reply_author: t.string,
|
|
1505
|
-
reply_excerpt: t.string
|
|
1506
|
-
});
|
|
1507
|
-
var encodeAnsweredEntry = (e) => ({
|
|
1508
|
-
code: e.code,
|
|
1509
|
-
title: e.title,
|
|
1510
|
-
description: e.description,
|
|
1511
|
-
reasoning: e.reasoning,
|
|
1512
|
-
severity: e.severity,
|
|
1513
|
-
path: e.path,
|
|
1514
|
-
patch: e.patch,
|
|
1515
|
-
replied_at: e.repliedAt,
|
|
1516
|
-
reply_id: e.replyId,
|
|
1517
|
-
thread_url: e.threadUrl,
|
|
1518
|
-
reply_url: e.replyUrl,
|
|
1519
|
-
reply_author: e.replyAuthor,
|
|
1520
|
-
reply_excerpt: e.replyExcerpt
|
|
1395
|
+
var answeredRegistryFrom = (closures, prior) => closures.flatMap((response) => {
|
|
1396
|
+
const finding = prior?.findings.find((f) => resolveFindingId(f) === response.id);
|
|
1397
|
+
return finding === void 0 ? [] : [
|
|
1398
|
+
{
|
|
1399
|
+
code: response.id,
|
|
1400
|
+
title: finding.title,
|
|
1401
|
+
description: finding.description,
|
|
1402
|
+
reasoning: finding.reasoning,
|
|
1403
|
+
severity: finding.severity,
|
|
1404
|
+
path: finding.path,
|
|
1405
|
+
patch: finding.patch ?? null,
|
|
1406
|
+
answerUrl: response.source_url,
|
|
1407
|
+
answerAuthor: response.author
|
|
1408
|
+
}
|
|
1409
|
+
];
|
|
1521
1410
|
});
|
|
1522
|
-
var decodeAnsweredEntry = (raw) => {
|
|
1523
|
-
const decoded = AnsweredEntryCodec.decode(raw);
|
|
1524
|
-
return decoded._tag === "Right" ? decoded.right : null;
|
|
1525
|
-
};
|
|
1526
|
-
var isHuman = (login, type8, botLogin) => login !== botLogin && type8 === "User";
|
|
1527
|
-
var EXCERPT_LIMIT = 400;
|
|
1528
|
-
var answeredRegistryFrom = (comments, botLogin) => {
|
|
1529
|
-
const ordered = [...comments].sort(
|
|
1530
|
-
(a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? "") || (a.id > b.id ? 1 : a.id < b.id ? -1 : 0)
|
|
1531
|
-
);
|
|
1532
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1533
|
-
for (const c of ordered) byId.set(c.id, c);
|
|
1534
|
-
const rootOf = (c) => {
|
|
1535
|
-
const chain = [
|
|
1536
|
-
...ancestors(
|
|
1537
|
-
c,
|
|
1538
|
-
(n) => n.in_reply_to_id === null ? null : byId.get(n.in_reply_to_id) ?? null
|
|
1539
|
-
)
|
|
1540
|
-
];
|
|
1541
|
-
const root = chain[chain.length - 1];
|
|
1542
|
-
return root !== void 0 && root.in_reply_to_id === null ? root : null;
|
|
1543
|
-
};
|
|
1544
|
-
const findingOf = (root) => {
|
|
1545
|
-
const decoded = parseFindingsMarker(root.body ?? "");
|
|
1546
|
-
const doc = typeof decoded === "object" && decoded !== null ? decoded.findings : void 0;
|
|
1547
|
-
const first = Array.isArray(doc) ? doc[0] : void 0;
|
|
1548
|
-
if (first === void 0) return null;
|
|
1549
|
-
const title = first["title"];
|
|
1550
|
-
const description = first["description"];
|
|
1551
|
-
const reasoning = first["reasoning"];
|
|
1552
|
-
const id = first["id"];
|
|
1553
|
-
const legacyCode = first["code"];
|
|
1554
|
-
const severity = first["severity"];
|
|
1555
|
-
const path = first["path"];
|
|
1556
|
-
const patch = first["patch"];
|
|
1557
|
-
return typeof title === "string" && typeof description === "string" && typeof reasoning === "string" && typeof path === "string" && (severity === "critical" || severity === "major" || severity === "minor" || severity === "nit") ? {
|
|
1558
|
-
// resolveRuleId: the ONE legacy-spelling precedence the upcast, this reader, and the
|
|
1559
|
-
// below-floor nit reader share — a pre-id marker (or one written before the migration)
|
|
1560
|
-
// carries `code`; a codeless one resolves to the same synthesized id the registry's
|
|
1561
|
-
// legacy upcast derives, so the entry keys to the identical claim on the next round.
|
|
1562
|
-
code: resolveRuleId({
|
|
1563
|
-
id: typeof id === "string" ? id : void 0,
|
|
1564
|
-
code: typeof legacyCode === "string" ? legacyCode : void 0,
|
|
1565
|
-
path,
|
|
1566
|
-
title
|
|
1567
|
-
}) ?? synthesizedFindingId(path, title),
|
|
1568
|
-
title,
|
|
1569
|
-
description,
|
|
1570
|
-
reasoning,
|
|
1571
|
-
severity,
|
|
1572
|
-
path,
|
|
1573
|
-
patch: typeof patch === "string" ? patch : null
|
|
1574
|
-
} : null;
|
|
1575
|
-
};
|
|
1576
|
-
const threads = /* @__PURE__ */ new Map();
|
|
1577
|
-
for (const c of ordered) {
|
|
1578
|
-
const root = rootOf(c);
|
|
1579
|
-
if (root === null) continue;
|
|
1580
|
-
const group = threads.get(root.id);
|
|
1581
|
-
if (group === void 0) threads.set(root.id, [c]);
|
|
1582
|
-
else group.push(c);
|
|
1583
|
-
}
|
|
1584
|
-
const entries = [];
|
|
1585
|
-
for (const [rootId, group] of threads) {
|
|
1586
|
-
const root = byId.get(rootId);
|
|
1587
|
-
if (root === void 0 || root.user_login !== botLogin) continue;
|
|
1588
|
-
const finding = findingOf(root);
|
|
1589
|
-
if (finding === null) continue;
|
|
1590
|
-
const humanReplies = group.filter(
|
|
1591
|
-
(c) => c.id !== root.id && isHuman(c.user_login, c.user_type, botLogin)
|
|
1592
|
-
);
|
|
1593
|
-
const reply = humanReplies[humanReplies.length - 1];
|
|
1594
|
-
if (reply === void 0) continue;
|
|
1595
|
-
entries.push({
|
|
1596
|
-
...finding,
|
|
1597
|
-
repliedAt: reply.created_at,
|
|
1598
|
-
replyId: reply.id,
|
|
1599
|
-
threadUrl: root.html_url,
|
|
1600
|
-
replyUrl: reply.html_url,
|
|
1601
|
-
replyAuthor: reply.user_login,
|
|
1602
|
-
replyExcerpt: clipText(reply.body ?? "", EXCERPT_LIMIT)
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
1606
|
-
for (const entry of [...entries].sort(
|
|
1607
|
-
(a, b) => (b.repliedAt ?? "").localeCompare(a.repliedAt ?? "") || b.replyId - a.replyId
|
|
1608
|
-
)) {
|
|
1609
|
-
const key2 = answeredNoteKey({ id: entry.code, title: entry.title });
|
|
1610
|
-
if (!byKey.has(key2)) byKey.set(key2, entry);
|
|
1611
|
-
}
|
|
1612
|
-
return [...byKey.values()];
|
|
1613
|
-
};
|
|
1614
1411
|
var isSynthesizedTitleMatch = (f, e) => isSynthesizedFindingId(e.code) && e.title === f.title;
|
|
1615
1412
|
var matches = (resolvedId, f, e) => e.code === resolvedId || isSynthesizedTitleMatch(f, e);
|
|
1413
|
+
var couldMatch = (code, findings) => isSynthesizedFindingId(code) || findings.some((f) => resolveFindingId(f) === code);
|
|
1616
1414
|
var VERBATIM_FIELDS = ["title", "description", "reasoning", "severity", "path", "patch"];
|
|
1617
1415
|
var verbatimFieldEqual = (f, e, field) => field === "patch" ? (f.patch ?? null) === e.patch : f[field] === e[field];
|
|
1618
|
-
var isVerbatimReRaise = (f, e) => VERBATIM_FIELDS.every((field) => verbatimFieldEqual(f, e, field));
|
|
1416
|
+
var isVerbatimReRaise = (f, e) => !hasRebuttal(f) && VERBATIM_FIELDS.every((field) => verbatimFieldEqual(f, e, field));
|
|
1619
1417
|
var isAnsweredDrop = (resolvedId, f, e) => matches(resolvedId, f, e) && isVerbatimReRaise(f, e) && f.severity !== "critical";
|
|
1620
1418
|
var verbatimMatchCount = (f, e) => VERBATIM_FIELDS.reduce((count, field) => count + (verbatimFieldEqual(f, e, field) ? 1 : 0), 0);
|
|
1621
1419
|
var answeredNoteKey = (f) => f.id !== "" ? f.id : `title:${f.title}`;
|
|
1622
|
-
var NO_TITLE_MATCH = { entry: void 0, score: -1 };
|
|
1623
1420
|
var bestTitleMatch = (f, registry) => {
|
|
1624
1421
|
let best;
|
|
1625
1422
|
let bestScore = -1;
|
|
@@ -1632,9 +1429,10 @@ var bestTitleMatch = (f, registry) => {
|
|
|
1632
1429
|
if (score === VERBATIM_FIELDS.length) break;
|
|
1633
1430
|
}
|
|
1634
1431
|
}
|
|
1635
|
-
return
|
|
1432
|
+
return best;
|
|
1636
1433
|
};
|
|
1637
|
-
var
|
|
1434
|
+
var byAuthor = (e) => e.answerAuthor === null ? "" : ` by \`${escapeCodeBackticks(e.answerAuthor)}\``;
|
|
1435
|
+
var answeredNote = (e) => `Re-raised; prior answer at ${linkSafeUrl(e.answerUrl)}${byAuthor(e)} \u2014 cite the new evidence that invalidates it.`;
|
|
1638
1436
|
var applyAnswered = (findings, registry) => {
|
|
1639
1437
|
const kept = [];
|
|
1640
1438
|
const noteEntries = [];
|
|
@@ -1644,15 +1442,15 @@ var applyAnswered = (findings, registry) => {
|
|
|
1644
1442
|
for (const f of findings) {
|
|
1645
1443
|
const resolvedId = resolveFindingId(f);
|
|
1646
1444
|
const idMatch = registry.find((e) => e.code === resolvedId);
|
|
1647
|
-
const
|
|
1445
|
+
const titleMatch = idMatch === void 0 ? bestTitleMatch(f, registry) : void 0;
|
|
1648
1446
|
const entry = idMatch ?? titleMatch;
|
|
1649
1447
|
if (entry === void 0) {
|
|
1650
1448
|
kept.push(f);
|
|
1651
1449
|
continue;
|
|
1652
1450
|
}
|
|
1653
|
-
const dropped = (
|
|
1451
|
+
const dropped = isAnsweredDrop(resolvedId, f, entry);
|
|
1654
1452
|
if (dropped) {
|
|
1655
|
-
droppedByEntry.set(entry.
|
|
1453
|
+
droppedByEntry.set(entry.code, entry);
|
|
1656
1454
|
droppedFindingIds.push(resolvedId);
|
|
1657
1455
|
droppedCount += 1;
|
|
1658
1456
|
} else {
|
|
@@ -1672,42 +1470,33 @@ var answeredReRaiseNote = (entries, count) => {
|
|
|
1672
1470
|
if (entries.length === 0) return "";
|
|
1673
1471
|
const label = (e) => e.code !== "" ? `\`${escapeCodeBackticks(e.code)}\`` : `\u201C${escapeCodeBackticks(e.title)}\u201D`;
|
|
1674
1472
|
const lines = entries.map(
|
|
1675
|
-
(e) => `> - ${label(e)} \u2014 [prior answer](${e.
|
|
1473
|
+
(e) => `> - ${label(e)} \u2014 [prior answer](${linkSafeUrl(e.answerUrl)})${byAuthor(e)}`
|
|
1676
1474
|
);
|
|
1677
1475
|
return [
|
|
1678
|
-
`> \u21A9\uFE0F **${String(count)} finding(s) re-raised without new evidence \u2014 treated as answered** (
|
|
1476
|
+
`> \u21A9\uFE0F **${String(count)} finding(s) re-raised without new evidence \u2014 treated as answered** (a maintainer refuted or dismissed each):`,
|
|
1679
1477
|
...lines
|
|
1680
1478
|
].join("\n");
|
|
1681
1479
|
};
|
|
1682
|
-
var
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
process.stderr.write(
|
|
1700
|
-
`Warning: could not fetch review threads to detect answered findings (${errMsg(err)}) \u2014 no answered-finding state for this post
|
|
1701
|
-
`
|
|
1702
|
-
);
|
|
1703
|
-
return null;
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1480
|
+
var DispositionCodec = t.keyof({ fixed: null, refuted: null, dismissed: null });
|
|
1481
|
+
var DISPOSITIONS = Object.keys(DispositionCodec.keys);
|
|
1482
|
+
var ResponseDispositionCodec = t.keyof({
|
|
1483
|
+
fixed: null,
|
|
1484
|
+
refuted: null,
|
|
1485
|
+
dismissed: null,
|
|
1486
|
+
unstated: null
|
|
1487
|
+
});
|
|
1488
|
+
var RESPONSE_FORM = `Review-Response: <id> ${DISPOSITIONS.join("|")} \u2014 <reason>`;
|
|
1489
|
+
var RESPONSE_TABLE_COLUMNS = ["id", "disposition", "reason"];
|
|
1490
|
+
`| ${RESPONSE_TABLE_COLUMNS.join(" | ")} |`;
|
|
1491
|
+
`| ${RESPONSE_TABLE_COLUMNS.map(() => "---").join(" | ")} |`;
|
|
1492
|
+
var listed = (words, conjunction) => {
|
|
1493
|
+
const quoted = words.map((word) => `\`${word}\``);
|
|
1494
|
+
return quoted.length < 2 ? quoted.join("") : `${quoted.slice(0, -1).join(", ")} ${conjunction} ${quoted.at(-1) ?? ""}`;
|
|
1495
|
+
};
|
|
1496
|
+
var RESPONSE_TEACHING = `To answer findings, post a PR conversation comment holding a rendered markdown table, not one inside a code block, whose columns are ${listed(RESPONSE_TABLE_COLUMNS, "and")}, one row per finding id, its disposition exactly one of ${listed(DISPOSITIONS, "or")}; or put \`${RESPONSE_FORM}\` lines in a commit message. Inline-thread replies are not read. A systemic problem's id, when shown, answers its whole class.`;
|
|
1706
1497
|
|
|
1707
1498
|
// src/render.ts
|
|
1708
1499
|
var escapePipes = (text) => text.replace(/\|/g, "\\|");
|
|
1709
|
-
var encodeAutolinkParens = (url) => url.replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
1710
|
-
var linkSafeUrl = (url) => encodeAutolinkParens(escapeCodeBackticks(url));
|
|
1711
1500
|
var shedNote = (count, summaryAvailable, runUrl, findingsDocLinked, thisRunArtifact) => count === 0 ? "" : `<sub>**${String(count)} finding${count === 1 ? "" : "s"} left out of this comment**${summaryAvailable && runUrl !== void 0 ? ` \u2014 the [run summary](<${runUrl}>) carries the whole review` : findingsDocLinked && thisRunArtifact ? " \u2014 the machine findings document carries them" : ""}.</sub>`;
|
|
1712
1501
|
var terminalNotice = (i) => [
|
|
1713
1502
|
`## ${i.verdictBadge}`,
|
|
@@ -1824,6 +1613,7 @@ var sanitizeSuppressedNit = (f, discussion, discussionTruncated) => ({
|
|
|
1824
1613
|
var carriedLines = (f) => [
|
|
1825
1614
|
`description: ${clipText(f.description, BODY_CLIP_CHARS)}`,
|
|
1826
1615
|
...f.recommendation !== void 0 ? [`recommendation: ${clipText(f.recommendation, BODY_CLIP_CHARS)}`] : [],
|
|
1616
|
+
...hasRebuttal(f) ? [`rebuttal: ${clipText(f.rebuttal ?? "", BODY_CLIP_CHARS)}`] : [],
|
|
1827
1617
|
`reasoning: ${clipText(f.reasoning, BODY_CLIP_CHARS)}`,
|
|
1828
1618
|
...f.patch !== void 0 ? ["patch:", clipText(f.patch, BODY_CLIP_CHARS)] : []
|
|
1829
1619
|
].flatMap((block) => commentSafe(block).split("\n")).map((line) => line.trimEnd());
|
|
@@ -1992,6 +1782,9 @@ var render = (input) => {
|
|
|
1992
1782
|
input.findingsPointer ?? findingsMarkerPair(input.jsonUrl, input.findings.convergence)
|
|
1993
1783
|
),
|
|
1994
1784
|
summaryAvailable: input.summaryAvailable ?? false,
|
|
1785
|
+
responseForm: RESPONSE_FORM,
|
|
1786
|
+
responseTeaching: RESPONSE_TEACHING,
|
|
1787
|
+
teachesResponses: isConvergenceRound(route, incomplete),
|
|
1995
1788
|
shedNote: shedNote(
|
|
1996
1789
|
input.shedCount ?? 0,
|
|
1997
1790
|
input.summaryAvailable ?? false,
|
|
@@ -2083,7 +1876,8 @@ var render = (input) => {
|
|
|
2083
1876
|
},
|
|
2084
1877
|
verdictBadge,
|
|
2085
1878
|
severityEmoji,
|
|
2086
|
-
formatConfidence
|
|
1879
|
+
formatConfidence,
|
|
1880
|
+
hasRebuttal
|
|
2087
1881
|
});
|
|
2088
1882
|
};
|
|
2089
1883
|
var key = (path, line) => `${path}:${String(line)}`;
|
|
@@ -2146,9 +1940,11 @@ var renderCommentBody = (f, eta, template, modelsText, jsonUrl, pointer, sameRoo
|
|
|
2146
1940
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
2147
1941
|
eta.renderString(template, {
|
|
2148
1942
|
...f,
|
|
1943
|
+
idLabel: escapeCodeBackticks(f.id),
|
|
2149
1944
|
patchProjection: projectPatch(f.patch, "diff-anchored"),
|
|
2150
1945
|
severityEmoji,
|
|
2151
1946
|
formatConfidence,
|
|
1947
|
+
hasRebuttal,
|
|
2152
1948
|
modelsText,
|
|
2153
1949
|
jsonUrl: jsonUrl ?? null,
|
|
2154
1950
|
findingsPointer: pointer,
|
|
@@ -2170,6 +1966,7 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
2170
1966
|
title: clipText(f.title, BODY_CLIP_CHARS),
|
|
2171
1967
|
description: clipText(f.description, BODY_CLIP_CHARS),
|
|
2172
1968
|
...f.recommendation != null ? { recommendation: clipText(f.recommendation, BODY_CLIP_CHARS) } : {},
|
|
1969
|
+
...hasRebuttal(f) ? { rebuttal: clipText(f.rebuttal ?? "", BODY_CLIP_CHARS) } : {},
|
|
2173
1970
|
reasoning: clipText(f.reasoning, BODY_CLIP_CHARS),
|
|
2174
1971
|
...f.patch != null ? { patch: clipText(f.patch, BODY_CLIP_CHARS) } : {}
|
|
2175
1972
|
} : f;
|
|
@@ -2204,7 +2001,7 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
2204
2001
|
var renderStraysSection = (strays) => {
|
|
2205
2002
|
if (strays.length === 0) return "";
|
|
2206
2003
|
const items = strays.map(
|
|
2207
|
-
(f) => `- **${f.severity}** \xB7 \`${f.path}:${String(f.start_line)}\` \u2014 ${f.title}`
|
|
2004
|
+
(f) => `- **${f.severity}** \xB7 \`${f.path}:${String(f.start_line)}\` \u2014 ${f.title}${f.id === "" ? "" : ` \xB7 id \`${escapeCodeBackticks(f.id)}\``}`
|
|
2208
2005
|
);
|
|
2209
2006
|
return [
|
|
2210
2007
|
"",
|
|
@@ -2215,6 +2012,110 @@ var renderStraysSection = (strays) => {
|
|
|
2215
2012
|
...items
|
|
2216
2013
|
].join("\n");
|
|
2217
2014
|
};
|
|
2015
|
+
var numField = (rec, key2) => {
|
|
2016
|
+
const v = rec[key2];
|
|
2017
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
2018
|
+
};
|
|
2019
|
+
var messageUsage = (entry) => {
|
|
2020
|
+
const rec = asRecord(entry);
|
|
2021
|
+
if (rec === null || rec["type"] !== "assistant") return null;
|
|
2022
|
+
const msg = asRecord(rec["message"]);
|
|
2023
|
+
if (msg === null) return null;
|
|
2024
|
+
const model = msg["model"];
|
|
2025
|
+
const usage = asRecord(msg["usage"]);
|
|
2026
|
+
if (typeof model !== "string" || usage === null) return null;
|
|
2027
|
+
const id = msg["id"];
|
|
2028
|
+
return {
|
|
2029
|
+
id: typeof id === "string" ? id : null,
|
|
2030
|
+
model: modelIdentity(model),
|
|
2031
|
+
input: numField(usage, "input_tokens"),
|
|
2032
|
+
output: numField(usage, "output_tokens"),
|
|
2033
|
+
cacheRead: numField(usage, "cache_read_input_tokens"),
|
|
2034
|
+
cacheWrite: numField(usage, "cache_creation_input_tokens")
|
|
2035
|
+
};
|
|
2036
|
+
};
|
|
2037
|
+
var tsMsOf = (entry) => {
|
|
2038
|
+
const rec = asRecord(entry);
|
|
2039
|
+
const ts = rec?.["timestamp"];
|
|
2040
|
+
if (typeof ts !== "string") return null;
|
|
2041
|
+
const ms = Date.parse(ts);
|
|
2042
|
+
return Number.isNaN(ms) ? null : ms;
|
|
2043
|
+
};
|
|
2044
|
+
var EMPTY_TOTALS = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2045
|
+
var parseJsonl = (text) => text.split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
2046
|
+
try {
|
|
2047
|
+
return [JSON.parse(line)];
|
|
2048
|
+
} catch {
|
|
2049
|
+
return [];
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
var sumTranscriptUsage = (entries) => {
|
|
2053
|
+
const summed = entries.reduce(
|
|
2054
|
+
(acc, entry) => {
|
|
2055
|
+
const u = messageUsage(entry);
|
|
2056
|
+
if (u === null) return acc;
|
|
2057
|
+
if (u.id !== null && acc.seen.has(u.id)) return acc;
|
|
2058
|
+
if (u.id !== null) acc.seen.add(u.id);
|
|
2059
|
+
const prev = acc.totals.get(u.model) ?? EMPTY_TOTALS;
|
|
2060
|
+
acc.totals.set(u.model, {
|
|
2061
|
+
input: prev.input + u.input,
|
|
2062
|
+
output: prev.output + u.output,
|
|
2063
|
+
cacheRead: prev.cacheRead + u.cacheRead,
|
|
2064
|
+
cacheWrite: prev.cacheWrite + u.cacheWrite
|
|
2065
|
+
});
|
|
2066
|
+
return { totals: acc.totals, turns: acc.turns + 1, seen: acc.seen };
|
|
2067
|
+
},
|
|
2068
|
+
{ totals: /* @__PURE__ */ new Map(), turns: 0, seen: /* @__PURE__ */ new Set() }
|
|
2069
|
+
);
|
|
2070
|
+
const models = [...summed.totals].map(([model, t9]) => ({
|
|
2071
|
+
model,
|
|
2072
|
+
input_tokens: t9.input,
|
|
2073
|
+
output_tokens: t9.output,
|
|
2074
|
+
cache_read_tokens: t9.cacheRead,
|
|
2075
|
+
cache_write_tokens: t9.cacheWrite
|
|
2076
|
+
}));
|
|
2077
|
+
const bounds = entries.reduce(
|
|
2078
|
+
(acc, entry) => {
|
|
2079
|
+
const ms = tsMsOf(entry);
|
|
2080
|
+
if (ms === null) return acc;
|
|
2081
|
+
return {
|
|
2082
|
+
min: acc.min === null || ms < acc.min ? ms : acc.min,
|
|
2083
|
+
max: acc.max === null || ms > acc.max ? ms : acc.max
|
|
2084
|
+
};
|
|
2085
|
+
},
|
|
2086
|
+
{ min: null, max: null }
|
|
2087
|
+
);
|
|
2088
|
+
return {
|
|
2089
|
+
models,
|
|
2090
|
+
turns: summed.turns,
|
|
2091
|
+
durationMs: bounds.min !== null && bounds.max !== null ? bounds.max - bounds.min : 0,
|
|
2092
|
+
firstTsMs: bounds.min,
|
|
2093
|
+
lastTsMs: bounds.max
|
|
2094
|
+
};
|
|
2095
|
+
};
|
|
2096
|
+
var subagentFiles = (mainPath) => {
|
|
2097
|
+
const dir = join(dirname(mainPath), basename(mainPath, ".jsonl"), "subagents");
|
|
2098
|
+
try {
|
|
2099
|
+
return readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => join(dir, name));
|
|
2100
|
+
} catch {
|
|
2101
|
+
return [];
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
var readTranscriptTree = (mainPath) => {
|
|
2105
|
+
const mainText = readFileOrNull(mainPath);
|
|
2106
|
+
const mainEntries = mainText === null ? [] : parseJsonl(mainText);
|
|
2107
|
+
const inlineSidechains = mainEntries.some((e) => asRecord(e)?.["isSidechain"] === true);
|
|
2108
|
+
const siblings = inlineSidechains ? [] : subagentFiles(mainPath);
|
|
2109
|
+
const siblingReads = siblings.flatMap((path) => {
|
|
2110
|
+
const text = readFileOrNull(path);
|
|
2111
|
+
return text === null ? [] : [{ path, entries: parseJsonl(text) }];
|
|
2112
|
+
});
|
|
2113
|
+
return {
|
|
2114
|
+
entries: [...mainEntries, ...siblingReads.flatMap((r) => r.entries)],
|
|
2115
|
+
files: [...mainText === null ? [] : [mainPath], ...siblingReads.map((r) => r.path)],
|
|
2116
|
+
missing: mainText === null
|
|
2117
|
+
};
|
|
2118
|
+
};
|
|
2218
2119
|
var addFormats = _addFormats;
|
|
2219
2120
|
var validatorCache = /* @__PURE__ */ new Map();
|
|
2220
2121
|
var compileSchema = (schemaPath) => {
|
|
@@ -2396,7 +2297,7 @@ var writesToDraft = (toolName, toolInput, draftPath) => {
|
|
|
2396
2297
|
const targets = [draftPath, basename(draftPath), "$DRAFT", "${DRAFT}"];
|
|
2397
2298
|
if (WRITE_TOOLS.has(toolName)) {
|
|
2398
2299
|
const fp = rec?.["file_path"] ?? rec?.["notebook_path"];
|
|
2399
|
-
if (typeof fp === "string" && targets.some((
|
|
2300
|
+
if (typeof fp === "string" && targets.some((t9) => fp === t9 || basename(fp) === basename(t9)))
|
|
2400
2301
|
return true;
|
|
2401
2302
|
}
|
|
2402
2303
|
if (toolName === "Bash") {
|
|
@@ -2423,7 +2324,6 @@ var sidecarPath = (draftPath, postfix) => {
|
|
|
2423
2324
|
return join(dirname(draftPath), `${basename(draftPath, ext)}${postfix}${ext}`);
|
|
2424
2325
|
};
|
|
2425
2326
|
var priorContextPath = (draftPath) => sidecarPath(draftPath, ".prior");
|
|
2426
|
-
var priorAnswersPath = (draftPath) => sidecarPath(draftPath, ".prior-answers");
|
|
2427
2327
|
var priorSuppressedPath = (draftPath) => sidecarPath(draftPath, ".prior-suppressed");
|
|
2428
2328
|
var lastValidPath = (draftPath) => sidecarPath(draftPath, ".last-valid");
|
|
2429
2329
|
var costAxisDisengagedPath = (draftPath) => sidecarPath(draftPath, ".cost-axis-disengaged");
|
|
@@ -2502,9 +2402,9 @@ var parseWallMs = (raw) => {
|
|
|
2502
2402
|
};
|
|
2503
2403
|
var parseEpochSecMs = (raw) => {
|
|
2504
2404
|
if (raw === void 0) return null;
|
|
2505
|
-
const
|
|
2506
|
-
if (!/^\d+$/.test(
|
|
2507
|
-
const n = Number.parseInt(
|
|
2405
|
+
const t9 = raw.trim();
|
|
2406
|
+
if (!/^\d+$/.test(t9)) return null;
|
|
2407
|
+
const n = Number.parseInt(t9, 10);
|
|
2508
2408
|
return Number.isFinite(n) && n > 0 ? n * 1e3 : null;
|
|
2509
2409
|
};
|
|
2510
2410
|
var anchoredElapsedMs = (src) => {
|
|
@@ -2625,10 +2525,12 @@ var findingsTable = [
|
|
|
2625
2525
|
{
|
|
2626
2526
|
minor: "0.4",
|
|
2627
2527
|
defaultVersion: "0.4.0",
|
|
2628
|
-
// The frozen tolerant-in legacy schema, NOT the live
|
|
2528
|
+
// The frozen tolerant-in legacy schema, NOT the live file: every ajv-gated channel
|
|
2629
2529
|
// (validate --schema-version, the extraction ladder) dispatches the RAW doc's declared minor
|
|
2630
2530
|
// through schemaPathFor, so the ajv gate must accept exactly what the tolerant legacy codec
|
|
2631
2531
|
// accepts — the live file (id required) would reject the legacy docs the upcast promises to read.
|
|
2532
|
+
// One deliberate exception: the codec drops a draft-only `rebuttal` that the frozen file rejects.
|
|
2533
|
+
// A pre-0.10 document never carried one, so only a hybrid doc the rescue exists for meets it.
|
|
2632
2534
|
schemaFile: "v0.9/findings.schema.json",
|
|
2633
2535
|
codec: legacyFindingsCodec,
|
|
2634
2536
|
normalize: legacyFindingsNormalize,
|
|
@@ -2660,6 +2562,14 @@ var findingsTable = [
|
|
|
2660
2562
|
},
|
|
2661
2563
|
{
|
|
2662
2564
|
minor: "0.10",
|
|
2565
|
+
defaultVersion: "0.10.0",
|
|
2566
|
+
schemaFile: "findings.schema.json",
|
|
2567
|
+
codec: FindingsCodec,
|
|
2568
|
+
normalize: identity,
|
|
2569
|
+
latest: false
|
|
2570
|
+
},
|
|
2571
|
+
{
|
|
2572
|
+
minor: "0.11",
|
|
2663
2573
|
defaultVersion: DEFAULT_SCHEMA_VERSION,
|
|
2664
2574
|
schemaFile: "findings.schema.json",
|
|
2665
2575
|
codec: FindingsCodec,
|
|
@@ -2714,21 +2624,28 @@ var describeValidationError = (e) => {
|
|
|
2714
2624
|
var formatErrors = (errors) => errors.map(describeValidationError);
|
|
2715
2625
|
var declaredVersion = (raw) => typeof raw === "object" && raw !== null && "schema_version" in raw ? typeof raw.schema_version === "string" ? raw.schema_version : void 0 : void 0;
|
|
2716
2626
|
var supportedVersions = (kind) => tableFor(kind).map((entry) => entry.minor);
|
|
2717
|
-
var defaultVersion = (kind) => {
|
|
2718
|
-
const latest = tableFor(kind).find((entry) => entry.latest);
|
|
2719
|
-
if (!latest) throw new Error(`Registry invariant violated \u2014 no latest entry for "${kind}"`);
|
|
2720
|
-
return latest.defaultVersion;
|
|
2721
|
-
};
|
|
2722
2627
|
var bundledSchemaPath = (relativePath) => resolve(import.meta.dirname, "..", "schema", relativePath);
|
|
2723
|
-
var
|
|
2628
|
+
var entryFor = (kind, version) => {
|
|
2724
2629
|
const table = tableFor(kind);
|
|
2725
2630
|
const entry = version === void 0 ? table.find((v) => v.latest) : table.find((v) => v.minor === majorMinor(version));
|
|
2631
|
+
if (!entry && version === void 0) {
|
|
2632
|
+
throw new Error(`Registry invariant violated \u2014 no latest entry for "${kind}"`);
|
|
2633
|
+
}
|
|
2726
2634
|
if (!entry) {
|
|
2727
2635
|
throw new Error(
|
|
2728
2636
|
`Unsupported ${kind} schema version "${version ?? ""}" \u2014 supported: ${supportedVersions(kind).join(", ")}`
|
|
2729
2637
|
);
|
|
2730
2638
|
}
|
|
2731
|
-
return
|
|
2639
|
+
return entry;
|
|
2640
|
+
};
|
|
2641
|
+
var schemaPathFor = (kind, version) => bundledSchemaPath(entryFor(kind, version).schemaFile);
|
|
2642
|
+
var versionFor = (kind, version) => entryFor(kind, version).defaultVersion;
|
|
2643
|
+
var defaultVersion = (kind) => versionFor(kind);
|
|
2644
|
+
var livePinFor = (kind, version) => {
|
|
2645
|
+
const table = tableFor(kind);
|
|
2646
|
+
const latest = table.find((v) => v.latest);
|
|
2647
|
+
const entry = version === void 0 ? latest : table.find((v) => v.minor === majorMinor(version));
|
|
2648
|
+
return entry !== void 0 && entry.schemaFile === latest?.schemaFile ? entry.defaultVersion : void 0;
|
|
2732
2649
|
};
|
|
2733
2650
|
var resolveFindings = (raw) => {
|
|
2734
2651
|
const version = declaredVersion(raw);
|
|
@@ -2762,7 +2679,7 @@ var resolveTolerantFindings = (doc) => {
|
|
|
2762
2679
|
};
|
|
2763
2680
|
|
|
2764
2681
|
// src/pr.ts
|
|
2765
|
-
var CANDIDATE_JQ = ".[] | {number: .number, state: .state, headRef: .head.ref, headSha: .head.sha}";
|
|
2682
|
+
var CANDIDATE_JQ = ".[] | {number: .number, state: .state, headRef: .head.ref, headSha: .head.sha, headRepo: (.head.repo.full_name // null)}";
|
|
2766
2683
|
var parseCandidates = (stdout) => parseJsonl(stdout);
|
|
2767
2684
|
var fetchDirectCandidates = async (repo, headSha, ghApi) => {
|
|
2768
2685
|
try {
|
|
@@ -2791,7 +2708,7 @@ var resolvePr = (candidates, headBranch) => {
|
|
|
2791
2708
|
const scoped = candidates.length > 1 && headBranch ? candidates.filter((c) => c.headRef === headBranch) : candidates;
|
|
2792
2709
|
const chosen = scoped.find((c) => c.state === "open") ?? scoped[0] ?? candidates[0];
|
|
2793
2710
|
if (chosen === void 0) return { kind: "none" };
|
|
2794
|
-
return chosen.state === "open" ? { kind: "open", prNumber: chosen.number } : { kind: "not-open", prNumber: chosen.number, state: chosen.state };
|
|
2711
|
+
return chosen.state === "open" ? { kind: "open", prNumber: chosen.number, headRepo: chosen.headRepo ?? null } : { kind: "not-open", prNumber: chosen.number, state: chosen.state };
|
|
2795
2712
|
};
|
|
2796
2713
|
var fetchDiff = async (repo, prNumber, ghApi) => ghApi([
|
|
2797
2714
|
`repos/${repo}/pulls/${String(prNumber)}`,
|
|
@@ -2900,6 +2817,220 @@ var checkRun = async (input, ghApi = runGhApi) => {
|
|
|
2900
2817
|
];
|
|
2901
2818
|
await ghApi(endpoint, JSON.stringify(body));
|
|
2902
2819
|
};
|
|
2820
|
+
var RESPONSE_REASON_CLIP_CHARS = 300;
|
|
2821
|
+
var RESPONSES_PER_CHANNEL = 25;
|
|
2822
|
+
var ChannelCodec = t.keyof({ comment: null, commit: null });
|
|
2823
|
+
var MAINTAINER_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
|
|
2824
|
+
var CLOSING_DISPOSITIONS = /* @__PURE__ */ new Set(["refuted", "dismissed"]);
|
|
2825
|
+
var ResponseShape = t.type({
|
|
2826
|
+
id: t.string,
|
|
2827
|
+
disposition: ResponseDispositionCodec,
|
|
2828
|
+
reason: t.string,
|
|
2829
|
+
channel: ChannelCodec,
|
|
2830
|
+
source_url: t.string,
|
|
2831
|
+
author: t.union([t.string, t.null]),
|
|
2832
|
+
author_association: t.union([t.string, t.null]),
|
|
2833
|
+
created_at: t.union([t.string, t.null])
|
|
2834
|
+
});
|
|
2835
|
+
var ResponseCodec = strictExact("Response", ResponseShape, [ResponseShape]);
|
|
2836
|
+
var UnmatchedResponseShape = t.type({
|
|
2837
|
+
id: t.string,
|
|
2838
|
+
channel: ChannelCodec,
|
|
2839
|
+
source_url: t.string
|
|
2840
|
+
});
|
|
2841
|
+
var UnmatchedResponseCodec = strictExact("UnmatchedResponse", UnmatchedResponseShape, [
|
|
2842
|
+
UnmatchedResponseShape
|
|
2843
|
+
]);
|
|
2844
|
+
var ResponsesFileShape = t.type({
|
|
2845
|
+
responses: t.array(ResponseCodec),
|
|
2846
|
+
unmatched: t.array(UnmatchedResponseCodec)
|
|
2847
|
+
});
|
|
2848
|
+
strictExact("ResponsesFile", ResponsesFileShape, [
|
|
2849
|
+
ResponsesFileShape
|
|
2850
|
+
]);
|
|
2851
|
+
var RESPONSE_LINE_RE = /^ {0,3}review-response:\s*(\S+)\s+([A-Za-z]+)(?![A-Za-z]|\s*\|)[\s—–:-]*(.*)$/i;
|
|
2852
|
+
var FENCE_OPEN_RE = /^ {0,3}(`{3,}(?=[^`]*$)|~{3,})/;
|
|
2853
|
+
var LINE_BREAK_RE = /\r\n?|\n|\u2028|\u2029/;
|
|
2854
|
+
var unfencedLines = (text) => text.split(LINE_BREAK_RE).reduce(
|
|
2855
|
+
(state, line) => {
|
|
2856
|
+
const marker = FENCE_OPEN_RE.exec(line)?.[1];
|
|
2857
|
+
if (state.fence === null) {
|
|
2858
|
+
if (marker !== void 0) return { fence: marker, lines: state.lines };
|
|
2859
|
+
state.lines.push(line);
|
|
2860
|
+
return state;
|
|
2861
|
+
}
|
|
2862
|
+
const closes = marker !== void 0 && marker[0] === state.fence[0] && marker.length >= state.fence.length && line.trim() === marker;
|
|
2863
|
+
return closes ? { fence: null, lines: state.lines } : state;
|
|
2864
|
+
},
|
|
2865
|
+
{ fence: null, lines: [] }
|
|
2866
|
+
).lines;
|
|
2867
|
+
var WHOLE_LINE_CODE_SPAN_RE = /^ {0,3}`([^`]+)`\s*$/;
|
|
2868
|
+
var answerLines = (text) => unfencedLines(text).map((line) => WHOLE_LINE_CODE_SPAN_RE.exec(line)?.[1] ?? line);
|
|
2869
|
+
var unwrapId = (token) => token.replace(/^[`"']+|[`"'.,:;]+$/g, "");
|
|
2870
|
+
var lineAnswers = (lines) => lines.flatMap((line) => {
|
|
2871
|
+
const match = RESPONSE_LINE_RE.exec(line);
|
|
2872
|
+
const id = unwrapId(match?.[1] ?? "");
|
|
2873
|
+
const disposition = match?.[2]?.toLowerCase();
|
|
2874
|
+
return id !== "" && DispositionCodec.is(disposition) ? [
|
|
2875
|
+
{
|
|
2876
|
+
id,
|
|
2877
|
+
disposition,
|
|
2878
|
+
reason: clipText((match?.[3] ?? "").trim(), RESPONSE_REASON_CLIP_CHARS)
|
|
2879
|
+
}
|
|
2880
|
+
] : [];
|
|
2881
|
+
});
|
|
2882
|
+
var ID_IN_CELL_RE = /`([^`]+)`/g;
|
|
2883
|
+
var ID_HEADERS = /* @__PURE__ */ new Set(["id", "ids", "finding", "findings"]);
|
|
2884
|
+
var DISPOSITION_HEADERS = /* @__PURE__ */ new Set(["disposition", "verdict", "resolution"]);
|
|
2885
|
+
var CELL_RE = /((?:\\[\s\S]?|[^\\|])*)(\||$)/g;
|
|
2886
|
+
var splitCells = (row) => {
|
|
2887
|
+
const cells = [...row.matchAll(CELL_RE)];
|
|
2888
|
+
return cells.slice(0, cells.findIndex((match) => match[2] !== "|") + 1).map((match) => match[1] ?? "");
|
|
2889
|
+
};
|
|
2890
|
+
var BLOCK_START_RE = /^ {0,3}([-+*]\s|\d+[.)]\s|#{1,6}(\s|$)|>)/;
|
|
2891
|
+
var tableRow = (line) => {
|
|
2892
|
+
if (!/^ {0,3}\S/.test(line) || BLOCK_START_RE.test(line)) return null;
|
|
2893
|
+
const split = splitCells(line.trim());
|
|
2894
|
+
if (split.length < 2) return null;
|
|
2895
|
+
const inner = split.slice(line.trim().startsWith("|") ? 1 : 0);
|
|
2896
|
+
return (inner.length > 1 && inner[inner.length - 1]?.trim() === "" ? inner.slice(0, -1) : inner).map((cell) => cell.trim().replace(/\\\|/g, "|"));
|
|
2897
|
+
};
|
|
2898
|
+
var isDelimiter = (cells) => cells !== null && cells.every((cell) => /^:?-+:?$/.test(cell));
|
|
2899
|
+
var plainCell = (cell) => cell.replace(/[*_`]/g, "").trim().toLowerCase();
|
|
2900
|
+
var columnName = (cell) => plainCell(cell).split(/[\s/]+/)[0] ?? "";
|
|
2901
|
+
var idsInCell = (cell) => {
|
|
2902
|
+
const ids = cell.replace(/\([^()]*\)/g, "");
|
|
2903
|
+
const quoted = [...ids.matchAll(ID_IN_CELL_RE)].map((match) => unwrapId((match[1] ?? "").trim())).filter((id) => id !== "");
|
|
2904
|
+
const bare = ids.split(",").map((piece) => unwrapId(piece.trim())).filter((id) => id !== "");
|
|
2905
|
+
return quoted.length > 0 ? quoted : bare.every((id) => /^\S+$/.test(id)) ? bare : [];
|
|
2906
|
+
};
|
|
2907
|
+
var tableDisposition = (cell) => {
|
|
2908
|
+
const firstWord = plainCell(cell).replace(/^[^a-z]+/, "").split(/[^a-z]/)[0] ?? "";
|
|
2909
|
+
return DispositionCodec.is(firstWord) ? firstWord : "unstated";
|
|
2910
|
+
};
|
|
2911
|
+
var rowAnswers = (cells, columns) => {
|
|
2912
|
+
const dispositionCell = cells[columns.disposition] ?? "";
|
|
2913
|
+
const disposition = tableDisposition(dispositionCell);
|
|
2914
|
+
const verdictOnly = DispositionCodec.is(plainCell(dispositionCell));
|
|
2915
|
+
const reason = clipText(
|
|
2916
|
+
cells.filter(
|
|
2917
|
+
(cell, column) => cell !== "" && column !== columns.id && !(verdictOnly && column === columns.disposition)
|
|
2918
|
+
).join(" \u2014 "),
|
|
2919
|
+
RESPONSE_REASON_CLIP_CHARS
|
|
2920
|
+
);
|
|
2921
|
+
return idsInCell(cells[columns.id] ?? "").map((id) => ({ id, disposition, reason }));
|
|
2922
|
+
};
|
|
2923
|
+
var namesAColumn = (name) => ID_HEADERS.has(name) || DISPOSITION_HEADERS.has(name);
|
|
2924
|
+
var tableAnswers = (lines) => {
|
|
2925
|
+
const rows = lines.map(tableRow);
|
|
2926
|
+
return rows.reduce(
|
|
2927
|
+
(state, cells, index) => {
|
|
2928
|
+
if (state.skipDelimiter) return { ...state, skipDelimiter: false };
|
|
2929
|
+
if (cells === null) return { ...state, inTable: false, columns: null };
|
|
2930
|
+
const opensTable = !isDelimiter(cells) && isDelimiter(rows[index + 1] ?? null) && (!state.inTable || cells.map(columnName).some(namesAColumn));
|
|
2931
|
+
if (opensTable) {
|
|
2932
|
+
const header = cells.map(columnName);
|
|
2933
|
+
const id = header.findIndex((name) => ID_HEADERS.has(name));
|
|
2934
|
+
const disposition = header.findIndex((name) => DISPOSITION_HEADERS.has(name));
|
|
2935
|
+
return {
|
|
2936
|
+
...state,
|
|
2937
|
+
inTable: true,
|
|
2938
|
+
skipDelimiter: true,
|
|
2939
|
+
columns: id < 0 || disposition < 0 ? null : { id, disposition }
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
if (state.inTable && state.columns !== null && !isDelimiter(cells)) {
|
|
2943
|
+
state.answers.push(...rowAnswers(cells, state.columns));
|
|
2944
|
+
}
|
|
2945
|
+
return isDelimiter(rows[index + 1] ?? null) ? { ...state, skipDelimiter: true } : state;
|
|
2946
|
+
},
|
|
2947
|
+
{ columns: null, inTable: false, skipDelimiter: false, answers: [] }
|
|
2948
|
+
).answers;
|
|
2949
|
+
};
|
|
2950
|
+
var parseResponses = (text) => {
|
|
2951
|
+
const lines = answerLines(text);
|
|
2952
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2953
|
+
return [...lineAnswers(lines), ...tableAnswers(lines)].filter((answer) => {
|
|
2954
|
+
const key2 = JSON.stringify([answer.id, answer.disposition, answer.reason]);
|
|
2955
|
+
if (seen.has(key2)) return false;
|
|
2956
|
+
seen.add(key2);
|
|
2957
|
+
return true;
|
|
2958
|
+
});
|
|
2959
|
+
};
|
|
2960
|
+
var isHuman = (login, type8, botLogin) => login !== botLogin && type8 === "User";
|
|
2961
|
+
var newestFirst = (a, b) => {
|
|
2962
|
+
const [left2, right2] = [a.created_at ?? "", b.created_at ?? ""];
|
|
2963
|
+
return left2 < right2 ? 1 : left2 > right2 ? -1 : b.id - a.id;
|
|
2964
|
+
};
|
|
2965
|
+
var answersFrom = (input) => {
|
|
2966
|
+
const comments = [...input.comments].filter((comment) => isHuman(comment.user.login, comment.user.type ?? null, input.botLogin)).sort(newestFirst).flatMap(
|
|
2967
|
+
(comment) => parseResponses(comment.body ?? "").map((line) => ({
|
|
2968
|
+
...line,
|
|
2969
|
+
channel: "comment",
|
|
2970
|
+
source_url: `https://github.com/${input.repo}/pull/${String(input.prNumber)}#issuecomment-${String(comment.id)}`,
|
|
2971
|
+
author: comment.user.login,
|
|
2972
|
+
author_association: comment.author_association ?? null,
|
|
2973
|
+
created_at: comment.created_at ?? null
|
|
2974
|
+
}))
|
|
2975
|
+
);
|
|
2976
|
+
const commits = [...input.commits].reverse().flatMap(
|
|
2977
|
+
(commit) => parseResponses(commit.message).map((line) => ({
|
|
2978
|
+
...line,
|
|
2979
|
+
channel: "commit",
|
|
2980
|
+
source_url: `https://github.com/${input.repo}/commit/${commit.sha}`,
|
|
2981
|
+
author: commit.author,
|
|
2982
|
+
author_association: null,
|
|
2983
|
+
created_at: commit.date ?? null
|
|
2984
|
+
}))
|
|
2985
|
+
);
|
|
2986
|
+
return { comments, commits };
|
|
2987
|
+
};
|
|
2988
|
+
var harvestResponses = (input) => {
|
|
2989
|
+
const { comments: fromComments, commits: fromCommits } = answersFrom(input);
|
|
2990
|
+
const matched = (response) => input.priorIds.has(response.id);
|
|
2991
|
+
const echoed = (response) => !matched(response) && ID_SHAPE_RE.test(response.id);
|
|
2992
|
+
const groups = [fromComments, fromCommits].flatMap((channel) => [
|
|
2993
|
+
channel.filter(matched),
|
|
2994
|
+
channel.filter(echoed)
|
|
2995
|
+
]);
|
|
2996
|
+
const [comments, commentEchoes, commits, commitEchoes] = groups.map(
|
|
2997
|
+
(group) => group.slice(0, RESPONSES_PER_CHANNEL)
|
|
2998
|
+
);
|
|
2999
|
+
return {
|
|
3000
|
+
file: {
|
|
3001
|
+
responses: [...comments ?? [], ...commits ?? []],
|
|
3002
|
+
unmatched: [...commentEchoes ?? [], ...commitEchoes ?? []].map(
|
|
3003
|
+
({ id, channel, source_url }) => ({ id, channel, source_url })
|
|
3004
|
+
)
|
|
3005
|
+
},
|
|
3006
|
+
dropped: groups.reduce(
|
|
3007
|
+
(total, group) => total + Math.max(0, group.length - RESPONSES_PER_CHANNEL),
|
|
3008
|
+
0
|
|
3009
|
+
)
|
|
3010
|
+
};
|
|
3011
|
+
};
|
|
3012
|
+
var isTrustedResponse = (response, headInBaseRepo) => {
|
|
3013
|
+
switch (response.channel) {
|
|
3014
|
+
case "comment":
|
|
3015
|
+
return MAINTAINER_ASSOCIATIONS.includes(response.author_association ?? "");
|
|
3016
|
+
case "commit":
|
|
3017
|
+
return headInBaseRepo;
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
var answeredInstant = (response) => response.created_at === null ? Number.NaN : Date.parse(response.created_at);
|
|
3021
|
+
var closingResponses = (responses, isTrusted) => {
|
|
3022
|
+
const byId = responses.filter((response) => response.disposition !== "unstated" && isTrusted(response)).reduce((groups, response) => {
|
|
3023
|
+
const timed = { response, at: answeredInstant(response) };
|
|
3024
|
+
groups.set(response.id, [...groups.get(response.id) ?? [], timed]);
|
|
3025
|
+
return groups;
|
|
3026
|
+
}, /* @__PURE__ */ new Map());
|
|
3027
|
+
return [...byId.values()].flatMap((answers) => {
|
|
3028
|
+
if (answers.some(({ at }) => Number.isNaN(at))) return [];
|
|
3029
|
+
const latest = Math.max(...answers.map(({ at }) => at));
|
|
3030
|
+
const newest = answers.filter(({ at }) => at === latest).map(({ response }) => response);
|
|
3031
|
+
return newest.every((response) => CLOSING_DISPOSITIONS.has(response.disposition)) ? [...newest].sort((a, b) => a.source_url < b.source_url ? -1 : 1).slice(0, 1) : [];
|
|
3032
|
+
});
|
|
3033
|
+
};
|
|
2903
3034
|
|
|
2904
3035
|
// src/post.ts
|
|
2905
3036
|
var STICKY_CHAR_LIMIT = 65536;
|
|
@@ -3095,7 +3226,8 @@ var postInlineReview = async (pr, comments, inDiff, ghApi) => {
|
|
|
3095
3226
|
return { url, posted, inlinePosted, unposted };
|
|
3096
3227
|
}
|
|
3097
3228
|
};
|
|
3098
|
-
var ISSUE_COMMENTS_JQ = '.[] | {id, user: (.user.login // "(deleted)"), created_at, html_url, body: (.body // "")}';
|
|
3229
|
+
var ISSUE_COMMENTS_JQ = '.[] | {id, user: (.user.login // "(deleted)"), user_type: (.user.type // null), author_association: (.author_association // null), created_at, html_url, body: (.body // "")}';
|
|
3230
|
+
var nullableString = (value) => typeof value === "string" ? value : null;
|
|
3099
3231
|
var parseIssueCommentRows = (raw) => {
|
|
3100
3232
|
let malformed = 0;
|
|
3101
3233
|
const rows = [];
|
|
@@ -3114,7 +3246,15 @@ var parseIssueCommentRows = (raw) => {
|
|
|
3114
3246
|
const url = rec?.["html_url"];
|
|
3115
3247
|
const body = rec?.["body"];
|
|
3116
3248
|
if (typeof id === "number" && typeof author === "string" && typeof created === "string" && typeof url === "string" && typeof body === "string") {
|
|
3117
|
-
rows.push({
|
|
3249
|
+
rows.push({
|
|
3250
|
+
id,
|
|
3251
|
+
author,
|
|
3252
|
+
created,
|
|
3253
|
+
url,
|
|
3254
|
+
body,
|
|
3255
|
+
authorType: nullableString(rec?.["user_type"]),
|
|
3256
|
+
association: nullableString(rec?.["author_association"])
|
|
3257
|
+
});
|
|
3118
3258
|
} else {
|
|
3119
3259
|
malformed += 1;
|
|
3120
3260
|
}
|
|
@@ -3124,6 +3264,45 @@ var parseIssueCommentRows = (raw) => {
|
|
|
3124
3264
|
);
|
|
3125
3265
|
return { rows, malformed };
|
|
3126
3266
|
};
|
|
3267
|
+
var COMMITS_JQ = ".[] | {sha, message: .commit.message, author: (.author.login // null), date: (.commit.author.date // null)}";
|
|
3268
|
+
var fetchCommitAnswersSource = async (repo, prNumber, ghApi) => {
|
|
3269
|
+
try {
|
|
3270
|
+
const raw = await ghApi([
|
|
3271
|
+
`repos/${repo}/pulls/${String(prNumber)}/commits?per_page=100`,
|
|
3272
|
+
"--paginate",
|
|
3273
|
+
"--jq",
|
|
3274
|
+
COMMITS_JQ
|
|
3275
|
+
]);
|
|
3276
|
+
const lines = raw.split("\n").filter((line) => line.trim() !== "");
|
|
3277
|
+
const commits = lines.flatMap((line) => {
|
|
3278
|
+
const parsed = tryParseJson(line.trim());
|
|
3279
|
+
const rec = parsed.ok ? asRecord(parsed.value) : null;
|
|
3280
|
+
const sha = rec?.["sha"];
|
|
3281
|
+
const message = rec?.["message"];
|
|
3282
|
+
return typeof sha === "string" && typeof message === "string" ? [
|
|
3283
|
+
{
|
|
3284
|
+
sha,
|
|
3285
|
+
message,
|
|
3286
|
+
author: nullableString(rec?.["author"]),
|
|
3287
|
+
date: nullableString(rec?.["date"])
|
|
3288
|
+
}
|
|
3289
|
+
] : [];
|
|
3290
|
+
});
|
|
3291
|
+
if (commits.length < lines.length) {
|
|
3292
|
+
process.stderr.write(
|
|
3293
|
+
`Warning: ${String(lines.length - commits.length)} of the PR's commits did not decode \u2014 their answers close nothing
|
|
3294
|
+
`
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
return commits;
|
|
3298
|
+
} catch (err) {
|
|
3299
|
+
process.stderr.write(
|
|
3300
|
+
`Warning: could not read the PR's commits (${errMsg(err)}) \u2014 no commit answer closes a finding this round
|
|
3301
|
+
`
|
|
3302
|
+
);
|
|
3303
|
+
return [];
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3127
3306
|
var fetchIssueCommentRows = async (repo, prNumber, ghApi) => {
|
|
3128
3307
|
const raw = await ghApi([
|
|
3129
3308
|
// per_page rides the QUERY string — a `-f` field would flip `gh api` from GET to POST
|
|
@@ -3194,7 +3373,6 @@ var discussionRows = (rows, stickyId, botLogin) => {
|
|
|
3194
3373
|
return c.id !== stickyId && c.author !== botLogin;
|
|
3195
3374
|
});
|
|
3196
3375
|
};
|
|
3197
|
-
var ID_SHAPE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
3198
3376
|
var mentionsOutsideKnown = (reachable, currentIds) => {
|
|
3199
3377
|
const known = new Set(currentIds);
|
|
3200
3378
|
const escapedToRaw = escapedIdIndex(currentIds);
|
|
@@ -3279,24 +3457,6 @@ var buildStickyDiscussion = (reachable, currentIds, priorIds) => {
|
|
|
3279
3457
|
orphanedTruncated: Object.fromEntries(orphanedTruncated)
|
|
3280
3458
|
};
|
|
3281
3459
|
};
|
|
3282
|
-
var priorIdsFrom = (doc) => {
|
|
3283
|
-
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return [];
|
|
3284
|
-
const rec = doc;
|
|
3285
|
-
const idsOf = (field, systemic) => (Array.isArray(rec[field]) ? rec[field] : []).flatMap((raw) => {
|
|
3286
|
-
const item = typeof raw === "object" && raw !== null ? asRecord(raw) : null;
|
|
3287
|
-
if (item === null) return [];
|
|
3288
|
-
const title = typeof item["title"] === "string" ? item["title"] : "";
|
|
3289
|
-
const resolved = resolveRuleId({
|
|
3290
|
-
...typeof item["id"] === "string" ? { id: item["id"] } : {},
|
|
3291
|
-
...typeof item["code"] === "string" ? { code: item["code"] } : {},
|
|
3292
|
-
...typeof item["path"] === "string" ? { path: item["path"] } : {},
|
|
3293
|
-
title
|
|
3294
|
-
});
|
|
3295
|
-
const id = resolved ?? (systemic && title !== "" ? synthesizedSystemicId(title) : void 0);
|
|
3296
|
-
return id !== void 0 ? [id] : [];
|
|
3297
|
-
});
|
|
3298
|
-
return [...idsOf("findings", false), ...idsOf("systemic_problems", true)];
|
|
3299
|
-
};
|
|
3300
3460
|
var appendBestEffort = (path, label, body) => {
|
|
3301
3461
|
if (path === void 0 || path === "") return;
|
|
3302
3462
|
try {
|
|
@@ -3391,8 +3551,8 @@ var priorBotCommentIds = (raw, botLogin) => {
|
|
|
3391
3551
|
const nodes = conn?.nodes;
|
|
3392
3552
|
if (!Array.isArray(nodes)) return { ids: [], truncated };
|
|
3393
3553
|
const logins = [botLogin.replace(/\[bot\]$/, ""), botLogin];
|
|
3394
|
-
const ids = nodes.flatMap((
|
|
3395
|
-
const cnodes =
|
|
3554
|
+
const ids = nodes.flatMap((t9) => {
|
|
3555
|
+
const cnodes = t9.comments?.nodes;
|
|
3396
3556
|
return Array.isArray(cnodes) ? cnodes.map((c) => priorBotCommentId(c, logins)).filter((id) => id !== null) : [];
|
|
3397
3557
|
});
|
|
3398
3558
|
return { ids, truncated };
|
|
@@ -3512,7 +3672,7 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
|
|
|
3512
3672
|
const logAnsweredDrops = () => {
|
|
3513
3673
|
if (verbatimReRaised.length > 0) {
|
|
3514
3674
|
process.stderr.write(
|
|
3515
|
-
`${String(droppedCount)} verbatim re-raise(s) of answered findings were treated as answered \u2014 the preserved sticky shows each finding
|
|
3675
|
+
`${String(droppedCount)} verbatim re-raise(s) of answered findings were treated as answered \u2014 the preserved sticky shows each finding
|
|
3516
3676
|
`
|
|
3517
3677
|
);
|
|
3518
3678
|
}
|
|
@@ -3637,8 +3797,40 @@ ${dropNote}` : ""}`,
|
|
|
3637
3797
|
process.exit(0);
|
|
3638
3798
|
}
|
|
3639
3799
|
const loadedFindings = findingsResult.findings;
|
|
3640
|
-
const
|
|
3641
|
-
const
|
|
3800
|
+
const answerable = existingSticky !== null && !priorIsMechanic && loadedFindings.findings.length > 0;
|
|
3801
|
+
const headInBaseRepo = answerable && resolution.headRepo !== null && resolution.headRepo.toLowerCase() === input.repo.toLowerCase();
|
|
3802
|
+
const answers = answerable ? answersFrom({
|
|
3803
|
+
repo: input.repo,
|
|
3804
|
+
prNumber,
|
|
3805
|
+
botLogin: input.botLogin,
|
|
3806
|
+
comments: commentRows.map((row) => ({
|
|
3807
|
+
id: row.id,
|
|
3808
|
+
body: row.body,
|
|
3809
|
+
user: { login: row.author, type: row.authorType },
|
|
3810
|
+
created_at: row.created,
|
|
3811
|
+
author_association: row.association
|
|
3812
|
+
})),
|
|
3813
|
+
commits: headInBaseRepo ? await fetchCommitAnswersSource(input.repo, prNumber, ghApi) : []
|
|
3814
|
+
}) : { comments: [], commits: [] };
|
|
3815
|
+
const closures = closingResponses(
|
|
3816
|
+
[...answers.comments, ...answers.commits],
|
|
3817
|
+
(response) => isTrustedResponse(response, headInBaseRepo)
|
|
3818
|
+
).filter((closure) => couldMatch(closure.id, loadedFindings.findings));
|
|
3819
|
+
const priorForAnswers = closures.length > 0 && existingSticky !== null ? { value: await resolvePriorFindings(existingSticky.body, readArtifact) } : null;
|
|
3820
|
+
const answeredPrior = priorForAnswers === null ? null : resolveTolerantFindings(priorForAnswers.value);
|
|
3821
|
+
if (priorForAnswers !== null && answeredPrior === null) {
|
|
3822
|
+
process.stderr.write(
|
|
3823
|
+
`Warning: ${String(closures.length)} maintainer answer(s) close prior findings, but the prior review's findings did not resolve \u2014 no re-raise is treated as answered
|
|
3824
|
+
`
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
const answeredRegistry = answeredRegistryFrom(closures, answeredPrior);
|
|
3828
|
+
if (answeredPrior !== null && answeredRegistry.length < closures.length) {
|
|
3829
|
+
process.stderr.write(
|
|
3830
|
+
`Warning: ${String(closures.length - answeredRegistry.length)} maintainer answer(s) name no finding the prior review reported \u2014 they close nothing this round
|
|
3831
|
+
`
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3642
3834
|
const answeredFilter = applyAnswered(loadedFindings.findings, answeredRegistry);
|
|
3643
3835
|
const reRaisedNotes = answeredFilter.reRaisedNotes;
|
|
3644
3836
|
const verbatimReRaised = answeredFilter.verbatimReRaised;
|
|
@@ -3666,7 +3858,7 @@ ${dropNote}` : ""}`,
|
|
|
3666
3858
|
const roundHasNit = findings.findings.some((f) => f.severity === "nit");
|
|
3667
3859
|
const nitWantsPrior = roundHasNit && existingSticky !== null && (isFullReviewSticky(existingSticky.body) || isFullReviewAncestry(existingSticky.body));
|
|
3668
3860
|
const broadWantsPrior = existingSticky !== null && !priorIsMechanic && mentionsOutsideKnown(reachable, broadCurrentIds);
|
|
3669
|
-
const resolvedPrior = existingSticky !== null && (nitWantsPrior || broadWantsPrior) ? await resolvePriorFindings(existingSticky.body, readArtifact) : null;
|
|
3861
|
+
const resolvedPrior = priorForAnswers !== null ? priorForAnswers.value : existingSticky !== null && (nitWantsPrior || broadWantsPrior) ? await resolvePriorFindings(existingSticky.body, readArtifact) : null;
|
|
3670
3862
|
const priorDocForNits = nitWantsPrior ? resolvedPrior : null;
|
|
3671
3863
|
const priorSuppressedKeys = new Set(
|
|
3672
3864
|
priorBelowFloorNits(priorDocForNits, input.nitVisibilityFloor).map(
|
|
@@ -4523,7 +4715,10 @@ var IssueCommentCodec = t.intersection([
|
|
|
4523
4715
|
t.type({
|
|
4524
4716
|
id: t.number,
|
|
4525
4717
|
body: t.union([t.string, t.null]),
|
|
4526
|
-
user: t.
|
|
4718
|
+
user: t.intersection([
|
|
4719
|
+
t.type({ login: t.string }),
|
|
4720
|
+
t.partial({ type: t.union([t.string, t.null]) })
|
|
4721
|
+
])
|
|
4527
4722
|
}),
|
|
4528
4723
|
t.partial({
|
|
4529
4724
|
created_at: t.union([t.string, t.null]),
|
|
@@ -4577,13 +4772,16 @@ var fetchFullDiff = async (repo, defaultBranch, headSha, ghApi, gitRun) => {
|
|
|
4577
4772
|
const base = (await gitRun(["rev-parse", "HEAD"])).trim();
|
|
4578
4773
|
return gitRun(["diff", `${base}...${headSha}`]);
|
|
4579
4774
|
};
|
|
4580
|
-
var CommitCodec = t.
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4775
|
+
var CommitCodec = t.intersection([
|
|
4776
|
+
t.type({
|
|
4777
|
+
sha: t.string,
|
|
4778
|
+
message: t.string,
|
|
4779
|
+
author: t.union([t.string, t.null]),
|
|
4780
|
+
email: t.union([t.string, t.null])
|
|
4781
|
+
}),
|
|
4782
|
+
t.partial({ date: t.union([t.string, t.null]), login: t.union([t.string, t.null]) })
|
|
4783
|
+
]);
|
|
4784
|
+
var COMMIT_JQ = ".commits[] | {sha: .sha, message: .commit.message, author: .commit.author.name, email: .commit.author.email, date: .commit.author.date, login: (.author.login // null)}";
|
|
4587
4785
|
var fetchCompareCommits = async (repo, defaultBranch, headSha, ghApi) => {
|
|
4588
4786
|
const rows = parseJsonl(
|
|
4589
4787
|
await ghApi([
|
|
@@ -4604,8 +4802,8 @@ var fetchCompareCommits = async (repo, defaultBranch, headSha, ghApi) => {
|
|
|
4604
4802
|
}
|
|
4605
4803
|
return commits;
|
|
4606
4804
|
};
|
|
4607
|
-
var COMMENT_JQ = ".[] | {id: .id, body: .body, user: {login: .user.login}, created_at: .created_at, author_association: .author_association}";
|
|
4608
|
-
var REVIEW_COMMENT_JQ =
|
|
4805
|
+
var COMMENT_JQ = ".[] | {id: .id, body: .body, user: {login: .user.login, type: .user.type}, created_at: .created_at, author_association: .author_association}";
|
|
4806
|
+
var REVIEW_COMMENT_JQ = ".[] | {body: .body, user: {login: .user.login}, created_at: .created_at, author_association: .author_association, path: .path, line: .line}";
|
|
4609
4807
|
var REVIEW_JQ = ".[] | {body: .body, user: {login: .user.login}, submitted_at: .submitted_at, author_association: .author_association, state: .state}";
|
|
4610
4808
|
var ReviewCommentCodec = t.intersection([
|
|
4611
4809
|
t.type({ body: t.union([t.string, t.null]), user: t.type({ login: t.string }) }),
|
|
@@ -4788,7 +4986,6 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
4788
4986
|
]);
|
|
4789
4987
|
const issueComments = decodeArrayOrNull(IssueCommentCodec, issueRows);
|
|
4790
4988
|
const reviewComments = decodeArrayOrNull(ReviewCommentCodec, reviewCommentRows);
|
|
4791
|
-
const threadComments = decodeArrayOrNull(ThreadCommentCodec, reviewCommentRows);
|
|
4792
4989
|
const reviews = decodeArrayOrNull(ReviewCodec, reviewRows);
|
|
4793
4990
|
const prior = issueComments === null ? null : priorReviewFrom(issueComments, input.botLogin);
|
|
4794
4991
|
writeFileSync(
|
|
@@ -4802,11 +4999,26 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
4802
4999
|
join(input.outDir, "prior_findings.json"),
|
|
4803
5000
|
priorFindings === null ? "null" : JSON.stringify(priorFindings)
|
|
4804
5001
|
);
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
5002
|
+
const harvest = seedsFromPrior ? harvestResponses({
|
|
5003
|
+
repo: input.repo,
|
|
5004
|
+
prNumber,
|
|
5005
|
+
botLogin: input.botLogin,
|
|
5006
|
+
priorIds: new Set(priorIdsFrom(priorFindings)),
|
|
5007
|
+
commits: commits.map((commit) => ({ ...commit, author: commit.login ?? null })),
|
|
5008
|
+
comments: issueComments ?? []
|
|
5009
|
+
}) : { file: { responses: [], unmatched: [] }, dropped: 0 };
|
|
5010
|
+
writeFileSync(join(input.outDir, "responses.json"), JSON.stringify(harvest.file));
|
|
5011
|
+
if (seedsFromPrior && priorFindings === null) {
|
|
5012
|
+
process.stderr.write(
|
|
5013
|
+
"Note: the prior review's findings did not resolve \u2014 every id-shaped answer is staged as unmatched\n"
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
5016
|
+
if (harvest.dropped > 0) {
|
|
5017
|
+
process.stderr.write(
|
|
5018
|
+
`Note: ${String(harvest.dropped)} answer(s) beyond the newest ${String(RESPONSES_PER_CHANNEL)} per channel were not staged
|
|
5019
|
+
`
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
4810
5022
|
writeFileSync(
|
|
4811
5023
|
join(input.outDir, "pr_conversation.json"),
|
|
4812
5024
|
JSON.stringify({
|
|
@@ -5170,7 +5382,7 @@ var parseScope = (raw) => {
|
|
|
5170
5382
|
reason: 'scope contains a character (newline, carriage return, backtick, "<", ">", or "|") that would corrupt the review prompt \u2014 use plain language names/tags'
|
|
5171
5383
|
};
|
|
5172
5384
|
}
|
|
5173
|
-
const languages = Array.from(new Set(trimmed.split(SCOPE_SEPARATOR_RE).filter((
|
|
5385
|
+
const languages = Array.from(new Set(trimmed.split(SCOPE_SEPARATOR_RE).filter((t9) => t9 !== "")));
|
|
5174
5386
|
return languages.length === 0 ? { kind: "absent" } : { kind: "ok", languages };
|
|
5175
5387
|
};
|
|
5176
5388
|
|
|
@@ -5798,18 +6010,19 @@ var deadlineCmd = defineCommand({
|
|
|
5798
6010
|
}
|
|
5799
6011
|
});
|
|
5800
6012
|
var derivedSchemaVersion = (kind, raw) => kind === "findings" ? declaredVersion(raw) : void 0;
|
|
6013
|
+
var liveSchemaPin = (kind, schemaPath, requestedVersion) => kind === "findings" && schemaPath === schemaPathFor(kind) ? livePinFor(kind, requestedVersion) : void 0;
|
|
5801
6014
|
var printableSchema = (schemaPath, pinVersion) => {
|
|
5802
6015
|
const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
|
|
5803
6016
|
const enforcementSchema = Object.fromEntries(
|
|
5804
6017
|
Object.entries(schema).filter(([key2]) => key2 !== "$schema")
|
|
5805
6018
|
);
|
|
5806
|
-
if (
|
|
6019
|
+
if (pinVersion === void 0) return JSON.stringify(enforcementSchema, null, 2);
|
|
5807
6020
|
const properties = enforcementSchema["properties"];
|
|
5808
6021
|
const schemaVersion = typeof properties === "object" && properties !== null ? properties["schema_version"] : void 0;
|
|
5809
6022
|
if (typeof schemaVersion === "object" && schemaVersion !== null) {
|
|
5810
6023
|
properties["schema_version"] = {
|
|
5811
6024
|
...schemaVersion,
|
|
5812
|
-
pattern: anchoredSchemaVersionPattern(
|
|
6025
|
+
pattern: anchoredSchemaVersionPattern(pinVersion)
|
|
5813
6026
|
};
|
|
5814
6027
|
}
|
|
5815
6028
|
return JSON.stringify(enforcementSchema, null, 2);
|
|
@@ -5845,7 +6058,8 @@ var validateCmd = defineCommand({
|
|
|
5845
6058
|
run: async ({ args }) => {
|
|
5846
6059
|
const kind = requireSchemaKind(args.kind || "findings");
|
|
5847
6060
|
const documentRaw = readJSON(args.document);
|
|
5848
|
-
const
|
|
6061
|
+
const requestedVersion = args["schema-version"] || derivedSchemaVersion(kind, documentRaw);
|
|
6062
|
+
const schemaPath = args.schema ? resolve(args.schema) : requireSchemaPath(kind, requestedVersion);
|
|
5849
6063
|
const { valid, errors } = validateAgainstSchema(documentRaw, schemaPath);
|
|
5850
6064
|
if (valid) {
|
|
5851
6065
|
process.stdout.write("\u2705 valid\n");
|
|
@@ -5857,7 +6071,7 @@ var validateCmd = defineCommand({
|
|
|
5857
6071
|
process.stderr.write(
|
|
5858
6072
|
`
|
|
5859
6073
|
The ${kind} document must conform to this schema (the field descriptions are the authoritative spec \u2014 match the property names exactly):
|
|
5860
|
-
${printableSchema(schemaPath, kind
|
|
6074
|
+
${printableSchema(schemaPath, liveSchemaPin(kind, schemaPath, requestedVersion))}
|
|
5861
6075
|
`
|
|
5862
6076
|
);
|
|
5863
6077
|
}
|
|
@@ -5881,10 +6095,6 @@ var seedDraftCmd = defineCommand({
|
|
|
5881
6095
|
type: "string",
|
|
5882
6096
|
description: "Path to the gather-staged prior findings document (prior_findings.json). gather resolves it \u2014 from the sticky's embedded blob, or by fetching the artifact the marker names \u2014 because gather holds the repo token and this step deliberately does not: it runs the jailed agent over untrusted PR code. Absent or null falls back to decoding an embedded blob out of --prior, which needs no token"
|
|
5883
6097
|
},
|
|
5884
|
-
"prior-answers": {
|
|
5885
|
-
type: "string",
|
|
5886
|
-
description: "Path to the gather-staged answered-findings registry (answered.json) \u2014 the prior inline findings whose threads a human reply answered (issue #151). Delivered out-of-band to the .prior-answers sidecar beside the prior context so the next-round agent sees the already-answered state; best-effort, never fails the seed"
|
|
5887
|
-
},
|
|
5888
6098
|
"nit-visibility-floor": {
|
|
5889
6099
|
type: "string",
|
|
5890
6100
|
description: "The nit visibility floor (issue #164), matched to the commenter's: the prior review's below-floor nits (confidence \xD7 likelihood below it) are re-derived from --prior and delivered to the .prior-suppressed sidecar as adjudicated context, so the next-round agent does not re-raise them as fresh nits; best-effort, never fails the seed. Empty \u21D2 the default"
|
|
@@ -5960,55 +6170,17 @@ var seedDraftCmd = defineCommand({
|
|
|
5960
6170
|
const strippedPrior = parsedPrior === null ? null : stripSurfaceFields(
|
|
5961
6171
|
isSurfaceStampedDoc(parsedPrior) ? parsedPrior : withoutScopeMetastasis(parsedPrior)
|
|
5962
6172
|
);
|
|
5963
|
-
const answeredRegistry = (() => {
|
|
5964
|
-
if (!args["prior-answers"]) return null;
|
|
5965
|
-
try {
|
|
5966
|
-
const raw = JSON.parse(readFileSync(resolve(args["prior-answers"]), "utf-8"));
|
|
5967
|
-
if (!Array.isArray(raw)) throw new Error("expected an array");
|
|
5968
|
-
const decoded = raw.flatMap((row) => {
|
|
5969
|
-
const entry = decodeAnsweredEntry(row);
|
|
5970
|
-
return entry === null ? [] : [entry];
|
|
5971
|
-
});
|
|
5972
|
-
if (decoded.length < raw.length) {
|
|
5973
|
-
process.stderr.write(
|
|
5974
|
-
`Warning: ${String(raw.length - decoded.length)} of ${String(raw.length)} answered-registry row(s) failed to decode \u2014 the seed's answered state is incomplete
|
|
5975
|
-
`
|
|
5976
|
-
);
|
|
5977
|
-
}
|
|
5978
|
-
return decoded;
|
|
5979
|
-
} catch (err) {
|
|
5980
|
-
process.stderr.write(
|
|
5981
|
-
`Warning: could not read the answered-findings registry ${args["prior-answers"]} (${errMsg(err)}) \u2014 no prior-answers sidecar
|
|
5982
|
-
`
|
|
5983
|
-
);
|
|
5984
|
-
return null;
|
|
5985
|
-
}
|
|
5986
|
-
})();
|
|
5987
6173
|
const priorFindings = (() => {
|
|
5988
6174
|
if (strippedPrior === null || typeof strippedPrior !== "object" || Array.isArray(strippedPrior))
|
|
5989
6175
|
return strippedPrior;
|
|
5990
6176
|
const resolved = resolvedPriorValue(strippedPrior);
|
|
5991
|
-
const doc = resolved === null ? strippedPrior :
|
|
5992
|
-
...resolved,
|
|
5993
|
-
findings: answeredRegistry !== null && answeredRegistry.length > 0 ? resolved.findings.filter((f) => {
|
|
5994
|
-
const resolvedId = resolveFindingId(f);
|
|
5995
|
-
return !answeredRegistry.some((e) => isAnsweredDrop(resolvedId, f, e));
|
|
5996
|
-
}) : resolved.findings
|
|
5997
|
-
};
|
|
6177
|
+
const doc = resolved === null ? strippedPrior : resolved;
|
|
5998
6178
|
const carried = doc["scope_metastasis"];
|
|
5999
6179
|
if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return doc;
|
|
6000
6180
|
if (doc["verdict"] === "error") return doc;
|
|
6001
6181
|
const computed = computeScopeMetastasis(priorTrajectory(parsedPrior, priorBody ?? ""));
|
|
6002
6182
|
return computed === null ? doc : { ...doc, scope_metastasis: computed };
|
|
6003
6183
|
})();
|
|
6004
|
-
if (answeredRegistry !== null) {
|
|
6005
|
-
writeFileSync(priorAnswersPath(outPath), `${JSON.stringify(answeredRegistry, null, 2)}
|
|
6006
|
-
`);
|
|
6007
|
-
process.stderr.write(
|
|
6008
|
-
`Seeded ${priorAnswersPath(outPath)} with ${String(answeredRegistry.length)} answered finding(s) as context
|
|
6009
|
-
`
|
|
6010
|
-
);
|
|
6011
|
-
}
|
|
6012
6184
|
if (parsedPrior !== null && isFullReviewAncestry(priorBody ?? "")) {
|
|
6013
6185
|
try {
|
|
6014
6186
|
const belowFloor = priorBelowFloorNits(
|
|
@@ -6040,7 +6212,9 @@ var seedDraftCmd = defineCommand({
|
|
|
6040
6212
|
) : priorFindings;
|
|
6041
6213
|
const accepts = (doc) => {
|
|
6042
6214
|
const resolved = resolvedPriorValue(doc);
|
|
6043
|
-
|
|
6215
|
+
if (resolved === null) return null;
|
|
6216
|
+
const carried = withoutRebuttals(resolved);
|
|
6217
|
+
return validateAgainstSchema(carried, schemaPath).valid ? carried : null;
|
|
6044
6218
|
};
|
|
6045
6219
|
const seedDoc = accepts(priorFindings) ?? (() => {
|
|
6046
6220
|
const bare = accepts(barePrior);
|
|
@@ -6328,7 +6502,7 @@ var printSchemaCmd = defineCommand({
|
|
|
6328
6502
|
const schemaKind = requireSchemaKind(args.name);
|
|
6329
6503
|
const schemaPath = requireSchemaPath(schemaKind, args["schema-version"]);
|
|
6330
6504
|
process.stdout.write(
|
|
6331
|
-
`${printableSchema(schemaPath, schemaKind
|
|
6505
|
+
`${printableSchema(schemaPath, liveSchemaPin(schemaKind, schemaPath, args["schema-version"]))}
|
|
6332
6506
|
`
|
|
6333
6507
|
);
|
|
6334
6508
|
}
|