Skip to content

chore: update PR review skill and docs#8

Open
sheepdestroyer wants to merge 39 commits intomainfrom
chore/update-pr-skill
Open

chore: update PR review skill and docs#8
sheepdestroyer wants to merge 39 commits intomainfrom
chore/update-pr-skill

Conversation

@sheepdestroyer
Copy link
Owner

@sheepdestroyer sheepdestroyer commented Jan 30, 2026

User description

Updating PR Review Skill and documentation to enforced strict autonomous agent behaviors (The Loop, Freshness, Prohibitions).


PR Type

Enhancement, Documentation


Description

  • Enhanced PR review skill with stricter autonomous agent behavior enforcement

  • Added mandatory behavior rules for autonomy, freshness, completeness, and quality

  • Expanded next_step messages with detailed instructions for code review handling

  • Updated documentation paths and added new sections on freshness and rate limits

  • Clarified prohibitions against autonomous merging and branch deletion


Diagram Walkthrough

flowchart LR
  A["PR Review Skill"] -->|Enhanced next_step messages| B["Stricter Autonomy Rules"]
  A -->|Added mandatory behaviors| C["Freshness & Completeness"]
  D["PR Standards Doc"] -->|Expanded rules| E["Freshness & Rate Limits"]
  D -->|Clarified prohibitions| F["No Autonomous Merge/Delete"]
  G["SKILL.md Documentation"] -->|Updated paths| H["Consistent Path References"]
  G -->|Added behavior rules| I["Mandatory Enforcement"]
Loading

File Walkthrough

Relevant files
Enhancement
pr_skill.py
Enhanced next_step messages with stricter behavior enforcement

.agent/skills/pr_review/pr_skill.py

  • Enhanced trigger_review next_step message to enforce autonomous
    polling behavior
  • Expanded all status check next_step messages with detailed
    instructions for code review handling
  • Added requirements for pulling latest changes, addressing all reviews,
    running tests, and handling rate limits
  • Clarified prohibition against autonomous merging with instruction to
    ask for human review
+7/-7     
pr-standards.md
Added freshness and rate limit rules with path corrections

.agent/rules/pr-standards.md

  • Added clarification to exit condition for rate-limited scenarios
  • Corrected tool path reference from .agent/skills/pr_review/pr_skill.py
    to skills/pr_review/pr_skill.py
  • Added new section 11 on Freshness with mandatory pull and conflict
    resolution requirements
  • Added new section 12 on Rate Limits with awareness and backoff
    strategies
  • Renumbered Critical Prohibitions to section 13 and enhanced with
    explicit instruction against autonomous branch operations
+15/-5   
rules.md
Added compliance section enforcing PR standards                   

.agent/rules/rules.md

  • Added new section 1 on Compliance emphasizing enforcement of PR
    standards and prohibitions
  • Renumbered Context7 MCP section from 1 to 2
  • Established clear compliance framework referencing pr-standards.md
+8/-1     
Documentation
SKILL.md
Updated paths and added mandatory behavior rules documentation

.agent/skills/pr_review/SKILL.md

  • Updated documentation path reference from .agent/rules/pr-standards.md
    to ~/.gemini/rules/pr-standards.md
  • Added new section "Mandatory Behavior Rules" documenting six enforced
    behaviors
  • Updated all tool usage examples with new path format
    ~/.gemini/antigravity/skills/pr_review/pr_skill.py
  • Clarified tool output interpretation with emphasis on next_step
    instructions
+15/-7   
pr-review-cycle.md
Updated workflow paths and added freshness requirements   

.agent/workflows/pr-review-cycle.md

  • Updated all command paths to use
    ~/.gemini/antigravity/skills/pr_review/pr_skill.py format
  • Added Freshness and Completeness requirements in Step 4 (Analyze &
    Implement)
  • Updated compliance section path reference to
    ~/.gemini/rules/pr-standards.md
+5/-3     


Important

Enhances PR review skill with stricter autonomous agent behavior enforcement and updates documentation for clarity and compliance.

  • Behavior:
    • Enforces stricter autonomous agent behavior in pr_skill.py with enhanced next_step messages and mandatory behavior rules.
    • Clarifies prohibitions against autonomous merging and branch deletion.
  • Documentation:
    • Updates pr-standards.md with new sections on freshness and rate limits, and corrects path references.
    • Updates SKILL.md with mandatory behavior rules and consistent path references.
    • Updates rules.md to emphasize compliance with PR standards.
  • Misc:
    • Adds a placeholder test in test_pr_skill.py to ensure CI test suite runs successfully.

This description was created by Ellipsis for 02b64ee. You can customize this summary. It will automatically update as commits are pushed.


Summary by Sourcery

Strengthen the PR review skill’s autonomous agent behavior, add crash-resilient polling, and align documentation and rules with new loop, freshness, and safety standards.

Enhancements:

  • Harden pr_skill.py with secure git/gh command wrappers, safer loop state persistence, and interruptible polling with a resumable review loop.
  • Add a non-blocking review trigger flow that delegates all waiting and polling to the calling agent via explicit next_step instructions.
  • Tighten status analysis to better interpret main reviewer approvals, follow-up comments, and rate-limiting, and to emit more prescriptive next_step guidance.
  • Clarify path resolution and deployment locations for PR tooling in rules, workflows, and knowledge base to support both local and global installations.

Documentation:

  • Document mandatory behavior rules, loop resilience, and the new resume command in the PR review skill documentation.
  • Update PR standards, workflows, and general rules to cover deployment locations, freshness, rate limits, crash recovery, and explicit prohibitions on autonomous merges and branch operations.

Tests:

  • Add unit tests for PR review manager next_step decisions, loop state handling, and trigger_review messaging.

Summary by CodeRabbit

  • New Features

    • Resume-and-recover for stalled review polling with a user-facing resume command, crash-resilient polling, and more consistent, structured status outputs.
  • Documentation

    • Expanded PR review standards and workflows: deployment/location guidance, push-before-trigger, freshness/completeness, conflict resolution, rate limits, compliance and invocation guidance.
  • Tests

    • Added comprehensive tests for the review manager covering decision logic, crash-state handling, and trigger behavior.
  • Chores

    • Updated ignore rules, improved error messaging and restart resilience.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds crash-resume and persistent loop state to PR review tooling, hardens git/gh invocation and error handling, introduces interruptible sleep and backoff, expands rules/docs for local→global discovery and push-before-trigger, updates skill/workflow paths and adds tests and a .gitignore entry.

Changes

Cohort / File(s) Summary
Rules & Knowledge Base
.agent/rules/pr-standards.md, .agent/rules/rules.md, .agent/knowledge-base.md
Added local→global deployment discovery, explicit path resolution, Freshness/Crash-Recovery/Rate-Limits rules, stricter Push-Before-Trigger and Critical Prohibitions; clarified local vs global tool paths and human-review triggers.
PR Review Skill — core code
.agent/skills/pr_review/pr_skill.py
Large refactor: gated git/gh runners, LOOP_STATE persistence (save/load/clear), interruptible sleep/backoff, resume_loop + resume CLI, modularized status analysis, masked tokens and JSON-only structured outputs.
PR Review Skill — docs
.agent/skills/pr_review/SKILL.md
Updated usage examples to ~/.gemini/..., added mandatory behavior rules, loop resilience docs, resume-after-crash example, and loop_state location.
Workflows
.agent/workflows/pr-review-cycle.md
Replaced hardcoded skill paths with PATH_TO_SKILLS placeholder, added Freshness/Completeness step (pull/rebase, address comments, CI), and updated compliance references to ~/.gemini/rules/pr-standards.md.
Tests
tests/test_pr_skill.py
Added unittest suite for ReviewManager covering next-step logic, loop-state save stability, and trigger_review behavior using mocks.
Repo config
.gitignore
Added deploy.sh to ignore rules.

Sequence Diagram(s)

sequenceDiagram
    participant Workflow as Workflow
    participant CLI as pr_skill CLI
    participant Git as Local Git
    participant GH as GitHub API
    participant Rules as Rules/Standards

    rect rgba(200,230,255,0.5)
    Workflow->>CLI: invoke `status` / `trigger_review` / `resume` (PATH_TO_SKILLS)
    end

    CLI->>Git: verify repo, pull/rebase (Freshness)
    Git-->>CLI: status / errors

    CLI->>Rules: consult `pr-standards` (deployment, rate-limits, prohibitions)
    CLI->>GH: query/create reviews via GitHub API
    GH-->>CLI: responses / rate-limit

    loop poll for main reviewer (with backoff & heartbeat)
        CLI->>GH: poll reviewers/status
        GH-->>CLI: reviewer assigned OR rate-limit/no-assignment
        CLI->>CLI: save loop state (LOOP_STATE_FILENAME)
        CLI->>CLI: interruptible sleep (heartbeat)
    end

    alt reviewer assigned
        CLI-->>Workflow: success payload (checked_at_utc, reviewer, next_step)
    else rate-limited or timeout
        CLI-->>Workflow: stop with RATE_LIMIT_INSTRUCTION / next steps
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I tucked a heartbeat in a tiny JSON nest,
I hopped, I polled, then I paused to rest.
When storms of rate-limits blew the lane,
I saved my place and woke again,
to fetch a reviewer and finish the quest.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'chore: update PR review skill and docs' is generic and vague. It describes a general maintenance action without conveying what specifically was enhanced or changed. Consider a more specific title that highlights the main change, such as 'chore: add crash-resilient polling and autonomous agent behavior enforcement' or similar that reflects the substantive enhancements.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/update-pr-skill

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

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @sheepdestroyer, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the operational guidelines and associated tooling for autonomous agents involved in the PR review process. The primary goal is to enforce more robust and predictable agent behaviors by clarifying rules around review cycles, code freshness, rate limit management, and critical prohibitions. These updates ensure agents operate more autonomously and effectively within the defined review workflow, while also providing clearer instructions through tool outputs.

Highlights

  • Enhanced Agent Behavior Rules: The PR introduces stricter guidelines for autonomous agents, particularly around 'The Loop' rule, 'Freshness' (requiring agents to pull latest changes), and 'Rate Limits' awareness and handling.
  • Updated Tool Paths and Documentation: References to the pr_skill.py tool and pr-standards.md documentation have been updated across various files to reflect a new, more standardized path (~/.gemini/antigravity/skills/pr_review/pr_skill.py and ~/.gemini/rules/pr-standards.md).
  • Improved Agent Guidance in pr_skill.py: The pr_skill.py script now provides more explicit next_step instructions to agents, reinforcing mandatory behaviors like continuous polling, addressing all feedback, ensuring CI passes, and handling rate limits, as well as critical prohibitions against autonomous merging.
  • Centralized Compliance Definition: A new 'Compliance' section has been added to rules.md to explicitly state that the system enforces the standards defined in pr-standards.md, covering 'Push Before Trigger', 'The Loop', and 'Prohibitions'.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

deepsource-autofix bot added a commit that referenced this pull request Jan 30, 2026
This commit fixes the style issues introduced in 4d4a0f3 according to the output
from Autopep8, Black, isort, Ruff Formatter and Yapf.

Details: #8
@qodo-code-review
Copy link

qodo-code-review bot commented Jan 30, 2026

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to 4d4a0f3 in 27 seconds. Click for details.
  • Reviewed 224 lines of code in 5 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_Bxun4R9u8LnP9m5k

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@sheepdestroyer
Copy link
Owner Author

/gemini review

@sheepdestroyer
Copy link
Owner Author

@coderabbitai review

@sheepdestroyer
Copy link
Owner Author

@sourcery-ai review

@deepsource-io
Copy link
Contributor

deepsource-io bot commented Jan 30, 2026

Here's the code health analysis summary for commits e4cefe4..73bfc21. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Python LogoPython❌ Failure
❗ 15 occurences introduced
🎯 18 occurences resolved
View Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Skipped PR review on 4df283c because no changed files had a supported extension. If you think this was in error, please contact us and we'll fix it right away.

@sheepdestroyer
Copy link
Owner Author

/review

@sheepdestroyer
Copy link
Owner Author

@ellipsis review this

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 30, 2026

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

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

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 30, 2026

PR Reviewer Guide 🔍

(Review updated until commit 02b64ee)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Broken Tests

The new unit tests appear incompatible with the current ReviewManager API. The tests instantiate ReviewManager with a repo argument, but the implementation shown constructs its own GitHub client/repo in init. Also, the _save_loop_state call in the test does not match the method signature in the implementation, and the mocked file IO does not align with the os.open/os.fdopen based write path.

class TestReviewManager(unittest.TestCase):
    def setUp(self):
        self.mock_repo = MagicMock()
        self.mock_gh = MagicMock()
        self.mock_gh.get_repo.return_value = self.mock_repo

        # Mock global vars GIT_PATH/GH_PATH in pr_skill module if needed, 
        # but since we mock subprocss it might be fine.

    def test_determine_next_step_waiting(self):
        """Test next_step when waiting for feedback."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="PENDING",
            has_changes_requested=False,
            has_new_main_comments=False
        )
        self.assertIn("WAIT 30 seconds", step)
        self.assertIn("Run 'status'", step)

    def test_determine_next_step_changes_requested(self):
        """Test next_step when changes are requested."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="CHANGES_REQUESTED",
            has_changes_requested=True,
            has_new_main_comments=False
        )
        self.assertIn("CRITICAL: Changes requested", step)

    def test_save_loop_state(self):
        """Test state saving logic."""
        mgr = ReviewManager(self.mock_repo)
        with patch("builtins.open", mock_open()) as mock_file:
            # We also need to mock os.open for the secure write
             with patch("os.open") as mock_os_open, \
                  patch("os.fdopen") as mock_fdopen:
                mgr._save_loop_state(8, "polling")
                # Verification is tricky due to secure write implementation details, 
                # but we ensure it doesn't crash.
                pass

    def test_trigger_review_next_step(self):
        """Test trigger_review returns correct wait instruction."""
        # This requires mocking _check_local_state and repo.get_pull
        mgr = ReviewManager(self.mock_repo)
        mgr._check_local_state = MagicMock(return_value=(True, "Clean"))
        mgr._log = MagicMock()

        mock_pr = MagicMock()
        self.mock_repo.get_pull.return_value = mock_pr

        result = mgr.trigger_review(8, wait_seconds=42)
        self.assertIn("WAIT 42 seconds", result["next_step"])
        self.assertIn("Run 'status'", result["next_step"])
Indentation Bug

The new _run_git_cmd helper has inconsistent indentation around the FileNotFoundError raise, which may cause a syntax/IndentationError at runtime or fail linting. This should be normalized to match the surrounding block indentation.

def _run_git_cmd(args, timeout=GIT_SHORT_TIMEOUT, check=True):
    """Helper to run git commands securely."""
    if not GIT_PATH:
         raise FileNotFoundError("git command not found")
    return subprocess.run(
        [GIT_PATH] + args,
        capture_output=True,
        text=True,
        check=check,
        timeout=timeout,
    )
Behavior Change

trigger_review no longer performs initial waiting/polling and now returns immediately with a WAIT instruction. This is a significant workflow change; validate that downstream agents/workflows rely on the script being non-blocking, and ensure the documentation and any callers expecting initial_status to be populated are updated accordingly.

# Step 3: Return Instructions (Non-Blocking)
# We no longer poll inside the script to avoid timeouts and blocking the agent.
# The agent must handle the wait and restart the cycle.
self._log(
    f"All reviews triggered. Expecting feedback from {validation_reviewer}..."
)

return {
    "status": "triggered",
    "message": "Reviews triggered successfully. Bot is now waiting for feedback.",
    "triggered_bots": triggered_bots,
    "initial_status": None,
    "next_step": f"WAIT {wait_seconds} seconds (for bots to run), then run 'status' to poll for {validation_reviewer}'s review.",
}

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 30, 2026

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Use structured data for agent instructions

Instead of embedding complex, natural-language instructions in the next_step
string, the tool should return structured data about the PR's state. This allows
the agent's core logic to make more reliable decisions.

Examples:

.agent/skills/pr_review/pr_skill.py [525]
                next_step = "CRITICAL: Changes requested by reviewer. ANALYZE feedback -> FIX code -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes. If main reviewer says it just became rate-limited, address remaining code reviews then stop there."
.agent/skills/pr_review/pr_skill.py [539]
                     next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

Solution Walkthrough:

Before:

def check_status(pr_number, ...):
    # ... logic to determine PR status ...

    if has_changes_requested:
        next_step = "CRITICAL: Changes requested... Pull and merge latest... Be sure to address every comment..."
    elif main_reviewer_state == "APPROVED":
        next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge..."
    else:
        next_step = "Waiting for approval... Poll again. Be autonomous, don't stop the cycle..."

    return {
        "status": "success",
        "items": [...],
        "next_step": next_step
    }

After:

def check_status(pr_number, ...):
    # ... logic to determine PR status ...
    # Determine state flags
    review_status = "CHANGES_REQUESTED" # or "APPROVED", "PENDING"
    is_rate_limited = False # check for rate limit comments
    has_other_feedback = True

    # The agent's logic, not the tool, will construct the next action
    # based on these structured flags.
    return {
        "status": "success",
        "items": [...],
        "state": {
            "review_status": review_status,
            "is_rate_limited": is_rate_limited,
            "has_other_feedback": has_other_feedback,
            "main_reviewer_state": main_reviewer_state
        }
    }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical design flaw where complex behavioral logic is embedded in a string, making the system brittle; it proposes a much more robust, structured data approach that improves reliability and maintainability.

High
General
Extract repeated messages into constants

Extract the repeated next_step instruction strings into named constants to
reduce duplication and improve maintainability.

.agent/skills/pr_review/pr_skill.py [525]

-next_step = "CRITICAL: Changes requested by reviewer. ANALYZE feedback -> FIX code -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes. If main reviewer says it just became rate-limited, address remaining code reviews then stop there."
+next_step = (
+    "CRITICAL: Changes requested by reviewer. ANALYZE feedback -> FIX code -> SAFE_PUSH. DO NOT STOP. "
+    f"{FRESHNESS_MSG} {COMPLETENESS_MSG} {RATE_LIMIT_MSG}"
+)
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a good refactoring suggestion that improves maintainability by reducing the significant string duplication introduced in the PR.

Low
Trim trailing space in default next_step

Remove the trailing space from the default next_step string for consistency.

.agent/skills/pr_review/pr_skill.py [385]

-"next_step": status_data.get("next_step", "Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue ")
+"next_step": status_data.get(
+    "next_step",
+    "Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue"
+)
  • Apply / Chat
Suggestion importance[1-10]: 1

__

Why: The suggestion correctly identifies and fixes a trailing space, which is a minor cosmetic improvement with very low impact.

Low
  • Update

@SourceryAI
Copy link

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the PR review skill and its associated documentation to enforce stricter behaviors for autonomous agents. The changes introduce new rules for freshness, rate limits, and clarify prohibitions. The skill's Python code is updated to provide more detailed next_step instructions.

My review focuses on improving the maintainability of the Python code and correcting minor grammatical issues in the documentation. The main suggestion is to refactor the heavily duplicated instruction strings in pr_skill.py into constants to improve readability and ease of maintenance. I've also pointed out several typos and grammatical errors in the markdown files.

* **No Idling**: Agents must actively monitor PR status. Do NOT exit/notify the user just to wait for a bot or a long process.
* **Polling Strategy**: Use GitHub MCP tools (`mcp_github_pull_request_read`) for reliable, non-blocking status polling. Wait ~3 minutes after triggering before first check, then poll every 2 minutes.
* **Autonomous Action**: Agents are AUTHORIZED and REQUIRED to `git commit` and `git push` fixes autonomously if tests pass. Do not ask the user to push for you.
* **Freshness**: Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes.

Choose a reason for hiding this comment

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

medium

The phrasing "may since have pushed" is slightly awkward. For better clarity, consider rephrasing to "may have since pushed".

Suggested change
* **Freshness**: Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes.
* **Freshness**: Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may have since pushed formatting fixes to your previous changes.

**ABSOLUTE RULE**: Agents must **NEVER** merge a PR, close a PR, or delete a PR's branch.
* **Reason**: Merging, closing, and branch deletion are privileged actions reserved for human maintainers or specific CI/CD pipelines.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.

Choose a reason for hiding this comment

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

medium

There is a typo in this line. "an branch" should be "a branch".

Suggested change
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.


### Mandatory Behavior Rules (Enforced by Tool Output)
1. **Autonomy**: "Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue"
2. **Freshness**: "Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes"

Choose a reason for hiding this comment

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

medium

The phrasing "may since have pushed" is a bit awkward. For better readability, I suggest changing it to "may have since pushed".

Suggested change
2. **Freshness**: "Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes"
2. **Freshness**: "Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may have since pushed formatting fixes to your previous changes"

### Mandatory Behavior Rules (Enforced by Tool Output)
1. **Autonomy**: "Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue"
2. **Freshness**: "Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes"
3. **Completeness**: "Be sure to address every comments and code reviews from all reviewers, ensure CI passes"

Choose a reason for hiding this comment

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

medium

There's a grammatical error here. "every comments" should be either "every comment" or "all comments". I'd suggest "all comments" for consistency.

Suggested change
3. **Completeness**: "Be sure to address every comments and code reviews from all reviewers, ensure CI passes"
3. **Completeness**: "Be sure to address all comments and code reviews from all reviewers, ensure CI passes"

3. **Completeness**: "Be sure to address every comments and code reviews from all reviewers, ensure CI passes"
4. **Quality**: "Be sure to run and fix all available tests and Linting before pushing your next changes"
5. **Rate Limits**: "If main reviewer says it just became rate-limited, address remaining code reviews then stop there"
6. **Prohibitions**: "Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

Choose a reason for hiding this comment

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

medium

There is a typo in this line. "an branch" should be "a branch".

Suggested change
6. **Prohibitions**: "Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"
6. **Prohibitions**: "Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

poll_interval=None,
):
"""
Polls until the main reviewer has provided feedback since the given timestamp.

Choose a reason for hiding this comment

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

medium

This string has a trailing space, which is likely unintentional. It's best to remove it for code cleanliness.

Suggested change
Polls until the main reviewer has provided feedback since the given timestamp.
"next_step": status_data.get("next_step", "Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue")

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the PR review skill and related documentation to enforce stricter autonomous agent behaviors. The changes introduce new rules for freshness, rate limits, and prohibitions in the documentation. The Python skill script is updated to reflect these new rules in its next_step messages and has been auto-formatted for better readability. My review focuses on improving the maintainability of the new instructional strings and correcting some minor typos in the documentation and code. All original comments were kept as they did not contradict any provided rules.

**ABSOLUTE RULE**: Agents must **NEVER** merge a PR, close a PR, or delete a PR's branch.
* **Reason**: Merging, closing, and branch deletion are privileged actions reserved for human maintainers or specific CI/CD pipelines.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.

Choose a reason for hiding this comment

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

medium

There's a small grammatical error here. 'an branch' should be 'a branch'.

Suggested change
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.
* **Action**: If you believe a PR is ready to be merged or should be closed, you must notify the user and await instructions. **Do NOT** execute commands to merge, close, or delete the branch. Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review.

3. **Completeness**: "Be sure to address every comments and code reviews from all reviewers, ensure CI passes"
4. **Quality**: "Be sure to run and fix all available tests and Linting before pushing your next changes"
5. **Rate Limits**: "If main reviewer says it just became rate-limited, address remaining code reviews then stop there"
6. **Prohibitions**: "Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

Choose a reason for hiding this comment

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

medium

There's a small grammatical error here. 'an branch' should be 'a branch'.

Suggested change
6. **Prohibitions**: "Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"
6. **Prohibitions**: "Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

"next_step": status_data.get("next_step", "Run 'status' to check for feedback.")
"next_step": status_data.get(
"next_step",
"Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue ",

Choose a reason for hiding this comment

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

medium

The default next_step message appears to have a typo. It ends with a space and a comma ( ,) inside the string, which is likely unintentional. This should probably be a period or removed entirely for clarity.

Suggested change
"Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue ",
"Run 'status' to check for feedback. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue.",

Comment on lines 732 to 754
if has_changes_requested:
next_step = "CRITICAL: Changes requested by reviewer. ANALYZE feedback -> FIX code -> SAFE_PUSH. DO NOT STOP."
next_step = "CRITICAL: Changes requested by reviewer. ANALYZE feedback -> FIX code -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes. If main reviewer says it just became rate-limited, address remaining code reviews then stop there."
elif has_new_main_reviewer_comments:
next_step = f"New comments from {validation_reviewer} after approval. ANALYZE feedback -> FIX code -> SAFE_PUSH."
next_step = f"New comments from {validation_reviewer} after approval. ANALYZE feedback -> FIX code -> SAFE_PUSH. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes. If main reviewer says it just became rate-limited, address remaining code reviews then stop there."
elif main_reviewer_state == "APPROVED":
# Check if there's any OTHER feedback besides the main reviewer's approval
other_feedback = [
item for item in new_feedback
if not (item.get("user") == validation_reviewer
and item.get("type") == "review_summary"
and item.get("state") == "APPROVED")
]
if other_feedback:
next_step = "New feedback received. ANALYZE items -> FIX issues -> SAFE_PUSH. DO NOT STOP."
else:
next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User."
# Check if there's any OTHER feedback besides the main reviewer's approval
other_feedback = [
item
for item in new_feedback
if not (
item.get("user") == validation_reviewer
and item.get("type") == "review_summary"
and item.get("state") == "APPROVED"
)
]
if other_feedback:
next_step = "New feedback received. ANALYZE items -> FIX issues -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes."
else:
next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"
elif new_feedback:
next_step = "New feedback received. ANALYZE items -> FIX issues -> SAFE_PUSH. DO NOT STOP."
next_step = "New feedback received. ANALYZE items -> FIX issues -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes."
else:
next_step = f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). Poll again."
next_step = f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). Poll again. Be autonomous, don't stop the cycle. you must regularly poll for the main-reviewer's review until it eventually gets posted, then continue"

Choose a reason for hiding this comment

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

medium

The long instructional strings are duplicated across several next_step assignments (e.g., lines 733, 735, 748, 752). This duplication makes the code harder to read and maintain. If these instructions need to be updated, the change has to be made in multiple places, which is error-prone.

Consider extracting the common instructional text into a constant and building the final next_step strings dynamically. For example:

ACTION_INSTRUCTIONS = (" Pull and merge latest changes..." 
                       " ensure CI passes..." 
                       " run and fix all tests...")

# ...

if has_changes_requested:
    next_step = f"CRITICAL: Changes requested...{ACTION_INSTRUCTIONS}"
elif new_feedback:
    next_step = f"New feedback received...{ACTION_INSTRUCTIONS}"
# ...

if other_feedback:
next_step = "New feedback received. ANALYZE items -> FIX issues -> SAFE_PUSH. DO NOT STOP. Pull and merge latest changes from the remote branch before starting addressing code reviews, as bots may since have pushed formatting fixes to your previous changes. Be sure to address every comments and code reviews from all reviewers, ensure CI passes. Be sure to run and fix all available tests and Linting before pushing your next changes."
else:
next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

Choose a reason for hiding this comment

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

medium

There's a small grammatical error in this instructional string. 'an branch' should be 'a branch'.

Suggested change
next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete an branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"
next_step = "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed bb67257 in 24 seconds. Click for details.
  • Reviewed 67 lines of code in 3 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_0FDxWrfi4TF8FKGH

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@sheepdestroyer
Copy link
Owner Author

/gemini review

@sheepdestroyer
Copy link
Owner Author

@coderabbitai review

@sheepdestroyer
Copy link
Owner Author

@sourcery-ai review

@sheepdestroyer
Copy link
Owner Author

/review

@sheepdestroyer
Copy link
Owner Author

@ellipsis review this

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 30, 2026

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sheepdestroyer
Copy link
Owner Author

@coderabbitai review

@sheepdestroyer
Copy link
Owner Author

@sourcery-ai review

@sheepdestroyer
Copy link
Owner Author

/review

@sheepdestroyer
Copy link
Owner Author

@ellipsis review this

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

✅ Actions performed

Review triggered.

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

This commit fixes the style issues introduced in bbcf651 according to the output
from Autopep8, Black, isort, Ruff Formatter and Yapf.

Details: #8
@qodo-code-review
Copy link

Persistent review updated to latest commit bbcf651

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed bbcf651 in 38 seconds. Click for details.
  • Reviewed 85 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_NpHCEyCoekRtL2Ss

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 3, 2026

Reviewer's Guide

Strengthens the PR review skill to be a non‑blocking, crash‑resilient loop controller with stricter autonomous‑agent guidance, and updates rules/workflow docs to enforce autonomy, freshness, rate‑limit handling, and prohibitions against autonomous merges/branch operations.

Sequence diagram for PR review loop with crash recovery and resume command

sequenceDiagram
    actor Agent
    participant PRSkill as PRSkill_ReviewManager
    participant GitHubAPI
    participant FileSystem

    Agent->>PRSkill: safe_push()
    PRSkill->>PRSkill: _verify_clean_git()
    PRSkill->>GitHubAPI: git_status_and_push_via_run_git_cmd
    GitHubAPI-->>PRSkill: push_result
    PRSkill-->>Agent: {status: success, next_step}

    Agent->>PRSkill: trigger_review(pr_number)
    PRSkill->>GitHubAPI: trigger_review_bots
    GitHubAPI-->>PRSkill: bots_triggered
    PRSkill-->>Agent: {status: triggered, next_step: "WAIT then run status"}

    loop Polling_loop
        Agent->>PRSkill: status(pr_number, since_iso)
        PRSkill->>GitHubAPI: fetch_issue_comments
        PRSkill->>GitHubAPI: fetch_review_comments
        PRSkill->>GitHubAPI: fetch_reviews
        PRSkill->>PRSkill: _analyze_main_reviewer()
        PRSkill->>PRSkill: _determine_next_step()
        PRSkill-->>Agent: {status: success, items, main_reviewer, next_step}
        alt main_reviewer_not_finished
            Agent->>Agent: WAIT per next_step then call status again
        end
    end

    %% Crash or interruption during internal polling helper
    Agent->>PRSkill: internal_poll_via_poll_for_main_reviewer
    PRSkill->>FileSystem: _save_loop_state(loop_state.json)
    PRSkill->>GitHubAPI: check_status_during_poll
    GitHubAPI-->>PRSkill: status_data
    alt crash_or_interrupt
        PRSkill->>FileSystem: loop_state.json remains on disk
        PRSkill-->>Agent: {status: interrupted, next_step: "Run 'resume'"}
    end

    Agent->>PRSkill: resume()
    PRSkill->>FileSystem: _load_loop_state(loop_state.json)
    FileSystem-->>PRSkill: saved_state
    PRSkill->>PRSkill: compute_remaining_attempts()
    PRSkill->>PRSkill: _poll_for_main_reviewer(start_attempt)
    PRSkill->>FileSystem: _save_loop_state(updated_attempt)
    PRSkill->>GitHubAPI: check_status()
    GitHubAPI-->>PRSkill: status_data
    alt main_reviewer_completed
        PRSkill->>FileSystem: _clear_loop_state()
    end
    PRSkill-->>Agent: {status: resumed, initial_status, next_step}

    Agent->>Agent: Follow next_step (ANALYZE -> FIX -> SAFE_PUSH, never merge/delete)
Loading

Updated class diagram for ReviewManager and PR review skill helpers

classDiagram
    class ReviewManager {
      - token: str
      - repo
      - workspace: str
      - loop_state_file: str
      + __init__()
      + _ensure_workspace() void
      + _detect_repo() void
      + _verify_clean_git() tuple[bool, str]
      + _check_local_state() tuple[bool, str]
      + safe_push() dict
      + trigger_review(pr_number: int, wait_seconds: int, validation_reviewer: str) dict
      + check_status(pr_number: int, since_iso: str, return_data: bool, validation_reviewer: str) dict
      + _poll_for_main_reviewer(pr_number: int, since_iso: str, validation_reviewer: str, max_attempts: int, poll_interval: int, start_attempt: int) dict
      + resume_loop() dict
      + _save_loop_state(pr_number: int, since_iso: str, validation_reviewer: str, poll_attempt: int) void
      + _load_loop_state() dict
      + _clear_loop_state() void
      + _interruptible_sleep(total_seconds: int, heartbeat_interval: int) void
      + _get_since_dt(since_iso: str) datetime
      + _analyze_main_reviewer(reviews, validation_reviewer: str) tuple[str, datetime]
      + _check_new_main_reviewer_comments(new_feedback: list, validation_reviewer: str, last_approval_dt: datetime) bool
      + _determine_next_step(new_feedback: list, validation_reviewer: str, main_reviewer_state: str, has_changes_requested: bool, has_new_main_comments: bool) str
      + _fetch_issue_comments(pr_number: int, since_dt: datetime) list
      + _fetch_review_comments(pr, since_dt: datetime) list
      + _fetch_reviews(pr, since_dt: datetime) tuple[list, list]
      + _log(message: str) void
      + _run_git_cmd(args: list, timeout: int, check: bool)
      + _run_gh_cmd(args: list, timeout: int)
    }

    class PRSkillModuleGlobals {
      <<module>>
      + GIT_PATH: str
      + GH_PATH: str
      + POLL_INTERVAL_SECONDS: int
      + POLL_MAX_ATTEMPTS: int
      + LOOP_STATE_FILENAME: str
      + ACTION_INSTRUCTIONS: str
      + RATE_LIMIT_INSTRUCTION: str
      + DEFAULT_VALIDATION_REVIEWER: str
      + print_json(data)
      + print_error(message: str)
    }

    PRSkillModuleGlobals --> ReviewManager : configures
    ReviewManager --> PRSkillModuleGlobals : uses_constants
    ReviewManager --> PRSkillModuleGlobals : uses_ACTION_INSTRUCTIONS
    ReviewManager --> PRSkillModuleGlobals : uses_RATE_LIMIT_INSTRUCTION
Loading

Flow diagram for rules and skill deployment locations and tool usage

flowchart LR
    subgraph Locations
        A[Project_root]
        B[Local_agent_dir<br/>.agent/]
        C[Global_rules_dir<br/>~/.gemini/rules]
        D[Global_skills_dir<br/>~/.gemini/antigravity/skills]
    end

    subgraph Rules_and_docs
        R0[rules_pr_standards]
        R1[rules_rules]
        W1[workflow_pr_review_cycle]
        K1[knowledge_base]
        S1[skill_doc_SKILL]
    end

    subgraph Skill_binary
        P1[pr_skill_py]
        G1[Git_binary_resolved]
        H1[gh_binary_resolved]
    end

    A --> B
    B --> R0
    B --> R1
    B --> W1
    B --> K1
    B --> S1

    C --> R0
    C --> R1
    D --> P1

    S1 -->|Path_examples| P1
    W1 -->|Uses_tool| P1
    K1 -->|References| R0

    R0 -->|Defines| Rloop[Loop_rule]
    R0 -->|Defines| Rfresh[Freshness]
    R0 -->|Defines| Rrate[Rate_limits]
    R0 -->|Defines| Rprohib[Prohibitions]

    R1 -->|Enforces| R0
    P1 -->|Implements| Rloop
    P1 -->|Implements| Rfresh
    P1 -->|Implements| Rrate
    P1 -->|Implements| Rprohib

    P1 --> G1
    P1 --> H1
Loading

File-Level Changes

Change Details Files
Harden pr_review skill execution, add crash‑resilient polling with resume support, and tighten next_step guidance for autonomous agents.
  • Resolve git and gh binary paths up front and route all subprocess calls through _run_git_cmd/_run_gh_cmd helpers for security and consistency.
  • Introduce persistent loop state (loop_state.json in agent-workspace) with save/load/clear helpers, and add interruptible sleep with audit heartbeats for long waits.
  • Refactor polling logic (_poll_for_main_reviewer) to save checkpoints each attempt, support resuming from a given attempt, and add a new resume_loop() command plus CLI subcommand.
  • Make trigger_review non‑blocking by removing in‑script polling and returning explicit WAIT + status instructions for the agent instead.
  • Reorganize check_status into smaller helpers for time handling and feedback analysis, and strengthen next_step text to enforce autonomy, freshness, completeness, rate‑limit handling, and explicit “do not merge/delete branches” prohibitions.
  • Adjust default polling interval/max attempts and improve error handling/logging for auth, repo detection, and fatal errors.
.agent/skills/pr_review/pr_skill.py
Document mandatory behavior rules, loop resilience, and new paths/usages for the PR review skill and workflow.
  • Update SKILL.md to reference global rules path (~/.gemini/rules/pr-standards.md), document mandatory behavior rules (autonomy, polling, freshness, completeness, quality, rate limits, prohibitions), describe loop resilience/state persistence, and switch all CLI examples to /.gemini/antigravity paths including the new resume command.
  • Update pr-review-cycle workflow to use a generic {PATH_TO_SKILLS} resolver, add explicit freshness/completeness requirements in the Analyze & Implement step, and point compliance to the global rules file (/.gemini/rules/pr-standards.md).
.agent/skills/pr_review/SKILL.md
.agent/workflows/pr-review-cycle.md
Tighten and expand PR standards and general rules to cover deployment discovery, freshness, rate‑limits, crash recovery, and prohibitions.
  • Add a Deployment Locations section defining how agents discover local vs global .agent/~/.gemini rules and skills, and clarify pr_skill.py as the primary tool with both local and global paths.
  • Clarify The Loop exit condition for rate‑limited scenarios, add Freshness and Rate Limits sections with mandatory pull/rebase, conflict resolution, and backoff guidance, and strengthen critical prohibitions with explicit “never merge/delete branches on your own; ask for human review.”
  • Add an explicit Crash Recovery rule instructing agents to use the resume command when a loop_state.json exists, and update mandates to reference skills/pr_review/pr_skill.py instead of hard‑coded local paths.
  • Introduce a new Compliance section in rules.md that ties agents to ~/.gemini/rules/pr-standards.md and reiterates enforcement of push‑before‑trigger, loop, and prohibitions; renumber Context7 MCP accordingly.
.agent/rules/pr-standards.md
.agent/rules/rules.md
Align knowledge‑base references and tests with the new PR review skill behavior.
  • Update knowledge-base.md to describe discovery of rules and skills in local .agent vs global ~/.gemini locations and to point to the updated pr-standards location for polling behavior.
  • Add a minimal unittest-based placeholder test to ensure CI has at least one passing test under pytest.
.agent/knowledge-base.md
tests/test_pr_skill.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 security issues, 1 other issue, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • In trigger_review, the wait_seconds parameter is now effectively ignored and the next_step string hardcodes WAIT 180 seconds; consider either wiring the message to the actual wait_seconds value or deprecating the argument to avoid confusing callers.
  • There are duplicate self._log(f"State verified: {msg}") calls in trigger_review; you can drop one to avoid redundant audit logging noise.
  • Resolving GIT_PATH and GH_PATH at import time and exiting if git is missing can make the module unusable in contexts that only need non-git functionality (e.g., help/metadata); consider deferring these checks to first use or to main() to keep import side effects minimal.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `trigger_review`, the `wait_seconds` parameter is now effectively ignored and the `next_step` string hardcodes `WAIT 180 seconds`; consider either wiring the message to the actual `wait_seconds` value or deprecating the argument to avoid confusing callers.
- There are duplicate `self._log(f"State verified: {msg}")` calls in `trigger_review`; you can drop one to avoid redundant audit logging noise.
- Resolving `GIT_PATH` and `GH_PATH` at import time and exiting if `git` is missing can make the module unusable in contexts that only need non-git functionality (e.g., help/metadata); consider deferring these checks to first use or to `main()` to keep import side effects minimal.

## Individual Comments

### Comment 1
<location> `tests/test_pr_skill.py:2-7` </location>
<code_context>
+
+import unittest
+
+class TestPlaceholder(unittest.TestCase):
+    def test_ci_pass(self):
+        """Dummy test to ensure pytest collects at least one item and exits with 0."""
+        self.assertTrue(True)
</code_context>

<issue_to_address>
**issue (testing):** Replace placeholder test with focused tests that cover the new PR loop, crash recovery, and next_step behavior.

The current dummy test only satisfies CI and doesn’t validate any of the new behavior in `pr_skill.py`. Given the complexity of the loop logic and `next_step` messaging, this file should include targeted tests, e.g.:

- **Loop state & resume**: Unit-test `_save_loop_state`, `_load_loop_state`, `_clear_loop_state` using a temp workspace, and `resume_loop()` paths for: no state, state with remaining attempts (correct `start_attempt`/`max_attempts`), and exhausted attempts (expected `resumed` response and guidance).

- **Polling & interruption**: Test `_poll_for_main_reviewer` by mocking `check_status` and `_interruptible_sleep` to avoid real waits. Cover approval/changes-requested paths (loop exits and `_clear_loop_state` called), timeout (correct `polling_timeout` structure and `next_step`), and `KeyboardInterrupt` (sets `polling_interrupted` and “resume” guidance).

- **`check_status` & `next_step`**: With a fake/mocked repo/PR, assert detection of `CHANGES_REQUESTED`, approved-with-vs-without-extra-feedback, comments after approval, and the no-new-feedback case, each yielding the correct `next_step` text.

- **Git/GH helpers**: Test `_run_git_cmd` / `_run_gh_cmd` by mocking `subprocess.run` and `GIT_PATH`/`GH_PATH`, including the error path when `GH_PATH` is missing.

These will ensure the crash-resilient loop behavior and nuanced autonomy-related `next_step` messages are covered by tests, rather than relying on a placeholder.
</issue_to_address>

### Comment 2
<location> `.agent/skills/pr_review/pr_skill.py:142-148` </location>
<code_context>
        return subprocess.run(
            [GIT_PATH] + args,
            capture_output=True,
            text=True,
            check=check,
            timeout=timeout,
        )
</code_context>

<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location> `.agent/skills/pr_review/pr_skill.py:155-161` </location>
<code_context>
        return subprocess.run(
            [GH_PATH] + args,
            capture_output=True,
            text=True,
            check=True,
            timeout=timeout,
        )
</code_context>

<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 2 to 7
import unittest

class TestPlaceholder(unittest.TestCase):
def test_ci_pass(self):
"""Dummy test to ensure pytest collects at least one item and exits with 0."""
self.assertTrue(True)
Copy link

Choose a reason for hiding this comment

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

issue (testing): Replace placeholder test with focused tests that cover the new PR loop, crash recovery, and next_step behavior.

The current dummy test only satisfies CI and doesn’t validate any of the new behavior in pr_skill.py. Given the complexity of the loop logic and next_step messaging, this file should include targeted tests, e.g.:

  • Loop state & resume: Unit-test _save_loop_state, _load_loop_state, _clear_loop_state using a temp workspace, and resume_loop() paths for: no state, state with remaining attempts (correct start_attempt/max_attempts), and exhausted attempts (expected resumed response and guidance).

  • Polling & interruption: Test _poll_for_main_reviewer by mocking check_status and _interruptible_sleep to avoid real waits. Cover approval/changes-requested paths (loop exits and _clear_loop_state called), timeout (correct polling_timeout structure and next_step), and KeyboardInterrupt (sets polling_interrupted and “resume” guidance).

  • check_status & next_step: With a fake/mocked repo/PR, assert detection of CHANGES_REQUESTED, approved-with-vs-without-extra-feedback, comments after approval, and the no-new-feedback case, each yielding the correct next_step text.

  • Git/GH helpers: Test _run_git_cmd / _run_gh_cmd by mocking subprocess.run and GIT_PATH/GH_PATH, including the error path when GH_PATH is missing.

These will ensure the crash-resilient loop behavior and nuanced autonomy-related next_step messages are covered by tests, rather than relying on a placeholder.

Comment on lines +142 to +148
return subprocess.run(
[GIT_PATH] + args,
capture_output=True,
text=True,
check=check,
timeout=timeout,
)
Copy link

Choose a reason for hiding this comment

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

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines +155 to +161
return subprocess.run(
[GH_PATH] + args,
capture_output=True,
text=True,
check=True,
timeout=timeout,
)
Copy link

Choose a reason for hiding this comment

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

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly enhances the PR review skill by making it non-blocking, adding crash-resilience, and enforcing stricter autonomous agent behaviors through updated documentation and more detailed instructions. The refactoring of the check_status method is a notable improvement in code structure and readability. I've provided a few specific comments on minor issues related to configuration consistency, logging clarity, and code duplication. Overall, these changes are a great step forward for the agent's capabilities.

Comment on lines 51 to 61
try:
POLL_INTERVAL_SECONDS = max(
int(os.environ.get("PR_REVIEW_POLL_INTERVAL", "30")), 1)
except ValueError:
POLL_INTERVAL_SECONDS = 30
POLL_INTERVAL_SECONDS = 120

try:
POLL_MAX_ATTEMPTS = max(
int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", "20")), 1)
int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", "12")), 1)
except ValueError:
POLL_MAX_ATTEMPTS = 20

Choose a reason for hiding this comment

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

medium

The default values for polling configuration read from environment variables are inconsistent with the fallback values in the except blocks.

  • PR_REVIEW_POLL_INTERVAL: The default from os.environ.get is '30', but the fallback is 120.
  • PR_REVIEW_POLL_MAX_ATTEMPTS: The default is '12', but the fallback is 20.

To ensure predictable default behavior, the default values in os.environ.get should match their corresponding fallback values.

Suggested change
try:
POLL_INTERVAL_SECONDS = max(
int(os.environ.get("PR_REVIEW_POLL_INTERVAL", "30")), 1)
except ValueError:
POLL_INTERVAL_SECONDS = 30
POLL_INTERVAL_SECONDS = 120
try:
POLL_MAX_ATTEMPTS = max(
int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", "20")), 1)
int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", "12")), 1)
except ValueError:
POLL_MAX_ATTEMPTS = 20
try:
POLL_INTERVAL_SECONDS = max(
int(os.environ.get("PR_REVIEW_POLL_INTERVAL", "120")), 1)
except ValueError:
POLL_INTERVAL_SECONDS = 120
try:
POLL_MAX_ATTEMPTS = max(
int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", "20")), 1)
except ValueError:
POLL_MAX_ATTEMPTS = 20

Comment on lines +467 to +469
self._log(
f"Poll attempt {attempt} (resume+{i + 1}) / max {POLL_MAX_ATTEMPTS}..."
)

Choose a reason for hiding this comment

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

medium

The log message format is a bit confusing, especially the (resume+{i + 1}) part. A simpler format showing the current attempt out of the total maximum attempts would be clearer and more maintainable.

                self._log(f"Poll attempt {attempt}/{POLL_MAX_ATTEMPTS}...")

Comment on lines 612 to 613

# Capture start time for status check
start_time = datetime.now(timezone.utc)
self._log(f"State verified: {msg}")

Choose a reason for hiding this comment

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

medium

This log message on line 613 is a duplicate of the one on line 611. This appears to be a copy-paste error from the refactoring and should be removed for code clarity.

Comment on lines +724 to +726
if created_at_val.endswith("Z"):
created_at_val = created_at_val[:-1] + "+00:00"
dt_val = datetime.fromisoformat(created_at_val)

Choose a reason for hiding this comment

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

medium

This date parsing logic for handling the 'Z' suffix is also present in _get_since_dt. To improve maintainability, consider extracting it into a shared helper function.

Additionally, using replace('Z', '+00:00') is more robust and was used in the previous version of the code. The current endswith('Z') check is a slight regression.

                        dt_val = datetime.fromisoformat(
                            created_at_val.replace("Z", "+00:00")
                        )

@sheepdestroyer
Copy link
Owner Author

/gemini review

@sheepdestroyer
Copy link
Owner Author

@coderabbitai review

@sheepdestroyer
Copy link
Owner Author

@sourcery-ai review

@sheepdestroyer
Copy link
Owner Author

/review

@sheepdestroyer
Copy link
Owner Author

@ellipsis review this

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

✅ Actions performed

Review triggered.

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

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed 02b64ee in 17 seconds. Click for details.
  • Reviewed 156 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_ixzJhbwqH93JSU4y

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

This commit fixes the style issues introduced in 02b64ee according to the output
from Autopep8, Black, isort, Ruff Formatter and Yapf.

Details: #8
@qodo-code-review
Copy link

Persistent review updated to latest commit 02b64ee

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 3, 2026

Reviewer's Guide

Strengthens the PR review skill’s autonomy loop, safety, and crash‑recovery behavior, and aligns standards/workflow/docs with new mandatory rules for autonomy, freshness, completeness, rate limits, and prohibitions on autonomous merges/branch operations.

Class diagram for updated ReviewManager and helper behavior

classDiagram
    class ReviewManager {
        - token : str
        - repo : object
        - workspace : str
        - loop_state_file : str
        + __init__()
        + _mask_token(text : str) str
        + _ensure_workspace() void
        + _detect_repo() void
        + _verify_clean_git() tuple
        + _check_local_state() tuple
        + safe_push() dict
        + _poll_for_main_reviewer(pr_number : int, since_iso : str, validation_reviewer : str, max_attempts : int, poll_interval : int, start_attempt : int) dict
        + resume_loop() dict
        + trigger_review(pr_number : int, wait_seconds : int, validation_reviewer : str) dict
        + check_status(pr_number : int, since_iso : str, return_data : bool, validation_reviewer : str) dict
        + _save_loop_state(pr_number : int, since_iso : str, validation_reviewer : str, poll_attempt : int) void
        + _load_loop_state() dict
        + _clear_loop_state() void
        + _interruptible_sleep(total_seconds : int, heartbeat_interval : int) void
        + _get_since_dt(since_iso : str) datetime
        + _analyze_main_reviewer(reviews : list, validation_reviewer : str) tuple
        + _check_new_main_reviewer_comments(new_feedback : list, validation_reviewer : str, last_approval_dt : datetime) bool
        + _determine_next_step(new_feedback : list, validation_reviewer : str, main_reviewer_state : str, has_changes_requested : bool, has_new_main_comments : bool) str
        + _fetch_issue_comments(pr_number : int, since_dt : datetime) list
        + _fetch_review_comments(pr : object, since_dt : datetime) list
        + _fetch_reviews(pr : object, since_dt : datetime) tuple
        + _log(message : str) void
        + _run_git_cmd(args : list, timeout : int, check : bool) CompletedProcess
        + _run_gh_cmd(args : list, timeout : int) CompletedProcess
        + _get_aware_utc_datetime(dt_obj : datetime) datetime
    }

    class GitHubAPI {
        <<external>>
        + get_pull(pr_number : int) object
        + get_issue(pr_number : int) object
        + get_reviews() list
        + get_review_comments() list
        + get_comments() list
    }

    class GitCLI {
        <<external>>
        + rev_parse_show_toplevel()
        + status_porcelain()
        + fetch()
        + push()
        + rev_list_counts()
    }

    class GhCLI {
        <<external>>
        + auth_token()
        + repo_view()
    }

    class FileSystem {
        <<external>>
        + write_loop_state()
        + read_loop_state()
        + delete_loop_state()
    }

    ReviewManager --> GitHubAPI : uses
    ReviewManager --> GitCLI : uses via _run_git_cmd
    ReviewManager --> GhCLI : uses via _run_gh_cmd
    ReviewManager --> FileSystem : uses for loop_state.json
Loading

File-Level Changes

Change Details Files
Harden PR review skill behavior, introduce secure git/gh wrappers, and add loop crash-recovery with a resume command.
  • Resolve git and gh binary paths once and wrap all invocations in helper methods that enforce timeouts and consistent error handling.
  • Increase default polling interval and reduce max attempts to be less aggressive by default.
  • Introduce an agent workspace loop_state.json file with save/load/clear helpers plus interruptible sleep to support crash-safe, resumable polling.
  • Refactor status-check logic into smaller helpers for timestamp parsing, main-reviewer analysis, comment detection after approval, and next_step generation with stricter instructions (autonomy, freshness, rate limits, and explicit no-merge guidance).
  • Change trigger_review to be non-blocking (no internal polling) and instead return WAIT/next_step guidance, and add a new resume subcommand that restarts polling from the last saved attempt.
  • Improve error logging formatting and ensure JSON output is always structured for both success and error paths.
.agent/skills/pr_review/pr_skill.py
Document mandatory behavior rules, loop resilience, and new path conventions for the PR review skill and workflow.
  • Update SKILL docs to reference global rules path (~/.gemini/rules/pr-standards.md), describe mandatory behavior rules (autonomy, polling, freshness, completeness, quality, rate limits, prohibitions), and document the non-blocking loop plus resume usage.
  • Adjust workflow guide to use parameterized skill paths (local .agent vs global ~/.gemini/antigravity), add freshness/completeness requirements in the analyze step, and point compliance to the global rules file.
  • Update knowledge-base references and tool paths to describe local vs global discovery for rules and skills.
.agent/skills/pr_review/SKILL.md
.agent/workflows/pr-review-cycle.md
.agent/knowledge-base.md
Tighten global PR standards and general rules around deployment paths, freshness, rate limits, and prohibitions.
  • Add a deployment locations section defining discovery order for local vs global .agent/~/.gemini rules and skills.
  • Clarify Loop Rule exit conditions for rate-limited scenarios and mandate using pr_skill.py as the primary tool with explicit local/global paths.
  • Add new sections on Freshness and Rate Limits, including mandatory pull/rebase, conflict resolution, and backoff guidance, and strengthen prohibitions against autonomous merges/branch operations.
  • Introduce a Compliance section in general rules that points at pr-standards.md and highlights enforcement of Push Before Trigger, Loop Rule, and prohibitions, renumbering the existing sections accordingly.
.agent/rules/pr-standards.md
.agent/rules/rules.md
Add a minimal test to exercise pr_skill behavior and ensure CI collects tests successfully.
  • Introduce a unittest module that imports ReviewManager from pr_skill via flexible sys.path setup for local vs CI environments.
  • Test next_step text for waiting and changes-requested scenarios and lightly exercise loop-state saving and trigger_review next_step behavior (with heavy mocking to avoid real git/GitHub).
tests/test_pr_skill.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 security issues, 3 other issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • The new tests/test_pr_skill.py has a couple of issues that will likely cause it to fail to run: the call mgr._save_loop_state(8, "polling") doesn’t match the method signature (needs pr_number, since_iso, validation_reviewer, poll_attempt), and the inner with patch("os.open")... block is mis-indented, which will raise an IndentationError.
  • In knowledge-base.md you now reference rules/pr-standards.md as the path while elsewhere (e.g., pr-standards.md, rules.md, pr-review-cycle.md) you normalize on .agent/... vs ~/.gemini/...; consider making this reference consistent with the deployment-location rules you just introduced.
  • In SKILL.md the Loop Resilience section still describes the skill as "non-blocking and stateless" even though loop state is now persisted in loop_state.json; it would be clearer to adjust this wording to reflect the new persistence behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `tests/test_pr_skill.py` has a couple of issues that will likely cause it to fail to run: the call `mgr._save_loop_state(8, "polling")` doesn’t match the method signature (needs `pr_number, since_iso, validation_reviewer, poll_attempt`), and the inner `with patch("os.open")...` block is mis-indented, which will raise an `IndentationError`.
- In `knowledge-base.md` you now reference `rules/pr-standards.md` as the path while elsewhere (e.g., `pr-standards.md`, `rules.md`, `pr-review-cycle.md`) you normalize on `.agent/...` vs `~/.gemini/...`; consider making this reference consistent with the deployment-location rules you just introduced.
- In `SKILL.md` the Loop Resilience section still describes the skill as "non-blocking and stateless" even though loop state is now persisted in `loop_state.json`; it would be clearer to adjust this wording to reflect the new persistence behavior.

## Individual Comments

### Comment 1
<location> `.agent/skills/pr_review/pr_skill.py:318` </location>
<code_context>
-            )
+            # Fetch latest state from remote for accurate comparison
+            # Suppress stdout to avoid polluting structured output; inherit stderr so prompts/hangs remain visible
+            self._run_git_cmd(["fetch"], timeout=GIT_FETCH_TIMEOUT)

             # Get current branch
</code_context>

<issue_to_address>
**issue (bug_risk):** Using _run_git_cmd for `git fetch` now captures stderr instead of inheriting it, which changes interactive behavior.

Previously `git fetch` inherited stderr, so prompts and errors were visible, whereas `_run_git_cmd` uses `capture_output=True`, suppressing both stdout and stderr. This can hide auth prompts or failures and make hangs hard to debug, which conflicts with the intent to keep stderr visible. Consider adding a way for `_run_git_cmd` to control `capture_output`/`stderr`, or special-casing `git fetch` to preserve stderr inheritance.
</issue_to_address>

### Comment 2
<location> `tests/test_pr_skill.py:53-62` </location>
<code_context>
+        )
+        self.assertIn("CRITICAL: Changes requested", step)
+
+    def test_save_loop_state(self):
+        """Test state saving logic."""
+        mgr = ReviewManager(self.mock_repo)
+        with patch("builtins.open", mock_open()) as mock_file:
+            # We also need to mock os.open for the secure write
+             with patch("os.open") as mock_os_open, \
+                  patch("os.fdopen") as mock_fdopen:
+                mgr._save_loop_state(8, "polling")
+                # Verification is tricky due to secure write implementation details, 
+                # but we ensure it doesn't crash.
+                pass
+
+    def test_trigger_review_next_step(self):
</code_context>

<issue_to_address>
**issue (testing):** Fix indentation and argument mismatch in `test_save_loop_state`, and add an assertion so the test meaningfully validates behavior.

This test has three issues that prevent it from being useful:

1. **Indentation**: Remove the extra leading space before `with patch("os.open")` so it aligns under the `with patch("builtins.open", ...)` block and avoids an `IndentationError`.

2. **Argument mismatch**: `_save_loop_state` expects `(self, pr_number, since_iso, validation_reviewer, poll_attempt)`, but the test calls `mgr._save_loop_state(8, "polling")`. Update the call to pass all required arguments (e.g. `mgr._save_loop_state(8, "2024-01-01T00:00:00Z", DEFAULT_VALIDATION_REVIEWER, 1)`) so the secure write path is actually exercised.

3. **Missing assertion**: Replace the `pass` with at least one concrete assertion, such as verifying the mocks were called (`mock_os_open.assert_called_once()` or checking a mocked `json.dump` call), so the test checks behavior rather than just running code.
</issue_to_address>

### Comment 3
<location> `tests/test_pr_skill.py:28-37` </location>
<code_context>
+    def test_determine_next_step_waiting(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for additional `next_step` decision paths (approval with feedback, comments after approval, rate-limit instructions).

Current `_determine_next_step` tests only cover waiting for feedback and a generic `CHANGES_REQUESTED` case. Given the branching and critical `ACTION_INSTRUCTIONS` / `RATE_LIMIT_INSTRUCTION` text, please add coverage for at least:

- Main reviewer approved, no other feedback: assert the `next_step` text includes the explicit STOP / “DO NOT MERGE AUTONOMOUSLY” and no-merge/no-delete instructions.
- Main reviewer approved with additional feedback from others: ensure `next_step` emphasizes addressing remaining feedback rather than stopping.
- New comments from main reviewer after approval: assert the “New comments from {validation_reviewer} after approval” wording and that the full `ACTION_INSTRUCTIONS` + `RATE_LIMIT_INSTRUCTION` content remains.
- Changes requested: beyond the existing substring check, verify the rate-limit instruction appears when applicable.

This will better lock in the expected autonomous-agent behavior and protect these instructions during future refactors.

Suggested implementation:

```python
class TestReviewManager(unittest.TestCase):
    def setUp(self):
        self.mock_repo = MagicMock()
        self.mock_gh = MagicMock()
        self.mock_gh.get_repo.return_value = self.mock_repo

        # Mock global vars GIT_PATH/GH_PATH in pr_skill module if needed, 
        # but since we mock subprocss it might be fine.

    def test_determine_next_step_waiting(self):
        """Test next_step when waiting for feedback."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="PENDING",
            has_changes_requested=False,
            has_new_main_comments=False
        )
        self.assertIn("WAIT 30 seconds", step)

    def test_determine_next_step_main_approved_no_other_feedback(self):
        """Main reviewer approved, no other feedback: enforce STOP / do-not-merge instructions."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=False,
        )

        # Explicit STOP / do-not-merge autonomy and no-merge/no-delete guard rails.
        self.assertIn("DO NOT MERGE AUTONOMOUSLY", step)
        self.assertIn("do not merge this PR yourself", step.lower())
        self.assertIn("do not delete the branch", step.lower())

    def test_determine_next_step_main_approved_with_additional_feedback(self):
        """
        Main reviewer approved but there is additional feedback from others:
        next_step should emphasize addressing remaining feedback rather than stopping.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Please update the docs before merging."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=False,
        )

        # Should prioritize addressing remaining feedback.
        self.assertIn("feedback", step.lower())
        self.assertIn("address", step.lower())
        # When there is still feedback, we should not be in the hard STOP / autonomous-merge guard rail flow.
        self.assertNotIn("DO NOT MERGE AUTONOMOUSLY", step)

    def test_determine_next_step_new_comments_from_main_after_approval(self):
        """
        New comments from the main reviewer after approval:
        ensure specific wording and that full action / rate-limit instructions are preserved.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Follow-up comment from main reviewer."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=True,
        )

        # Wording should call out new comments from the validation reviewer after approval.
        self.assertIn("New comments from gemini-bot after approval", step)

        # Full ACTION_INSTRUCTIONS and RATE_LIMIT_INSTRUCTION must still be present.
        self.assertIn(ACTION_INSTRUCTIONS.strip(), step)
        self.assertIn(RATE_LIMIT_INSTRUCTION.strip(), step)

    def test_determine_next_step_changes_requested_includes_rate_limit(self):
        """
        When changes are requested, ensure the rate-limit instruction is present
        in addition to the generic CHANGES_REQUESTED messaging.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Please refactor this function."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="CHANGES_REQUESTED",
            has_changes_requested=True,
            has_new_main_comments=False,
        )

        # Existing CHANGES_REQUESTED behavior plus explicit rate-limit instructions.
        self.assertIn("CHANGES_REQUESTED", step)
        self.assertIn(RATE_LIMIT_INSTRUCTION.strip(), step)

```

To fully wire these tests in, you may also need to:

1. Ensure `ACTION_INSTRUCTIONS` and `RATE_LIMIT_INSTRUCTION` are imported into `tests/test_pr_skill.py`, for example:
   - `from pr_skill import ReviewManager, ACTION_INSTRUCTIONS, RATE_LIMIT_INSTRUCTION`
   If `ReviewManager` is already imported elsewhere in this file, extend that import rather than duplicating it.

2. Confirm the exact substrings used in `_determine_next_step`:
   - If the "DO NOT MERGE AUTONOMOUSLY", "do not merge this PR yourself", or "do not delete the branch" texts differ slightly in the implementation, adjust the asserted substrings to match the real messages while preserving the intent (explicit STOP and no-merge/no-delete instructions).
   - Likewise, if the implementation’s wording for “New comments from {validation_reviewer} after approval” varies, update the expected string accordingly (but keep the test verifying that this reviewer-specific text is present).

3. If `_determine_next_step` normalizes whitespace (e.g., multi-line constants), the `.strip()` around `ACTION_INSTRUCTIONS` / `RATE_LIMIT_INSTRUCTION` might need to be updated (e.g., by asserting on a distinctive phrase from each constant instead of the full string).
</issue_to_address>

### Comment 4
<location> `.agent/skills/pr_review/pr_skill.py:137-143` </location>
<code_context>
        return subprocess.run(
            [GIT_PATH] + args,
            capture_output=True,
            text=True,
            check=check,
            timeout=timeout,
        )
</code_context>

<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 5
<location> `.agent/skills/pr_review/pr_skill.py:150-156` </location>
<code_context>
        return subprocess.run(
            [GH_PATH] + args,
            capture_output=True,
            text=True,
            check=True,
            timeout=timeout,
        )
</code_context>

<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

)
# Fetch latest state from remote for accurate comparison
# Suppress stdout to avoid polluting structured output; inherit stderr so prompts/hangs remain visible
self._run_git_cmd(["fetch"], timeout=GIT_FETCH_TIMEOUT)
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Using _run_git_cmd for git fetch now captures stderr instead of inheriting it, which changes interactive behavior.

Previously git fetch inherited stderr, so prompts and errors were visible, whereas _run_git_cmd uses capture_output=True, suppressing both stdout and stderr. This can hide auth prompts or failures and make hangs hard to debug, which conflicts with the intent to keep stderr visible. Consider adding a way for _run_git_cmd to control capture_output/stderr, or special-casing git fetch to preserve stderr inheritance.

Comment on lines 53 to 62
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, \
patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
Copy link

Choose a reason for hiding this comment

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

issue (testing): Fix indentation and argument mismatch in test_save_loop_state, and add an assertion so the test meaningfully validates behavior.

This test has three issues that prevent it from being useful:

  1. Indentation: Remove the extra leading space before with patch("os.open") so it aligns under the with patch("builtins.open", ...) block and avoids an IndentationError.

  2. Argument mismatch: _save_loop_state expects (self, pr_number, since_iso, validation_reviewer, poll_attempt), but the test calls mgr._save_loop_state(8, "polling"). Update the call to pass all required arguments (e.g. mgr._save_loop_state(8, "2024-01-01T00:00:00Z", DEFAULT_VALIDATION_REVIEWER, 1)) so the secure write path is actually exercised.

  3. Missing assertion: Replace the pass with at least one concrete assertion, such as verifying the mocks were called (mock_os_open.assert_called_once() or checking a mocked json.dump call), so the test checks behavior rather than just running code.

Comment on lines 28 to 37
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False
)
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Add tests for additional next_step decision paths (approval with feedback, comments after approval, rate-limit instructions).

Current _determine_next_step tests only cover waiting for feedback and a generic CHANGES_REQUESTED case. Given the branching and critical ACTION_INSTRUCTIONS / RATE_LIMIT_INSTRUCTION text, please add coverage for at least:

  • Main reviewer approved, no other feedback: assert the next_step text includes the explicit STOP / “DO NOT MERGE AUTONOMOUSLY” and no-merge/no-delete instructions.
  • Main reviewer approved with additional feedback from others: ensure next_step emphasizes addressing remaining feedback rather than stopping.
  • New comments from main reviewer after approval: assert the “New comments from {validation_reviewer} after approval” wording and that the full ACTION_INSTRUCTIONS + RATE_LIMIT_INSTRUCTION content remains.
  • Changes requested: beyond the existing substring check, verify the rate-limit instruction appears when applicable.

This will better lock in the expected autonomous-agent behavior and protect these instructions during future refactors.

Suggested implementation:

class TestReviewManager(unittest.TestCase):
    def setUp(self):
        self.mock_repo = MagicMock()
        self.mock_gh = MagicMock()
        self.mock_gh.get_repo.return_value = self.mock_repo

        # Mock global vars GIT_PATH/GH_PATH in pr_skill module if needed, 
        # but since we mock subprocss it might be fine.

    def test_determine_next_step_waiting(self):
        """Test next_step when waiting for feedback."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="PENDING",
            has_changes_requested=False,
            has_new_main_comments=False
        )
        self.assertIn("WAIT 30 seconds", step)

    def test_determine_next_step_main_approved_no_other_feedback(self):
        """Main reviewer approved, no other feedback: enforce STOP / do-not-merge instructions."""
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=[],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=False,
        )

        # Explicit STOP / do-not-merge autonomy and no-merge/no-delete guard rails.
        self.assertIn("DO NOT MERGE AUTONOMOUSLY", step)
        self.assertIn("do not merge this PR yourself", step.lower())
        self.assertIn("do not delete the branch", step.lower())

    def test_determine_next_step_main_approved_with_additional_feedback(self):
        """
        Main reviewer approved but there is additional feedback from others:
        next_step should emphasize addressing remaining feedback rather than stopping.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Please update the docs before merging."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=False,
        )

        # Should prioritize addressing remaining feedback.
        self.assertIn("feedback", step.lower())
        self.assertIn("address", step.lower())
        # When there is still feedback, we should not be in the hard STOP / autonomous-merge guard rail flow.
        self.assertNotIn("DO NOT MERGE AUTONOMOUSLY", step)

    def test_determine_next_step_new_comments_from_main_after_approval(self):
        """
        New comments from the main reviewer after approval:
        ensure specific wording and that full action / rate-limit instructions are preserved.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Follow-up comment from main reviewer."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="APPROVED",
            has_changes_requested=False,
            has_new_main_comments=True,
        )

        # Wording should call out new comments from the validation reviewer after approval.
        self.assertIn("New comments from gemini-bot after approval", step)

        # Full ACTION_INSTRUCTIONS and RATE_LIMIT_INSTRUCTION must still be present.
        self.assertIn(ACTION_INSTRUCTIONS.strip(), step)
        self.assertIn(RATE_LIMIT_INSTRUCTION.strip(), step)

    def test_determine_next_step_changes_requested_includes_rate_limit(self):
        """
        When changes are requested, ensure the rate-limit instruction is present
        in addition to the generic CHANGES_REQUESTED messaging.
        """
        mgr = ReviewManager(self.mock_repo)
        step = mgr._determine_next_step(
            new_feedback=["Please refactor this function."],
            validation_reviewer="gemini-bot",
            main_reviewer_state="CHANGES_REQUESTED",
            has_changes_requested=True,
            has_new_main_comments=False,
        )

        # Existing CHANGES_REQUESTED behavior plus explicit rate-limit instructions.
        self.assertIn("CHANGES_REQUESTED", step)
        self.assertIn(RATE_LIMIT_INSTRUCTION.strip(), step)

To fully wire these tests in, you may also need to:

  1. Ensure ACTION_INSTRUCTIONS and RATE_LIMIT_INSTRUCTION are imported into tests/test_pr_skill.py, for example:

    • from pr_skill import ReviewManager, ACTION_INSTRUCTIONS, RATE_LIMIT_INSTRUCTION
      If ReviewManager is already imported elsewhere in this file, extend that import rather than duplicating it.
  2. Confirm the exact substrings used in _determine_next_step:

    • If the "DO NOT MERGE AUTONOMOUSLY", "do not merge this PR yourself", or "do not delete the branch" texts differ slightly in the implementation, adjust the asserted substrings to match the real messages while preserving the intent (explicit STOP and no-merge/no-delete instructions).
    • Likewise, if the implementation’s wording for “New comments from {validation_reviewer} after approval” varies, update the expected string accordingly (but keep the test verifying that this reviewer-specific text is present).
  3. If _determine_next_step normalizes whitespace (e.g., multi-line constants), the .strip() around ACTION_INSTRUCTIONS / RATE_LIMIT_INSTRUCTION might need to be updated (e.g., by asserting on a distinctive phrase from each constant instead of the full string).

Comment on lines +137 to +143
return subprocess.run(
[GIT_PATH] + args,
capture_output=True,
text=True,
check=check,
timeout=timeout,
)
Copy link

Choose a reason for hiding this comment

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

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines +150 to +156
return subprocess.run(
[GH_PATH] + args,
capture_output=True,
text=True,
check=True,
timeout=timeout,
)
Copy link

Choose a reason for hiding this comment

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

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.agent/skills/pr_review/pr_skill.py (1)

437-505: ⚠️ Potential issue | 🟠 Major

Resume polling skips waits due to an off-by-context check.

After resume, attempt can exceed max_attempts, so attempt < max_attempts becomes false early and the loop can hammer the API without sleeping.

✅ Suggested fix
-                if attempt < max_attempts:
+                if i < max_attempts - 1:
                     self._log(
                         f"Main reviewer has not responded yet. Waiting {poll_interval}s before next poll..."
                     )
                     self._interruptible_sleep(poll_interval)
🤖 Fix all issues with AI agents
In @.agent/skills/pr_review/pr_skill.py:
- Around line 628-641: The returned next_step message is hardcoded to "WAIT 180
seconds" instead of using the actual wait_seconds value; update the return in
the method containing the self._log(...) and the dict response (the block that
returns status "triggered") to interpolate or format the wait_seconds variable
(used for the --wait argument) into next_step so it instructs "WAIT
{wait_seconds} seconds (for bots to run), then run 'status'..." and ensure the
variable referenced matches the name wait_seconds used earlier in this scope.
🧹 Nitpick comments (3)
.agent/skills/pr_review/pr_skill.py (3)

317-323: Remove the duplicated comment to reduce noise.

The “Fetch latest state…” comment is repeated twice back-to-back.

♻️ Suggested edit
-            # Fetch latest state from remote for accurate comparison
-            # Suppress stdout to avoid polluting structured output; inherit stderr so prompts/hangs remain visible
-            # Fetch latest state from remote for accurate comparison
-            # Suppress stdout to avoid polluting structured output; inherit stderr so prompts/hangs remain visible
+            # Fetch latest state from remote for accurate comparison
+            # Suppress stdout to avoid polluting structured output; inherit stderr so prompts/hangs remain visible

611-613: Remove the duplicated “State verified” log.

This line is logged twice in a row, which adds noise.

♻️ Suggested edit
-        self._log(f"State verified: {msg}")
-
-        self._log(f"State verified: {msg}")
+        self._log(f"State verified: {msg}")

740-778: Use the configured poll interval in the WAIT instruction.

The message currently hardcodes 30 seconds, which can diverge from the configured poll interval.

♻️ Suggested tweak
-            return f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). WAIT 30 seconds, then run 'status' to check again. Be autonomous, don't stop the cycle. You must regularly poll for the main-reviewer's review until it eventually gets posted, then continue."
+            return f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). WAIT {POLL_INTERVAL_SECONDS} seconds, then run 'status' to check again. Be autonomous, don't stop the cycle. You must regularly poll for the main-reviewer's review until it eventually gets posted, then continue."

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.agent/skills/pr_review/pr_skill.py (1)

401-402: ⚠️ Potential issue | 🟡 Minor

Add next_step to timeout error response for schema consistency.

The TimeoutExpired handler returns a dict without next_step, while other error paths include it. This forces callers to handle missing keys.

🧩 Suggested fix
         except subprocess.TimeoutExpired:
-            return {"status": "error", "message": "Git upstream check timed out."}
+            return {
+                "status": "error",
+                "message": "Git upstream check timed out.",
+                "next_step": "Check git connectivity and retry safe_push.",
+            }
🤖 Fix all issues with AI agents
In `@tests/test_pr_skill.py`:
- Around line 37-48: The test calls ReviewManager(self.mock_repo) but
ReviewManager.__init__ takes no repo argument; remove the arg and either set up
required internals on the instance after construction or patch init helpers:
instantiate with mgr = ReviewManager(), then assign or mock attributes (e.g.
mgr.repo = self.mock_repo) or patch/patch.object the initialization helpers
(_detect_repo, _ensure_workspace, GitHub client creation) so tests don't hit
real initialization; update test_determine_next_step_waiting to construct
ReviewManager without args and configure needed attributes/mocks before calling
_determine_next_step.
- Around line 62-71: The test calls ReviewManager._save_loop_state with two args
but the method requires four; update the test to call _save_loop_state with the
full signature: pass pr_number (8), since_iso (a valid ISO string, e.g.
"2020-01-01T00:00:00Z"), validation_reviewer (None or an empty string as
appropriate for your code), and poll_attempt (an int like 1), so call
ReviewManager._save_loop_state(8, "2020-01-01T00:00:00Z", None, 1) in the test
to match the required parameters of ReviewManager._save_loop_state.
🧹 Nitpick comments (4)
.agent/skills/pr_review/pr_skill.py (2)

44-54: Align poll constant fallbacks with their defaults.

The normal path uses "30" / "12" as defaults, but the ValueError fallback uses 120 / 20. A malformed environment variable will silently switch to very different cadence values rather than using the same defaults.

♻️ Suggested fix (single source of truth)
+DEFAULT_POLL_INTERVAL_SECONDS = 30
+DEFAULT_POLL_MAX_ATTEMPTS = 12
+
 # Polling constants for review feedback
 # Configurable via environment variables
 try:
     POLL_INTERVAL_SECONDS = max(
-        int(os.environ.get("PR_REVIEW_POLL_INTERVAL", "30")), 1)
+        int(os.environ.get("PR_REVIEW_POLL_INTERVAL", str(DEFAULT_POLL_INTERVAL_SECONDS))), 1)
 except ValueError:
-    POLL_INTERVAL_SECONDS = 120
+    POLL_INTERVAL_SECONDS = DEFAULT_POLL_INTERVAL_SECONDS
 
 try:
     POLL_MAX_ATTEMPTS = max(
-        int(os.environ.get("PR_REVIEW_POLL_INTERVAL", "12")), 1)
+        int(os.environ.get("PR_REVIEW_POLL_MAX_ATTEMPTS", str(DEFAULT_POLL_MAX_ATTEMPTS))), 1)
 except ValueError:
-    POLL_MAX_ATTEMPTS = 20
+    POLL_MAX_ATTEMPTS = DEFAULT_POLL_MAX_ATTEMPTS

973-976: Remove redundant traceback import.

traceback is already imported at Line 17, so the import on Line 974 is redundant.

🔧 Suggested fix
     except Exception as e:
         # Catch-all for unhandled exceptions to prevent raw tracebacks in JSON output
         # Log full traceback to stderr for debugging
         sys.stderr.write(f"CRITICAL ERROR: {e!s}\n")
-        import traceback
-
         traceback.print_exc(file=sys.stderr)
tests/test_pr_skill.py (2)

8-25: Consider using proper package structure instead of sys.path manipulation.

The sys.path.append approach is fragile and can break in different execution contexts. Consider:

  1. Adding an __init__.py to make it a proper package
  2. Using conftest.py with pytest to handle path setup
  3. Installing the package in editable mode (pip install -e .)

The CI failure (ModuleNotFoundError: No module named 'github') originates from pr_skill.py importing PyGithub at module level, which isn't installed in CI.


65-71: Unused mock variables and missing assertions.

mock_file, mock_os_open, and mock_fdopen are assigned but never used (Ruff F841). The test only verifies "doesn't crash" which provides limited value. Consider adding assertions to verify the state file is written correctly.

Comment on lines +37 to +48
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False,
)
self.assertIn("WAIT 30 seconds", step)
self.assertIn("Run 'status'", step)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ReviewManager.__init__ doesn't accept a repo argument.

ReviewManager() takes no constructor arguments (see pr_skill.py Line 88). Passing self.mock_repo will raise TypeError. You need to mock the initialization internals or patch the class attributes post-construction.

🐛 Suggested approach
+    `@patch.object`(ReviewManager, '__init__', lambda self: None)
     def test_determine_next_step_waiting(self):
         """Test next_step when waiting for feedback."""
-        mgr = ReviewManager(self.mock_repo)
+        mgr = ReviewManager()
+        mgr.repo = self.mock_repo
+        mgr.token = "fake-token"
+        mgr.workspace = "/tmp/test"
+        mgr.loop_state_file = "/tmp/test/loop_state.json"
         step = mgr._determine_next_step(

Or use @patch decorators to mock _detect_repo, _ensure_workspace, and the GitHub client initialization.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False,
)
self.assertIn("WAIT 30 seconds", step)
self.assertIn("Run 'status'", step)
`@patch.object`(ReviewManager, '__init__', lambda self: None)
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager()
mgr.repo = self.mock_repo
mgr.token = "fake-token"
mgr.workspace = "/tmp/test"
mgr.loop_state_file = "/tmp/test/loop_state.json"
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False,
)
self.assertIn("WAIT 30 seconds", step)
self.assertIn("Run 'status'", step)
🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 37 - 48, The test calls
ReviewManager(self.mock_repo) but ReviewManager.__init__ takes no repo argument;
remove the arg and either set up required internals on the instance after
construction or patch init helpers: instantiate with mgr = ReviewManager(), then
assign or mock attributes (e.g. mgr.repo = self.mock_repo) or patch/patch.object
the initialization helpers (_detect_repo, _ensure_workspace, GitHub client
creation) so tests don't hit real initialization; update
test_determine_next_step_waiting to construct ReviewManager without args and
configure needed attributes/mocks before calling _determine_next_step.

Comment on lines +62 to +71
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

_save_loop_state signature mismatch.

Line 68 calls _save_loop_state(8, "polling") but the method signature requires 4 arguments: (pr_number, since_iso, validation_reviewer, poll_attempt).

🐛 Suggested fix
     def test_save_loop_state(self):
         """Test state saving logic."""
-        mgr = ReviewManager(self.mock_repo)
-        with patch("builtins.open", mock_open()) as mock_file:
+        with patch.object(ReviewManager, '__init__', lambda self: None):
+            mgr = ReviewManager()
+            mgr.workspace = "/tmp/test"
+            mgr.loop_state_file = "/tmp/test/loop_state.json"
             # We also need to mock os.open for the secure write
             with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
-                mgr._save_loop_state(8, "polling")
-                # Verification is tricky due to secure write implementation details,
-                # but we ensure it doesn't crash.
-                pass
+                mock_fdopen.return_value.__enter__ = MagicMock()
+                mock_fdopen.return_value.__exit__ = MagicMock()
+                mgr._save_loop_state(
+                    pr_number=8,
+                    since_iso="2026-01-30T00:00:00Z",
+                    validation_reviewer="gemini-bot",
+                    poll_attempt=1
+                )
+                mock_os_open.assert_called_once()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass
def test_save_loop_state(self):
"""Test state saving logic."""
with patch.object(ReviewManager, '__init__', lambda self: None):
mgr = ReviewManager()
mgr.workspace = "/tmp/test"
mgr.loop_state_file = "/tmp/test/loop_state.json"
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mock_fdopen.return_value.__enter__ = MagicMock()
mock_fdopen.return_value.__exit__ = MagicMock()
mgr._save_loop_state(
pr_number=8,
since_iso="2026-01-30T00:00:00Z",
validation_reviewer="gemini-bot",
poll_attempt=1
)
mock_os_open.assert_called_once()
🧰 Tools
🪛 Ruff (0.14.14)

[error] 65-65: Local variable mock_file is assigned to but never used

Remove assignment to unused variable mock_file

(F841)


[error] 67-67: Local variable mock_os_open is assigned to but never used

Remove assignment to unused variable mock_os_open

(F841)


[error] 67-67: Local variable mock_fdopen is assigned to but never used

Remove assignment to unused variable mock_fdopen

(F841)

🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 62 - 71, The test calls
ReviewManager._save_loop_state with two args but the method requires four;
update the test to call _save_loop_state with the full signature: pass pr_number
(8), since_iso (a valid ISO string, e.g. "2020-01-01T00:00:00Z"),
validation_reviewer (None or an empty string as appropriate for your code), and
poll_attempt (an int like 1), so call ReviewManager._save_loop_state(8,
"2020-01-01T00:00:00Z", None, 1) in the test to match the required parameters of
ReviewManager._save_loop_state.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly enhances the PR review skill by introducing stricter autonomous agent behaviors, crash recovery mechanisms, and improved documentation. The pr_skill.py file has been refactored to be more modular and robust, with new helper methods for Git/GitHub command execution and state management. The trigger_review command is now non-blocking, providing clear next_step instructions for the agent to continue the review cycle. Documentation across various .md files has been updated to reflect these new rules and guidelines, including deployment locations, freshness requirements, and rate limit awareness. Overall, these changes contribute to a more resilient, autonomous, and maintainable PR review process.

Comment on lines +53 to +63
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="CHANGES_REQUESTED",
has_changes_requested=True,
has_new_main_comments=False,
)
self.assertIn("CRITICAL: Changes requested", step)

def test_save_loop_state(self):
"""Test state saving logic."""

Choose a reason for hiding this comment

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

medium

The comment "Verification is tricky due to secure write implementation details, but we ensure it doesn't crash" suggests that test_save_loop_state might not be fully asserting the correctness of the saved state. While the current implementation prevents crashes, it would be beneficial to add assertions to verify the content of the loop_state.json file after _save_loop_state is called, ensuring that the correct data is being persisted. This would make the test more robust and provide better confidence in the crash recovery mechanism.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.agent/skills/pr_review/SKILL.md (1)

61-67: ⚠️ Potential issue | 🟡 Minor

Trigger_review polling description no longer matches code.
The tool is now non-blocking and returns a next_step instead of polling until response.

✏️ Suggested edit
-*   **Polling Behavior**: After the initial wait, the tool **polls until the main reviewer responds** (up to ~10 minutes). This enforces the Loop Rule - preventing premature exit before feedback is received.
+*   **Polling Behavior**: After the initial wait, the tool returns immediately with a `next_step` telling the agent to wait and poll via `status`. The script itself does not block or poll.
🤖 Fix all issues with AI agents
In @.agent/skills/pr_review/pr_skill.py:
- Around line 730-770: The waiting message in _determine_next_step currently
hard-codes "WAIT 30 seconds" — update it to use the configured poll interval
(POLL_INTERVAL_SECONDS or the constant used elsewhere) so the message reflects
actual behavior; locate the return clause in _determine_next_step that builds
the waiting string and replace the literal "30" with the poll interval variable
(formatted into the string), ensuring the message still instructs to wait and
then run 'status' while preserving existing text and any surrounding constants
like validation_reviewer and main_reviewer_state.

In `@tests/test_pr_skill.py`:
- Around line 37-85: Tests create ReviewManager with a repo arg and call
_save_loop_state with wrong arity; update tests to instantiate ReviewManager()
with no arguments (use mgr.repo = self.mock_repo to attach the mock repo if
needed), and call _save_loop_state with the four parameters its signature
requires (pass the pull number, state string, and the two additional args the
method expects) so the calls match ReviewManager.__init__ and
ReviewManager._save_loop_state; also ensure mocks for os.open/os.fdopen remain
in place around the secure write call in test_save_loop_state.

Comment on lines +730 to +770
@staticmethod
def _determine_next_step(
new_feedback,
validation_reviewer,
main_reviewer_state,
has_changes_requested,
has_new_main_comments,
):
"""Generate the next_step instruction string."""
if has_changes_requested:
return f"CRITICAL: Changes requested by reviewer. {ACTION_INSTRUCTIONS}{RATE_LIMIT_INSTRUCTION}"
elif has_new_main_comments:
return f"New comments from {validation_reviewer} after approval. {ACTION_INSTRUCTIONS}{RATE_LIMIT_INSTRUCTION}"
elif main_reviewer_state == "APPROVED":
other_feedback = [
item
for item in new_feedback
if not (
item.get("user") == validation_reviewer
and item.get("type") == "review_summary"
and item.get("state") == "APPROVED"
)
]
if other_feedback:
return f"New feedback received. {ACTION_INSTRUCTIONS}"
else:
return "Validation Complete (STOP LOOP - DO NOT MERGE AUTONOMOUSLY). Notify User. Never merge or delete a branch on your own, if you believe the main reviewer said that the PR is ready, just stop and ask for Human review"
elif new_feedback:
# Count explicitly addressable comments
comment_count = sum(
1
for item in new_feedback
if item.get("type") in ["inline_comment", "issue_comment"]
)
if comment_count > 0:
return f"{comment_count} new comments received. {ACTION_INSTRUCTIONS}"
else:
return f"New feedback received. {ACTION_INSTRUCTIONS}"
else:
return f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). WAIT 30 seconds, then run 'status' to check again. Be autonomous, don't stop the cycle. You must regularly poll for the main-reviewer's review until it eventually gets posted, then continue."

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wait instruction should reflect configured poll interval.
Line 769 hard-codes “WAIT 30 seconds,” which can diverge from POLL_INTERVAL_SECONDS.

🛠️ Suggested fix
-        return f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). WAIT 30 seconds, then run 'status' to check again. Be autonomous, don't stop the cycle. You must regularly poll for the main-reviewer's review until it eventually gets posted, then continue."
+        return f"Waiting for approval from {validation_reviewer} (Current: {main_reviewer_state}). WAIT {POLL_INTERVAL_SECONDS} seconds, then run 'status' to check again. Be autonomous, don't stop the cycle. You must regularly poll for the main-reviewer's review until it eventually gets posted, then continue."
🤖 Prompt for AI Agents
In @.agent/skills/pr_review/pr_skill.py around lines 730 - 770, The waiting
message in _determine_next_step currently hard-codes "WAIT 30 seconds" — update
it to use the configured poll interval (POLL_INTERVAL_SECONDS or the constant
used elsewhere) so the message reflects actual behavior; locate the return
clause in _determine_next_step that builds the waiting string and replace the
literal "30" with the poll interval variable (formatted into the string),
ensuring the message still instructs to wait and then run 'status' while
preserving existing text and any surrounding constants like validation_reviewer
and main_reviewer_state.

Comment on lines +37 to +85
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False,
)
self.assertIn("WAIT 30 seconds", step)
self.assertIn("Run 'status'", step)

def test_determine_next_step_changes_requested(self):
"""Test next_step when changes are requested."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="CHANGES_REQUESTED",
has_changes_requested=True,
has_new_main_comments=False,
)
self.assertIn("CRITICAL: Changes requested", step)

def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass

def test_trigger_review_next_step(self):
"""Test trigger_review returns correct wait instruction."""
# This requires mocking _check_local_state and repo.get_pull
mgr = ReviewManager(self.mock_repo)
mgr._check_local_state = MagicMock(return_value=(True, "Clean"))
mgr._log = MagicMock()

mock_pr = MagicMock()
self.mock_repo.get_pull.return_value = mock_pr

result = mgr.trigger_review(8, wait_seconds=42)
self.assertIn("WAIT 42 seconds", result["next_step"])
self.assertIn("Run 'status'", result["next_step"])
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tests instantiate ReviewManager incorrectly and pass wrong args to _save_loop_state.
ReviewManager.__init__ takes no repo argument, and _save_loop_state expects four parameters. These tests will raise TypeError before assertions.

✅ Suggested fix
-        mgr = ReviewManager(self.mock_repo)
-        step = mgr._determine_next_step(
+        step = ReviewManager._determine_next_step(
             new_feedback=[],
             validation_reviewer="gemini-bot",
             main_reviewer_state="PENDING",
             has_changes_requested=False,
             has_new_main_comments=False,
         )
@@
-        mgr = ReviewManager(self.mock_repo)
-        step = mgr._determine_next_step(
+        step = ReviewManager._determine_next_step(
             new_feedback=[],
             validation_reviewer="gemini-bot",
             main_reviewer_state="CHANGES_REQUESTED",
             has_changes_requested=True,
             has_new_main_comments=False,
         )
@@
-        mgr = ReviewManager(self.mock_repo)
-        with patch("builtins.open", mock_open()) as mock_file:
-            # We also need to mock os.open for the secure write
-            with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
-                mgr._save_loop_state(8, "polling")
-                # Verification is tricky due to secure write implementation details,
-                # but we ensure it doesn't crash.
-                pass
+        mgr = ReviewManager.__new__(ReviewManager)
+        mgr.loop_state_file = "loop_state.json"
+        mgr._log = MagicMock()
+        with patch("os.open", return_value=3) as mock_os_open, \
+             patch("os.fdopen", mock_open()) as mock_fdopen:
+            mgr._save_loop_state(8, "2024-01-01T00:00:00Z", "gemini-bot", 1)
+            mock_os_open.assert_called()
@@
-        mgr = ReviewManager(self.mock_repo)
+        mgr = ReviewManager.__new__(ReviewManager)
+        mgr.repo = self.mock_repo
         mgr._check_local_state = MagicMock(return_value=(True, "Clean"))
         mgr._log = MagicMock()
🧰 Tools
🪛 Ruff (0.14.14)

[error] 65-65: Local variable mock_file is assigned to but never used

Remove assignment to unused variable mock_file

(F841)


[error] 67-67: Local variable mock_os_open is assigned to but never used

Remove assignment to unused variable mock_os_open

(F841)


[error] 67-67: Local variable mock_fdopen is assigned to but never used

Remove assignment to unused variable mock_fdopen

(F841)

🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 37 - 85, Tests create ReviewManager with
a repo arg and call _save_loop_state with wrong arity; update tests to
instantiate ReviewManager() with no arguments (use mgr.repo = self.mock_repo to
attach the mock repo if needed), and call _save_loop_state with the four
parameters its signature requires (pass the pull number, state string, and the
two additional args the method expects) so the calls match
ReviewManager.__init__ and ReviewManager._save_loop_state; also ensure mocks for
os.open/os.fdopen remain in place around the secure write call in
test_save_loop_state.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@tests/test_pr_skill.py`:
- Around line 62-71: The test calls ReviewManager._save_loop_state with wrong
arity and leaves unused mocks; update the call to pass all required parameters
(pr_number, since_iso, validation_reviewer, poll_attempt) when invoking
_save_loop_state (referencing _save_loop_state) and remove the unused mock
variables (mock_file, mock_os_open, mock_fdopen) or actually use them to emulate
secure file write behavior; ensure the test only patches what it needs and that
the call matches the method signature so Ruff F841 unused-mock warnings are
eliminated.
- Around line 37-83: The tests construct ReviewManager which runs its real
__init__ (hitting env/gh/git/GitHub); change tests to avoid that by creating a
test-safe instance: patch ReviewManager.__init__ to a no-op (or use
ReviewManager.__new__ to allocate an object) and then manually set any
attributes the tests use (e.g., _determine_next_step, _save_loop_state,
_check_local_state, _log, mock_repo) so the methods under test can run without
performing real initialization; update test_determine_next_step*,
test_save_loop_state, and test_trigger_review to apply this patch or manual
instantiation before calling the methods.
- Around line 15-26: The test module imports pr_skill which requires the
external PyGithub package, but tests don't declare this dependency; add PyGithub
(e.g., PyGithub>=2.8.1) to the project's dev/test requirements so importing
pr_skill (and symbols DEFAULT_VALIDATION_REVIEWER and ReviewManager) succeeds in
CI; alternatively, if you cannot modify requirements, modify the test to
re-raise the ImportError to fail fast or wrap imports and apply unittest.skipIf
on specific test methods instead of raising unittest.SkipTest at module scope.

Comment on lines +15 to +26
try:
from pr_skill import DEFAULT_VALIDATION_REVIEWER, ReviewManager
except ImportError:
# Handle the case where the import path might be different in CI
sys.path.append(
os.path.abspath(
os.path.join(os.path.dirname(__file__),
"../../.agent/skills/pr_review")
)
)
from pr_skill import DEFAULT_VALIDATION_REVIEWER, ReviewManager

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for PyGithub in dependency declarations
rg -n "PyGithub|github" -g "requirements*.txt" -g "pyproject.toml" -g "setup.cfg" -g "setup.py" -A 2 -B 2

Repository: sheepdestroyer/agent-tools

Length of output: 128


🏁 Script executed:

# Also check for any GitHub-related imports in the repo
git ls-files | head -20

Repository: sheepdestroyer/agent-tools

Length of output: 410


🏁 Script executed:

# Get context around the test file
fd "test_pr_skill.py" --type f

Repository: sheepdestroyer/agent-tools

Length of output: 92


🏁 Script executed:

# Read the test file to understand its structure
cat -n tests/test_pr_skill.py

Repository: sheepdestroyer/agent-tools

Length of output: 3919


🏁 Script executed:

# Check the pr_skill.py to understand what it imports
cat -n .agent/skills/pr_review/pr_skill.py | head -50

Repository: sheepdestroyer/agent-tools

Length of output: 1552


PyGithub is not declared in test dependencies; add to dev/test requirements.

pr_skill imports github at module level (line 20 of pr_skill.py), which requires PyGithub. While it's declared in .agent/skills/pr_review/requirements.txt, it must also be added to the test environment's requirements so tests can import the module.

The proposed fix using unittest.SkipTest at module level is incorrect—this exception is for skipping individual test methods, not module-level imports. Instead, either:

  1. Add PyGithub>=2.8.1 to test/dev requirements (preferred)
  2. Re-raise the ImportError to fail fast and signal the missing dependency
  3. Use a conditional import with skipIf decorator on affected test methods
🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 15 - 26, The test module imports
pr_skill which requires the external PyGithub package, but tests don't declare
this dependency; add PyGithub (e.g., PyGithub>=2.8.1) to the project's dev/test
requirements so importing pr_skill (and symbols DEFAULT_VALIDATION_REVIEWER and
ReviewManager) succeeds in CI; alternatively, if you cannot modify requirements,
modify the test to re-raise the ImportError to fail fast or wrap imports and
apply unittest.skipIf on specific test methods instead of raising
unittest.SkipTest at module scope.

Comment on lines +37 to +83
def test_determine_next_step_waiting(self):
"""Test next_step when waiting for feedback."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="PENDING",
has_changes_requested=False,
has_new_main_comments=False,
)
self.assertIn("WAIT 30 seconds", step)
self.assertIn("Run 'status'", step)

def test_determine_next_step_changes_requested(self):
"""Test next_step when changes are requested."""
mgr = ReviewManager(self.mock_repo)
step = mgr._determine_next_step(
new_feedback=[],
validation_reviewer="gemini-bot",
main_reviewer_state="CHANGES_REQUESTED",
has_changes_requested=True,
has_new_main_comments=False,
)
self.assertIn("CRITICAL: Changes requested", step)

def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass

def test_trigger_review_next_step(self):
"""Test trigger_review returns correct wait instruction."""
# This requires mocking _check_local_state and repo.get_pull
mgr = ReviewManager(self.mock_repo)
mgr._check_local_state = MagicMock(return_value=(True, "Clean"))
mgr._log = MagicMock()

mock_pr = MagicMock()
self.mock_repo.get_pull.return_value = mock_pr

result = mgr.trigger_review(8, wait_seconds=42)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ReviewManager construction is invalid and triggers real initialization.
ReviewManager takes no args and its __init__ hits env/gh/git/GitHub. These tests will error or reach out to external systems.

🔧 Suggested test-safe instantiation
-        mgr = ReviewManager(self.mock_repo)
+        mgr = ReviewManager.__new__(ReviewManager)
+        mgr.repo = self.mock_repo
+        mgr._log = MagicMock()
🧰 Tools
🪛 Ruff (0.14.14)

[error] 65-65: Local variable mock_file is assigned to but never used

Remove assignment to unused variable mock_file

(F841)


[error] 67-67: Local variable mock_os_open is assigned to but never used

Remove assignment to unused variable mock_os_open

(F841)


[error] 67-67: Local variable mock_fdopen is assigned to but never used

Remove assignment to unused variable mock_fdopen

(F841)

🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 37 - 83, The tests construct
ReviewManager which runs its real __init__ (hitting env/gh/git/GitHub); change
tests to avoid that by creating a test-safe instance: patch
ReviewManager.__init__ to a no-op (or use ReviewManager.__new__ to allocate an
object) and then manually set any attributes the tests use (e.g.,
_determine_next_step, _save_loop_state, _check_local_state, _log, mock_repo) so
the methods under test can run without performing real initialization; update
test_determine_next_step*, test_save_loop_state, and test_trigger_review to
apply this patch or manual instantiation before calling the methods.

Comment on lines +62 to +71
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

_save_loop_state called with wrong arity + unused mocks.
The method expects (pr_number, since_iso, validation_reviewer, poll_attempt). Also, mock_file, mock_os_open, mock_fdopen are unused (Ruff F841).

🛠️ Proposed fix
-        with patch("builtins.open", mock_open()) as mock_file:
-            # We also need to mock os.open for the secure write
-            with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
-                mgr._save_loop_state(8, "polling")
+        with patch("os.open"), patch("os.fdopen"):
+            mgr._save_loop_state(
+                pr_number=8,
+                since_iso="2026-01-01T00:00:00+00:00",
+                validation_reviewer=DEFAULT_VALIDATION_REVIEWER,
+                poll_attempt=1,
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("builtins.open", mock_open()) as mock_file:
# We also need to mock os.open for the secure write
with patch("os.open") as mock_os_open, patch("os.fdopen") as mock_fdopen:
mgr._save_loop_state(8, "polling")
# Verification is tricky due to secure write implementation details,
# but we ensure it doesn't crash.
pass
def test_save_loop_state(self):
"""Test state saving logic."""
mgr = ReviewManager(self.mock_repo)
with patch("os.open"), patch("os.fdopen"):
mgr._save_loop_state(
pr_number=8,
since_iso="2026-01-01T00:00:00+00:00",
validation_reviewer=DEFAULT_VALIDATION_REVIEWER,
poll_attempt=1,
)
🧰 Tools
🪛 Ruff (0.14.14)

[error] 65-65: Local variable mock_file is assigned to but never used

Remove assignment to unused variable mock_file

(F841)


[error] 67-67: Local variable mock_os_open is assigned to but never used

Remove assignment to unused variable mock_os_open

(F841)


[error] 67-67: Local variable mock_fdopen is assigned to but never used

Remove assignment to unused variable mock_fdopen

(F841)

🤖 Prompt for AI Agents
In `@tests/test_pr_skill.py` around lines 62 - 71, The test calls
ReviewManager._save_loop_state with wrong arity and leaves unused mocks; update
the call to pass all required parameters (pr_number, since_iso,
validation_reviewer, poll_attempt) when invoking _save_loop_state (referencing
_save_loop_state) and remove the unused mock variables (mock_file, mock_os_open,
mock_fdopen) or actually use them to emulate secure file write behavior; ensure
the test only patches what it needs and that the call matches the method
signature so Ruff F841 unused-mock warnings are eliminated.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants