@jphutchins/code-review 0.1.0-alpha.45 → 0.1.0-alpha.46
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 +195 -131
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/findings.schema.json +49 -0
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);
|
|
@@ -469,30 +517,12 @@ var parseRounds = (body) => {
|
|
|
469
517
|
}
|
|
470
518
|
return kept;
|
|
471
519
|
};
|
|
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;
|
|
520
|
+
var formatScore = (score) => formatConfidence(score);
|
|
521
|
+
var TRAJECTORY_SCORES = 8;
|
|
492
522
|
var roundsSummary = (rounds, count = rounds.length) => {
|
|
493
523
|
if (count === 0) return "";
|
|
494
|
-
const
|
|
495
|
-
const trajectory =
|
|
524
|
+
const cells = rounds.slice(-TRAJECTORY_SCORES).map((r) => typeof r.score === "number" ? formatScore(r.score) : "\u2014");
|
|
525
|
+
const trajectory = cells.length === 0 ? "" : rounds.length > TRAJECTORY_SCORES ? `\u2026 \u2192 ${cells.join(" \u2192 ")}` : cells.join(" \u2192 ");
|
|
496
526
|
return trajectory === "" ? `**Round ${String(count)}**` : `**Round ${String(count)}** \xB7 ${trajectory}`;
|
|
497
527
|
};
|
|
498
528
|
var computeCodeCounts = (findings, systemic = []) => {
|
|
@@ -506,15 +536,6 @@ var computeCodeCounts = (findings, systemic = []) => {
|
|
|
506
536
|
}
|
|
507
537
|
return Object.fromEntries(counts);
|
|
508
538
|
};
|
|
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
539
|
var consecutiveCodeStreaks = (rounds) => {
|
|
519
540
|
const entries = [];
|
|
520
541
|
if (rounds.length === 0) return {};
|
|
@@ -608,6 +629,18 @@ var convergenceScore = (doc, threshold) => round2(
|
|
|
608
629
|
0
|
|
609
630
|
)
|
|
610
631
|
);
|
|
632
|
+
var buildConvergence = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD, priorRounds = [], round = 1, codes = {}, sha) => {
|
|
633
|
+
const score = convergenceScore(doc, threshold);
|
|
634
|
+
const normalized = normalizeCodeCounts(codes, priorRounds[priorRounds.length - 1]?.codes);
|
|
635
|
+
const current = {
|
|
636
|
+
round,
|
|
637
|
+
score,
|
|
638
|
+
...normalized !== void 0 ? { codes: { ...normalized } } : {},
|
|
639
|
+
...sha !== void 0 ? { sha } : {}
|
|
640
|
+
};
|
|
641
|
+
const rounds = [...priorRounds, current].slice(-64);
|
|
642
|
+
return { score, threshold, converged: score <= threshold, rounds };
|
|
643
|
+
};
|
|
611
644
|
var DEFAULT_NIT_VISIBILITY_FLOOR = 0.25;
|
|
612
645
|
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
646
|
var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
@@ -631,26 +664,13 @@ var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
|
631
664
|
}
|
|
632
665
|
return nits;
|
|
633
666
|
};
|
|
634
|
-
var
|
|
635
|
-
|
|
636
|
-
return converged ? `**Convergence** \u{1F3C1} ${String(score)} \u2264 ${String(threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${String(score)} > ${String(threshold)} \u2014 iterating`;
|
|
637
|
-
};
|
|
667
|
+
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`;
|
|
668
|
+
var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => convergenceBadge(convergenceSignal(doc, threshold));
|
|
638
669
|
var SURFACE_SCHEMA_VERSION = "0.8.0";
|
|
639
670
|
var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
640
671
|
const score = convergenceScore(doc, threshold);
|
|
641
672
|
return { score, threshold, converged: score <= threshold };
|
|
642
673
|
};
|
|
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
674
|
var SIGNAL_RE = /<!-- code-review:signal;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
655
675
|
var parseSignalMarker = (body) => {
|
|
656
676
|
const b64 = SIGNAL_RE.exec(body)?.[1];
|
|
@@ -674,6 +694,39 @@ var parseSurfaceSignal = (doc) => {
|
|
|
674
694
|
convergence: { score: c["score"], threshold: c["threshold"], converged: c["converged"] }
|
|
675
695
|
};
|
|
676
696
|
};
|
|
697
|
+
var validStampedConvergence = (raw) => {
|
|
698
|
+
const decoded = ConvergenceCodec.decode(raw);
|
|
699
|
+
return decoded._tag === "Right" && decoded.right.rounds !== void 0 && decoded.right.rounds.length > 0 ? decoded.right : null;
|
|
700
|
+
};
|
|
701
|
+
var parseConvergence = (priorDoc) => {
|
|
702
|
+
if (typeof priorDoc !== "object" || priorDoc === null) return null;
|
|
703
|
+
const raw = priorDoc["convergence"];
|
|
704
|
+
return raw === void 0 ? null : validStampedConvergence(raw);
|
|
705
|
+
};
|
|
706
|
+
var CONVERGENCE_RE = /<!-- code-review:convergence;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
707
|
+
var convergenceMarker = (convergence) => `<!-- code-review:convergence;base64 ${Buffer.from(JSON.stringify(convergence), "utf-8").toString(
|
|
708
|
+
"base64"
|
|
709
|
+
)} -->`;
|
|
710
|
+
var parseConvergenceMarker = (body) => {
|
|
711
|
+
const b64 = CONVERGENCE_RE.exec(body)?.[1];
|
|
712
|
+
return b64 === void 0 ? null : validStampedConvergence(decodeBase64Json(b64));
|
|
713
|
+
};
|
|
714
|
+
var roundRecordsToConvergenceRounds = (records) => records.map((r, i) => ({
|
|
715
|
+
round: r.round ?? i + 1,
|
|
716
|
+
...r.codes !== void 0 ? { codes: { ...r.codes } } : {},
|
|
717
|
+
...r.sha !== void 0 ? { sha: r.sha } : {}
|
|
718
|
+
}));
|
|
719
|
+
var priorTrajectory = (priorDoc, priorBody) => parseConvergence(priorDoc)?.rounds ?? parseConvergenceMarker(priorBody)?.rounds ?? roundRecordsToConvergenceRounds(parseRounds(priorBody));
|
|
720
|
+
var carriedConvergence = (priorDoc, priorBody) => {
|
|
721
|
+
const stamped = parseConvergence(priorDoc) ?? parseConvergenceMarker(priorBody);
|
|
722
|
+
if (stamped !== null) return stamped;
|
|
723
|
+
const signal = parseSignalMarker(priorBody) ?? parseSurfaceSignal(priorDoc);
|
|
724
|
+
if (signal === null) return null;
|
|
725
|
+
const legacy = roundRecordsToConvergenceRounds(parseRounds(priorBody));
|
|
726
|
+
const last = legacy[legacy.length - 1];
|
|
727
|
+
const rounds = last === void 0 ? [{ round: signal.round }] : last.round < signal.round ? [...legacy.slice(0, -1), { ...last, round: signal.round }] : legacy;
|
|
728
|
+
return { ...signal.convergence, rounds };
|
|
729
|
+
};
|
|
677
730
|
var SURFACE_SCHEMA_VERSIONS = ["0.7.0", SURFACE_SCHEMA_VERSION];
|
|
678
731
|
var isSurfaceVersion = (version) => typeof version === "string" && SURFACE_SCHEMA_VERSIONS.includes(version);
|
|
679
732
|
var stripSurfaceFields = (doc) => {
|
|
@@ -689,12 +742,13 @@ var carryForwardMarkers = (body) => {
|
|
|
689
742
|
const findings = /<!-- code-review:findings-json[^>]*-->/.exec(body)?.[0];
|
|
690
743
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
691
744
|
const reviewedRoute = ROUTE_RE.exec(body)?.[0];
|
|
745
|
+
const convergence = CONVERGENCE_RE.exec(body)?.[0];
|
|
692
746
|
const rounds = ROUNDS_RE.exec(body)?.[0];
|
|
693
747
|
const signal = SIGNAL_RE.exec(body)?.[0];
|
|
694
748
|
const completedAncestor = parseReviewComplete(body) || parseCompletedAncestor(body) ? COMPLETED_ANCESTOR_MARKER : void 0;
|
|
695
749
|
const findingsBlock = findings ? `${AGENTS_STOP_DIRECTIVE}
|
|
696
750
|
${findings}` : void 0;
|
|
697
|
-
return [findingsBlock, reviewedSha, reviewedRoute, completedAncestor, rounds, signal].filter((m) => m !== void 0).join("\n\n");
|
|
751
|
+
return [findingsBlock, reviewedSha, reviewedRoute, completedAncestor, convergence, rounds, signal].filter((m) => m !== void 0).join("\n\n");
|
|
698
752
|
};
|
|
699
753
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
700
754
|
var projectPatch = (patch) => {
|
|
@@ -1071,10 +1125,6 @@ var computeSeverityCounts = (findings) => findings.reduce(
|
|
|
1071
1125
|
emptySeverityCounts()
|
|
1072
1126
|
);
|
|
1073
1127
|
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
1128
|
var isConvergenceRound = (route, incomplete) => route === "full review" && !incomplete;
|
|
1079
1129
|
var render = (input) => {
|
|
1080
1130
|
const eta = new Eta({ autoTrim: false });
|
|
@@ -1087,9 +1137,10 @@ var render = (input) => {
|
|
|
1087
1137
|
const effort = input.effort ?? input.envelope?.effort ?? null;
|
|
1088
1138
|
const modelNames = input.envelope ? input.envelope.models.map((m) => m.model).join(", ") : "";
|
|
1089
1139
|
const severityCounts = input.severityCounts ?? computeSeverityCounts(input.findings.findings);
|
|
1090
|
-
const
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1140
|
+
const convergence = input.findings.convergence;
|
|
1141
|
+
const trajectory = convergence?.rounds ?? input.rounds ?? [];
|
|
1142
|
+
const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(trajectory.slice(0, -1), input.findings.findings);
|
|
1143
|
+
const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && trajectory.length > 0)) && isReviewVerdict(input.findings.verdict);
|
|
1093
1144
|
const advisoryAllowed = isFullReviewRound;
|
|
1094
1145
|
return eta.renderString(input.template, {
|
|
1095
1146
|
findings: input.findings,
|
|
@@ -1106,7 +1157,7 @@ var render = (input) => {
|
|
|
1106
1157
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
1107
1158
|
postedAt: input.postedAt ?? "",
|
|
1108
1159
|
severityCounts,
|
|
1109
|
-
convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold)
|
|
1160
|
+
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold),
|
|
1110
1161
|
strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
|
|
1111
1162
|
suppressedNits: (input.suppressedNits ?? []).map(sanitizeSuppressedNit),
|
|
1112
1163
|
nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
|
|
@@ -1115,19 +1166,13 @@ var render = (input) => {
|
|
|
1115
1166
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
1116
1167
|
runUrl: input.runUrl ?? null,
|
|
1117
1168
|
jsonUrl: input.jsonUrl ?? null,
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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) : "",
|
|
1169
|
+
// The blob is the agent's complete document with the pipeline-stamped convergence field inside it
|
|
1170
|
+
// (issue #174) — no separate signal or rounds marker rides beside it. post always supplies the
|
|
1171
|
+
// precomputed marker; the standalone `render` command falls back to encoding the doc here.
|
|
1172
|
+
findingsPointer: input.findingsPointer ?? findingsPointer(input.findings, input.jsonUrl),
|
|
1173
|
+
roundsMarker: "",
|
|
1174
|
+
roundsSummary: roundsSummary(trajectory, input.roundCount),
|
|
1175
|
+
metastasisNote: advisoryAllowed ? metastasisNote(trajectory) : "",
|
|
1131
1176
|
sameRootNotes: advisoryAllowed ? sameRootNotes : {},
|
|
1132
1177
|
answeredNotes: input.answeredNotes ?? {},
|
|
1133
1178
|
answeredReRaiseNote: input.answeredReRaiseNote ?? "",
|
|
@@ -2010,14 +2055,9 @@ var checkLongSuggestions = (comments) => {
|
|
|
2010
2055
|
});
|
|
2011
2056
|
return { comments: adjusted, longFiles };
|
|
2012
2057
|
};
|
|
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);
|
|
2058
|
+
var PIPELINE_STAMPED_FIELDS = /* @__PURE__ */ new Set(["convergence", "scope_metastasis"]);
|
|
2059
|
+
var decodeFindings = (doc) => {
|
|
2060
|
+
const resolution = resolve("findings", doc);
|
|
2021
2061
|
switch (resolution.kind) {
|
|
2022
2062
|
case "ok":
|
|
2023
2063
|
return { kind: "ok", findings: resolution.value };
|
|
@@ -2028,6 +2068,28 @@ var loadFindings = (path) => {
|
|
|
2028
2068
|
return { kind: "invalid-shape" };
|
|
2029
2069
|
}
|
|
2030
2070
|
};
|
|
2071
|
+
var loadFindings = (path) => {
|
|
2072
|
+
let raw;
|
|
2073
|
+
try {
|
|
2074
|
+
raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
2075
|
+
} catch {
|
|
2076
|
+
return { kind: "corrupt" };
|
|
2077
|
+
}
|
|
2078
|
+
const first = decodeFindings(raw);
|
|
2079
|
+
if (first.kind === "invalid-shape" && typeof raw === "object" && raw !== null && !Array.isArray(raw) && Object.keys(raw).some((k) => PIPELINE_STAMPED_FIELDS.has(k))) {
|
|
2080
|
+
const stripped = Object.fromEntries(
|
|
2081
|
+
Object.entries(raw).filter(([key2]) => !PIPELINE_STAMPED_FIELDS.has(key2))
|
|
2082
|
+
);
|
|
2083
|
+
const retry = decodeFindings(stripped);
|
|
2084
|
+
if (retry.kind === "ok") {
|
|
2085
|
+
process.stderr.write(
|
|
2086
|
+
"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"
|
|
2087
|
+
);
|
|
2088
|
+
return retry;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return first;
|
|
2092
|
+
};
|
|
2031
2093
|
var noticeMessageFor = (result) => {
|
|
2032
2094
|
switch (result.kind) {
|
|
2033
2095
|
case "corrupt":
|
|
@@ -2312,11 +2374,24 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
2312
2374
|
const wouldBuryCompleted = (incomplete) => incomplete && existingComplete;
|
|
2313
2375
|
const priorIsFullReview = (body) => isFullReviewSticky(body) || parseReviewedRoute(body) === null && (parseReviewComplete(body) || parseCompletedAncestor(body));
|
|
2314
2376
|
const emptyMechanicWouldBury = (route, incomplete) => route === "mechanic" && !incomplete && findings.findings.length === 0 && existingSticky !== null && priorIsFullReview(existingSticky.body);
|
|
2315
|
-
const
|
|
2316
|
-
const
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
|
|
2377
|
+
const priorDoc = existingSticky !== null ? parseFindingsMarker(existingSticky.body) : null;
|
|
2378
|
+
const priorBody = existingSticky?.body ?? "";
|
|
2379
|
+
const priorTraj = priorTrajectory(priorDoc, priorBody);
|
|
2380
|
+
const priorConv = carriedConvergence(priorDoc, priorBody);
|
|
2381
|
+
const lastRound = (rounds) => rounds.length > 0 ? rounds[rounds.length - 1]?.round ?? rounds.length : 0;
|
|
2382
|
+
const priorRoundCount = Math.max(lastRound(priorTraj), lastRound(priorConv?.rounds ?? []));
|
|
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,
|
|
@@ -3794,9 +3853,13 @@ var renderCmd = defineCommand({
|
|
|
3794
3853
|
const testReport = args["test-report"] ? decode(TestSummaryCodec.decode(readJSON(args["test-report"])), "test report") : void 0;
|
|
3795
3854
|
const route = args.route || envelope.route || null;
|
|
3796
3855
|
const isRound = isConvergenceRound(route, envelope.incomplete === true || isIncompleteFindings(findings)) && isReviewVerdict(findings.verdict);
|
|
3797
|
-
const
|
|
3856
|
+
const threshold = parseConvergenceThreshold(args["convergence-threshold"]);
|
|
3857
|
+
const stampedFindings = {
|
|
3858
|
+
...findings,
|
|
3859
|
+
convergence: isRound ? buildConvergence(findings, threshold) : void 0
|
|
3860
|
+
};
|
|
3798
3861
|
const output2 = render({
|
|
3799
|
-
findings,
|
|
3862
|
+
findings: stampedFindings,
|
|
3800
3863
|
envelope,
|
|
3801
3864
|
prices,
|
|
3802
3865
|
pricesProvided: priceResolution.kind === "provided",
|
|
@@ -3805,9 +3868,9 @@ var renderCmd = defineCommand({
|
|
|
3805
3868
|
route: args.route,
|
|
3806
3869
|
effort: args.effort,
|
|
3807
3870
|
testReport,
|
|
3808
|
-
|
|
3809
|
-
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
3871
|
+
convergenceThreshold: threshold,
|
|
3810
3872
|
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
3873
|
+
convergenceRound: isRound,
|
|
3811
3874
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
3812
3875
|
});
|
|
3813
3876
|
process.stdout.write(output2);
|
|
@@ -4249,6 +4312,7 @@ ${printableSchema(schemaPath)}
|
|
|
4249
4312
|
}
|
|
4250
4313
|
});
|
|
4251
4314
|
var isSurfaceStampedDoc = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) && doc["schema_version"] === SURFACE_SCHEMA_VERSION;
|
|
4315
|
+
var PIPELINE_STAMPED_FIELDS2 = /* @__PURE__ */ new Set(["scope_metastasis", "convergence"]);
|
|
4252
4316
|
var withoutScopeMetastasis = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) ? Object.fromEntries(Object.entries(doc).filter(([key2]) => key2 !== "scope_metastasis")) : doc;
|
|
4253
4317
|
var seedDraftCmd = defineCommand({
|
|
4254
4318
|
meta: {
|
|
@@ -4337,7 +4401,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4337
4401
|
const carried = strippedPrior["scope_metastasis"];
|
|
4338
4402
|
if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return strippedPrior;
|
|
4339
4403
|
if (strippedPrior["verdict"] === "error") return strippedPrior;
|
|
4340
|
-
const computed = computeScopeMetastasis(
|
|
4404
|
+
const computed = computeScopeMetastasis(priorTrajectory(parsedPrior, priorBody ?? ""));
|
|
4341
4405
|
return computed === null ? strippedPrior : { ...strippedPrior, scope_metastasis: computed };
|
|
4342
4406
|
})();
|
|
4343
4407
|
if (args["prior-answers"]) {
|
|
@@ -4393,7 +4457,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4393
4457
|
const schemaPath = args.schema ? resolve$1(args.schema) : schemaPathFor(kind, args["schema-version"]);
|
|
4394
4458
|
const barePrior = typeof priorFindings === "object" && !Array.isArray(priorFindings) ? Object.fromEntries(
|
|
4395
4459
|
Object.entries(priorFindings).filter(
|
|
4396
|
-
([key2]) => key2
|
|
4460
|
+
([key2]) => !PIPELINE_STAMPED_FIELDS2.has(key2)
|
|
4397
4461
|
)
|
|
4398
4462
|
) : priorFindings;
|
|
4399
4463
|
const accepts = (doc) => validateAgainstSchema(doc, schemaPath).valid && resolve("findings", doc).kind === "ok";
|