@jphutchins/code-review 0.1.0-alpha.64 → 0.1.0-alpha.66
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 +1035 -442
- 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 +78 -14
- package/templates/inline.eta +5 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { execFile } from 'child_process';
|
|
|
9
9
|
import { mkdtemp, readFile, rm, writeFile } from 'fs/promises';
|
|
10
10
|
import { tmpdir } from 'os';
|
|
11
11
|
import { promisify } from 'util';
|
|
12
|
+
import { isRight } from 'fp-ts/lib/Either.js';
|
|
12
13
|
import parseDiff from 'parse-diff';
|
|
13
14
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
14
15
|
import _addFormats from 'ajv-formats';
|
|
@@ -109,7 +110,8 @@ var FindingIdRequired = t.type({ id: t.string });
|
|
|
109
110
|
var FindingOptional = t.partial({
|
|
110
111
|
side: SideCodec,
|
|
111
112
|
recommendation: t.string,
|
|
112
|
-
patch: t.string
|
|
113
|
+
patch: t.string,
|
|
114
|
+
rebuttal: t.string
|
|
113
115
|
});
|
|
114
116
|
var EndGeStart = (shape) => t.refinement(shape, (f) => f.end_line >= f.start_line, "EndGeStart");
|
|
115
117
|
var FindingShape = t.intersection([
|
|
@@ -135,7 +137,8 @@ var SystemicRequired = t.type({
|
|
|
135
137
|
var SystemicOptional = t.partial({
|
|
136
138
|
id: t.string,
|
|
137
139
|
finding_ids: t.array(t.string),
|
|
138
|
-
paths: t.array(t.string)
|
|
140
|
+
paths: t.array(t.string),
|
|
141
|
+
rebuttal: t.string
|
|
139
142
|
});
|
|
140
143
|
var SystemicProblemShape = t.intersection([SystemicRequired, RuleUrlCodec, SystemicOptional]);
|
|
141
144
|
var SystemicProblemCodec = strictExact("SystemicProblemStrict", SystemicProblemShape, [
|
|
@@ -245,11 +248,53 @@ var RECOVERABLE_OPTIONAL_FIELDS = /* @__PURE__ */ new Set([
|
|
|
245
248
|
...PIPELINE_STAMPED_FIELDS,
|
|
246
249
|
"change_size"
|
|
247
250
|
]);
|
|
251
|
+
var ID_SHAPE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
252
|
+
var priorIdsFrom = (doc) => {
|
|
253
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return [];
|
|
254
|
+
const rec = doc;
|
|
255
|
+
const idsOf = (field, systemic) => (Array.isArray(rec[field]) ? rec[field] : []).flatMap((raw) => {
|
|
256
|
+
const item = typeof raw === "object" && raw !== null ? asRecord(raw) : null;
|
|
257
|
+
if (item === null) return [];
|
|
258
|
+
const title = typeof item["title"] === "string" ? item["title"] : "";
|
|
259
|
+
const resolved = resolveRuleId({
|
|
260
|
+
...typeof item["id"] === "string" ? { id: item["id"] } : {},
|
|
261
|
+
...typeof item["code"] === "string" ? { code: item["code"] } : {},
|
|
262
|
+
...typeof item["path"] === "string" ? { path: item["path"] } : {},
|
|
263
|
+
title
|
|
264
|
+
});
|
|
265
|
+
const id = resolved ?? (systemic && title !== "" ? synthesizedSystemicId(title) : void 0);
|
|
266
|
+
return id !== void 0 ? [id] : [];
|
|
267
|
+
});
|
|
268
|
+
return [...idsOf("findings", false), ...idsOf("systemic_problems", true)];
|
|
269
|
+
};
|
|
270
|
+
var hasRebuttal = (item) => (item.rebuttal ?? "").trim() !== "";
|
|
271
|
+
var withoutRebuttals = (doc) => ({
|
|
272
|
+
...doc,
|
|
273
|
+
findings: doc.findings.map(({ rebuttal: _rebuttal, ...f }) => f),
|
|
274
|
+
...doc.systemic_problems !== void 0 ? { systemic_problems: doc.systemic_problems.map(({ rebuttal: _rebuttal, ...s }) => s) } : {}
|
|
275
|
+
});
|
|
276
|
+
var withSystemicProblems = ({ systemic_problems: _replaced, ...doc }, systemic) => ({
|
|
277
|
+
...doc,
|
|
278
|
+
...systemic.length > 0 ? { systemic_problems: [...systemic] } : {}
|
|
279
|
+
});
|
|
248
280
|
var synthesizedId = (parts, prefix) => `${prefix}${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 12)}`;
|
|
249
281
|
var synthesizedFindingId = (path, title) => synthesizedId([path, title], "f-");
|
|
250
282
|
var synthesizedSystemicId = (title) => synthesizedId([title], "s-");
|
|
251
283
|
var isSynthesizedFindingId = (id) => /^f-[A-Za-z0-9_-]{12}$/.test(id);
|
|
252
284
|
var resolveFindingId = (f) => f.id !== "" ? f.id : synthesizedFindingId(f.path, f.title);
|
|
285
|
+
var resolveSystemicId = (s) => s.id !== void 0 && s.id !== "" ? s.id : s.title !== "" ? synthesizedSystemicId(s.title) : void 0;
|
|
286
|
+
var findingIdIn = (ids) => (f) => ids.has(resolveFindingId(f));
|
|
287
|
+
var systemicIdIn = (ids) => (s) => {
|
|
288
|
+
const id = resolveSystemicId(s);
|
|
289
|
+
return id !== void 0 && ids.has(id);
|
|
290
|
+
};
|
|
291
|
+
var withoutIds = (doc, ids) => ids.size === 0 ? doc : {
|
|
292
|
+
...withSystemicProblems(
|
|
293
|
+
doc,
|
|
294
|
+
(doc.systemic_problems ?? []).filter((s) => !systemicIdIn(ids)(s))
|
|
295
|
+
),
|
|
296
|
+
findings: doc.findings.filter((f) => !findingIdIn(ids)(f))
|
|
297
|
+
};
|
|
253
298
|
var resolveRuleId = (rec) => rec.id !== void 0 && rec.id !== "" ? rec.id : rec.code !== void 0 && rec.code !== "" ? rec.code : rec.path !== void 0 ? synthesizedFindingId(rec.path, rec.title) : void 0;
|
|
254
299
|
var usableCountsMap = (v) => {
|
|
255
300
|
if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
|
|
@@ -270,12 +315,18 @@ var LegacyRuleCodec = t.partial({
|
|
|
270
315
|
id: t.string,
|
|
271
316
|
code_url: UriString
|
|
272
317
|
});
|
|
273
|
-
var
|
|
318
|
+
var FindingOptionalV09 = t.partial({
|
|
319
|
+
side: SideCodec,
|
|
320
|
+
recommendation: t.string,
|
|
321
|
+
patch: t.string
|
|
322
|
+
});
|
|
323
|
+
var FindingShapeV09 = t.intersection([FindingCoreRequired, LegacyRuleCodec, FindingOptionalV09]);
|
|
274
324
|
var FindingCodecV09 = t.exact(EndGeStart(FindingShapeV09));
|
|
275
325
|
var SystemicV09Optional = t.partial({
|
|
276
326
|
finding_codes: t.array(t.string),
|
|
277
327
|
finding_ids: t.array(t.string),
|
|
278
|
-
paths: t.array(t.string)
|
|
328
|
+
paths: t.array(t.string),
|
|
329
|
+
rebuttal: t.unknown
|
|
279
330
|
});
|
|
280
331
|
var SystemicV09Shape = t.intersection([SystemicRequired, LegacyRuleCodec, SystemicV09Optional]);
|
|
281
332
|
var SystemicProblemCodecV09 = strictExact("SystemicV09Strict", SystemicV09Shape, [
|
|
@@ -343,7 +394,7 @@ var normalizeV09 = (doc) => {
|
|
|
343
394
|
id: resolveRuleId({ id, code, path: f.path, title: f.title }) ?? synthesizedFindingId(f.path, f.title)
|
|
344
395
|
}));
|
|
345
396
|
const systemic_problems = doc.systemic_problems?.map(
|
|
346
|
-
({ code, id, finding_codes, finding_ids, ...s }) => ({
|
|
397
|
+
({ code, id, finding_codes, finding_ids, rebuttal: _rebuttal, ...s }) => ({
|
|
347
398
|
...s,
|
|
348
399
|
id: resolveRuleId({ id, code, title: s.title }) ?? synthesizedSystemicId(s.title),
|
|
349
400
|
...finding_ids !== void 0 && finding_ids.length > 0 ? { finding_ids } : finding_codes !== void 0 ? { finding_ids: finding_codes } : {}
|
|
@@ -484,7 +535,7 @@ var TestSummaryCodec = t.intersection([
|
|
|
484
535
|
failures: t.array(TestFailureCodec)
|
|
485
536
|
})
|
|
486
537
|
]);
|
|
487
|
-
var DEFAULT_SCHEMA_VERSION = "0.
|
|
538
|
+
var DEFAULT_SCHEMA_VERSION = "0.11.0";
|
|
488
539
|
var incompleteFindings = (summary) => ({
|
|
489
540
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
490
541
|
summary,
|
|
@@ -494,7 +545,7 @@ var incompleteFindings = (summary) => ({
|
|
|
494
545
|
var isIncompleteFindings = (findings) => findings.verdict === "error" && findings.findings.length === 0;
|
|
495
546
|
|
|
496
547
|
// src/released.ts
|
|
497
|
-
var RELEASED = "2026-10-
|
|
548
|
+
var RELEASED = "2026-10-10";
|
|
498
549
|
|
|
499
550
|
// src/cost.ts
|
|
500
551
|
var defaultWarn = (message) => {
|
|
@@ -631,6 +682,369 @@ var warnStalePrices = (prices, warn = defaultWarn) => {
|
|
|
631
682
|
`::warning:: code-review cost: the price map was last verified ${prices._updated}, before this CLI's release (${RELEASED}) \u2014 the map may miss pricing this CLI ships; re-verify against the provider's pricing page`
|
|
632
683
|
);
|
|
633
684
|
};
|
|
685
|
+
var DispositionCodec = t.keyof({ fixed: null, refuted: null, dismissed: null });
|
|
686
|
+
var DISPOSITIONS = Object.keys(DispositionCodec.keys);
|
|
687
|
+
var RulingCodec = t.keyof({ upheld: null, overruled: null });
|
|
688
|
+
var RULINGS = Object.keys(RulingCodec.keys);
|
|
689
|
+
var ResponseDispositionCodec = t.keyof({
|
|
690
|
+
fixed: null,
|
|
691
|
+
refuted: null,
|
|
692
|
+
dismissed: null,
|
|
693
|
+
upheld: null,
|
|
694
|
+
overruled: null,
|
|
695
|
+
unstated: null
|
|
696
|
+
});
|
|
697
|
+
var isAnswerWord = (word) => DispositionCodec.is(word) || RulingCodec.is(word);
|
|
698
|
+
var RESPONSE_FORM = `Review-Response: <id> ${DISPOSITIONS.join("|")} \u2014 <reason>`;
|
|
699
|
+
var RESPONSE_TABLE_COLUMNS = ["id", "disposition", "reason"];
|
|
700
|
+
`| ${RESPONSE_TABLE_COLUMNS.join(" | ")} |`;
|
|
701
|
+
`| ${RESPONSE_TABLE_COLUMNS.map(() => "---").join(" | ")} |`;
|
|
702
|
+
var listed = (words, conjunction) => {
|
|
703
|
+
const quoted = words.map((word) => `\`${word}\``);
|
|
704
|
+
return quoted.length < 2 ? quoted.join("") : `${quoted.slice(0, -1).join(", ")} ${conjunction} ${quoted.at(-1) ?? ""}`;
|
|
705
|
+
};
|
|
706
|
+
var RULING_FORM = `Review-Response: <id> ${RULINGS.join("|")} \u2014 <reason>`;
|
|
707
|
+
var RULING_TEACHING = `A maintainer rules on a contested finding by answering its id, in a table row or a \`${RULING_FORM}\` commit line, with \`upheld\` (the finding stands at full weight until it is fixed) or \`overruled\` (it is closed for good).`;
|
|
708
|
+
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.`;
|
|
709
|
+
|
|
710
|
+
// src/responses.ts
|
|
711
|
+
var RESPONSE_REASON_CLIP_CHARS = 300;
|
|
712
|
+
var RESPONSES_PER_CHANNEL = 25;
|
|
713
|
+
var ChannelCodec = t.keyof({ comment: null, commit: null });
|
|
714
|
+
var MAINTAINER_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
|
|
715
|
+
var CLOSING_DISPOSITIONS = /* @__PURE__ */ new Set(["refuted", "dismissed"]);
|
|
716
|
+
var ResponseShape = t.type({
|
|
717
|
+
id: t.string,
|
|
718
|
+
disposition: ResponseDispositionCodec,
|
|
719
|
+
reason: t.string,
|
|
720
|
+
channel: ChannelCodec,
|
|
721
|
+
source_url: t.string,
|
|
722
|
+
author: t.union([t.string, t.null]),
|
|
723
|
+
author_association: t.union([t.string, t.null]),
|
|
724
|
+
created_at: t.union([t.string, t.null])
|
|
725
|
+
});
|
|
726
|
+
var ResponseCodec = strictExact("Response", ResponseShape, [ResponseShape]);
|
|
727
|
+
var UnmatchedResponseShape = t.type({
|
|
728
|
+
id: t.string,
|
|
729
|
+
channel: ChannelCodec,
|
|
730
|
+
source_url: t.string
|
|
731
|
+
});
|
|
732
|
+
var UnmatchedResponseCodec = strictExact("UnmatchedResponse", UnmatchedResponseShape, [
|
|
733
|
+
UnmatchedResponseShape
|
|
734
|
+
]);
|
|
735
|
+
var ResponsesFileAnswers = t.type({
|
|
736
|
+
responses: t.array(ResponseCodec),
|
|
737
|
+
unmatched: t.array(UnmatchedResponseCodec)
|
|
738
|
+
});
|
|
739
|
+
var HeldClaimShape = t.type({
|
|
740
|
+
id: t.string,
|
|
741
|
+
title: t.string,
|
|
742
|
+
severity: SeverityCodec,
|
|
743
|
+
since: t.string
|
|
744
|
+
});
|
|
745
|
+
var HeldClaimCodec = strictExact("HeldClaim", HeldClaimShape, [HeldClaimShape]);
|
|
746
|
+
var ResponsesFileDialogue = t.partial({
|
|
747
|
+
contested: t.array(HeldClaimCodec),
|
|
748
|
+
upheld: t.array(HeldClaimCodec),
|
|
749
|
+
overruled: t.array(HeldClaimCodec)
|
|
750
|
+
});
|
|
751
|
+
var ResponsesFileShape = t.intersection([ResponsesFileAnswers, ResponsesFileDialogue]);
|
|
752
|
+
strictExact("ResponsesFile", ResponsesFileShape, [
|
|
753
|
+
ResponsesFileAnswers,
|
|
754
|
+
ResponsesFileDialogue
|
|
755
|
+
]);
|
|
756
|
+
var RESPONSE_LINE_RE = /^ {0,3}review-response:\s*(\S+)\s+([A-Za-z]+)(?![A-Za-z]|\s*\|)[\s—–:-]*(.*)$/i;
|
|
757
|
+
var FENCE_OPEN_RE = /^ {0,3}(`{3,}(?=[^`]*$)|~{3,})/;
|
|
758
|
+
var LINE_BREAK_RE = /\r\n?|\n|\u2028|\u2029/;
|
|
759
|
+
var unfencedLines = (text) => text.split(LINE_BREAK_RE).reduce(
|
|
760
|
+
(state, line) => {
|
|
761
|
+
const marker = FENCE_OPEN_RE.exec(line)?.[1];
|
|
762
|
+
if (state.fence === null) {
|
|
763
|
+
if (marker !== void 0) return { fence: marker, lines: state.lines };
|
|
764
|
+
state.lines.push(line);
|
|
765
|
+
return state;
|
|
766
|
+
}
|
|
767
|
+
const closes = marker !== void 0 && marker[0] === state.fence[0] && marker.length >= state.fence.length && line.trim() === marker;
|
|
768
|
+
return closes ? { fence: null, lines: state.lines } : state;
|
|
769
|
+
},
|
|
770
|
+
{ fence: null, lines: [] }
|
|
771
|
+
).lines;
|
|
772
|
+
var WHOLE_LINE_CODE_SPAN_RE = /^ {0,3}`([^`]+)`\s*$/;
|
|
773
|
+
var answerLines = (text) => unfencedLines(text).map((line) => WHOLE_LINE_CODE_SPAN_RE.exec(line)?.[1] ?? line);
|
|
774
|
+
var unwrapId = (token) => token.replace(/^[`"']+|[`"'.,:;]+$/g, "");
|
|
775
|
+
var lineAnswers = (lines) => lines.flatMap((line) => {
|
|
776
|
+
const match = RESPONSE_LINE_RE.exec(line);
|
|
777
|
+
const id = unwrapId(match?.[1] ?? "");
|
|
778
|
+
const disposition = match?.[2]?.toLowerCase();
|
|
779
|
+
return id !== "" && isAnswerWord(disposition) ? [
|
|
780
|
+
{
|
|
781
|
+
id,
|
|
782
|
+
disposition,
|
|
783
|
+
reason: clipText((match?.[3] ?? "").trim(), RESPONSE_REASON_CLIP_CHARS)
|
|
784
|
+
}
|
|
785
|
+
] : [];
|
|
786
|
+
});
|
|
787
|
+
var ID_IN_CELL_RE = /`([^`]+)`/g;
|
|
788
|
+
var ID_HEADERS = /* @__PURE__ */ new Set(["id", "ids", "finding", "findings"]);
|
|
789
|
+
var DISPOSITION_HEADERS = /* @__PURE__ */ new Set(["disposition", "verdict", "resolution"]);
|
|
790
|
+
var CELL_RE = /((?:\\[\s\S]?|[^\\|])*)(\||$)/g;
|
|
791
|
+
var splitCells = (row) => {
|
|
792
|
+
const cells = [...row.matchAll(CELL_RE)];
|
|
793
|
+
return cells.slice(0, cells.findIndex((match) => match[2] !== "|") + 1).map((match) => match[1] ?? "");
|
|
794
|
+
};
|
|
795
|
+
var BLOCK_START_RE = /^ {0,3}([-+*]\s|\d+[.)]\s|#{1,6}(\s|$)|>)/;
|
|
796
|
+
var tableRow = (line) => {
|
|
797
|
+
if (!/^ {0,3}\S/.test(line) || BLOCK_START_RE.test(line)) return null;
|
|
798
|
+
const split = splitCells(line.trim());
|
|
799
|
+
if (split.length < 2) return null;
|
|
800
|
+
const inner = split.slice(line.trim().startsWith("|") ? 1 : 0);
|
|
801
|
+
return (inner.length > 1 && inner[inner.length - 1]?.trim() === "" ? inner.slice(0, -1) : inner).map((cell) => cell.trim().replace(/\\\|/g, "|"));
|
|
802
|
+
};
|
|
803
|
+
var isDelimiter = (cells) => cells !== null && cells.every((cell) => /^:?-+:?$/.test(cell));
|
|
804
|
+
var plainCell = (cell) => cell.replace(/[*_`]/g, "").trim().toLowerCase();
|
|
805
|
+
var columnName = (cell) => plainCell(cell).split(/[\s/]+/)[0] ?? "";
|
|
806
|
+
var idsInCell = (cell) => {
|
|
807
|
+
const ids = cell.replace(/\([^()]*\)/g, "");
|
|
808
|
+
const quoted = [...ids.matchAll(ID_IN_CELL_RE)].map((match) => unwrapId((match[1] ?? "").trim())).filter((id) => id !== "");
|
|
809
|
+
const bare = ids.split(",").map((piece) => unwrapId(piece.trim())).filter((id) => id !== "");
|
|
810
|
+
return quoted.length > 0 ? quoted : bare.every((id) => /^\S+$/.test(id)) ? bare : [];
|
|
811
|
+
};
|
|
812
|
+
var tableDisposition = (cell) => {
|
|
813
|
+
const firstWord = plainCell(cell).replace(/^[^a-z]+/, "").split(/[^a-z]/)[0] ?? "";
|
|
814
|
+
return isAnswerWord(firstWord) ? firstWord : "unstated";
|
|
815
|
+
};
|
|
816
|
+
var rowAnswers = (cells, columns) => {
|
|
817
|
+
const dispositionCell = cells[columns.disposition] ?? "";
|
|
818
|
+
const disposition = tableDisposition(dispositionCell);
|
|
819
|
+
const verdictOnly = isAnswerWord(plainCell(dispositionCell));
|
|
820
|
+
const reason = clipText(
|
|
821
|
+
cells.filter(
|
|
822
|
+
(cell, column) => cell !== "" && column !== columns.id && !(verdictOnly && column === columns.disposition)
|
|
823
|
+
).join(" \u2014 "),
|
|
824
|
+
RESPONSE_REASON_CLIP_CHARS
|
|
825
|
+
);
|
|
826
|
+
return idsInCell(cells[columns.id] ?? "").map((id) => ({ id, disposition, reason }));
|
|
827
|
+
};
|
|
828
|
+
var namesAColumn = (name) => ID_HEADERS.has(name) || DISPOSITION_HEADERS.has(name);
|
|
829
|
+
var tableAnswers = (lines) => {
|
|
830
|
+
const rows = lines.map(tableRow);
|
|
831
|
+
return rows.reduce(
|
|
832
|
+
(state, cells, index) => {
|
|
833
|
+
if (state.skipDelimiter) return { ...state, skipDelimiter: false };
|
|
834
|
+
if (cells === null) return { ...state, inTable: false, columns: null };
|
|
835
|
+
const opensTable = !isDelimiter(cells) && isDelimiter(rows[index + 1] ?? null) && (!state.inTable || cells.map(columnName).some(namesAColumn));
|
|
836
|
+
if (opensTable) {
|
|
837
|
+
const header = cells.map(columnName);
|
|
838
|
+
const id = header.findIndex((name) => ID_HEADERS.has(name));
|
|
839
|
+
const disposition = header.findIndex((name) => DISPOSITION_HEADERS.has(name));
|
|
840
|
+
return {
|
|
841
|
+
...state,
|
|
842
|
+
inTable: true,
|
|
843
|
+
skipDelimiter: true,
|
|
844
|
+
columns: id < 0 || disposition < 0 ? null : { id, disposition }
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
if (state.inTable && state.columns !== null && !isDelimiter(cells)) {
|
|
848
|
+
state.answers.push(...rowAnswers(cells, state.columns));
|
|
849
|
+
}
|
|
850
|
+
return isDelimiter(rows[index + 1] ?? null) ? { ...state, skipDelimiter: true } : state;
|
|
851
|
+
},
|
|
852
|
+
{ columns: null, inTable: false, skipDelimiter: false, answers: [] }
|
|
853
|
+
).answers;
|
|
854
|
+
};
|
|
855
|
+
var parseResponses = (text) => {
|
|
856
|
+
const lines = answerLines(text);
|
|
857
|
+
const seen = /* @__PURE__ */ new Set();
|
|
858
|
+
return [...lineAnswers(lines), ...tableAnswers(lines)].filter((answer) => {
|
|
859
|
+
const key2 = JSON.stringify([answer.id, answer.disposition, answer.reason]);
|
|
860
|
+
if (seen.has(key2)) return false;
|
|
861
|
+
seen.add(key2);
|
|
862
|
+
return true;
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
var isHuman = (login, type9, botLogin) => login !== botLogin && type9 === "User";
|
|
866
|
+
var newestFirst = (a, b) => {
|
|
867
|
+
const [left2, right2] = [a.created_at ?? "", b.created_at ?? ""];
|
|
868
|
+
return left2 < right2 ? 1 : left2 > right2 ? -1 : b.id - a.id;
|
|
869
|
+
};
|
|
870
|
+
var answersFrom = (input) => {
|
|
871
|
+
const comments = [...input.comments].filter((comment) => isHuman(comment.user.login, comment.user.type ?? null, input.botLogin)).sort(newestFirst).flatMap(
|
|
872
|
+
(comment) => parseResponses(comment.body ?? "").map((line) => ({
|
|
873
|
+
...line,
|
|
874
|
+
channel: "comment",
|
|
875
|
+
source_url: `https://github.com/${input.repo}/pull/${String(input.prNumber)}#issuecomment-${String(comment.id)}`,
|
|
876
|
+
author: comment.user.login,
|
|
877
|
+
author_association: comment.author_association ?? null,
|
|
878
|
+
created_at: comment.created_at ?? null
|
|
879
|
+
}))
|
|
880
|
+
);
|
|
881
|
+
const commits = [...input.commits].reverse().flatMap(
|
|
882
|
+
(commit) => parseResponses(commit.message).map((line) => ({
|
|
883
|
+
...line,
|
|
884
|
+
channel: "commit",
|
|
885
|
+
source_url: `https://github.com/${input.repo}/commit/${commit.sha}`,
|
|
886
|
+
author: commit.author,
|
|
887
|
+
author_association: null,
|
|
888
|
+
created_at: commit.date ?? null
|
|
889
|
+
}))
|
|
890
|
+
);
|
|
891
|
+
return { comments, commits };
|
|
892
|
+
};
|
|
893
|
+
var harvestResponses = (input) => {
|
|
894
|
+
const { comments: fromComments, commits: fromCommits } = answersFrom(input);
|
|
895
|
+
const matched = (response) => input.priorIds.has(response.id);
|
|
896
|
+
const echoed = (response) => !matched(response) && ID_SHAPE_RE.test(response.id);
|
|
897
|
+
const groups = [fromComments, fromCommits].flatMap((channel) => [
|
|
898
|
+
channel.filter(matched),
|
|
899
|
+
channel.filter(echoed)
|
|
900
|
+
]);
|
|
901
|
+
const [comments, commentEchoes, commits, commitEchoes] = groups.map(
|
|
902
|
+
(group) => group.slice(0, RESPONSES_PER_CHANNEL)
|
|
903
|
+
);
|
|
904
|
+
return {
|
|
905
|
+
file: {
|
|
906
|
+
responses: [...comments ?? [], ...commits ?? []],
|
|
907
|
+
unmatched: [...commentEchoes ?? [], ...commitEchoes ?? []].map(
|
|
908
|
+
({ id, channel, source_url }) => ({ id, channel, source_url })
|
|
909
|
+
)
|
|
910
|
+
},
|
|
911
|
+
dropped: groups.reduce(
|
|
912
|
+
(total, group) => total + Math.max(0, group.length - RESPONSES_PER_CHANNEL),
|
|
913
|
+
0
|
|
914
|
+
)
|
|
915
|
+
};
|
|
916
|
+
};
|
|
917
|
+
var isTrustedResponse = (response, headInBaseRepo) => {
|
|
918
|
+
switch (response.channel) {
|
|
919
|
+
case "comment":
|
|
920
|
+
return MAINTAINER_ASSOCIATIONS.includes(response.author_association ?? "");
|
|
921
|
+
case "commit":
|
|
922
|
+
return headInBaseRepo;
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
var answeredInstant = (response) => response.created_at === null ? Number.NaN : Date.parse(response.created_at);
|
|
926
|
+
var closingResponses = (responses, isTrusted) => newestTrustedAnswersIn(responses, isTrusted, CLOSING_DISPOSITIONS);
|
|
927
|
+
var withRulingsOn = (responses, ruledIds, isTrusted) => responses.map(
|
|
928
|
+
(response) => RulingCodec.is(response.disposition) && !(ruledIds.has(response.id) && isTrusted(response)) ? { ...response, disposition: "unstated" } : response
|
|
929
|
+
);
|
|
930
|
+
var newestTrustedAnswersIn = (responses, isTrusted, dispositions) => {
|
|
931
|
+
const byId = responses.filter((response) => response.disposition !== "unstated" && isTrusted(response)).reduce((groups, response) => {
|
|
932
|
+
const timed = { response, at: answeredInstant(response) };
|
|
933
|
+
groups.set(response.id, [...groups.get(response.id) ?? [], timed]);
|
|
934
|
+
return groups;
|
|
935
|
+
}, /* @__PURE__ */ new Map());
|
|
936
|
+
return [...byId.values()].flatMap((answers) => {
|
|
937
|
+
if (answers.some(({ at }) => Number.isNaN(at))) return [];
|
|
938
|
+
const latest = Math.max(...answers.map(({ at }) => at));
|
|
939
|
+
const newest = answers.filter(({ at }) => at === latest).map(({ response }) => response);
|
|
940
|
+
return newest.every((response) => dispositions.has(response.disposition)) ? [...newest].sort((a, b) => a.source_url < b.source_url ? -1 : 1).slice(0, 1) : [];
|
|
941
|
+
});
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// src/dialogue.ts
|
|
945
|
+
var DialogueStateCodec = t.keyof({
|
|
946
|
+
rebutted: null,
|
|
947
|
+
contested: null,
|
|
948
|
+
upheld: null,
|
|
949
|
+
overruled: null
|
|
950
|
+
});
|
|
951
|
+
var DialogueEntryCodec = t.type({
|
|
952
|
+
id: t.string,
|
|
953
|
+
state: DialogueStateCodec,
|
|
954
|
+
answer: t.string,
|
|
955
|
+
at: t.string,
|
|
956
|
+
title: t.string,
|
|
957
|
+
severity: SeverityCodec
|
|
958
|
+
});
|
|
959
|
+
var DIALOGUE_STATES = Object.keys(DialogueStateCodec.keys);
|
|
960
|
+
var DIALOGUE_ENTRY_CAP = 32;
|
|
961
|
+
var decodeDialogue = (raw) => {
|
|
962
|
+
const items = Array.isArray(raw) ? raw : [];
|
|
963
|
+
const decoded = items.map((item) => ({ item, entry: DialogueEntryCodec.decode(item) }));
|
|
964
|
+
const entries = decoded.flatMap(({ entry }) => isRight(entry) ? [entry.right] : []);
|
|
965
|
+
const undecoded = decoded.flatMap(({ item, entry }) => isRight(entry) ? [] : [item]);
|
|
966
|
+
return { entries, undecoded, skipped: Array.isArray(raw) ? undecoded.length : 1 };
|
|
967
|
+
};
|
|
968
|
+
var dialogueAnswers = (responses, isTrusted) => {
|
|
969
|
+
const newest = (dispositions) => new Map(
|
|
970
|
+
newestTrustedAnswersIn(responses, isTrusted, new Set(dispositions)).map((response) => [
|
|
971
|
+
response.id,
|
|
972
|
+
response
|
|
973
|
+
])
|
|
974
|
+
);
|
|
975
|
+
return {
|
|
976
|
+
closures: newest(["refuted", "dismissed"]),
|
|
977
|
+
overrulings: newest(["overruled"]),
|
|
978
|
+
upholdings: newest(["upheld"]),
|
|
979
|
+
fixed: new Set(newest(["fixed"]).keys())
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
var linked = (entry, answer, state) => answer.created_at === null ? [entry] : [{ ...entry, state, answer: answer.source_url, at: answer.created_at }];
|
|
983
|
+
var isNewer = (answer, entry) => answer.created_at !== null && Date.parse(answer.created_at) > Date.parse(entry.at);
|
|
984
|
+
var isOlder = (answer, entry) => answer.created_at !== null && Date.parse(answer.created_at) < Date.parse(entry.at);
|
|
985
|
+
var advanceEntry = (entry, answers, item) => {
|
|
986
|
+
if (answers.fixed.has(entry.id)) return [];
|
|
987
|
+
const refreshed = item !== void 0 && (entry.state === "contested" || entry.state === "upheld" || item.rebutted) ? { ...entry, title: item.title, severity: item.severity } : entry;
|
|
988
|
+
const closure = answers.closures.get(entry.id);
|
|
989
|
+
switch (entry.state) {
|
|
990
|
+
case "rebutted":
|
|
991
|
+
return closure === void 0 ? [refreshed] : linked(refreshed, closure, isNewer(closure, entry) ? "contested" : "rebutted");
|
|
992
|
+
case "contested": {
|
|
993
|
+
const overruling = answers.overrulings.get(entry.id);
|
|
994
|
+
const upholding = answers.upholdings.get(entry.id);
|
|
995
|
+
return upholding !== void 0 ? linked(refreshed, upholding, "upheld") : overruling !== void 0 ? linked(refreshed, overruling, "overruled") : closure === void 0 ? [refreshed] : linked(refreshed, closure, isOlder(closure, entry) ? "rebutted" : "contested");
|
|
996
|
+
}
|
|
997
|
+
case "upheld": {
|
|
998
|
+
const overruling = answers.overrulings.get(entry.id);
|
|
999
|
+
return overruling !== void 0 && isNewer(overruling, entry) ? linked(refreshed, overruling, "overruled") : [refreshed];
|
|
1000
|
+
}
|
|
1001
|
+
case "overruled": {
|
|
1002
|
+
const upholding = answers.upholdings.get(entry.id);
|
|
1003
|
+
return upholding !== void 0 && isNewer(upholding, entry) ? linked(refreshed, upholding, "upheld") : [refreshed];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
var advanceDialogue = (prior, answers, current) => {
|
|
1008
|
+
const currentById = new Map(current.map((item) => [item.id, item]));
|
|
1009
|
+
const priorIds = new Set(prior.map((entry) => entry.id));
|
|
1010
|
+
const opened = [...currentById.values()].flatMap((item) => {
|
|
1011
|
+
const closure = answers.closures.get(item.id);
|
|
1012
|
+
const at = closure?.created_at ?? null;
|
|
1013
|
+
return priorIds.has(item.id) || !item.rebutted || closure === void 0 || at === null ? [] : [
|
|
1014
|
+
{
|
|
1015
|
+
id: item.id,
|
|
1016
|
+
state: "rebutted",
|
|
1017
|
+
answer: closure.source_url,
|
|
1018
|
+
at,
|
|
1019
|
+
title: item.title,
|
|
1020
|
+
severity: item.severity
|
|
1021
|
+
}
|
|
1022
|
+
];
|
|
1023
|
+
});
|
|
1024
|
+
const entries = [
|
|
1025
|
+
...prior.flatMap((entry) => advanceEntry(entry, answers, currentById.get(entry.id))),
|
|
1026
|
+
...opened
|
|
1027
|
+
];
|
|
1028
|
+
const held = entries.filter((entry) => entry.state !== "rebutted");
|
|
1029
|
+
return [
|
|
1030
|
+
...held,
|
|
1031
|
+
...entries.filter((entry) => entry.state === "rebutted").slice(0, Math.max(0, DIALOGUE_ENTRY_CAP - held.length))
|
|
1032
|
+
];
|
|
1033
|
+
};
|
|
1034
|
+
var dialogueMetricsTable = (metrics) => [
|
|
1035
|
+
"### \u{1F4AC} Review dialogue",
|
|
1036
|
+
"",
|
|
1037
|
+
"| | |",
|
|
1038
|
+
"|---|--:|",
|
|
1039
|
+
`| answers read (comments \xB7 commits) | ${String(metrics.commentAnswers)} \xB7 ${String(metrics.commitAnswers)} |`,
|
|
1040
|
+
`| trusted closing answers | ${String(metrics.closures)} |`,
|
|
1041
|
+
`| re-raises dropped (answered \xB7 overruled) | ${String(metrics.droppedAnswered)} \xB7 ${String(metrics.droppedOverruled)} |`,
|
|
1042
|
+
`| re-raises kept and annotated | ${String(metrics.annotated)} |`,
|
|
1043
|
+
`| ${DIALOGUE_STATES.join(" \xB7 ")} | ${DIALOGUE_STATES.map(
|
|
1044
|
+
(state) => String(metrics.entries.filter((entry) => entry.state === state).length)
|
|
1045
|
+
).join(" \xB7 ")} |`,
|
|
1046
|
+
`| systemic problems no trusted answer names | ${String(metrics.systemicUnanswered)} |`
|
|
1047
|
+
].join("\n");
|
|
634
1048
|
|
|
635
1049
|
// src/patch.ts
|
|
636
1050
|
var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
@@ -758,6 +1172,7 @@ var decodeBase64Json = (b64) => {
|
|
|
758
1172
|
return void 0;
|
|
759
1173
|
}
|
|
760
1174
|
};
|
|
1175
|
+
var noteFor = (notes, key2) => notes !== void 0 && Object.prototype.hasOwnProperty.call(notes, key2) ? notes[key2] ?? "" : "";
|
|
761
1176
|
var findingsPointer = (jsonUrl) => encodeMarker(jsonUrl);
|
|
762
1177
|
var warnedValveFindings = /* @__PURE__ */ new Set();
|
|
763
1178
|
var INLINE_PROSE_CLIP_THRESHOLD_CHARS = 3e4;
|
|
@@ -824,6 +1239,8 @@ var isSeverityCounts = (u) => typeof u === "object" && u !== null && SEVERITIES.
|
|
|
824
1239
|
var MAX_IDS_PER_ROUND = 8;
|
|
825
1240
|
var hasId = (ids, code) => ids !== void 0 && Object.prototype.hasOwnProperty.call(ids, code);
|
|
826
1241
|
var escapeCodeBackticks = (code) => code.replace(/`/g, "-").replace(/\r?\n/g, " ");
|
|
1242
|
+
var encodeAutolinkParens = (url) => url.replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
1243
|
+
var linkSafeUrl = (url) => encodeAutolinkParens(escapeCodeBackticks(url));
|
|
827
1244
|
var normalizeIdCounts = (ids, priorCodes, preferredKeys, preValidated = false) => {
|
|
828
1245
|
const entries = Object.entries((preValidated ? ids : usableCountsMap(ids)) ?? {});
|
|
829
1246
|
if (entries.length === 0) return void 0;
|
|
@@ -987,8 +1404,15 @@ var convergenceScore = (doc, threshold) => round2(
|
|
|
987
1404
|
0
|
|
988
1405
|
)
|
|
989
1406
|
);
|
|
990
|
-
var
|
|
991
|
-
|
|
1407
|
+
var CONTESTED_MAJOR = 0.51;
|
|
1408
|
+
var contestedWeight = (severity, threshold) => severity === "major" ? CONTESTED_MAJOR : convergenceFloor(severity, threshold);
|
|
1409
|
+
var dialogueScore = (doc, contested, threshold) => {
|
|
1410
|
+
return round2(
|
|
1411
|
+
convergenceScore(withoutIds(doc, new Set(contested.map((entry) => entry.id))), threshold) + contested.reduce((sum, entry) => sum + contestedWeight(entry.severity, threshold), 0)
|
|
1412
|
+
);
|
|
1413
|
+
};
|
|
1414
|
+
var buildConvergence = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD, priorRounds = [], round = 1, ids = {}, sha, contested = []) => {
|
|
1415
|
+
const score = dialogueScore(doc, contested, threshold);
|
|
992
1416
|
const normalized = normalizeIdCounts(ids, priorRounds[priorRounds.length - 1]?.ids);
|
|
993
1417
|
const current = {
|
|
994
1418
|
round,
|
|
@@ -1119,6 +1543,13 @@ var findingsMarkerPair = (jsonUrl, convergence) => {
|
|
|
1119
1543
|
return marker === "" ? conv : `${marker}
|
|
1120
1544
|
${conv}`;
|
|
1121
1545
|
};
|
|
1546
|
+
var DIALOGUE_RE = /<!-- code-review:dialogue;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
1547
|
+
var dialogueMarker = (entries, undecoded = []) => entries.length + undecoded.length === 0 ? "" : `<!-- code-review:dialogue;base64 ${Buffer.from(JSON.stringify([...entries, ...undecoded]), "utf-8").toString("base64")} -->`;
|
|
1548
|
+
var carriedDialogueMarker = (body) => DIALOGUE_RE.exec(body)?.[0] ?? "";
|
|
1549
|
+
var parseDialogueMarker = (body) => {
|
|
1550
|
+
const b64 = DIALOGUE_RE.exec(body)?.[1];
|
|
1551
|
+
return b64 === void 0 ? { entries: [], undecoded: [], skipped: 0 } : decodeDialogue(decodeBase64Json(b64));
|
|
1552
|
+
};
|
|
1122
1553
|
var parseConvergenceMarker = (body) => {
|
|
1123
1554
|
const b64 = CONVERGENCE_RE.exec(body)?.[1];
|
|
1124
1555
|
return b64 === void 0 ? null : validStampedConvergence(decodeBase64Json(b64));
|
|
@@ -1175,11 +1606,20 @@ var carryForwardMarkers = (body) => {
|
|
|
1175
1606
|
const findings = carriedFindingsMarker(body);
|
|
1176
1607
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
1177
1608
|
const convergence = CONVERGENCE_RE.exec(body)?.[0];
|
|
1609
|
+
const dialogue = DIALOGUE_RE.exec(body)?.[0];
|
|
1178
1610
|
const rounds = ROUNDS_RE.exec(body)?.[0];
|
|
1179
1611
|
const signal = SIGNAL_RE.exec(body)?.[0];
|
|
1180
1612
|
const findingsBlock = findings ? `${AGENTS_STOP_DIRECTIVE}
|
|
1181
1613
|
${findings}` : void 0;
|
|
1182
|
-
return [
|
|
1614
|
+
return [
|
|
1615
|
+
findingsBlock,
|
|
1616
|
+
reviewedSha,
|
|
1617
|
+
carriedProvenanceMarkers(body),
|
|
1618
|
+
convergence,
|
|
1619
|
+
dialogue,
|
|
1620
|
+
rounds,
|
|
1621
|
+
signal
|
|
1622
|
+
].filter((m) => m !== void 0 && m !== "").join("\n\n");
|
|
1183
1623
|
};
|
|
1184
1624
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
1185
1625
|
var projectPatch = (patch, surface) => {
|
|
@@ -1356,270 +1796,41 @@ var resolvePriorFindings = async (body, read) => {
|
|
|
1356
1796
|
}
|
|
1357
1797
|
};
|
|
1358
1798
|
|
|
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
1799
|
// src/answered.ts
|
|
1475
|
-
var
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
in_reply_to_id: t.union([t.number, t.null]),
|
|
1479
|
-
user_login: t.string,
|
|
1480
|
-
user_type: t.union([t.string, t.null]),
|
|
1481
|
-
body: t.union([t.string, t.null]),
|
|
1482
|
-
html_url: t.string,
|
|
1483
|
-
path: t.union([t.string, t.null]),
|
|
1484
|
-
line: t.union([t.number, t.null]),
|
|
1485
|
-
created_at: t.union([t.string, t.null])
|
|
1486
|
-
});
|
|
1487
|
-
var AnsweredEntryCodec = t.type({
|
|
1488
|
-
code: t.string,
|
|
1489
|
-
title: t.string,
|
|
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
|
|
1800
|
+
var closedPriorItems = (closures, items, idOf) => closures.flatMap((response) => {
|
|
1801
|
+
const item = items.find((candidate) => idOf(candidate) === response.id);
|
|
1802
|
+
return item === void 0 ? [] : [[item, response]];
|
|
1506
1803
|
});
|
|
1507
|
-
var
|
|
1508
|
-
code:
|
|
1509
|
-
title:
|
|
1510
|
-
description:
|
|
1511
|
-
reasoning:
|
|
1512
|
-
severity:
|
|
1513
|
-
|
|
1514
|
-
|
|
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
|
|
1804
|
+
var closedClaim = (item, response) => ({
|
|
1805
|
+
code: response.id,
|
|
1806
|
+
title: item.title,
|
|
1807
|
+
description: item.description,
|
|
1808
|
+
reasoning: item.reasoning,
|
|
1809
|
+
severity: item.severity,
|
|
1810
|
+
answerUrl: response.source_url,
|
|
1811
|
+
answerAuthor: response.author
|
|
1521
1812
|
});
|
|
1522
|
-
var
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
};
|
|
1813
|
+
var answeredRegistryFrom = (closures, prior) => closedPriorItems(closures, prior?.findings ?? [], resolveFindingId).map(
|
|
1814
|
+
([finding, response]) => ({
|
|
1815
|
+
...closedClaim(finding, response),
|
|
1816
|
+
path: finding.path,
|
|
1817
|
+
patch: finding.patch ?? null
|
|
1818
|
+
})
|
|
1819
|
+
);
|
|
1820
|
+
var answeredSystemicRegistryFrom = (closures, prior) => closedPriorItems(closures, prior?.systemic_problems ?? [], resolveSystemicId).map(
|
|
1821
|
+
([systemic, response]) => closedClaim(systemic, response)
|
|
1822
|
+
);
|
|
1614
1823
|
var isSynthesizedTitleMatch = (f, e) => isSynthesizedFindingId(e.code) && e.title === f.title;
|
|
1615
1824
|
var matches = (resolvedId, f, e) => e.code === resolvedId || isSynthesizedTitleMatch(f, e);
|
|
1616
|
-
var
|
|
1825
|
+
var couldMatch = (code, doc) => isSynthesizedFindingId(code) || doc.findings.some((f) => resolveFindingId(f) === code) || (doc.systemic_problems ?? []).some((s) => resolveSystemicId(s) === code);
|
|
1826
|
+
var CLAIM_FIELDS = ["title", "description", "reasoning", "severity"];
|
|
1827
|
+
var VERBATIM_FIELDS = [...CLAIM_FIELDS, "path", "patch"];
|
|
1617
1828
|
var verbatimFieldEqual = (f, e, field) => field === "patch" ? (f.patch ?? null) === e.patch : f[field] === e[field];
|
|
1618
|
-
var isVerbatimReRaise = (
|
|
1619
|
-
var
|
|
1829
|
+
var isVerbatimReRaise = (item, fields, fieldEqual) => !hasRebuttal(item) && fields.every(fieldEqual);
|
|
1830
|
+
var isVerbatimNonCritical = (item, fields, fieldEqual) => isVerbatimReRaise(item, fields, fieldEqual) && item.severity !== "critical";
|
|
1831
|
+
var isAnsweredDrop = (resolvedId, f, e) => matches(resolvedId, f, e) && isVerbatimNonCritical(f, VERBATIM_FIELDS, (field) => verbatimFieldEqual(f, e, field));
|
|
1620
1832
|
var verbatimMatchCount = (f, e) => VERBATIM_FIELDS.reduce((count, field) => count + (verbatimFieldEqual(f, e, field) ? 1 : 0), 0);
|
|
1621
1833
|
var answeredNoteKey = (f) => f.id !== "" ? f.id : `title:${f.title}`;
|
|
1622
|
-
var NO_TITLE_MATCH = { entry: void 0, score: -1 };
|
|
1623
1834
|
var bestTitleMatch = (f, registry) => {
|
|
1624
1835
|
let best;
|
|
1625
1836
|
let bestScore = -1;
|
|
@@ -1632,9 +1843,10 @@ var bestTitleMatch = (f, registry) => {
|
|
|
1632
1843
|
if (score === VERBATIM_FIELDS.length) break;
|
|
1633
1844
|
}
|
|
1634
1845
|
}
|
|
1635
|
-
return
|
|
1846
|
+
return best;
|
|
1636
1847
|
};
|
|
1637
|
-
var
|
|
1848
|
+
var byAuthor = (e) => e.answerAuthor === null ? "" : ` by \`${escapeCodeBackticks(e.answerAuthor)}\``;
|
|
1849
|
+
var answeredNote = (e) => `Re-raised; prior answer at ${linkSafeUrl(e.answerUrl)}${byAuthor(e)} \u2014 cite the new evidence that invalidates it.`;
|
|
1638
1850
|
var applyAnswered = (findings, registry) => {
|
|
1639
1851
|
const kept = [];
|
|
1640
1852
|
const noteEntries = [];
|
|
@@ -1644,15 +1856,15 @@ var applyAnswered = (findings, registry) => {
|
|
|
1644
1856
|
for (const f of findings) {
|
|
1645
1857
|
const resolvedId = resolveFindingId(f);
|
|
1646
1858
|
const idMatch = registry.find((e) => e.code === resolvedId);
|
|
1647
|
-
const
|
|
1859
|
+
const titleMatch = idMatch === void 0 ? bestTitleMatch(f, registry) : void 0;
|
|
1648
1860
|
const entry = idMatch ?? titleMatch;
|
|
1649
1861
|
if (entry === void 0) {
|
|
1650
1862
|
kept.push(f);
|
|
1651
1863
|
continue;
|
|
1652
1864
|
}
|
|
1653
|
-
const dropped = (
|
|
1865
|
+
const dropped = isAnsweredDrop(resolvedId, f, entry);
|
|
1654
1866
|
if (dropped) {
|
|
1655
|
-
droppedByEntry.set(entry.
|
|
1867
|
+
droppedByEntry.set(entry.code, entry);
|
|
1656
1868
|
droppedFindingIds.push(resolvedId);
|
|
1657
1869
|
droppedCount += 1;
|
|
1658
1870
|
} else {
|
|
@@ -1668,46 +1880,64 @@ var applyAnswered = (findings, registry) => {
|
|
|
1668
1880
|
droppedCount
|
|
1669
1881
|
};
|
|
1670
1882
|
};
|
|
1671
|
-
var
|
|
1672
|
-
|
|
1673
|
-
const
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1883
|
+
var answeredSystemicNoteKey = (s) => `systemic:${resolveSystemicId(s) ?? ""}`;
|
|
1884
|
+
var systemicVerdict = (systemic, registry) => {
|
|
1885
|
+
const resolvedId = resolveSystemicId(systemic);
|
|
1886
|
+
const entry = registry.find((e) => e.code === resolvedId);
|
|
1887
|
+
return entry === void 0 ? { kind: "unanswered", systemic } : {
|
|
1888
|
+
kind: isVerbatimNonCritical(
|
|
1889
|
+
systemic,
|
|
1890
|
+
CLAIM_FIELDS,
|
|
1891
|
+
(field) => systemic[field] === entry[field]
|
|
1892
|
+
) ? "dropped" : "annotated",
|
|
1893
|
+
systemic,
|
|
1894
|
+
entry
|
|
1895
|
+
};
|
|
1681
1896
|
};
|
|
1682
|
-
var
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
"
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1897
|
+
var applyAnsweredSystemic = (systemic, registry) => {
|
|
1898
|
+
const verdicts = systemic.map((s) => systemicVerdict(s, registry));
|
|
1899
|
+
const dropped = verdicts.flatMap((v) => v.kind === "dropped" ? [v.entry] : []);
|
|
1900
|
+
return {
|
|
1901
|
+
systemic: verdicts.flatMap((v) => v.kind === "dropped" ? [] : [v.systemic]),
|
|
1902
|
+
reRaisedNotes: Object.fromEntries(
|
|
1903
|
+
verdicts.flatMap(
|
|
1904
|
+
(v) => v.kind === "annotated" ? [[answeredSystemicNoteKey(v.systemic), answeredNote(v.entry)]] : []
|
|
1905
|
+
)
|
|
1906
|
+
),
|
|
1907
|
+
verbatimReRaised: [...new Map(dropped.map((e) => [e.code, e])).values()],
|
|
1908
|
+
droppedCount: dropped.length
|
|
1909
|
+
};
|
|
1910
|
+
};
|
|
1911
|
+
var applyOverruled = (items, rulings, idOf, noteKey) => {
|
|
1912
|
+
const ruled = items.map((item) => ({ item, ruling: rulings.get(idOf(item) ?? "") }));
|
|
1913
|
+
const drops = (entry) => entry.ruling !== void 0 && entry.item.severity !== "critical";
|
|
1914
|
+
const dropped = ruled.filter(drops).flatMap(({ ruling }) => ruling === void 0 ? [] : [ruling]);
|
|
1915
|
+
return {
|
|
1916
|
+
kept: ruled.filter((entry) => !drops(entry)).map(({ item }) => item),
|
|
1917
|
+
reRaisedNotes: Object.fromEntries(
|
|
1918
|
+
ruled.flatMap(
|
|
1919
|
+
({ item, ruling }) => ruling !== void 0 && !drops({ item, ruling }) ? [[noteKey(item), answeredNote(ruling)]] : []
|
|
1920
|
+
)
|
|
1921
|
+
),
|
|
1922
|
+
verbatimReRaised: [...new Map(dropped.map((ruling) => [ruling.code, ruling])).values()],
|
|
1923
|
+
droppedCount: dropped.length
|
|
1924
|
+
};
|
|
1705
1925
|
};
|
|
1926
|
+
var closingAnswerLines = (entries, linkText) => entries.map((e) => {
|
|
1927
|
+
const label = e.code !== "" ? `\`${escapeCodeBackticks(e.code)}\`` : `\u201C${escapeCodeBackticks(e.title)}\u201D`;
|
|
1928
|
+
return `> - ${label} \u2014 [${linkText}](${linkSafeUrl(e.answerUrl)})${byAuthor(e)}`;
|
|
1929
|
+
});
|
|
1930
|
+
var answeredReRaiseNote = (entries, count) => entries.length === 0 ? "" : [
|
|
1931
|
+
`> \u21A9\uFE0F **${String(count)} re-raise(s) without new evidence \u2014 treated as answered** (a maintainer's answer closed each):`,
|
|
1932
|
+
...closingAnswerLines(entries, "prior answer")
|
|
1933
|
+
].join("\n");
|
|
1934
|
+
var overruledReRaiseNote = (entries, count) => entries.length === 0 ? "" : [
|
|
1935
|
+
`> \u2696\uFE0F **${String(count)} re-raise(s) of an id a maintainer overruled \u2014 closed for good**:`,
|
|
1936
|
+
...closingAnswerLines(entries, "the ruling")
|
|
1937
|
+
].join("\n");
|
|
1706
1938
|
|
|
1707
1939
|
// src/render.ts
|
|
1708
1940
|
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
1941
|
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
1942
|
var terminalNotice = (i) => [
|
|
1713
1943
|
`## ${i.verdictBadge}`,
|
|
@@ -1798,7 +2028,7 @@ var sanitizeFinding = (f, answeredNotes, discussionByFinding, permalinkBase, una
|
|
|
1798
2028
|
rangeLabel: lineRange(f.start_line, f.end_line, "\u2013"),
|
|
1799
2029
|
...permalink !== void 0 ? { permalink, permalinkAnchored: anchored } : {},
|
|
1800
2030
|
patchProjection: projectPatch(f.patch, "comment-body"),
|
|
1801
|
-
answeredNote:
|
|
2031
|
+
answeredNote: noteFor(answeredNotes, key2),
|
|
1802
2032
|
discussion: discussionByFinding !== void 0 && Object.prototype.hasOwnProperty.call(discussionByFinding, f.id) ? discussionByFinding[f.id] ?? [] : [],
|
|
1803
2033
|
...discussionTruncated !== void 0 && Object.prototype.hasOwnProperty.call(discussionTruncated, f.id) ? { discussionTruncated: discussionTruncated[f.id] } : {}
|
|
1804
2034
|
};
|
|
@@ -1824,12 +2054,14 @@ var sanitizeSuppressedNit = (f, discussion, discussionTruncated) => ({
|
|
|
1824
2054
|
var carriedLines = (f) => [
|
|
1825
2055
|
`description: ${clipText(f.description, BODY_CLIP_CHARS)}`,
|
|
1826
2056
|
...f.recommendation !== void 0 ? [`recommendation: ${clipText(f.recommendation, BODY_CLIP_CHARS)}`] : [],
|
|
2057
|
+
...hasRebuttal(f) ? [`rebuttal: ${clipText(f.rebuttal ?? "", BODY_CLIP_CHARS)}`] : [],
|
|
1827
2058
|
`reasoning: ${clipText(f.reasoning, BODY_CLIP_CHARS)}`,
|
|
1828
2059
|
...f.patch !== void 0 ? ["patch:", clipText(f.patch, BODY_CLIP_CHARS)] : []
|
|
1829
2060
|
].flatMap((block) => commentSafe(block).split("\n")).map((line) => line.trimEnd());
|
|
1830
|
-
var sanitizeSystemic = (s, discussion, discussionTruncated) => ({
|
|
2061
|
+
var sanitizeSystemic = (s, answeredNotes, discussion, discussionTruncated) => ({
|
|
1831
2062
|
...s,
|
|
1832
2063
|
title: escapePipes(s.title),
|
|
2064
|
+
answeredNote: noteFor(answeredNotes, answeredSystemicNoteKey(s)),
|
|
1833
2065
|
discussion,
|
|
1834
2066
|
...discussionTruncated !== void 0 ? { discussionTruncated } : {},
|
|
1835
2067
|
discussionHtml: discussion.length > 0 ? discussionAsideHtml(discussion, discussionTruncated, "") : "",
|
|
@@ -1881,6 +2113,7 @@ var render = (input) => {
|
|
|
1881
2113
|
const trajectory = convergence?.rounds ?? input.rounds ?? [];
|
|
1882
2114
|
const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(trajectory.slice(0, -1), input.findings.findings);
|
|
1883
2115
|
const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && trajectory.length > 0)) && isReviewVerdict(input.findings.verdict);
|
|
2116
|
+
const contestedSuffix = (input.contested ?? []).length > 0 ? ` \xB7 ${String((input.contested ?? []).length)} contested` : "";
|
|
1884
2117
|
const advisoryAllowed = isFullReviewRound;
|
|
1885
2118
|
const discussionLinksFor = (id) => input.discussionByFinding !== void 0 && Object.prototype.hasOwnProperty.call(input.discussionByFinding, id) ? input.discussionByFinding[id] ?? [] : [];
|
|
1886
2119
|
const discussionTruncatedFor = (id) => input.discussionTruncated !== void 0 && Object.prototype.hasOwnProperty.call(input.discussionTruncated, id) ? input.discussionTruncated[id] : void 0;
|
|
@@ -1960,6 +2193,7 @@ var render = (input) => {
|
|
|
1960
2193
|
(input.findings.systemic_problems ?? []).map(
|
|
1961
2194
|
(s) => sanitizeSystemic(
|
|
1962
2195
|
s,
|
|
2196
|
+
input.answeredNotes,
|
|
1963
2197
|
s.id !== void 0 ? discussionLinksFor(s.id) : [],
|
|
1964
2198
|
s.id !== void 0 ? discussionTruncatedFor(s.id) : void 0
|
|
1965
2199
|
)
|
|
@@ -1968,6 +2202,23 @@ var render = (input) => {
|
|
|
1968
2202
|
(s) => s.discussion.length > 0 ? DISCUSSION_BLOCK_OVERHEAD + s.discussionHtml.length : 0,
|
|
1969
2203
|
(s) => ({ ...s, discussion: [], discussionHtml: "" })
|
|
1970
2204
|
);
|
|
2205
|
+
const contestedBudget = budgetBySize(
|
|
2206
|
+
(input.contested ?? []).map((view) => {
|
|
2207
|
+
const discussion = discussionLinksFor(view.id);
|
|
2208
|
+
return {
|
|
2209
|
+
...view,
|
|
2210
|
+
...view.reRaise?.location !== void 0 ? { reRaise: { ...view.reRaise, location: escapeCodeBackticks(view.reRaise.location) } } : {},
|
|
2211
|
+
id: escapeCodeBackticks(view.id),
|
|
2212
|
+
title: escapePipes(view.title),
|
|
2213
|
+
answerUrl: linkSafeUrl(view.answerUrl),
|
|
2214
|
+
discussion,
|
|
2215
|
+
discussionHtml: discussion.length > 0 ? discussionAsideHtml(discussion, discussionTruncatedFor(view.id), "") : ""
|
|
2216
|
+
};
|
|
2217
|
+
}),
|
|
2218
|
+
DISCUSSION_TOTAL_CHARS - orphanedBudget.used - discussionBudget.used - systemicBudget.used,
|
|
2219
|
+
(view) => view.discussion.length > 0 ? DISCUSSION_BLOCK_OVERHEAD + view.discussionHtml.length : 0,
|
|
2220
|
+
(view) => ({ ...view, discussion: [], discussionHtml: "" })
|
|
2221
|
+
);
|
|
1971
2222
|
const orphanedRender = {
|
|
1972
2223
|
lines: orphanedBudget.kept.map(([, entry]) => entry.html),
|
|
1973
2224
|
// The "(showing N of M)" total counts DISTINCT DISPLAYED entries — the pre-budget fold above
|
|
@@ -1992,6 +2243,10 @@ var render = (input) => {
|
|
|
1992
2243
|
input.findingsPointer ?? findingsMarkerPair(input.jsonUrl, input.findings.convergence)
|
|
1993
2244
|
),
|
|
1994
2245
|
summaryAvailable: input.summaryAvailable ?? false,
|
|
2246
|
+
responseForm: RESPONSE_FORM,
|
|
2247
|
+
responseTeaching: RESPONSE_TEACHING,
|
|
2248
|
+
rulingTeaching: RULING_TEACHING,
|
|
2249
|
+
teachesResponses: isConvergenceRound(route, incomplete),
|
|
1995
2250
|
shedNote: shedNote(
|
|
1996
2251
|
input.shedCount ?? 0,
|
|
1997
2252
|
input.summaryAvailable ?? false,
|
|
@@ -2007,7 +2262,7 @@ var render = (input) => {
|
|
|
2007
2262
|
route: route ?? void 0,
|
|
2008
2263
|
postedAt: input.postedAt ?? "",
|
|
2009
2264
|
roundsSummary: roundsSummary(trajectory, input.roundCount),
|
|
2010
|
-
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : "",
|
|
2265
|
+
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) + contestedSuffix : "",
|
|
2011
2266
|
inlinePosted: input.inlineDisposition?.kind === "posted" ? input.inlineDisposition.count : 0,
|
|
2012
2267
|
runUrl: input.runUrl,
|
|
2013
2268
|
summaryAvailable: input.summaryAvailable ?? false,
|
|
@@ -2034,15 +2289,21 @@ var render = (input) => {
|
|
|
2034
2289
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
2035
2290
|
postedAt: input.postedAt ?? "",
|
|
2036
2291
|
severityCounts,
|
|
2037
|
-
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold),
|
|
2292
|
+
convergenceSummary: !isFullReviewRound ? "" : (convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold)) + contestedSuffix,
|
|
2038
2293
|
strays: discussionBudget.kept,
|
|
2039
2294
|
orphanedLines: orphanedRender.lines,
|
|
2040
2295
|
orphanedUnresolvable: input.orphanedUnresolvable === true,
|
|
2041
|
-
discussionDropped: discussionBudget.droppedItems.length + systemicBudget.droppedItems.length + orphanedBudget.droppedItems.length,
|
|
2042
|
-
discussionDroppedIds:
|
|
2296
|
+
discussionDropped: discussionBudget.droppedItems.length + systemicBudget.droppedItems.length + contestedBudget.droppedItems.length + orphanedBudget.droppedItems.length,
|
|
2297
|
+
discussionDroppedIds: [
|
|
2298
|
+
...discussionBudget.droppedItems.map((v) => clipText(escapeCodeBackticks(v.idKey), 64)),
|
|
2299
|
+
...contestedBudget.droppedItems.map((v) => clipText(v.id, 64))
|
|
2300
|
+
].slice(0, DROPPED_IDS_SHOWN),
|
|
2043
2301
|
discussionDroppedSystemicIds: systemicBudget.droppedItems.flatMap((s) => s.id !== void 0 ? [clipText(s.id, 64)] : []).slice(0, DROPPED_IDS_SHOWN),
|
|
2044
2302
|
discussionDroppedOrphanedIds: orphanedBudget.droppedItems.map(([token]) => clipText(token, 64)).slice(0, DROPPED_IDS_SHOWN),
|
|
2045
|
-
discussionDroppedExtra: Math.max(
|
|
2303
|
+
discussionDroppedExtra: Math.max(
|
|
2304
|
+
0,
|
|
2305
|
+
discussionBudget.droppedItems.length + contestedBudget.droppedItems.length - DROPPED_IDS_SHOWN
|
|
2306
|
+
) + Math.max(0, systemicBudget.droppedItems.length - DROPPED_IDS_SHOWN) + Math.max(0, orphanedBudget.droppedItems.length - DROPPED_IDS_SHOWN),
|
|
2046
2307
|
discussionCap: PER_FINDING_LINKS,
|
|
2047
2308
|
orphanedTotal: orphanedRender.total,
|
|
2048
2309
|
suppressedNits: suppressedBudget.kept,
|
|
@@ -2064,6 +2325,7 @@ var render = (input) => {
|
|
|
2064
2325
|
sameRootNotes: advisoryAllowed ? sameRootNotes : {},
|
|
2065
2326
|
answeredNotes: input.answeredNotes ?? {},
|
|
2066
2327
|
answeredReRaiseNote: input.answeredReRaiseNote ?? "",
|
|
2328
|
+
contested: contestedBudget.kept,
|
|
2067
2329
|
reviewUrl: input.reviewUrl ?? null,
|
|
2068
2330
|
formatTokens: (n) => Number.isFinite(n) && n >= 0 ? n.toLocaleString("en-US") : "\u2014",
|
|
2069
2331
|
// N/A (never a false $0.00) when no real price map was provided — real tokens, no rates to
|
|
@@ -2083,7 +2345,8 @@ var render = (input) => {
|
|
|
2083
2345
|
},
|
|
2084
2346
|
verdictBadge,
|
|
2085
2347
|
severityEmoji,
|
|
2086
|
-
formatConfidence
|
|
2348
|
+
formatConfidence,
|
|
2349
|
+
hasRebuttal
|
|
2087
2350
|
});
|
|
2088
2351
|
};
|
|
2089
2352
|
var key = (path, line) => `${path}:${String(line)}`;
|
|
@@ -2146,9 +2409,11 @@ var renderCommentBody = (f, eta, template, modelsText, jsonUrl, pointer, sameRoo
|
|
|
2146
2409
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
2147
2410
|
eta.renderString(template, {
|
|
2148
2411
|
...f,
|
|
2412
|
+
idLabel: escapeCodeBackticks(f.id),
|
|
2149
2413
|
patchProjection: projectPatch(f.patch, "diff-anchored"),
|
|
2150
2414
|
severityEmoji,
|
|
2151
2415
|
formatConfidence,
|
|
2416
|
+
hasRebuttal,
|
|
2152
2417
|
modelsText,
|
|
2153
2418
|
jsonUrl: jsonUrl ?? null,
|
|
2154
2419
|
findingsPointer: pointer,
|
|
@@ -2161,7 +2426,6 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
2161
2426
|
const { inDiff, strays } = context.partition ?? partitionFindings(findings, indexDiff(diff));
|
|
2162
2427
|
const eta = new Eta({ autoTrim: false });
|
|
2163
2428
|
const modelsText = formatModels(models);
|
|
2164
|
-
const noteFor = (notes, key2) => notes !== void 0 && Object.prototype.hasOwnProperty.call(notes, key2) ? notes[key2] ?? "" : "";
|
|
2165
2429
|
const comments = inDiff.map((f) => {
|
|
2166
2430
|
const pointer = fullFindings ? findingPointer(f, fullFindings.schema_version, jsonUrl) : "";
|
|
2167
2431
|
const clipProse = fullFindings !== void 0 && findingPayload(f, fullFindings.schema_version).length > INLINE_PROSE_CLIP_THRESHOLD_CHARS;
|
|
@@ -2170,6 +2434,7 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
2170
2434
|
title: clipText(f.title, BODY_CLIP_CHARS),
|
|
2171
2435
|
description: clipText(f.description, BODY_CLIP_CHARS),
|
|
2172
2436
|
...f.recommendation != null ? { recommendation: clipText(f.recommendation, BODY_CLIP_CHARS) } : {},
|
|
2437
|
+
...hasRebuttal(f) ? { rebuttal: clipText(f.rebuttal ?? "", BODY_CLIP_CHARS) } : {},
|
|
2173
2438
|
reasoning: clipText(f.reasoning, BODY_CLIP_CHARS),
|
|
2174
2439
|
...f.patch != null ? { patch: clipText(f.patch, BODY_CLIP_CHARS) } : {}
|
|
2175
2440
|
} : f;
|
|
@@ -2204,7 +2469,7 @@ var buildInlineComments = (findings, diff, context) => {
|
|
|
2204
2469
|
var renderStraysSection = (strays) => {
|
|
2205
2470
|
if (strays.length === 0) return "";
|
|
2206
2471
|
const items = strays.map(
|
|
2207
|
-
(f) => `- **${f.severity}** \xB7 \`${f.path}:${String(f.start_line)}\` \u2014 ${f.title}`
|
|
2472
|
+
(f) => `- **${f.severity}** \xB7 \`${f.path}:${String(f.start_line)}\` \u2014 ${f.title}${f.id === "" ? "" : ` \xB7 id \`${escapeCodeBackticks(f.id)}\``}`
|
|
2208
2473
|
);
|
|
2209
2474
|
return [
|
|
2210
2475
|
"",
|
|
@@ -2215,6 +2480,110 @@ var renderStraysSection = (strays) => {
|
|
|
2215
2480
|
...items
|
|
2216
2481
|
].join("\n");
|
|
2217
2482
|
};
|
|
2483
|
+
var numField = (rec, key2) => {
|
|
2484
|
+
const v = rec[key2];
|
|
2485
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
2486
|
+
};
|
|
2487
|
+
var messageUsage = (entry) => {
|
|
2488
|
+
const rec = asRecord(entry);
|
|
2489
|
+
if (rec === null || rec["type"] !== "assistant") return null;
|
|
2490
|
+
const msg = asRecord(rec["message"]);
|
|
2491
|
+
if (msg === null) return null;
|
|
2492
|
+
const model = msg["model"];
|
|
2493
|
+
const usage = asRecord(msg["usage"]);
|
|
2494
|
+
if (typeof model !== "string" || usage === null) return null;
|
|
2495
|
+
const id = msg["id"];
|
|
2496
|
+
return {
|
|
2497
|
+
id: typeof id === "string" ? id : null,
|
|
2498
|
+
model: modelIdentity(model),
|
|
2499
|
+
input: numField(usage, "input_tokens"),
|
|
2500
|
+
output: numField(usage, "output_tokens"),
|
|
2501
|
+
cacheRead: numField(usage, "cache_read_input_tokens"),
|
|
2502
|
+
cacheWrite: numField(usage, "cache_creation_input_tokens")
|
|
2503
|
+
};
|
|
2504
|
+
};
|
|
2505
|
+
var tsMsOf = (entry) => {
|
|
2506
|
+
const rec = asRecord(entry);
|
|
2507
|
+
const ts = rec?.["timestamp"];
|
|
2508
|
+
if (typeof ts !== "string") return null;
|
|
2509
|
+
const ms = Date.parse(ts);
|
|
2510
|
+
return Number.isNaN(ms) ? null : ms;
|
|
2511
|
+
};
|
|
2512
|
+
var EMPTY_TOTALS = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2513
|
+
var parseJsonl = (text) => text.split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
2514
|
+
try {
|
|
2515
|
+
return [JSON.parse(line)];
|
|
2516
|
+
} catch {
|
|
2517
|
+
return [];
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
var sumTranscriptUsage = (entries) => {
|
|
2521
|
+
const summed = entries.reduce(
|
|
2522
|
+
(acc, entry) => {
|
|
2523
|
+
const u = messageUsage(entry);
|
|
2524
|
+
if (u === null) return acc;
|
|
2525
|
+
if (u.id !== null && acc.seen.has(u.id)) return acc;
|
|
2526
|
+
if (u.id !== null) acc.seen.add(u.id);
|
|
2527
|
+
const prev = acc.totals.get(u.model) ?? EMPTY_TOTALS;
|
|
2528
|
+
acc.totals.set(u.model, {
|
|
2529
|
+
input: prev.input + u.input,
|
|
2530
|
+
output: prev.output + u.output,
|
|
2531
|
+
cacheRead: prev.cacheRead + u.cacheRead,
|
|
2532
|
+
cacheWrite: prev.cacheWrite + u.cacheWrite
|
|
2533
|
+
});
|
|
2534
|
+
return { totals: acc.totals, turns: acc.turns + 1, seen: acc.seen };
|
|
2535
|
+
},
|
|
2536
|
+
{ totals: /* @__PURE__ */ new Map(), turns: 0, seen: /* @__PURE__ */ new Set() }
|
|
2537
|
+
);
|
|
2538
|
+
const models = [...summed.totals].map(([model, t10]) => ({
|
|
2539
|
+
model,
|
|
2540
|
+
input_tokens: t10.input,
|
|
2541
|
+
output_tokens: t10.output,
|
|
2542
|
+
cache_read_tokens: t10.cacheRead,
|
|
2543
|
+
cache_write_tokens: t10.cacheWrite
|
|
2544
|
+
}));
|
|
2545
|
+
const bounds = entries.reduce(
|
|
2546
|
+
(acc, entry) => {
|
|
2547
|
+
const ms = tsMsOf(entry);
|
|
2548
|
+
if (ms === null) return acc;
|
|
2549
|
+
return {
|
|
2550
|
+
min: acc.min === null || ms < acc.min ? ms : acc.min,
|
|
2551
|
+
max: acc.max === null || ms > acc.max ? ms : acc.max
|
|
2552
|
+
};
|
|
2553
|
+
},
|
|
2554
|
+
{ min: null, max: null }
|
|
2555
|
+
);
|
|
2556
|
+
return {
|
|
2557
|
+
models,
|
|
2558
|
+
turns: summed.turns,
|
|
2559
|
+
durationMs: bounds.min !== null && bounds.max !== null ? bounds.max - bounds.min : 0,
|
|
2560
|
+
firstTsMs: bounds.min,
|
|
2561
|
+
lastTsMs: bounds.max
|
|
2562
|
+
};
|
|
2563
|
+
};
|
|
2564
|
+
var subagentFiles = (mainPath) => {
|
|
2565
|
+
const dir = join(dirname(mainPath), basename(mainPath, ".jsonl"), "subagents");
|
|
2566
|
+
try {
|
|
2567
|
+
return readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => join(dir, name));
|
|
2568
|
+
} catch {
|
|
2569
|
+
return [];
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
var readTranscriptTree = (mainPath) => {
|
|
2573
|
+
const mainText = readFileOrNull(mainPath);
|
|
2574
|
+
const mainEntries = mainText === null ? [] : parseJsonl(mainText);
|
|
2575
|
+
const inlineSidechains = mainEntries.some((e) => asRecord(e)?.["isSidechain"] === true);
|
|
2576
|
+
const siblings = inlineSidechains ? [] : subagentFiles(mainPath);
|
|
2577
|
+
const siblingReads = siblings.flatMap((path) => {
|
|
2578
|
+
const text = readFileOrNull(path);
|
|
2579
|
+
return text === null ? [] : [{ path, entries: parseJsonl(text) }];
|
|
2580
|
+
});
|
|
2581
|
+
return {
|
|
2582
|
+
entries: [...mainEntries, ...siblingReads.flatMap((r) => r.entries)],
|
|
2583
|
+
files: [...mainText === null ? [] : [mainPath], ...siblingReads.map((r) => r.path)],
|
|
2584
|
+
missing: mainText === null
|
|
2585
|
+
};
|
|
2586
|
+
};
|
|
2218
2587
|
var addFormats = _addFormats;
|
|
2219
2588
|
var validatorCache = /* @__PURE__ */ new Map();
|
|
2220
2589
|
var compileSchema = (schemaPath) => {
|
|
@@ -2396,7 +2765,7 @@ var writesToDraft = (toolName, toolInput, draftPath) => {
|
|
|
2396
2765
|
const targets = [draftPath, basename(draftPath), "$DRAFT", "${DRAFT}"];
|
|
2397
2766
|
if (WRITE_TOOLS.has(toolName)) {
|
|
2398
2767
|
const fp = rec?.["file_path"] ?? rec?.["notebook_path"];
|
|
2399
|
-
if (typeof fp === "string" && targets.some((
|
|
2768
|
+
if (typeof fp === "string" && targets.some((t10) => fp === t10 || basename(fp) === basename(t10)))
|
|
2400
2769
|
return true;
|
|
2401
2770
|
}
|
|
2402
2771
|
if (toolName === "Bash") {
|
|
@@ -2423,7 +2792,6 @@ var sidecarPath = (draftPath, postfix) => {
|
|
|
2423
2792
|
return join(dirname(draftPath), `${basename(draftPath, ext)}${postfix}${ext}`);
|
|
2424
2793
|
};
|
|
2425
2794
|
var priorContextPath = (draftPath) => sidecarPath(draftPath, ".prior");
|
|
2426
|
-
var priorAnswersPath = (draftPath) => sidecarPath(draftPath, ".prior-answers");
|
|
2427
2795
|
var priorSuppressedPath = (draftPath) => sidecarPath(draftPath, ".prior-suppressed");
|
|
2428
2796
|
var lastValidPath = (draftPath) => sidecarPath(draftPath, ".last-valid");
|
|
2429
2797
|
var costAxisDisengagedPath = (draftPath) => sidecarPath(draftPath, ".cost-axis-disengaged");
|
|
@@ -2502,9 +2870,9 @@ var parseWallMs = (raw) => {
|
|
|
2502
2870
|
};
|
|
2503
2871
|
var parseEpochSecMs = (raw) => {
|
|
2504
2872
|
if (raw === void 0) return null;
|
|
2505
|
-
const
|
|
2506
|
-
if (!/^\d+$/.test(
|
|
2507
|
-
const n = Number.parseInt(
|
|
2873
|
+
const t10 = raw.trim();
|
|
2874
|
+
if (!/^\d+$/.test(t10)) return null;
|
|
2875
|
+
const n = Number.parseInt(t10, 10);
|
|
2508
2876
|
return Number.isFinite(n) && n > 0 ? n * 1e3 : null;
|
|
2509
2877
|
};
|
|
2510
2878
|
var anchoredElapsedMs = (src) => {
|
|
@@ -2625,10 +2993,12 @@ var findingsTable = [
|
|
|
2625
2993
|
{
|
|
2626
2994
|
minor: "0.4",
|
|
2627
2995
|
defaultVersion: "0.4.0",
|
|
2628
|
-
// The frozen tolerant-in legacy schema, NOT the live
|
|
2996
|
+
// The frozen tolerant-in legacy schema, NOT the live file: every ajv-gated channel
|
|
2629
2997
|
// (validate --schema-version, the extraction ladder) dispatches the RAW doc's declared minor
|
|
2630
2998
|
// through schemaPathFor, so the ajv gate must accept exactly what the tolerant legacy codec
|
|
2631
2999
|
// accepts — the live file (id required) would reject the legacy docs the upcast promises to read.
|
|
3000
|
+
// One deliberate exception: the codec drops a draft-only `rebuttal` that the frozen file rejects.
|
|
3001
|
+
// A pre-0.10 document never carried one, so only a hybrid doc the rescue exists for meets it.
|
|
2632
3002
|
schemaFile: "v0.9/findings.schema.json",
|
|
2633
3003
|
codec: legacyFindingsCodec,
|
|
2634
3004
|
normalize: legacyFindingsNormalize,
|
|
@@ -2660,6 +3030,14 @@ var findingsTable = [
|
|
|
2660
3030
|
},
|
|
2661
3031
|
{
|
|
2662
3032
|
minor: "0.10",
|
|
3033
|
+
defaultVersion: "0.10.0",
|
|
3034
|
+
schemaFile: "findings.schema.json",
|
|
3035
|
+
codec: FindingsCodec,
|
|
3036
|
+
normalize: identity,
|
|
3037
|
+
latest: false
|
|
3038
|
+
},
|
|
3039
|
+
{
|
|
3040
|
+
minor: "0.11",
|
|
2663
3041
|
defaultVersion: DEFAULT_SCHEMA_VERSION,
|
|
2664
3042
|
schemaFile: "findings.schema.json",
|
|
2665
3043
|
codec: FindingsCodec,
|
|
@@ -2714,21 +3092,28 @@ var describeValidationError = (e) => {
|
|
|
2714
3092
|
var formatErrors = (errors) => errors.map(describeValidationError);
|
|
2715
3093
|
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
3094
|
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
3095
|
var bundledSchemaPath = (relativePath) => resolve(import.meta.dirname, "..", "schema", relativePath);
|
|
2723
|
-
var
|
|
3096
|
+
var entryFor = (kind, version) => {
|
|
2724
3097
|
const table = tableFor(kind);
|
|
2725
3098
|
const entry = version === void 0 ? table.find((v) => v.latest) : table.find((v) => v.minor === majorMinor(version));
|
|
3099
|
+
if (!entry && version === void 0) {
|
|
3100
|
+
throw new Error(`Registry invariant violated \u2014 no latest entry for "${kind}"`);
|
|
3101
|
+
}
|
|
2726
3102
|
if (!entry) {
|
|
2727
3103
|
throw new Error(
|
|
2728
3104
|
`Unsupported ${kind} schema version "${version ?? ""}" \u2014 supported: ${supportedVersions(kind).join(", ")}`
|
|
2729
3105
|
);
|
|
2730
3106
|
}
|
|
2731
|
-
return
|
|
3107
|
+
return entry;
|
|
3108
|
+
};
|
|
3109
|
+
var schemaPathFor = (kind, version) => bundledSchemaPath(entryFor(kind, version).schemaFile);
|
|
3110
|
+
var versionFor = (kind, version) => entryFor(kind, version).defaultVersion;
|
|
3111
|
+
var defaultVersion = (kind) => versionFor(kind);
|
|
3112
|
+
var livePinFor = (kind, version) => {
|
|
3113
|
+
const table = tableFor(kind);
|
|
3114
|
+
const latest = table.find((v) => v.latest);
|
|
3115
|
+
const entry = version === void 0 ? latest : table.find((v) => v.minor === majorMinor(version));
|
|
3116
|
+
return entry !== void 0 && entry.schemaFile === latest?.schemaFile ? entry.defaultVersion : void 0;
|
|
2732
3117
|
};
|
|
2733
3118
|
var resolveFindings = (raw) => {
|
|
2734
3119
|
const version = declaredVersion(raw);
|
|
@@ -2762,7 +3147,7 @@ var resolveTolerantFindings = (doc) => {
|
|
|
2762
3147
|
};
|
|
2763
3148
|
|
|
2764
3149
|
// src/pr.ts
|
|
2765
|
-
var CANDIDATE_JQ = ".[] | {number: .number, state: .state, headRef: .head.ref, headSha: .head.sha}";
|
|
3150
|
+
var CANDIDATE_JQ = ".[] | {number: .number, state: .state, headRef: .head.ref, headSha: .head.sha, headRepo: (.head.repo.full_name // null)}";
|
|
2766
3151
|
var parseCandidates = (stdout) => parseJsonl(stdout);
|
|
2767
3152
|
var fetchDirectCandidates = async (repo, headSha, ghApi) => {
|
|
2768
3153
|
try {
|
|
@@ -2791,7 +3176,7 @@ var resolvePr = (candidates, headBranch) => {
|
|
|
2791
3176
|
const scoped = candidates.length > 1 && headBranch ? candidates.filter((c) => c.headRef === headBranch) : candidates;
|
|
2792
3177
|
const chosen = scoped.find((c) => c.state === "open") ?? scoped[0] ?? candidates[0];
|
|
2793
3178
|
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 };
|
|
3179
|
+
return chosen.state === "open" ? { kind: "open", prNumber: chosen.number, headRepo: chosen.headRepo ?? null } : { kind: "not-open", prNumber: chosen.number, state: chosen.state };
|
|
2795
3180
|
};
|
|
2796
3181
|
var fetchDiff = async (repo, prNumber, ghApi) => ghApi([
|
|
2797
3182
|
`repos/${repo}/pulls/${String(prNumber)}`,
|
|
@@ -3095,7 +3480,8 @@ var postInlineReview = async (pr, comments, inDiff, ghApi) => {
|
|
|
3095
3480
|
return { url, posted, inlinePosted, unposted };
|
|
3096
3481
|
}
|
|
3097
3482
|
};
|
|
3098
|
-
var ISSUE_COMMENTS_JQ = '.[] | {id, user: (.user.login // "(deleted)"), created_at, html_url, body: (.body // "")}';
|
|
3483
|
+
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 // "")}';
|
|
3484
|
+
var nullableString = (value) => typeof value === "string" ? value : null;
|
|
3099
3485
|
var parseIssueCommentRows = (raw) => {
|
|
3100
3486
|
let malformed = 0;
|
|
3101
3487
|
const rows = [];
|
|
@@ -3114,7 +3500,15 @@ var parseIssueCommentRows = (raw) => {
|
|
|
3114
3500
|
const url = rec?.["html_url"];
|
|
3115
3501
|
const body = rec?.["body"];
|
|
3116
3502
|
if (typeof id === "number" && typeof author === "string" && typeof created === "string" && typeof url === "string" && typeof body === "string") {
|
|
3117
|
-
rows.push({
|
|
3503
|
+
rows.push({
|
|
3504
|
+
id,
|
|
3505
|
+
author,
|
|
3506
|
+
created,
|
|
3507
|
+
url,
|
|
3508
|
+
body,
|
|
3509
|
+
authorType: nullableString(rec?.["user_type"]),
|
|
3510
|
+
association: nullableString(rec?.["author_association"])
|
|
3511
|
+
});
|
|
3118
3512
|
} else {
|
|
3119
3513
|
malformed += 1;
|
|
3120
3514
|
}
|
|
@@ -3124,6 +3518,45 @@ var parseIssueCommentRows = (raw) => {
|
|
|
3124
3518
|
);
|
|
3125
3519
|
return { rows, malformed };
|
|
3126
3520
|
};
|
|
3521
|
+
var COMMITS_JQ = ".[] | {sha, message: .commit.message, author: (.author.login // null), date: (.commit.author.date // null)}";
|
|
3522
|
+
var fetchCommitAnswersSource = async (repo, prNumber, ghApi) => {
|
|
3523
|
+
try {
|
|
3524
|
+
const raw = await ghApi([
|
|
3525
|
+
`repos/${repo}/pulls/${String(prNumber)}/commits?per_page=100`,
|
|
3526
|
+
"--paginate",
|
|
3527
|
+
"--jq",
|
|
3528
|
+
COMMITS_JQ
|
|
3529
|
+
]);
|
|
3530
|
+
const lines = raw.split("\n").filter((line) => line.trim() !== "");
|
|
3531
|
+
const commits = lines.flatMap((line) => {
|
|
3532
|
+
const parsed = tryParseJson(line.trim());
|
|
3533
|
+
const rec = parsed.ok ? asRecord(parsed.value) : null;
|
|
3534
|
+
const sha = rec?.["sha"];
|
|
3535
|
+
const message = rec?.["message"];
|
|
3536
|
+
return typeof sha === "string" && typeof message === "string" ? [
|
|
3537
|
+
{
|
|
3538
|
+
sha,
|
|
3539
|
+
message,
|
|
3540
|
+
author: nullableString(rec?.["author"]),
|
|
3541
|
+
date: nullableString(rec?.["date"])
|
|
3542
|
+
}
|
|
3543
|
+
] : [];
|
|
3544
|
+
});
|
|
3545
|
+
if (commits.length < lines.length) {
|
|
3546
|
+
process.stderr.write(
|
|
3547
|
+
`Warning: ${String(lines.length - commits.length)} of the PR's commits did not decode \u2014 their answers close nothing
|
|
3548
|
+
`
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
return commits;
|
|
3552
|
+
} catch (err) {
|
|
3553
|
+
process.stderr.write(
|
|
3554
|
+
`Warning: could not read the PR's commits (${errMsg(err)}) \u2014 no commit answer closes a finding this round
|
|
3555
|
+
`
|
|
3556
|
+
);
|
|
3557
|
+
return [];
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
3127
3560
|
var fetchIssueCommentRows = async (repo, prNumber, ghApi) => {
|
|
3128
3561
|
const raw = await ghApi([
|
|
3129
3562
|
// per_page rides the QUERY string — a `-f` field would flip `gh api` from GET to POST
|
|
@@ -3194,7 +3627,6 @@ var discussionRows = (rows, stickyId, botLogin) => {
|
|
|
3194
3627
|
return c.id !== stickyId && c.author !== botLogin;
|
|
3195
3628
|
});
|
|
3196
3629
|
};
|
|
3197
|
-
var ID_SHAPE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
3198
3630
|
var mentionsOutsideKnown = (reachable, currentIds) => {
|
|
3199
3631
|
const known = new Set(currentIds);
|
|
3200
3632
|
const escapedToRaw = escapedIdIndex(currentIds);
|
|
@@ -3279,24 +3711,6 @@ var buildStickyDiscussion = (reachable, currentIds, priorIds) => {
|
|
|
3279
3711
|
orphanedTruncated: Object.fromEntries(orphanedTruncated)
|
|
3280
3712
|
};
|
|
3281
3713
|
};
|
|
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
3714
|
var appendBestEffort = (path, label, body) => {
|
|
3301
3715
|
if (path === void 0 || path === "") return;
|
|
3302
3716
|
try {
|
|
@@ -3391,8 +3805,8 @@ var priorBotCommentIds = (raw, botLogin) => {
|
|
|
3391
3805
|
const nodes = conn?.nodes;
|
|
3392
3806
|
if (!Array.isArray(nodes)) return { ids: [], truncated };
|
|
3393
3807
|
const logins = [botLogin.replace(/\[bot\]$/, ""), botLogin];
|
|
3394
|
-
const ids = nodes.flatMap((
|
|
3395
|
-
const cnodes =
|
|
3808
|
+
const ids = nodes.flatMap((t10) => {
|
|
3809
|
+
const cnodes = t10.comments?.nodes;
|
|
3396
3810
|
return Array.isArray(cnodes) ? cnodes.map((c) => priorBotCommentId(c, logins)).filter((id) => id !== null) : [];
|
|
3397
3811
|
});
|
|
3398
3812
|
return { ids, truncated };
|
|
@@ -3488,7 +3902,7 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
|
|
|
3488
3902
|
convergence: conv ?? void 0
|
|
3489
3903
|
});
|
|
3490
3904
|
let warnedNoJsonUrl = false;
|
|
3491
|
-
const
|
|
3905
|
+
const findingsPair = (doc) => {
|
|
3492
3906
|
if (input.jsonUrl) return findingsMarkerPair(input.jsonUrl, doc.convergence);
|
|
3493
3907
|
const carriedLink = existingSticky !== null ? findingsArtifactUrl(existingSticky.body) : null;
|
|
3494
3908
|
if (carriedLink !== null) {
|
|
@@ -3502,6 +3916,8 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
|
|
|
3502
3916
|
}
|
|
3503
3917
|
return findingsMarkerPair(void 0, doc.convergence);
|
|
3504
3918
|
};
|
|
3919
|
+
const carriedDialogue = existingSticky !== null ? carriedDialogueMarker(existingSticky.body) : "";
|
|
3920
|
+
const findingsBlob = (doc, dialogue2 = carriedDialogue) => [findingsPair(doc), dialogue2].filter((marker) => marker !== "").join("\n");
|
|
3505
3921
|
const leaveInPlace = (message) => {
|
|
3506
3922
|
process.stderr.write(
|
|
3507
3923
|
message ?? "Review did not complete and the sticky already reflects a completed review \u2014 leaving it in place\n"
|
|
@@ -3510,9 +3926,9 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
|
|
|
3510
3926
|
process.exit(0);
|
|
3511
3927
|
};
|
|
3512
3928
|
const logAnsweredDrops = () => {
|
|
3513
|
-
if (
|
|
3929
|
+
if (droppedCount > 0) {
|
|
3514
3930
|
process.stderr.write(
|
|
3515
|
-
`${String(droppedCount)} verbatim re-raise(s) of answered findings were treated as answered \u2014 the preserved sticky shows each
|
|
3931
|
+
`${String(droppedCount)} verbatim re-raise(s) of answered findings or systemic problems were treated as answered \u2014 the preserved sticky shows each one
|
|
3516
3932
|
`
|
|
3517
3933
|
);
|
|
3518
3934
|
}
|
|
@@ -3590,7 +4006,7 @@ ${dropNote}` : ""}`,
|
|
|
3590
4006
|
const carried = existingSticky !== null ? carriedFindingsMarker(existingSticky.body) : null;
|
|
3591
4007
|
if (carried === null) return findingsBlob(doc);
|
|
3592
4008
|
const provenance = existingSticky !== null ? carriedAncestryMarkers(existingSticky.body) : "";
|
|
3593
|
-
return [carriedMarkerPointer(carried, doc.convergence), provenance].filter((p) => p !== "").join("\n\n");
|
|
4009
|
+
return [carriedMarkerPointer(carried, doc.convergence), carriedDialogue, provenance].filter((p) => p !== "").join("\n\n");
|
|
3594
4010
|
};
|
|
3595
4011
|
return formatMarkdown(
|
|
3596
4012
|
render({
|
|
@@ -3637,36 +4053,179 @@ ${dropNote}` : ""}`,
|
|
|
3637
4053
|
process.exit(0);
|
|
3638
4054
|
}
|
|
3639
4055
|
const loadedFindings = findingsResult.findings;
|
|
3640
|
-
const
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
4056
|
+
const priorDialogue = existingSticky !== null && input.withoutDialogue !== true ? parseDialogueMarker(existingSticky.body) : { entries: [], undecoded: [], skipped: 0 };
|
|
4057
|
+
if (priorDialogue.skipped > 0) {
|
|
4058
|
+
process.stderr.write(
|
|
4059
|
+
`Warning: ${String(priorDialogue.skipped)} entry(ies) of the prior sticky's dialogue state did not decode \u2014 carried unread
|
|
4060
|
+
`
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
const dialogueRound = input.withoutDialogue !== true && envelope !== null && isConvergenceRound(
|
|
4064
|
+
effectiveRoute,
|
|
4065
|
+
envelope.incomplete === true || isIncompleteFindings(loadedFindings)
|
|
4066
|
+
) && isReviewVerdict(loadedFindings.verdict);
|
|
4067
|
+
const answerable = input.withoutDialogue !== true && existingSticky !== null && !priorIsMechanic && (loadedFindings.findings.length > 0 || (loadedFindings.systemic_problems ?? []).length > 0 || dialogueRound && priorDialogue.entries.length > 0);
|
|
4068
|
+
const headInBaseRepo = answerable && resolution.headRepo !== null && resolution.headRepo.toLowerCase() === input.repo.toLowerCase();
|
|
4069
|
+
const answers = answerable ? answersFrom({
|
|
4070
|
+
repo: input.repo,
|
|
4071
|
+
prNumber,
|
|
4072
|
+
botLogin: input.botLogin,
|
|
4073
|
+
comments: commentRows.map((row) => ({
|
|
4074
|
+
id: row.id,
|
|
4075
|
+
body: row.body,
|
|
4076
|
+
user: { login: row.author, type: row.authorType },
|
|
4077
|
+
created_at: row.created,
|
|
4078
|
+
author_association: row.association
|
|
4079
|
+
})),
|
|
4080
|
+
commits: headInBaseRepo ? await fetchCommitAnswersSource(input.repo, prNumber, ghApi) : []
|
|
4081
|
+
}) : { comments: [], commits: [] };
|
|
4082
|
+
const isTrusted = (response) => isTrustedResponse(response, headInBaseRepo);
|
|
4083
|
+
const allAnswers = withRulingsOn(
|
|
4084
|
+
[...answers.comments, ...answers.commits],
|
|
4085
|
+
new Set(
|
|
4086
|
+
priorDialogue.entries.filter((entry) => entry.state !== "rebutted").map((entry) => entry.id)
|
|
4087
|
+
),
|
|
4088
|
+
isTrusted
|
|
4089
|
+
);
|
|
4090
|
+
const unorderedRulings = allAnswers.filter(
|
|
4091
|
+
(answer) => RulingCodec.is(answer.disposition) && Number.isNaN(Date.parse(answer.created_at ?? ""))
|
|
4092
|
+
).length;
|
|
4093
|
+
if (unorderedRulings > 0) {
|
|
4094
|
+
process.stderr.write(
|
|
4095
|
+
`Warning: ${String(unorderedRulings)} maintainer ruling(s) carry no readable time, so they cannot be ordered against the other answers \u2014 not applied
|
|
4096
|
+
`
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
const allClosures = closingResponses(allAnswers, isTrusted);
|
|
4100
|
+
const dialogue = answerable && dialogueRound ? advanceDialogue(priorDialogue.entries, dialogueAnswers(allAnswers, isTrusted), [
|
|
4101
|
+
...loadedFindings.findings.map((f) => ({
|
|
4102
|
+
id: resolveFindingId(f),
|
|
4103
|
+
title: f.title,
|
|
4104
|
+
severity: f.severity,
|
|
4105
|
+
rebutted: hasRebuttal(f)
|
|
4106
|
+
})),
|
|
4107
|
+
...(loadedFindings.systemic_problems ?? []).flatMap((s) => {
|
|
4108
|
+
const id = resolveSystemicId(s);
|
|
4109
|
+
return id === void 0 ? [] : [{ id, title: s.title, severity: s.severity, rebutted: hasRebuttal(s) }];
|
|
4110
|
+
})
|
|
4111
|
+
]) : priorDialogue.entries;
|
|
4112
|
+
const upheldIds = new Set(
|
|
4113
|
+
dialogue.filter((entry) => entry.state === "upheld").map((entry) => entry.id)
|
|
4114
|
+
);
|
|
4115
|
+
const closures = allClosures.filter(
|
|
4116
|
+
(closure) => couldMatch(closure.id, loadedFindings) && !upheldIds.has(closure.id)
|
|
4117
|
+
);
|
|
4118
|
+
const contested = dialogueRound ? dialogue.filter((entry) => entry.state === "contested") : [];
|
|
4119
|
+
const contestedIds = new Set(contested.map((entry) => entry.id));
|
|
4120
|
+
const isContestedFinding = findingIdIn(contestedIds);
|
|
4121
|
+
const isContestedSystemic = systemicIdIn(contestedIds);
|
|
4122
|
+
const priorForAnswers = closures.length > 0 && existingSticky !== null ? { value: await resolvePriorFindings(existingSticky.body, readArtifact) } : null;
|
|
4123
|
+
const answeredPrior = priorForAnswers === null ? null : resolveTolerantFindings(priorForAnswers.value);
|
|
4124
|
+
if (priorForAnswers !== null && answeredPrior === null) {
|
|
4125
|
+
process.stderr.write(
|
|
4126
|
+
`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
|
|
4127
|
+
`
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
const answeredRegistry = answeredRegistryFrom(closures, answeredPrior);
|
|
4131
|
+
const answeredSystemicRegistry = answeredSystemicRegistryFrom(closures, answeredPrior);
|
|
4132
|
+
const boundIds = new Set([...answeredRegistry, ...answeredSystemicRegistry].map((e) => e.code));
|
|
4133
|
+
const unboundClosureCount = answeredPrior === null ? 0 : closures.filter((closure) => !boundIds.has(closure.id)).length;
|
|
4134
|
+
if (unboundClosureCount > 0) {
|
|
4135
|
+
process.stderr.write(
|
|
4136
|
+
`Warning: ${String(unboundClosureCount)} maintainer answer(s) name nothing the prior review reported \u2014 they close nothing this round
|
|
4137
|
+
`
|
|
4138
|
+
);
|
|
4139
|
+
}
|
|
4140
|
+
const rulings = new Map(
|
|
4141
|
+
dialogue.filter((entry) => entry.state === "overruled").map((entry) => [
|
|
4142
|
+
entry.id,
|
|
4143
|
+
{
|
|
4144
|
+
code: entry.id,
|
|
4145
|
+
title: entry.title,
|
|
4146
|
+
answerUrl: entry.answer,
|
|
4147
|
+
answerAuthor: allAnswers.find((answer) => answer.source_url === entry.answer)?.author ?? null
|
|
4148
|
+
}
|
|
4149
|
+
])
|
|
4150
|
+
);
|
|
4151
|
+
const overruledFilter = applyOverruled(
|
|
4152
|
+
loadedFindings.findings.filter((f) => !isContestedFinding(f)),
|
|
4153
|
+
rulings,
|
|
4154
|
+
resolveFindingId,
|
|
4155
|
+
answeredNoteKey
|
|
4156
|
+
);
|
|
4157
|
+
const overruledSystemicFilter = applyOverruled(
|
|
4158
|
+
(loadedFindings.systemic_problems ?? []).filter((s) => !isContestedSystemic(s)),
|
|
4159
|
+
rulings,
|
|
4160
|
+
resolveSystemicId,
|
|
4161
|
+
answeredSystemicNoteKey
|
|
4162
|
+
);
|
|
4163
|
+
const answeredFilter = applyAnswered(overruledFilter.kept, answeredRegistry);
|
|
4164
|
+
const answeredSystemicFilter = applyAnsweredSystemic(
|
|
4165
|
+
overruledSystemicFilter.kept,
|
|
4166
|
+
answeredSystemicRegistry
|
|
4167
|
+
);
|
|
4168
|
+
const contestedFindings = loadedFindings.findings.filter(isContestedFinding);
|
|
4169
|
+
const contestedSystemic = (loadedFindings.systemic_problems ?? []).filter(isContestedSystemic);
|
|
4170
|
+
const contestedReRaises = new Map([
|
|
4171
|
+
...contestedSystemic.map((s) => [resolveSystemicId(s) ?? "", s]),
|
|
4172
|
+
...contestedFindings.map((f) => [resolveFindingId(f), f])
|
|
4173
|
+
]);
|
|
4174
|
+
const contestedViews = contested.map((entry) => {
|
|
4175
|
+
const reRaise = contestedReRaises.get(entry.id);
|
|
4176
|
+
return {
|
|
4177
|
+
id: entry.id,
|
|
4178
|
+
title: entry.title,
|
|
4179
|
+
severity: entry.severity,
|
|
4180
|
+
answerUrl: entry.answer,
|
|
4181
|
+
...reRaise === void 0 ? {} : {
|
|
4182
|
+
reRaise: {
|
|
4183
|
+
..."path" in reRaise ? {
|
|
4184
|
+
location: `${reRaise.path}:${lineRange(reRaise.start_line, reRaise.end_line, "\u2013")}`
|
|
4185
|
+
} : {},
|
|
4186
|
+
description: reRaise.description,
|
|
4187
|
+
reasoning: reRaise.reasoning,
|
|
4188
|
+
...hasRebuttal(reRaise) ? { rebuttal: reRaise.rebuttal ?? "" } : {}
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
};
|
|
4192
|
+
});
|
|
4193
|
+
const reRaisedNotes = {
|
|
4194
|
+
...answeredFilter.reRaisedNotes,
|
|
4195
|
+
...answeredSystemicFilter.reRaisedNotes,
|
|
4196
|
+
...overruledFilter.reRaisedNotes,
|
|
4197
|
+
...overruledSystemicFilter.reRaisedNotes
|
|
4198
|
+
};
|
|
3644
4199
|
const verbatimReRaised = answeredFilter.verbatimReRaised;
|
|
3645
|
-
const
|
|
4200
|
+
const answeredDropCount = answeredFilter.droppedCount + answeredSystemicFilter.droppedCount;
|
|
4201
|
+
const overruledDropCount = overruledFilter.droppedCount + overruledSystemicFilter.droppedCount;
|
|
4202
|
+
const droppedCount = answeredDropCount + overruledDropCount;
|
|
3646
4203
|
const droppedIds = /* @__PURE__ */ new Set([
|
|
3647
|
-
...verbatimReRaised.flatMap(
|
|
4204
|
+
...[...verbatimReRaised, ...overruledFilter.verbatimReRaised].flatMap(
|
|
4205
|
+
(e) => e.code !== "" ? [e.code] : []
|
|
4206
|
+
),
|
|
3648
4207
|
...answeredFilter.droppedFindingIds.filter((id) => id !== "")
|
|
3649
4208
|
]);
|
|
3650
|
-
const keptCodes = new Set(answeredFilter.findings.map((f) => f.id));
|
|
4209
|
+
const keptCodes = new Set([...answeredFilter.findings, ...contestedFindings].map((f) => f.id));
|
|
3651
4210
|
const trulyDropped = new Set([...droppedIds].filter((c) => !keptCodes.has(c)));
|
|
3652
|
-
const systemic = trulyDropped.size === 0 ?
|
|
4211
|
+
const systemic = trulyDropped.size === 0 ? answeredSystemicFilter.systemic : answeredSystemicFilter.systemic.map((s) => {
|
|
3653
4212
|
if (s.finding_ids === void 0) return s;
|
|
3654
4213
|
const codes = s.finding_ids.filter((c) => !trulyDropped.has(c));
|
|
3655
4214
|
return codes.length === s.finding_ids.length ? s : { ...s, finding_ids: codes };
|
|
3656
4215
|
});
|
|
3657
4216
|
const findings = {
|
|
3658
|
-
...loadedFindings,
|
|
3659
|
-
findings: [...answeredFilter.findings]
|
|
3660
|
-
...systemic.length > 0 ? { systemic_problems: systemic } : {}
|
|
4217
|
+
...withSystemicProblems(loadedFindings, [...systemic, ...contestedSystemic]),
|
|
4218
|
+
findings: [...answeredFilter.findings, ...contestedFindings]
|
|
3661
4219
|
};
|
|
3662
4220
|
const broadCurrentIds = [
|
|
3663
4221
|
...findings.findings.map((f) => f.id).filter((id) => id !== ""),
|
|
3664
4222
|
...(findings.systemic_problems ?? []).map((s) => s.id).filter((id) => id !== void 0 && id !== "")
|
|
3665
4223
|
];
|
|
3666
|
-
const
|
|
4224
|
+
const argued = findings.findings.filter((f) => !isContestedFinding(f));
|
|
4225
|
+
const roundHasNit = argued.some((f) => f.severity === "nit");
|
|
3667
4226
|
const nitWantsPrior = roundHasNit && existingSticky !== null && (isFullReviewSticky(existingSticky.body) || isFullReviewAncestry(existingSticky.body));
|
|
3668
4227
|
const broadWantsPrior = existingSticky !== null && !priorIsMechanic && mentionsOutsideKnown(reachable, broadCurrentIds);
|
|
3669
|
-
const resolvedPrior = existingSticky !== null && (nitWantsPrior || broadWantsPrior) ? await resolvePriorFindings(existingSticky.body, readArtifact) : null;
|
|
4228
|
+
const resolvedPrior = priorForAnswers !== null ? priorForAnswers.value : existingSticky !== null && (nitWantsPrior || broadWantsPrior) ? await resolvePriorFindings(existingSticky.body, readArtifact) : null;
|
|
3670
4229
|
const priorDocForNits = nitWantsPrior ? resolvedPrior : null;
|
|
3671
4230
|
const priorSuppressedKeys = new Set(
|
|
3672
4231
|
priorBelowFloorNits(priorDocForNits, input.nitVisibilityFloor).map(
|
|
@@ -3677,15 +4236,25 @@ ${dropNote}` : ""}`,
|
|
|
3677
4236
|
// synthesized prior), the answeredNoteKey form (an empty-id prior), and the bare title: key
|
|
3678
4237
|
// (a pathless codeless prior).
|
|
3679
4238
|
priorSuppressedKeys.has(resolveFindingId(f)) || priorSuppressedKeys.has(answeredNoteKey(f)) || priorSuppressedKeys.has(`title:${f.title}`));
|
|
3680
|
-
const suppressedNits =
|
|
3681
|
-
const visibleFindings =
|
|
3682
|
-
const answeredDropNote =
|
|
4239
|
+
const suppressedNits = argued.filter(isSuppressedNit);
|
|
4240
|
+
const visibleFindings = argued.filter((f) => !isSuppressedNit(f));
|
|
4241
|
+
const answeredDropNote = [
|
|
4242
|
+
answeredReRaiseNote(
|
|
4243
|
+
[...verbatimReRaised, ...answeredSystemicFilter.verbatimReRaised],
|
|
4244
|
+
answeredDropCount
|
|
4245
|
+
),
|
|
4246
|
+
overruledReRaiseNote(
|
|
4247
|
+
[...overruledFilter.verbatimReRaised, ...overruledSystemicFilter.verbatimReRaised],
|
|
4248
|
+
overruledDropCount
|
|
4249
|
+
)
|
|
4250
|
+
].filter((note) => note !== "").join("\n>\n") + (droppedCount > 0 && findings.findings.length === 0 && (findings.systemic_problems ?? []).length === 0 ? "\n> _The stop signal reflects the kept findings \u2014 this round carries none._" : "");
|
|
3683
4251
|
const inlinePartition = input.inline === true && envelope !== null ? partitionFindings(visibleFindings, indexDiff(diff)) : null;
|
|
3684
4252
|
const straysForDiscussion = inlinePartition?.strays ?? visibleFindings;
|
|
3685
4253
|
const currentIds = [
|
|
3686
4254
|
...straysForDiscussion.map((f) => f.id).filter((id) => id !== ""),
|
|
3687
4255
|
...suppressedNits.map((f) => f.id).filter((id) => id !== ""),
|
|
3688
|
-
...(findings.systemic_problems ?? []).map((s) => s.id).filter((id) => id !== void 0 && id !== "")
|
|
4256
|
+
...(findings.systemic_problems ?? []).map((s) => s.id).filter((id) => id !== void 0 && id !== ""),
|
|
4257
|
+
...contested.map((entry) => entry.id)
|
|
3689
4258
|
];
|
|
3690
4259
|
const orphanResolveFailed = existingSticky !== null && broadWantsPrior && carriedFindingsMarker(existingSticky.body) !== null && resolvedPrior === null;
|
|
3691
4260
|
const broadLive = new Set(broadCurrentIds);
|
|
@@ -3782,6 +4351,7 @@ ${note}
|
|
|
3782
4351
|
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
3783
4352
|
answeredNotes: reRaisedNotes,
|
|
3784
4353
|
answeredReRaiseNote: answeredDropNote,
|
|
4354
|
+
contested: contestedViews,
|
|
3785
4355
|
discussionByFinding,
|
|
3786
4356
|
orphanedDiscussion,
|
|
3787
4357
|
orphanedTotal,
|
|
@@ -3902,7 +4472,8 @@ ${note}
|
|
|
3902
4472
|
priorTraj,
|
|
3903
4473
|
roundNumber,
|
|
3904
4474
|
currentCodes,
|
|
3905
|
-
input.headSha.slice(0, 12)
|
|
4475
|
+
input.headSha.slice(0, 12),
|
|
4476
|
+
contested
|
|
3906
4477
|
) : effectiveRoute === "mechanic" ? (
|
|
3907
4478
|
// A mechanic pass means CI failed — whatever the carried prior says, its own stamp must
|
|
3908
4479
|
// never read "converged". The threshold-relative critical floor keeps the carried triple
|
|
@@ -3911,10 +4482,13 @@ ${note}
|
|
|
3911
4482
|
) : priorConv;
|
|
3912
4483
|
const stampedFindings = stampConvergence(findings, convergence);
|
|
3913
4484
|
const currentRoundCount = isRound ? roundNumber : priorRoundCount;
|
|
3914
|
-
const findingsMarker = findingsBlob(
|
|
4485
|
+
const findingsMarker = findingsBlob(
|
|
4486
|
+
stampedFindings,
|
|
4487
|
+
answerable && dialogueRound ? dialogueMarker(dialogue, priorDialogue.undecoded) : carriedDialogue
|
|
4488
|
+
);
|
|
3915
4489
|
const commonRenderInput = {
|
|
3916
4490
|
...sharedRenderInput,
|
|
3917
|
-
findings: stampedFindings,
|
|
4491
|
+
findings: withoutIds(stampedFindings, contestedIds),
|
|
3918
4492
|
envelope,
|
|
3919
4493
|
incomplete: thisIncomplete,
|
|
3920
4494
|
sameRootNotes,
|
|
@@ -4122,6 +4696,24 @@ ${note}
|
|
|
4122
4696
|
finalDiscussionOverride
|
|
4123
4697
|
)
|
|
4124
4698
|
);
|
|
4699
|
+
if (answerable && dialogueRound) {
|
|
4700
|
+
appendRunSummary(process.env["GITHUB_STEP_SUMMARY"], () => {
|
|
4701
|
+
const trustedAnswerIds = new Set(allAnswers.filter(isTrusted).map((answer) => answer.id));
|
|
4702
|
+
return dialogueMetricsTable({
|
|
4703
|
+
commentAnswers: answers.comments.length,
|
|
4704
|
+
commitAnswers: answers.commits.length,
|
|
4705
|
+
closures: allClosures.length,
|
|
4706
|
+
droppedAnswered: answeredDropCount,
|
|
4707
|
+
droppedOverruled: overruledDropCount,
|
|
4708
|
+
annotated: Object.keys(reRaisedNotes).length,
|
|
4709
|
+
entries: dialogue,
|
|
4710
|
+
systemicUnanswered: (findings.systemic_problems ?? []).filter((s) => {
|
|
4711
|
+
const id = resolveSystemicId(s);
|
|
4712
|
+
return id === void 0 || !trustedAnswerIds.has(id);
|
|
4713
|
+
}).length
|
|
4714
|
+
});
|
|
4715
|
+
});
|
|
4716
|
+
}
|
|
4125
4717
|
};
|
|
4126
4718
|
var noticeBody = (lead, existingBody) => {
|
|
4127
4719
|
const carried = existingBody ? carryForwardMarkers(existingBody) : "";
|
|
@@ -4523,7 +5115,10 @@ var IssueCommentCodec = t.intersection([
|
|
|
4523
5115
|
t.type({
|
|
4524
5116
|
id: t.number,
|
|
4525
5117
|
body: t.union([t.string, t.null]),
|
|
4526
|
-
user: t.
|
|
5118
|
+
user: t.intersection([
|
|
5119
|
+
t.type({ login: t.string }),
|
|
5120
|
+
t.partial({ type: t.union([t.string, t.null]) })
|
|
5121
|
+
])
|
|
4527
5122
|
}),
|
|
4528
5123
|
t.partial({
|
|
4529
5124
|
created_at: t.union([t.string, t.null]),
|
|
@@ -4577,13 +5172,16 @@ var fetchFullDiff = async (repo, defaultBranch, headSha, ghApi, gitRun) => {
|
|
|
4577
5172
|
const base = (await gitRun(["rev-parse", "HEAD"])).trim();
|
|
4578
5173
|
return gitRun(["diff", `${base}...${headSha}`]);
|
|
4579
5174
|
};
|
|
4580
|
-
var CommitCodec = t.
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
5175
|
+
var CommitCodec = t.intersection([
|
|
5176
|
+
t.type({
|
|
5177
|
+
sha: t.string,
|
|
5178
|
+
message: t.string,
|
|
5179
|
+
author: t.union([t.string, t.null]),
|
|
5180
|
+
email: t.union([t.string, t.null])
|
|
5181
|
+
}),
|
|
5182
|
+
t.partial({ date: t.union([t.string, t.null]), login: t.union([t.string, t.null]) })
|
|
5183
|
+
]);
|
|
5184
|
+
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
5185
|
var fetchCompareCommits = async (repo, defaultBranch, headSha, ghApi) => {
|
|
4588
5186
|
const rows = parseJsonl(
|
|
4589
5187
|
await ghApi([
|
|
@@ -4604,8 +5202,8 @@ var fetchCompareCommits = async (repo, defaultBranch, headSha, ghApi) => {
|
|
|
4604
5202
|
}
|
|
4605
5203
|
return commits;
|
|
4606
5204
|
};
|
|
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 =
|
|
5205
|
+
var COMMENT_JQ = ".[] | {id: .id, body: .body, user: {login: .user.login, type: .user.type}, created_at: .created_at, author_association: .author_association}";
|
|
5206
|
+
var REVIEW_COMMENT_JQ = ".[] | {body: .body, user: {login: .user.login}, created_at: .created_at, author_association: .author_association, path: .path, line: .line}";
|
|
4609
5207
|
var REVIEW_JQ = ".[] | {body: .body, user: {login: .user.login}, submitted_at: .submitted_at, author_association: .author_association, state: .state}";
|
|
4610
5208
|
var ReviewCommentCodec = t.intersection([
|
|
4611
5209
|
t.type({ body: t.union([t.string, t.null]), user: t.type({ login: t.string }) }),
|
|
@@ -4788,7 +5386,6 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
4788
5386
|
]);
|
|
4789
5387
|
const issueComments = decodeArrayOrNull(IssueCommentCodec, issueRows);
|
|
4790
5388
|
const reviewComments = decodeArrayOrNull(ReviewCommentCodec, reviewCommentRows);
|
|
4791
|
-
const threadComments = decodeArrayOrNull(ThreadCommentCodec, reviewCommentRows);
|
|
4792
5389
|
const reviews = decodeArrayOrNull(ReviewCodec, reviewRows);
|
|
4793
5390
|
const prior = issueComments === null ? null : priorReviewFrom(issueComments, input.botLogin);
|
|
4794
5391
|
writeFileSync(
|
|
@@ -4802,11 +5399,40 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
4802
5399
|
join(input.outDir, "prior_findings.json"),
|
|
4803
5400
|
priorFindings === null ? "null" : JSON.stringify(priorFindings)
|
|
4804
5401
|
);
|
|
4805
|
-
const
|
|
5402
|
+
const held = (seedsFromPrior ? parseDialogueMarker(prior.body).entries : []).filter(
|
|
5403
|
+
(entry) => entry.state !== "rebutted"
|
|
5404
|
+
);
|
|
5405
|
+
const harvest = seedsFromPrior ? harvestResponses({
|
|
5406
|
+
repo: input.repo,
|
|
5407
|
+
prNumber,
|
|
5408
|
+
botLogin: input.botLogin,
|
|
5409
|
+
priorIds: /* @__PURE__ */ new Set([...priorIdsFrom(priorFindings), ...held.map((entry) => entry.id)]),
|
|
5410
|
+
commits: commits.map((commit) => ({ ...commit, author: commit.login ?? null })),
|
|
5411
|
+
comments: issueComments ?? []
|
|
5412
|
+
}) : { file: { responses: [], unmatched: [] }, dropped: 0 };
|
|
4806
5413
|
writeFileSync(
|
|
4807
|
-
join(input.outDir, "
|
|
4808
|
-
JSON.stringify(
|
|
5414
|
+
join(input.outDir, "responses.json"),
|
|
5415
|
+
JSON.stringify({
|
|
5416
|
+
...harvest.file,
|
|
5417
|
+
...Object.fromEntries(
|
|
5418
|
+
["contested", "upheld", "overruled"].flatMap((state) => {
|
|
5419
|
+
const claims = held.filter((entry) => entry.state === state).map(({ id, title, severity, at }) => ({ id, title, severity, since: at }));
|
|
5420
|
+
return claims.length > 0 ? [[state, claims]] : [];
|
|
5421
|
+
})
|
|
5422
|
+
)
|
|
5423
|
+
})
|
|
4809
5424
|
);
|
|
5425
|
+
if (seedsFromPrior && priorFindings === null) {
|
|
5426
|
+
process.stderr.write(
|
|
5427
|
+
"Note: the prior review's findings did not resolve \u2014 every id-shaped answer is staged as unmatched\n"
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
5430
|
+
if (harvest.dropped > 0) {
|
|
5431
|
+
process.stderr.write(
|
|
5432
|
+
`Note: ${String(harvest.dropped)} answer(s) beyond the newest ${String(RESPONSES_PER_CHANNEL)} per channel were not staged
|
|
5433
|
+
`
|
|
5434
|
+
);
|
|
5435
|
+
}
|
|
4810
5436
|
writeFileSync(
|
|
4811
5437
|
join(input.outDir, "pr_conversation.json"),
|
|
4812
5438
|
JSON.stringify({
|
|
@@ -5170,7 +5796,7 @@ var parseScope = (raw) => {
|
|
|
5170
5796
|
reason: 'scope contains a character (newline, carriage return, backtick, "<", ">", or "|") that would corrupt the review prompt \u2014 use plain language names/tags'
|
|
5171
5797
|
};
|
|
5172
5798
|
}
|
|
5173
|
-
const languages = Array.from(new Set(trimmed.split(SCOPE_SEPARATOR_RE).filter((
|
|
5799
|
+
const languages = Array.from(new Set(trimmed.split(SCOPE_SEPARATOR_RE).filter((t10) => t10 !== "")));
|
|
5174
5800
|
return languages.length === 0 ? { kind: "absent" } : { kind: "ok", languages };
|
|
5175
5801
|
};
|
|
5176
5802
|
|
|
@@ -5798,18 +6424,19 @@ var deadlineCmd = defineCommand({
|
|
|
5798
6424
|
}
|
|
5799
6425
|
});
|
|
5800
6426
|
var derivedSchemaVersion = (kind, raw) => kind === "findings" ? declaredVersion(raw) : void 0;
|
|
6427
|
+
var liveSchemaPin = (kind, schemaPath, requestedVersion) => kind === "findings" && schemaPath === schemaPathFor(kind) ? livePinFor(kind, requestedVersion) : void 0;
|
|
5801
6428
|
var printableSchema = (schemaPath, pinVersion) => {
|
|
5802
6429
|
const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
|
|
5803
6430
|
const enforcementSchema = Object.fromEntries(
|
|
5804
6431
|
Object.entries(schema).filter(([key2]) => key2 !== "$schema")
|
|
5805
6432
|
);
|
|
5806
|
-
if (
|
|
6433
|
+
if (pinVersion === void 0) return JSON.stringify(enforcementSchema, null, 2);
|
|
5807
6434
|
const properties = enforcementSchema["properties"];
|
|
5808
6435
|
const schemaVersion = typeof properties === "object" && properties !== null ? properties["schema_version"] : void 0;
|
|
5809
6436
|
if (typeof schemaVersion === "object" && schemaVersion !== null) {
|
|
5810
6437
|
properties["schema_version"] = {
|
|
5811
6438
|
...schemaVersion,
|
|
5812
|
-
pattern: anchoredSchemaVersionPattern(
|
|
6439
|
+
pattern: anchoredSchemaVersionPattern(pinVersion)
|
|
5813
6440
|
};
|
|
5814
6441
|
}
|
|
5815
6442
|
return JSON.stringify(enforcementSchema, null, 2);
|
|
@@ -5845,7 +6472,8 @@ var validateCmd = defineCommand({
|
|
|
5845
6472
|
run: async ({ args }) => {
|
|
5846
6473
|
const kind = requireSchemaKind(args.kind || "findings");
|
|
5847
6474
|
const documentRaw = readJSON(args.document);
|
|
5848
|
-
const
|
|
6475
|
+
const requestedVersion = args["schema-version"] || derivedSchemaVersion(kind, documentRaw);
|
|
6476
|
+
const schemaPath = args.schema ? resolve(args.schema) : requireSchemaPath(kind, requestedVersion);
|
|
5849
6477
|
const { valid, errors } = validateAgainstSchema(documentRaw, schemaPath);
|
|
5850
6478
|
if (valid) {
|
|
5851
6479
|
process.stdout.write("\u2705 valid\n");
|
|
@@ -5857,7 +6485,7 @@ var validateCmd = defineCommand({
|
|
|
5857
6485
|
process.stderr.write(
|
|
5858
6486
|
`
|
|
5859
6487
|
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
|
|
6488
|
+
${printableSchema(schemaPath, liveSchemaPin(kind, schemaPath, requestedVersion))}
|
|
5861
6489
|
`
|
|
5862
6490
|
);
|
|
5863
6491
|
}
|
|
@@ -5881,10 +6509,6 @@ var seedDraftCmd = defineCommand({
|
|
|
5881
6509
|
type: "string",
|
|
5882
6510
|
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
6511
|
},
|
|
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
6512
|
"nit-visibility-floor": {
|
|
5889
6513
|
type: "string",
|
|
5890
6514
|
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 +6584,17 @@ var seedDraftCmd = defineCommand({
|
|
|
5960
6584
|
const strippedPrior = parsedPrior === null ? null : stripSurfaceFields(
|
|
5961
6585
|
isSurfaceStampedDoc(parsedPrior) ? parsedPrior : withoutScopeMetastasis(parsedPrior)
|
|
5962
6586
|
);
|
|
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
6587
|
const priorFindings = (() => {
|
|
5988
6588
|
if (strippedPrior === null || typeof strippedPrior !== "object" || Array.isArray(strippedPrior))
|
|
5989
6589
|
return strippedPrior;
|
|
5990
6590
|
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
|
-
};
|
|
6591
|
+
const doc = resolved === null ? strippedPrior : resolved;
|
|
5998
6592
|
const carried = doc["scope_metastasis"];
|
|
5999
6593
|
if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return doc;
|
|
6000
6594
|
if (doc["verdict"] === "error") return doc;
|
|
6001
6595
|
const computed = computeScopeMetastasis(priorTrajectory(parsedPrior, priorBody ?? ""));
|
|
6002
6596
|
return computed === null ? doc : { ...doc, scope_metastasis: computed };
|
|
6003
6597
|
})();
|
|
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
6598
|
if (parsedPrior !== null && isFullReviewAncestry(priorBody ?? "")) {
|
|
6013
6599
|
try {
|
|
6014
6600
|
const belowFloor = priorBelowFloorNits(
|
|
@@ -6040,7 +6626,9 @@ var seedDraftCmd = defineCommand({
|
|
|
6040
6626
|
) : priorFindings;
|
|
6041
6627
|
const accepts = (doc) => {
|
|
6042
6628
|
const resolved = resolvedPriorValue(doc);
|
|
6043
|
-
|
|
6629
|
+
if (resolved === null) return null;
|
|
6630
|
+
const carried = withoutRebuttals(resolved);
|
|
6631
|
+
return validateAgainstSchema(carried, schemaPath).valid ? carried : null;
|
|
6044
6632
|
};
|
|
6045
6633
|
const seedDoc = accepts(priorFindings) ?? (() => {
|
|
6046
6634
|
const bare = accepts(barePrior);
|
|
@@ -6328,7 +6916,7 @@ var printSchemaCmd = defineCommand({
|
|
|
6328
6916
|
const schemaKind = requireSchemaKind(args.name);
|
|
6329
6917
|
const schemaPath = requireSchemaPath(schemaKind, args["schema-version"]);
|
|
6330
6918
|
process.stdout.write(
|
|
6331
|
-
`${printableSchema(schemaPath, schemaKind
|
|
6919
|
+
`${printableSchema(schemaPath, liveSchemaPin(schemaKind, schemaPath, args["schema-version"]))}
|
|
6332
6920
|
`
|
|
6333
6921
|
);
|
|
6334
6922
|
}
|
|
@@ -6580,6 +7168,10 @@ var postCmd = defineCommand({
|
|
|
6580
7168
|
"unverified-no-logs": {
|
|
6581
7169
|
type: "boolean",
|
|
6582
7170
|
description: "Mark the review unverified: the fast-fix route ran with no failing-job logs staged, so its findings came from the diff alone. The caller decides this \u2014 the logs are staged in the review job, not here"
|
|
7171
|
+
},
|
|
7172
|
+
"without-dialogue": {
|
|
7173
|
+
type: "boolean",
|
|
7174
|
+
description: "Turn the review dialogue off: read no maintainer answers, so none closes, contests or rules on a finding, and carry the sticky's dialogue state untouched"
|
|
6583
7175
|
}
|
|
6584
7176
|
},
|
|
6585
7177
|
run: async ({ args }) => {
|
|
@@ -6614,6 +7206,7 @@ var postCmd = defineCommand({
|
|
|
6614
7206
|
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
6615
7207
|
inline: args.inline,
|
|
6616
7208
|
unverifiedNoLogs: args["unverified-no-logs"],
|
|
7209
|
+
withoutDialogue: args["without-dialogue"],
|
|
6617
7210
|
postedAt: formatUtc(/* @__PURE__ */ new Date()),
|
|
6618
7211
|
pricedAt: /* @__PURE__ */ new Date()
|
|
6619
7212
|
});
|