Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-11
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Why

- GitGuardex needs a local PR review runner that can reuse already-authenticated Codex or Claude CLI sessions without requiring OpenAI or Anthropic API keys.
- GitHub posting should work with GitHub Actions `GITHUB_TOKEN` or local `gh` auth, with an artifact fallback when GitHub auth is unavailable.

## What Changes

- Add `gx pr-review --provider codex|claude --pr <num> [--post]`.
- Fetch the PR diff through `gh pr diff <num>`, prompt the selected local CLI for structured findings, and post one GitHub review with inline comments through `gh api`.
- Write a markdown review artifact instead of posting when `--post` is omitted or GitHub auth is unavailable.

## Impact

- Scope is CLI-only and self-hosted-runner friendly. It does not introduce model API dependencies or hosted bot state.
- Inline comment accuracy depends on provider output using changed-file paths and changed-line numbers.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## ADDED Requirements

### Requirement: Local PR Review Runner
The system SHALL provide a `gx pr-review` command that reviews a GitHub pull request using an authenticated local agent CLI without requiring OpenAI or Anthropic API tokens.

#### Scenario: Review with GitHub posting
- **WHEN** `gx pr-review --provider codex --pr <number> --post` runs in a repository with GitHub auth
- **THEN** the command reads the pull request diff through `gh pr diff <number>`
- **AND** sends a compact structured-review prompt to the selected local provider
- **AND** posts one GitHub review containing inline comments for returned findings.

#### Scenario: Review without GitHub auth
- **WHEN** `gx pr-review --provider claude --pr <number> --post` runs without `GITHUB_TOKEN`, `GH_TOKEN`, or usable `gh auth`
- **THEN** the command does not require model API credentials
- **AND** writes a markdown review artifact containing the structured findings instead of posting.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Definition of Done

This change is complete only when **all** of the following are true:

- Every checkbox below is checked.
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.

## Handoff

- Handoff: change=`agent-codex-codex-task-2026-05-11-15-20`; branch=`agent/codex/codex-task-2026-05-11-15-20`; scope=`gx pr-review local PR review runner`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
- Copy prompt: Continue `agent-codex-codex-task-2026-05-11-15-20` on branch `agent/codex/codex-task-2026-05-11-15-20`. Work inside the existing sandbox, review `openspec/changes/agent-codex-codex-task-2026-05-11-15-20/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/codex/codex-task-2026-05-11-15-20 --base main --via-pr --wait-for-merge --cleanup`.

## 1. Specification

- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-11-15-20`.
- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`.

## 2. Implementation

- [x] 2.1 Implement scoped behavior changes.
- [x] 2.2 Add/update focused regression coverage.

## 3. Verification

- [x] 3.1 Run targeted project verification commands.
- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-11-15-20 --type change --strict`.
- [x] 3.3 Run `openspec validate --specs`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).
64 changes: 64 additions & 0 deletions src/cli/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,69 @@ function parseReviewArgs(rawArgs) {
};
}

function parsePrReviewArgs(rawArgs) {
const parsed = parseTargetFlag(rawArgs, process.cwd());
const options = {
target: parsed.target,
provider: 'codex',
pr: '',
post: false,
artifact: '',
timeoutMs: 10 * 60 * 1000,
};

for (let index = 0; index < parsed.args.length; index += 1) {
const arg = parsed.args[index];
if (arg === '--provider') {
const next = requireValue(parsed.args, index, '--provider');
if (!['codex', 'claude'].includes(next)) {
throw new Error(`Invalid --provider value: ${next} (expected codex|claude)`);
}
options.provider = next;
index += 1;
continue;
}
if (arg === '--pr') {
options.pr = requireValue(parsed.args, index, '--pr');
index += 1;
continue;
}
if (arg === '--post') {
options.post = true;
continue;
}
if (arg === '--no-post') {
options.post = false;
continue;
}
if (arg === '--artifact' || arg === '--output') {
options.artifact = requireValue(parsed.args, index, arg);
index += 1;
continue;
}
if (arg === '--timeout-ms') {
const raw = requireValue(parsed.args, index, '--timeout-ms');
const parsedTimeout = Number.parseInt(raw, 10);
if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
throw new Error('--timeout-ms requires a positive integer');
}
options.timeoutMs = parsedTimeout;
index += 1;
continue;
}
throw new Error(`Unknown option: ${arg}`);
}

if (!options.pr) {
throw new Error('--pr requires a pull request number');
}
if (!/^\d+$/.test(String(options.pr))) {
throw new Error(`--pr must be a pull request number (received: ${options.pr})`);
}

return options;
}

function parseAgentsArgs(rawArgs) {
const parsed = parseTargetFlag(rawArgs, process.cwd());
const [subcommandRaw = '', ...rest] = parsed.args;
Expand Down Expand Up @@ -1138,6 +1201,7 @@ module.exports = {
parseDoctorArgs,
parseTargetFlag,
parseReviewArgs,
parsePrReviewArgs,
parseAgentsArgs,
parseReportArgs,
parseSyncArgs,
Expand Down
10 changes: 10 additions & 0 deletions src/cli/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { finishAgentSession } = require('../agents/finish');
const sessionSeverityReport = require('../report/session-severity');
const cockpitModule = require('../cockpit');
const agentsStart = require('../agents/start');
const prReviewModule = require('../pr-review');
const {
fs,
path,
Expand Down Expand Up @@ -135,6 +136,7 @@ const {
parseDoctorArgs,
parseTargetFlag,
parseReviewArgs,
parsePrReviewArgs,
parseAgentsArgs,
parseReportArgs,
parseSyncArgs,
Expand Down Expand Up @@ -2541,6 +2543,13 @@ function review(rawArgs) {
process.exitCode = typeof result.status === 'number' ? result.status : 1;
}

function prReview(rawArgs) {
const options = parsePrReviewArgs(rawArgs);
const result = prReviewModule.runPrReview(options);
prReviewModule.printPrReviewResult(result);
process.exitCode = 0;
}

function agentsStatePathForRepo(repoRoot) {
return path.join(repoRoot, AGENTS_BOTS_STATE_RELATIVE);
}
Expand Down Expand Up @@ -3938,6 +3947,7 @@ async function main() {
}

if (command === 'prompt') return prompt(rest);
if (command === 'pr-review') return prReview(rest);
if (command === 'doctor') return doctor(rest);
if (command === 'branch') return branch(rest);
if (command === 'pivot') return pivot(rest);
Expand Down
1 change: 1 addition & 0 deletions src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ const CLI_COMMAND_GROUPS = [
description: 'Review / cleanup bots, AI setup prompts, and safety reports.',
commands: [
['agents', 'Start/stop repo-scoped review + cleanup bots'],
['pr-review', 'Run local Codex/Claude PR review and post inline GitHub comments or write an artifact'],
['cockpit', 'Create or attach to a repo tmux cockpit session'],
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
Expand Down
Loading
Loading