@jphutchins/code-review 0.1.0-alpha.66 → 0.1.0-alpha.67

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/dist/index.js CHANGED
@@ -70,6 +70,11 @@ var LineNumber = t.refinement(
70
70
  "LineNumber"
71
71
  );
72
72
  var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
73
+ var NonEmptyString = t.refinement(
74
+ t.string,
75
+ (s) => s.trim().length > 0,
76
+ "NonEmptyString"
77
+ );
73
78
  var Likelihood = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Likelihood");
74
79
  var FiniteNumber = t.refinement(t.number, (n) => Number.isFinite(n), "FiniteNumber");
75
80
  var NonNegativePrice = t.refinement(t.number, (n) => n >= 0, "NonNegativePrice");
@@ -236,7 +241,8 @@ var FindingsCodec = t.exact(
236
241
  // Pipeline-stamped only (issue #174); see ConvergenceCodec.
237
242
  convergence: ConvergenceCodec,
238
243
  // Agent-written best-effort (issue #182); see ChangeSizeCodec.
239
- change_size: ChangeSizeCodec
244
+ change_size: ChangeSizeCodec,
245
+ unfinished: t.array(NonEmptyString)
240
246
  })
241
247
  ])
242
248
  );
@@ -246,8 +252,31 @@ var PIPELINE_STAMPED_FIELDS = /* @__PURE__ */ new Set([
246
252
  ]);
247
253
  var RECOVERABLE_OPTIONAL_FIELDS = /* @__PURE__ */ new Set([
248
254
  ...PIPELINE_STAMPED_FIELDS,
249
- "change_size"
255
+ "change_size",
256
+ "unfinished"
250
257
  ]);
258
+ var withoutBrokenOptionalFields = (doc, accepts) => {
259
+ const bare = Object.fromEntries(
260
+ Object.entries(doc).filter(([key2]) => !RECOVERABLE_OPTIONAL_FIELDS.has(key2))
261
+ );
262
+ if (!accepts(bare)) return null;
263
+ const usable = (kept, key2, value) => {
264
+ if (accepts({ ...kept, [key2]: value })) return value;
265
+ if (!Array.isArray(value)) return void 0;
266
+ const entries = value.filter((entry) => accepts({ ...kept, [key2]: [entry] }));
267
+ return entries.length > 0 && accepts({ ...kept, [key2]: entries }) ? entries : void 0;
268
+ };
269
+ const restored = Object.entries(doc).filter(([key2]) => RECOVERABLE_OPTIONAL_FIELDS.has(key2)).reduce((kept, [key2, value]) => {
270
+ const restoredValue = usable(kept, key2, value);
271
+ return restoredValue === void 0 ? kept : { ...kept, [key2]: restoredValue };
272
+ }, bare);
273
+ return {
274
+ doc: restored,
275
+ stripped: Object.keys(doc).filter(
276
+ (key2) => !Object.hasOwn(restored, key2) || restored[key2] !== doc[key2]
277
+ )
278
+ };
279
+ };
251
280
  var ID_SHAPE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
252
281
  var priorIdsFrom = (doc) => {
253
282
  if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return [];
@@ -1939,6 +1968,19 @@ var overruledReRaiseNote = (entries, count) => entries.length === 0 ? "" : [
1939
1968
  // src/render.ts
1940
1969
  var escapePipes = (text) => text.replace(/\|/g, "\\|");
1941
1970
  var shedNote = (count, summaryAvailable, runUrl, findingsDocLinked, thisRunArtifact) => count === 0 ? "" : `<sub>**${String(count)} finding${count === 1 ? "" : "s"} left out of this comment**${summaryAvailable && runUrl !== void 0 ? ` \u2014 the [run summary](<${runUrl}>) carries the whole review` : findingsDocLinked && thisRunArtifact ? " \u2014 the machine findings document carries them" : ""}.</sub>`;
1971
+ var UNFINISHED_SHOWN = 8;
1972
+ var UNFINISHED_CLIP_CHARS = 240;
1973
+ var unfinishedLine = (check) => {
1974
+ const escaped = escapeHtml(check.replace(/\s+/g, " ").trim());
1975
+ return escaped.length > UNFINISHED_CLIP_CHARS ? `${escaped.slice(0, UNFINISHED_CLIP_CHARS).replace(/&[#a-z0-9]*$/i, "").replace(/[\uD800-\uDBFF]$/, "")}\u2026` : escaped;
1976
+ };
1977
+ var shownUnfinished = (doc, fullReviewRound) => {
1978
+ const checks = fullReviewRound ? doc.unfinished ?? [] : [];
1979
+ return [
1980
+ ...checks.slice(0, UNFINISHED_SHOWN).map(unfinishedLine),
1981
+ ...checks.length > UNFINISHED_SHOWN ? [`\u2026 and ${String(checks.length - UNFINISHED_SHOWN)} more`] : []
1982
+ ];
1983
+ };
1942
1984
  var terminalNotice = (i) => [
1943
1985
  `## ${i.verdictBadge}`,
1944
1986
  "",
@@ -1950,6 +1992,7 @@ var terminalNotice = (i) => [
1950
1992
  ] : [],
1951
1993
  "",
1952
1994
  `> **The review for \`${escapeCodeBackticks(i.reviewedSha.slice(0, 7))}\` exceeds GitHub's comment-size limit.**` + (i.summaryAvailable ? i.runUrl ? ` The whole review lives in the [run summary](<${i.runUrl}>).` : " The whole review lives in the workflow run's summary." : i.findingsDocLinked && i.thisRunArtifact ? " The whole review lives in the machine findings document." : "") + (i.findingsDocLinked && i.thisRunArtifact ? " The machine findings document is linked above." : ""),
1995
+ ...i.unfinished.length > 0 ? ["", `> **Unfinished checks:** ${i.unfinished.join(" \xB7 ")}`] : [],
1953
1996
  "",
1954
1997
  "> [!WARNING]",
1955
1998
  `> **LLM Disclosure** \u2014 this review was produced by ${i.modelNames || "unknown model"}.`,
@@ -2113,6 +2156,8 @@ var render = (input) => {
2113
2156
  const trajectory = convergence?.rounds ?? input.rounds ?? [];
2114
2157
  const sameRootNotes = input.sameRootNotes ?? computeSameRootNotes(trajectory.slice(0, -1), input.findings.findings);
2115
2158
  const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && trajectory.length > 0)) && isReviewVerdict(input.findings.verdict);
2159
+ const unfinished = input.findings.unfinished ?? [];
2160
+ const unfinishedSuffix = unfinished.length > 0 ? ` \xB7 ${String(unfinished.length)} unfinished check${unfinished.length === 1 ? "" : "s"}` : "";
2116
2161
  const contestedSuffix = (input.contested ?? []).length > 0 ? ` \xB7 ${String((input.contested ?? []).length)} contested` : "";
2117
2162
  const advisoryAllowed = isFullReviewRound;
2118
2163
  const discussionLinksFor = (id) => input.discussionByFinding !== void 0 && Object.prototype.hasOwnProperty.call(input.discussionByFinding, id) ? input.discussionByFinding[id] ?? [] : [];
@@ -2257,12 +2302,13 @@ var render = (input) => {
2257
2302
  input.jsonUrl !== void 0
2258
2303
  ),
2259
2304
  terminalNotice: terminalNotice({
2305
+ unfinished: shownUnfinished(input.findings, isFullReviewRound),
2260
2306
  reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
2261
2307
  verdictBadge: verdictBadge(input.findings.verdict),
2262
2308
  route: route ?? void 0,
2263
2309
  postedAt: input.postedAt ?? "",
2264
2310
  roundsSummary: roundsSummary(trajectory, input.roundCount),
2265
- convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) + contestedSuffix : "",
2311
+ convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) + contestedSuffix + unfinishedSuffix : "",
2266
2312
  inlinePosted: input.inlineDisposition?.kind === "posted" ? input.inlineDisposition.count : 0,
2267
2313
  runUrl: input.runUrl,
2268
2314
  summaryAvailable: input.summaryAvailable ?? false,
@@ -2289,7 +2335,7 @@ var render = (input) => {
2289
2335
  reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
2290
2336
  postedAt: input.postedAt ?? "",
2291
2337
  severityCounts,
2292
- convergenceSummary: !isFullReviewRound ? "" : (convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold)) + contestedSuffix,
2338
+ convergenceSummary: !isFullReviewRound ? "" : (convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold)) + contestedSuffix + unfinishedSuffix,
2293
2339
  strays: discussionBudget.kept,
2294
2340
  orphanedLines: orphanedRender.lines,
2295
2341
  orphanedUnresolvable: input.orphanedUnresolvable === true,
@@ -2326,6 +2372,7 @@ var render = (input) => {
2326
2372
  answeredNotes: input.answeredNotes ?? {},
2327
2373
  answeredReRaiseNote: input.answeredReRaiseNote ?? "",
2328
2374
  contested: contestedBudget.kept,
2375
+ unfinished: shownUnfinished(input.findings, isFullReviewRound),
2329
2376
  reviewUrl: input.reviewUrl ?? null,
2330
2377
  formatTokens: (n) => Number.isFinite(n) && n >= 0 ? n.toLocaleString("en-US") : "\u2014",
2331
2378
  // N/A (never a false $0.00) when no real price map was provided — real tokens, no rates to
@@ -2740,8 +2787,8 @@ var spendClause = (i) => i.spentUsd === null ? null : i.budgetUsd !== null && i.
2740
2787
  var timeClause = (i) => i.elapsedMs === null ? null : i.wallMs !== null && i.wallMs > 0 ? `${mins(i.elapsedMs)}/${mins(i.wallMs)} elapsed (${pct(i.elapsedMs / i.wallMs)})` : `${mins(i.elapsedMs)} elapsed`;
2741
2788
  var directive = (phase, draftPath, isSubagent) => {
2742
2789
  if (isSubagent)
2743
- return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now and report the findings you have back to the main agent in your reply. Do not write ${draftPath} yourself; only the main agent writes it.` : `Wind down investigation and report the findings you have back to the main agent in your reply \u2014 do not write ${draftPath} yourself; the main agent writes it, and you may run out of budget otherwise.`;
2744
- return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Do not wait for subagents still running in the background. Write your COMPLETE findings from what you already have to ${draftPath} and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 fold in the subagent reports you already have rather than waiting on stragglers; you may run out of budget before you finish otherwise.`;
2790
+ return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now and report the findings you have back to the main agent in your reply, naming each check you planned but did not finish. Do not write ${draftPath} yourself; only the main agent writes it.` : `Wind down investigation and report the findings you have back to the main agent in your reply, naming each check you planned but did not finish \u2014 do not write ${draftPath} yourself; the main agent writes it, and you may run out of budget otherwise.`;
2791
+ return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Do not wait for subagents still running in the background. Write your COMPLETE findings from what you already have to ${draftPath}, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish, and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish, then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 fold in the subagent reports you already have rather than waiting on stragglers; you may run out of budget before you finish otherwise.`;
2745
2792
  };
2746
2793
  var budgetMessage = (i, phase, draftPath, isSubagent) => {
2747
2794
  const status = [spendClause(i), timeClause(i)].filter((c) => c !== null).join(" \xB7 ");
@@ -3333,15 +3380,16 @@ var loadFindings = (path) => {
3333
3380
  }
3334
3381
  const first = decodeFindings(raw);
3335
3382
  if (first.kind === "invalid-shape" && typeof raw === "object" && raw !== null && !Array.isArray(raw) && Object.keys(raw).some((k) => RECOVERABLE_OPTIONAL_FIELDS.has(k))) {
3336
- const stripped = Object.fromEntries(
3337
- Object.entries(raw).filter(([key2]) => !RECOVERABLE_OPTIONAL_FIELDS.has(key2))
3383
+ const recovered = withoutBrokenOptionalFields(
3384
+ raw,
3385
+ (candidate) => decodeFindings(candidate).kind === "ok"
3338
3386
  );
3339
- const retry = decodeFindings(stripped);
3340
- if (retry.kind === "ok") {
3387
+ if (recovered !== null) {
3341
3388
  process.stderr.write(
3342
- "Warning: the review draft carried an invalid best-effort field (convergence/scope_metastasis/change_size) \u2014 stripped it and used the rest; the pipeline re-stamps convergence\n"
3389
+ `Warning: the review draft carried an invalid best-effort field (${recovered.stripped.join("/")}) \u2014 stripped it and used the rest; the pipeline re-stamps convergence
3390
+ `
3343
3391
  );
3344
- return retry;
3392
+ return decodeFindings(recovered.doc);
3345
3393
  }
3346
3394
  }
3347
3395
  return first;
@@ -4304,6 +4352,7 @@ ${findingsPointer2}`
4304
4352
  ] : [],
4305
4353
  "",
4306
4354
  terminalNotice({
4355
+ unfinished: [],
4307
4356
  reviewedSha: input.headSha,
4308
4357
  verdictBadge: verdictBadge(loadedFindings.verdict),
4309
4358
  route: route ?? void 0,
@@ -6619,11 +6668,6 @@ var seedDraftCmd = defineCommand({
6619
6668
  const seededFromPrior = priorFindings === null ? false : (() => {
6620
6669
  try {
6621
6670
  const schemaPath = args.schema ? resolve(args.schema) : schemaPathFor(kind, args["schema-version"]);
6622
- const barePrior = typeof priorFindings === "object" && !Array.isArray(priorFindings) ? Object.fromEntries(
6623
- Object.entries(priorFindings).filter(
6624
- ([key2]) => !RECOVERABLE_OPTIONAL_FIELDS.has(key2)
6625
- )
6626
- ) : priorFindings;
6627
6671
  const accepts = (doc) => {
6628
6672
  const resolved = resolvedPriorValue(doc);
6629
6673
  if (resolved === null) return null;
@@ -6631,13 +6675,18 @@ var seedDraftCmd = defineCommand({
6631
6675
  return validateAgainstSchema(carried, schemaPath).valid ? carried : null;
6632
6676
  };
6633
6677
  const seedDoc = accepts(priorFindings) ?? (() => {
6634
- const bare = accepts(barePrior);
6635
- if (bare === null) return null;
6678
+ if (typeof priorFindings !== "object" || Array.isArray(priorFindings))
6679
+ return null;
6680
+ const recovered = withoutBrokenOptionalFields(
6681
+ priorFindings,
6682
+ (candidate) => accepts(candidate) !== null
6683
+ );
6684
+ if (recovered === null) return null;
6636
6685
  process.stderr.write(
6637
- `Note: the in-force schema rejects a carried recoverable field (scope_metastasis/convergence/change_size) \u2014 seeding the prior without it (issue #150 review r2 / #182 review r2)
6686
+ `Note: the in-force schema rejects a carried recoverable field (${recovered.stripped.join("/")}) \u2014 seeding the prior without it (issue #150 review r2 / #182 review r2)
6638
6687
  `
6639
6688
  );
6640
- return bare;
6689
+ return accepts(recovered.doc);
6641
6690
  })();
6642
6691
  if (seedDoc === null) return false;
6643
6692
  if (isIncompleteFindings(seedDoc)) return false;