@jphutchins/code-review 0.1.0-alpha.7 → 0.1.0-alpha.8
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 +11 -9
- package/dist/index.js +28 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/schema/findings.schema.json +9 -9
- package/templates/comment.eta +5 -2
- package/templates/inline.eta +2 -2
package/README.md
CHANGED
|
@@ -25,8 +25,9 @@ the security boundary, and the cost.
|
|
|
25
25
|
## The CLI
|
|
26
26
|
|
|
27
27
|
The npm package is the **deterministic commenter** — the presentation and posting layer that no
|
|
28
|
-
model should do, plus the adapter glue between an agent CLI's native output and the
|
|
29
|
-
envelope
|
|
28
|
+
model should do, plus the adapter glue between an agent CLI's native output and the abstract result
|
|
29
|
+
envelope defined in [`src/schema.ts`](src/schema.ts) (the deliverable of
|
|
30
|
+
[SPEC §3.2](SPEC.md#32-the-deliverable)).
|
|
30
31
|
|
|
31
32
|
```sh
|
|
32
33
|
npm install -g @jphutchins/code-review
|
|
@@ -40,7 +41,7 @@ npx @jphutchins/code-review <subcommand>
|
|
|
40
41
|
| `gather` | Resolve the PR from the CI head SHA and gather the review inputs (diff with git-diff fallback, PR context, prior bot review, failing-job logs) into the workspace for the agent |
|
|
41
42
|
| `render` | Render the sticky-comment markdown from findings + usage + prices |
|
|
42
43
|
| `inline` | Build the GitHub reviews `comments[]` payload from findings + diff (in-diff validation; strays demote to the summary) |
|
|
43
|
-
| `adapt` | Map a native agent-CLI result envelope onto the abstract
|
|
44
|
+
| `adapt` | Map a native agent-CLI result envelope onto the abstract result envelope (`src/schema.ts`) |
|
|
44
45
|
| `extract` | Recover findings/triage JSON from a native envelope via the deterministic extraction ladder |
|
|
45
46
|
| `validate-patches` | Validate each finding's `patch` against the real PR-head file, aligning the finding's line range to it and keeping the patch (projected into a suggestion at render time), or dropping the patch |
|
|
46
47
|
| `cost` | Recompute USD cost from the envelope's per-model token counts + a price map |
|
|
@@ -63,16 +64,17 @@ in [templates/](templates/). See [docs/adapters.md](docs/adapters.md) for the ad
|
|
|
63
64
|
letting the CLI pick where your key gets sent.
|
|
64
65
|
3. Commit `.github/prices.json` (fork [schema/prices.example.json](schema/prices.example.json) and
|
|
65
66
|
fill in your provider's per-token rates) — without it the cost footer renders **$0**
|
|
66
|
-
([SPEC §
|
|
67
|
+
([SPEC §4.4](SPEC.md#44-required-controls-conformance)).
|
|
67
68
|
4. Merge to your default branch first — `workflow_run` only fires from the default branch, so the
|
|
68
69
|
introducing PR won't review itself — then open a test PR.
|
|
69
70
|
5. First run: consider `egress-policy: audit` to discover the real allowlist, then switch to `block`
|
|
70
|
-
([SPEC
|
|
71
|
+
([SPEC Appendix A](SPEC.md#appendix-a--reference-realization-github-actions-non-normative)).
|
|
71
72
|
|
|
72
73
|
Every model knob is committed step `env` on the workflow's triage and review steps — models,
|
|
73
74
|
efforts, the subagent model, and the tier aliases, right where each is consumed — edited and
|
|
74
|
-
PR-reviewed like the rest of the file (
|
|
75
|
-
|
|
75
|
+
PR-reviewed like the rest of the file (implementation detail the spec deliberately leaves to this
|
|
76
|
+
repo — see [SPEC Appendix A](SPEC.md#appendix-a--reference-realization-github-actions-non-normative)).
|
|
77
|
+
Only the backend endpoint is a per-repo **Actions variable** (`API_BASE_URL`, required, no default); pointing it at
|
|
76
78
|
another provider requires adding that provider's API host to the workflow's egress allowlist in the
|
|
77
79
|
same reviewed PR.
|
|
78
80
|
|
|
@@ -87,8 +89,8 @@ links to:
|
|
|
87
89
|
downstream tool) SHOULD base64-decode and parse that marker rather than parse the comment's prose.
|
|
88
90
|
Embedding in the comment (rather than only linking the artifact) keeps the pointer from expiring
|
|
89
91
|
with artifact retention; when the encoded findings are too large to embed, the sticky falls back to
|
|
90
|
-
a `<!-- code-review:findings-json <url> -->` link marker instead
|
|
91
|
-
|
|
92
|
+
a `<!-- code-review:findings-json <url> -->` link marker instead — the shared serializer is
|
|
93
|
+
[`src/surface.ts`](src/surface.ts).
|
|
92
94
|
- **`code-review-transcript`** — the full Claude Code session transcripts for the triage and review
|
|
93
95
|
phases. This is advisory/auditability only: it is never read by the comment job and never affects
|
|
94
96
|
what gets posted.
|
package/dist/index.js
CHANGED
|
@@ -172,10 +172,18 @@ ${marker}` : "";
|
|
|
172
172
|
};
|
|
173
173
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
174
174
|
var projectPatch = (patch) => {
|
|
175
|
-
if (patch ===
|
|
175
|
+
if (patch === void 0) return { kind: "none" };
|
|
176
176
|
const lowered = patchToSuggestion(patch);
|
|
177
177
|
return typeof lowered === "string" ? { kind: "suggestion", text: escapeFence(lowered) } : { kind: "patch", raw: escapeFence(patch) };
|
|
178
178
|
};
|
|
179
|
+
var formatConfidence = (n) => n.toFixed(2);
|
|
180
|
+
var reviewBodyPointer = (headSha, stickyUrl, marker) => {
|
|
181
|
+
const sha7 = headSha.slice(0, 7);
|
|
182
|
+
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.`;
|
|
183
|
+
return marker ? `${marker}
|
|
184
|
+
|
|
185
|
+
${linkLine}` : linkLine;
|
|
186
|
+
};
|
|
179
187
|
|
|
180
188
|
// src/render.ts
|
|
181
189
|
var escapePipes = (text) => text.replace(/\|/g, "\\|");
|
|
@@ -215,6 +223,7 @@ var render = (input) => {
|
|
|
215
223
|
modelNames,
|
|
216
224
|
testReport: input.testReport ?? null,
|
|
217
225
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
226
|
+
postedAt: input.postedAt ?? "",
|
|
218
227
|
severityCounts: input.severityCounts ?? computeSeverityCounts(input.findings.findings),
|
|
219
228
|
strays: (input.strays ?? []).map(sanitizeFinding),
|
|
220
229
|
inlineDisposition: input.inlineDisposition ?? null,
|
|
@@ -243,7 +252,8 @@ var render = (input) => {
|
|
|
243
252
|
return `\u2753 ${v}`;
|
|
244
253
|
}
|
|
245
254
|
},
|
|
246
|
-
severityEmoji
|
|
255
|
+
severityEmoji,
|
|
256
|
+
formatConfidence
|
|
247
257
|
});
|
|
248
258
|
};
|
|
249
259
|
var key = (path, line) => `${path}:${String(line)}`;
|
|
@@ -308,6 +318,7 @@ var renderCommentBody = (f, eta, template, modelsText, jsonUrl, pointer) => (
|
|
|
308
318
|
...f,
|
|
309
319
|
patchProjection: projectPatch(f.patch),
|
|
310
320
|
severityEmoji,
|
|
321
|
+
formatConfidence,
|
|
311
322
|
modelsText,
|
|
312
323
|
jsonUrl: jsonUrl ?? null,
|
|
313
324
|
findingsPointer: pointer
|
|
@@ -388,7 +399,7 @@ var FindingShape = t.intersection([
|
|
|
388
399
|
code: t.string,
|
|
389
400
|
code_url: t.string,
|
|
390
401
|
recommendation: t.string,
|
|
391
|
-
patch: t.
|
|
402
|
+
patch: t.string
|
|
392
403
|
})
|
|
393
404
|
]);
|
|
394
405
|
var EndGeStart = t.refinement(
|
|
@@ -528,6 +539,8 @@ var formatMarkdown = (md) => {
|
|
|
528
539
|
return `${lines.join("\n").replace(/\n+$/, "")}
|
|
529
540
|
`;
|
|
530
541
|
};
|
|
542
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
543
|
+
var formatUtc = (d) => `${String(d.getUTCFullYear())}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())} UTC`;
|
|
531
544
|
var identity = (decoded) => decoded;
|
|
532
545
|
var findingsTable = [
|
|
533
546
|
{
|
|
@@ -745,11 +758,7 @@ var parseHtmlUrl = (raw) => {
|
|
|
745
758
|
}
|
|
746
759
|
};
|
|
747
760
|
var postInlineReview = async (repo, prNumber, headSha, comments, stickyUrl, marker, ghApi) => {
|
|
748
|
-
const
|
|
749
|
-
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.`;
|
|
750
|
-
const pointer = marker ? `${marker}
|
|
751
|
-
|
|
752
|
-
${linkLine}` : linkLine;
|
|
761
|
+
const pointer = reviewBodyPointer(headSha, stickyUrl, marker);
|
|
753
762
|
const body = JSON.stringify({
|
|
754
763
|
body: pointer,
|
|
755
764
|
commit_id: headSha,
|
|
@@ -901,7 +910,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
901
910
|
reviewedSha: input.headSha,
|
|
902
911
|
effort: input.effort,
|
|
903
912
|
runUrl: input.runUrl,
|
|
904
|
-
jsonUrl: input.jsonUrl
|
|
913
|
+
jsonUrl: input.jsonUrl,
|
|
914
|
+
postedAt: input.postedAt
|
|
905
915
|
})
|
|
906
916
|
);
|
|
907
917
|
if (isEmptyDiff(diff)) {
|
|
@@ -942,7 +952,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
942
952
|
testReport,
|
|
943
953
|
inlineDisposition: { kind: "no-envelope" },
|
|
944
954
|
runUrl: input.runUrl,
|
|
945
|
-
jsonUrl: input.jsonUrl
|
|
955
|
+
jsonUrl: input.jsonUrl,
|
|
956
|
+
postedAt: input.postedAt
|
|
946
957
|
})
|
|
947
958
|
);
|
|
948
959
|
await upsertSticky(input.repo, prNumber, existingSticky, body, ghApi);
|
|
@@ -981,7 +992,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
981
992
|
strays,
|
|
982
993
|
runUrl: input.runUrl,
|
|
983
994
|
jsonUrl: input.jsonUrl,
|
|
984
|
-
findingsPointer: findingsMarker
|
|
995
|
+
findingsPointer: findingsMarker,
|
|
996
|
+
postedAt: input.postedAt
|
|
985
997
|
};
|
|
986
998
|
const longFilesNote = longFiles.length > 0 ? `
|
|
987
999
|
|
|
@@ -1597,7 +1609,8 @@ var renderCmd = defineCommand({
|
|
|
1597
1609
|
reviewedSha: args["reviewed-sha"],
|
|
1598
1610
|
route: args.route,
|
|
1599
1611
|
effort: args.effort,
|
|
1600
|
-
testReport
|
|
1612
|
+
testReport,
|
|
1613
|
+
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
1601
1614
|
});
|
|
1602
1615
|
process.stdout.write(output);
|
|
1603
1616
|
}
|
|
@@ -1813,7 +1826,7 @@ var readFileLines = (path) => {
|
|
|
1813
1826
|
}
|
|
1814
1827
|
};
|
|
1815
1828
|
var validateFinding = (finding, repoRoot) => {
|
|
1816
|
-
if (finding.patch === void 0
|
|
1829
|
+
if (finding.patch === void 0) return finding;
|
|
1817
1830
|
const lines = readFileLines(resolve$1(repoRoot, finding.path));
|
|
1818
1831
|
if (lines === null) {
|
|
1819
1832
|
process.stderr.write(
|
|
@@ -2128,7 +2141,8 @@ var postCmd = defineCommand({
|
|
|
2128
2141
|
effort: args.effort,
|
|
2129
2142
|
testReportPath: args["test-report"],
|
|
2130
2143
|
runUrl: args["run-url"],
|
|
2131
|
-
jsonUrl: args["json-url"]
|
|
2144
|
+
jsonUrl: args["json-url"],
|
|
2145
|
+
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
2132
2146
|
});
|
|
2133
2147
|
}
|
|
2134
2148
|
});
|