@jphutchins/code-review 0.1.0-alpha.43 → 0.1.0-alpha.44

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 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.6.0 contract the review agent is held to and
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.6.0",
114
+ "schema_version": "0.9.0",
115
115
  "verdict": "comment",
116
116
  "summary": "...",
117
117
  "findings": []
@@ -121,8 +121,10 @@ 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` = critical·4 + major·2 +
125
- minor·1 + nit·0, `threshold` defaults to 1, and `converged` = score threshold as a literal
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` (ceilings critical 4 ·
126
+ major 2 · minor 1 · nit 0; floors critical `threshold + 0.01` · major 0.5 · else 0), rounded to 2
127
+ decimals; `threshold` defaults to 1, and `converged` = score ≤ threshold as a literal
126
128
  boolean — `converged: true` means the last completed round is at or below the tolerance, so another
127
129
  iteration round is not warranted. The commenter computes the signal from the review's own severities
128
130
  (the agent never writes it); it appears once at least one full-review round has completed and is
@@ -137,8 +139,10 @@ links to:
137
139
  frequencies — from which the re-review seed re-derives the advisory `scope_metastasis` entry it
138
140
  hands the next-round agent. It is deliberately NOT embedded in the findings blob: the blob is the
139
141
  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), so the sticky and
141
- review-body markers remain the whole-document source of truth.
142
+ comment embeds only its own finding (a `schema_version` + one-finding fragment), and the
143
+ review-object body only links the sticky so the **sticky is the sole documented decode surface**
144
+ for the whole-document marker; a decoding agent reads it there. The review body is written only after
145
+ the sticky exists (a failed sticky write aborts the run first), so it never carries the blob itself.
142
146
  - **`code-review-transcript`** — the full Claude Code session transcripts for the triage and review
143
147
  phases. This is advisory/auditability only: it is never read by the comment job and never affects
144
148
  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.6.0";
208
+ var DEFAULT_SCHEMA_VERSION = "0.9.0";
206
209
  var incompleteFindings = (summary) => ({
207
210
  schema_version: DEFAULT_SCHEMA_VERSION,
208
211
  summary,
@@ -585,19 +588,30 @@ var computeSameRootNotes = (priorRounds, findings, currentSha) => {
585
588
  }
586
589
  return Object.fromEntries(entries);
587
590
  };
588
- var CONVERGENCE_WEIGHTS = { critical: 4, major: 2, minor: 1, nit: 0 };
591
+ var CONVERGENCE_CEILINGS = { critical: 4, major: 2, minor: 1, nit: 0 };
592
+ var CRITICAL_FLOOR_MARGIN = 0.01;
589
593
  var DEFAULT_CONVERGENCE_THRESHOLD = 1;
590
- var convergenceScore = (counts) => SEVERITIES.reduce((sum, k) => sum + counts[k] * CONVERGENCE_WEIGHTS[k], 0);
591
- var convergenceSummary = (counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
592
- const { score, converged } = convergenceSignal(counts, threshold);
594
+ var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : 0;
595
+ var round2 = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
596
+ var convergenceScore = (doc, threshold) => round2(
597
+ [...doc.findings, ...doc.systemic_problems ?? []].reduce(
598
+ (sum, { severity, confidence, likelihood }) => {
599
+ const floor = convergenceFloor(severity, threshold);
600
+ return sum + floor + Math.max(0, CONVERGENCE_CEILINGS[severity] - floor) * confidence * likelihood;
601
+ },
602
+ 0
603
+ )
604
+ );
605
+ var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
606
+ const { score, converged } = convergenceSignal(doc, threshold);
593
607
  return converged ? `**Convergence** \u{1F3C1} ${String(score)} \u2264 ${String(threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${String(score)} > ${String(threshold)} \u2014 iterating`;
594
608
  };
595
609
  var SURFACE_SCHEMA_VERSION = "0.8.0";
596
- var convergenceSignal = (counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
597
- const score = convergenceScore(counts);
610
+ var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
611
+ const score = convergenceScore(doc, threshold);
598
612
  return { score, threshold, converged: score <= threshold };
599
613
  };
600
- var signalForRound = (round, counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(counts, threshold) });
614
+ var signalForRound = (round, doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(doc, threshold) });
601
615
  var signalMarker = (signal) => `<!-- code-review:signal;base64 ${Buffer.from(
602
616
  JSON.stringify({ schema_version: SURFACE_SCHEMA_VERSION, ...signal }),
603
617
  "utf-8"
@@ -660,12 +674,9 @@ var projectPatch = (patch) => {
660
674
  return typeof lowered === "string" ? { kind: "suggestion", text: escapeFence(lowered) } : { kind: "patch", raw: escapeFence(patch) };
661
675
  };
662
676
  var formatConfidence = (n) => n.toFixed(2);
663
- var reviewBodyPointer = (headSha, stickyUrl, marker) => {
677
+ var reviewBodyPointer = (headSha, stickyUrl) => {
664
678
  const sha7 = headSha.slice(0, 7);
665
- const linkLine = 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.`;
666
- return marker ? `${marker}
667
-
668
- ${linkLine}` : linkLine;
679
+ 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
680
  };
670
681
  var asRecord = (u) => typeof u === "object" && u !== null && !Array.isArray(u) ? u : null;
671
682
  var errMsg = (e) => e instanceof Error ? e.message : String(e);
@@ -1043,7 +1054,6 @@ var render = (input) => {
1043
1054
  const rounds = input.rounds ?? [];
1044
1055
  const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(rounds.slice(0, -1), input.findings.findings);
1045
1056
  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
1057
  const advisoryAllowed = isFullReviewRound;
1048
1058
  return eta.renderString(input.template, {
1049
1059
  findings: input.findings,
@@ -1060,7 +1070,7 @@ var render = (input) => {
1060
1070
  reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
1061
1071
  postedAt: input.postedAt ?? "",
1062
1072
  severityCounts,
1063
- convergenceSummary: isFullReviewRound ? convergenceSummary(convergenceCounts, input.convergenceThreshold) : "",
1073
+ convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold) : "",
1064
1074
  strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
1065
1075
  systemic: (input.findings.systemic_problems ?? []).map(sanitizeSystemic),
1066
1076
  unanchoredCount: input.unanchoredCount ?? 0,
@@ -1070,11 +1080,11 @@ var render = (input) => {
1070
1080
  findingsPointer: input.findingsPointer ?? surfacedFindingsPointer(
1071
1081
  input.findings,
1072
1082
  // The fallback embeds a signal exactly when the badge renders — never beside a suppressed
1073
- // badge, and from the same counts the badge reads. It assumes a post-style history (the
1083
+ // badge, and scores the same findings the badge does. It assumes a post-style history (the
1074
1084
  // caller appends this run's counts last), numbering the round exactly as the trajectory
1075
1085
  // label does; post always supplies the marker, so this path cannot disagree with it in
1076
1086
  // production (issue #141 review r4).
1077
- isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, convergenceCounts, input.convergenceThreshold) : null,
1087
+ isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, input.findings, input.convergenceThreshold) : null,
1078
1088
  input.jsonUrl
1079
1089
  ),
1080
1090
  roundsMarker: roundsMarker(rounds),
@@ -1656,6 +1666,14 @@ var findingsTable = [
1656
1666
  },
1657
1667
  {
1658
1668
  minor: "0.6",
1669
+ defaultVersion: "0.6.0",
1670
+ schemaFile: "findings.schema.json",
1671
+ codec: FindingsCodec,
1672
+ normalize: identity,
1673
+ latest: false
1674
+ },
1675
+ {
1676
+ minor: "0.9",
1659
1677
  defaultVersion: DEFAULT_SCHEMA_VERSION,
1660
1678
  schemaFile: "findings.schema.json",
1661
1679
  codec: FindingsCodec,
@@ -2026,8 +2044,8 @@ var commentPayload = (c) => ({
2026
2044
  ...c.start_line !== void 0 && c.start_side !== void 0 ? { start_line: c.start_line, start_side: c.start_side } : {},
2027
2045
  body: formatMarkdown(c.body)
2028
2046
  });
2029
- var postInlineReview = async (repo, prNumber, headSha, comments, inDiff, stickyUrl, marker, ghApi) => {
2030
- const pointer = reviewBodyPointer(headSha, stickyUrl, marker);
2047
+ var postInlineReview = async (repo, prNumber, headSha, comments, inDiff, stickyUrl, ghApi) => {
2048
+ const pointer = reviewBodyPointer(headSha, stickyUrl);
2031
2049
  const reviewBody = (withComments) => JSON.stringify({
2032
2050
  body: pointer,
2033
2051
  commit_id: headSha,
@@ -2472,7 +2490,7 @@ ${dropNote}` : ""}`,
2472
2490
  roundNumber
2473
2491
  )
2474
2492
  ] : priorRounds;
2475
- const signal = isRound ? signalForRound(roundNumber, computeRoundCounts(findings), input.convergenceThreshold) : thisIncomplete || !isReviewVerdict(findings.verdict) ? null : priorSignal;
2493
+ const signal = isRound ? signalForRound(roundNumber, findings, input.convergenceThreshold) : thisIncomplete || !isReviewVerdict(findings.verdict) ? null : priorSignal;
2476
2494
  const findingsMarker = findingsMarkerFor(findings, signal);
2477
2495
  const markerForm = findingsMarkerForm(findings, input.jsonUrl);
2478
2496
  const signalNote = signal !== null || priorSignal !== null ? " (the stop signal still rides the compact marker)" : "";
@@ -2551,7 +2569,6 @@ ${dropNote}` : ""}`,
2551
2569
  comments,
2552
2570
  inDiff,
2553
2571
  stickyRef?.url,
2554
- findingsMarker,
2555
2572
  ghApi
2556
2573
  );
2557
2574
  process.stderr.write(
@@ -3664,7 +3681,7 @@ var resolvePrices = (pricesArg) => {
3664
3681
  return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
3665
3682
  };
3666
3683
  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 weighted-severity score (critical 4 \xB7 major 2 \xB7 minor 1 \xB7 nit 0) at or below which the sticky reads as converged (default: 1 \u2014 unlimited nits plus at most one minor)";
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)";
3668
3685
  var renderCmd = defineCommand({
3669
3686
  meta: {
3670
3687
  name: "render",