feat(review): exact scope and parallel skills for reviewer agents - #1647
feat(review): exact scope and parallel skills for reviewer agents#1647peyton-alt wants to merge 27 commits into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit ac36d17. Configure here.
There was a problem hiding this comment.
Pull request overview
This PR improves entire review orchestration by passing parent-computed review scope into spawned reviewer agents and the consolidating judge, adding explicit per-reviewer pacing guidance from the effective timeout, and pre-approving a constrained tool allowlist for headless Claude review children so read-only inspection commands aren’t auto-denied.
Changes:
- Add
ScopeContexttoRunConfig, build it from git (commits/files/uncommitted + optionally inline diff), and inject it into reviewer prompts. - Thread authoritative scope into the judge/synthesis prompt so it can discard out-of-scope findings.
- Add reviewer time-budget hint rendering, and pass per-agent timeouts in the multi-agent path.
- Add
--allowedToolsallowlist to Claude review child invocation.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/review/types/reviewer.go | Introduces ScopeContext on RunConfig and defines scope payload shape/zero check. |
| cmd/entire/cli/review/synthesis_sink.go | Threads authoritative scope into synthesis prompt composition. |
| cmd/entire/cli/review/synthesis_prompt.go | Adds “scope gate” section to judge prompt to discard out-of-scope findings. |
| cmd/entire/cli/review/synthesis_prompt_test.go | Tests that synthesis prompt includes (or omits) the scope gate correctly. |
| cmd/entire/cli/review/scope.go | Builds bounded scope context (commits/files/uncommitted + inline diff budget). |
| cmd/entire/cli/review/scope_test.go | Adds coverage for scope context building, three-dot semantics, and caps. |
| cmd/entire/cli/review/prompt.go | Renders scope context + time budget into reviewer prompt; optionally inlines diff. |
| cmd/entire/cli/review/prompt_test.go | Tests scope injection and time budget hint behavior. |
| cmd/entire/cli/review/export_test.go | Updates/export helpers for new synthesis prompt signature and scoped testing. |
| cmd/entire/cli/review/cmd.go | Injects scope context into single/multi-agent configs; threads scope to sinks; sets per-agent timeout. |
| cmd/entire/cli/review/cmd_test.go | Extends tests to validate per-agent timeout threading and scope context injection. |
| cmd/entire/cli/agent/claudecode/reviewer.go | Adds --allowedTools allowlist to headless Claude review child. |
| cmd/entire/cli/agent/claudecode/reviewer_test.go | Tests that allowlist is present and does not enable unsafe flags. |
…ge prompts The parent computes the review scope (commits, files, uncommitted, diff) for its banner, then discards it and tells each child agent only how to re-derive it. That cost minutes of setup per reviewer and let one agent diff a behind-main branch in the wrong direction, reporting mainline evolution as branch regressions — two of four high findings in a real run were such phantoms, and the judge consolidated them unchecked. ComposeReviewPrompt now renders a parent-computed ScopeContext: commit list (oldest first), three-dot name-status file list, porcelain uncommitted lines, and the diff itself when it fits a 48KiB inline budget (beyond that, the exact three-dot command). The judge prompt gets the authoritative changed-file list with an instruction to discard findings outside it. Lists are capped with truncation flags so agents never mistake a bounded enumeration for an exhaustive one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: ca5fa66e3b98
cb8a0dc to
130abe3
Compare
|
Relationship to #1312's task design (for reviewers wondering whether the no-injected-task commits undo something intentional): The default-task machinery (
Default task texts remain in the code as runtime fallbacks (and |
1faf416 to
5507991
Compare
|
Update to the #1312-relationship table above: after review discussion, the fourth row is now also kept — the branch no longer changes which workers receive the canonical task. Worker briefing is exactly main's behavior (every worker gets the user task or the built-in default; the A/B without the brief was ~35% faster but missed a real finding, so depth stays). The only task-related change in this PR is that setup persists an empty task and the built-in brief applies at runtime only — saved profiles stay clean and pick up improved default wording. Pinned by |
Headless claude -p auto-denies any tool call that would have prompted interactively, and buildReviewCmd passed no permission config — so finder subagents bounced off denials for git diff/log/show and detoured around them (observed in a live review transcript: 'attempted tool calls, hit approval/tool restrictions, and continued'). Pass --allowedTools with the read-only surface a review needs: Read/ Grep/Glob/Task/TodoWrite, enumerated read-only git subcommands (not Bash(git:*) — aliases/hooks execute arbitrary code and push/commit match the blanket), and entire's read-only lookup commands referenced by the checkpoint context. Never --dangerously-skip-permissions: reviewers process untrusted diffs and must stay unable to write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: f0c2e8deec13
…h command The composed prompt leads with the configured skill invocation line (e.g. /pr-review-toolkit:review-pr). claude -p expands a leading slash token as a command — observed live in two runs: it resolved to the BUILT-IN /review skill with the entire composed prompt as its arguments, interpolating the configured skill name where a PR number belongs. The configured skill never ran; the built-in maximum-breadth PR-review harness ran instead (its diff-proportional line-by-line finder alone took 11.4 of the run's 15 minutes on a 102KB scope). Prefix a prose preamble whenever the composed prompt starts with "/", instructing the agent to invoke the skill lines via the Skill tool — expansion is impossible once the first byte isn't a slash. Add Skill to the reviewer allowlist so headless invocation is pre-approved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 5ffb689d299f
Both setup paths (guided picker fallback and non-interactive first-run) wrote the built-in default brief into the saved profile's task field, making it permanently indistinguishable from a task the user wrote — which defeated the skill-workers-get-no-injected-task rule for every profile created by setup. Persist empty; the built-in brief stays a runtime fallback for skill-less workers and the judge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 53e0eeb2e399
Four review findings and one lint failure, all confirmed real: - Commit cap kept the OLDEST commits (first lines of --reverse output) and silently dropped everything near HEAD, while the prompt forbids re-deriving scope. Cap newest-first, then reverse the kept slice for reading order — recent commits are the ones a review can least afford to lose. (Cursor Bugbot, high) - Inline diff budget 48KiB could exceed Windows' ~32KiB CreateProcess command-line cap (prompt travels as argv and ENTIRE_REVIEW_PROMPT). Lowered to 24KiB. (Copilot) - A diff containing ``` (any markdown-touching change) closed the diff fence early, letting diff content escape into instruction position. Fence is now one backtick longer than the longest run in the diff. (Copilot) - Bash(git branch:*) allowed mutating -d/-D/-m; removed from the read-only allowlist. (Copilot) - guidedProfileTask's profileName parameter became unused when the built-in fallback was removed; parameter dropped. (revive) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 320d86619050
…ted-task fix Ran the fixed `entire review` against this branch itself; its verdict identified that fd7d1cb was incomplete and five smaller defects. All confirmed against the code: - buildCrewProfile, saveReviewProfileConfig, and the flags-configure path still seeded the built-in task into saved profiles, so every interactively-configured profile reintroduced the maximal-audit brief for skill-bearing workers. The prior test was tautological (inputs production never produces); replaced with a test through the real constructor, plus an inverted stale assertion in configure_test. - Scope-context failure degraded at Debug (invisible at default level) right after the banner claimed the scope was computed. Now Warn plus a one-line stderr notice. - The categorical "discard out-of-scope findings" rule contradicted truncated file lists (files past the cap are in scope) and rendered nonsense for net-zero diffs (commit + revert). The rule now matches what was rendered: absent without lists, softened to verify-first when truncated. - Rendered list bytes are charged against the inline-diff budget so the composed prompt stays bounded on ~32KiB-limited platforms. - Allowlist comment now names the accepted --output residual instead of claiming blanket write-safety. - Test gaps: fan-out loop now asserts per-worker ScopeContext/Task wiring; uncommitted-only scope pins Diff==""/!DiffOmitted; redundant Skill allowlist test folded into the main one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: d4ef50cdaee1
…rker, never persisted Coverage note after review discussion: the branch no longer changes which workers receive the canonical task (main's #1312 behavior stands: every worker gets the user task or the built-in default). What it does change is persistence — setup writes an empty task and the built-in brief is applied at runtime only. Pin both halves so neither regresses: worker briefing (skill-bearing, skill-less, user-task-verbatim) and the runtime-only nature of the default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: a457400d7876
18f6e77 to
c1e199c
Compare
…runs A worker configured with N skills previously joined them into one child's prompt: skills executed sequentially (or blended), so selecting more skills made the user wait for the SUM of their durations. Measured live: a two-skill claude worker ran ~9 minutes as one child. explodeSkillWorkers splits each multi-skill worker into one worker per skill at plan time (keys like claude-code:review, deduped against existing workers), so skills run concurrently as ordinary slots — the wait becomes the slowest skill. Two exploded workers also mean the judge consolidates per-skill reports, extending the crew+judge value prop to single-agent multi-skill profiles. --agent now selects ALL of that agent's workers as a filtered crew (previously an ambiguity error), running the single-agent path only when exactly one worker matches. Same-agent SAME-model exploded workers defeat the existing agent+model session matching (assignments could cross, attributing tokens and transcripts to the wrong skill). AgentRun gains Skills, propagated through planned runs and both run paths, and the matcher requires skill-set agreement when both sides carry skills — mirroring the same-agent different-model disambiguation from #1313. Verified end-to-end with a claude shim: a two-skill profile spawns two one-skill children in parallel (~3s wall for both) plus the judge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 116f0cd95d56
…(trail findings) Two trail findings on the scope-injection design: - Commit subjects and filenames are attacker-controlled on a branch under review, yet they rendered unfenced inside the section framed as authoritative instructions — the exact injection surface the diff's dynamic fence already guards one paragraph below. The enumerations now ship inside their own dynamic fence (same longest-backtick-run sizing), introduced as untrusted data with an explicit do-not-act marker; entire's own instructions (authoritative-scope framing, discard rule) stay outside the fence. - The list sections were capped by line count only (50/200/100), so a wide branch with long paths could push the rendered lists past the ~32KiB platform argv cap even with the diff omitted. Lists are now trimmed to half the inline budget (charged commits -> files -> uncommitted, keeping leading lines; commits trimmed newest-first before the oldest-first reversal so the newest survive), and the remainder is charged against the diff allowance as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 23a2d752af4a
…text # Conflicts: # cmd/entire/cli/review/run.go
… allowlist, fanout fallback A full-crew dogfood review of this branch (request changes, 4 mediums + 1 low, all verified) on the integrated stack: - capScopeListsToBudget's 'defer slices.Reverse(sc.Commits)' captured the pre-trim slice header, so byte-trimmed commit lists reversed the FULL backing array and the trimmed view held the OLDEST commits — inverting keep-newest exactly when truncation matters. Now a closure re-reading sc.Commits at return; pinned by a test that reproduces the inversion. - writeSynthesisScopeGate wrote branch-controlled file paths raw into the judge prompt ahead of the discard rule — the same injection class fixed for worker prompts, but feeding the FINAL verdict gate. The list now renders inside a dynamic fence introduced as untrusted data, instructions outside. - The allowlist granted only 'entire checkpoint explain' while the injected checkpoint context tells reviewers to run 'entire explain <id>' — headless -p denied the prompt's own guidance. Added Bash(entire explain:*). - Unconditional explodeSkillWorkers regressed single multi-skill agents without a review-runner adapter (previously served by RunMarkerFallback; explosion forced them into the multi-agent branch which hard-fails, with a circular --agent workaround). Explosion now skips adapter-less agents. - picker.go task-persistence comment referenced a nonexistent workerTask function; aligned with the actual runtime contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KX14MDBGM3BNQK1JKDH030FY
…ail finding)
The absolute discard order ('findings that point at files not listed
are out of scope') dropped a legitimate finding class: an in-scope
change that breaks an UNCHANGED caller or consumer in an unlisted file
— a cross-file regression, exactly what reviews exist to surface — got
discarded because the finding anchors at the broken call site. Both the
worker prompt's rule and the judge's scope gate now gate on where the
CAUSE lives and explicitly keep in-scope-cause/out-of-scope-impact
findings, anchored to the in-scope change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 01KX18CRN7KXZZ55BS5R6WCMQA
…text # Conflicts: # cmd/entire/cli/review/cmd_test.go
The fan-out was meant to be behavior-only (run an agent's skills as parallel children instead of one blended pass), but it leaked into the TUI: because rows are drawn one-per-worker, a two-skill agent showed as two rows (claude-code:review + claude-code:pr-review). Restore the pre-fan-out display — one row per agent — without changing the parallel execution. The collapse lives entirely in the TUI sink; execution, the summary, the manifest, and skills-based session matching stay per-worker (all load-bearing for attribution). TUISink.groupWorkers takes the per-agent row order plus a worker->agent map; AgentEvent routes each worker's events to its agent row (live events already key by name, so this is a lookup), and RunFinished folds the per-worker summary into one entry per agent row (worst-status wins, tokens summed) so the model's by-index row sync still aligns. Grouping key is agent+model, so genuine agent+model workers stay separate rows — only the skill dimension collapses. Verified: sink unit tests pin routing + collapse; a live shim run showed a single claude-code row with two parallel children in flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KXH1KM896NXV3ZV3XVJ8FEXK
Follow-on to the one-row-per-agent collapse: #1666 (merged) streams cumulative token counts per worker, and the TUI row overwrites on each Tokens event (CU2). With two skill-workers folded into one agent row, their independent cumulative counts overwrote each other — the live number bounced between skills instead of showing the agent total. (The final summary was already summed; only the live display was wrong.) The sink now tracks each worker's latest cumulative Tokens and forwards the per-agent SUM on every Tokens event, so the row's overwrite shows a correct running total. Single-worker rows pass through unchanged. Runs on the serial dispatch goroutine, so no locking; race-clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KXH2J8TTE8GJYBQE47W4BHKX
…olish Pre-merge cleanup of the agent-row collapse, which had accreted across three commits as scattered TUISink fields/methods. Same behavior, one owner, plus the two facets found by reading rather than design. (c) New agentGrouping type owns the whole reconciliation between per-worker execution and one-row-per-agent display: worker->row routing, live per-agent token summing, and the summary fold. TUISink holds one *agentGrouping (nil on the single-agent path) instead of four fields and four methods. (a) The folded summary's duration now spans earliest-worker-start to latest-worker-end instead of keeping one worker's slice. The live row duration was already correct (wall-clock from first event to finish); this makes the folded field honest for any future consumer. (b) Drill-in (Ctrl+O) into a collapsed agent tags each event with its skill when the buffer holds more than one worker, so two skills running in parallel read cleanly instead of interleaving anonymously; single-source buffers stay untagged. agentEventMsg carries the source worker; the row buffer keeps it per event. Execution, summary, manifest, and per-skill session attribution are untouched — the collapse remains display-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KXH6FFA9B5JWVHSRGMH1W20B
When codex fails, it wraps the upstream API error as a JSON string in
its envelope message, so a failed reviewer row showed the whole blob
(e.g. `codex: {"type":"error","status":400,"error":{"message":
"The 'gpt-5.6-sol' model requires a newer version of Codex..."}}`) —
the actual reason buried inside. cleanCodexFailureMessage unwraps it to
the `.error.message` text so the row reads "codex: The 'gpt-5.6-sol'
model requires a newer version of Codex...". Plain (non-JSON) messages
and JSON without a message pass through unchanged; unwrapping is bounded
against multiple nesting. Found while diagnosing a live codex failure
(model/CLI-version mismatch) that the raw blob made hard to read.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 01KXH8Z4S6VJF3PFYBW4V44WEN
…text # Conflicts: # cmd/entire/cli/review/cmd.go # cmd/entire/cli/review/cmd_test.go
… native invocation The user asked whether configured codex $code-reviewer skills are retained after #1749's legacy-config repair merged in. They are, by two independent guards; these tests lock that so a future migration edit can't clobber real skills: - normalizeLegacyCodexDefaultSkills only rewrites codex's exact ["/review"] artifact; a $code-reviewer config and multi-skill configs pass through untouched. - codexNativeSkillInvocations rewrites only slash-form to $; $name and plain text pass through verbatim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KXHANJCZ4G2GCS548V1CKZSS
…inish (trail finding) A Finished/RunError event stamped row.runEnd/status on the first worker to finish, so a collapsed agent row (skill fan-out: N workers per row) showed a frozen duration and a terminal status while sibling skills were still running. groupWorkers now tells the model each row's worker count via rowWorkerCountsMsg, and the row only turns terminal once every distinct worker has emitted a terminal event — worst-status-wins across workers, mirroring collapseSummary. The runFinishedMsg summary sync remains the backstop for dropped events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
784245e to
68bb386
Compare
…r consolidation docs/architecture/review-command.md now covers the branch's three undocumented subsystems: parent-computed scope enumeration (BuildScopeContext, byte budgets, data fencing), the claude reviewer child's read-only tool allowlist + guardSlashExpansion, and skill fan-out with the one-row-per-agent collapse and judge scope fencing. selectProfileWorker is now a thin single-match wrapper over selectProfileWorkers instead of a parallel implementation, and stringSetsEqual uses sorted-clone comparison instead of a bespoke count map. Net −20 lines of code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Flow step understated reviewToolAllowlist (omitted Task/TodoWrite/Skill) — for a security-relevant enumeration, an incomplete list reads as the complete surface. Also 'no-write' not 'read-only': Task and TodoWrite are not reads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s (trail finding) Row grouping keyed purely on agent+model, so two independently configured duplicate slots (claude-code / claude-code-2, same model) merged into one dashboard row — blending live tokens, statuses, and summary entries of unrelated reviewers. explodeSkillWorkers now reports which source worker each exploded key came from, and planAgentRows collapses a row only for genuine fan-out siblings; every other worker keeps its own row, with label collisions falling back to the unique worker key. Also reframes the runGit comment to state its real job (centralizing the wrapcheck exemption for gitexec's pre-wrapped errors) rather than churn-avoidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

https://entire.io/gh/entireio/cli/trails/766
One-sentence summary
When
entire reviewlaunches a reviewer, it now hands that reviewer everything the CLI already knows — exactly which code to review, how long it may run, which tools it may use, and how to start its configured skill — instead of a vague instruction each reviewer had to interpret for itself.Problems, as observed in real runs
claude -pauto-denies any tool call that isn't pre-approved, and the review command passed no permission config. Reviewers bounced off denials forgit diff/git log, then either worked around them slowly or skipped verifying their findings./pr-review-toolkit:review-pr).claude -ptreats a leading slash as a slash command and expanded it — resolving to the built-in/reviewskill with the entire composed prompt stuffed in as its argument (it interpolated the skill name where a GitHub PR number belongs). Every claude review was silently running the wrong workflow.review_profiles.<name>.taskas if you had typed it — indistinguishable from user config, and frozen: improving the default wording could never reach existing profiles.Changes (numbered to match the problems)
git diff base...HEADcommand to run). The judge additionally receives the changed-file list with an instruction to discard findings that point outside it — so even a reviewer that somehow mis-scopes can't pollute the final verdict.--allowedTools: Read/Grep/Glob/Task/Skill plus enumerated read-only git subcommands (diff,log,show,status,blame,rev-parse,merge-base,ls-files). Deliberately notBash(git:*)— git aliases/hooks execute arbitrary code andpush/commitwould match the blanket — and never--dangerously-skip-permissions: reviewers process untrusted diffs and must stay unable to modify the repo./, a prose preamble is prepended — "Lines starting with «/» name configured review skills: invoke each with the Skill tool and follow it" — making slash-expansion structurally impossible. Verified in live transcripts: realSkilltool invocations of the configured skill, followed by that skill's actual workflow.TestRunReview_TaskSemantics.What a reviewer receives now (abridged real capture)
(The checkpoint/session sections come from entire's own history for exactly the commits and working tree under review — the intent behind each commit and what any still-uncommitted agent session was doing. They're empty in fresh repos, which is why the raw capture above was taken with them elided; predates this PR, included here for completeness of what a reviewer receives.)
Plus env (
ENTIRE_REVIEW_*session-tagging handshake, unchanged) and the--allowedToolsflag from change 2.Deadline handling is deliberately NOT part of this PR — it landed as #1664: the default reviewer timeout is dropped entirely (reviewers run until done,
--timeoutstays as an explicit cap). An activity-based stall detector was investigated and ruled out — legitimate review subagents run 12+ minutes with zero parent output, so no liveness signal distinguishes quiet work from a hang.Measured (same branch: 13 commits, 37 files, ~102KB diff; real claude runs)
Testing
mise run test,test:integration(390),test:e2e:canary— greenclaudeon PATH captures argv/env through a realentire reviewrun; asserts prompt contents, allowlist, and flag shape end-to-end🤖 Generated with Claude Code
Also folded in: skill fan-out (was #1651)
A worker configured with N skills joined them into one child's prompt: the skills ran sequentially (or blended into one muddled pass), so selecting more skills grew the wait linearly. Measured live: a two-skill claude worker took ~9 minutes as a single child. The parallel machinery already existed one level up (worker slots fan out in
run_multi); multi-skill selection just never used it.explodeSkillWorkers: at plan time, each multi-skill worker becomes one worker per skill (keys likeclaude-code:review, deduped against existing worker names; agent/model/prompt preserved). Wait becomes max(skills) instead of sum(skills). Two exploded workers also engage the judge, extending crew+judge consolidation to single-agent multi-skill profiles.--agentselects a filtered crew: all of that agent's workers run (previously: ambiguity error). Single-agent path only when exactly one worker matches.AgentRungainsSkills, and the matcher requires skill-set agreement when both sides carry skills (recorded fromENTIRE_REVIEW_SKILLSby the lifecycle hook). Adversarially-ordered test fails without the discriminator.Was stacked as #1651 (its diff was exactly the last commit here); combined into one PR for a single review/approval/merge.