11---
22description : Run AI code review locally using OpenAI API before opening a PR
3- argument-hint : " [--full-registry] [--model <model>] [--dry-run]"
3+ argument-hint : " [--context minimal|standard|deep] [--include-files <files>] [--token-budget <n>] [--force-fresh] [-- full-registry] [--model <model>] [--dry-run]"
44---
55
66# Local AI Code Review
@@ -12,6 +12,15 @@ pre-PR use. Designed for iterative review/revision cycles before submitting a PR
1212## Arguments
1313
1414` $ARGUMENTS ` may contain optional flags:
15+ - ` --context {minimal,standard,deep} ` : Context depth (default: ` standard ` )
16+ - ` minimal ` : Diff only (original behavior)
17+ - ` standard ` : Diff + full contents of changed ` diff_diff/ ` source files
18+ - ` deep ` : Standard + import-graph expansion (files imported by changed files)
19+ - ` --include-files <file1,file2,...> ` : Extra files to include as read-only context
20+ (filenames resolve under ` diff_diff/ ` , or use paths relative to repo root)
21+ - ` --token-budget <n> ` : Max estimated input tokens before dropping import-context
22+ files (default: 200000). Changed source files are always included regardless of budget.
23+ - ` --force-fresh ` : Skip delta-diff mode, run a full fresh review even if previous state exists
1524- ` --full-registry ` : Include the entire REGISTRY.md instead of selective sections
1625- ` --model <name> ` : Override the OpenAI model (default: ` gpt-5.4 ` )
1726- ` --dry-run ` : Print the compiled prompt without calling the API
@@ -31,7 +40,8 @@ before any data is sent externally.
3140### Step 1: Parse Arguments
3241
3342Parse ` $ARGUMENTS ` for the optional flags listed above. All flags are optional —
34- the default behavior (selective registry, gpt-5.4, live API call) requires no arguments.
43+ the default behavior (standard context, selective registry, gpt-5.4, live API call)
44+ requires no arguments.
3545
3646### Step 2: Validate Prerequisites
3747
@@ -146,20 +156,149 @@ Options:
146156
147157If user selects abort, clean up temp files and stop. If continue, proceed.
148158
159+ ### Step 3c: Full-File Secret Scan (for standard/deep context)
160+
161+ When ` --context ` is not ` minimal ` , full source files will be uploaded to OpenAI. The diff-based scan in Step 3b only covers changed lines, so scan the full content of every file that will be transmitted:
162+
163+ ``` bash
164+ # Category 1: Changed diff_diff/ source files (standard/deep)
165+ upload_scan_files=" "
166+ if [ " $context_level " != " minimal" ]; then
167+ upload_scan_files=$( git diff --name-only " ${comparison_ref} ...HEAD" | grep " ^diff_diff/.*\.py$" || true)
168+ fi
169+
170+ # Category 2: All diff_diff/*.py for deep mode (conservative superset of import expansion)
171+ if [ " $context_level " = " deep" ]; then
172+ upload_scan_files=$( find diff_diff -name " *.py" -not -path " */__pycache__/*" 2> /dev/null | sort -u)
173+ fi
174+
175+ # Category 3: --include-files (mirror script's path confinement)
176+ if [ -n " $include_files " ]; then
177+ repo_root_real=$( pwd -P)
178+ for f in $( echo " $include_files " | tr ' ,' ' ' ) ; do
179+ # Reject absolute paths (mirrors script's os.path.isabs check)
180+ case " $f " in /* ) continue ;; esac
181+ # Mirror script's branching: only bare filenames (no /) resolve under diff_diff/
182+ # Slash-containing inputs resolve relative to repo root only
183+ case " $f " in
184+ * /* ) if [ -f " $f " ]; then candidate=" $f " ; else continue ; fi ;;
185+ * ) if [ -f " diff_diff/$f " ]; then candidate=" diff_diff/$f " ; else continue ; fi ;;
186+ esac
187+ # Verify within repo root (mirrors script's realpath containment)
188+ real_candidate=$( cd " $( dirname " $candidate " ) " && pwd -P) /$( basename " $candidate " )
189+ case " $real_candidate " in " $repo_root_real " /* ) upload_scan_files=" $upload_scan_files $candidate " ;; esac
190+ done
191+ fi
192+
193+ # Scan using same canonical content patterns from Step 3b (never echo secret values)
194+ if [ -n " $upload_scan_files " ]; then
195+ secret_hits=$( grep -rlE " (AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" $upload_scan_files 2> /dev/null || true)
196+ sensitive_hits=$( echo " $upload_scan_files " | tr ' ' ' \n' | grep -iE " (\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true)
197+ fi
198+ ```
199+
200+ If either ` secret_hits ` or ` sensitive_hits ` is non-empty, use AskUserQuestion:
201+ ```
202+ Warning: Potential secrets detected in source files that would be uploaded to OpenAI
203+ (full-file scan, not just diff hunks):
204+ - <list filenames>
205+
206+ Options:
207+ 1. Abort — review and remove secrets before retrying
208+ 2. Continue — I confirm these are not real secrets
209+ ```
210+
211+ If user selects abort, clean up temp files and stop. If continue, proceed.
212+
149213### Step 4: Handle Re-Review State
150214
151- If ` .claude/reviews/local-review-latest.md ` exists, preserve it for re-review context:
215+ Check for existing review state and generate delta diff if applicable. ** Validate that the
216+ stored state matches the current branch and base** before reusing — stale state from a
217+ different branch can contaminate re-review context.
218+
219+ ** If ` --force-fresh ` is set** , delete prior state but still seed a new baseline:
152220``` bash
153- if [ -f .claude/reviews/local-review-latest.md ]; then
154- cp .claude/reviews/local-review-latest.md .claude/reviews/local-review-previous.md
155- echo " Previous review preserved for re-review context."
221+ rm -f .claude/reviews/review-state.json
222+ rm -f .claude/reviews/local-review-latest.md
223+ rm -f .claude/reviews/local-review-previous.md
224+ echo " Force-fresh: deleted all prior review state. Fresh review will seed new baseline."
225+ # Do NOT pass --previous-review or --delta-diff/--delta-changed-files
226+ # DO still pass --review-state, --commit-sha, --base-ref so the fresh run seeds a new baseline
227+ ```
228+
229+ ** Otherwise** , validate existing review state using the Python validator (single-point
230+ validation that checks schema version, branch/base match, and required finding fields):
231+ ``` bash
232+ if [ -f .claude/reviews/review-state.json ]; then
233+ # Use the script's validate_review_state() for comprehensive validation
234+ # Returns: last_reviewed_commit and is_valid flag
235+ validation_result=$( python3 -c "
236+ import importlib.util, sys
237+ spec = importlib.util.spec_from_file_location('openai_review', '.claude/scripts/openai_review.py')
238+ mod = importlib.util.module_from_spec(spec)
239+ spec.loader.exec_module(mod)
240+ _, _, commit, valid = mod.validate_review_state(
241+ '.claude/reviews/review-state.json', '$branch_name ', '$comparison_ref '
242+ )
243+ print(f'{commit}|{valid}')
244+ " 2> /dev/null || echo " |False" )
245+ last_reviewed_commit=$( echo " $validation_result " | cut -d' |' -f1)
246+ state_valid=$( echo " $validation_result " | cut -d' |' -f2)
247+
248+ if [ " $state_valid " != " True" ]; then
249+ echo " Warning: review-state.json validation failed. Running fresh review."
250+ rm -f .claude/reviews/review-state.json
251+ rm -f .claude/reviews/local-review-previous.md
252+ last_reviewed_commit=" "
253+ fi
254+
255+ # Only generate delta when state is valid AND commit is an ancestor of HEAD
256+ if [ -n " $last_reviewed_commit " ] && git merge-base --is-ancestor " $last_reviewed_commit " HEAD 2> /dev/null; then
257+ # SHA is a valid ancestor — generate delta diff
258+ git diff --unified=5 " ${last_reviewed_commit} ...HEAD" > /tmp/ai-review-delta-diff.patch
259+ git diff --name-status " ${last_reviewed_commit} ...HEAD" > /tmp/ai-review-delta-files.txt
260+
261+ # Check if delta is empty
262+ if [ ! -s /tmp/ai-review-delta-diff.patch ]; then
263+ echo " No changes since last review (commit ${last_reviewed_commit: 0: 7} ). Use --force-fresh to re-review."
264+ rm -f /tmp/ai-review-delta-diff.patch /tmp/ai-review-delta-files.txt
265+ rm -f /tmp/ai-review-diff.patch /tmp/ai-review-files.txt
266+ # Stop here
267+ fi
268+ # State validated and delta generated — preserve previous review for re-review context
269+ if [ -f .claude/reviews/local-review-latest.md ]; then
270+ cp .claude/reviews/local-review-latest.md .claude/reviews/local-review-previous.md
271+ echo " Previous review preserved for re-review context."
272+ fi
273+ else
274+ echo " Warning: Previous review commit is not an ancestor of HEAD (likely rebase). Running fresh review."
275+ rm -f .claude/reviews/review-state.json
276+ rm -f .claude/reviews/local-review-previous.md
277+ fi
278+ fi
279+
280+ # Final cleanup: if delta mode was NOT activated (no delta files generated),
281+ # ensure no stale previous-review file can leak into the fresh review.
282+ # This covers the case where review-state.json is missing entirely but
283+ # local-review-previous.md lingers from a prior run.
284+ if [ ! -f /tmp/ai-review-delta-diff.patch ]; then
285+ rm -f .claude/reviews/local-review-previous.md
156286fi
157287```
158288
289+ ** Important** : Previous review text is ONLY preserved when delta mode is active (delta files
290+ generated and state validated). When delta mode is NOT active — whether because
291+ ` review-state.json ` is missing, branch/base mismatch, non-ancestor, or ` --force-fresh ` —
292+ the previous review file is deleted to prevent stale findings from leaking into a fresh
293+ review via ` --previous-review ` .
294+
159295### Step 5: Run the Review Script
160296
161- Build and run the command. Include ` --previous-review ` only if the previous review file
162- exists from Step 4.
297+ Build and run the command. Include optional arguments only when their conditions are met:
298+ - ` --previous-review ` : only if ` .claude/reviews/local-review-previous.md ` exists AND ` --force-fresh ` was NOT set
299+ - ` --delta-diff ` and ` --delta-changed-files ` : only if delta files were generated in Step 4
300+ - ` --review-state ` , ` --commit-sha ` , ` --base-ref ` : always include (even with ` --force-fresh ` , to seed a new baseline)
301+ - ` --context ` , ` --include-files ` , ` --token-budget ` : pass through from parsed arguments
163302
164303``` bash
165304python3 .claude/scripts/openai_review.py \
@@ -169,14 +308,26 @@ python3 .claude/scripts/openai_review.py \
169308 --changed-files /tmp/ai-review-files.txt \
170309 --output .claude/reviews/local-review-latest.md \
171310 --branch-info " $branch_name " \
311+ --repo-root " $( pwd) " \
312+ --context " $context_level " \
313+ --review-state .claude/reviews/review-state.json \
314+ --commit-sha " $( git rev-parse HEAD) " \
315+ --base-ref " $comparison_ref " \
172316 [--previous-review .claude/reviews/local-review-previous.md] \
317+ [--delta-diff /tmp/ai-review-delta-diff.patch] \
318+ [--delta-changed-files /tmp/ai-review-delta-files.txt] \
319+ [--include-files " $include_files " ] \
320+ [--token-budget " $token_budget " ] \
173321 [--full-registry] \
174322 [--model < model> ] \
175323 [--dry-run]
176324```
177325
178- If ` --dry-run ` : display the prompt output and stop. Report the estimated token count
179- and model that would be used.
326+ Note: ` --force-fresh ` is a skill-only flag — it controls whether delta diffs are
327+ generated in Step 4 and is NOT passed to the script.
328+
329+ If ` --dry-run ` : display the prompt output and stop. Report the estimated token count,
330+ cost estimate, and model that would be used.
180331
181332If the script exits non-zero, display the error output and stop.
182333
@@ -191,6 +342,10 @@ Parse the review output to extract ALL findings. For each finding, capture:
191342- Section (Methodology, Code Quality, etc.)
192343- One-line summary of the issue
193344
345+ Note: The script handles writing ` review-state.json ` automatically (finding tracking
346+ across rounds). The skill does NOT need to write JSON — just pass ` --review-state `
347+ and ` --commit-sha ` to the script.
348+
194349Present a ** findings summary** showing every finding, grouped by severity:
195350
196351```
@@ -243,13 +398,21 @@ directly — for P0/P1 issues use `EnterPlanMode` for a structured approach; for
243398issues, fix them directly since they are minor.
244399
245400After fixes are committed, the user re-runs ` /ai-review-local ` for a follow-up review.
401+ On re-review, the script automatically activates delta-diff mode (comparing only
402+ changes since the last reviewed commit) and shows a structured findings table
403+ tracking which previous findings have been addressed.
246404
247405### Step 8: Cleanup
248406
249407``` bash
250- rm -f /tmp/ai-review-diff.patch /tmp/ai-review-files.txt
408+ rm -f /tmp/ai-review-diff.patch /tmp/ai-review-files.txt \
409+ /tmp/ai-review-delta-diff.patch /tmp/ai-review-delta-files.txt
251410```
252411
412+ Note: ` .claude/reviews/review-state.json ` is preserved across review rounds (it
413+ tracks the last reviewed commit and findings). It is cleaned up when the user
414+ runs ` --force-fresh ` or when a rebase invalidates the tracked commit.
415+
253416## Error Handling
254417
255418| Scenario | Response |
@@ -260,28 +423,57 @@ rm -f /tmp/ai-review-diff.patch /tmp/ai-review-files.txt
260423| Script exits non-zero | Display stderr output from script |
261424| Previous review file missing on re-run | Script warns and continues as fresh review |
262425| User aborts due to uncommitted changes | Clean exit |
426+ | No changes since last review (empty delta) | Report and stop, suggest ` --force-fresh ` |
427+ | Rebase invalidates last reviewed commit | Warn, delete stale state, run fresh review |
428+ | ` review-state.json ` schema mismatch | Script warns and starts fresh |
263429
264430## Examples
265431
266432``` bash
267- # Standard review of current branch vs main
433+ # Standard review of current branch vs main (default: full source file context)
268434/ai-review-local
269435
270- # Review with full methodology registry
271- /ai-review-local --full-registry
436+ # Review with minimal context (diff only, original behavior)
437+ /ai-review-local --context minimal
438+
439+ # Review with deep context (changed files + imported files)
440+ /ai-review-local --context deep
441+
442+ # Include specific files as extra context
443+ /ai-review-local --include-files linalg.py,utils.py
272444
273445# Preview the compiled prompt without calling the API
274446/ai-review-local --dry-run
275447
276- # Use a different model
277- /ai-review-local --model gpt-4.1
448+ # Force a fresh review (ignore previous review state)
449+ /ai-review-local --force-fresh
450+
451+ # Use a different model with full registry
452+ /ai-review-local --model gpt-4.1 --full-registry
453+
454+ # Limit token budget for faster/cheaper reviews
455+ /ai-review-local --token-budget 100000
278456```
279457
280458## Notes
281459
282460- This skill does NOT modify source files — it only generates temp files and
283461 review artifacts in ` .claude/reviews/ ` (which is gitignored). It may also
284462 create a commit if there are uncommitted changes (Step 3).
463+ - ** Context levels** : By default (` standard ` ), the full contents of changed
464+ ` diff_diff/ ` source files are sent alongside the diff. This catches "sins of
465+ omission" — code that should have changed but wasn't (e.g., a wrapper missing
466+ a new parameter). Use ` --context deep ` to also include files imported by
467+ changed files as read-only reference.
468+ - ** Delta-diff re-review** : When ` review-state.json ` exists from a previous run,
469+ the script automatically generates a delta diff (changes since the last reviewed
470+ commit) and focuses the reviewer on those changes. The full branch diff is
471+ included as reference context. Use ` --force-fresh ` to bypass this.
472+ - ** Finding tracking** : The script writes structured findings to ` review-state.json `
473+ after each review. On re-review, previous findings are shown in a table with
474+ their status (open/addressed), enabling the reviewer to focus on what changed.
475+ - ** Cost visibility** : The script shows estimated cost before the API call and
476+ actual cost (from the API response) after completion.
285477- Re-review mode activates automatically when a previous review exists in
286478 ` .claude/reviews/local-review-latest.md `
287479- The review criteria are adapted from ` .github/codex/prompts/pr_review.md ` (same
@@ -290,8 +482,9 @@ rm -f /tmp/ai-review-diff.patch /tmp/ai-review-files.txt
290482- The CI review (Codex action with full repo access) remains the authoritative final
291483 check — local review is a fast first pass to catch most issues early
292484- ** Data transmission** : In non-dry-run mode, this skill transmits the unified diff,
293- changed-file metadata, selected methodology registry text, and prior review context
294- (if present) to OpenAI via the Chat Completions API. Use ` --dry-run ` to preview
295- exactly what would be sent.
485+ changed-file metadata, full source file contents (in standard/deep mode),
486+ import-context files (in deep mode), selected methodology registry text, and
487+ prior review context (if present) to OpenAI via the Chat Completions API.
488+ Use ` --dry-run ` to preview exactly what would be sent.
296489- This skill pairs naturally with the iterative workflow:
297490 ` /ai-review-local ` -> address findings -> ` /ai-review-local ` -> ` /submit-pr `
0 commit comments