Skip to content
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ inputs:
description: "Override reasoning effort for review flows (passed to Droid Exec as --reasoning-effort). If empty and review_model is also empty, the action defaults internally to gpt-5.2 at high reasoning."
required: false
default: "high"
validator_model:
description: "Override the model used for the validator pass (phase 2) of code review. If empty, falls back to review_model. Only applies when review_use_validator is true."
required: false
default: ""
review_use_validator:
description: "Enable two-pass review: generate candidate comments to JSON, then validate and post only approved ones."
required: false
Expand Down Expand Up @@ -347,6 +351,7 @@ runs:
REVIEW_CANDIDATES_PATH: ${{ inputs.review_candidates_path }}
DROID_COMMENT_ID: ${{ steps.prepare.outputs.droid_comment_id }}
REVIEW_MODEL: ${{ inputs.review_model }}
VALIDATOR_MODEL: ${{ inputs.validator_model }}
REASONING_EFFORT: ${{ inputs.reasoning_effort }}

- name: Run Droid Exec (validator)
Expand Down
14 changes: 13 additions & 1 deletion src/create-prompt/templates/review-candidates-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,21 @@ Write output to \`${reviewCandidatesPath}\` using this exact schema:
- **comments**: Array of comment objects
- \`path\`: Relative file path (e.g., "src/index.ts")
- \`body\`: Comment text starting with priority tag [P0|P1|P2], then title, then 1 paragraph explanation
If you have **high confidence** a fix will address the issue and won’t break CI, append a GitHub suggestion block:

\`\`\`suggestion
<replacement code>
\`\`\`

**Suggestion rules:**
- Keep suggestion blocks ≤ 100 lines
- Preserve exact leading whitespace
- Use RIGHT-side anchors only; do not include removed/LEFT-side lines
- For insert-only suggestions, repeat the anchor line unchanged, then append new lines
- \`line\`: Target line number (single-line) or end line number (multi-line). Must be ≥ 0.
- \`startLine\`: \`null\` for single-line comments, or start line number for multi-line comments
- \`side\`: "RIGHT" for new/modified code (default), "LEFT" only for removed code
- \`side\`: "RIGHT" for new/modified code (default). Use "LEFT" only for removed code **without** suggestions.
If you include a suggestion block, choose a RIGHT-side anchor and keep it unchanged so the validator can reuse it.
- \`commit_id\`: "${prHeadSha}"

- **reviewSummary**:
Expand Down
17 changes: 17 additions & 0 deletions src/create-prompt/templates/review-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,22 @@ One short paragraph explaining *why* this is a bug and *how* it manifests.
* Code snippets ≤3 lines, Markdown fenced
* Matter-of-fact, non-accusatory tone

### Suggestion blocks (when applicable)

If you have **high confidence** a fix will address the issue and won’t break CI, include a GitHub suggestion block after the explanation:

\`\`\`suggestion
<replacement code>
\`\`\`

Rules:
* Only include a suggestion when the fix is clear and high-confidence
* Keep the suggestion minimal and scoped to the reported line range
* Do not exceed 100 lines in a suggestion block
* Preserve exact leading whitespace of replaced lines
* Use RIGHT-side anchors only; do not include removed/LEFT-side lines
* For insert-only suggestions, repeat the anchor line unchanged, then append new lines

---

## Phase 3: Submit Review
Expand All @@ -333,6 +349,7 @@ One short paragraph explaining *why* this is a bug and *how* it manifests.
* Anchor using **path + side + line**
* RIGHT = new/modified code, LEFT = removed code
* Line numbers must correspond to the chosen side
* If you include a suggestion, keep the anchor on RIGHT-side lines; for insert-only suggestions, repeat the anchor line unchanged, then append new lines
* Use \`github_pr___submit_review\` for the summary
* Use \`github_pr___delete_comment\` or \`github_pr___minimize_comment\` for outdated "no issues" comments
* Use \`github_pr___reply_to_comment\` to acknowledge resolved issues
Expand Down
14 changes: 14 additions & 0 deletions src/create-prompt/templates/review-validator-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ Reject if:
* It's stylistic / naming / formatting
* It's not anchored to a valid changed line
* It's already reported (dedupe against existing comments)
* The anchor (path/side/line/startLine) would need to change to make the suggestion work — reject instead

### Deduplication (STRICT)

Before approving a candidate, check for duplicates:
1. **Among candidates**: If two or more candidates describe the same underlying bug (same root cause, even if anchored to different lines or worded differently), approve only the ONE with the best anchor and clearest explanation. Reject the rest with reason "duplicate of candidate N".
2. **Against existing comments**: If a candidate repeats an issue already covered by an existing PR comment (from \`${commentsPath}\`), reject it with reason "already reported in existing comments".
3. Same file + overlapping line range + same issue = duplicate, even if the body text differs.

Suggestion block rules (minimal):
* Preserve exact leading whitespace and keep blocks ≤ 100 lines
* Use RIGHT-side anchors only; do not include removed/LEFT-side lines
* For insert-only suggestions, repeat the anchor line unchanged, then append new lines
* Do not change the anchor fields (path/side/line/startLine) from the candidate — only edit the body

When rejecting, write a concise reason.

Expand Down
6 changes: 4 additions & 2 deletions src/tag/commands/review-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@ export async function prepareReviewValidatorMode({
droidArgParts.push(`--enabled-tools "${allowedTools.join(",")}"`);
droidArgParts.push('--tag "code-review"');

const validatorModel = process.env.VALIDATOR_MODEL?.trim();
const reviewModel = process.env.REVIEW_MODEL?.trim();
const modelToUse = validatorModel || reviewModel;
const reasoningEffort = process.env.REASONING_EFFORT?.trim();

if (reviewModel) {
droidArgParts.push(`--model "${reviewModel}"`);
if (modelToUse) {
droidArgParts.push(`--model "${modelToUse}"`);
}
if (reasoningEffort) {
droidArgParts.push(`--reasoning-effort "${reasoningEffort}"`);
Expand Down