Skip to content

feat: --explain mode - #28

Merged
brucearctor merged 2 commits into
mainfrom
feat/explain-mode
Jul 20, 2026
Merged

feat: --explain mode#28
brucearctor merged 2 commits into
mainfrom
feat/explain-mode

Conversation

@brucearctor

@brucearctor brucearctor commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

New --explain flag that asks the model to explain what a diff does in plain Markdown — instead of reviewing or summarizing it.

Use cases: onboarding, code handoffs, understanding unfamiliar changes, PR walkthroughs.

Usage

# Local — explain your uncommitted changes
code-reviewer --diff --explain

# CI — post explanation as MR comment
code-reviewer --ci --explain

# With a specific ref
code-reviewer --diff --explain HEAD~3

Architecture

File What
model/explain.go Free-form explain prompt (Markdown output, no JSON)
model/explain_provider.go ExplainProvider interface wrapping generateRaw()
reviewer/explain.go RunExplain() pipeline: diff → prompt → render
config.go --explain flag, mutually exclusive with --summarize/--intent
main.go Dispatch to RunExplain

Output

Terminal:

🔍 Explanation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This diff adds a JWT validation middleware to the auth package...

CI/GitLab: Posted as MR comment with ## 🔍 Diff Explanation header.

Validation

  • Mutually exclusive with --summarize and --intent
  • Graceful error when provider doesn't implement ExplainProvider
  • 7 new tests (prompt, pipeline, formatters, unsupported provider)
  • go build ./... passes
  • go test ./... passes

Summary by CodeRabbit

  • New Features
    • Added an --explain mode that generates a plain-language explanation of code changes.
    • Include merge request context (title/description when present) and the relevant diff.
    • Render output in the terminal for local runs, or post as a merge request note in CI, with terminal control characters sanitized.
  • Bug Fixes
    • Prevented incompatible combinations of --explain with summarization and with intent review options.
  • Tests
    • Added coverage for explain generation, prompt/adversarial warning content, formatting (terminal/markdown), provider support, and output sanitization.

Add --explain flag that asks the model to explain what a diff does
in plain Markdown instead of reviewing it. Useful for onboarding,
code handoffs, and understanding unfamiliar changes.

Architecture:
- model/explain.go: Free-form explain prompt (no JSON parsing)
- model/explain_provider.go: ExplainProvider interface wrapping generateRaw
- reviewer/explain.go: RunExplain pipeline (diff → prompt → render)
- config.go: --explain flag, mutually exclusive with --summarize/--intent
- main.go: dispatch to RunExplain

Output:
- Terminal: 🔍 header + markdown explanation
- CI/GitLab: posted as MR comment with ## header

7 new tests across explain_test.go and explain_provider tests.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an --explain execution mode that builds prompts from merge request metadata and numbered diffs, invokes supported model providers, and outputs explanations locally or posts them as GitLab notes.

Changes

Explain mode

Layer / File(s) Summary
Mode configuration and model contracts
internal/config/config.go, internal/model/explain.go, internal/model/explain_provider.go, internal/model/explain_test.go
Adds the Explain configuration flag, validates incompatible modes, defines explain prompts, exposes provider methods backed by generateRaw, and tests prompt construction and adversarial-data warnings.
Reviewer explain pipeline
internal/reviewer/explain.go, internal/reviewer/explain_test.go
Fetches and filters diffs, invokes ExplainProvider, sanitizes and formats explanations for terminal or GitLab output, and tests supported and unsupported providers plus control-character removal.
CLI explain dispatch
cmd/code-reviewer/main.go
Routes execution to RunExplain before summarize or normal review handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Reviewer
  participant DiffSource
  participant Model
  participant GitLab
  CLI->>Reviewer: RunExplain(ctx)
  Reviewer->>DiffSource: fetch and filter diffs
  Reviewer->>Model: Explain(systemPrompt, userPrompt)
  Model-->>Reviewer: explanation and token usage
  Reviewer->>GitLab: post Markdown note when running in CI
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the new --explain mode.
Description check ✅ Passed The description covers what, how, output, and validation, but it omits the template's explicit Why section and checklist-style testing items.
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 feat/explain-mode

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 `@internal/model/explain.go`:
- Around line 32-36: Update the prompt-building logic in
internal/model/explain.go to treat mrTitle, mrDescription, and all diff content
as untrusted data. Extend the guardrail beyond code constructs so none of these
inputs can provide instructions or steer the generated GitLab comment;
explicitly state that they are informational content only. Apply the same
protection to the related logic covering the additional referenced section.

In `@internal/reviewer/explain.go`:
- Line 69: Sanitize the untrusted explanation in the rendering path before the
fmt.Print call, using the existing formatExplainTerminal flow as the integration
point. Remove terminal control sequences while preserving ordinary newlines and
tabs, then print the sanitized result.
🪄 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: f9955dac-2c5b-4e57-b726-ac76f486837f

📥 Commits

Reviewing files that changed from the base of the PR and between deaddae and 5874974.

📒 Files selected for processing (7)
  • cmd/code-reviewer/main.go
  • internal/config/config.go
  • internal/model/explain.go
  • internal/model/explain_provider.go
  • internal/model/explain_test.go
  • internal/reviewer/explain.go
  • internal/reviewer/explain_test.go

Comment thread internal/model/explain.go Outdated
Comment thread internal/reviewer/explain.go
1. Prompt injection: Expanded adversarial content warning to explicitly
   cover MR title, MR description, and diff as untrusted data — not just
   code constructs. Prevents malicious MR metadata from steering output.

2. Terminal injection: Added stripControlChars() that removes OSC/CSI
   escape sequences from model output before terminal rendering, while
   preserving newlines and tabs. Prevents injected sequences from
   manipulating terminal (clear screen, change title, etc).

5 new tests:
- Adversarial warning coverage (title, desc, diff, 'untrusted data')
- stripControlChars preserves normal text
- stripControlChars removes ESC/CSI sequences
- stripControlChars removes OSC sequences
- formatExplainTerminal sanitizes embedded control chars
@brucearctor
brucearctor merged commit 19be4df into main Jul 20, 2026
3 of 4 checks passed

@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 `@internal/model/explain_test.go`:
- Around line 20-31: Strengthen the adversarial warning assertions in the test
around result validation so they verify that “MR title,” “MR description,” and
“diff” are collectively identified as “untrusted data.” Replace the independent
strings.Contains checks with an exact complete-clause assertion or a regex that
binds each input to the untrusted-data label.

In `@internal/reviewer/explain_test.go`:
- Around line 144-163: Strengthen TestStripControlChars_RemovesOSC and
TestFormatExplainTerminal_SanitizesControlChars by asserting that text following
the OSC and CSI sequences remains in the sanitized output. Verify “normal” and
“rest” are preserved for stripControlChars, and “but this clears screen” is
preserved for formatExplainTerminal, while retaining the existing
control-character checks.
🪄 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: 9b2234ec-fd0b-4ea8-9ecb-b6fdb02bce1b

📥 Commits

Reviewing files that changed from the base of the PR and between 5874974 and aaf3860.

📒 Files selected for processing (4)
  • internal/model/explain.go
  • internal/model/explain_test.go
  • internal/reviewer/explain.go
  • internal/reviewer/explain_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/model/explain.go
  • internal/reviewer/explain.go

Comment on lines +20 to +31
// Must explicitly call out ALL untrusted inputs.
if !strings.Contains(result, "MR title") {
t.Error("adversarial warning must mention MR title as untrusted")
}
if !strings.Contains(result, "MR description") {
t.Error("adversarial warning must mention MR description as untrusted")
}
if !strings.Contains(result, "diff") {
t.Error("adversarial warning must mention diff as untrusted")
}
if !strings.Contains(result, "untrusted data") {
t.Error("adversarial warning must label inputs as untrusted data")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert the adversarial warning semantically.

Independent strings.Contains checks can pass even if “MR title”, “MR description”, and “diff” are no longer labeled as untrusted together. Assert the complete warning clause, or use a regex that binds each input to “untrusted data”.

As per coding guidelines, adversarial-content sections in prompts are security-critical and require meaningful validation.

🤖 Prompt for 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.

In `@internal/model/explain_test.go` around lines 20 - 31, Strengthen the
adversarial warning assertions in the test around result validation so they
verify that “MR title,” “MR description,” and “diff” are collectively identified
as “untrusted data.” Replace the independent strings.Contains checks with an
exact complete-clause assertion or a regex that binds each input to the
untrusted-data label.

Source: Coding guidelines

Comment on lines +144 to +163
func TestStripControlChars_RemovesOSC(t *testing.T) {
// OSC (Operating System Command) can change terminal title.
input := "normal\x1b]0;malicious title\x07rest"
result := stripControlChars(input)
if strings.Contains(result, "\x1b") || strings.Contains(result, "\x07") {
t.Errorf("expected control chars removed, got %q", result)
}
}

func TestFormatExplainTerminal_SanitizesControlChars(t *testing.T) {
// Model output with embedded CSI sequence.
explanation := "This is safe\x1b[2J\x1b[H but this clears screen"
result := formatExplainTerminal(explanation, false)
if strings.Contains(result, "\x1b") {
t.Error("expected control sequences stripped from terminal output")
}
if !strings.Contains(result, "This is safe") {
t.Error("expected safe content preserved")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify text after control sequences is preserved.

The OSC test only checks that control bytes disappear, and the terminal test only checks the prefix. A sanitizer that truncates output at the OSC/CSI sequence would pass both tests. Also assert that "normal"/"rest" and "but this clears screen" remain.

🤖 Prompt for 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.

In `@internal/reviewer/explain_test.go` around lines 144 - 163, Strengthen
TestStripControlChars_RemovesOSC and
TestFormatExplainTerminal_SanitizesControlChars by asserting that text following
the OSC and CSI sequences remains in the sanitized output. Verify “normal” and
“rest” are preserved for stripControlChars, and “but this clears screen” is
preserved for formatExplainTerminal, while retaining the existing
control-character checks.

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.

1 participant