@jphutchins/code-review 0.1.0-alpha.44 → 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 -28
- package/dist/index.js +320 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/findings.schema.json +50 -1
- package/templates/comment.eta +13 -2
- package/templates/inline.eta +1 -1
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
|
);
|
|
@@ -374,7 +420,8 @@ var severityEmoji = (s) => {
|
|
|
374
420
|
}
|
|
375
421
|
};
|
|
376
422
|
var EMBED_LIMIT = 42700;
|
|
377
|
-
var
|
|
423
|
+
var FINDINGS_SCHEMA_URL = "https://raw.githubusercontent.com/JPHutchins/code-review/main/schema/findings.schema.json";
|
|
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. -->`;
|
|
378
425
|
var b64LengthOf = (document) => Buffer.from(JSON.stringify(document), "utf-8").toString("base64").length;
|
|
379
426
|
var encodeMarker = (document, jsonUrl, limit) => {
|
|
380
427
|
const b64 = Buffer.from(JSON.stringify(document), "utf-8").toString("base64");
|
|
@@ -404,7 +451,9 @@ var ROUTE_RE = /<!-- reviewed-route: ([^>]*) -->/;
|
|
|
404
451
|
var parseReviewedRoute = (body) => ROUTE_RE.exec(body)?.[1] || null;
|
|
405
452
|
var isFullReviewSticky = (body) => {
|
|
406
453
|
const route = parseReviewedRoute(body);
|
|
407
|
-
|
|
454
|
+
if (route === "full review") return true;
|
|
455
|
+
if (route === "mechanic") return false;
|
|
456
|
+
return priorTrajectory(parseFindingsMarker(body), body).length > 0;
|
|
408
457
|
};
|
|
409
458
|
var COMPLETED_ANCESTOR_MARKER = "<!-- review-complete-ancestor -->";
|
|
410
459
|
var parseCompletedAncestor = (body) => body.includes(COMPLETED_ANCESTOR_MARKER);
|
|
@@ -468,30 +517,12 @@ var parseRounds = (body) => {
|
|
|
468
517
|
}
|
|
469
518
|
return kept;
|
|
470
519
|
};
|
|
471
|
-
var
|
|
472
|
-
var
|
|
473
|
-
if (rounds.length === 0) return "";
|
|
474
|
-
const serialize = (kept2) => `<!-- code-review:rounds;base64 ${Buffer.from(JSON.stringify(kept2), "utf-8").toString("base64")} -->`;
|
|
475
|
-
const stripCodes = (n) => rounds.map(
|
|
476
|
-
(r, i) => i < n ? { critical: r.critical, major: r.major, minor: r.minor, nit: r.nit } : r
|
|
477
|
-
);
|
|
478
|
-
let stripped = 0;
|
|
479
|
-
while (stripped < rounds.length && serialize(stripCodes(stripped)).length > ROUNDS_MARKER_LIMIT) {
|
|
480
|
-
stripped += 1;
|
|
481
|
-
}
|
|
482
|
-
const kept = stripCodes(stripped);
|
|
483
|
-
const bounded = serialize(kept).length > ROUNDS_MARKER_LIMIT ? kept.slice(-8) : kept;
|
|
484
|
-
return serialize(bounded);
|
|
485
|
-
};
|
|
486
|
-
var roundChip = (c) => {
|
|
487
|
-
const parts = SEVERITIES.filter((k) => c[k] > 0).map((k) => `${severityEmoji(k)}${String(c[k])}`);
|
|
488
|
-
return parts.length === 0 ? "clean" : parts.join(" ");
|
|
489
|
-
};
|
|
490
|
-
var TRAJECTORY_CHIPS = 8;
|
|
520
|
+
var formatScore = (score) => formatConfidence(score);
|
|
521
|
+
var TRAJECTORY_SCORES = 8;
|
|
491
522
|
var roundsSummary = (rounds, count = rounds.length) => {
|
|
492
523
|
if (count === 0) return "";
|
|
493
|
-
const
|
|
494
|
-
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 ");
|
|
495
526
|
return trajectory === "" ? `**Round ${String(count)}**` : `**Round ${String(count)}** \xB7 ${trajectory}`;
|
|
496
527
|
};
|
|
497
528
|
var computeCodeCounts = (findings, systemic = []) => {
|
|
@@ -505,15 +536,6 @@ var computeCodeCounts = (findings, systemic = []) => {
|
|
|
505
536
|
}
|
|
506
537
|
return Object.fromEntries(counts);
|
|
507
538
|
};
|
|
508
|
-
var roundRecord = (counts, codes, priorCodes, sha, round) => {
|
|
509
|
-
const normalized = normalizeCodeCounts(codes, priorCodes);
|
|
510
|
-
const record3 = normalized === void 0 ? { ...counts } : { ...counts, codes: normalized };
|
|
511
|
-
return {
|
|
512
|
-
...record3,
|
|
513
|
-
...sha !== void 0 ? { sha } : {},
|
|
514
|
-
...round !== void 0 ? { round } : {}
|
|
515
|
-
};
|
|
516
|
-
};
|
|
517
539
|
var consecutiveCodeStreaks = (rounds) => {
|
|
518
540
|
const entries = [];
|
|
519
541
|
if (rounds.length === 0) return {};
|
|
@@ -590,38 +612,65 @@ var computeSameRootNotes = (priorRounds, findings, currentSha) => {
|
|
|
590
612
|
};
|
|
591
613
|
var CONVERGENCE_CEILINGS = { critical: 4, major: 2, minor: 1, nit: 0 };
|
|
592
614
|
var CRITICAL_FLOOR_MARGIN = 0.01;
|
|
615
|
+
var MINOR_FLOOR = 0.1;
|
|
593
616
|
var DEFAULT_CONVERGENCE_THRESHOLD = 1;
|
|
594
|
-
var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : 0;
|
|
617
|
+
var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : severity === "minor" ? MINOR_FLOOR : 0;
|
|
595
618
|
var round2 = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
|
|
619
|
+
var contribution = (severity, confidence, likelihood, threshold) => {
|
|
620
|
+
const floor = convergenceFloor(severity, threshold);
|
|
621
|
+
return floor + Math.max(0, CONVERGENCE_CEILINGS[severity] - floor) * confidence * likelihood;
|
|
622
|
+
};
|
|
596
623
|
var convergenceScore = (doc, threshold) => round2(
|
|
597
|
-
|
|
598
|
-
(sum, { severity, confidence, likelihood }) =>
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
},
|
|
624
|
+
doc.findings.reduce(
|
|
625
|
+
(sum, { severity, confidence, likelihood }) => sum + contribution(severity, confidence, likelihood, threshold),
|
|
626
|
+
0
|
|
627
|
+
) + (doc.systemic_problems ?? []).reduce(
|
|
628
|
+
(sum, { severity, confidence }) => sum + contribution(severity, confidence, 1, threshold),
|
|
602
629
|
0
|
|
603
630
|
)
|
|
604
631
|
);
|
|
605
|
-
var
|
|
606
|
-
const
|
|
607
|
-
|
|
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
|
+
};
|
|
644
|
+
var DEFAULT_NIT_VISIBILITY_FLOOR = 0.25;
|
|
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;
|
|
646
|
+
var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
647
|
+
if (typeof priorDoc !== "object" || priorDoc === null) return [];
|
|
648
|
+
const arr = priorDoc["findings"];
|
|
649
|
+
if (!Array.isArray(arr)) return [];
|
|
650
|
+
const nits = [];
|
|
651
|
+
for (const f of arr) {
|
|
652
|
+
if (typeof f !== "object" || f === null) continue;
|
|
653
|
+
const rec = f;
|
|
654
|
+
if (!isBelowVisibilityFloor(rec, floor)) continue;
|
|
655
|
+
const title = rec["title"];
|
|
656
|
+
if (typeof title !== "string") continue;
|
|
657
|
+
const code = typeof rec["code"] === "string" && rec["code"] !== "" ? rec["code"] : void 0;
|
|
658
|
+
const path = typeof rec["path"] === "string" ? rec["path"] : void 0;
|
|
659
|
+
nits.push({
|
|
660
|
+
title,
|
|
661
|
+
...code !== void 0 ? { code } : {},
|
|
662
|
+
...path !== void 0 ? { path } : {}
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return nits;
|
|
608
666
|
};
|
|
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));
|
|
609
669
|
var SURFACE_SCHEMA_VERSION = "0.8.0";
|
|
610
670
|
var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
611
671
|
const score = convergenceScore(doc, threshold);
|
|
612
672
|
return { score, threshold, converged: score <= threshold };
|
|
613
673
|
};
|
|
614
|
-
var signalForRound = (round, doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(doc, threshold) });
|
|
615
|
-
var signalMarker = (signal) => `<!-- code-review:signal;base64 ${Buffer.from(
|
|
616
|
-
JSON.stringify({ schema_version: SURFACE_SCHEMA_VERSION, ...signal }),
|
|
617
|
-
"utf-8"
|
|
618
|
-
).toString("base64")} -->`;
|
|
619
|
-
var joinSignalMarker = (base, marker) => base === "" ? marker : `${base}
|
|
620
|
-
${marker}`;
|
|
621
|
-
var surfacedFindingsPointer = (findings, signal, jsonUrl) => {
|
|
622
|
-
const marker = findingsPointer(findings, jsonUrl);
|
|
623
|
-
return signal === null ? marker : joinSignalMarker(marker, signalMarker(signal));
|
|
624
|
-
};
|
|
625
674
|
var SIGNAL_RE = /<!-- code-review:signal;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
626
675
|
var parseSignalMarker = (body) => {
|
|
627
676
|
const b64 = SIGNAL_RE.exec(body)?.[1];
|
|
@@ -645,6 +694,39 @@ var parseSurfaceSignal = (doc) => {
|
|
|
645
694
|
convergence: { score: c["score"], threshold: c["threshold"], converged: c["converged"] }
|
|
646
695
|
};
|
|
647
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
|
+
};
|
|
648
730
|
var SURFACE_SCHEMA_VERSIONS = ["0.7.0", SURFACE_SCHEMA_VERSION];
|
|
649
731
|
var isSurfaceVersion = (version) => typeof version === "string" && SURFACE_SCHEMA_VERSIONS.includes(version);
|
|
650
732
|
var stripSurfaceFields = (doc) => {
|
|
@@ -660,12 +742,13 @@ var carryForwardMarkers = (body) => {
|
|
|
660
742
|
const findings = /<!-- code-review:findings-json[^>]*-->/.exec(body)?.[0];
|
|
661
743
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
662
744
|
const reviewedRoute = ROUTE_RE.exec(body)?.[0];
|
|
745
|
+
const convergence = CONVERGENCE_RE.exec(body)?.[0];
|
|
663
746
|
const rounds = ROUNDS_RE.exec(body)?.[0];
|
|
664
747
|
const signal = SIGNAL_RE.exec(body)?.[0];
|
|
665
748
|
const completedAncestor = parseReviewComplete(body) || parseCompletedAncestor(body) ? COMPLETED_ANCESTOR_MARKER : void 0;
|
|
666
749
|
const findingsBlock = findings ? `${AGENTS_STOP_DIRECTIVE}
|
|
667
750
|
${findings}` : void 0;
|
|
668
|
-
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");
|
|
669
752
|
};
|
|
670
753
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
671
754
|
var projectPatch = (patch) => {
|
|
@@ -1018,6 +1101,13 @@ var sanitizeFinding = (f, answeredNotes) => {
|
|
|
1018
1101
|
answeredNote: answeredNotes !== void 0 && Object.prototype.hasOwnProperty.call(answeredNotes, key2) ? answeredNotes[key2] ?? "" : ""
|
|
1019
1102
|
};
|
|
1020
1103
|
};
|
|
1104
|
+
var sanitizeSuppressedNit = (f) => ({
|
|
1105
|
+
title: escapeCodeBackticks(f.title),
|
|
1106
|
+
...f.code !== void 0 ? { code: escapeCodeBackticks(f.code) } : {},
|
|
1107
|
+
path: escapeCodeBackticks(f.path),
|
|
1108
|
+
startLine: f.start_line,
|
|
1109
|
+
m: formatConfidence(f.confidence * f.likelihood)
|
|
1110
|
+
});
|
|
1021
1111
|
var sanitizeSystemic = (s) => ({
|
|
1022
1112
|
...s,
|
|
1023
1113
|
title: escapePipes(s.title),
|
|
@@ -1035,10 +1125,6 @@ var computeSeverityCounts = (findings) => findings.reduce(
|
|
|
1035
1125
|
emptySeverityCounts()
|
|
1036
1126
|
);
|
|
1037
1127
|
var isReviewVerdict = (verdict) => verdict !== "error";
|
|
1038
|
-
var computeRoundCounts = (findings) => (findings.systemic_problems ?? []).reduce(
|
|
1039
|
-
(acc, s) => s.severity in acc ? { ...acc, [s.severity]: acc[s.severity] + 1 } : acc,
|
|
1040
|
-
computeSeverityCounts(findings.findings)
|
|
1041
|
-
);
|
|
1042
1128
|
var isConvergenceRound = (route, incomplete) => route === "full review" && !incomplete;
|
|
1043
1129
|
var render = (input) => {
|
|
1044
1130
|
const eta = new Eta({ autoTrim: false });
|
|
@@ -1051,9 +1137,10 @@ var render = (input) => {
|
|
|
1051
1137
|
const effort = input.effort ?? input.envelope?.effort ?? null;
|
|
1052
1138
|
const modelNames = input.envelope ? input.envelope.models.map((m) => m.model).join(", ") : "";
|
|
1053
1139
|
const severityCounts = input.severityCounts ?? computeSeverityCounts(input.findings.findings);
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
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);
|
|
1057
1144
|
const advisoryAllowed = isFullReviewRound;
|
|
1058
1145
|
return eta.renderString(input.template, {
|
|
1059
1146
|
findings: input.findings,
|
|
@@ -1070,26 +1157,22 @@ var render = (input) => {
|
|
|
1070
1157
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
1071
1158
|
postedAt: input.postedAt ?? "",
|
|
1072
1159
|
severityCounts,
|
|
1073
|
-
convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold)
|
|
1160
|
+
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold),
|
|
1074
1161
|
strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
|
|
1162
|
+
suppressedNits: (input.suppressedNits ?? []).map(sanitizeSuppressedNit),
|
|
1163
|
+
nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
|
|
1075
1164
|
systemic: (input.findings.systemic_problems ?? []).map(sanitizeSystemic),
|
|
1076
1165
|
unanchoredCount: input.unanchoredCount ?? 0,
|
|
1077
1166
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
1078
1167
|
runUrl: input.runUrl ?? null,
|
|
1079
1168
|
jsonUrl: input.jsonUrl ?? null,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, input.findings, input.convergenceThreshold) : null,
|
|
1088
|
-
input.jsonUrl
|
|
1089
|
-
),
|
|
1090
|
-
roundsMarker: roundsMarker(rounds),
|
|
1091
|
-
roundsSummary: roundsSummary(rounds, input.roundCount),
|
|
1092
|
-
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) : "",
|
|
1093
1176
|
sameRootNotes: advisoryAllowed ? sameRootNotes : {},
|
|
1094
1177
|
answeredNotes: input.answeredNotes ?? {},
|
|
1095
1178
|
answeredReRaiseNote: input.answeredReRaiseNote ?? "",
|
|
@@ -1454,6 +1537,7 @@ var sidecarPath = (draftPath, postfix) => {
|
|
|
1454
1537
|
};
|
|
1455
1538
|
var priorContextPath = (draftPath) => sidecarPath(draftPath, ".prior");
|
|
1456
1539
|
var priorAnswersPath = (draftPath) => sidecarPath(draftPath, ".prior-answers");
|
|
1540
|
+
var priorSuppressedPath = (draftPath) => sidecarPath(draftPath, ".prior-suppressed");
|
|
1457
1541
|
var lastValidPath = (draftPath) => sidecarPath(draftPath, ".last-valid");
|
|
1458
1542
|
var spawnFloorMessage = (draftPath) => `Write your own first-pass findings to ${draftPath} before spawning subagents \u2014 a review must never depend on subagents alone, and the pre-seeded $DRAFT is a non-review sentinel: it does not count until you have replaced it with your own review. Write ${draftPath} from what you have read so far (preliminary findings are fine), run \`code-review validate ${draftPath} --explain\` until it passes, then fan out; your subagents run in the background, so keep refining the draft as their reports arrive.`;
|
|
1459
1543
|
var forceBackgroundSpawn = (toolInput) => ({
|
|
@@ -1971,14 +2055,9 @@ var checkLongSuggestions = (comments) => {
|
|
|
1971
2055
|
});
|
|
1972
2056
|
return { comments: adjusted, longFiles };
|
|
1973
2057
|
};
|
|
1974
|
-
var
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
1978
|
-
} catch {
|
|
1979
|
-
return { kind: "corrupt" };
|
|
1980
|
-
}
|
|
1981
|
-
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);
|
|
1982
2061
|
switch (resolution.kind) {
|
|
1983
2062
|
case "ok":
|
|
1984
2063
|
return { kind: "ok", findings: resolution.value };
|
|
@@ -1989,6 +2068,28 @@ var loadFindings = (path) => {
|
|
|
1989
2068
|
return { kind: "invalid-shape" };
|
|
1990
2069
|
}
|
|
1991
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
|
+
};
|
|
1992
2093
|
var noticeMessageFor = (result) => {
|
|
1993
2094
|
switch (result.kind) {
|
|
1994
2095
|
case "corrupt":
|
|
@@ -2273,11 +2374,24 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
2273
2374
|
const wouldBuryCompleted = (incomplete) => incomplete && existingComplete;
|
|
2274
2375
|
const priorIsFullReview = (body) => isFullReviewSticky(body) || parseReviewedRoute(body) === null && (parseReviewComplete(body) || parseCompletedAncestor(body));
|
|
2275
2376
|
const emptyMechanicWouldBury = (route, incomplete) => route === "mechanic" && !incomplete && findings.findings.length === 0 && existingSticky !== null && priorIsFullReview(existingSticky.body);
|
|
2276
|
-
const
|
|
2277
|
-
const
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
|
|
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}`;
|
|
2281
2395
|
};
|
|
2282
2396
|
const leaveInPlace = (message) => {
|
|
2283
2397
|
process.stderr.write(
|
|
@@ -2321,7 +2435,7 @@ ${dropNote}` : ""}`,
|
|
|
2321
2435
|
const template = readFileSync(input.templatePath, "utf-8");
|
|
2322
2436
|
const inlineTemplate = readFileSync(input.inlineTemplatePath, "utf-8");
|
|
2323
2437
|
const renderNotice = (message) => {
|
|
2324
|
-
const findings2 = incompleteFindings(`### \u26A0\uFE0F ${message}`);
|
|
2438
|
+
const findings2 = stampConvergence(incompleteFindings(`### \u26A0\uFE0F ${message}`), priorConv);
|
|
2325
2439
|
return formatMarkdown(
|
|
2326
2440
|
render({
|
|
2327
2441
|
findings: findings2,
|
|
@@ -2333,17 +2447,12 @@ ${dropNote}` : ""}`,
|
|
|
2333
2447
|
route: input.route,
|
|
2334
2448
|
reviewedSha: input.headSha,
|
|
2335
2449
|
effort: input.effort,
|
|
2336
|
-
rounds: priorRounds,
|
|
2337
2450
|
sameRootNotes: {},
|
|
2338
|
-
roundCount:
|
|
2451
|
+
roundCount: priorRoundCount,
|
|
2339
2452
|
convergenceRound: false,
|
|
2340
2453
|
runUrl: input.runUrl,
|
|
2341
2454
|
jsonUrl: input.jsonUrl,
|
|
2342
|
-
|
|
2343
|
-
// stop signal for a run that produced no verdict (issue #141 review r2). The prior signal
|
|
2344
|
-
// survives on the sticky in the compact marker (findingsMarkerFor), and the carried-forward
|
|
2345
|
-
// trajectory (rounds marker) remains the historical record.
|
|
2346
|
-
findingsPointer: findingsMarkerFor(findings2, null),
|
|
2455
|
+
findingsPointer: findingsBlob(findings2),
|
|
2347
2456
|
postedAt: input.postedAt
|
|
2348
2457
|
})
|
|
2349
2458
|
);
|
|
@@ -2393,6 +2502,12 @@ ${dropNote}` : ""}`,
|
|
|
2393
2502
|
findings: [...answeredFilter.findings],
|
|
2394
2503
|
...systemic.length > 0 ? { systemic_problems: systemic } : {}
|
|
2395
2504
|
};
|
|
2505
|
+
const priorSuppressedKeys = new Set(
|
|
2506
|
+
(existingSticky !== null && isFullReviewSticky(existingSticky.body) ? priorBelowFloorNits(parseFindingsMarker(existingSticky.body), input.nitVisibilityFloor) : []).map((n) => answeredNoteKey({ code: n.code, title: n.title }))
|
|
2507
|
+
);
|
|
2508
|
+
const isSuppressedNit = (f) => f.severity === "nit" && (isBelowVisibilityFloor(f, input.nitVisibilityFloor) || priorSuppressedKeys.has(answeredNoteKey(f)));
|
|
2509
|
+
const suppressedNits = findings.findings.filter(isSuppressedNit);
|
|
2510
|
+
const visibleFindings = findings.findings.filter((f) => !isSuppressedNit(f));
|
|
2396
2511
|
const answeredDropNote = answeredReRaiseNote(verbatimReRaised, droppedCount) + (verbatimReRaised.length > 0 && findings.findings.length === 0 ? "\n> _The stop signal reflects the kept findings \u2014 this round carries none._" : "");
|
|
2397
2512
|
const envelope = loadEnvelope(input.envelopePath);
|
|
2398
2513
|
const testReport = input.testReportPath ? loadTestReport(input.testReportPath) : void 0;
|
|
@@ -2405,9 +2520,10 @@ ${dropNote}` : ""}`,
|
|
|
2405
2520
|
}
|
|
2406
2521
|
if (emptyMechanicWouldBury(effectiveRoute, envelopelessIncomplete) && existingSticky !== null)
|
|
2407
2522
|
await emptyMechanicLeaveOrNote(existingSticky);
|
|
2523
|
+
const stampedFindings2 = stampConvergence(findings, priorConv);
|
|
2408
2524
|
const body = formatMarkdown(
|
|
2409
2525
|
render({
|
|
2410
|
-
findings,
|
|
2526
|
+
findings: stampedFindings2,
|
|
2411
2527
|
envelope: null,
|
|
2412
2528
|
incomplete: envelopelessIncomplete,
|
|
2413
2529
|
prices: decodedPrices.right,
|
|
@@ -2416,27 +2532,26 @@ ${dropNote}` : ""}`,
|
|
|
2416
2532
|
route: effectiveRoute,
|
|
2417
2533
|
reviewedSha: input.headSha,
|
|
2418
2534
|
effort: input.effort,
|
|
2419
|
-
rounds: priorRounds,
|
|
2420
2535
|
sameRootNotes: {},
|
|
2421
2536
|
// The answered-state honesty rules apply on EVERY surface that renders the filtered
|
|
2422
|
-
// findings — the lost-envelope branch lists every finding (no inline review exists to
|
|
2537
|
+
// findings — the lost-envelope branch lists every VISIBLE finding (no inline review exists to
|
|
2423
2538
|
// carry them) so the kept re-raises' annotations actually render, and names the drops
|
|
2424
|
-
// exactly like the main path (issues #151 review r1 + r2).
|
|
2425
|
-
|
|
2539
|
+
// exactly like the main path (issues #151 review r1 + r2). The nit visibility floor applies
|
|
2540
|
+
// here too (issue #164): below-floor nits are hidden from the human list and shown only in the
|
|
2541
|
+
// collapsed aside — the floor is a human-visibility policy, not an inline-comment policy, so it
|
|
2542
|
+
// must hold on the surface that lists findings without an inline review.
|
|
2543
|
+
strays: visibleFindings,
|
|
2544
|
+
suppressedNits,
|
|
2545
|
+
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2426
2546
|
answeredNotes: reRaisedNotes,
|
|
2427
2547
|
answeredReRaiseNote: answeredDropNote,
|
|
2428
|
-
roundCount:
|
|
2548
|
+
roundCount: priorRoundCount,
|
|
2429
2549
|
convergenceRound: false,
|
|
2430
2550
|
testReport,
|
|
2431
2551
|
inlineDisposition: { kind: "no-envelope" },
|
|
2432
2552
|
runUrl: input.runUrl,
|
|
2433
2553
|
jsonUrl: input.jsonUrl,
|
|
2434
|
-
|
|
2435
|
-
// ride the prior signal on the compact marker, never inside the blob.
|
|
2436
|
-
findingsPointer: findingsMarkerFor(
|
|
2437
|
-
findings,
|
|
2438
|
-
isReviewVerdict(findings.verdict) ? priorSignal : null
|
|
2439
|
-
),
|
|
2554
|
+
findingsPointer: findingsBlob(stampedFindings2),
|
|
2440
2555
|
postedAt: input.postedAt
|
|
2441
2556
|
})
|
|
2442
2557
|
);
|
|
@@ -2454,12 +2569,12 @@ ${dropNote}` : ""}`,
|
|
|
2454
2569
|
if (emptyMechanicWouldBury(effectiveRoute, thisIncomplete) && existingSticky !== null)
|
|
2455
2570
|
await emptyMechanicLeaveOrNote(existingSticky);
|
|
2456
2571
|
const isRound = isConvergenceRound(effectiveRoute, thisIncomplete) && isReviewVerdict(findings.verdict);
|
|
2457
|
-
const sameRootNotes = isRound ? computeSameRootNotes(
|
|
2572
|
+
const sameRootNotes = isRound ? computeSameRootNotes(priorTraj, findings.findings, input.headSha.slice(0, 12)) : {};
|
|
2458
2573
|
const {
|
|
2459
2574
|
comments: rawComments,
|
|
2460
2575
|
strays,
|
|
2461
2576
|
inDiff
|
|
2462
|
-
} = buildInlineComments(
|
|
2577
|
+
} = buildInlineComments(visibleFindings, diff, {
|
|
2463
2578
|
inlineTemplate,
|
|
2464
2579
|
models: envelope.models.map((m) => m.model),
|
|
2465
2580
|
findings,
|
|
@@ -2478,35 +2593,30 @@ ${dropNote}` : ""}`,
|
|
|
2478
2593
|
const initialDisposition = comments.length === 0 && strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
2479
2594
|
const currentCounts = computeSeverityCounts(findings.findings);
|
|
2480
2595
|
const currentCodes = computeCodeCounts(findings.findings, findings.systemic_problems ?? []);
|
|
2481
|
-
const
|
|
2482
|
-
const
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
const
|
|
2494
|
-
const findingsMarker = findingsMarkerFor(findings, signal);
|
|
2495
|
-
const markerForm = findingsMarkerForm(findings, input.jsonUrl);
|
|
2496
|
-
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);
|
|
2497
2609
|
if (markerForm === "link") {
|
|
2498
2610
|
process.stderr.write(
|
|
2499
|
-
|
|
2500
|
-
`
|
|
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"
|
|
2501
2612
|
);
|
|
2502
2613
|
} else if (markerForm === "omitted") {
|
|
2503
2614
|
process.stderr.write(
|
|
2504
|
-
|
|
2505
|
-
`
|
|
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"
|
|
2506
2616
|
);
|
|
2507
2617
|
}
|
|
2508
2618
|
const commonRenderInput = {
|
|
2509
|
-
findings,
|
|
2619
|
+
findings: stampedFindings,
|
|
2510
2620
|
envelope,
|
|
2511
2621
|
incomplete: thisIncomplete,
|
|
2512
2622
|
prices: decodedPrices.right,
|
|
@@ -2517,14 +2627,15 @@ ${dropNote}` : ""}`,
|
|
|
2517
2627
|
effort: input.effort,
|
|
2518
2628
|
testReport,
|
|
2519
2629
|
severityCounts: currentCounts,
|
|
2520
|
-
rounds,
|
|
2521
2630
|
sameRootNotes,
|
|
2522
2631
|
answeredNotes: reRaisedNotes,
|
|
2523
2632
|
answeredReRaiseNote: answeredDropNote,
|
|
2524
|
-
roundCount:
|
|
2633
|
+
roundCount: currentRoundCount,
|
|
2525
2634
|
convergenceThreshold: input.convergenceThreshold,
|
|
2635
|
+
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2526
2636
|
convergenceRound: isRound,
|
|
2527
2637
|
strays,
|
|
2638
|
+
suppressedNits,
|
|
2528
2639
|
runUrl: input.runUrl,
|
|
2529
2640
|
jsonUrl: input.jsonUrl,
|
|
2530
2641
|
findingsPointer: findingsMarker,
|
|
@@ -3681,7 +3792,8 @@ var resolvePrices = (pricesArg) => {
|
|
|
3681
3792
|
return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
|
|
3682
3793
|
};
|
|
3683
3794
|
var TEST_REPORT_DESCRIPTION = 'Path to a JSON test summary: {"passed": number, "failed": number, "total": number, "failures"?: [{"name": string, "message"?: string}]}';
|
|
3684
|
-
var CONVERGENCE_THRESHOLD_DESCRIPTION = "Advisory convergence tolerance: the per-finding convergence score (each finding's floor + confidence-weighted headroom; ceilings critical 4 \xB7 major 2 \xB7 minor 1 \xB7 nit 0) at or below which the sticky reads as converged (default: 1)";
|
|
3795
|
+
var CONVERGENCE_THRESHOLD_DESCRIPTION = "Advisory convergence tolerance: the per-finding convergence score (each finding's severity floor + confidence-and-likelihood-weighted headroom; ceilings critical 4 \xB7 major 2 \xB7 minor 1 \xB7 nit 0) at or below which the sticky reads as converged. The floor values and the systemic-likelihood rule are documented in the README and the findings schema (default: 1)";
|
|
3796
|
+
var NIT_VISIBILITY_FLOOR_DESCRIPTION = "Nit visibility floor: nits whose confidence \xD7 likelihood falls below this are hidden from humans (no inline comment; a collapsed aside in the sticky) but kept in the machine blob as adjudicated. In [0, 1] (default: 0.25)";
|
|
3685
3797
|
var renderCmd = defineCommand({
|
|
3686
3798
|
meta: {
|
|
3687
3799
|
name: "render",
|
|
@@ -3725,6 +3837,10 @@ var renderCmd = defineCommand({
|
|
|
3725
3837
|
"convergence-threshold": {
|
|
3726
3838
|
type: "string",
|
|
3727
3839
|
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
3840
|
+
},
|
|
3841
|
+
"nit-visibility-floor": {
|
|
3842
|
+
type: "string",
|
|
3843
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
3728
3844
|
}
|
|
3729
3845
|
},
|
|
3730
3846
|
run: async ({ args }) => {
|
|
@@ -3737,9 +3853,13 @@ var renderCmd = defineCommand({
|
|
|
3737
3853
|
const testReport = args["test-report"] ? decode(TestSummaryCodec.decode(readJSON(args["test-report"])), "test report") : void 0;
|
|
3738
3854
|
const route = args.route || envelope.route || null;
|
|
3739
3855
|
const isRound = isConvergenceRound(route, envelope.incomplete === true || isIncompleteFindings(findings)) && isReviewVerdict(findings.verdict);
|
|
3740
|
-
const
|
|
3856
|
+
const threshold = parseConvergenceThreshold(args["convergence-threshold"]);
|
|
3857
|
+
const stampedFindings = {
|
|
3858
|
+
...findings,
|
|
3859
|
+
convergence: isRound ? buildConvergence(findings, threshold) : void 0
|
|
3860
|
+
};
|
|
3741
3861
|
const output2 = render({
|
|
3742
|
-
findings,
|
|
3862
|
+
findings: stampedFindings,
|
|
3743
3863
|
envelope,
|
|
3744
3864
|
prices,
|
|
3745
3865
|
pricesProvided: priceResolution.kind === "provided",
|
|
@@ -3748,8 +3868,9 @@ var renderCmd = defineCommand({
|
|
|
3748
3868
|
route: args.route,
|
|
3749
3869
|
effort: args.effort,
|
|
3750
3870
|
testReport,
|
|
3751
|
-
|
|
3752
|
-
|
|
3871
|
+
convergenceThreshold: threshold,
|
|
3872
|
+
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
3873
|
+
convergenceRound: isRound,
|
|
3753
3874
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
3754
3875
|
});
|
|
3755
3876
|
process.stdout.write(output2);
|
|
@@ -3774,13 +3895,19 @@ var inlineCmd = defineCommand({
|
|
|
3774
3895
|
template: {
|
|
3775
3896
|
type: "string",
|
|
3776
3897
|
description: "Path to inline comment Eta template (default: bundled templates/inline.eta)"
|
|
3898
|
+
},
|
|
3899
|
+
"nit-visibility-floor": {
|
|
3900
|
+
type: "string",
|
|
3901
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
3777
3902
|
}
|
|
3778
3903
|
},
|
|
3779
3904
|
run: async ({ args }) => {
|
|
3780
3905
|
const findings = decode(FindingsCodec.decode(readJSON(args.findings)), "findings");
|
|
3781
3906
|
const diff = readFileSync(resolve$1(args.diff), "utf-8");
|
|
3782
3907
|
const inlineTemplate = readFileSync(resolveInlineTemplatePath(args.template), "utf-8");
|
|
3783
|
-
const
|
|
3908
|
+
const floor = parseNitVisibilityFloor(args["nit-visibility-floor"]);
|
|
3909
|
+
const visibleFindings = findings.findings.filter((f) => !isBelowVisibilityFloor(f, floor));
|
|
3910
|
+
const { comments, strays } = buildInlineComments(visibleFindings, diff, {
|
|
3784
3911
|
inlineTemplate,
|
|
3785
3912
|
findings
|
|
3786
3913
|
});
|
|
@@ -3882,6 +4009,26 @@ var parseConvergenceThreshold = (raw) => {
|
|
|
3882
4009
|
}
|
|
3883
4010
|
return n;
|
|
3884
4011
|
};
|
|
4012
|
+
var parseNitVisibilityFloor = (raw) => {
|
|
4013
|
+
const trimmed = raw?.trim();
|
|
4014
|
+
if (trimmed === void 0 || trimmed === "") return void 0;
|
|
4015
|
+
if (!/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4016
|
+
fail(`--nit-visibility-floor must be a number in [0, 1]; got "${trimmed}"`);
|
|
4017
|
+
}
|
|
4018
|
+
const n = Number.parseFloat(trimmed);
|
|
4019
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
4020
|
+
fail(
|
|
4021
|
+
`--nit-visibility-floor must be in [0, 1] (it gates confidence \xD7 likelihood); got "${trimmed}"`
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
return n;
|
|
4025
|
+
};
|
|
4026
|
+
var parseNitVisibilityFloorLenient = (raw) => {
|
|
4027
|
+
const trimmed = raw?.trim();
|
|
4028
|
+
if (trimmed === void 0 || trimmed === "" || !/^\d+(\.\d+)?$/.test(trimmed)) return void 0;
|
|
4029
|
+
const n = Number.parseFloat(trimmed);
|
|
4030
|
+
return Number.isFinite(n) && n >= 0 && n <= 1 ? n : void 0;
|
|
4031
|
+
};
|
|
3885
4032
|
var transcriptPathOf = (input) => {
|
|
3886
4033
|
const tp = (typeof input === "object" && input !== null ? input : {})["transcript_path"];
|
|
3887
4034
|
return typeof tp === "string" ? tp : void 0;
|
|
@@ -4165,6 +4312,7 @@ ${printableSchema(schemaPath)}
|
|
|
4165
4312
|
}
|
|
4166
4313
|
});
|
|
4167
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"]);
|
|
4168
4316
|
var withoutScopeMetastasis = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) ? Object.fromEntries(Object.entries(doc).filter(([key2]) => key2 !== "scope_metastasis")) : doc;
|
|
4169
4317
|
var seedDraftCmd = defineCommand({
|
|
4170
4318
|
meta: {
|
|
@@ -4180,6 +4328,10 @@ var seedDraftCmd = defineCommand({
|
|
|
4180
4328
|
type: "string",
|
|
4181
4329
|
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"
|
|
4182
4330
|
},
|
|
4331
|
+
"nit-visibility-floor": {
|
|
4332
|
+
type: "string",
|
|
4333
|
+
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"
|
|
4334
|
+
},
|
|
4183
4335
|
"head-sha": {
|
|
4184
4336
|
type: "string",
|
|
4185
4337
|
description: "Current head SHA, compared against the prior review's embedded reviewed-sha to distinguish a same-commit re-review from a new-commit one; an unknown or mismatched prior SHA is treated as a new commit"
|
|
@@ -4249,7 +4401,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4249
4401
|
const carried = strippedPrior["scope_metastasis"];
|
|
4250
4402
|
if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return strippedPrior;
|
|
4251
4403
|
if (strippedPrior["verdict"] === "error") return strippedPrior;
|
|
4252
|
-
const computed = computeScopeMetastasis(
|
|
4404
|
+
const computed = computeScopeMetastasis(priorTrajectory(parsedPrior, priorBody ?? ""));
|
|
4253
4405
|
return computed === null ? strippedPrior : { ...strippedPrior, scope_metastasis: computed };
|
|
4254
4406
|
})();
|
|
4255
4407
|
if (args["prior-answers"]) {
|
|
@@ -4275,6 +4427,27 @@ var seedDraftCmd = defineCommand({
|
|
|
4275
4427
|
} catch (err) {
|
|
4276
4428
|
process.stderr.write(
|
|
4277
4429
|
`Warning: could not read the answered-findings registry ${args["prior-answers"]} (${errMsg(err)}) \u2014 no prior-answers sidecar
|
|
4430
|
+
`
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
if (parsedPrior !== null && parseReviewedRoute(priorBody ?? "") === "full review") {
|
|
4435
|
+
try {
|
|
4436
|
+
const belowFloor = priorBelowFloorNits(
|
|
4437
|
+
parsedPrior,
|
|
4438
|
+
parseNitVisibilityFloorLenient(args["nit-visibility-floor"])
|
|
4439
|
+
);
|
|
4440
|
+
if (belowFloor.length > 0) {
|
|
4441
|
+
writeFileSync(priorSuppressedPath(outPath), `${JSON.stringify(belowFloor, null, 2)}
|
|
4442
|
+
`);
|
|
4443
|
+
process.stderr.write(
|
|
4444
|
+
`Seeded ${priorSuppressedPath(outPath)} with ${String(belowFloor.length)} below-floor nit(s) as adjudicated context
|
|
4445
|
+
`
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
} catch (err) {
|
|
4449
|
+
process.stderr.write(
|
|
4450
|
+
`Warning: could not derive the prior below-floor nits (${errMsg(err)}) \u2014 no prior-suppressed sidecar
|
|
4278
4451
|
`
|
|
4279
4452
|
);
|
|
4280
4453
|
}
|
|
@@ -4284,7 +4457,7 @@ var seedDraftCmd = defineCommand({
|
|
|
4284
4457
|
const schemaPath = args.schema ? resolve$1(args.schema) : schemaPathFor(kind, args["schema-version"]);
|
|
4285
4458
|
const barePrior = typeof priorFindings === "object" && !Array.isArray(priorFindings) ? Object.fromEntries(
|
|
4286
4459
|
Object.entries(priorFindings).filter(
|
|
4287
|
-
([key2]) => key2
|
|
4460
|
+
([key2]) => !PIPELINE_STAMPED_FIELDS2.has(key2)
|
|
4288
4461
|
)
|
|
4289
4462
|
) : priorFindings;
|
|
4290
4463
|
const accepts = (doc) => validateAgainstSchema(doc, schemaPath).valid && resolve("findings", doc).kind === "ok";
|
|
@@ -4789,6 +4962,10 @@ var postCmd = defineCommand({
|
|
|
4789
4962
|
"convergence-threshold": {
|
|
4790
4963
|
type: "string",
|
|
4791
4964
|
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
4965
|
+
},
|
|
4966
|
+
"nit-visibility-floor": {
|
|
4967
|
+
type: "string",
|
|
4968
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
4792
4969
|
}
|
|
4793
4970
|
},
|
|
4794
4971
|
run: async ({ args }) => {
|
|
@@ -4810,6 +4987,7 @@ var postCmd = defineCommand({
|
|
|
4810
4987
|
runUrl: args["run-url"],
|
|
4811
4988
|
jsonUrl: args["json-url"],
|
|
4812
4989
|
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
4990
|
+
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
4813
4991
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
4814
4992
|
});
|
|
4815
4993
|
}
|