Skip to content

feat(cli): add comprehensive GitHub PR workflow commands#305

Merged
frankbria merged 4 commits into
mainfrom
feature/pr-cli-commands
Jan 28, 2026
Merged

feat(cli): add comprehensive GitHub PR workflow commands#305
frankbria merged 4 commits into
mainfrom
feature/pr-cli-commands

Conversation

@frankbria

@frankbria frankbria commented Jan 28, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a new codeframe pr command group for GitHub Pull Request management, following v2 CLI patterns (headless, no server required).

New Commands

Command Description
codeframe pr create Create PRs with auto-generated descriptions from commits
codeframe pr list List PRs with status filtering and JSON output
codeframe pr get Get detailed PR information
codeframe pr merge Merge PRs with squash/merge/rebase strategies
codeframe pr close Close PRs without merging
codeframe pr status Show PR status for current branch

Implementation Details

  • Uses existing GitHubIntegration async client from codeframe/git/github_integration.py
  • Wraps async calls with asyncio.run() for CLI context
  • Environment-based configuration (GITHUB_TOKEN, GITHUB_REPO)
  • Commands registered in both v1 CLI (__init__.py) and v2 CLI (app.py)

Files Changed

  • New: codeframe/cli/pr_commands.py - Core PR command implementation (545 lines)
  • New: tests/cli/test_pr_commands.py - Comprehensive test suite (23 tests)
  • Modified: codeframe/cli/app.py - Register PR commands in v2 CLI
  • Modified: codeframe/cli/__init__.py - Register PR commands in v1 CLI
  • Modified: docs/CLI_WIREFRAME.md - Updated documentation with examples

Example Usage

# Create a PR from current branch
codeframe pr create --title "Add new feature"

# List open PRs
codeframe pr list --status open

# Get PR details in JSON format
codeframe pr get 42 --format json

# Merge with squash strategy
codeframe pr merge 42 --strategy squash

# Check current branch PR status
codeframe pr status

Test Plan

  • All 23 new PR command tests pass
  • All 1060 existing v2 tests pass (no regressions)
  • Code linted with ruff
  • CLI help displays correctly for all commands
  • Manual testing with real GitHub repo (requires GITHUB_TOKEN)

Summary by CodeRabbit

  • New Features

    • Adds a new "pr" CLI group with commands to create (auto-generated or custom descriptions), list/filter (open/closed/all) with table/JSON output, get details, merge (multiple strategies), close, and show status for the current branch.
  • Documentation

    • Expanded CLI docs with examples and usage patterns for all PR commands.
  • Tests

    • Comprehensive test suites covering PR commands, outputs, error handling, and git interactions.
  • Chores

    • CI workflow adjustments for review handling and credential sanitation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new "pr" Typer subcommand group implementing pull request management (create, list, get, merge, close, status), git helper utilities, tests, documentation, and CI workflow adjustments; registers pr_app with the top-level CLI.

Changes

Cohort / File(s) Summary
CLI Registration
codeframe/cli/__init__.py, codeframe/cli/app.py
Import and register pr_app from codeframe.cli.pr_commands with the main Typer app (app.add_typer(pr_app, name="pr", help="Pull request management (create, list, merge, close)")).
PR Commands Module
codeframe/cli/pr_commands.py
New module implementing PR CLI commands (create, list, get, merge, close, status); adds git helpers (get_current_branch, get_git_diff_stats, get_commit_messages), GitHub config loader, async runner, JSON/table output, Rich formatting, and GitHubIntegration API usage with GitHubAPIError handling.
Tests (unit & integration)
tests/cli/test_pr_commands.py, tests/cli/test_v2_cli_integration.py
New comprehensive tests covering create/list/get/merge/close/status flows, JSON output, error cases, and git-helper behavior; use Typer CliRunner, fixtures, and mocked GitHubIntegration.
Documentation
docs/CLI_WIREFRAME.md
Expanded PR command docs and examples; updated module references to pr_commands.py; documents options/flags and adapter usage for GitHubIntegration.
CI Workflow
.github/workflows/opencode-review.yml
Adjusted review workflow paths-ignore, added PR context variables, enhanced review prompt, and added a credential-clearing step for large changes.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as PR CLI Command
    participant GitHelper as Git Helpers
    participant GitHub as GitHubIntegration

    User->>CLI: codeframe pr create --title "..."
    CLI->>GitHelper: get_current_branch()
    GitHelper-->>CLI: feature/branch-name
    CLI->>GitHelper: get_commit_messages(base, head)
    GitHelper-->>CLI: commit messages
    CLI->>GitHub: create_pull_request(head=feature/branch-name, title, body, base)
    GitHub-->>CLI: PR details (number, url)
    CLI-->>User: Print PR metadata & URL
Loading
sequenceDiagram
    actor User
    participant CLI as PR CLI Command
    participant GitHub as GitHubIntegration

    User->>CLI: codeframe pr list --status open --format table
    CLI->>GitHub: list_pull_requests(state='open')
    GitHub-->>CLI: list of PRs
    CLI->>CLI: format results (table or json)
    CLI-->>User: Rendered output
Loading
sequenceDiagram
    actor User
    participant CLI as PR CLI Command
    participant GitHub as GitHubIntegration

    User->>CLI: codeframe pr merge --number 42 --strategy squash
    CLI->>GitHub: get_pull_request(42)
    GitHub-->>CLI: PR details (state)
    CLI->>GitHub: merge_pull_request(42, method='squash')
    GitHub-->>CLI: merge result (merged, sha)
    CLI-->>User: Success/failure message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
I hopped through branches, nibbed a diff or two,
Commands now sprout where pull requests grew.
Create, list, merge — with tests snug and bright,
A rabbit's cheer for CLI work done right. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly summarizes the main change: adding comprehensive GitHub PR workflow commands to the CLI, which aligns with the primary objective of implementing pr create, list, get, merge, close, and status commands.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% 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 feature/pr-cli-commands

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

@macroscopeapp

macroscopeapp Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

Add codeframe CLI pr command group and wire GitHub PR workflows in pr_commands.py and opencode-review.yml

Introduce a new pr Typer app with create, list, get, merge, close, and status commands backed by GitHubIntegration, register it in the main CLI, and adjust the OpenCode review workflow to ignore pyproject.toml, clear git credentials after checkout, and pass PR context env vars. See pr_commands.py and opencode-review.yml.

📍Where to Start

Start with the pr_app and command handlers in pr_commands.py, then follow registration in app.py.


Macroscope summarized 5f235f9.

Comment thread codeframe/cli/pr_commands.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Based on my review of PR #305, here's my feedback:

Summary

This PR implements GitHub Pull Request CLI commands that are part of Phase 5 (Integrated Git Workflow & PR Management) in the Golden Path. The implementation follows v2 architecture patterns correctly: CLI-first, headless, no server required.

✅ Strengths

  1. Golden Path Alignment: Commands are directly specified in GOLDEN_PATH.md Phase 5
  2. v2 Architecture: Properly implements CLI-first pattern using environment variables, no server dependencies
  3. Code Quality: Well-organized, comprehensive docstrings, consistent type hints throughout
  4. Test Coverage: 23 well-structured tests covering success paths, error cases, and edge cases
  5. Error Handling: User-friendly error messages with helpful guidance (e.g., token setup instructions)
  6. Security: Tokens never logged, proper Bearer auth used in underlying GitHubIntegration

⚠️ Issues to Address

1. Potential AttributeError in get_pr() (MEDIUM - Recommended Fix)

Location: pr_commands.py:374

console.print(f"[bold]Merged:[/bold] {pr.merged_at.strftime('%Y-%m-%d %H:%M')}")

The merged_at field is Optional[datetime] per PRDetails definition (line 45 of github_integration.py). This will crash if pr.merged_at is None (e.g., for an open PR).

Fix: Add None check like line 371 does:

console.print(f"[bold]Merged:[/bold] {pr.merged_at.strftime('%Y-%m-%d %H:%M') if pr.merged_at else 'N/A'}")

2. Silent error handling in git helpers (LOW - Optional Enhancement)

Locations: pr_commands.py:91-92 and 115-116

except subprocess.CalledProcessError:
    return ""

Git command failures (network issues, permissions, branch not found) are silently swallowed. This makes debugging difficult for users.

Suggestion: Add logging:

except subprocess.CalledProcessError as e:
    logger.debug(f"Git diff stats failed: {e.stderr}")
    return ""

📝 Design Notes (For Future Consideration)

  • State Machine Integration: Golden Path mentions task state transitions (e.g., DONE→IN_REVIEW→MERGED) when PR operations occur. Current PR commands don't integrate with task state machine. This is acceptable for this PR as a starting point, but should be planned for future iterations.
  • Credential Management: Commands use environment variables directly rather than the CredentialManager system mentioned in architecture docs. This is consistent with the headless approach and works correctly, but note the gap for future enhancement.

✅ Approval

The PR is well-implemented and ready to merge with the recommended fix for issue #1. Issue #2 is optional and can be addressed in a follow-up. The test suite provides good coverage, and the code follows v2 patterns correctly.

@github-actions

Copy link
Copy Markdown
Contributor

Review comment posted successfully. The PR is well-implemented with good test coverage and follows v2 architecture patterns. The main issue identified is a potential AttributeError when merged_at is None in the get_pr command at line 374.

New%20session%20-%202026-01-28T15%3A55%3A02.307Z
opencode session  |  github run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@codeframe/cli/app.py`:
- Around line 4164-4167: The import of pr_app is placed after executable code
which triggers E402; either move the import of pr_app to the module-level
imports at the top of the file or suppress the check on that line by adding the
inline comment "# noqa: E402" to the import statement that precedes
app.add_typer(pr_app, name="pr"); update the same import line where pr_app is
referenced so linters stop failing while preserving the existing
app.add_typer(pr_app, name="pr") call.
🧹 Nitpick comments (1)
codeframe/cli/pr_commands.py (1)

245-399: Validate enum-like CLI options before hitting GitHub.
status, format, and strategy accept any string today, which can lead to confusing API errors despite the documented allowed values. Consider normalizing and validating these inputs locally.

♻️ Suggested validation (local normalization + explicit allowed sets)
 def list_prs(
     status: str = typer.Option(
         "open", "--status", "-s", help="Filter by status: open, closed, all"
     ),
     format: str = typer.Option(
         "table", "--format", "-f", help="Output format: table or json"
     ),
 ):
@@
-    try:
+    status = status.lower()
+    if status not in {"open", "closed", "all"}:
+        console.print(f"[red]Error:[/red] Invalid status '{status}'. Use open|closed|all.")
+        raise typer.Exit(1)
+    format = format.lower()
+    if format not in {"table", "json"}:
+        console.print(f"[red]Error:[/red] Invalid format '{format}'. Use table|json.")
+        raise typer.Exit(1)
+    try:
         token, repo = _get_github_config()
@@
 def get_pr(
     pr_number: int = typer.Argument(..., help="PR number"),
     format: str = typer.Option(
         "text", "--format", "-f", help="Output format: text or json"
     ),
 ):
@@
-    try:
+    format = format.lower()
+    if format not in {"text", "json"}:
+        console.print(f"[red]Error:[/red] Invalid format '{format}'. Use text|json.")
+        raise typer.Exit(1)
+    try:
         token, repo = _get_github_config()
@@
 def merge_pr(
     pr_number: int = typer.Argument(..., help="PR number to merge"),
     strategy: str = typer.Option(
         "squash",
         "--strategy",
         "-s",
         help="Merge strategy: squash, merge, rebase",
     ),
 ):
@@
-    try:
+    strategy = strategy.lower()
+    if strategy not in {"squash", "merge", "rebase"}:
+        console.print(f"[red]Error:[/red] Invalid strategy '{strategy}'. Use squash|merge|rebase.")
+        raise typer.Exit(1)
+    try:
         token, repo = _get_github_config()

Comment thread codeframe/cli/app.py Outdated
@frankbria frankbria linked an issue Jan 28, 2026 that may be closed by this pull request
32 tasks
Add `codeframe pr` command group for GitHub Pull Request management:
- `pr create`: Create PRs with auto-generated descriptions from commits
- `pr list`: List PRs with status filtering and JSON output
- `pr get`: Get detailed PR information
- `pr merge`: Merge PRs with squash/merge/rebase strategies
- `pr close`: Close PRs without merging
- `pr status`: Show PR status for current branch

Implementation follows v2 CLI patterns (headless, no server required):
- Uses existing GitHubIntegration async client
- Wraps async calls with asyncio.run() for CLI context
- Full test coverage (23 tests) using TDD approach
- Environment-based configuration (GITHUB_TOKEN, GITHUB_REPO)

Commands registered in both v1 CLI (__init__.py) and v2 CLI (app.py).
Documentation updated in CLI_WIREFRAME.md with examples.
@frankbria frankbria force-pushed the feature/pr-cli-commands branch from ebfc9d8 to 5aa3fe4 Compare January 28, 2026 16:16
@github-actions

Copy link
Copy Markdown
Contributor

PR Review Summary

This PR implements GitHub Pull Request CLI commands following v2 architecture patterns correctly. The implementation is well-structured with comprehensive test coverage and proper CLI-first, headless design.

✅ Strengths

  • Golden Path Alignment: Commands support Phase 5 (Integrated Git Workflow & PR Management)
  • v2 Architecture: Proper CLI-first pattern using environment variables, no server dependencies
  • Code Quality: Well-organized, comprehensive docstrings, consistent type hints
  • Test Coverage: 23 tests covering success paths, error cases, and edge cases
  • Security: Tokens never logged, proper Bearer auth in GitHubIntegration

⚠️ Issues Requiring Fix

1. E402 Lint Error Blocking CI (HIGH - Must Fix)

Location:

The import lacks the suppression comment that other late imports use:

This is causing CI failures and needs to be fixed to merge.

2. Misleading Success Message in close_pr (MEDIUM - Recommended)

Location:

When a PR is already closed, the command prints a warning but still prints "✓ PR closed" success message:

Fix: Match the pattern from :

Then add check at the end:

📝 Optional Enhancements

Input Validation (LOW - Future Enhancement)

Commands like , , and accept any string values for options like , , and . While GitHub API rejects invalid values, local validation would provide better UX. See CodeRabbit's detailed suggestion for implementation.

Silent Error Handling in Git Helpers (LOW - Future Enhancement)

Lines 91-92 and 115-116 silently swallow git subprocess errors, making debugging harder. Consider adding debug logging while keeping empty string returns for backward compatibility.

✅ Approval Condition

This PR is well-implemented and ready to merge after fixing issue #1 (E402 error). Issue #2 is recommended for better UX but not blocking.


Note: The potential on mentioned in earlier reviews is NOT an issue - line 373 has a proper check before accessing on line 374.

@github-actions

Copy link
Copy Markdown
Contributor

Review posted successfully. The key findings are:

Must Fix (blocking CI):

  • E402 lint error in app.py:26 - needs # noqa: E402 on the pr_app import

Recommended Fix:

  • close_pr command shows misleading success message when PR is already closed (lines 475-488)

Clarification:

  • The potential AttributeError on merged_at.strftime() mentioned in earlier reviews is NOT an issue - the code has a proper if pr.merged_at: check at line 373 before accessing it at line 374

The PR is otherwise well-implemented with excellent test coverage and follows v2 architecture patterns correctly.

New%20session%20-%202026-01-28T16%3A17%3A03.409Z
opencode session  |  github run

Address code review feedback: return None instead of True when PR
is already closed to skip the success message, consistent with
merge_pr handling of already-merged PRs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@codeframe/cli/pr_commands.py`:
- Around line 45-68: The get_current_branch function currently only catches
subprocess.CalledProcessError, so if the git executable is missing
subprocess.run will raise FileNotFoundError and crash; update get_current_branch
to also catch FileNotFoundError (in addition to CalledProcessError) and raise a
RuntimeError with a clear, user-friendly message (e.g., "git not found; please
install git and ensure it's on PATH") so callers of get_current_branch receive a
consistent RuntimeError when git is unavailable.
- Around line 71-116: Both get_git_diff_stats and get_commit_messages can raise
FileNotFoundError when git is not installed; update each function (around the
subprocess.run call in get_git_diff_stats and get_commit_messages) to also catch
FileNotFoundError (in addition to subprocess.CalledProcessError) and return an
empty string instead of letting the exception bubble up, so pr create won't
abort if git is missing.

Comment on lines +45 to +68
def get_current_branch(repo_path: Optional[Path] = None) -> str:
"""Get the current git branch name.

Args:
repo_path: Path to the git repository (defaults to cwd)

Returns:
Current branch name

Raises:
RuntimeError: If not in a git repository
"""
cwd = repo_path or Path.cwd()
try:
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=cwd,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to get current branch: {e.stderr}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle missing git executable in get_current_branch.

If git isn’t installed, subprocess.run raises FileNotFoundError and the CLI will crash instead of showing a friendly error.

🧩 Proposed fix
 def get_current_branch(repo_path: Optional[Path] = None) -> str:
@@
-    except subprocess.CalledProcessError as e:
-        raise RuntimeError(f"Failed to get current branch: {e.stderr}")
+    except FileNotFoundError as e:
+        raise RuntimeError("git executable not found. Please install Git.") from e
+    except subprocess.CalledProcessError as e:
+        raise RuntimeError(f"Failed to get current branch: {e.stderr}") from e
🤖 Prompt for AI Agents
In `@codeframe/cli/pr_commands.py` around lines 45 - 68, The get_current_branch
function currently only catches subprocess.CalledProcessError, so if the git
executable is missing subprocess.run will raise FileNotFoundError and crash;
update get_current_branch to also catch FileNotFoundError (in addition to
CalledProcessError) and raise a RuntimeError with a clear, user-friendly message
(e.g., "git not found; please install git and ensure it's on PATH") so callers
of get_current_branch receive a consistent RuntimeError when git is unavailable.

Comment on lines +71 to +116
def get_git_diff_stats(repo_path: Path, base: str, head: str) -> str:
"""Get diff statistics between two branches.

Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name

Returns:
Diff statistics as string
"""
try:
result = subprocess.run(
["git", "diff", "--stat", f"{base}...{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""


def get_commit_messages(repo_path: Path, base: str, head: str) -> str:
"""Get commit messages between two branches.

Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name

Returns:
Commit messages as string
"""
try:
result = subprocess.run(
["git", "log", "--oneline", f"{base}..{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prevent auto-description from crashing when git is missing.

Both helpers can raise FileNotFoundError, which currently bubbles up and aborts pr create.

🧩 Proposed fix
 def get_git_diff_stats(repo_path: Path, base: str, head: str) -> str:
@@
-    except subprocess.CalledProcessError:
+    except FileNotFoundError:
+        return ""
+    except subprocess.CalledProcessError:
         return ""
@@
 def get_commit_messages(repo_path: Path, base: str, head: str) -> str:
@@
-    except subprocess.CalledProcessError:
+    except FileNotFoundError:
+        return ""
+    except subprocess.CalledProcessError:
         return ""
📝 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 get_git_diff_stats(repo_path: Path, base: str, head: str) -> str:
"""Get diff statistics between two branches.
Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name
Returns:
Diff statistics as string
"""
try:
result = subprocess.run(
["git", "diff", "--stat", f"{base}...{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""
def get_commit_messages(repo_path: Path, base: str, head: str) -> str:
"""Get commit messages between two branches.
Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name
Returns:
Commit messages as string
"""
try:
result = subprocess.run(
["git", "log", "--oneline", f"{base}..{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""
def get_git_diff_stats(repo_path: Path, base: str, head: str) -> str:
"""Get diff statistics between two branches.
Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name
Returns:
Diff statistics as string
"""
try:
result = subprocess.run(
["git", "diff", "--stat", f"{base}...{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except FileNotFoundError:
return ""
except subprocess.CalledProcessError:
return ""
def get_commit_messages(repo_path: Path, base: str, head: str) -> str:
"""Get commit messages between two branches.
Args:
repo_path: Path to the git repository
base: Base branch name
head: Head branch name
Returns:
Commit messages as string
"""
try:
result = subprocess.run(
["git", "log", "--oneline", f"{base}..{head}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except FileNotFoundError:
return ""
except subprocess.CalledProcessError:
return ""
🤖 Prompt for AI Agents
In `@codeframe/cli/pr_commands.py` around lines 71 - 116, Both get_git_diff_stats
and get_commit_messages can raise FileNotFoundError when git is not installed;
update each function (around the subprocess.run call in get_git_diff_stats and
get_commit_messages) to also catch FileNotFoundError (in addition to
subprocess.CalledProcessError) and return an empty string instead of letting the
exception bubble up, so pr create won't abort if git is missing.

@github-actions

Copy link
Copy Markdown
Contributor

PR Review Summary

This PR implements GitHub Pull Request CLI commands following v2 architecture patterns correctly. The implementation is well-structured with comprehensive test coverage and proper CLI-first, headless design.

✅ Strengths

  • Golden Path Alignment: Commands support Phase 5 (Integrated Git Workflow & PR Management)
  • v2 Architecture: Proper CLI-first pattern using environment variables, no server dependencies
  • Code Quality: Well-organized, comprehensive docstrings, consistent type hints
  • Test Coverage: 23 tests covering success paths, error cases, and edge cases
  • Security: Tokens never logged, proper Bearer auth in GitHubIntegration

⚠️ Issues Requiring Fix

1. E402 Lint Error Blocking CI (HIGH - Must Fix)

Location:

The import lacks the # noqa: E402 suppression comment that other late imports use in __init__.py:

# In __init__.py (has noqa: E402):
from codeframe.cli.pr_commands import pr_app  # noqa: E402

# In app.py (missing noqa: E402):
from codeframe.cli.pr_commands import pr_app

Fix: Add # noqa: E402 comment to the import line in app.py:

from codeframe.cli.pr_commands import pr_app  # noqa: E402

This is causing CI failures and needs to be fixed to merge.

2. Misleading Success Message in close_pr (MEDIUM - Recommended)

Location: pr_commands.py:453-472

When a PR is already closed, the command prints a warning but still prints "✓ PR closed" success message:

if pr.state == "closed":
    console.print(f"[yellow]PR #{pr_number} is already closed.[/yellow]")
    return None

# ... later ...
if result is None:
    raise typer.Exit(0)

if result:
    console.print(f"[green]✓ PR #{pr_number} closed[/green]")

Fix: Match the pattern from merge_pr (lines 385-389):

if pr.state == "closed":
    console.print(f"[yellow]PR #{pr_number} is already closed.[/yellow]")
    raise typer.Exit(0)

Then add check at the end:

if result is None or result:
    console.print(f"[green]✓ PR #{pr_number} closed[/green]")
else:
    console.print(f"[red]Error:[/red] Failed to close PR #{pr_number}")
    raise typer.Exit(1)

📝 Optional Enhancements

Input Validation (LOW - Future Enhancement)

Commands like list_prs, get_pr, and merge_pr accept any string values for options like status, format, and strategy. While GitHub API rejects invalid values, local validation would provide better UX. See CodeRabbit's detailed suggestion for implementation.

Silent Error Handling in Git Helpers (LOW - Future Enhancement)

Lines 91-92 and 115-116 silently swallow git subprocess errors, making debugging harder. Consider adding debug logging while keeping empty string returns for backward compatibility.

✅ Approval Condition

This PR is well-implemented and ready to merge after fixing issue #1 (E402 error). Issue #2 is recommended for better UX but not blocking.


Note: The potential AttributeError on merged_at.strftime() mentioned in earlier reviews is NOT an issue - the code has a proper if pr.merged_at: check at line 373 before accessing it at line 374.

@github-actions

Copy link
Copy Markdown
Contributor

The review comment has been posted successfully at: #305 (comment)

Summary of Review Findings:

Must Fix (blocking CI):

  • E402 lint error in app.py:26 - needs # noqa: E402 on the pr_app import

Recommended Fix:

  • close_pr command shows misleading success message when PR is already closed (lines 453-472)

Clarification:

  • The potential AttributeError on merged_at.strftime() mentioned in earlier reviews is NOT an issue - the code has a proper if pr.merged_at: check at line 373 before accessing it at line 374

The PR is otherwise well-implemented with excellent test coverage and follows v2 architecture patterns correctly.

New%20session%20-%202026-01-28T16%3A22%3A06.692Z
opencode session  |  github run

Improvements from ralph-claude-code workflow:
- Add git credentials clearing step to prevent auth conflicts
- Pass PR context (title, body, number, repo) as environment variables
- Include PR title directly in prompt to avoid gh pr view calls
- Add pyproject.toml to paths-ignore to skip config-only changes
- Remove gh pr view/diff instructions (info now passed directly)

Kept existing:
- Concurrency block to prevent duplicate reviews for same PR
- 10-minute timeout
- Substantial changes threshold (5+ files OR 20+ lines)
@github-actions

Copy link
Copy Markdown
Contributor

Based on my review of PR #305, here are my findings:

Summary

This PR implements comprehensive GitHub Pull Request CLI commands following v2 architecture patterns (CLI-first, headless, no server required). The implementation supports Phase 5 of the Golden Path and is well-structured with good test coverage.

✅ Strengths

  1. Golden Path Alignment: Commands directly support Phase 5 (Integrated Git Workflow & PR Management)
  2. v2 Architecture: Proper CLI-first pattern using environment variables, no server dependencies
  3. Code Quality: Well-organized, comprehensive docstrings, consistent type hints throughout
  4. Test Coverage: 23 well-structured tests covering success paths, error cases, and edge cases
  5. Error Handling: User-friendly error messages with helpful guidance
  6. Security: Tokens never logged, proper Bearer auth in underlying GitHubIntegration

⚠️ Issues Requiring Fix

1. E402 Lint Error Blocking CI (HIGH - Must Fix)

Location: codeframe/cli/app.py:26

The import appears after environment variable loading code (_cwd = Path.cwd() on line 30 and subsequent .exists()/load_dotenv() calls), which triggers E402 violations:

# Lines 24-26: imports
from codeframe.cli.auth_commands import auth_app
from codeframe.cli.pr_commands import pr_app  # <- Needs noqa: E402

# Lines 28-37: Executable code follows
_cwd = Path.cwd()  # Function call at import time
# ... more executable code

Fix: Add # noqa: E402 suppression comment to match pattern used in __init__.py:

from codeframe.cli.pr_commands import pr_app  # noqa: E402

2. FileNotFoundError Not Caught in Git Helpers (MEDIUM - Recommended Fix)

Locations: pr_commands.py:91-92 and 115-116

If git is not installed, subprocess.run raises FileNotFoundError, which is not caught:

def get_git_diff_stats(repo_path: Path, base: str, head: str) -> str:
    try:
        result = subprocess.run([...], check=True)
        return result.stdout.strip()
    except subprocess.CalledProcessError:  # <- FileNotFoundError not caught
        return ""

Fix: Add FileNotFoundError exception handler:

except FileNotFoundError:
    return ""
except subprocess.CalledProcessError:
    return ""

Apply same fix to get_commit_messages function.

📝 Optional Enhancements

Input Validation (LOW - Future Enhancement)

Commands accept any string values for options like --status, --format, --strategy. While GitHub API rejects invalid values, local validation would provide better UX with immediate feedback rather than API errors.

✅ Code Review Findings

Verified correct behavior:

  • close_pr command (lines 475-495): Already correctly exits with typer.Exit(0) when PR is already closed - success message is NOT printed (as verified by code logic)
  • get_pr merged_at access (line 374): Already protected by if pr.merged_at: check at line 373 - no AttributeError risk

✅ Approval Condition

This PR is well-implemented and ready to merge after fixing issue #1 (E402 error). Issue #2 (FileNotFoundError handling) is recommended for robustness but not blocking.

@github-actions

Copy link
Copy Markdown
Contributor

Review comment posted successfully. The key findings are:

Must Fix (blocking CI):

  • E402 lint error in app.py:26 - needs # noqa: E402 on the pr_app import

Recommended Fix:

  • FileNotFoundError handling in git helpers (get_git_diff_stats and get_commit_messages) to prevent crashes when git is not installed

Optional Enhancements:

  • Input validation for CLI options (status, format, strategy)

Verified NOT issues:

  • close_pr command already correctly exits without printing success message when PR is already closed
  • get_pr merged_at access is already protected by a proper None check

The PR is otherwise well-implemented with excellent test coverage and follows v2 architecture patterns correctly.

New%20session%20-%202026-01-28T16%3A26%3A54.772Z
opencode session  |  github run

Add TestPRCommands class with 9 tests covering:
- pr help
- pr list (table and JSON formats)
- pr get
- pr create
- pr merge
- pr close
- pr status
- Missing token error handling

Tests use monkeypatch to mock GitHubIntegration, following the
existing pattern for AI integration tests with MockProvider.

All tests are marked v2 via the module-level pytestmark.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@codeframe/cli/pr_commands.py`:
- Around line 245-329: The PR list command (list_prs) and pr_status rely on
GitHubIntegration.list_pull_requests which currently fetches only the default
first page (30 items), causing missing PRs; update the call to
list_pull_requests to support pagination by passing per_page and page parameters
(or add a new CLI option like --limit/--page to list_prs that surfaces
per_page/page to the user), then loop/fetch subsequent pages until fewer than
per_page items are returned (or until the user-specified limit) and aggregate
results before rendering; adjust pr_status to also use the paginated
list_pull_requests so branch lookup can find PRs beyond the first page.

Comment thread codeframe/cli/pr_commands.py
@github-actions

Copy link
Copy Markdown
Contributor

PR Review

This PR implements comprehensive GitHub PR CLI commands following v2 architecture patterns. The implementation is well-structured with excellent test coverage and follows the CLI-first, headless approach correctly.

✅ Strengths

  • Golden Path Alignment: Commands directly support Phase 5 (Integrated Git Workflow & PR Management)
  • v2 Architecture: Proper CLI-first pattern using environment variables, no server dependencies
  • Code Quality: Well-organized, comprehensive docstrings, consistent type hints throughout
  • Test Coverage: 23 well-structured tests covering success paths, error cases, and edge cases
  • Error Handling: User-friendly error messages with helpful guidance (e.g., token setup instructions)
  • Security: Tokens never logged, proper Bearer auth in GitHubIntegration

⚠️ Issues Requiring Fix

1. E402 Lint Error Blocking CI (HIGH - Must Fix)

Location:

The import appears after environment variable loading code, which triggers E402 violations:

Fix: Add suppression comment to match pattern used in :

2. FileNotFoundError Not Caught in Git Helpers (MEDIUM - Recommended Fix)

Locations: and

If git is not installed, raises , which is not caught:

Fix: Add exception handler:

Apply same fix to function.

📝 Verified Correct Behavior

  • command (lines 475-495): Already correctly exits with when PR is already closed - success message is NOT printed
  • merged_at access (line 374): Already protected by check at line 373 - no AttributeError risk

📝 Optional Enhancements

  • Input Validation: Commands accept any string values for options like , , . While GitHub API rejects invalid values, local validation would provide better UX with immediate feedback.

✅ Approval Condition

This PR is well-implemented and ready to merge after fixing issue #1 (E402 error). Issue #2 (FileNotFoundError handling) is recommended for robustness but not blocking.

@github-actions

Copy link
Copy Markdown
Contributor

Review comment posted successfully at #305 (comment)

New%20session%20-%202026-01-28T16%3A33%3A15.136Z
opencode session  |  github run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/opencode-review.yml:
- Around line 33-36: The assignments to additions and deletions use unquoted
GitHub context variables and risk shell injection; update the workflow to assign
them safely by wrapping the expressions in quotes and validating/coercing to
integers before the arithmetic (e.g., set additions and deletions using quoted
expansions like additions="${{ github.event.pull_request.additions }}" and
deletions="${{ github.event.pull_request.deletions }}", then sanitize or default
non-numeric values to 0 before computing total), and ensure echo writes the
computed numeric total to GITHUB_OUTPUT.
- Around line 82-86: The workflow currently passes user-controlled env vars
(PR_TITLE, PR_BODY, PR_NUMBER, REPO_NAME) into the
anomalyco/opencode/github@latest action without sanitization; update the step
that calls the action so it first sanitizes/escapes these inputs before they are
interpolated into prompts or shell commands (e.g., implement or call a
sanitizeInput helper that strips/escapes control characters, backticks, shell
metacharacters, and prompt injection tokens, enforce max length, and validate
expected characters) and ensure the action invocation uses the sanitized values
rather than raw ${{ github.event.pull_request.* }} env vars; also verify the
action's input handling (e.g., any prompt-construction code inside the action)
performs the same sanitization/escaping for PR_TITLE and PR_BODY before passing
them to AI or to any shell.
🧹 Nitpick comments (3)
tests/cli/test_v2_cli_integration.py (3)

926-941: Mock setup is repeated across all test methods—consider extracting to a fixture.

Each test method creates a nearly identical AsyncMock setup for GitHubIntegration. This repetition increases maintenance burden and makes tests harder to update.

Consider a shared mock_github fixture
`@pytest.fixture`
def mock_github(self, monkeypatch):
    """Create and configure mock GitHubIntegration."""
    from unittest.mock import AsyncMock
    
    mock_gh = AsyncMock()
    mock_gh.close = AsyncMock()
    
    monkeypatch.setattr(
        "codeframe.cli.pr_commands.GitHubIntegration",
        lambda **kwargs: mock_gh,
    )
    return mock_gh

Then in tests:

def test_pr_list(self, mock_github_env, mock_pr_details, mock_github):
    mock_github.list_pull_requests = AsyncMock(return_value=[mock_pr_details])
    result = runner.invoke(app, ["pr", "list"])
    # ...

1003-1022: Missing verification that merge was called with correct parameters.

The test verifies the command succeeds but doesn't assert that merge_pull_request was called with the expected PR number and merge strategy.

Add mock call verification
         result = runner.invoke(app, ["pr", "merge", "42"])
         assert result.exit_code == 0
         assert "merged" in result.output.lower()
+        mock_gh.merge_pull_request.assert_called_once()
+        call_args = mock_gh.merge_pull_request.call_args
+        assert call_args[0][0] == 42  # PR number

886-1070: Consider adding tests for additional edge cases.

The test coverage is solid for the happy path, but consider adding tests for:

  • PR not found (404 scenario)
  • Network/API errors
  • Merge conflicts
  • Already merged PR

Comment thread .github/workflows/opencode-review.yml
Comment thread .github/workflows/opencode-review.yml
@frankbria frankbria merged commit 01e68a3 into main Jan 28, 2026
19 checks passed
@frankbria frankbria deleted the feature/pr-cli-commands branch January 28, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[V2] Add comprehensive Git/PR workflow CLI commands

1 participant