Skip to content

Feature/tokenless diff provider#2467

Open
vectorkovacspeter wants to merge 15 commits into
The-PR-Agent:mainfrom
vectorkovacspeter:feature/tokenless-diff-provider
Open

Feature/tokenless diff provider#2467
vectorkovacspeter wants to merge 15 commits into
The-PR-Agent:mainfrom
vectorkovacspeter:feature/tokenless-diff-provider

Conversation

@vectorkovacspeter

Copy link
Copy Markdown

feature: tokenless local diff mode (--stdin / --diff-file)

Closes #2457

Summary

Adds a purely local, stateless way to run PR-Agent against a unified diff supplied on
stdin or from a file — no git-platform API token and no PR URL required. The
generated review/improvements/description are printed to stdout (markdown), and can
additionally be written to a file with --output.

# pipe a diff from stdin
git diff main...feature-branch | pr-agent --stdin review

# or pass a diff file and also save the output
git diff main...feature-branch > changes.diff
pr-agent --diff-file changes.diff --output review.md review

Supported commands: review, improve, describe, ask.

Why

Today PR-Agent requires a platform-specific token (GitHub PAT/App, GitLab token, …)
because it talks to the hosting platform's API to fetch PR data and publish comments.
In security-first / restricted environments, teams often manage repositories over SSH
and deliberately avoid issuing HTTP access tokens, since every static token is an extra
credential that can leak.

This feature lets those users get AI code reviews while keeping a strict, token-free
posture:

  • No token attack surface — nothing platform-related is read or required.
  • Air-gapped friendly — works where only the LLM endpoint is reachable and the code
    host's API is blocked.
  • Local workflow integration — easy to wire into pre-commit / pre-push hooks.

How to use

Flag Meaning
--stdin Read a unified diff from standard input
--diff-file <path> Read a unified diff from a file
--output <path> Also write the result to this file (in addition to stdout)

The provider is selected automatically when --stdin or --diff-file is present, and
the usual --pr_url / --issue_url requirement is bypassed.

git diff main...feature | pr-agent --stdin review
git diff main...feature | pr-agent --stdin improve
git diff main...feature | pr-agent --stdin describe
git diff main...feature | pr-agent --stdin ask "What is the riskiest change here?"
pr-agent --diff-file changes.diff --output review.md review

Any LLM supported by PR-Agent works, including a fully local / OpenAI-compatible
endpoint (e.g. Ollama or vLLM) for an end-to-end air-gapped setup.

How it works

  • A new git provider, git_provider = "diff" (DiffGitProvider), parses the unified
    diff with the unidiff library into PR-Agent's FilePatchInfo objects.
  • When run inside the repository working tree, it reads each changed file and
    reverse-applies the diff to reconstruct the original (base) file, so each hunk
    gets full surrounding context (extend_patch) — matching the quality of the normal
    API path rather than only the diff's few context lines.
  • When the working tree isn't available, or has drifted from the diff, it cleanly falls
    back to a patch-only review instead of producing a wrong base file.
  • Output goes to stdout, and to --output <path> when provided.

Design notes / scope

  • This is a new, dedicated provider, intentionally separate from the existing
    branch-based local provider (which requires a clean repo and a target branch). The
    local provider is left untouched.
  • Operations that have no meaning without a hosting platform (inline comments,
    committable code suggestions, labels, reactions) are no-ops / NotImplementedError,
    mirroring LocalGitProvider; is_supported() reports them as unsupported so the
    tools degrade gracefully.
  • Working-tree reads are constrained to the repository root — absolute paths and
    ..-escapes from the diff are rejected — so an untrusted diff cannot make the tool
    read files outside the repo.
  • One new runtime dependency: unidiff (MIT), added to requirements.txt.

Testing

New tests under tests/unittest/:

  • test_diff_parsing.py — parsing (modify/add/delete/rename/binary) and base-file
    reverse-apply (success, drift → patch-only, multi-hunk, CRLF, EOF-newline).
  • test_diff_provider.py — provider behavior, stdout/file output, temporary-comment
    suppression, malformed-diff guard, capability gating, and a path-traversal guard
    (with a sentinel test verified to fail if the guard is removed).
  • test_cli_diff_mode.py — CLI flag parsing.
  • test_diff_mode_e2e.py — base reconstruction from the working tree and a mocked-LLM
    run through PRReviewer.

All diff-feature tests pass. Unrelated pre-existing test-collection errors in some
test_mosaico_* files (optional dependencies not installed) are not affected by this
change.

Documentation

Adds docs/docs/usage-guide/tokenless_diff_mode.md and a navigation entry in
docs/mkdocs.yml, including usage, the working-tree vs. patch-only quality trade-off,
and how this differs from the local provider.

AI assistance disclosure

In the interest of transparency: this contribution was developed with substantial help
from an AI coding assistant — it contains AI-generated and/or AI-assisted code,
tests, and documentation. All of it was reviewed, tested, and validated by me before
submission, and I take full responsibility for the contents of this PR. I'm happy to
adjust anything to fit the project's conventions and review feedback.

…Agent#2457)

- Add publish_file_comments to is_supported() deny-list so describe
  walkthrough is not silently dropped with inline_file_summary=true
- Guard get_diff_files() against path traversal: reject absolute paths
  and relative paths that resolve outside the repo root; log and fall
  back to patch-only mode instead of raising
- Fix tokenless_diff_mode.md comparison table: local provider inline
  comments are Not supported (raises NotImplementedError)
- Promote page title from H2 to H1 to match other usage-guide pages
- Add tests: test_publish_file_comments_not_supported,
  test_path_traversal_file_not_read
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add tokenless unified-diff provider (stdin/file) with stdout output
✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add --stdin / --diff-file diff mode to run PR-Agent without PR URL or platform token.
• Implement DiffGitProvider to parse unified diffs and optionally reconstruct base files from
 working tree.
• Add dependency, tests, and docs for safe tokenless diff workflows (stdout/--output).
Diagram

graph TD
  A["CLI flags (--stdin/--diff-file/--output)"] --> B["Dynaconf settings (diff.content)"] --> C["DiffGitProvider"]
  C --> D["Diff parsing (unidiff)"]
  C --> E[("Working tree files")]
  C --> F["PR tools (review/describe/improve/ask)"] --> G["stdout + optional output file"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Shell out to `git apply --reverse` / `patch` for base reconstruction
  • ➕ Leverages battle-tested patch application semantics (offsets/fuzz/edge cases).
  • ➕ May better handle complex diffs than a custom reverse-apply implementation.
  • ➖ Adds reliance on external binaries/OS differences; harder to keep fully portable.
  • ➖ More security surface area if invoking shell commands on untrusted diffs.
  • ➖ Harder to guarantee consistent behavior across environments/containers.
2. Extend existing `LocalGitProvider` to accept diff input
  • ➕ Avoids introducing a new provider type and reduces duplicated capability gating.
  • ➕ May reuse existing local provider output conventions.
  • ➖ Local provider semantics differ (branch comparison + clean tree requirement); mixing modes risks confusing UX and codepaths.
  • ➖ Still needs separate stdout/file publishing behavior and safety checks.
3. Patch-only diff mode (no working-tree enrichment)
  • ➕ Much simpler implementation; no file reads, no path traversal concerns.
  • ➕ Deterministic output purely from provided artifact.
  • ➖ Lower-quality LLM prompts due to limited context lines in hunks.
  • ➖ Cannot match API/provider quality when full file context is important.

Recommendation: Current approach (dedicated diff provider + optional working-tree enrichment + safe fallback to patch-only) is the best trade-off for tokenless, portable usage. Keep the pure-Python reconstruction for portability, but consider documenting known reconstruction limitations vs. git apply semantics as follow-up if users report diff edge cases.

Files changed (11) +770 / -3

Enhancement (4) +274 / -3
cli.pyAdd CLI diff-mode flags and route execution without PR/issue URL +24/-2

Add CLI diff-mode flags and route execution without PR/issue URL

• Introduces '--stdin', '--diff-file', and '--output' flags; reads unified diff content from stdin or a file and stores it in settings while switching 'config.git_provider' to 'diff'. Updates invocation so a sentinel target ('local_diff') is used when no '--pr_url' is provided.

pr_agent/cli.py

__init__.pyRegister the new 'diff' git provider +3/-1

Register the new 'diff' git provider

• Imports 'DiffGitProvider' and adds it to the '_GIT_PROVIDERS' registry so it can be selected via configuration.

pr_agent/git_providers/init.py

diff_parsing.pyParse unified diffs and reverse-apply patches to reconstruct base files +95/-0

Parse unified diffs and reverse-apply patches to reconstruct base files

• Adds 'parse_unified_diff()' using 'unidiff.PatchSet' to convert diff text into 'FilePatchInfo' objects, classifying added/deleted/renamed/modified files and skipping binaries. Implements 'reconstruct_base_file()' to reverse-apply a single-file patch against head content with drift detection and newline/CRLF handling.

pr_agent/git_providers/diff_parsing.py

diff_provider.pyImplement 'DiffGitProvider' with safe working-tree enrichment and stdout/file output +152/-0

Implement 'DiffGitProvider' with safe working-tree enrichment and stdout/file output

• Adds a tokenless GitProvider implementation that consumes diff content from settings, parses it into diff files, and optionally reads working-tree files to reconstruct base versions. Includes safety guards against absolute paths and '..' traversal outside the repo root, suppresses temporary comments, gates unsupported platform capabilities, and writes results to stdout and optional '--output' file.

pr_agent/git_providers/diff_provider.py

Tests (4) +419 / -0
test_cli_diff_mode.pyAdd CLI parsing tests for diff-mode flags +15/-0

Add CLI parsing tests for diff-mode flags

• Verifies that '--diff-file'/'--output' and '--stdin' are accepted by the CLI parser and populate expected arguments.

tests/unittest/test_cli_diff_mode.py

test_diff_mode_e2e.pyAdd end-to-end tests covering provider output and mocked LLM integration +122/-0

Add end-to-end tests covering provider output and mocked LLM integration

• Covers DiffGitProvider behavior with and without a working tree file and verifies stdout publishing. Adds an async integration test that runs 'PRReviewer' with a fake AI handler to assert prompts include diff-derived content, proving wiring from diff provider to LLM boundary.

tests/unittest/test_diff_mode_e2e.py

test_diff_parsing.pyAdd unit tests for diff parsing and base reconstruction edge cases +155/-0

Add unit tests for diff parsing and base reconstruction edge cases

• Tests modify/add/delete/rename parsing plus base reconstruction success/failure (drift), multi-hunk behavior, and newline handling expectations.

tests/unittest/test_diff_parsing.py

test_diff_provider.pyAdd DiffGitProvider unit tests for registration, safety, and capability gating +127/-0

Add DiffGitProvider unit tests for registration, safety, and capability gating

• Validates provider registration, diff parsing, stdout/file publishing, and empty/malformed diff error handling. Includes a sentinel path-traversal test to ensure the provider will not read files outside the repo root and checks 'publish_file_comments' is reported unsupported.

tests/unittest/test_diff_provider.py

Documentation (2) +76 / -0
tokenless_diff_mode.mdDocument tokenless unified-diff mode usage and behavior +75/-0

Document tokenless unified-diff mode usage and behavior

• Adds a new usage guide page describing '--stdin' / '--diff-file' / '--output', supported commands, and the working-tree enrichment vs patch-only fallback trade-off. Clarifies differences from the existing 'local' provider, including unsupported inline comments.

docs/docs/usage-guide/tokenless_diff_mode.md

mkdocs.ymlAdd navigation entry for tokenless diff mode docs +1/-0

Add navigation entry for tokenless diff mode docs

• Registers the new documentation page in the MkDocs navigation under the usage guide.

docs/mkdocs.yml

Other (1) +1 / -0
requirements.txtAdd 'unidiff' runtime dependency +1/-0

Add 'unidiff' runtime dependency

• Adds 'unidiff==0.7.5' to support parsing unified diffs for the new diff provider.

requirements.txt

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used

Grey Divider


Action required

1. diff_parsing import not isort ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
diff_provider.py wraps the diff_parsing import in a parenthesized/continuation style that is not
what Ruff’s isort check will format for a short two-name import. This can cause Ruff/CI lint
failures on the new provider file.
Code

pr_agent/git_providers/diff_provider.py[R9-10]

+from pr_agent.git_providers.diff_parsing import (parse_unified_diff,
+                                                 reconstruct_base_file)
Evidence
Ruff is configured to enforce isort import formatting (I001). The new diff_provider.py uses a
wrapped/parenthesized import for just two names, which isort would normally rewrite (thus triggering
I001).

AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions
pr_agent/git_providers/diff_provider.py[9-10]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The import from `pr_agent.git_providers.diff_parsing` is formatted in a way that likely fails Ruff's `I001` (isort formatting), creating lint/CI failures.
## Issue Context
Ruff is configured with `I001` in `pyproject.toml`, so import formatting must match isort output.
## Fix Focus Areas
- pr_agent/git_providers/diff_provider.py[9-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unsorted imports in test_diff_mode_e2e ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
test_diff_mode_e2e.py imports are not in isort-sorted order (the pr_agent.algo... import comes
after other pr_agent.* imports). This can fail Ruff’s I001 import-sorting enforcement.
Code

tests/unittest/test_diff_mode_e2e.py[R3-5]

+from pr_agent.config_loader import get_settings
+from pr_agent.git_providers.diff_provider import DiffGitProvider
+from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
Evidence
The compliance checklist requires Ruff/isort conventions. The new test file’s pr_agent.* imports
are not sorted in isort order, which Ruff enforces via I001.

AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions: AGENTS.md: Python Code Must Conform to Ruff Formatting and Import/String Conventions
tests/unittest/test_diff_mode_e2e.py[3-5]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The imports in `tests/unittest/test_diff_mode_e2e.py` are not ordered as isort would produce, which can trigger Ruff `I001` failures.
## Issue Context
Ruff is configured to check import sorting/formatting via `I001`.
## Fix Focus Areas
- tests/unittest/test_diff_mode_e2e.py[3-5]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Diff metadata misparsed as hunks ✓ Resolved 🐞 Bug ≡ Correctness
Description
parse_unified_diff() stores FilePatchInfo.patch as str(pf), and the downstream hunk-to-line-numbers
conversion treats any leading '+'/'-' lines before the first '@@' as real diff content, so diff
metadata lines (e.g., '---'/'+++') can be emitted as a bogus hunk with incorrect line numbering.
This degrades review accuracy (wrong line references / malformed hunks) in tokenless diff mode
because PRReviewer always enables add_line_numbers_to_hunks=True.
Code

pr_agent/git_providers/diff_parsing.py[R43-51]

+        files.append(
+            FilePatchInfo(
+                base_file="",
+                head_file="",
+                patch=str(pf),
+                filename=filename,
+                edit_type=edit_type,
+                old_filename=old_filename,
+            )
Evidence
The diff provider stores patch=str(pf) without normalizing it, while the line-number conversion
code treats any +/- prefixed lines as real diff lines and can flush pre-@@ accumulated lines
as a hunk, using start2 before it’s initialized by a real hunk header. PRReviewer always enables
this conversion, so malformed/incorrect hunks reach the LLM prompt.

pr_agent/git_providers/diff_parsing.py[16-52]
pr_agent/algo/git_patch_processing.py[344-385]
pr_agent/tools/pr_reviewer.py[189-194]
pr_agent/git_providers/diff_provider.py[56-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In tokenless diff mode, `FilePatchInfo.patch` is set to `str(pf)` from `unidiff`. The rest of PR-Agent’s pipeline (notably `decouple_and_convert_to_hunks_with_lines_numbers`) assumes patches are primarily hunk bodies starting at `@@ ... @@` and treats any `+...` / `-...` lines as actual additions/deletions. If the per-file patch string includes diff metadata lines like `--- ...` / `+++ ...` before the first hunk, those are misinterpreted as content changes and can be flushed as a bogus “first hunk” with incorrect line numbering.
### Issue Context
`PRReviewer` forces `add_line_numbers_to_hunks=True`, so this formatting mismatch directly impacts the prompt that is sent to the LLM and any line-based outputs.
### Fix Focus Areas
- pr_agent/git_providers/diff_parsing.py[16-53]
- pr_agent/git_providers/diff_provider.py[56-77]
- pr_agent/algo/git_patch_processing.py[344-406]
### Suggested fix approach
After base reconstruction (while you still have the full patch available if needed), normalize `f.patch` to the hunk-only form used elsewhere:
- Remove leading diff metadata lines up to (but not including) the first `@@` header.
- Keep all hunks (including multi-hunk patches) intact.
- If a patch has no hunks (rename-only), set `f.patch` to `""` (or a minimal, explicitly handled marker) so downstream logic doesn’t emit malformed hunks.
This can be implemented either:
- In `DiffGitProvider.get_diff_files()` after `reconstruct_base_file(...)` (recommended, since reconstruction may depend on full patch form), or
- By changing `parse_unified_diff()` to emit hunk-only patches and adjusting reconstruction to work with that format.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (11)
4. Tests leak get_settings() state ✓ Resolved 📘 Rule violation ☼ Reliability
Description
New tests set values on the global Dynaconf settings singleton via get_settings().set(...) without
restoring prior values, risking order-dependent/flaky tests. This violates the requirement to
control external/global state deterministically in tests.
Code

tests/unittest/test_diff_mode_e2e.py[R108-113]

+    # --- configure provider ---
+    get_settings().set("config.git_provider", "diff")
+    get_settings().set("diff.content", DIFF)
+    get_settings().set("diff.output_path", None)
+    # Disable publish so we don't need a real comment sink
+    get_settings().set("config.publish_output", False)
Evidence
PR Compliance ID 20 requires tests to explicitly control and isolate external/global state to avoid
machine/order-dependent behavior. The tests set multiple global settings values, and the CLI code
comments confirm get_settings() is process-wide, making restoration necessary to prevent leakage
across tests.

tests/unittest/test_diff_mode_e2e.py[19-22]
tests/unittest/test_diff_mode_e2e.py[108-113]
pr_agent/cli.py[122-125]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tests mutate the global `get_settings()` singleton (e.g., `config.git_provider`, `diff.content`, `config.publish_output`) but do not restore prior values. This can leak state across tests and cause flaky, order-dependent failures.
## Issue Context
The codebase explicitly notes that `get_settings()` is a process-wide singleton, so test changes must be reverted.
## Fix Focus Areas
- tests/unittest/test_diff_mode_e2e.py[19-22]
- tests/unittest/test_diff_mode_e2e.py[108-113]
- pr_agent/cli.py[122-125]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Incremental mode TypeError ✓ Resolved 🐞 Bug ☼ Reliability
Description
When users pass "-i" (incremental review), DiffGitProvider inherits
GitProvider.get_incremental_commits() which is a no-op, leaving IncrementalPR.commits_range as None.
PRReviewer then executes len(self.incremental.commits_range) and crashes with a TypeError, aborting
the run.
Code

pr_agent/git_providers/diff_provider.py[R28-38]

+    def __init__(self, pr_url=None, incremental=False):
+        diff_text = get_settings().get("diff.content", None)
+        if not diff_text or not str(diff_text).strip():
+            raise ValueError("No diff content provided for the 'diff' git provider")
+        self.diff_text = diff_text
+        self.output_path = get_settings().get("diff.output_path", None)
+        self.diff_files = None
+        self.pr = PullRequestMimic(self.get_pr_title(), self.get_diff_files())
+        # inline code comments are not supported for the diff provider
+        get_settings().pr_reviewer.inline_code_comments = False
+
Evidence
DiffGitProvider does not implement incremental commit discovery, but PRReviewer’s incremental path
assumes commits_range is populated and takes its length; the base GitProvider implementation leaves
commits_range as None by default.

pr_agent/git_providers/diff_provider.py[21-38]
pr_agent/git_providers/git_provider.py[165-167]
pr_agent/git_providers/git_provider.py[477-483]
pr_agent/tools/pr_reviewer.py[47-52]
pr_agent/tools/pr_reviewer.py[328-342]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In diff mode, the incremental review flag (`-i`) can cause a runtime crash because `DiffGitProvider` does not populate `IncrementalPR.commits_range`, but `PRReviewer._can_run_incremental_review()` assumes it is a list.
### Issue Context
- `GitProvider.get_incremental_commits()` is a no-op by default, and `IncrementalPR.commits_range` defaults to `None`.
- `PRReviewer` calls `git_provider.get_incremental_commits()` and later does `len(self.incremental.commits_range)`.
### Fix Focus Areas
- Implement `DiffGitProvider.get_incremental_commits()` to explicitly disable incremental mode (e.g., set `incremental.is_incremental = False` and log why) so PRReviewer never enters the incremental path.
- (Optional hardening) In `PRReviewer._can_run_incremental_review()`, guard against `commits_range is None` before calling `len()`.
- pr_agent/git_providers/diff_provider.py[21-38]
- pr_agent/tools/pr_reviewer.py[47-52]
- pr_agent/tools/pr_reviewer.py[328-342]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Extra config overrides diff ✓ Resolved 🐞 Bug ≡ Correctness
Description
CLI diff mode forces config.git_provider="diff", but apply_repo_settings() merges
CONFIG.EXTRA_CONFIG_URL into settings before provider selection. If that extra config file sets
[config].git_provider, it can silently override the diff provider and break tokenless mode
(switching back to a hosted provider).
Code

pr_agent/cli.py[R93-112]

+    diff_mode = getattr(args, "stdin", False) or getattr(args, "diff_file", None)
+    if diff_mode:
+        if args.stdin and args.diff_file:
+            parser.error("--stdin and --diff-file are mutually exclusive")
+        if args.diff_file:
+            try:
+                with open(args.diff_file, "r", encoding="utf-8") as fh:
+                    diff_content = fh.read()
+            except OSError as e:
+                parser.error(f"Could not read --diff-file '{args.diff_file}': {e}")
+            except UnicodeDecodeError as e:
+                parser.error(f"--diff-file '{args.diff_file}' is not valid UTF-8 text: {e}")
+        else:
+            diff_content = sys.stdin.read()
+        if not diff_content.strip():
+            parser.error("No diff content received (empty stdin/file)")
+        get_settings().set("config.git_provider", "diff")
+        get_settings().set("diff.content", diff_content)
+        get_settings().set("diff.output_path", getattr(args, "output", None))
+    elif not args.pr_url and not args.issue_url:
Evidence
The provider is selected from current settings inside get_git_provider_with_context(), but
apply_repo_settings() applies extra config first; therefore an extra config can overwrite the
CLI-forced provider before selection happens.

pr_agent/cli.py[89-112]
pr_agent/git_providers/utils.py[225-257]
pr_agent/git_providers/init.py[60-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In tokenless diff mode, `config.git_provider` is forced to `diff`, but `apply_repo_settings()` merges extra config **before** constructing the git provider. An extra config that includes `[config].git_provider = ...` can override the forced diff provider and derail tokenless mode.
### Issue Context
- CLI sets `config.git_provider = "diff"`.
- `apply_repo_settings()` loads and merges `CONFIG.EXTRA_CONFIG_URL` and only then calls `get_git_provider_with_context()`.
- Provider selection reads `get_settings().config.git_provider` at call time.
### Fix Focus Areas
- In `apply_repo_settings()`, snapshot whether the invocation is in diff mode (e.g., `forced_provider = get_settings().config.git_provider` or a dedicated flag) and after merging extra config, restore `config.git_provider = "diff"` before calling `get_git_provider_with_context()`.
- Alternatively, during extra-config merge, ignore/strip `config.git_provider` when diff mode is active.
- pr_agent/cli.py[89-112]
- pr_agent/git_providers/utils.py[225-257]
- pr_agent/git_providers/__init__.py[60-65]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Broad except Exception output ✓ Resolved 📘 Rule violation ☼ Reliability
Description
_write_output() catches Exception broadly and only logs the error, which can mask unexpected
failures when writing the requested --output file. This violates the narrow, explicit
exception-handling requirement for provider code paths.
Code

pr_agent/git_providers/diff_provider.py[R80-85]

+            try:
+                with open(self.output_path, "w", encoding="utf-8") as fh:
+                    fh.write(content)
+            except Exception as e:
+                get_logger().error(f"Failed to write output to {self.output_path}: {e}")
+
Evidence
PR Compliance ID 22 requires narrow exception handling and discourages broad except Exception that
suppresses errors. _write_output() uses except Exception as e and continues execution after
logging, potentially hiding real failures to persist output.

pr_agent/git_providers/diff_provider.py[80-85]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The diff provider output writer uses a broad `except Exception as e` and suppresses the exception.
## Issue Context
In provider code paths, exception handling should be narrow and explicit; unexpected exceptions should not be silently swallowed. For output writing, handle expected I/O exceptions (e.g., `OSError`, `UnicodeError`) and consider re-raising (or failing fast) when `--output` was explicitly requested.
## Fix Focus Areas
- pr_agent/git_providers/diff_provider.py[80-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Improve crashes in diff mode ✓ Resolved 🐞 Bug ≡ Correctness
Description
DiffGitProvider.publish_code_suggestions() raises NotImplementedError, but the improve flow
unconditionally calls publish_code_suggestions(), so --stdin/--diff-file improve can crash when
suggestions exist. This breaks a documented supported command in tokenless diff mode.
Code

pr_agent/git_providers/diff_provider.py[R129-130]

+    def publish_code_suggestions(self, code_suggestions: list) -> bool:
+        raise NotImplementedError("Code suggestions are not supported by the diff provider")
Evidence
DiffGitProvider raises NotImplementedError for code-suggestion publishing, while PRCodeSuggestions
always invokes publish_code_suggestions() without capability checks, so diff-mode improve will
crash at publish time.

pr_agent/git_providers/diff_provider.py[119-131]
pr_agent/tools/pr_code_suggestions.py[550-580]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In tokenless diff mode, `improve` uses `PRCodeSuggestions`, which always calls `git_provider.publish_code_suggestions(...)`. The new `DiffGitProvider.publish_code_suggestions()` currently raises `NotImplementedError`, causing a runtime crash instead of emitting output to stdout / `--output`.
## Issue Context
In diff mode there is no hosting platform API, but `improve` can still produce useful markdown output; the provider should therefore support publishing code suggestions locally (or the tool should gracefully fallback to `publish_comment`).
## Fix Focus Areas
- pr_agent/git_providers/diff_provider.py[119-131]
- pr_agent/tools/pr_code_suggestions.py[550-580]
## Suggested fix approach
- Option A (preferred): implement `DiffGitProvider.publish_code_suggestions()` and `publish_code_suggestion()` to render the suggestions list into a single markdown document and send it through `_write_output()`.
- Option B: in `PRCodeSuggestions`, if the provider does not support code suggestions, fallback to publishing a markdown comment (stdout) instead of calling `publish_code_suggestions()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. CWD file disclosure risk ✓ Resolved 🐞 Bug ⛨ Security
Description
If no repository root is detected, DiffGitProvider uses the current working directory as the
enrichment root and will read any file under it whose path appears in the diff. That content is
stored in head_file and can be incorporated into the generated prompt, potentially leaking unrelated
local files/secrets to the LLM.
Code

pr_agent/git_providers/diff_provider.py[R48-49]

+        repo_root = _find_repository_root()
+        root = os.path.realpath(str(repo_root) if repo_root else os.getcwd())
Evidence
The provider explicitly uses os.getcwd() when _find_repository_root() fails, then reads
candidate files from that root; the read content is stored on FilePatchInfo and later fed into diff
generation used for LLM prompting.

pr_agent/git_providers/diff_provider.py[46-70]
pr_agent/config_loader.py[63-75]
pr_agent/algo/pr_processing.py[175-187]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DiffGitProvider.get_diff_files()` attempts working-tree enrichment by reading `f.filename` from disk. When `_find_repository_root()` returns `None`, it falls back to `os.getcwd()` as the root, allowing an untrusted diff to cause reads of any file within the current working directory tree (even if that directory is not a repo). The read contents can be sent to the LLM via the generated diff.
## Issue Context
There is already a traversal guard that prevents escaping the chosen root via `..`, but the root choice itself becomes unsafe when it is not a real repo root.
## Fix Focus Areas
- pr_agent/git_providers/diff_provider.py[46-70]
- pr_agent/config_loader.py[63-75]
- pr_agent/algo/pr_processing.py[175-187]
## Suggested fix approach
- If `_find_repository_root()` returns `None`, skip working-tree enrichment entirely (leave `head_file`/`base_file` empty) and operate in patch-only mode.
- Optionally log once that enrichment was disabled because no `.git` root was found, so behavior is explainable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. --diff-file read lacks handling ✓ Resolved 📘 Rule violation ☼ Reliability
Description
In diff mode, the CLI opens the user-provided --diff-file with open(args.diff_file, ...) without
handling OSError/UnicodeDecodeError, so a missing or unreadable file path can crash the program
with a traceback instead of failing fast with a clear, user-facing argparse-style error. This makes
tokenless diff mode brittle in automation.
Code

pr_agent/cli.py[R93-103]

+    diff_mode = getattr(args, "stdin", False) or getattr(args, "diff_file", None)
+    if diff_mode:
+        if args.stdin and args.diff_file:
+            parser.error("--stdin and --diff-file are mutually exclusive")
+        if args.diff_file:
+            with open(args.diff_file, "r", encoding="utf-8") as fh:
+                diff_content = fh.read()
+        else:
+            diff_content = sys.stdin.read()
+        if not diff_content.strip():
+            parser.error("No diff content received (empty stdin/file)")
Evidence
The diff-mode branch reads the --diff-file path using a bare open() call and does not surround
it with any exception handling or convert failures into parser.error(...), so filesystem and
decoding errors (e.g., missing file, permission issues, invalid encoding) will propagate as uncaught
exceptions and terminate the CLI. This violates the requirement to validate boundary inputs and
provide clear fail-fast behavior for user-provided paths/content.

pr_agent/cli.py[93-103]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`--diff-file` is user input, but in diff mode the CLI reads it via `open(...).read()` without guarding for `OSError` (e.g., `FileNotFoundError`, `PermissionError`) or `UnicodeDecodeError`. As a result, invalid paths or unreadable/invalidly-encoded files can crash the CLI with a traceback instead of exiting cleanly with an actionable, user-friendly argparse-style error.
## Issue Context
This is a CLI-only code path (tokenless diff mode) and should behave like other argparse validation errors by failing fast and reporting a clear message. Compliance guidance also requires boundary inputs (user-provided paths/content) to be validated/normalized with clear fail-fast behavior.
## Fix Focus Areas
- pr_agent/cli.py[93-103]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. reconstruct_base_file broad exception catch ✓ Resolved 📘 Rule violation ☼ Reliability
Description
reconstruct_base_file uses except Exception as e when parsing patches, which can hide unexpected
errors and makes failures harder to debug; it should catch the specific unidiff parse exception
types and handle them explicitly.
Code

pr_agent/git_providers/diff_parsing.py[R58-62]

+    try:
+        patch_set = PatchSet(patch_str)
+    except Exception as e:
+        get_logger().info(f"Could not parse patch for base reconstruction: {e}")
+        return ""
Evidence
Rule 20 prohibits broad except Exception in parsing/provider-init paths; the new
reconstruct_base_file implementation catches all exceptions instead of a specific unidiff parse
exception class.

pr_agent/git_providers/diff_parsing.py[58-62]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`reconstruct_base_file()` catches `Exception` broadly when calling `PatchSet(patch_str)`, then logs and returns an empty string. This risks masking non-parse-related bugs and violates the narrow-exception requirement.
## Issue Context
This is patch parsing/base reconstruction logic (a sensitive code path per the checklist) and should use narrow exception types.
## Fix Focus Areas
- pr_agent/git_providers/diff_parsing.py[58-62]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. get_diff_files() broad exception catches ✓ Resolved 📘 Rule violation ☼ Reliability
Description
DiffGitProvider.get_diff_files() catches Exception during diff parsing and working-tree file
reads, which can swallow unexpected failures and violates the requirement for narrow exception
handling in provider/parsing code paths.
Code

pr_agent/git_providers/diff_provider.py[R40-63]

+        try:
+            files = parse_unified_diff(self.diff_text)
+        except Exception as e:
+            raise ValueError(f"Failed to parse the provided diff: {e}") from e
+        root = os.path.realpath(os.getcwd())
+        for f in files:
+            head = ""
+            if f.filename:
+                if os.path.isabs(f.filename):
+                    get_logger().info(
+                        f"Skipping absolute path in diff (unsafe): {f.filename}"
+                    )
+                else:
+                    candidate = os.path.realpath(os.path.join(root, f.filename))
+                    if candidate != root and not candidate.startswith(root + os.sep):
+                        get_logger().info(
+                            f"Skipping path that escapes repo root (path traversal): {f.filename}"
+                        )
+                    elif os.path.isfile(candidate):
+                        try:
+                            with open(candidate, "r", encoding="utf-8") as fh:
+                                head = fh.read()
+                        except Exception as e:
+                            get_logger().info(f"Could not read working-tree file {f.filename}: {e}")
Evidence
Rule 20 requires catching specific exception types in parsing/provider-init code; the new provider
catches Exception broadly for diff parsing and file reading.

pr_agent/git_providers/diff_provider.py[40-63]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DiffGitProvider.get_diff_files()` uses `except Exception as e` both when parsing the diff and when reading files from disk. This is overly broad for provider initialization/parsing paths.
## Issue Context
The checklist requires narrow, explicit exception handling (ideally catching `UnidiffParseError` for parsing and `OSError`/`UnicodeDecodeError` for file IO), while preserving context for debugging.
## Fix Focus Areas
- pr_agent/git_providers/diff_provider.py[40-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Unused imports in test_diff_mode_e2e ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
tests/unittest/test_diff_mode_e2e.py adds unused imports (AsyncMock, MagicMock), which will
trigger Ruff/Pyflakes (F401) and violate the requirement that Python changes have zero lint
issues.
Code

tests/unittest/test_diff_mode_e2e.py[R1-6]

+import pytest
+from unittest.mock import AsyncMock, MagicMock
+
+from pr_agent.config_loader import get_settings
+from pr_agent.git_providers.diff_provider import DiffGitProvider
+from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
Evidence
Rule 13 requires Python code to satisfy Ruff/isort conventions with zero issues; the added test file
includes unused imports on the changed lines.

AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues: AGENTS.md: Python Code Must Satisfy Ruff/isort Conventions and Flake8 Must Have Zero Issues
tests/unittest/test_diff_mode_e2e.py[1-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

##...

Comment thread pr_agent/cli.py
Comment thread pr_agent/git_providers/diff_parsing.py
Comment thread pr_agent/git_providers/diff_provider.py
Comment thread tests/unittest/test_diff_mode_e2e.py Outdated
Comment thread pr_agent/git_providers/diff_parsing.py
- cli: handle OSError/UnicodeDecodeError when reading --diff-file, failing
  fast with a clear parser.error instead of an uncaught traceback
- diff_parsing: catch the specific UnidiffParseError (not bare Exception) in
  reconstruct_base_file; keep an empty reconstructed base as "" (never "\n")
  so downstream extend_patch() treats it as having no original file
- diff_provider: narrow exception handling to UnidiffParseError for parsing
  and (OSError, UnicodeDecodeError) for working-tree reads; resolve diff
  paths against the repository root (_find_repository_root) instead of the
  raw CWD so enrichment still works when run from a subdirectory
- tests: update add-to-empty base expectation to ""; add regression tests
  for subdirectory enrichment and missing --diff-file fail-fast; remove
  unused imports (F401)
Comment thread pr_agent/git_providers/diff_provider.py
Comment thread pr_agent/git_providers/diff_provider.py Outdated
Comment thread pr_agent/git_providers/diff_provider.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 304384d

@naorpeled

Copy link
Copy Markdown
Member

Hey @vectorkovacspeter, thanks for taking this!
Can you please address Qodo's comments?

@vectorkovacspeter

Copy link
Copy Markdown
Author

Thanks for the review. I've addressed all six findings in 304384d:

  • Fail-fast handling for unreadable/invalid --diff-file (OSError/UnicodeDecodeError)
  • Narrowed broad except Exception to UnidiffParseError / (OSError, UnicodeDecodeError) in parsing and working-tree reads
  • Empty reconstructed base now stays "" (never "\n") so extend_patch() treats it correctly
  • Diff paths resolved against the repo root, so enrichment works when run from a subdirectory
  • Removed the unused AsyncMock/MagicMock imports

Added regression tests for the subdirectory case and the missing-file fail-fast; suite is now 25 passing.

@naorpeled

Copy link
Copy Markdown
Member

Hey @vectorkovacspeter,
Thanks!

Could you also please address the first 3 that Qodo hasn't marked as resolved?

… mode

Addresses the new findings raised after the previous fix round:

- improve crash (correctness): DiffGitProvider now implements
  publish_code_suggestions()/publish_code_suggestion() to render suggestions
  as a markdown document to stdout/--output instead of raising
  NotImplementedError, so `--stdin/--diff-file improve` works as documented
- CWD file-disclosure risk (security): when no .git repository root is
  detected, working-tree enrichment is disabled (patch-only) instead of
  reading files from an arbitrary current directory
- broad except (reliability): _write_output() now catches only
  (OSError, UnicodeError) and re-raises, since --output is an explicit
  request whose failure must not be swallowed
- style: double quotes for the new CLI args; wrapped the long
  publish_inline_comment signature to satisfy Ruff
- docs: note that improve renders suggestions as markdown in this mode
- tests: add coverage for code-suggestion rendering (and empty no-op),
  no-repo-root patch-only mode; harden the path-traversal sentinel with a
  real .git root so it isolates the traversal guard
@vectorkovacspeter

Copy link
Copy Markdown
Author

Done — pushed dfb19b2.

Addressed the new findings:

  • improve now renders code suggestions as markdown to stdout/--output (it previously crashed)
  • enrichment is disabled to patch-only when no .git root is found (avoids reading arbitrary CWD files)
  • narrowed the _write_output exception handling
  • fixed the quote/line-length style nits.

Added tests for the suggestion rendering and the no-repo-root path; suite is 28 passing.

Comment thread tests/unittest/test_diff_mode_e2e.py Outdated
Comment thread pr_agent/git_providers/diff_provider.py
Comment thread pr_agent/cli.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit dfb19b2

@vectorkovacspeter

Copy link
Copy Markdown
Author

Oh man, this bot is a hard one , but i working on it 😄

…ff mode

- incremental crash (correctness): DiffGitProvider.get_incremental_commits()
  now disables incremental mode (-i has no meaning for a standalone diff),
  preventing a TypeError when PRReviewer takes len() of an empty commits_range
- extra-config override (correctness): provider selection now forces the diff
  provider whenever diff content is loaded, so an extra/repo config setting
  config.git_provider can no longer silently derail tokenless mode
- suppressed output (correctness): CLI diff mode forces config.publish_output
  =True so stdout/--output is always produced even if publishing was disabled
- global mutation (reliability): removed the dead
  pr_reviewer.inline_code_comments=False write in __init__ (never read
  anywhere; was copied from LocalGitProvider)
- test isolation (reliability): added an autouse fixture to each diff test
  module that snapshots and restores the mutated global settings keys
- tests: added coverage for incremental-disabled, diff-content-forces-provider,
  and publish_output forcing
@vectorkovacspeter

Copy link
Copy Markdown
Author

Pushed a05bce3 addressing the new findings:

  • disabled incremental mode in diff mode (was a -i crash)
  • made the diff provider sticky against extra-config git_provider overrides
  • forced publish_output=True so stdout/--output is never suppressed
  • removed the dead inline_code_comments global write
  • added test-isolation fixtures. Suite is 31 passing.

Comment thread pr_agent/git_providers/diff_provider.py Outdated
Comment thread tests/unittest/test_diff_mode_e2e.py Outdated
Comment thread pr_agent/git_providers/diff_parsing.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a05bce3

…und 4)

- patch misparsed as hunks (correctness): the stored FilePatchInfo.patch was
  str(pf) from unidiff, which keeps the 'diff --git'/'index'/'---'/'+++'
  headers before the first '@@'. The shared hunk/line-number converter treats
  '+'/'-' lines as content, so those headers were emitted as a bogus leading
  hunk with invalid line numbers into the LLM prompt. Added
  to_hunk_only_patch() and normalize f.patch after base reconstruction (which
  still needs the full headers to parse), matching platform providers.
- isort (I001): collapsed the two-name diff_parsing import to one line and
  sorted imports in the diff test modules; verified with ruff.
- test isolation: tests now mutate settings only through an autouse `cfg`
  fixture that snapshots and restores the diff-mode keys (covers run()'s
  indirect mutations too); no bare get_settings().set() in test bodies.
- test style: replaced `assert False` with pytest.raises (B011).
- tests: added a hunk-only-patch assertion.
@vectorkovacspeter

Copy link
Copy Markdown
Author

Pushed ae473c4. Addressed the findings:

  • Diff metadata misparsed as hunks — good catch; the patch now strips the file headers to hunk-only form (matching the platform providers) after base reconstruction, so no phantom hunk reaches the prompt. Added a regression test.
  • isort (1, 2) — fixed and verified with ruff check.
  • Test state isolation — tests now go through an autouse cfg fixture that snapshots and restores the settings keys; no bare get_settings().set() left in the bodies.

32 tests pass.

Note: I scoped changes to this feature and left pre-existing repo lint (e.g. the duplicate GiteaProvider import, some long lines) untouched to keep the diff focused — happy to fold those in separately if you'd like.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ae473c4

@vectorkovacspeter

Copy link
Copy Markdown
Author

@naorpeled all of Qodo's findings are now resolved — on the latest push it finally came back empty-handed. 🎉

Genuinely impressed with that review bot, by the way: it caught a couple of real ones I'd have shipped (the improve crash and the diff-metadata-misparsed-as-hunks bug were proper catches). It's also… thorough. Four rounds deep I'm fairly sure it would flag the Mona Lisa for an unsorted import. 😄 No complaints though — the code is better for it.

Ready for your review whenever you have a chance, and happy to make any further changes you'd like.

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.

CLI-only / Tokenless Local Mode via Git Diff (stdin/file)

2 participants