@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 +70 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +1 -1
- package/schema/findings.schema.json +5 -0
- package/templates/comment.eta +8 -0
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
|
|
3337
|
-
|
|
3383
|
+
const recovered = withoutBrokenOptionalFields(
|
|
3384
|
+
raw,
|
|
3385
|
+
(candidate) => decodeFindings(candidate).kind === "ok"
|
|
3338
3386
|
);
|
|
3339
|
-
|
|
3340
|
-
if (retry.kind === "ok") {
|
|
3387
|
+
if (recovered !== null) {
|
|
3341
3388
|
process.stderr.write(
|
|
3342
|
-
|
|
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
|
|
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
|
-
|
|
6635
|
-
|
|
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 (
|
|
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
|
|
6689
|
+
return accepts(recovered.doc);
|
|
6641
6690
|
})();
|
|
6642
6691
|
if (seedDoc === null) return false;
|
|
6643
6692
|
if (isIncompleteFindings(seedDoc)) return false;
|