@jphutchins/code-review 0.1.0-alpha.43 → 0.1.0-alpha.45
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 +12 -6
- package/dist/index.js +161 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +4 -3
- package/schema/findings.schema.json +15 -2
- package/templates/comment.eta +13 -2
- package/templates/inline.eta +1 -1
package/README.md
CHANGED
|
@@ -104,14 +104,14 @@ links to:
|
|
|
104
104
|
[`src/surface.ts`](src/surface.ts).
|
|
105
105
|
|
|
106
106
|
The embedded document is the agent's **complete** findings document — the same `schema_version`
|
|
107
|
-
0.
|
|
107
|
+
0.9.0 contract the review agent is held to and
|
|
108
108
|
[`schema/findings.schema.json`](schema/findings.schema.json) validates, and the same object the
|
|
109
109
|
comment is rendered from. It is embedded verbatim: no field is added or dropped, so the machine
|
|
110
110
|
channel can never carry less than, or drift from, the rendered prose.
|
|
111
111
|
|
|
112
112
|
```json
|
|
113
113
|
{
|
|
114
|
-
"schema_version": "0.
|
|
114
|
+
"schema_version": "0.9.0",
|
|
115
115
|
"verdict": "comment",
|
|
116
116
|
"summary": "...",
|
|
117
117
|
"findings": []
|
|
@@ -121,8 +121,12 @@ links to:
|
|
|
121
121
|
The deterministic **stop signal** for an iterating author-agent rides its own compact marker beside
|
|
122
122
|
the findings blob, `<!-- code-review:signal;base64 <base64> -->`, whose decoded payload is
|
|
123
123
|
`{ "schema_version": "0.8.0", "round": <n>, "convergence": { "score": <s>, "threshold": <t>, "converged": <bool> } }`.
|
|
124
|
-
`round` is the count of completed full-review rounds; `convergence.score`
|
|
125
|
-
|
|
124
|
+
`round` is the count of completed full-review rounds; `convergence.score` sums each finding and
|
|
125
|
+
systemic problem's `floor(severity) + max(0, ceiling − floor) × confidence × likelihood` (ceilings
|
|
126
|
+
critical 4 · major 2 · minor 1 · nit 0; floors critical `threshold + 0.01` · major 0.5 · minor 0.1
|
|
127
|
+
· nit 0). A **systemic problem is scored with `likelihood` = 1** — a structural observation has no
|
|
128
|
+
single triggering input, so it is never discounted by likelihood. Rounded to 2
|
|
129
|
+
decimals; `threshold` defaults to 1, and `converged` = score ≤ threshold as a literal
|
|
126
130
|
boolean — `converged: true` means the last completed round is at or below the tolerance, so another
|
|
127
131
|
iteration round is not warranted. The commenter computes the signal from the review's own severities
|
|
128
132
|
(the agent never writes it); it appears once at least one full-review round has completed and is
|
|
@@ -137,8 +141,10 @@ links to:
|
|
|
137
141
|
frequencies — from which the re-review seed re-derives the advisory `scope_metastasis` entry it
|
|
138
142
|
hands the next-round agent. It is deliberately NOT embedded in the findings blob: the blob is the
|
|
139
143
|
agent's own document, and a recurrence claim is round state the commenter owns. Each inline review
|
|
140
|
-
comment embeds only its own finding (a `schema_version` + one-finding fragment),
|
|
141
|
-
review-body
|
|
144
|
+
comment embeds only its own finding (a `schema_version` + one-finding fragment), and the
|
|
145
|
+
review-object body only links the sticky — so the **sticky is the sole documented decode surface**
|
|
146
|
+
for the whole-document marker; a decoding agent reads it there. The review body is written only after
|
|
147
|
+
the sticky exists (a failed sticky write aborts the run first), so it never carries the blob itself.
|
|
142
148
|
- **`code-review-transcript`** — the full Claude Code session transcripts for the triage and review
|
|
143
149
|
phases. This is advisory/auditability only: it is never read by the comment job and never affects
|
|
144
150
|
what gets posted.
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var LineNumber = t.refinement(
|
|
|
31
31
|
"LineNumber"
|
|
32
32
|
);
|
|
33
33
|
var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
|
|
34
|
+
var Likelihood = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Likelihood");
|
|
34
35
|
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-]+)*)?$/;
|
|
35
36
|
var SchemaVersion = t.refinement(
|
|
36
37
|
t.string,
|
|
@@ -55,7 +56,8 @@ var FindingShape = t.intersection([
|
|
|
55
56
|
title: t.string,
|
|
56
57
|
description: t.string,
|
|
57
58
|
reasoning: t.string,
|
|
58
|
-
confidence: Confidence
|
|
59
|
+
confidence: Confidence,
|
|
60
|
+
likelihood: Likelihood
|
|
59
61
|
}),
|
|
60
62
|
FindingRuleCodec,
|
|
61
63
|
t.partial({
|
|
@@ -75,7 +77,8 @@ var SystemicRequired = t.type({
|
|
|
75
77
|
description: t.string,
|
|
76
78
|
severity: SeverityCodec,
|
|
77
79
|
reasoning: t.string,
|
|
78
|
-
confidence: Confidence
|
|
80
|
+
confidence: Confidence,
|
|
81
|
+
likelihood: Likelihood
|
|
79
82
|
});
|
|
80
83
|
var SystemicOptional = t.partial({
|
|
81
84
|
finding_codes: t.array(t.string),
|
|
@@ -202,7 +205,7 @@ var TestSummaryCodec = t.intersection([
|
|
|
202
205
|
failures: t.array(TestFailureCodec)
|
|
203
206
|
})
|
|
204
207
|
]);
|
|
205
|
-
var DEFAULT_SCHEMA_VERSION = "0.
|
|
208
|
+
var DEFAULT_SCHEMA_VERSION = "0.9.0";
|
|
206
209
|
var incompleteFindings = (summary) => ({
|
|
207
210
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
208
211
|
summary,
|
|
@@ -371,7 +374,8 @@ var severityEmoji = (s) => {
|
|
|
371
374
|
}
|
|
372
375
|
};
|
|
373
376
|
var EMBED_LIMIT = 42700;
|
|
374
|
-
var
|
|
377
|
+
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 and every code-review:* marker (findings, signal, rounds), not only the fields you recognize: the recurrence signals an iterating agent needs (round streaks, scope metastasis, same-mechanism) are decodable there. -->`;
|
|
375
379
|
var b64LengthOf = (document) => Buffer.from(JSON.stringify(document), "utf-8").toString("base64").length;
|
|
376
380
|
var encodeMarker = (document, jsonUrl, limit) => {
|
|
377
381
|
const b64 = Buffer.from(JSON.stringify(document), "utf-8").toString("base64");
|
|
@@ -585,19 +589,58 @@ var computeSameRootNotes = (priorRounds, findings, currentSha) => {
|
|
|
585
589
|
}
|
|
586
590
|
return Object.fromEntries(entries);
|
|
587
591
|
};
|
|
588
|
-
var
|
|
592
|
+
var CONVERGENCE_CEILINGS = { critical: 4, major: 2, minor: 1, nit: 0 };
|
|
593
|
+
var CRITICAL_FLOOR_MARGIN = 0.01;
|
|
594
|
+
var MINOR_FLOOR = 0.1;
|
|
589
595
|
var DEFAULT_CONVERGENCE_THRESHOLD = 1;
|
|
590
|
-
var
|
|
591
|
-
var
|
|
592
|
-
|
|
596
|
+
var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : severity === "minor" ? MINOR_FLOOR : 0;
|
|
597
|
+
var round2 = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
|
|
598
|
+
var contribution = (severity, confidence, likelihood, threshold) => {
|
|
599
|
+
const floor = convergenceFloor(severity, threshold);
|
|
600
|
+
return floor + Math.max(0, CONVERGENCE_CEILINGS[severity] - floor) * confidence * likelihood;
|
|
601
|
+
};
|
|
602
|
+
var convergenceScore = (doc, threshold) => round2(
|
|
603
|
+
doc.findings.reduce(
|
|
604
|
+
(sum, { severity, confidence, likelihood }) => sum + contribution(severity, confidence, likelihood, threshold),
|
|
605
|
+
0
|
|
606
|
+
) + (doc.systemic_problems ?? []).reduce(
|
|
607
|
+
(sum, { severity, confidence }) => sum + contribution(severity, confidence, 1, threshold),
|
|
608
|
+
0
|
|
609
|
+
)
|
|
610
|
+
);
|
|
611
|
+
var DEFAULT_NIT_VISIBILITY_FLOOR = 0.25;
|
|
612
|
+
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
|
+
var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
614
|
+
if (typeof priorDoc !== "object" || priorDoc === null) return [];
|
|
615
|
+
const arr = priorDoc["findings"];
|
|
616
|
+
if (!Array.isArray(arr)) return [];
|
|
617
|
+
const nits = [];
|
|
618
|
+
for (const f of arr) {
|
|
619
|
+
if (typeof f !== "object" || f === null) continue;
|
|
620
|
+
const rec = f;
|
|
621
|
+
if (!isBelowVisibilityFloor(rec, floor)) continue;
|
|
622
|
+
const title = rec["title"];
|
|
623
|
+
if (typeof title !== "string") continue;
|
|
624
|
+
const code = typeof rec["code"] === "string" && rec["code"] !== "" ? rec["code"] : void 0;
|
|
625
|
+
const path = typeof rec["path"] === "string" ? rec["path"] : void 0;
|
|
626
|
+
nits.push({
|
|
627
|
+
title,
|
|
628
|
+
...code !== void 0 ? { code } : {},
|
|
629
|
+
...path !== void 0 ? { path } : {}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return nits;
|
|
633
|
+
};
|
|
634
|
+
var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
635
|
+
const { score, converged } = convergenceSignal(doc, threshold);
|
|
593
636
|
return converged ? `**Convergence** \u{1F3C1} ${String(score)} \u2264 ${String(threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${String(score)} > ${String(threshold)} \u2014 iterating`;
|
|
594
637
|
};
|
|
595
638
|
var SURFACE_SCHEMA_VERSION = "0.8.0";
|
|
596
|
-
var convergenceSignal = (
|
|
597
|
-
const score = convergenceScore(
|
|
639
|
+
var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
640
|
+
const score = convergenceScore(doc, threshold);
|
|
598
641
|
return { score, threshold, converged: score <= threshold };
|
|
599
642
|
};
|
|
600
|
-
var signalForRound = (round,
|
|
643
|
+
var signalForRound = (round, doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(doc, threshold) });
|
|
601
644
|
var signalMarker = (signal) => `<!-- code-review:signal;base64 ${Buffer.from(
|
|
602
645
|
JSON.stringify({ schema_version: SURFACE_SCHEMA_VERSION, ...signal }),
|
|
603
646
|
"utf-8"
|
|
@@ -660,12 +703,9 @@ var projectPatch = (patch) => {
|
|
|
660
703
|
return typeof lowered === "string" ? { kind: "suggestion", text: escapeFence(lowered) } : { kind: "patch", raw: escapeFence(patch) };
|
|
661
704
|
};
|
|
662
705
|
var formatConfidence = (n) => n.toFixed(2);
|
|
663
|
-
var reviewBodyPointer = (headSha, stickyUrl
|
|
706
|
+
var reviewBodyPointer = (headSha, stickyUrl) => {
|
|
664
707
|
const sha7 = headSha.slice(0, 7);
|
|
665
|
-
|
|
666
|
-
return marker ? `${marker}
|
|
667
|
-
|
|
668
|
-
${linkLine}` : linkLine;
|
|
708
|
+
return stickyUrl ? `\u{1F916} Automated code review for \`${sha7}\` \u2014 see the [summary comment](${stickyUrl}) for the verdict, walkthrough, and cost.` : `\u{1F916} Automated code review for \`${sha7}\` \u2014 see the summary comment for the verdict, walkthrough, and cost.`;
|
|
669
709
|
};
|
|
670
710
|
var asRecord = (u) => typeof u === "object" && u !== null && !Array.isArray(u) ? u : null;
|
|
671
711
|
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
@@ -1007,6 +1047,13 @@ var sanitizeFinding = (f, answeredNotes) => {
|
|
|
1007
1047
|
answeredNote: answeredNotes !== void 0 && Object.prototype.hasOwnProperty.call(answeredNotes, key2) ? answeredNotes[key2] ?? "" : ""
|
|
1008
1048
|
};
|
|
1009
1049
|
};
|
|
1050
|
+
var sanitizeSuppressedNit = (f) => ({
|
|
1051
|
+
title: escapeCodeBackticks(f.title),
|
|
1052
|
+
...f.code !== void 0 ? { code: escapeCodeBackticks(f.code) } : {},
|
|
1053
|
+
path: escapeCodeBackticks(f.path),
|
|
1054
|
+
startLine: f.start_line,
|
|
1055
|
+
m: formatConfidence(f.confidence * f.likelihood)
|
|
1056
|
+
});
|
|
1010
1057
|
var sanitizeSystemic = (s) => ({
|
|
1011
1058
|
...s,
|
|
1012
1059
|
title: escapePipes(s.title),
|
|
@@ -1043,7 +1090,6 @@ var render = (input) => {
|
|
|
1043
1090
|
const rounds = input.rounds ?? [];
|
|
1044
1091
|
const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(rounds.slice(0, -1), input.findings.findings);
|
|
1045
1092
|
const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && rounds.length > 0)) && isReviewVerdict(input.findings.verdict);
|
|
1046
|
-
const convergenceCounts = rounds[rounds.length - 1] ?? computeRoundCounts(input.findings);
|
|
1047
1093
|
const advisoryAllowed = isFullReviewRound;
|
|
1048
1094
|
return eta.renderString(input.template, {
|
|
1049
1095
|
findings: input.findings,
|
|
@@ -1060,8 +1106,10 @@ var render = (input) => {
|
|
|
1060
1106
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
1061
1107
|
postedAt: input.postedAt ?? "",
|
|
1062
1108
|
severityCounts,
|
|
1063
|
-
convergenceSummary: isFullReviewRound ? convergenceSummary(
|
|
1109
|
+
convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold) : "",
|
|
1064
1110
|
strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
|
|
1111
|
+
suppressedNits: (input.suppressedNits ?? []).map(sanitizeSuppressedNit),
|
|
1112
|
+
nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
|
|
1065
1113
|
systemic: (input.findings.systemic_problems ?? []).map(sanitizeSystemic),
|
|
1066
1114
|
unanchoredCount: input.unanchoredCount ?? 0,
|
|
1067
1115
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
@@ -1070,11 +1118,11 @@ var render = (input) => {
|
|
|
1070
1118
|
findingsPointer: input.findingsPointer ?? surfacedFindingsPointer(
|
|
1071
1119
|
input.findings,
|
|
1072
1120
|
// The fallback embeds a signal exactly when the badge renders — never beside a suppressed
|
|
1073
|
-
// badge, and
|
|
1121
|
+
// badge, and scores the same findings the badge does. It assumes a post-style history (the
|
|
1074
1122
|
// caller appends this run's counts last), numbering the round exactly as the trajectory
|
|
1075
1123
|
// label does; post always supplies the marker, so this path cannot disagree with it in
|
|
1076
1124
|
// production (issue #141 review r4).
|
|
1077
|
-
isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length,
|
|
1125
|
+
isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, input.findings, input.convergenceThreshold) : null,
|
|
1078
1126
|
input.jsonUrl
|
|
1079
1127
|
),
|
|
1080
1128
|
roundsMarker: roundsMarker(rounds),
|
|
@@ -1444,6 +1492,7 @@ var sidecarPath = (draftPath, postfix) => {
|
|
|
1444
1492
|
};
|
|
1445
1493
|
var priorContextPath = (draftPath) => sidecarPath(draftPath, ".prior");
|
|
1446
1494
|
var priorAnswersPath = (draftPath) => sidecarPath(draftPath, ".prior-answers");
|
|
1495
|
+
var priorSuppressedPath = (draftPath) => sidecarPath(draftPath, ".prior-suppressed");
|
|
1447
1496
|
var lastValidPath = (draftPath) => sidecarPath(draftPath, ".last-valid");
|
|
1448
1497
|
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.`;
|
|
1449
1498
|
var forceBackgroundSpawn = (toolInput) => ({
|
|
@@ -1656,6 +1705,14 @@ var findingsTable = [
|
|
|
1656
1705
|
},
|
|
1657
1706
|
{
|
|
1658
1707
|
minor: "0.6",
|
|
1708
|
+
defaultVersion: "0.6.0",
|
|
1709
|
+
schemaFile: "findings.schema.json",
|
|
1710
|
+
codec: FindingsCodec,
|
|
1711
|
+
normalize: identity,
|
|
1712
|
+
latest: false
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
minor: "0.9",
|
|
1659
1716
|
defaultVersion: DEFAULT_SCHEMA_VERSION,
|
|
1660
1717
|
schemaFile: "findings.schema.json",
|
|
1661
1718
|
codec: FindingsCodec,
|
|
@@ -2026,8 +2083,8 @@ var commentPayload = (c) => ({
|
|
|
2026
2083
|
...c.start_line !== void 0 && c.start_side !== void 0 ? { start_line: c.start_line, start_side: c.start_side } : {},
|
|
2027
2084
|
body: formatMarkdown(c.body)
|
|
2028
2085
|
});
|
|
2029
|
-
var postInlineReview = async (repo, prNumber, headSha, comments, inDiff, stickyUrl,
|
|
2030
|
-
const pointer = reviewBodyPointer(headSha, stickyUrl
|
|
2086
|
+
var postInlineReview = async (repo, prNumber, headSha, comments, inDiff, stickyUrl, ghApi) => {
|
|
2087
|
+
const pointer = reviewBodyPointer(headSha, stickyUrl);
|
|
2031
2088
|
const reviewBody = (withComments) => JSON.stringify({
|
|
2032
2089
|
body: pointer,
|
|
2033
2090
|
commit_id: headSha,
|
|
@@ -2375,6 +2432,12 @@ ${dropNote}` : ""}`,
|
|
|
2375
2432
|
findings: [...answeredFilter.findings],
|
|
2376
2433
|
...systemic.length > 0 ? { systemic_problems: systemic } : {}
|
|
2377
2434
|
};
|
|
2435
|
+
const priorSuppressedKeys = new Set(
|
|
2436
|
+
(existingSticky !== null && isFullReviewSticky(existingSticky.body) ? priorBelowFloorNits(parseFindingsMarker(existingSticky.body), input.nitVisibilityFloor) : []).map((n) => answeredNoteKey({ code: n.code, title: n.title }))
|
|
2437
|
+
);
|
|
2438
|
+
const isSuppressedNit = (f) => f.severity === "nit" && (isBelowVisibilityFloor(f, input.nitVisibilityFloor) || priorSuppressedKeys.has(answeredNoteKey(f)));
|
|
2439
|
+
const suppressedNits = findings.findings.filter(isSuppressedNit);
|
|
2440
|
+
const visibleFindings = findings.findings.filter((f) => !isSuppressedNit(f));
|
|
2378
2441
|
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._" : "");
|
|
2379
2442
|
const envelope = loadEnvelope(input.envelopePath);
|
|
2380
2443
|
const testReport = input.testReportPath ? loadTestReport(input.testReportPath) : void 0;
|
|
@@ -2401,10 +2464,15 @@ ${dropNote}` : ""}`,
|
|
|
2401
2464
|
rounds: priorRounds,
|
|
2402
2465
|
sameRootNotes: {},
|
|
2403
2466
|
// The answered-state honesty rules apply on EVERY surface that renders the filtered
|
|
2404
|
-
// findings — the lost-envelope branch lists every finding (no inline review exists to
|
|
2467
|
+
// findings — the lost-envelope branch lists every VISIBLE finding (no inline review exists to
|
|
2405
2468
|
// carry them) so the kept re-raises' annotations actually render, and names the drops
|
|
2406
|
-
// exactly like the main path (issues #151 review r1 + r2).
|
|
2407
|
-
|
|
2469
|
+
// exactly like the main path (issues #151 review r1 + r2). The nit visibility floor applies
|
|
2470
|
+
// here too (issue #164): below-floor nits are hidden from the human list and shown only in the
|
|
2471
|
+
// collapsed aside — the floor is a human-visibility policy, not an inline-comment policy, so it
|
|
2472
|
+
// must hold on the surface that lists findings without an inline review.
|
|
2473
|
+
strays: visibleFindings,
|
|
2474
|
+
suppressedNits,
|
|
2475
|
+
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2408
2476
|
answeredNotes: reRaisedNotes,
|
|
2409
2477
|
answeredReRaiseNote: answeredDropNote,
|
|
2410
2478
|
roundCount: priorSignal?.round ?? priorRounds.length,
|
|
@@ -2441,7 +2509,7 @@ ${dropNote}` : ""}`,
|
|
|
2441
2509
|
comments: rawComments,
|
|
2442
2510
|
strays,
|
|
2443
2511
|
inDiff
|
|
2444
|
-
} = buildInlineComments(
|
|
2512
|
+
} = buildInlineComments(visibleFindings, diff, {
|
|
2445
2513
|
inlineTemplate,
|
|
2446
2514
|
models: envelope.models.map((m) => m.model),
|
|
2447
2515
|
findings,
|
|
@@ -2472,7 +2540,7 @@ ${dropNote}` : ""}`,
|
|
|
2472
2540
|
roundNumber
|
|
2473
2541
|
)
|
|
2474
2542
|
] : priorRounds;
|
|
2475
|
-
const signal = isRound ? signalForRound(roundNumber,
|
|
2543
|
+
const signal = isRound ? signalForRound(roundNumber, findings, input.convergenceThreshold) : thisIncomplete || !isReviewVerdict(findings.verdict) ? null : priorSignal;
|
|
2476
2544
|
const findingsMarker = findingsMarkerFor(findings, signal);
|
|
2477
2545
|
const markerForm = findingsMarkerForm(findings, input.jsonUrl);
|
|
2478
2546
|
const signalNote = signal !== null || priorSignal !== null ? " (the stop signal still rides the compact marker)" : "";
|
|
@@ -2505,8 +2573,10 @@ ${dropNote}` : ""}`,
|
|
|
2505
2573
|
answeredReRaiseNote: answeredDropNote,
|
|
2506
2574
|
roundCount: signal?.round ?? priorSignal?.round ?? priorRounds.length,
|
|
2507
2575
|
convergenceThreshold: input.convergenceThreshold,
|
|
2576
|
+
nitVisibilityFloor: input.nitVisibilityFloor,
|
|
2508
2577
|
convergenceRound: isRound,
|
|
2509
2578
|
strays,
|
|
2579
|
+
suppressedNits,
|
|
2510
2580
|
runUrl: input.runUrl,
|
|
2511
2581
|
jsonUrl: input.jsonUrl,
|
|
2512
2582
|
findingsPointer: findingsMarker,
|
|
@@ -2551,7 +2621,6 @@ ${dropNote}` : ""}`,
|
|
|
2551
2621
|
comments,
|
|
2552
2622
|
inDiff,
|
|
2553
2623
|
stickyRef?.url,
|
|
2554
|
-
findingsMarker,
|
|
2555
2624
|
ghApi
|
|
2556
2625
|
);
|
|
2557
2626
|
process.stderr.write(
|
|
@@ -3664,7 +3733,8 @@ var resolvePrices = (pricesArg) => {
|
|
|
3664
3733
|
return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
|
|
3665
3734
|
};
|
|
3666
3735
|
var TEST_REPORT_DESCRIPTION = 'Path to a JSON test summary: {"passed": number, "failed": number, "total": number, "failures"?: [{"name": string, "message"?: string}]}';
|
|
3667
|
-
var CONVERGENCE_THRESHOLD_DESCRIPTION = "Advisory convergence tolerance: the
|
|
3736
|
+
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)";
|
|
3737
|
+
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)";
|
|
3668
3738
|
var renderCmd = defineCommand({
|
|
3669
3739
|
meta: {
|
|
3670
3740
|
name: "render",
|
|
@@ -3708,6 +3778,10 @@ var renderCmd = defineCommand({
|
|
|
3708
3778
|
"convergence-threshold": {
|
|
3709
3779
|
type: "string",
|
|
3710
3780
|
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
3781
|
+
},
|
|
3782
|
+
"nit-visibility-floor": {
|
|
3783
|
+
type: "string",
|
|
3784
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
3711
3785
|
}
|
|
3712
3786
|
},
|
|
3713
3787
|
run: async ({ args }) => {
|
|
@@ -3733,6 +3807,7 @@ var renderCmd = defineCommand({
|
|
|
3733
3807
|
testReport,
|
|
3734
3808
|
rounds: isRound ? [counts] : [],
|
|
3735
3809
|
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
3810
|
+
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
3736
3811
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
3737
3812
|
});
|
|
3738
3813
|
process.stdout.write(output2);
|
|
@@ -3757,13 +3832,19 @@ var inlineCmd = defineCommand({
|
|
|
3757
3832
|
template: {
|
|
3758
3833
|
type: "string",
|
|
3759
3834
|
description: "Path to inline comment Eta template (default: bundled templates/inline.eta)"
|
|
3835
|
+
},
|
|
3836
|
+
"nit-visibility-floor": {
|
|
3837
|
+
type: "string",
|
|
3838
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
3760
3839
|
}
|
|
3761
3840
|
},
|
|
3762
3841
|
run: async ({ args }) => {
|
|
3763
3842
|
const findings = decode(FindingsCodec.decode(readJSON(args.findings)), "findings");
|
|
3764
3843
|
const diff = readFileSync(resolve$1(args.diff), "utf-8");
|
|
3765
3844
|
const inlineTemplate = readFileSync(resolveInlineTemplatePath(args.template), "utf-8");
|
|
3766
|
-
const
|
|
3845
|
+
const floor = parseNitVisibilityFloor(args["nit-visibility-floor"]);
|
|
3846
|
+
const visibleFindings = findings.findings.filter((f) => !isBelowVisibilityFloor(f, floor));
|
|
3847
|
+
const { comments, strays } = buildInlineComments(visibleFindings, diff, {
|
|
3767
3848
|
inlineTemplate,
|
|
3768
3849
|
findings
|
|
3769
3850
|
});
|
|
@@ -3865,6 +3946,26 @@ var parseConvergenceThreshold = (raw) => {
|
|
|
3865
3946
|
}
|
|
3866
3947
|
return n;
|
|
3867
3948
|
};
|
|
3949
|
+
var parseNitVisibilityFloor = (raw) => {
|
|
3950
|
+
const trimmed = raw?.trim();
|
|
3951
|
+
if (trimmed === void 0 || trimmed === "") return void 0;
|
|
3952
|
+
if (!/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
3953
|
+
fail(`--nit-visibility-floor must be a number in [0, 1]; got "${trimmed}"`);
|
|
3954
|
+
}
|
|
3955
|
+
const n = Number.parseFloat(trimmed);
|
|
3956
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
3957
|
+
fail(
|
|
3958
|
+
`--nit-visibility-floor must be in [0, 1] (it gates confidence \xD7 likelihood); got "${trimmed}"`
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
return n;
|
|
3962
|
+
};
|
|
3963
|
+
var parseNitVisibilityFloorLenient = (raw) => {
|
|
3964
|
+
const trimmed = raw?.trim();
|
|
3965
|
+
if (trimmed === void 0 || trimmed === "" || !/^\d+(\.\d+)?$/.test(trimmed)) return void 0;
|
|
3966
|
+
const n = Number.parseFloat(trimmed);
|
|
3967
|
+
return Number.isFinite(n) && n >= 0 && n <= 1 ? n : void 0;
|
|
3968
|
+
};
|
|
3868
3969
|
var transcriptPathOf = (input) => {
|
|
3869
3970
|
const tp = (typeof input === "object" && input !== null ? input : {})["transcript_path"];
|
|
3870
3971
|
return typeof tp === "string" ? tp : void 0;
|
|
@@ -4163,6 +4264,10 @@ var seedDraftCmd = defineCommand({
|
|
|
4163
4264
|
type: "string",
|
|
4164
4265
|
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"
|
|
4165
4266
|
},
|
|
4267
|
+
"nit-visibility-floor": {
|
|
4268
|
+
type: "string",
|
|
4269
|
+
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"
|
|
4270
|
+
},
|
|
4166
4271
|
"head-sha": {
|
|
4167
4272
|
type: "string",
|
|
4168
4273
|
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"
|
|
@@ -4258,6 +4363,27 @@ var seedDraftCmd = defineCommand({
|
|
|
4258
4363
|
} catch (err) {
|
|
4259
4364
|
process.stderr.write(
|
|
4260
4365
|
`Warning: could not read the answered-findings registry ${args["prior-answers"]} (${errMsg(err)}) \u2014 no prior-answers sidecar
|
|
4366
|
+
`
|
|
4367
|
+
);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
if (parsedPrior !== null && parseReviewedRoute(priorBody ?? "") === "full review") {
|
|
4371
|
+
try {
|
|
4372
|
+
const belowFloor = priorBelowFloorNits(
|
|
4373
|
+
parsedPrior,
|
|
4374
|
+
parseNitVisibilityFloorLenient(args["nit-visibility-floor"])
|
|
4375
|
+
);
|
|
4376
|
+
if (belowFloor.length > 0) {
|
|
4377
|
+
writeFileSync(priorSuppressedPath(outPath), `${JSON.stringify(belowFloor, null, 2)}
|
|
4378
|
+
`);
|
|
4379
|
+
process.stderr.write(
|
|
4380
|
+
`Seeded ${priorSuppressedPath(outPath)} with ${String(belowFloor.length)} below-floor nit(s) as adjudicated context
|
|
4381
|
+
`
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
} catch (err) {
|
|
4385
|
+
process.stderr.write(
|
|
4386
|
+
`Warning: could not derive the prior below-floor nits (${errMsg(err)}) \u2014 no prior-suppressed sidecar
|
|
4261
4387
|
`
|
|
4262
4388
|
);
|
|
4263
4389
|
}
|
|
@@ -4772,6 +4898,10 @@ var postCmd = defineCommand({
|
|
|
4772
4898
|
"convergence-threshold": {
|
|
4773
4899
|
type: "string",
|
|
4774
4900
|
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
4901
|
+
},
|
|
4902
|
+
"nit-visibility-floor": {
|
|
4903
|
+
type: "string",
|
|
4904
|
+
description: NIT_VISIBILITY_FLOOR_DESCRIPTION
|
|
4775
4905
|
}
|
|
4776
4906
|
},
|
|
4777
4907
|
run: async ({ args }) => {
|
|
@@ -4793,6 +4923,7 @@ var postCmd = defineCommand({
|
|
|
4793
4923
|
runUrl: args["run-url"],
|
|
4794
4924
|
jsonUrl: args["json-url"],
|
|
4795
4925
|
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
4926
|
+
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
4796
4927
|
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
4797
4928
|
});
|
|
4798
4929
|
}
|