pi-pr-review 1.10.1 → 1.10.2
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/lib/pr-review-publish.ts +14 -7
- package/package.json +1 -1
- package/prompts/pr-review.md +1 -1
- package/tests/pr-review-publish.test.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.2](https://github.com/10ego/pi-pr-review/compare/v1.10.1...v1.10.2) (2026-07-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **publish:** preserve findings with duplicate anchors ([#25](https://github.com/10ego/pi-pr-review/issues/25)) ([1df420f](https://github.com/10ego/pi-pr-review/commit/1df420f30b1dd1a74017c0442b2a880949d4b864))
|
|
9
|
+
|
|
3
10
|
## [1.10.1](https://github.com/10ego/pi-pr-review/compare/v1.10.0...v1.10.1) (2026-07-14)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ Publishing is off by default.
|
|
|
184
184
|
|
|
185
185
|
The extension—not the model—owns publishing. It creates one formal review with the event hardcoded to `COMMENT`; it never submits `APPROVE` or `REQUEST_CHANGES`. Before writing, it verifies the current PR head, validates inline anchors, and checks for a review of the same head by the current GitHub identity.
|
|
186
186
|
|
|
187
|
-
Closed or merged PRs use a body-only review. Open PRs attach eligible P0–P3 findings as inline comments and keep nits or off-diff findings in the review body.
|
|
187
|
+
Closed or merged PRs use a body-only review. Open PRs attach eligible P0–P3 findings as inline comments and keep nits or off-diff findings in the review body. When multiple findings target the same diff anchor, the first is attached inline and later findings remain in the review body so publication neither fails nor drops review content.
|
|
188
188
|
|
|
189
189
|
After a review completes, you can directly ask the agent to “post the inline review” or “post it as an inline review.” The extension handles that request directly before an agent turn, selecting the latest cached review for an unnumbered request or the named PR in “publish the review for PR #123.” Only fresh interactive/RPC input can trigger this path; extension-generated, queued, or steering input cannot. The extension publishes only validated cached content, never replacement model-authored text, and never starts or reruns review agents. A direct request permits stale publication.
|
|
190
190
|
|
package/lib/pr-review-publish.ts
CHANGED
|
@@ -734,11 +734,20 @@ export function buildReviewSummary(review: ReviewLike, inlineComments: PublishCo
|
|
|
734
734
|
lines.push("### Strengths", "", ...review.strengths.map((strength) => `- ${String(strength).replace(/^\s*-\s*/, "").trim()}`), "");
|
|
735
735
|
}
|
|
736
736
|
const findings = Array.isArray(review.findings) ? review.findings : [];
|
|
737
|
-
const inlineAnchors = new
|
|
737
|
+
const inlineAnchors = new Map<string, number>();
|
|
738
|
+
for (const comment of inlineComments) {
|
|
739
|
+
const anchor = publishCommentAnchor(comment);
|
|
740
|
+
inlineAnchors.set(anchor, (inlineAnchors.get(anchor) ?? 0) + 1);
|
|
741
|
+
}
|
|
738
742
|
const summaryFindings = findings.filter((finding) => {
|
|
739
743
|
if (!finding.code_location?.commentable || !isInlineSeverity(finding)) return true;
|
|
740
744
|
const anchor = findingAnchor(finding);
|
|
741
|
-
|
|
745
|
+
if (!anchor) return true;
|
|
746
|
+
const remaining = inlineAnchors.get(anchor) ?? 0;
|
|
747
|
+
if (remaining === 0) return true;
|
|
748
|
+
if (remaining === 1) inlineAnchors.delete(anchor);
|
|
749
|
+
else inlineAnchors.set(anchor, remaining - 1);
|
|
750
|
+
return false;
|
|
742
751
|
});
|
|
743
752
|
lines.push(
|
|
744
753
|
`### Findings — ${findings.length} total (${inlineComments.length} inline, ${summaryFindings.length} summary-only)`,
|
|
@@ -879,11 +888,6 @@ export function validateInlineComments(
|
|
|
879
888
|
continue;
|
|
880
889
|
}
|
|
881
890
|
const anchor = `${path}:${side}:${start}:${end}`;
|
|
882
|
-
if (anchors.has(anchor)) {
|
|
883
|
-
errors.push(`${label}: duplicate inline anchor`);
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
anchors.add(anchor);
|
|
887
891
|
const body = [`**${String(finding.title ?? "Review finding").trim()}**`, finding.body?.trim()]
|
|
888
892
|
.filter(Boolean)
|
|
889
893
|
.join("\n\n");
|
|
@@ -895,6 +899,9 @@ export function validateInlineComments(
|
|
|
895
899
|
errors.push(`${label}: comment body contains a reserved pi-pr-review marker`);
|
|
896
900
|
continue;
|
|
897
901
|
}
|
|
902
|
+
// GitHub receives one comment per anchor; later findings remain in the top-level summary.
|
|
903
|
+
if (anchors.has(anchor)) continue;
|
|
904
|
+
anchors.add(anchor);
|
|
898
905
|
comments.push({
|
|
899
906
|
path,
|
|
900
907
|
body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pr-review",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "Parallel AI code review for GitHub pull requests in the Pi coding agent, with model-agnostic tiered subagents, structured findings, optional verification, and safe COMMENT-only publishing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/prompts/pr-review.md
CHANGED
|
@@ -194,7 +194,7 @@ The orchestrator must never call `gh` to post comments or reviews. Always finish
|
|
|
194
194
|
- `--comment` → force publication for this run, but never bypass validation, stale-head checks, or duplicate checks
|
|
195
195
|
- `--no-comment` → suppress publication for this run
|
|
196
196
|
|
|
197
|
-
When enabled, the extension creates exactly one formal GitHub pull-request review. Its top-level body contains the overview, verification, strengths, suggested verdict, counts, nits, and findings that cannot be attached inline. Eligible P0–P3 diff-anchored findings are attached as inline comments within that same review and are omitted from the top-level issue list to avoid duplication. The API event is hardcoded to `COMMENT`: publication never sends `APPROVE` or `REQUEST_CHANGES`, even when the suggested verdict is `request_changes`. It appends the same-head marker, verifies the current head, validates every inline anchor against GitHub diff metadata, and refuses partial open-PR publication. For a known closed/merged PR it requires either trusted `--include-closed`/`--review-closed` invocation authority or the one-shot affirmative confirmation flow, then posts one body-only `COMMENT` review with each inline finding folded into the body exactly once. Unknown lifecycle states and unconfirmed non-open writes fail without posting or falling back to an issue comment.
|
|
197
|
+
When enabled, the extension creates exactly one formal GitHub pull-request review. Its top-level body contains the overview, verification, strengths, suggested verdict, counts, nits, and findings that cannot be attached inline. Eligible P0–P3 diff-anchored findings are attached as inline comments within that same review and are omitted from the top-level issue list to avoid duplication. If multiple findings share one diff anchor, the first is attached inline and later findings remain in the top-level body instead of failing publication or dropping content. The API event is hardcoded to `COMMENT`: publication never sends `APPROVE` or `REQUEST_CHANGES`, even when the suggested verdict is `request_changes`. It appends the same-head marker, verifies the current head, validates every inline anchor against GitHub diff metadata, and refuses partial open-PR publication. For a known closed/merged PR it requires either trusted `--include-closed`/`--review-closed` invocation authority or the one-shot affirmative confirmation flow, then posts one body-only `COMMENT` review with each inline finding folded into the body exactly once. Unknown lifecycle states and unconfirmed non-open writes fail without posting or falling back to an issue comment.
|
|
198
198
|
|
|
199
199
|
The extension caches the latest valid completed review per repository and PR before publication preflight in the current Pi session. The session-backed cache survives extension reloads and session resumes but is bound to the originating session instance and repository. On a later turn, the extension intercepts that direct input before an agent turn, publishes only extension-cached content, permits stale publication, and cannot rerun review agents. Stale publication is also enabled by default through the invocation-captured `allowStalePublish` setting. Every stale review is body-only, displays a warning with the reviewed and preflight-observed commit hashes, and omits potentially invalid inline anchors. If the captured setting disabled stale publication, the user can still run `/pr-review-publish <PR-NUM> --allow-stale`. Never rerun the review merely to change posting intent, and never attempt a direct GitHub write yourself.
|
|
200
200
|
|
|
@@ -648,6 +648,34 @@ describe("atomic COMMENT review payload", () => {
|
|
|
648
648
|
expect(summary).toContain("[P3] Summary-only collision");
|
|
649
649
|
});
|
|
650
650
|
|
|
651
|
+
test("summarizes additional inline findings that share an anchor", () => {
|
|
652
|
+
const colliding: ReviewLike = JSON.parse(JSON.stringify(review));
|
|
653
|
+
colliding.findings!.push({
|
|
654
|
+
title: "[P2] Preserve the second issue",
|
|
655
|
+
severity: "P2",
|
|
656
|
+
blocking: false,
|
|
657
|
+
body: "This distinct issue targets the same diff range.",
|
|
658
|
+
confidence_score: 0.85,
|
|
659
|
+
code_location: {
|
|
660
|
+
absolute_file_path: "src/parser.ts",
|
|
661
|
+
line_range: { start: 2, end: 3 },
|
|
662
|
+
side: "RIGHT",
|
|
663
|
+
commentable: true,
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const validated = validateInlineComments(colliding, changedFiles);
|
|
668
|
+
expect(validated.errors).toEqual([]);
|
|
669
|
+
expect(validated.comments).toHaveLength(1);
|
|
670
|
+
expect(validated.comments[0]?.body).toContain("[P2] Handle empty input");
|
|
671
|
+
|
|
672
|
+
const summary = buildReviewSummary(colliding, validated.comments);
|
|
673
|
+
expect(summary).toContain("3 total (1 inline, 2 summary-only)");
|
|
674
|
+
expect(summary).not.toContain("#### [P2] Handle empty input");
|
|
675
|
+
expect(summary).toContain("#### [P2] Preserve the second issue");
|
|
676
|
+
expect(summary).toContain("This distinct issue targets the same diff range.");
|
|
677
|
+
});
|
|
678
|
+
|
|
651
679
|
test("rejects anchors outside changed diff metadata", () => {
|
|
652
680
|
const invalid: ReviewLike = JSON.parse(JSON.stringify(review));
|
|
653
681
|
invalid.findings![0]!.code_location!.line_range = { start: 20, end: 20 };
|