@jphutchins/code-review 0.1.0-alpha.45 → 0.1.0-alpha.47
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 +40 -30
- package/dist/index.js +216 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +15 -11
- package/schema/findings.schema.json +49 -0
- package/templates/comment.eta +0 -3
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var LineNumber = t.refinement(
|
|
|
32
32
|
);
|
|
33
33
|
var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
|
|
34
34
|
var Likelihood = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Likelihood");
|
|
35
|
+
var FiniteNumber = t.refinement(t.number, (n) => Number.isFinite(n), "FiniteNumber");
|
|
35
36
|
var SCHEMA_VERSION_RE = /^(0|[1-9]\d*)\.(\d+)\.(\d+)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
36
37
|
var SchemaVersion = t.refinement(
|
|
37
38
|
t.string,
|
|
@@ -126,6 +127,49 @@ var ScopeMetastasisStrict = t.refinement(
|
|
|
126
127
|
"ScopeMetastasisStrict"
|
|
127
128
|
);
|
|
128
129
|
var ScopeMetastasisCodec = t.exact(ScopeMetastasisStrict);
|
|
130
|
+
var RoundNumber = t.refinement(
|
|
131
|
+
t.number,
|
|
132
|
+
(n) => Number.isSafeInteger(n) && n >= 1,
|
|
133
|
+
"RoundNumber"
|
|
134
|
+
);
|
|
135
|
+
var CodeFrequency = t.record(
|
|
136
|
+
t.string,
|
|
137
|
+
t.refinement(t.number, (n) => Number.isSafeInteger(n) && n >= 0, "CodeFrequency")
|
|
138
|
+
);
|
|
139
|
+
var ConvergenceRoundRequired = t.type({ round: RoundNumber });
|
|
140
|
+
var ConvergenceRoundOptional = t.partial({
|
|
141
|
+
score: FiniteNumber,
|
|
142
|
+
codes: CodeFrequency,
|
|
143
|
+
sha: t.string
|
|
144
|
+
});
|
|
145
|
+
var ConvergenceRoundShape = t.intersection([ConvergenceRoundRequired, ConvergenceRoundOptional]);
|
|
146
|
+
var CONVERGENCE_ROUND_KEYS = /* @__PURE__ */ new Set([
|
|
147
|
+
...Object.keys(ConvergenceRoundRequired.props),
|
|
148
|
+
...Object.keys(ConvergenceRoundOptional.props)
|
|
149
|
+
]);
|
|
150
|
+
var ConvergenceRoundStrict = t.refinement(
|
|
151
|
+
ConvergenceRoundShape,
|
|
152
|
+
(r) => Object.keys(r).every((k) => CONVERGENCE_ROUND_KEYS.has(k)),
|
|
153
|
+
"ConvergenceRoundStrict"
|
|
154
|
+
);
|
|
155
|
+
var ConvergenceRoundCodec = t.exact(ConvergenceRoundStrict);
|
|
156
|
+
var ConvergenceCoreShape = t.type({
|
|
157
|
+
score: FiniteNumber,
|
|
158
|
+
threshold: FiniteNumber,
|
|
159
|
+
converged: t.boolean
|
|
160
|
+
});
|
|
161
|
+
var ConvergenceOptional = t.partial({ rounds: t.array(ConvergenceRoundCodec) });
|
|
162
|
+
var ConvergenceShape = t.intersection([ConvergenceCoreShape, ConvergenceOptional]);
|
|
163
|
+
var CONVERGENCE_KEYS = /* @__PURE__ */ new Set([
|
|
164
|
+
...Object.keys(ConvergenceCoreShape.props),
|
|
165
|
+
...Object.keys(ConvergenceOptional.props)
|
|
166
|
+
]);
|
|
167
|
+
var ConvergenceStrict = t.refinement(
|
|
168
|
+
ConvergenceShape,
|
|
169
|
+
(c) => Object.keys(c).every((k) => CONVERGENCE_KEYS.has(k)),
|
|
170
|
+
"ConvergenceStrict"
|
|
171
|
+
);
|
|
172
|
+
var ConvergenceCodec = t.exact(ConvergenceStrict);
|
|
129
173
|
var FindingsCodec = t.exact(
|
|
130
174
|
t.intersection([
|
|
131
175
|
t.type({
|
|
@@ -137,7 +181,9 @@ var FindingsCodec = t.exact(
|
|
|
137
181
|
t.partial({
|
|
138
182
|
systemic_problems: t.array(SystemicProblemCodec),
|
|
139
183
|
// Pipeline-stamped only (issue #150); see ScopeMetastasisCodec.
|
|
140
|
-
scope_metastasis: ScopeMetastasisCodec
|
|
184
|
+
scope_metastasis: ScopeMetastasisCodec,
|
|
185
|
+
// Pipeline-stamped only (issue #174); see ConvergenceCodec.
|
|
186
|
+
convergence: ConvergenceCodec
|
|
141
187
|
})
|
|
142
188
|
])
|
|
143
189
|
);
|
|
@@ -375,7 +421,7 @@ var severityEmoji = (s) => {
|
|
|
375
421
|
};
|
|
376
422
|
var EMBED_LIMIT = 42700;
|
|
377
423
|
var FINDINGS_SCHEMA_URL = "https://raw.githubusercontent.com/JPHutchins/code-review/main/schema/findings.schema.json";
|
|
378
|
-
var AGENTS_STOP_DIRECTIVE = `<!-- AGENTS: STOP \u2014 this comment is a machine channel; act on the data, not the prose. It embeds a base64 code-review findings JSON (an oversized review instead links the artifact URL in the same marker). Decode or fetch it, read its schema_version, then DOWNLOAD AND READ THE FULL SCHEMA for that version before you act \u2014 the schema's own $id is its canonical URL; the current schema is at ${FINDINGS_SCHEMA_URL} \u2014 read that schema, then parse the WHOLE findings document
|
|
424
|
+
var AGENTS_STOP_DIRECTIVE = `<!-- AGENTS: STOP \u2014 this comment is a machine channel; act on the data, not the prose. It embeds a base64 code-review findings JSON (an oversized review instead links the artifact URL in the same marker). Decode or fetch it, read its schema_version, then DOWNLOAD AND READ THE FULL SCHEMA for that version before you act \u2014 the schema's own $id is its canonical URL; the current schema is at ${FINDINGS_SCHEMA_URL} \u2014 read that schema, then parse the WHOLE findings document, not only the fields you recognize: the convergence score, its threshold, and the per-round trajectory (so you know whether the review has converged and how to read the number), plus the recurrence signals an iterating agent needs (round streaks, scope metastasis, same-mechanism), are all fields inside that document. -->`;
|
|
379
425
|
var b64LengthOf = (document) => Buffer.from(JSON.stringify(document), "utf-8").toString("base64").length;
|
|
380
426
|
var encodeMarker = (document, jsonUrl, limit) => {
|
|
381
427
|
const b64 = Buffer.from(JSON.stringify(document), "utf-8").toString("base64");
|
|
@@ -405,7 +451,9 @@ var ROUTE_RE = /<!-- reviewed-route: ([^>]*) -->/;
|
|
|
405
451
|
var parseReviewedRoute = (body) => ROUTE_RE.exec(body)?.[1] || null;
|
|
406
452
|
var isFullReviewSticky = (body) => {
|
|
407
453
|
const route = parseReviewedRoute(body);
|
|
408
|
-
|
|
454
|
+
if (route === "full review") return true;
|
|
455
|
+
if (route === "mechanic") return false;
|
|
456
|
+
return priorTrajectory(parseFindingsMarker(body), body).length > 0;
|
|
409
457
|
};
|
|
410
458
|
var COMPLETED_ANCESTOR_MARKER = "<!-- review-complete-ancestor -->";
|
|
411
459
|
var parseCompletedAncestor = (body) => body.includes(COMPLETED_ANCESTOR_MARKER);
|
|
@@ -429,13 +477,7 @@ var normalizeCodeCounts = (codes, priorCodes) => {
|
|
|
429
477
|
if (typeof codes !== "object" || codes === null || Array.isArray(codes)) return void 0;
|
|
430
478
|
const entries = Object.entries(codes).filter(
|
|
431
479
|
(e) => typeof e[1] === "number" && Number.isSafeInteger(e[1]) && e[1] > 0
|
|
432
|
-
)
|
|
433
|
-
if (b[1] !== a[1]) return b[1] - a[1];
|
|
434
|
-
const aPrior = hasCode(priorCodes, a[0]) ? 1 : 0;
|
|
435
|
-
const bPrior = hasCode(priorCodes, b[0]) ? 1 : 0;
|
|
436
|
-
if (aPrior !== bPrior) return bPrior - aPrior;
|
|
437
|
-
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
438
|
-
});
|
|
480
|
+
);
|
|
439
481
|
if (entries.length === 0) return void 0;
|
|
440
482
|
const sorted = entries.sort((a, b) => {
|
|
441
483
|
if (b[1] !== a[1]) return b[1] - a[1];
|
|
@@ -469,30 +511,12 @@ var parseRounds = (body) => {
|
|
|
469
511
|
}
|
|
470
512
|
return kept;
|
|
471
513
|
};
|
|
472
|
-
var
|
|
473
|
-
var
|
|
474
|
-
if (rounds.length === 0) return "";
|
|
475
|
-
const serialize = (kept2) => `<!-- code-review:rounds;base64 ${Buffer.from(JSON.stringify(kept2), "utf-8").toString("base64")} -->`;
|
|
476
|
-
const stripCodes = (n) => rounds.map(
|
|
477
|
-
(r, i) => i < n ? { critical: r.critical, major: r.major, minor: r.minor, nit: r.nit } : r
|
|
478
|
-
);
|
|
479
|
-
let stripped = 0;
|
|
480
|
-
while (stripped < rounds.length && serialize(stripCodes(stripped)).length > ROUNDS_MARKER_LIMIT) {
|
|
481
|
-
stripped += 1;
|
|
482
|
-
}
|
|
483
|
-
const kept = stripCodes(stripped);
|
|
484
|
-
const bounded = serialize(kept).length > ROUNDS_MARKER_LIMIT ? kept.slice(-8) : kept;
|
|
485
|
-
return serialize(bounded);
|
|
486
|
-
};
|
|
487
|
-
var roundChip = (c) => {
|
|
488
|
-
const parts = SEVERITIES.filter((k) => c[k] > 0).map((k) => `${severityEmoji(k)}${String(c[k])}`);
|
|
489
|
-
return parts.length === 0 ? "clean" : parts.join(" ");
|
|
490
|
-
};
|
|
491
|
-
var TRAJECTORY_CHIPS = 8;
|
|
514
|
+
var formatScore = (score) => formatConfidence(score);
|
|
515
|
+
var TRAJECTORY_SCORES = 8;
|
|
492
516
|
var roundsSummary = (rounds, count = rounds.length) => {
|
|
493
517
|
if (count === 0) return "";
|
|
494
|
-
const
|
|
495
|
-
const trajectory =
|
|
518
|
+
const cells = rounds.slice(-TRAJECTORY_SCORES).map((r) => typeof r.score === "number" ? formatScore(r.score) : "\u2014");
|
|
519
|
+
const trajectory = cells.length === 0 ? "" : rounds.length > TRAJECTORY_SCORES ? `\u2026 \u2192 ${cells.join(" \u2192 ")}` : cells.join(" \u2192 ");
|
|
496
520
|
return trajectory === "" ? `**Round ${String(count)}**` : `**Round ${String(count)}** \xB7 ${trajectory}`;
|
|
497
521
|
};
|
|
498
522
|
var computeCodeCounts = (findings, systemic = []) => {
|
|
@@ -506,15 +530,6 @@ var computeCodeCounts = (findings, systemic = []) => {
|
|
|
506
530
|
}
|
|
507
531
|
return Object.fromEntries(counts);
|
|
508
532
|
};
|
|
509
|
-
var roundRecord = (counts, codes, priorCodes, sha, round) => {
|
|
510
|
-
const normalized = normalizeCodeCounts(codes, priorCodes);
|
|
511
|
-
const record3 = normalized === void 0 ? { ...counts } : { ...counts, codes: normalized };
|
|
512
|
-
return {
|
|
513
|
-
...record3,
|
|
514
|
-
...sha !== void 0 ? { sha } : {},
|
|
515
|
-
...round !== void 0 ? { round } : {}
|
|
516
|
-
};
|
|
517
|
-
};
|
|
518
533
|
var consecutiveCodeStreaks = (rounds) => {
|
|
519
534
|
const entries = [];
|
|
520
535
|
if (rounds.length === 0) return {};
|
|
@@ -608,6 +623,18 @@ var convergenceScore = (doc, threshold) => round2(
|
|
|
608
623
|
0
|
|
609
624
|
)
|
|
610
625
|
);
|
|
626
|
+
var buildConvergence = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD, priorRounds = [], round = 1, codes = {}, sha) => {
|
|
627
|
+
const score = convergenceScore(doc, threshold);
|
|
628
|
+
const normalized = normalizeCodeCounts(codes, priorRounds[priorRounds.length - 1]?.codes);
|
|
629
|
+
const current = {
|
|
630
|
+
round,
|
|
631
|
+
score,
|
|
632
|
+
...normalized !== void 0 ? { codes: { ...normalized } } : {},
|
|
633
|
+
...sha !== void 0 ? { sha } : {}
|
|
634
|
+
};
|
|
635
|
+
const rounds = [...priorRounds, current].slice(-64);
|
|
636
|
+
return { score, threshold, converged: score <= threshold, rounds };
|
|
637
|
+
};
|
|
611
638
|
var DEFAULT_NIT_VISIBILITY_FLOOR = 0.25;
|
|
612
639
|
var isBelowVisibilityFloor = (f, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => f.severity === "nit" && typeof f.confidence === "number" && typeof f.likelihood === "number" && f.confidence * f.likelihood < floor;
|
|
613
640
|
var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
@@ -631,26 +658,21 @@ var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
|
631
658
|
}
|
|
632
659
|
return nits;
|
|
633
660
|
};
|
|
634
|
-
var
|
|
635
|
-
|
|
636
|
-
|
|
661
|
+
var convergenceBadge = (c) => c.converged ? `**Convergence** \u{1F3C1} ${formatScore(c.score)} \u2264 ${String(c.threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${formatScore(c.score)} > ${String(c.threshold)} \u2014 iterating`;
|
|
662
|
+
var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => convergenceBadge(convergenceSignal(doc, threshold));
|
|
663
|
+
var nextRoundNumber = (priorTraj, priorConvRounds) => {
|
|
664
|
+
const last = (rounds) => rounds.length > 0 ? rounds[rounds.length - 1]?.round ?? rounds.length : 0;
|
|
665
|
+
return Math.max(last(priorTraj), last(priorConvRounds)) + 1;
|
|
666
|
+
};
|
|
667
|
+
var inProgressConvergence = (prior, runningRound) => {
|
|
668
|
+
const rounds = prior.rounds ?? [];
|
|
669
|
+
return rounds.length === 0 ? "" : `${roundsSummary(rounds, runningRound)} \u2192 \u23F3`;
|
|
637
670
|
};
|
|
638
671
|
var SURFACE_SCHEMA_VERSION = "0.8.0";
|
|
639
672
|
var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
640
673
|
const score = convergenceScore(doc, threshold);
|
|
641
674
|
return { score, threshold, converged: score <= threshold };
|
|
642
675
|
};
|
|
643
|
-
var signalForRound = (round, doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(doc, threshold) });
|
|
644
|
-
var signalMarker = (signal) => `<!-- code-review:signal;base64 ${Buffer.from(
|
|
645
|
-
JSON.stringify({ schema_version: SURFACE_SCHEMA_VERSION, ...signal }),
|
|
646
|
-
"utf-8"
|
|
647
|
-
).toString("base64")} -->`;
|
|
648
|
-
var joinSignalMarker = (base, marker) => base === "" ? marker : `${base}
|
|
649
|
-
${marker}`;
|
|
650
|
-
var surfacedFindingsPointer = (findings, signal, jsonUrl) => {
|
|
651
|
-
const marker = findingsPointer(findings, jsonUrl);
|
|
652
|
-
return signal === null ? marker : joinSignalMarker(marker, signalMarker(signal));
|
|
653
|
-
};
|
|
654
676
|
var SIGNAL_RE = /<!-- code-review:signal;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
655
677
|
var parseSignalMarker = (body) => {
|
|
656
678
|
const b64 = SIGNAL_RE.exec(body)?.[1];
|
|
@@ -674,6 +696,39 @@ var parseSurfaceSignal = (doc) => {
|
|
|
674
696
|
convergence: { score: c["score"], threshold: c["threshold"], converged: c["converged"] }
|
|
675
697
|
};
|
|
676
698
|
};
|
|
699
|
+
var validStampedConvergence = (raw) => {
|
|
700
|
+
const decoded = ConvergenceCodec.decode(raw);
|
|
701
|
+
return decoded._tag === "Right" && decoded.right.rounds !== void 0 && decoded.right.rounds.length > 0 ? decoded.right : null;
|
|
702
|
+
};
|
|
703
|
+
var parseConvergence = (priorDoc) => {
|
|
704
|
+
if (typeof priorDoc !== "object" || priorDoc === null) return null;
|
|
705
|
+
const raw = priorDoc["convergence"];
|
|
706
|
+
return raw === void 0 ? null : validStampedConvergence(raw);
|
|
707
|
+
};
|
|
708
|
+
var CONVERGENCE_RE = /<!-- code-review:convergence;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
709
|
+
var convergenceMarker = (convergence) => `<!-- code-review:convergence;base64 ${Buffer.from(JSON.stringify(convergence), "utf-8").toString(
|
|
710
|
+
"base64"
|
|
711
|
+
)} -->`;
|
|
712
|
+
var parseConvergenceMarker = (body) => {
|
|
713
|
+
const b64 = CONVERGENCE_RE.exec(body)?.[1];
|
|
714
|
+
return b64 === void 0 ? null : validStampedConvergence(decodeBase64Json(b64));
|
|
715
|
+
};
|
|
716
|
+
var roundRecordsToConvergenceRounds = (records) => records.map((r, i) => ({
|
|
717
|
+
round: r.round ?? i + 1,
|
|
718
|
+
...r.codes !== void 0 ? { codes: { ...r.codes } } : {},
|
|
719
|
+
...r.sha !== void 0 ? { sha: r.sha } : {}
|
|
720
|
+
}));
|
|
721
|
+
var priorTrajectory = (priorDoc, priorBody) => parseConvergence(priorDoc)?.rounds ?? parseConvergenceMarker(priorBody)?.rounds ?? roundRecordsToConvergenceRounds(parseRounds(priorBody));
|
|
722
|
+
var carriedConvergence = (priorDoc, priorBody) => {
|
|
723
|
+
const stamped = parseConvergence(priorDoc) ?? parseConvergenceMarker(priorBody);
|
|
724
|
+
if (stamped !== null) return stamped;
|
|
725
|
+
const signal = parseSignalMarker(priorBody) ?? parseSurfaceSignal(priorDoc);
|
|
726
|
+
if (signal === null) return null;
|
|
727
|
+
const legacy = roundRecordsToConvergenceRounds(parseRounds(priorBody));
|
|
728
|
+
const last = legacy[legacy.length - 1];
|
|
729
|
+
const rounds = last === void 0 ? [{ round: signal.round }] : last.round < signal.round ? [...legacy.slice(0, -1), { ...last, round: signal.round }] : legacy;
|
|
730
|
+
return { ...signal.convergence, rounds };
|
|
731
|
+
};
|
|
677
732
|
var SURFACE_SCHEMA_VERSIONS = ["0.7.0", SURFACE_SCHEMA_VERSION];
|
|
678
733
|
var isSurfaceVersion = (version) => typeof version === "string" && SURFACE_SCHEMA_VERSIONS.includes(version);
|
|
679
734
|
var stripSurfaceFields = (doc) => {
|
|
@@ -689,12 +744,13 @@ var carryForwardMarkers = (body) => {
|
|
|
689
744
|
const findings = /<!-- code-review:findings-json[^>]*-->/.exec(body)?.[0];
|
|
690
745
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
691
746
|
const reviewedRoute = ROUTE_RE.exec(body)?.[0];
|
|
747
|
+
const convergence = CONVERGENCE_RE.exec(body)?.[0];
|
|
692
748
|
const rounds = ROUNDS_RE.exec(body)?.[0];
|
|
693
749
|
const signal = SIGNAL_RE.exec(body)?.[0];
|
|
694
750
|
const completedAncestor = parseReviewComplete(body) || parseCompletedAncestor(body) ? COMPLETED_ANCESTOR_MARKER : void 0;
|
|
695
751
|
const findingsBlock = findings ? `${AGENTS_STOP_DIRECTIVE}
|
|
696
752
|
${findings}` : void 0;
|
|
697
|
-
return [findingsBlock, reviewedSha, reviewedRoute, completedAncestor, rounds, signal].filter((m) => m !== void 0).join("\n\n");
|
|
753
|
+
return [findingsBlock, reviewedSha, reviewedRoute, completedAncestor, convergence, rounds, signal].filter((m) => m !== void 0).join("\n\n");
|
|
698
754
|
};
|
|
699
755
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
700
756
|
var projectPatch = (patch) => {
|
|
@@ -1071,10 +1127,6 @@ var computeSeverityCounts = (findings) => findings.reduce(
|
|
|
1071
1127
|
emptySeverityCounts()
|
|
1072
1128
|
);
|
|
1073
1129
|
var isReviewVerdict = (verdict) => verdict !== "error";
|
|
1074
|
-
var computeRoundCounts = (findings) => (findings.systemic_problems ?? []).reduce(
|
|
1075
|
-
(acc, s) => s.severity in acc ? { ...acc, [s.severity]: acc[s.severity] + 1 } : acc,
|
|
1076
|
-
computeSeverityCounts(findings.findings)
|
|
1077
|
-
);
|
|
1078
1130
|
var isConvergenceRound = (route, incomplete) => route === "full review" && !incomplete;
|
|
1079
1131
|
var render = (input) => {
|
|
1080
1132
|
const eta = new Eta({ autoTrim: false });
|
|
@@ -1087,9 +1139,10 @@ var render = (input) => {
|
|
|
1087
1139
|
const effort = input.effort ?? input.envelope?.effort ?? null;
|
|
1088
1140
|
const modelNames = input.envelope ? input.envelope.models.map((m) => m.model).join(", ") : "";
|
|
1089
1141
|
const severityCounts = input.severityCounts ?? computeSeverityCounts(input.findings.findings);
|
|
1090
|
-
const
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1142
|
+
const convergence = input.findings.convergence;
|
|
1143
|
+
const trajectory = convergence?.rounds ?? input.rounds ?? [];
|
|
1144
|
+
const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(trajectory.slice(0, -1), input.findings.findings);
|
|
1145
|
+
const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && trajectory.length > 0)) && isReviewVerdict(input.findings.verdict);
|
|
1093
1146
|
const advisoryAllowed = isFullReviewRound;
|
|
1094
1147
|
return eta.renderString(input.template, {
|
|
1095
1148
|
findings: input.findings,
|
|
@@ -1106,7 +1159,7 @@ var render = (input) => {
|
|
|
1106
1159
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
1107
1160
|
postedAt: input.postedAt ?? "",
|
|
1108
1161
|
severityCounts,
|
|
1109
|
-
convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold)
|
|
1162
|
+
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold),
|
|
1110
1163
|
strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
|
|
1111
1164
|
suppressedNits: (input.suppressedNits ?? []).map(sanitizeSuppressedNit),
|
|
1112
1165
|
nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
|
|
@@ -1115,19 +1168,12 @@ var render = (input) => {
|
|
|
1115
1168
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
1116
1169
|
runUrl: input.runUrl ?? null,
|
|
1117
1170
|
jsonUrl: input.jsonUrl ?? null,
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
// production (issue #141 review r4).
|
|
1125
|
-
isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, input.findings, input.convergenceThreshold) : null,
|
|
1126
|
-
input.jsonUrl
|
|
1127
|
-
),
|
|
1128
|
-
roundsMarker: roundsMarker(rounds),
|
|
1129
|
-
roundsSummary: roundsSummary(rounds, input.roundCount),
|
|
1130
|
-
metastasisNote: advisoryAllowed ? metastasisNote(rounds) : "",
|
|
1171
|
+
// The blob is the agent's complete document with the pipeline-stamped convergence field inside it
|
|
1172
|
+
// (issue #174) — no separate signal or rounds marker rides beside it. post always supplies the
|
|
1173
|
+
// precomputed marker; the standalone `render` command falls back to encoding the doc here.
|
|
1174
|
+
findingsPointer: input.findingsPointer ?? findingsPointer(input.findings, input.jsonUrl),
|
|
1175
|
+
roundsSummary: roundsSummary(trajectory, input.roundCount),
|
|
1176
|
+
metastasisNote: advisoryAllowed ? metastasisNote(trajectory) : "",
|
|
1131
1177
|
sameRootNotes: advisoryAllowed ? sameRootNotes : {},
|
|
1132
1178
|
answeredNotes: input.answeredNotes ?? {},
|
|
1133
1179
|
answeredReRaiseNote: input.answeredReRaiseNote ?? "",
|
|
@@ -2010,14 +2056,9 @@ var checkLongSuggestions = (comments) => {
|
|
|
2010
2056
|
});
|
|
2011
2057
|
return { comments: adjusted, longFiles };
|
|
2012
2058
|
};
|
|
2013
|
-
var
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
2017
|
-
} catch {
|
|
2018
|
-
return { kind: "corrupt" };
|
|
2019
|
-
}
|
|
2020
|
-
const resolution = resolve("findings", raw);
|
|
2059
|
+
var PIPELINE_STAMPED_FIELDS = /* @__PURE__ */ new Set(["convergence", "scope_metastasis"]);
|
|
2060
|
+
var decodeFindings = (doc) => {
|
|
2061
|
+
const resolution = resolve("findings", doc);
|
|
2021
2062
|
switch (resolution.kind) {
|
|
2022
2063
|
case "ok":
|
|
2023
2064
|
return { kind: "ok", findings: resolution.value };
|
|
@@ -2028,6 +2069,28 @@ var loadFindings = (path) => {
|
|
|
2028
2069
|
return { kind: "invalid-shape" };
|
|
2029
2070
|
}
|
|
2030
2071
|
};
|
|
2072
|
+
var loadFindings = (path) => {
|
|
2073
|
+
let raw;
|
|
2074
|
+
try {
|
|
2075
|
+
raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
2076
|
+
} catch {
|
|
2077
|
+
return { kind: "corrupt" };
|
|
2078
|
+
}
|
|
2079
|
+
const first = decodeFindings(raw);
|
|
2080
|
+
if (first.kind === "invalid-shape" && typeof raw === "object" && raw !== null && !Array.isArray(raw) && Object.keys(raw).some((k) => PIPELINE_STAMPED_FIELDS.has(k))) {
|
|
2081
|
+
const stripped = Object.fromEntries(
|
|
2082
|
+
Object.entries(raw).filter(([key2]) => !PIPELINE_STAMPED_FIELDS.has(key2))
|
|
2083
|
+
);
|
|
2084
|
+
const retry = decodeFindings(stripped);
|
|
2085
|
+
if (retry.kind === "ok") {
|
|
2086
|
+
process.stderr.write(
|
|
2087
|
+
"Warning: the review draft carried an invalid pipeline-stamped field (convergence/scope_metastasis) \u2014 stripped it and used the rest; the pipeline re-stamps convergence\n"
|
|
2088
|
+
);
|
|
2089
|
+
return retry;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return first;
|
|
2093
|
+
};
|
|
2031
2094
|
var noticeMessageFor = (result) => {
|
|
2032
2095
|
switch (result.kind) {
|
|
2033
2096
|
case "corrupt":
|
|
@@ -2312,11 +2375,23 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
2312
2375
|
const wouldBuryCompleted = (incomplete) => incomplete && existingComplete;
|
|
2313
2376
|
const priorIsFullReview = (body) => isFullReviewSticky(body) || parseReviewedRoute(body) === null && (parseReviewComplete(body) || parseCompletedAncestor(body));
|
|
2314
2377
|
const emptyMechanicWouldBury = (route, incomplete) => route === "mechanic" && !incomplete && findings.findings.length === 0 && existingSticky !== null && priorIsFullReview(existingSticky.body);
|
|
2315
|
-
const
|
|
2316
|
-
const
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
|
|
2378
|
+
const priorDoc = existingSticky !== null ? parseFindingsMarker(existingSticky.body) : null;
|
|
2379
|
+
const priorBody = existingSticky?.body ?? "";
|
|
2380
|
+
const priorTraj = priorTrajectory(priorDoc, priorBody);
|
|
2381
|
+
const priorConv = carriedConvergence(priorDoc, priorBody);
|
|
2382
|
+
const priorRoundCount = nextRoundNumber(priorTraj, priorConv?.rounds ?? []) - 1;
|
|
2383
|
+
const stampConvergence = (doc, conv) => ({
|
|
2384
|
+
...doc,
|
|
2385
|
+
convergence: conv ?? void 0
|
|
2386
|
+
});
|
|
2387
|
+
const findingsBlob = (doc) => {
|
|
2388
|
+
const marker = findingsPointer(doc, input.jsonUrl);
|
|
2389
|
+
if (doc.convergence === void 0 || findingsMarkerForm(doc, input.jsonUrl) === "embedded") {
|
|
2390
|
+
return marker;
|
|
2391
|
+
}
|
|
2392
|
+
const conv = convergenceMarker(doc.convergence);
|
|
2393
|
+
return marker === "" ? conv : `${marker}
|
|
2394
|
+
${conv}`;
|
|
2320
2395
|
};
|
|
2321
2396
|
const leaveInPlace = (message) => {
|
|
2322
2397
|
process.stderr.write(
|
|
@@ -2360,7 +2435,7 @@ ${dropNote}` : ""}`,
|
|
|
2360
2435
|
const template = readFileSync(input.templatePath, "utf-8");
|
|
2361
2436
|
const inlineTemplate = readFileSync(input.inlineTemplatePath, "utf-8");
|
|
2362
2437
|
const renderNotice = (message) => {
|
|
2363
|
-
const findings2 = incompleteFindings(`### \u26A0\uFE0F ${message}`);
|
|
2438
|
+
const findings2 = stampConvergence(incompleteFindings(`### \u26A0\uFE0F ${message}`), priorConv);
|
|
2364
2439
|
return formatMarkdown(
|
|
2365
2440
|
render({
|
|
2366
2441
|
findings: findings2,
|
|
@@ -2372,17 +2447,12 @@ ${dropNote}` : ""}`,
|
|
|
2372
2447
|
route: input.route,
|
|
2373
2448
|
reviewedSha: input.headSha,
|
|
2374
2449
|
effort: input.effort,
|
|
2375
|
-
rounds: priorRounds,
|
|
2376
2450
|
sameRootNotes: {},
|
|
2377
|
-
roundCount:
|
|
2451
|
+
roundCount: priorRoundCount,
|
|
2378
2452
|
convergenceRound: false,
|
|
2379
2453
|
runUrl: input.runUrl,
|
|
2380
2454
|
jsonUrl: input.jsonUrl,
|
|
2381
|
-
|
|
2382
|
-
// stop signal for a run that produced no verdict (issue #141 review r2). The prior signal
|
|
2383
|
-
// survives on the sticky in the compact marker (findingsMarkerFor), and the carried-forward
|
|
2384
|
-
// trajectory (rounds marker) remains the historical record.
|
|
2385
|
-
findingsPointer: findingsMarkerFor(findings2, null),
|
|
2455
|
+
findingsPointer: findingsBlob(findings2),
|
|
2386
2456
|
postedAt: input.postedAt
|
|
2387
2457
|
})
|
|
2388
2458
|
);
|
|
@@ -2450,9 +2520,10 @@ ${dropNote}` : ""}`,
|
|
|
2450
2520
|
}
|
|
2451
2521
|
if (emptyMechanicWouldBury(effectiveRoute, envelopelessIncomplete) && existingSticky !== null)
|
|
2452
2522
|
await emptyMechanicLeaveOrNote(existingSticky);
|
|
2523
|
+
const stampedFindings2 = stampConvergence(findings, priorConv);
|
|
2453
2524
|
const body = formatMarkdown(
|
|
2454
2525
|
render({
|
|
2455
|
-
findings,
|
|
2526
|
+
findings: stampedFindings2,
|
|
2456
2527
|
envelope: null,
|
|
2457
2528
|
incomplete: envelopelessIncomplete,
|
|
2458
2529
|
prices: decodedPrices.right,
|
|
@@ -2461,7 +2532,6 @@ ${dropNote}` : ""}`,
|
|
|
2461
2532
|
route: effectiveRoute,
|
|
2462
2533
|
reviewedSha: input.headSha,
|
|
2463
2534
|
effort: input.effort,
|
|
2464
|
-
rounds: priorRounds,
|
|
2465
2535
|
sameRootNotes: {},
|
|
2466
2536
|
// The answered-state honesty rules apply on EVERY surface that renders the filtered
|
|
2467
2537
|
// findings — the lost-envelope branch lists every VISIBLE finding (no inline review exists to
|
|
@@ -2475,18 +2545,13 @@ ${dropNote}` : ""}`,
|
|
|
2475
2545
|
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2476
2546
|
answeredNotes: reRaisedNotes,
|
|
2477
2547
|
answeredReRaiseNote: answeredDropNote,
|
|
2478
|
-
roundCount:
|
|
2548
|
+
roundCount: priorRoundCount,
|
|
2479
2549
|
convergenceRound: false,
|
|
2480
2550
|
testReport,
|
|
2481
2551
|
inlineDisposition: { kind: "no-envelope" },
|
|
2482
2552
|
runUrl: input.runUrl,
|
|
2483
2553
|
jsonUrl: input.jsonUrl,
|
|
2484
|
-
|
|
2485
|
-
// ride the prior signal on the compact marker, never inside the blob.
|
|
2486
|
-
findingsPointer: findingsMarkerFor(
|
|
2487
|
-
findings,
|
|
2488
|
-
isReviewVerdict(findings.verdict) ? priorSignal : null
|
|
2489
|
-
),
|
|
2554
|
+
findingsPointer: findingsBlob(stampedFindings2),
|
|
2490
2555
|
postedAt: input.postedAt
|
|
2491
2556
|
})
|
|
2492
2557
|
);
|
|
@@ -2504,7 +2569,7 @@ ${dropNote}` : ""}`,
|
|
|
2504
2569
|
if (emptyMechanicWouldBury(effectiveRoute, thisIncomplete) && existingSticky !== null)
|
|
2505
2570
|
await emptyMechanicLeaveOrNote(existingSticky);
|
|
2506
2571
|
const isRound = isConvergenceRound(effectiveRoute, thisIncomplete) && isReviewVerdict(findings.verdict);
|
|
2507
|
-
const sameRootNotes = isRound ? computeSameRootNotes(
|
|
2572
|
+
const sameRootNotes = isRound ? computeSameRootNotes(priorTraj, findings.findings, input.headSha.slice(0, 12)) : {};
|
|
2508
2573
|
const {
|
|
2509
2574
|
comments: rawComments,
|
|
2510
2575
|
strays,
|
|
@@ -2528,35 +2593,30 @@ ${dropNote}` : ""}`,
|
|
|
2528
2593
|
const initialDisposition = comments.length === 0 && strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
2529
2594
|
const currentCounts = computeSeverityCounts(findings.findings);
|
|
2530
2595
|
const currentCodes = computeCodeCounts(findings.findings, findings.systemic_problems ?? []);
|
|
2531
|
-
const
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
const
|
|
2544
|
-
const findingsMarker = findingsMarkerFor(findings, signal);
|
|
2545
|
-
const markerForm = findingsMarkerForm(findings, input.jsonUrl);
|
|
2546
|
-
const signalNote = signal !== null || priorSignal !== null ? " (the stop signal still rides the compact marker)" : "";
|
|
2596
|
+
const roundNumber = priorRoundCount + 1;
|
|
2597
|
+
const convergence = isRound ? buildConvergence(
|
|
2598
|
+
findings,
|
|
2599
|
+
input.convergenceThreshold,
|
|
2600
|
+
priorTraj,
|
|
2601
|
+
roundNumber,
|
|
2602
|
+
currentCodes,
|
|
2603
|
+
input.headSha.slice(0, 12)
|
|
2604
|
+
) : priorConv;
|
|
2605
|
+
const stampedFindings = stampConvergence(findings, convergence);
|
|
2606
|
+
const currentRoundCount = isRound ? roundNumber : priorRoundCount;
|
|
2607
|
+
const findingsMarker = findingsBlob(stampedFindings);
|
|
2608
|
+
const markerForm = findingsMarkerForm(stampedFindings, input.jsonUrl);
|
|
2547
2609
|
if (markerForm === "link") {
|
|
2548
2610
|
process.stderr.write(
|
|
2549
|
-
|
|
2550
|
-
`
|
|
2611
|
+
"Warning: the findings-json blob exceeds the embed limit \u2014 degraded to the jsonUrl-link form; the convergence rides a compact marker beside it, but a decoding agent (and the next-round seed) must fetch the artifact for the FINDINGS\n"
|
|
2551
2612
|
);
|
|
2552
2613
|
} else if (markerForm === "omitted") {
|
|
2553
2614
|
process.stderr.write(
|
|
2554
|
-
|
|
2555
|
-
`
|
|
2615
|
+
"Warning: the findings-json blob exceeds the embed limit and no --json-url was given \u2014 the convergence rides a compact marker but the embedded findings seed is dropped from the posted surfaces\n"
|
|
2556
2616
|
);
|
|
2557
2617
|
}
|
|
2558
2618
|
const commonRenderInput = {
|
|
2559
|
-
findings,
|
|
2619
|
+
findings: stampedFindings,
|
|
2560
2620
|
envelope,
|
|
2561
2621
|
incomplete: thisIncomplete,
|
|
2562
2622
|
prices: decodedPrices.right,
|
|
@@ -2567,11 +2627,10 @@ ${dropNote}` : ""}`,
|
|
|
2567
2627
|
effort: input.effort,
|
|
2568
2628
|
testReport,
|
|
2569
2629
|
severityCounts: currentCounts,
|
|
2570
|
-
rounds,
|
|
2571
2630
|
sameRootNotes,
|
|
2572
2631
|
answeredNotes: reRaisedNotes,
|
|
2573
2632
|
answeredReRaiseNote: answeredDropNote,
|
|
2574
|
-
roundCount:
|
|
2633
|
+
roundCount: currentRoundCount,
|
|
2575
2634
|
convergenceThreshold: input.convergenceThreshold,
|
|
2576
2635
|
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2577
2636
|
convergenceRound: isRound,
|
|
@@ -2665,12 +2724,22 @@ var bodyRefsRun = (body, runUrl) => {
|
|
|
2665
2724
|
const runId = runIdFromUrl(runUrl);
|
|
2666
2725
|
return runId === null ? body.includes(runUrl) : new RegExp(`/actions/runs/${runId}(?!\\d)`).test(body);
|
|
2667
2726
|
};
|
|
2668
|
-
var announceBody = (headSha, runUrl, existingBody) =>
|
|
2669
|
-
|
|
2727
|
+
var announceBody = (headSha, runUrl, existingBody) => {
|
|
2728
|
+
const priorDoc = existingBody !== void 0 ? parseFindingsMarker(existingBody) : null;
|
|
2729
|
+
const prior = existingBody !== void 0 ? carriedConvergence(priorDoc, existingBody) : null;
|
|
2730
|
+
const progress = prior !== null ? inProgressConvergence(
|
|
2731
|
+
prior,
|
|
2732
|
+
nextRoundNumber(priorTrajectory(priorDoc, existingBody ?? ""), prior.rounds ?? [])
|
|
2733
|
+
) : "";
|
|
2734
|
+
return noticeBody(
|
|
2735
|
+
`${DEFAULT_MARKER}
|
|
2670
2736
|
|
|
2671
|
-
\u{1F504} **Code review in progress** for \`${headSha.slice(0, 7)}\` \u2014 see the [workflow run](${runUrl}) for progress; this comment is updated with the review when it completes
|
|
2672
|
-
|
|
2673
|
-
|
|
2737
|
+
\u{1F504} **Code review in progress** for \`${headSha.slice(0, 7)}\` \u2014 see the [workflow run](${runUrl}) for progress; this comment is updated with the review when it completes.${progress ? `
|
|
2738
|
+
|
|
2739
|
+
${progress}` : ""}`,
|
|
2740
|
+
existingBody
|
|
2741
|
+
);
|
|
2742
|
+
};
|
|
2674
2743
|
var announce = async (input, ghApi = runGhApi) => {
|
|
2675
2744
|
const candidates = await fetchPrCandidates(input.repo, input.headSha, ghApi);
|
|
2676
2745
|
const resolution = resolvePr(candidates, input.headBranch);
|
|
@@ -3794,9 +3863,13 @@ var renderCmd = defineCommand({
|
|
|
3794
3863
|
const testReport = args["test-report"] ? decode(TestSummaryCodec.decode(readJSON(args["test-report"])), "test report") : void 0;
|
|
3795
3864
|
const route = args.route || envelope.route || null;
|
|
3796
3865
|
const isRound = isConvergenceRound(route, envelope.incomplete === true || isIncompleteFindings(findings)) && isReviewVerdict(findings.verdict);
|
|
3797
|
-
const
|
|
3866
|
+
const threshold = parseConvergenceThreshold(args["convergence-threshold"]);
|
|
3867
|
+
const stampedFindings = {
|
|
3868
|
+
...findings,
|
|
3869
|
+
convergence: isRound ? buildConvergence(findings, threshold) : void 0
|
|
3870
|
+
};
|
|
3798
3871
|
const output2 = render({
|
|
3799
|
-
findings,
|
|
3872
|
+
findings: stampedFindings,
|
|
3800
3873
|
envelope,
|
|
3801
3874
|
prices,
|
|
3802
3875
|
pricesProvided: priceResolution.kind === "provided",
|
|
@@ -3805,9 +3878,9 @@ var renderCmd = defineCommand({
|
|
|
3805
3878
|
route: args.route,
|
|
3806
3879
|
effort: args.effort,
|
|
3807
3880
|
testReport,
|
|
3808
|
-
|
|
3809
|
-
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
3881
|
+
convergenceThreshold: threshold,
|
|
3810
3882
|
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
3883
|
+
convergenceRound: isRound,
|
|
3811
3884
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
3812
3885
|
});
|
|
3813
3886
|
process.stdout.write(output2);
|
|
@@ -4249,6 +4322,7 @@ ${printableSchema(schemaPath)}
|
|
|
4249
4322
|
}
|
|
4250
4323
|
});
|
|
4251
4324
|
var isSurfaceStampedDoc = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) && doc["schema_version"] === SURFACE_SCHEMA_VERSION;
|
|
4325
|
+
var PIPELINE_STAMPED_FIELDS2 = /* @__PURE__ */ new Set(["scope_metastasis", "convergence"]);
|
|
4252
4326
|
var withoutScopeMetastasis = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) ? Object.fromEntries(Object.entries(doc).filter(([key2]) => key2 !== "scope_metastasis")) : doc;
|
|
4253
4327
|
var seedDraftCmd = defineCommand({
|
|
4254
4328
|
meta: {
|
|
@@ -4337,7 +4411,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4337
4411
|
const carried = strippedPrior["scope_metastasis"];
|
|
4338
4412
|
if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return strippedPrior;
|
|
4339
4413
|
if (strippedPrior["verdict"] === "error") return strippedPrior;
|
|
4340
|
-
const computed = computeScopeMetastasis(
|
|
4414
|
+
const computed = computeScopeMetastasis(priorTrajectory(parsedPrior, priorBody ?? ""));
|
|
4341
4415
|
return computed === null ? strippedPrior : { ...strippedPrior, scope_metastasis: computed };
|
|
4342
4416
|
})();
|
|
4343
4417
|
if (args["prior-answers"]) {
|
|
@@ -4393,7 +4467,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4393
4467
|
const schemaPath = args.schema ? resolve$1(args.schema) : schemaPathFor(kind, args["schema-version"]);
|
|
4394
4468
|
const barePrior = typeof priorFindings === "object" && !Array.isArray(priorFindings) ? Object.fromEntries(
|
|
4395
4469
|
Object.entries(priorFindings).filter(
|
|
4396
|
-
([key2]) => key2
|
|
4470
|
+
([key2]) => !PIPELINE_STAMPED_FIELDS2.has(key2)
|
|
4397
4471
|
)
|
|
4398
4472
|
) : priorFindings;
|
|
4399
4473
|
const accepts = (doc) => validateAgainstSchema(doc, schemaPath).valid && resolve("findings", doc).kind === "ok";
|