Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1f8d417
feat(git): add branch utilities for plan command
afewyards Apr 26, 2026
b75ae33
feat(plan): add prompt module for plan generation
afewyards Apr 26, 2026
f837442
feat(plan): add orchestration module for plan generation
afewyards Apr 26, 2026
9988eeb
feat: add plan() entry point
afewyards Apr 26, 2026
b254072
feat: add :CodeReviewPlan command
afewyards Apr 26, 2026
736c084
test(plan): add integration tests
afewyards Apr 26, 2026
6a14acc
fix(plan): handle nil paths and limit concurrent AI calls
afewyards Apr 28, 2026
8dbff1d
refactor(ai): extract shared orchestrator from plan/review
afewyards Apr 29, 2026
d9b621a
feat(ai): skip lockfiles, generated, vendored and binary diffs
afewyards Apr 29, 2026
3a0188b
refactor(ai): drop Phase 1 summary and per-file Branch Context
afewyards Apr 29, 2026
eacc495
feat(ai): cache-friendly prompt order + Anthropic cache_control
afewyards Apr 29, 2026
b2a4154
feat(ai): per-file progress reporting for CLI providers
afewyards Apr 29, 2026
f027d0d
feat(ai): batch files into single AI calls by token budget
afewyards Apr 29, 2026
8564b30
fix(ai): address code review findings (filtered-files spinner, progre…
afewyards Apr 29, 2026
41e0b1c
feat(ai): add ai_skip_patterns parser to auth module
afewyards May 4, 2026
d57128a
feat(ai): merge dotfile skip patterns in orchestrator
afewyards May 4, 2026
9c4f355
feat(ai): merge dotfile skip patterns in review init
afewyards May 4, 2026
e74b249
refactor(ai): extract get_all_skip_patterns helper to file_filter
afewyards May 4, 2026
7be4028
docs: add ai_skip_patterns configuration
afewyards May 4, 2026
b1307e4
fix(ai): URL encoding and glob pattern matching bugs
afewyards May 4, 2026
701ab1c
fix(api): convert JSON null to Lua nil on decode
afewyards May 13, 2026
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Lines starting with `#` are comments. Keys and values are trimmed of whitespace.
| `project` | `owner/repo` (auto-detected from git remote if omitted) |
| `base_url` | API URL override (e.g., self-hosted GitLab instance) |
| `token` | Auth token for this project |
| `ai_skip_patterns` | Comma-separated glob patterns — files matching any pattern are excluded from AI review |

> **Security:** Add `.codereview.nvim` to your `.gitignore` if it contains a token.

Expand Down Expand Up @@ -229,6 +230,16 @@ The `ai.review_level` option controls the verbosity of AI code reviews. Higher l

The AI is instructed to skip items below the configured level, saving tokens and reducing noise. To see lower-severity items again, change the level and re-run the AI review.

### AI Skip Patterns

Skip specific files from AI review by adding patterns to `.codereview.nvim`:

```
ai_skip_patterns=*.test.ts,docs/**,*.snap,fixtures/**
```

Patterns are comma-separated globs, merged with plugin config `ai.skip_patterns` and built-in defaults (lockfiles, minified files, generated code, vendor directories).

## Default Keymaps

### Navigation
Expand Down
321 changes: 321 additions & 0 deletions docs/plans/2026-05-04-ai-skip-patterns-config-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
# AI Skip Patterns Config Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Allow per-repo AI skip patterns via `ai_skip_patterns` key in `.codereview.nvim` file.

**Architecture:** Parse comma-separated patterns from dotfile, expose via auth module, merge with Lua config patterns at filter call sites.

**Tech Stack:** Lua, Neovim plugin APIs

---

## Task 1: Add pattern parser to auth.lua

**Files:**
- Modify: `lua/codereview/api/auth.lua:115-118`
- Test: `tests/codereview/auth_skip_patterns_spec.lua` (create)

**Step 1: Write failing test**

Create `tests/codereview/auth_skip_patterns_spec.lua`:

```lua
local auth = require("codereview.api.auth")

describe("auth.get_ai_skip_patterns", function()
before_each(function()
auth.reset()
end)

it("returns empty table when no config file", function()
local patterns = auth.get_ai_skip_patterns()
assert.same({}, patterns)
end)

it("parses comma-separated patterns", function()
-- Mock will be needed; for now test the parse logic
local parse = auth._parse_skip_patterns_for_test
local result = parse("*.test.ts,docs/**,*.snap")
assert.same({ "*.test.ts", "docs/**", "*.snap" }, result)
end)

it("trims whitespace around patterns", function()
local parse = auth._parse_skip_patterns_for_test
local result = parse(" *.test.ts , docs/** ,*.snap ")
assert.same({ "*.test.ts", "docs/**", "*.snap" }, result)
end)

it("handles empty string", function()
local parse = auth._parse_skip_patterns_for_test
local result = parse("")
assert.same({}, result)
end)
end)
```

**Step 2: Run test to verify it fails**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedFile tests/codereview/auth_skip_patterns_spec.lua"`

Expected: FAIL — `get_ai_skip_patterns` and `_parse_skip_patterns_for_test` not defined

**Step 3: Implement in auth.lua**

Add before `return M` (around line 117):

```lua
local function parse_skip_patterns(value)
if not value or value == "" then
return {}
end
local patterns = {}
for pat in value:gmatch("[^,]+") do
local trimmed = pat:match("^%s*(.-)%s*$")
if trimmed ~= "" then
table.insert(patterns, trimmed)
end
end
return patterns
end

function M.get_ai_skip_patterns()
local file_config = read_config_file()
if not file_config or not file_config.ai_skip_patterns then
return {}
end
return parse_skip_patterns(file_config.ai_skip_patterns)
end

M._parse_skip_patterns_for_test = parse_skip_patterns
```

**Step 4: Run test to verify it passes**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedFile tests/codereview/auth_skip_patterns_spec.lua"`

Expected: PASS

**Step 5: Commit**

```bash
git add lua/codereview/api/auth.lua tests/codereview/auth_skip_patterns_spec.lua
git commit -m "feat(ai): add ai_skip_patterns parser to auth module"
```

---

## Task 2: Update orchestrator.lua to merge patterns

**Files:**
- Modify: `lua/codereview/ai/orchestrator.lua:28-31`

**Step 1: Write failing test**

Create `tests/codereview/ai/orchestrator_skip_patterns_spec.lua`:

```lua
describe("orchestrator skip patterns merge", function()
local orchestrator = require("codereview.ai.orchestrator")
local auth = require("codereview.api.auth")
local config = require("codereview.config")

before_each(function()
auth.reset()
end)

it("merges dotfile patterns with config patterns", function()
-- This is an integration concern; verify via file_filter.apply call
-- The key behavior: both sources should be combined
local file_filter = require("codereview.ai.file_filter")
local diffs = {
{ new_path = "src/app.ts", diff = "..." },
{ new_path = "src/app.test.ts", diff = "..." },
}
-- With pattern *.test.ts, second file should be filtered
local result = file_filter.apply(diffs, { "*.test.ts" })
assert.equals(1, #result)
assert.equals("src/app.ts", result[1].new_path)
end)
end)
```

**Step 2: Run test to verify baseline**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedFile tests/codereview/ai/orchestrator_skip_patterns_spec.lua"`

Expected: PASS (baseline — file_filter works)

**Step 3: Update orchestrator.lua**

Change line 31 from:

```lua
spec.diffs = file_filter.apply(spec.diffs or {}, (cfg.ai or {}).skip_patterns)
```

To:

```lua
local auth = require("codereview.api.auth")
local lua_patterns = (cfg.ai or {}).skip_patterns or {}
local dotfile_patterns = auth.get_ai_skip_patterns()
local merged = {}
for _, p in ipairs(lua_patterns) do table.insert(merged, p) end
for _, p in ipairs(dotfile_patterns) do table.insert(merged, p) end
spec.diffs = file_filter.apply(spec.diffs or {}, merged)
```

**Step 4: Run test to verify it passes**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedFile tests/codereview/ai/orchestrator_skip_patterns_spec.lua"`

Expected: PASS

**Step 5: Commit**

```bash
git add lua/codereview/ai/orchestrator.lua tests/codereview/ai/orchestrator_skip_patterns_spec.lua
git commit -m "feat(ai): merge dotfile skip patterns in orchestrator"
```

---

## Task 3: Update review/init.lua to merge patterns

**Files:**
- Modify: `lua/codereview/review/init.lua:185`

**Step 1: Locate and update**

Change line 185 from:

```lua
local filtered_diffs = file_filter.apply(diffs, (cfg.ai or {}).skip_patterns)
```

To:

```lua
local auth = require("codereview.api.auth")
local lua_patterns = (cfg.ai or {}).skip_patterns or {}
local dotfile_patterns = auth.get_ai_skip_patterns()
local merged = {}
for _, p in ipairs(lua_patterns) do table.insert(merged, p) end
for _, p in ipairs(dotfile_patterns) do table.insert(merged, p) end
local filtered_diffs = file_filter.apply(diffs, merged)
```

**Step 2: Run existing tests**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/codereview/"`

Expected: All tests PASS

**Step 3: Commit**

```bash
git add lua/codereview/review/init.lua
git commit -m "feat(ai): merge dotfile skip patterns in review init"
```

---

## Task 4: Extract merge helper to file_filter.lua (DRY)

**Files:**
- Modify: `lua/codereview/ai/file_filter.lua`
- Modify: `lua/codereview/ai/orchestrator.lua`
- Modify: `lua/codereview/review/init.lua`

**Step 1: Add helper to file_filter.lua**

Add before `return M`:

```lua
function M.get_all_skip_patterns()
local config = require("codereview.config").get()
local auth = require("codereview.api.auth")
local lua_patterns = (config.ai or {}).skip_patterns or {}
local dotfile_patterns = auth.get_ai_skip_patterns()
local merged = {}
for _, p in ipairs(lua_patterns) do table.insert(merged, p) end
for _, p in ipairs(dotfile_patterns) do table.insert(merged, p) end
return merged
end
```

**Step 2: Simplify orchestrator.lua call site**

Replace the merge logic with:

```lua
spec.diffs = file_filter.apply(spec.diffs or {}, file_filter.get_all_skip_patterns())
```

**Step 3: Simplify review/init.lua call site**

Replace the merge logic with:

```lua
local filtered_diffs = file_filter.apply(diffs, file_filter.get_all_skip_patterns())
```

**Step 4: Run all tests**

Run: `cd /Users/kleist/Sites/codereview.nvim && nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/codereview/"`

Expected: All tests PASS

**Step 5: Commit**

```bash
git add lua/codereview/ai/file_filter.lua lua/codereview/ai/orchestrator.lua lua/codereview/review/init.lua
git commit -m "refactor(ai): extract get_all_skip_patterns helper to file_filter"
```

---

## Task 5: Update README documentation

**Files:**
- Modify: `README.md` (configuration section)

**Step 1: Find config docs section**

Search for existing `.codereview.nvim` documentation in README.

**Step 2: Add ai_skip_patterns docs**

Add to the `.codereview.nvim` section:

```markdown
### AI Skip Patterns

Skip specific files from AI review by adding patterns to `.codereview.nvim`:

```
ai_skip_patterns=*.test.ts,docs/**,*.snap,fixtures/**
```

Patterns are comma-separated globs, merged with plugin config `ai.skip_patterns` and built-in defaults (lockfiles, minified files, generated code, vendor directories).
```

**Step 3: Commit**

```bash
git add README.md
git commit -m "docs: add ai_skip_patterns configuration"
```

---

## Summary

| Task | Description | Deps |
|------|-------------|------|
| 1 | Parser in auth.lua + tests | — |
| 2 | Merge in orchestrator.lua | 1 |
| 3 | Merge in review/init.lua | 1 |
| 4 | Extract DRY helper | 2, 3 |
| 5 | README docs | 4 |
34 changes: 34 additions & 0 deletions lua/codereview/ai/batch.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
local M = {}

--- Group diffs into batches by character budget and file count cap.
--- An oversize file (diff alone exceeds budget) goes in its own batch.
---
--- @param diffs table[] List of {new_path, old_path, diff}
--- @param opts table? {char_budget: integer, max_files: integer}
--- @return table[][] List of batches; each batch is a list of diffs
function M.build(diffs, opts)
local budget = (opts and opts.char_budget) or 80000
local cap = (opts and opts.max_files) or 15
local batches = {}
local cur = {}
local cur_size = 0

for _, f in ipairs(diffs) do
local sz = #(f.diff or "")
if (#cur > 0) and (cur_size + sz > budget or #cur >= cap) then
table.insert(batches, cur)
cur = {}
cur_size = 0
end
table.insert(cur, f)
cur_size = cur_size + sz
end

if #cur > 0 then
table.insert(batches, cur)
end

return batches
end

return M
Loading
Loading