@jphutchins/code-review 0.1.0-alpha.44 → 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 CHANGED
@@ -122,8 +122,10 @@ links to:
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
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
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
127
129
  decimals; `threshold` defaults to 1, and `converged` = score ≤ threshold as a literal
128
130
  boolean — `converged: true` means the last completed round is at or below the tolerance, so another
129
131
  iteration round is not warranted. The commenter computes the signal from the review's own severities
package/dist/index.js CHANGED
@@ -374,7 +374,8 @@ var severityEmoji = (s) => {
374
374
  }
375
375
  };
376
376
  var EMBED_LIMIT = 42700;
377
- var AGENTS_STOP_DIRECTIVE = "<!-- AGENTS: STOP \u2014 do not parse the prose below; decode this findings JSON and read schema_version first. -->";
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. -->`;
378
379
  var b64LengthOf = (document) => Buffer.from(JSON.stringify(document), "utf-8").toString("base64").length;
379
380
  var encodeMarker = (document, jsonUrl, limit) => {
380
381
  const b64 = Buffer.from(JSON.stringify(document), "utf-8").toString("base64");
@@ -590,18 +591,46 @@ var computeSameRootNotes = (priorRounds, findings, currentSha) => {
590
591
  };
591
592
  var CONVERGENCE_CEILINGS = { critical: 4, major: 2, minor: 1, nit: 0 };
592
593
  var CRITICAL_FLOOR_MARGIN = 0.01;
594
+ var MINOR_FLOOR = 0.1;
593
595
  var DEFAULT_CONVERGENCE_THRESHOLD = 1;
594
- var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : 0;
596
+ var convergenceFloor = (severity, threshold) => severity === "critical" ? threshold + CRITICAL_FLOOR_MARGIN : severity === "major" ? 0.5 : severity === "minor" ? MINOR_FLOOR : 0;
595
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
+ };
596
602
  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
- },
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),
602
608
  0
603
609
  )
604
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
+ };
605
634
  var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
606
635
  const { score, converged } = convergenceSignal(doc, threshold);
607
636
  return converged ? `**Convergence** \u{1F3C1} ${String(score)} \u2264 ${String(threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${String(score)} > ${String(threshold)} \u2014 iterating`;
@@ -1018,6 +1047,13 @@ var sanitizeFinding = (f, answeredNotes) => {
1018
1047
  answeredNote: answeredNotes !== void 0 && Object.prototype.hasOwnProperty.call(answeredNotes, key2) ? answeredNotes[key2] ?? "" : ""
1019
1048
  };
1020
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
+ });
1021
1057
  var sanitizeSystemic = (s) => ({
1022
1058
  ...s,
1023
1059
  title: escapePipes(s.title),
@@ -1072,6 +1108,8 @@ var render = (input) => {
1072
1108
  severityCounts,
1073
1109
  convergenceSummary: isFullReviewRound ? convergenceSummary(input.findings, input.convergenceThreshold) : "",
1074
1110
  strays: (input.strays ?? []).map((f) => sanitizeFinding(f, input.answeredNotes)),
1111
+ suppressedNits: (input.suppressedNits ?? []).map(sanitizeSuppressedNit),
1112
+ nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
1075
1113
  systemic: (input.findings.systemic_problems ?? []).map(sanitizeSystemic),
1076
1114
  unanchoredCount: input.unanchoredCount ?? 0,
1077
1115
  inlineDisposition: input.inlineDisposition ?? null,
@@ -1454,6 +1492,7 @@ var sidecarPath = (draftPath, postfix) => {
1454
1492
  };
1455
1493
  var priorContextPath = (draftPath) => sidecarPath(draftPath, ".prior");
1456
1494
  var priorAnswersPath = (draftPath) => sidecarPath(draftPath, ".prior-answers");
1495
+ var priorSuppressedPath = (draftPath) => sidecarPath(draftPath, ".prior-suppressed");
1457
1496
  var lastValidPath = (draftPath) => sidecarPath(draftPath, ".last-valid");
1458
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.`;
1459
1498
  var forceBackgroundSpawn = (toolInput) => ({
@@ -2393,6 +2432,12 @@ ${dropNote}` : ""}`,
2393
2432
  findings: [...answeredFilter.findings],
2394
2433
  ...systemic.length > 0 ? { systemic_problems: systemic } : {}
2395
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));
2396
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._" : "");
2397
2442
  const envelope = loadEnvelope(input.envelopePath);
2398
2443
  const testReport = input.testReportPath ? loadTestReport(input.testReportPath) : void 0;
@@ -2419,10 +2464,15 @@ ${dropNote}` : ""}`,
2419
2464
  rounds: priorRounds,
2420
2465
  sameRootNotes: {},
2421
2466
  // 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
2467
+ // findings — the lost-envelope branch lists every VISIBLE finding (no inline review exists to
2423
2468
  // 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
- strays: findings.findings,
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,
2426
2476
  answeredNotes: reRaisedNotes,
2427
2477
  answeredReRaiseNote: answeredDropNote,
2428
2478
  roundCount: priorSignal?.round ?? priorRounds.length,
@@ -2459,7 +2509,7 @@ ${dropNote}` : ""}`,
2459
2509
  comments: rawComments,
2460
2510
  strays,
2461
2511
  inDiff
2462
- } = buildInlineComments(findings.findings, diff, {
2512
+ } = buildInlineComments(visibleFindings, diff, {
2463
2513
  inlineTemplate,
2464
2514
  models: envelope.models.map((m) => m.model),
2465
2515
  findings,
@@ -2523,8 +2573,10 @@ ${dropNote}` : ""}`,
2523
2573
  answeredReRaiseNote: answeredDropNote,
2524
2574
  roundCount: signal?.round ?? priorSignal?.round ?? priorRounds.length,
2525
2575
  convergenceThreshold: input.convergenceThreshold,
2576
+ nitVisibilityFloor: input.nitVisibilityFloor,
2526
2577
  convergenceRound: isRound,
2527
2578
  strays,
2579
+ suppressedNits,
2528
2580
  runUrl: input.runUrl,
2529
2581
  jsonUrl: input.jsonUrl,
2530
2582
  findingsPointer: findingsMarker,
@@ -3681,7 +3733,8 @@ var resolvePrices = (pricesArg) => {
3681
3733
  return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
3682
3734
  };
3683
3735
  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)";
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)";
3685
3738
  var renderCmd = defineCommand({
3686
3739
  meta: {
3687
3740
  name: "render",
@@ -3725,6 +3778,10 @@ var renderCmd = defineCommand({
3725
3778
  "convergence-threshold": {
3726
3779
  type: "string",
3727
3780
  description: CONVERGENCE_THRESHOLD_DESCRIPTION
3781
+ },
3782
+ "nit-visibility-floor": {
3783
+ type: "string",
3784
+ description: NIT_VISIBILITY_FLOOR_DESCRIPTION
3728
3785
  }
3729
3786
  },
3730
3787
  run: async ({ args }) => {
@@ -3750,6 +3807,7 @@ var renderCmd = defineCommand({
3750
3807
  testReport,
3751
3808
  rounds: isRound ? [counts] : [],
3752
3809
  convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
3810
+ nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
3753
3811
  postedAt: formatUtc(/* @__PURE__ */ new Date())
3754
3812
  });
3755
3813
  process.stdout.write(output2);
@@ -3774,13 +3832,19 @@ var inlineCmd = defineCommand({
3774
3832
  template: {
3775
3833
  type: "string",
3776
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
3777
3839
  }
3778
3840
  },
3779
3841
  run: async ({ args }) => {
3780
3842
  const findings = decode(FindingsCodec.decode(readJSON(args.findings)), "findings");
3781
3843
  const diff = readFileSync(resolve$1(args.diff), "utf-8");
3782
3844
  const inlineTemplate = readFileSync(resolveInlineTemplatePath(args.template), "utf-8");
3783
- const { comments, strays } = buildInlineComments(findings.findings, diff, {
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, {
3784
3848
  inlineTemplate,
3785
3849
  findings
3786
3850
  });
@@ -3882,6 +3946,26 @@ var parseConvergenceThreshold = (raw) => {
3882
3946
  }
3883
3947
  return n;
3884
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
+ };
3885
3969
  var transcriptPathOf = (input) => {
3886
3970
  const tp = (typeof input === "object" && input !== null ? input : {})["transcript_path"];
3887
3971
  return typeof tp === "string" ? tp : void 0;
@@ -4180,6 +4264,10 @@ var seedDraftCmd = defineCommand({
4180
4264
  type: "string",
4181
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"
4182
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
+ },
4183
4271
  "head-sha": {
4184
4272
  type: "string",
4185
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"
@@ -4275,6 +4363,27 @@ var seedDraftCmd = defineCommand({
4275
4363
  } catch (err) {
4276
4364
  process.stderr.write(
4277
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
4278
4387
  `
4279
4388
  );
4280
4389
  }
@@ -4789,6 +4898,10 @@ var postCmd = defineCommand({
4789
4898
  "convergence-threshold": {
4790
4899
  type: "string",
4791
4900
  description: CONVERGENCE_THRESHOLD_DESCRIPTION
4901
+ },
4902
+ "nit-visibility-floor": {
4903
+ type: "string",
4904
+ description: NIT_VISIBILITY_FLOOR_DESCRIPTION
4792
4905
  }
4793
4906
  },
4794
4907
  run: async ({ args }) => {
@@ -4810,6 +4923,7 @@ var postCmd = defineCommand({
4810
4923
  runUrl: args["run-url"],
4811
4924
  jsonUrl: args["json-url"],
4812
4925
  convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
4926
+ nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
4813
4927
  postedAt: formatUtc(/* @__PURE__ */ new Date())
4814
4928
  });
4815
4929
  }