Skip to content

feat(pr-review-toolkit): production-harden checkout, overlap identity, and manifest verification#83

Open
cblecker wants to merge 9 commits into
mainfrom
claude/pr-review-toolkit-audit-x7fe72
Open

feat(pr-review-toolkit): production-harden checkout, overlap identity, and manifest verification#83
cblecker wants to merge 9 commits into
mainfrom
claude/pr-review-toolkit-audit-x7fe72

Conversation

@cblecker

@cblecker cblecker commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Production-hardening pass over pr-review-toolkit from a design-doc-driven audit. Every change was checked against the intent in docs/PR_REVIEW_REQUIREMENTS.md and docs/WORKFLOW_REWRITE_PLAN.md, validated functionally, and then adversarially re-verified by independent review passes (which caught and fixed four additional defects in the first iteration of this branch, noted below).

checkout.sh

  • Origin verified against the PR base repository before fetching (parsed from the PR URL; the rewrite plan lists this as an eligibility check that precedes the fetch). PR URLs with query strings/fragments now parse.
  • Merge deletions are applied. The plumbing checkout (read-tree + checkout-index) never deleted files, so files removed by the PR stayed in the worktree and specialists could Read/Grep code the PR deletes. Deletions now run first (handling file→dir type changes and rename sources via --no-renames), with emptied directories pruned.
  • Mid-failure rollback. A failure between read-tree and update-ref previously left the index at the merge tree with HEAD on the original branch — permanently blocking future runs on the "dirty worktree" check. Merge parents are now resolved before any mutation, and failures restore the original index and tracked worktree content, including removing partially-written merge additions.
  • Protected paths no longer wedge the repo. A PR touching a sandbox-protected path (.vscode/, .claude/, .gitmodules, …) left index ≠ worktree after a successful checkout, so every later run silently degraded to MCP fallback. Touched protected paths are now marked skip-worktree.
  • Verification counts emitted: prDiffFileCount (merge-base..head, matches GitHub's changedFiles semantics) and mergeDiffFileCount (guards against incomplete manifest parsing). Runs from the repo toplevel; index refreshed after checkout.

review-pr.js

  • Local manifest survives base-branch advancement. The merge diff legitimately differs from GitHub's changedFiles when the base has moved; the workflow now keeps the local manifest when the like-for-like counts verify, instead of discarding accurate local data for MCP fallback.
  • Thread identity is consistent. Merged duplicate findings could pair one thread's threadId with another's commentId, pointing endorsement replies at the wrong thread. Identity (threadId/commentId/isResolved) now always comes from a single thread, both in merging and in overlap inference.
  • Synthesizer overlap classification is preserved. Token-overlap inference no longer downgrades the synthesizer's by-concern classification to none (which routed already-covered findings into "recommended to post"); it only attaches thread identity or classifies when the synthesizer said none.
  • isResolved is honestly tri-state. The schema no longer forces the collector to fabricate resolution state; unknown stays absent rather than being coerced to false, and the posting preview says when resolution is unknown.
  • Thread-collection failure is visible (log + reviewMeta.threadCollectionFailed + board warning) instead of silently disabling overlap classification.
  • Very large PRs no longer blow specialist prompts: above 400 files, prompts carry the highest-signal subset plus an explicit per-category omission summary (reviewMeta.manifestPromptTruncation); the complete manifest is still collected, per the large-PR honesty requirements.
  • Diff-header parsing hardened: +++ b/ headers with trailing tabs (space-containing paths) and C-quoted paths now key hunks by the real path, so diff-content signals aren't silently lost; a comments signal selects comment-analyzer for comment-only source changes.

Agents

  • pr-review-analysis-readonly now uses a tools allowlist (Read/Grep/Glob, PR reads, gopls read tools). GitHub write tools (add_reply_to_pull_request_comment, issue_write, push_files, …) were previously available and only discouraged by prose, contradicting the README and the safety requirements.

Skills / docs

  • SKILL contracts updated for the new checkout output, including C-quoted path decoding; README corrected (checkout description, exclusion list, permissions including reply/issue-comment capabilities, APPROVE event, agent table); WORKFLOW_REWRITE_PLAN.md marked historical.
  • address-pr-feedback: simpler mainline detection with fallback, plan-mode wording fix, resolved-thread awareness in both collection paths.
  • Version bumped 1.12.1 → 1.13.0 (minor).

Validation

  • checkout.sh git harness, 13 scenarios: happy path (deletions, dir pruning, count verification), idempotent rerun, query-string URLs, origin mismatch (no fetch performed), dirty worktree, missing merge ref, non-merge ref (no mutation), rename + file→dir type change, subdirectory invocation, case-insensitive matching, protected-path PR (clean status + rerun works).
  • Workflow simulation, 11 scenarios: the real review-pr.js executed with stubbed agent()/parallel() — verified manifest keep/fallback decisions, thread-failure visibility, overlap preservation and identity consistency, fallback board, prompt capping, comment-signal selection, TAB/C-quoted header handling, tri-state isResolved, incomplete-parse fallback.
  • Adversarial verification: 8 independent refute-minded review passes (per change area, plus design-intent judge and completeness critic) over the diff against the design docs. Confirmed findings from that pass — the protected-path wedge, a trailing-TAB header regression, an asNumber(null, -1) === 0 sentinel bug, and the isResolved coercion — are fixed in this branch.
  • claude plugin validate ✓ (marketplace + plugin), markdownlint ✓ (0 issues), skillsaw --strict ✓ (A+).

🤖 Generated with Claude Code

https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Expanded review-board context with merge/pr diff file counts, manifest truncation details, and thread-resolution status (resolved/unknown tri-state handling).
    • Added clearer reply guidance, including behavior when threads are resolved or when no valid reply target exists.
    • Tightened read-only reviewer tool permissions with a stricter allowlist and expanded supported reply behaviors.
  • Bug Fixes

    • Improved safer local PR checkout/diff derivation (stricter URL validation, protected-path handling, and more robust diff parsing).
    • Better handling of resolved/unresolved threads when resolution data is missing; improved reply targeting/validation.
  • Documentation

    • Updated workflow/permissions/checkout documentation and bumped plugin version to 1.13.0.

…, and manifest verification

Fixes from a design-doc-driven audit of the review-pr flow, each validated
with a functional git harness (13 scenarios) and a stubbed end-to-end run of
the workflow script (11 scenarios), then adversarially re-verified:

checkout.sh
- verify origin matches the PR base repository before fetching (per the
  rewrite plan's eligibility ordering), tolerating URL query/fragment tails
- apply merge deletions and prune emptied directories (plumbing checkout
  previously left deleted files readable by specialists)
- restore the original index and tracked worktree content on mid-checkout
  failure, including removing partially-written merge additions
- mark protected paths the merge touched as skip-worktree so a successful
  checkout no longer leaves the repo permanently dirty on reruns
- emit prDiffFileCount and mergeDiffFileCount so the workflow can keep a
  verified local manifest when the base branch has advanced, and reject an
  incompletely parsed one
- run from the repository toplevel; refresh the index after checkout

review-pr.js
- preserve the synthesizer's overlap classification instead of downgrading
  it via token matching; attach thread identity as a consistent triple so
  threadId/commentId/isResolved always describe one thread (also when
  merging duplicate findings)
- propagate isResolved as tri-state; unknown resolution is no longer
  recorded as false, and the posting preview says when it is unknown
- surface review-thread collection failure in logs and reviewMeta instead
  of silently disabling overlap classification
- cap specialist prompt manifests for very large PRs with an explicit
  per-category omission summary (complete manifest still collected)
- parse diff headers from +++ b/ lines handling trailing tabs and C-quoted
  paths; decode quoted paths so hunk keys match manifest paths
- add a comments signal so comment-only changes select comment-analyzer
  when the full diff is available

agents
- enforce read-only analysis via a tools allowlist (GitHub write tools were
  previously only discouraged by prose)

skills/docs
- update SKILL contracts for the new checkout output, C-quoted path
  decoding, and truncation/thread-failure warnings
- address-pr-feedback: simpler mainline detection, plan-mode wording fix,
  resolved-thread awareness in both collection paths
- README: correct checkout description, permissions (including reply and
  issue-comment capabilities), APPROVE event, agent table; mark the rewrite
  plan as historical
- bump plugin to 1.13.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 25, 2026 03:12
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fcb81a9d-3437-4c34-8747-0ff3d678c2e5

📥 Commits

Reviewing files that changed from the base of the PR and between 25e6189 and fc74a87.

📒 Files selected for processing (4)
  • pr-review-toolkit/skills/address-pr-feedback/SKILL.md
  • pr-review-toolkit/skills/review-pr/SKILL.md
  • pr-review-toolkit/skills/review-pr/review-pr.js
  • pr-review-toolkit/skills/review-pr/scripts/checkout.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • pr-review-toolkit/skills/review-pr/SKILL.md

📝 Walkthrough

Walkthrough

The PR hardens local merge checkout and manifest verification, preserves tri-state review-thread resolution, adds review-board diagnostics and manifest truncation metadata, narrows specialist permissions, updates feedback workflows, and increments the plugin version.

Changes

Review workflow hardening

Layer / File(s) Summary
Validated local checkout and manifest source
pr-review-toolkit/skills/review-pr/scripts/checkout.sh, pr-review-toolkit/skills/review-pr/SKILL.md, pr-review-toolkit/skills/review-pr/review-pr.js
Checkout validates repository identity, protects sandbox paths, restores partial failures, emits merge and diff metadata, and supports verified local manifest consumption.
Review context, manifest signals, and verification
pr-review-toolkit/skills/review-pr/review-pr.js, pr-review-toolkit/skills/review-pr/SKILL.md
Git paths and diff signals are enriched, local manifests are checked against diff counts, and specialist prompts use prioritized manifests with truncation metadata.
Tri-state thread resolution and overlap synthesis
pr-review-toolkit/skills/review-pr/review-pr.js, pr-review-toolkit/skills/review-pr/SKILL.md, pr-review-toolkit/skills/address-pr-feedback/SKILL.md
Thread matching preserves unknown resolution state and selected identities, while collection, preview, and reply flows carry conditional isResolved behavior.
Workflow instructions and agent boundaries
pr-review-toolkit/skills/review-pr/SKILL.md, pr-review-toolkit/skills/address-pr-feedback/SKILL.md, pr-review-toolkit/agents/pr-review-analysis-readonly.md, pr-review-toolkit/README.md
Workflow guidance documents checkout validation, branch detection, planning, reply visibility, posting rules, and restricted read-only specialist tools.
Documentation and release metadata
pr-review-toolkit/README.md, pr-review-toolkit/docs/WORKFLOW_REWRITE_PLAN.md, pr-review-toolkit/.claude-plugin/plugin.json
Documentation identifies authoritative implementation behavior and the plugin version changes to 1.13.0.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Reviewer
  participant ReviewWorkflow
  participant checkout.sh
  participant GitHubMCP
  Reviewer->>ReviewWorkflow: start PR review
  ReviewWorkflow->>checkout.sh: validate and prepare synthetic merge
  checkout.sh-->>ReviewWorkflow: return manifest and diff counts
  ReviewWorkflow->>GitHubMCP: collect threads and fallback data when needed
  GitHubMCP-->>ReviewWorkflow: return review context
  ReviewWorkflow-->>Reviewer: present review board and posting options
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: hardened checkout, overlap identity handling, and manifest verification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pr-review-toolkit-audit-x7fe72

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pr-review-toolkit/skills/review-pr/review-pr.js`:
- Around line 985-1007: Update unquoteGitPath to accumulate octal escape values
as raw bytes and decode the complete byte sequence as UTF-8, rather than
converting each octal escape directly with String.fromCharCode. Preserve
existing handling for ordinary characters and non-octal escapes, and ensure
paths such as escaped UTF-8 filenames match manifest keys used by
hunks[currentFile].

In `@pr-review-toolkit/skills/review-pr/scripts/checkout.sh`:
- Around line 25-27: Extend the checkout preflight after the existing git
diff-index check to detect untracked files as well as tracked modifications, and
call the existing skip path with the clean-worktree message when any are
present. Use Git’s standard untracked-file listing or status check, preserving
the current behavior for clean worktrees and avoiding changes to checkout-index
or restore_original.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d87fd3f-108a-4ed3-919d-85bd905da25d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d1a0f4 and da5d882.

📒 Files selected for processing (8)
  • pr-review-toolkit/.claude-plugin/plugin.json
  • pr-review-toolkit/README.md
  • pr-review-toolkit/agents/pr-review-analysis-readonly.md
  • pr-review-toolkit/docs/WORKFLOW_REWRITE_PLAN.md
  • pr-review-toolkit/skills/address-pr-feedback/SKILL.md
  • pr-review-toolkit/skills/review-pr/SKILL.md
  • pr-review-toolkit/skills/review-pr/review-pr.js
  • pr-review-toolkit/skills/review-pr/scripts/checkout.sh

Comment thread pr-review-toolkit/skills/review-pr/review-pr.js
Comment thread pr-review-toolkit/skills/review-pr/scripts/checkout.sh

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens pr-review-toolkit checkout safety, manifest verification, overlap handling, and reviewer permissions.

Changes:

  • Adds rollback-aware local merge checkout and manifest verification.
  • Improves thread identity, large-PR prompts, and diff-path parsing.
  • Updates agent restrictions, skill contracts, and documentation.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pr-review-toolkit/.claude-plugin/plugin.json Bumps plugin minor version.
pr-review-toolkit/agents/pr-review-analysis-readonly.md Adds an explicit read-only tool allowlist.
pr-review-toolkit/README.md Documents checkout, workflow, and permission changes.
pr-review-toolkit/docs/WORKFLOW_REWRITE_PLAN.md Marks the rewrite plan as historical.
pr-review-toolkit/skills/address-pr-feedback/SKILL.md Improves mainline detection and resolved-thread handling.
pr-review-toolkit/skills/review-pr/SKILL.md Updates checkout, manifest, and preview contracts.
pr-review-toolkit/skills/review-pr/scripts/checkout.sh Implements origin checks, rollback, deletions, and verification counts.
pr-review-toolkit/skills/review-pr/review-pr.js Hardens overlap, manifest, thread, and diff processing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +42
normalized_origin="$(printf '%s' "${origin_url}" \
| sed -e 's|/*$||' -e 's|\.git$||' -e 's|:|/|g' \
| awk -F/ 'NF>=2 {print tolower($(NF-1))"/"tolower($NF)}')"
expected_repo="$(printf '%s/%s' "${pr_owner}" "${pr_repo}" | tr '[:upper:]' '[:lower:]')"
if [[ "${normalized_origin}" != "${expected_repo}" ]]; then
skip "origin does not match PR base repository"
fi
Comment on lines +99 to +100
while IFS= read -r -d '' added_path; do
rm -f -- "${added_path}" 2>/dev/null || true
Comment on lines +985 to +1007
function unquoteGitPath(value) {
var escapes = { t: '\t', n: '\n', r: '\r', '"': '"', '\\': '\\', a: '\x07', b: '\b', f: '\f', v: '\v' }
var out = ''
for (var i = 0; i < value.length; i++) {
var ch = value.charAt(i)
if (ch !== '\\') {
out += ch
continue
}
var next = value.charAt(i + 1)
if (next >= '0' && next <= '7') {
var oct = ''
while (oct.length < 3 && value.charAt(i + 1) >= '0' && value.charAt(i + 1) <= '7') {
oct += value.charAt(++i)
}
out += String.fromCharCode(parseInt(oct, 8))
} else {
out += escapes[next] != null ? escapes[next] : next
i++
}
}
return out
}
Comment on lines +845 to +847
if (!matched) {
const best = bestThreadMatch(item, threads)
matched = best ? best.thread : null
… and path decoding

Addresses the four valid items from CodeRabbit and Copilot review on #83:

- checkout.sh: refuse to overwrite untracked local files — paths the merge
  adds that already exist on disk now skip before any mutation, matching
  git checkout's own guard (targeted collision check rather than rejecting
  all untracked files, which would disable the local path for most real
  worktrees)
- checkout.sh: require the effective origin host to be github.com before
  the owner/repo comparison; PR URLs are always github.com, so a same-named
  repository on another host is never fetched
- review-pr.js: decode octal path escapes as UTF-8 byte sequences via
  decodeURIComponent (Buffer is unavailable in the Workflow sandbox, so the
  bots' suggested fix is adapted), with a per-byte fallback for malformed
  sequences
- review-pr.js/SKILL.md: instruct the synthesizer to copy thread ids from
  its input when classifying overlaps, and make the posting preview flag
  overlap findings with no reply target instead of silently falling back
  to a new line comment

Validated: checkout harness extended with untracked-collision, evil-host,
and host-extraction scenarios (all green, plus regressions); workflow
simulation extended with a UTF-8 quoted-path scenario; decoder unit checks
including malformed-sequence fallback; plugin validate and markdownlint
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 02:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Comment on lines +126 to +129
while IFS= read -r -d '' added_path; do
if [[ -e "${added_path}" || -L "${added_path}" ]]; then
skip "untracked files would be overwritten by the merge checkout"
fi
@@ -1477,15 +1616,30 @@ if (localGitManifest && localGitManifest.length > 0) {
manifestSource = 'local-git'

if (pr.changedFiles && files.length !== pr.changedFiles) {
Comment on lines +22 to +26
- Get the mainline branch: run
`git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null` and strip
the `origin/` prefix. If that fails (origin/HEAD not set locally), fall
back to
`git ls-remote --symref origin HEAD 2>/dev/null | grep "^ref:" | awk '{print $2}' | sed 's|refs/heads/||'`
const threadData = await threadCollectionPromise

const threads = (threadData && Array.isArray(threadData.threads)) ? threadData.threads : []
const threadCollectionFailed = !(threadData && Array.isArray(threadData.threads))
Comment on lines +78 to +79
- `isResolved` — thread resolution state when the API response exposes it;
null when unknown (do not guess)
'concurrency': /\b(mutex|Mutex|chan\s|go\s+func|async\s|await\s|WaitGroup|Semaphore|threading|concurrent)\b|Lock\(\)|RLock\(\)/,
'public-api': /\b(export\s|pub\s+fn|public\s+func|module\.exports)\b/
'public-api': /\b(export\s|pub\s+fn|public\s+func|module\.exports)\b/,
'comments': /(^|\n)\s*(\/\/|#\s|\/\*|\*\s|<!--|"""|''')/
- checkout.sh: collision guard only treats regular files and symlinks as
  untracked collisions, so a tracked directory replaced by a file
  (dir-to-file type change) is no longer spuriously refused; genuine
  untracked collisions still skip before mutation
- review-pr.js: validate the parsed manifest against mergeDiffFileCount
  unconditionally — a lossy parse that coincidentally equals GitHub's
  changedFiles no longer bypasses verification
- review-pr.js: thread collector reports collectionFailed explicitly, so a
  failed read is distinguishable from a PR with no review threads and the
  board warning fires reliably
- review-pr.js: comments signal matches #TODO/#noqa-style line comments
  while still excluding shebangs and C-preprocessor directives (line-start
  anchoring is kept deliberately so CSS colors and URL fragments do not
  trigger it)
- address-pr-feedback: mainline detection queries the remote first (cached
  origin/HEAD can be stale after a default-branch rename) with the local
  symbolic ref as offline fallback; deduplicated items track isResolved per
  reply target so resolution warnings apply per thread
- review-pr SKILL.md: trimmed under the skillsaw context budget (was 3,089
  tokens against a 3,000 warn limit) with no semantic changes

Validated: checkout harness adds a dir-to-file type-change scenario plus
collision and protected-path regressions; workflow simulation adds lossy
parse and collector-reported-failure scenarios (14 total green); comments
regex unit checks; plugin validate, markdownlint, and skillsaw (A+, 0
warnings) all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 02:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

# stale-path loop below, and untracked leftovers make checkout-index fail
# into the rollback path instead of losing data.
while IFS= read -r -d '' added_path; do
if [[ -f "${added_path}" || -L "${added_path}" ]]; then
Comment on lines +117 to +118
git read-tree "${orig_head}" 2>/dev/null || return 0
checkout_worktree 2>/dev/null || true
const order = { none: 0, overlaps: 1, already_covered: 2 }
const leftStatus = (left && left.status) || 'none'
const rightStatus = (right && right.status) || 'none'
const selected = (order[rightStatus] > order[leftStatus]) ? right : left
'concurrency': /\b(mutex|Mutex|chan\s|go\s+func|async\s|await\s|WaitGroup|Semaphore|threading|concurrent)\b|Lock\(\)|RLock\(\)/,
'public-api': /\b(export\s|pub\s+fn|public\s+func|module\.exports)\b/
'public-api': /\b(export\s|pub\s+fn|public\s+func|module\.exports)\b/,
'comments': /(^|\n)\s*(\/\/|#(?!!|include\b|define\b|undef\b|ifdef\b|ifndef\b|if\b|elif\b|else\b|endif\b|pragma\b|error\b|line\b)|\/\*|\*\s|<!--|"""|''')/
…ut and merge edge cases

- checkout.sh: collision guard treats any non-directory inode (FIFO,
  socket, device node, not just regular files and symlinks) as untracked
  data that must not be overwritten, while still allowing dir-to-file
  type changes through
- checkout.sh: rollback failures propagate — restore_original reports
  whether tracked state was confirmed restored, and every mutation-phase
  failure goes through fail_checkout, whose skip reason tells the user to
  run git reset --hard when the restore was incomplete instead of
  pretending the fallback repository is clean
- review-pr.js: bestOverlap breaks equal-status ties in favor of the
  duplicate that carries a reply target, so merging can no longer discard
  a confirmed threadId/commentId and then claim no reply target exists
- review-pr.js: comments signal also matches inline/trailing comments
  (whitespace-delimited // and #, inline /* */) while keeping CSS colors,
  URLs, shebangs, and preprocessor directives excluded

Validated: checkout harness adds a FIFO-collision scenario plus dir-to-file,
file-collision, protected-path, and happy-path regressions; workflow
simulation adds an equal-status tie-break scenario (15 total green); inline
comment regex unit checks; plugin validate and skillsaw (A+, 0 warnings)
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 02:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

pr-review-toolkit/skills/review-pr/scripts/checkout.sh:146

  • This guard still allows an existing symlink whose target is a directory because -d follows symlinks. For a merge-added path, checkout-index -f can then replace that untracked symlink, and rollback removes the replacement rather than restoring the symlink. Test -L independently so untracked symlinks are never overwritten.
    if [[ (-e "${added_path}" || -L "${added_path}") && ! -d "${added_path}" ]]; then

pr-review-toolkit/skills/review-pr/review-pr.js:788

  • On equal overlap status this prefers a left-side threadId over a right-side commentId, even though only commentId is a usable reply target. A duplicate merge can therefore discard a valid reply target and later report that none is available. Prefer either side's commentId before falling back to a thread-only identity.
    selected = (left && (left.commentId || left.threadId)) ? left : (right || left)

pr-review-toolkit/skills/review-pr/review-pr.js:858

  • If the synthesizer supplies an identity that does not resolve, this fallback can attach a different token-matched thread's identity—the exact mismatch the following comment says must be avoided. Only infer a thread by content when no identity was supplied; otherwise keep the unresolved identity together rather than redirecting a reply.
    if (!matched) {
      const best = bestThreadMatch(item, threads)
      matched = best ? best.thread : null
    }

pr-review-toolkit/skills/review-pr/review-pr.js:153

  • The schema makes collectionFailed optional while the consumer treats an omitted flag plus threads: [] as a successful collection. A collector can therefore return schema-valid output after a failed read and silently disable the new warning. Require the flag so success versus failure is explicit.
        required: ['id', 'path', 'author', 'body']

pr-review-toolkit/skills/review-pr/review-pr.js:1700

  • These metadata fields still serialize explicit null counts as 0, so reviewMeta.sources claims a known zero instead of the documented -1 unknown sentinel. Apply the same null-aware conversion used for manifest validation.
    prDiffFileCount: asNumber(configSources ? configSources.prDiffFileCount : undefined, -1),
    mergeDiffFileCount: asNumber(configSources ? configSources.mergeDiffFileCount : undefined, -1),

Comment on lines +1642 to +1646
const prDiffFileCount = asNumber(configSources ? configSources.prDiffFileCount : undefined, -1)
const mergeDiffFileCount = asNumber(configSources ? configSources.mergeDiffFileCount : undefined, -1)
let manifestProblem = ''
if (mergeDiffFileCount >= 0 && files.length !== mergeDiffFileCount) {
manifestProblem = 'local manifest parse incomplete: merge diff has ' + mergeDiffFileCount + ' file(s), parsed ' + files.length
…ity and sentinel edge cases

- checkout.sh: collision guard tests -L independently of -d, so an
  untracked symlink pointing at a directory is protected instead of being
  replaced by checkout-index
- review-pr.js: sourceCount helper maps null/undefined verification counts
  to the -1 unknown sentinel everywhere (Number(null) is 0), and the local
  manifest is no longer trusted at all when mergeDiffFileCount is missing —
  checkout.sh always emits it, so absence means the contract was broken
- review-pr.js: equal-status duplicate merges prefer the side with a
  commentId (the id posting actually uses) over a thread-only identity
- review-pr.js: content matching attaches a thread only when the
  synthesizer supplied no identity; a supplied-but-unresolved identity is
  kept as-is so a reply is never silently redirected to a token-matched
  thread that may be the wrong conversation
- review-pr.js: THREAD_SCHEMA requires collectionFailed (removing a
  duplicate required key that made the earlier addition ineffective), so a
  failed read can never be schema-valid while resembling a comment-free PR

Validated: checkout harness adds a symlink-to-directory collision scenario
plus FIFO, dir-to-file, file-collision, protected-path, and happy-path
regressions; workflow simulation adds missing-count fallback with -1
provenance and commentId-preference tie-break scenarios (17 total green);
plugin validate, markdownlint, and skillsaw (A+, 0 warnings) clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment on lines +187 to +188
done < <(git diff --name-only -z --no-renames --diff-filter=AM \
"${orig_head}" "${merge_sha}" -- "${protected_match[@]}" 2>/dev/null)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The observation is correct, but falling back to MCP would be disproportionate: the sandbox deny list makes unlinking these paths impossible by design, so a leftover untracked file is the best achievable worktree state — and it's the same documented tradeoff already accepted for modified protected paths (which keep pre-merge content). Discarding the entire verified local diff because a PR deletes .vscode/settings.json would hurt review quality far more than the leftover hurts it. Preflight and reruns are unaffected (diff-index ignores untracked files, verified in the harness), and the merge diff/manifest still carry the real deletion. ab38275 documents the deletion case explicitly in the README instead.


Generated by Claude Code

Comment on lines +865 to +868
if (idsSupplied) {
matched = (threads || []).find(t =>
t && ((existing.threadId && t.id === existing.threadId) || (existing.commentId && t.commentId === existing.commentId))
) || null
Comment on lines +286 to +289
- if there is no `commentId`, do not silently fall through to a new comment:
show `⚠ No reply target available — posting would create a new line comment
that may duplicate the existing thread.` and let the user choose line
comment, review body, or skip
…targeting

- SKILL.md: a reply target the API rejects at posting time no longer falls
  back to a new line comment silently — the finding is converted, the
  revised preview is shown, and approval is requested again, matching the
  invalid-line-location flow
- review-pr.js: when the synthesizer supplies both thread ids, threadId is
  authoritative — commentId is used for lookup only when no threadId was
  supplied, so a mangled id pair can never resolve to whichever thread
  happens to appear first and misdirect an approved reply
- README: document that a protected path deleted by a PR remains on disk
  as an untracked leftover (the sandbox blocks unlinking it); preflight and
  reruns are unaffected and the merge diff still carries the real change

Validated: workflow simulation adds a conflicting-id scenario asserting the
identity triple stays within the threadId-resolved conversation (18 total
green); plugin validate, markdownlint, and skillsaw (A+, 0 warnings) clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 03:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

pr-review-toolkit/skills/address-pr-feedback/SKILL.md:27

  • The offline fallback is not reliably reached: in a normal shell, this pipeline’s exit status is sed’s, so a failed git ls-remote still typically exits 0 with empty output. That leaves mainline empty and can let the skill continue while checked out on the mainline branch—the safety check this phase is meant to enforce. Trigger the fallback when the command returns an empty branch name (not only on a nonzero pipeline status), and stop safely if both lookups are empty.
  `git ls-remote --symref origin HEAD 2>/dev/null | grep "^ref:" | awk '{print $2}' | sed 's|refs/heads/||'`.
  If that fails (offline), fall back to
  `git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null` and strip
  the `origin/` prefix

Comment on lines +185 to +188
while IFS= read -r -d '' protected_path; do
git update-index --skip-worktree -- "${protected_path}" 2>/dev/null || true
done < <(git diff --name-only -z --no-renames --diff-filter=AM \
"${orig_head}" "${merge_sha}" -- "${protected_match[@]}" 2>/dev/null)
…den mainline detection

- checkout.sh: read-tree clears skip-worktree bits, and on an idempotent
  rerun the orig->merge diff is empty so the marking loop never restores
  them — a third run then saw a dirty tracked worktree and silently fell
  back to MCP. Bits carried by protected paths are now captured before any
  read-tree and reapplied after the checkout (and after rollback, whose
  restore read-tree clears them too)
- address-pr-feedback: the ls-remote mainline pipeline exits 0 with empty
  output when the remote is unreachable (the pipeline status is sed's), so
  the fallback is now triggered on empty output rather than exit status,
  and execution stops safely when both lookups return nothing instead of
  proceeding with the mainline safety check unresolved

Validated: checkout harness adds a three-run idempotency scenario asserting
the skip-worktree bit and clean status survive every rerun, plus collision,
dir-to-file, and happy-path regressions; plugin validate, markdownlint, and
skillsaw (A+, 0 warnings) clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 03:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

pr-review-toolkit/skills/review-pr/scripts/checkout.sh:168

  • This captures every pre-existing skip-worktree entry as though the script owned it. The clean-worktree preflight does not see local edits behind that bit; read-tree then clears the bit and checkout-index -f overwrites those paths before the bit is reapplied, which can destroy user changes (and also materialize sparse-checkout paths). Treat only sandbox-protected entries as script-owned, and abort or separately verify any non-protected skip-worktree entries before mutating the index.
skip_worktree_paths=()
while IFS= read -r -d '' entry; do
    if [[ "${entry}" == "S "* ]]; then
        skip_worktree_paths+=("${entry:2}")
    fi
done < <(git ls-files -t -z 2>/dev/null)

pr-review-toolkit/skills/review-pr/review-pr.js:889

  • When an authoritative threadId fails lookup, this still preserves the synthesizer's commentId. Posting uses only commentId, so a mangled pair whose comment ID belongs to another valid thread will still send the approved reply to that wrong thread—the exact identity-mixing failure this branch is intended to prevent. Drop the comment target and resolution state when a supplied threadId cannot be resolved so the preview requires a new posting choice.
      threadId: matched ? (matched.id || '') : (existing.threadId || ''),
      commentId: matched ? (matched.commentId || undefined) : (existing.commentId || undefined),
      isResolved: matched ? knownResolved(matched.isResolved) : knownResolved(existing.isResolved),

Comment on lines +1445 to +1448
omittedSummary: {
omittedFileCount: omitted.length,
byCategory: byCategory,
note: 'The complete manifest was collected; this prompt lists the ' + PROMPT_FILE_CAP + ' highest-signal files and summarizes the rest by category above. Omitted files are still part of the PR and can be inspected directly (Read/Grep on the merged checkout, or the focused patch access instructions).'

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pr-review-toolkit/skills/address-pr-feedback/SKILL.md`:
- Around line 24-30: Update the mainline-branch lookup instructions around git
ls-remote and the cached origin/HEAD fallback to capture and inspect the remote
command’s exit status. Permit the cached fallback only for confirmed
connectivity/offline failures; treat authentication, invalid-remote, and other
lookup errors as fatal, display the existing determination error, and stop
without proceeding with an unresolved branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e09622fe-2112-4199-81b5-ff3fc495b5ee

📥 Commits

Reviewing files that changed from the base of the PR and between 2e66ab0 and 25e6189.

📒 Files selected for processing (5)
  • pr-review-toolkit/README.md
  • pr-review-toolkit/skills/address-pr-feedback/SKILL.md
  • pr-review-toolkit/skills/review-pr/SKILL.md
  • pr-review-toolkit/skills/review-pr/review-pr.js
  • pr-review-toolkit/skills/review-pr/scripts/checkout.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • pr-review-toolkit/README.md
  • pr-review-toolkit/skills/review-pr/scripts/checkout.sh

Comment thread pr-review-toolkit/skills/address-pr-feedback/SKILL.md Outdated
…d reply targets, index omitted files

Addresses CodeRabbit's first full review and Copilot's seventh round:

- checkout.sh: the script only owns skip-worktree bits on sandbox-protected
  paths. Bits elsewhere (manual skip-worktree, sparse checkout) can hide
  local modifications the clean-worktree preflight cannot see, and
  checkout-index -f would overwrite them while reapplying the bit hid the
  damage — such worktrees are now refused before any mutation
- review-pr.js: when the synthesizer's authoritative threadId cannot be
  resolved, the commentId and resolution state are dropped rather than
  kept — posting uses commentId, so a mangled pair could still reply to a
  wrong-but-valid thread; the preview now asks for a posting choice instead
- review-pr.js: the truncated-manifest summary carries a compact
  per-directory expansion index (count, categories, get_files page range)
  so omitted large-PR areas are actionable on follow-up per the design
  docs' large-PR requirements, without reinflating specialist prompts
- address-pr-feedback: mainline detection fails closed — the cached
  origin/HEAD fallback is permitted only for connectivity failures;
  authentication, invalid-remote, and configuration errors stop execution
  instead of letting a stale cached branch pass the safety check

Validated: checkout harness adds a user-owned skip-worktree refusal
scenario (hidden local edit and bit survive untouched) plus three-run
protected-path and collision regressions; workflow simulation adds a
ghost-threadId scenario and byDirectory index assertions (19 total green);
plugin validate, markdownlint, and skillsaw (A+, 0 warnings) clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 03:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

pr-review-toolkit/skills/review-pr/review-pr.js:1463

  • The 400-file cap does not actually bound prompt size because byDirectory creates one entry per distinct two-segment directory among every omitted file. A very large PR with files spread across many directories can therefore still inject an O(total files) omission index into every specialist prompt, defeating the hardening this function is intended to provide. Cap directory entries too and aggregate the remainder into counts/categories.
  const byDirectory = {}
  omitted.forEach(file => {
    const segments = String(file.path || '').split('/')
    const dir = segments.length > 1 ? segments.slice(0, Math.min(2, segments.length - 1)).join('/') : '.'
    if (!byDirectory[dir]) byDirectory[dir] = { count: 0, categories: {} }

Comment on lines +1092 to +1095
if (awaitingHeader) {
var headerMatch = /^\+\+\+ (?:"b\/(.+)"|b\/(.+?))\t?$/.exec(line)
if (headerMatch) {
currentFile = headerMatch[1] != null ? unquoteGitPath(headerMatch[1]) : headerMatch[2]
Comment on lines +181 to +182
while IFS= read -r -d '' entry; do
if [[ "${entry}" != "S "* ]]; then
…n state and prompt bounds

- checkout.sh: the hidden-state guard also refuses assume-unchanged
  entries outside the sandbox-protected set — diff-index skips them like
  skip-worktree paths, so checkout-index -f could overwrite local edits
  the preflight cannot see
- review-pr.js: the omitted-file directory index is capped at 100 entries
  (largest first, deterministic ordering) with the remainder aggregated
  into counts and categories, so a PR spread across thousands of
  directories cannot reintroduce the O(files) prompt payload the file cap
  exists to prevent
- SKILL.md: the full-diff command pins --src-prefix=a/ --dst-prefix=b/ so
  header parsing (and every diff-derived reviewer signal) survives
  diff.noprefix and diff.mnemonicPrefix configuration

Validated: checkout harness adds assume-unchanged refusal and a live
noprefix-config prefix-override check, plus skip-worktree, three-run, and
happy-path regressions; workflow simulation adds a 600-file many-directory
scenario asserting the capped index and aggregate counts reconcile (20
total green); plugin validate, markdownlint, and skillsaw (A+, 0 warnings)
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjwsgsMM77sFNC6GvGjz1P
Copilot AI review requested due to automatic review settings July 26, 2026 03:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Owner Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

@cblecker: Starting a review of PR #83.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants