diff --git a/README.md b/README.md index 42072a5..87d85ac 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,16 @@ require("codereview").setup({ -- AI review ai = { enabled = true, - provider = "claude_cli", -- "claude_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd" + provider = "claude_cli", -- "claude_cli" | "codex_cli" | "copilot_cli" | "gemini_cli" | "opencode_cli" | "qwen_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd" review_level = "info", -- "info" | "suggestion" | "warning" | "error" max_file_size = 500, -- skip files larger than N lines (0 = unlimited) claude_cli = { cmd = "claude", agent = "code-review" }, + codex_cli = { cmd = "codex", model = nil }, + copilot_cli = { cmd = "copilot", model = nil, agent = nil }, + gemini_cli = { cmd = "gemini", model = nil }, + opencode_cli = { cmd = "opencode", model = nil, agent = "plan", variant = nil }, + qwen_cli = { cmd = "qwen", model = nil }, anthropic = { api_key = nil, model = "claude-sonnet-4-20250514" }, openai = { api_key = nil, model = "gpt-4o", base_url = nil }, ollama = { model = "llama3", base_url = "http://localhost:11434" }, @@ -185,6 +190,11 @@ Set `ai.provider` to choose a backend. Each provider has its own sub-table: | Provider | Config key | Requirements | |----------|-----------|--------------| | Claude CLI | `claude_cli` | [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) installed | +| Codex CLI | `codex_cli` | [Codex CLI](https://github.com/openai/codex) installed and authenticated | +| Copilot CLI | `copilot_cli` | [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-copilot-cli) installed and authenticated | +| Gemini CLI | `gemini_cli` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed and authenticated | +| OpenCode CLI | `opencode_cli` | [OpenCode CLI](https://github.com/anomalyco/opencode) installed and authenticated | +| Qwen CLI | `qwen_cli` | [Qwen CLI](https://github.com/QwenLM/qwen-code) installed and authenticated | | Anthropic API | `anthropic` | `api_key` set | | OpenAI | `openai` | `api_key` set | | Ollama | `ollama` | Ollama running locally | diff --git a/doc/codereview.txt b/doc/codereview.txt index ef3e1bd..679ea36 100644 --- a/doc/codereview.txt +++ b/doc/codereview.txt @@ -306,7 +306,7 @@ Options for AI review. See |codereview-ai-review|. -- boolean enable AI review features enabled = true, - -- "claude_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd" + -- "claude_cli" | "codex_cli" | "copilot_cli" | "gemini_cli" | "opencode_cli" | "qwen_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd" provider = "claude_cli", -- "info" | "suggestion" | "warning" | "error" minimum severity to display @@ -321,6 +321,29 @@ Options for AI review. See |codereview-ai-review|. cmd = "claude", -- path or name of the claude binary agent = "code-review", -- Claude agent/profile name }, + codex_cli = { + cmd = "codex", -- path or name of the codex binary + model = nil, -- optional model override + }, + copilot_cli = { + cmd = "copilot", -- path or name of the copilot binary + model = nil, -- optional model override + agent = nil, -- optional agent override + }, + gemini_cli = { + cmd = "gemini", -- path or name of the gemini binary + model = nil, -- optional model override + }, + opencode_cli = { + cmd = "opencode", -- path or name of the opencode binary + model = nil, -- optional model override + agent = "plan", -- optional agent override + variant = nil, -- optional model variant (provider-specific) + }, + qwen_cli = { + cmd = "qwen", -- path or name of the qwen binary + model = nil, -- optional model override + }, anthropic = { api_key = nil, -- falls back to ANTHROPIC_API_KEY model = "claude-sonnet-4-20250514", @@ -380,6 +403,104 @@ Requirements: } < +------------------------------------------------------------------------------ +Codex CLI *codereview-ai-codex-cli* + +Uses the `codex exec` command-line interface to prompt non-interactively using +the currently configured or default model until explicitly specified. + +Requirements: + - `codex` binary installed and on PATH + - Codex CLI already authenticated +>lua + ai = { + provider = "codex_cli", + codex_cli = { + cmd = "codex", -- binary name or full path + model = "gpt-5.4", -- optional model override + }, + } +< + +------------------------------------------------------------------------------ +Copilot CLI *codereview-ai-copilot-cli* + +Uses the `copilot` command-line interface to prompt non-interactively using +the currently configured or default model until explicitly specified. + +Requirements: + - `copilot` binary installed and on PATH + - Copilot CLI already authenticated +>lua + ai = { + provider = "copilot_cli", + copilot_cli = { + cmd = "copilot", -- binary name or full path + model = "claude-opus-4.6", -- optional model override + agent = nil -- specify a custom agent to use + }, + } +< + +------------------------------------------------------------------------------ +Gemini CLI *codereview-ai-gemini-cli* + +Uses the `gemini` command-line interface to prompt non-interactively using +the currently configured or default model until explicitly specified. + +Requirements: + - `gemini` binary installed and on PATH + - Gemini CLI already authenticated +>lua + ai = { + provider = "gemini_cli", + gemini_cli = { + cmd = "gemini", -- binary name or full path + model = "gemini-3-pro", -- optional model override + }, + } +< + +------------------------------------------------------------------------------ +OpenCode CLI *codereview-ai-opencode-cli* + +Uses the `opencode run` command-line interface to prompt non-interactively using +the currently configured or default model until explicitly specified. + +Requirements: + - `opencode` binary installed and on PATH + - OpenCode CLI already authenticated +>lua + ai = { + provider = "opencode_cli", + opencode_cli = { + cmd = "opencode", -- binary name or full path + model = "github-copilot/claude-sonnet-4.6", -- optional provider/model + agent = "plan", -- optional agent to use + variant = nil, -- model variant + }, + } +< + +------------------------------------------------------------------------------ +Qwen CLI *codereview-ai-qwen-cli* + +Uses the `qwen` command-line interface to prompt non-interactively using +the currently configured or default model until explicitly specified. + +Requirements: + - `qwen` binary installed and on PATH + - Qwen CLI already authenticated +>lua + ai = { + provider = "qwen_cli", + qwen_cli = { + cmd = "qwen", -- binary name or full path + model = "qwen3-coder-plus", -- optional model override + }, + } +< + ------------------------------------------------------------------------------ Anthropic API *codereview-ai-anthropic* diff --git a/lua/codereview/ai/providers/claude_cli.lua b/lua/codereview/ai/providers/claude_cli.lua index 8552bad..bf20a6b 100644 --- a/lua/codereview/ai/providers/claude_cli.lua +++ b/lua/codereview/ai/providers/claude_cli.lua @@ -1,5 +1,5 @@ local config = require("codereview.config") -local log = require("codereview.log") +local utils = require("codereview.ai.providers.utils") local M = {} function M.build_cmd(claude_cmd, agent) @@ -23,71 +23,7 @@ function M.run(prompt, callback, opts) local claude_cmd = pcfg.cmd or cfg.ai.claude_cmd or "claude" local agent = (not opts.skip_agent) and (pcfg.agent or cfg.ai.agent) or nil local cmd = M.build_cmd(claude_cmd, agent) - log.debug("AI claude_cli: starting " .. table.concat(cmd, " ")) - local stdout_chunks = {} - local stderr_chunks = {} - local done = false - - local job_id = vim.fn.jobstart(cmd, { - stdout_buffered = true, - stderr_buffered = true, - on_stdout = function(_, data) - if data then - for i, chunk in ipairs(data) do - if i < #data or chunk ~= "" then - table.insert(stdout_chunks, chunk) - end - end - end - end, - on_stderr = function(_, data) - if data then - for i, chunk in ipairs(data) do - if i < #data or chunk ~= "" then - table.insert(stderr_chunks, chunk) - end - end - end - end, - on_exit = function(_, code) - if done then - return - end - done = true - vim.schedule(function() - local stderr_str = table.concat(stderr_chunks, "\n") - if stderr_str ~= "" then - log.warn("AI claude_cli stderr: " .. stderr_str) - end - local output = table.concat(stdout_chunks, "\n") - if code ~= 0 then - local msg = "Claude CLI exited with code " .. code - if output ~= "" then - msg = msg .. "\n" .. output - end - if stderr_str ~= "" then - msg = msg .. "\n" .. stderr_str - end - log.error("AI claude_cli: " .. msg) - callback(nil, msg) - else - log.debug("AI claude_cli: completed, output length=" .. #output) - callback(output) - end - end) - end, - }) - - if job_id <= 0 then - local msg = "Failed to start Claude CLI. Is '" .. claude_cmd .. "' in your PATH?" - log.error("AI claude_cli: " .. msg) - callback(nil, msg) - return - end - - vim.fn.chansend(job_id, prompt) - vim.fn.chanclose(job_id, "stdin") - return job_id + return utils.run_cli(prompt, callback, cmd) end return M diff --git a/lua/codereview/ai/providers/codex_cli.lua b/lua/codereview/ai/providers/codex_cli.lua new file mode 100644 index 0000000..5a6deb0 --- /dev/null +++ b/lua/codereview/ai/providers/codex_cli.lua @@ -0,0 +1,28 @@ +local config = require("codereview.config") +local utils = require("codereview.ai.providers.utils") +local M = {} + +function M.build_cmd(codex_cmd, model) + local cmd = { codex_cmd or "codex", "exec" } + table.insert(cmd, "--sandbox=read-only") + if model and model ~= "" then + table.insert(cmd, "--model") + table.insert(cmd, model) + end + table.insert(cmd, "--") + return cmd +end + +function M.run(prompt, callback) + local cfg = config.get() + if not cfg.ai.enabled then + callback(nil, "AI review is disabled in config") + return + end + + local pcfg = cfg.ai.codex_cli or {} + local cmd = M.build_cmd(pcfg.cmd, pcfg.model) + return utils.run_cli(prompt, callback, cmd) +end + +return M diff --git a/lua/codereview/ai/providers/copilot_cli.lua b/lua/codereview/ai/providers/copilot_cli.lua new file mode 100644 index 0000000..5910df1 --- /dev/null +++ b/lua/codereview/ai/providers/copilot_cli.lua @@ -0,0 +1,32 @@ +local config = require("codereview.config") +local utils = require("codereview.ai.providers.utils") +local M = {} + +function M.build_cmd(copilot_cmd, model, agent) + local cmd = { copilot_cmd or "copilot" } + table.insert(cmd, "--silent") + table.insert(cmd, "--no-auto-update") + if model and model ~= "" then + table.insert(cmd, "--model") + table.insert(cmd, model) + end + if agent and agent ~= "" then + table.insert(cmd, "--agent") + table.insert(cmd, agent) + end + return cmd +end + +function M.run(prompt, callback) + local cfg = config.get() + if not cfg.ai.enabled then + callback(nil, "AI review is disabled in config") + return + end + + local pcfg = cfg.ai.copilot_cli or {} + local cmd = M.build_cmd(pcfg.cmd, pcfg.model, pcfg.agent) + return utils.run_cli(prompt, callback, cmd) +end + +return M diff --git a/lua/codereview/ai/providers/custom_cmd.lua b/lua/codereview/ai/providers/custom_cmd.lua index 0e0dc72..8be5df8 100644 --- a/lua/codereview/ai/providers/custom_cmd.lua +++ b/lua/codereview/ai/providers/custom_cmd.lua @@ -1,5 +1,5 @@ local config = require("codereview.config") -local log = require("codereview.log") +local utils = require("codereview.ai.providers.utils") local M = {} function M.build_cmd(cmd, args) @@ -10,7 +10,7 @@ function M.build_cmd(cmd, args) return t end -function M.run(prompt, callback, opts) +function M.run(prompt, callback) local cfg = config.get() if not cfg.ai.enabled then callback(nil, "AI review is disabled in config") @@ -24,71 +24,7 @@ function M.run(prompt, callback, opts) end local cmd = M.build_cmd(pcfg.cmd, pcfg.args) - log.debug("AI custom_cmd: starting " .. table.concat(cmd, " ")) - local stdout_chunks = {} - local stderr_chunks = {} - local done = false - - local job_id = vim.fn.jobstart(cmd, { - stdout_buffered = true, - stderr_buffered = true, - on_stdout = function(_, data) - if data then - for i, chunk in ipairs(data) do - if i < #data or chunk ~= "" then - table.insert(stdout_chunks, chunk) - end - end - end - end, - on_stderr = function(_, data) - if data then - for i, chunk in ipairs(data) do - if i < #data or chunk ~= "" then - table.insert(stderr_chunks, chunk) - end - end - end - end, - on_exit = function(_, code) - if done then - return - end - done = true - vim.schedule(function() - local stderr_str = table.concat(stderr_chunks, "\n") - if stderr_str ~= "" then - log.warn("AI custom_cmd stderr: " .. stderr_str) - end - local output = table.concat(stdout_chunks, "\n") - if code ~= 0 then - local msg = "Custom command exited with code " .. code - if output ~= "" then - msg = msg .. "\n" .. output - end - if stderr_str ~= "" then - msg = msg .. "\n" .. stderr_str - end - log.error("AI custom_cmd: " .. msg) - callback(nil, msg) - else - log.debug("AI custom_cmd: completed, output length=" .. #output) - callback(output) - end - end) - end, - }) - - if job_id <= 0 then - local msg = "Failed to start '" .. pcfg.cmd .. "'. Is it in your PATH?" - log.error("AI custom_cmd: " .. msg) - callback(nil, msg) - return - end - - vim.fn.chansend(job_id, prompt) - vim.fn.chanclose(job_id, "stdin") - return job_id + return utils.run_cli(prompt, callback, cmd) end return M diff --git a/lua/codereview/ai/providers/gemini_cli.lua b/lua/codereview/ai/providers/gemini_cli.lua new file mode 100644 index 0000000..beffe03 --- /dev/null +++ b/lua/codereview/ai/providers/gemini_cli.lua @@ -0,0 +1,27 @@ +local config = require("codereview.config") +local utils = require("codereview.ai.providers.utils") +local M = {} + +function M.build_cmd(gemini_cmd, model) + local cmd = { gemini_cmd or "gemini" } + table.insert(cmd, "--approval-mode=plan") + if model and model ~= "" then + table.insert(cmd, "--model") + table.insert(cmd, model) + end + return cmd +end + +function M.run(prompt, callback) + local cfg = config.get() + if not cfg.ai.enabled then + callback(nil, "AI review is disabled in config") + return + end + + local pcfg = cfg.ai.gemini_cli or {} + local cmd = M.build_cmd(pcfg.cmd, pcfg.model) + return utils.run_cli(prompt, callback, cmd) +end + +return M diff --git a/lua/codereview/ai/providers/init.lua b/lua/codereview/ai/providers/init.lua index 14307d3..5d25930 100644 --- a/lua/codereview/ai/providers/init.lua +++ b/lua/codereview/ai/providers/init.lua @@ -2,6 +2,11 @@ local M = {} local valid_providers = { claude_cli = "codereview.ai.providers.claude_cli", + codex_cli = "codereview.ai.providers.codex_cli", + copilot_cli = "codereview.ai.providers.copilot_cli", + gemini_cli = "codereview.ai.providers.gemini_cli", + opencode_cli = "codereview.ai.providers.opencode_cli", + qwen_cli = "codereview.ai.providers.qwen_cli", anthropic = "codereview.ai.providers.anthropic", openai = "codereview.ai.providers.openai", ollama = "codereview.ai.providers.ollama", diff --git a/lua/codereview/ai/providers/opencode_cli.lua b/lua/codereview/ai/providers/opencode_cli.lua new file mode 100644 index 0000000..f322d7b --- /dev/null +++ b/lua/codereview/ai/providers/opencode_cli.lua @@ -0,0 +1,35 @@ +local config = require("codereview.config") +local utils = require("codereview.ai.providers.utils") +local M = {} + +function M.build_cmd(opencode_cmd, model, agent, variant) + local cmd = { opencode_cmd or "opencode", "run" } + if model and model ~= "" then + table.insert(cmd, "--model") + table.insert(cmd, model) + end + if agent and agent ~= "" then + table.insert(cmd, "--agent") + table.insert(cmd, agent) + end + if variant and variant ~= "" then + table.insert(cmd, "--variant") + table.insert(cmd, variant) + end + table.insert(cmd, "--") + return cmd +end + +function M.run(prompt, callback) + local cfg = config.get() + if not cfg.ai.enabled then + callback(nil, "AI review is disabled in config") + return + end + + local pcfg = cfg.ai.opencode_cli or {} + local cmd = M.build_cmd(pcfg.cmd, pcfg.model, pcfg.agent, pcfg.variant) + return utils.run_cli(prompt, callback, cmd) +end + +return M diff --git a/lua/codereview/ai/providers/qwen_cli.lua b/lua/codereview/ai/providers/qwen_cli.lua new file mode 100644 index 0000000..5b67aea --- /dev/null +++ b/lua/codereview/ai/providers/qwen_cli.lua @@ -0,0 +1,27 @@ +local config = require("codereview.config") +local utils = require("codereview.ai.providers.utils") +local M = {} + +function M.build_cmd(qwen_cmd, model) + local cmd = { qwen_cmd or "qwen" } + table.insert(cmd, "--approval-mode=plan") + if model then + table.insert(cmd, "--model") + table.insert(cmd, model) + end + return cmd +end + +function M.run(prompt, callback) + local cfg = config.get() + if not cfg.ai.enabled then + callback(nil, "AI review is disabled in config") + return + end + + local pcfg = cfg.ai.qwen_cli or {} + local cmd = M.build_cmd(pcfg.cmd, pcfg.model) + return utils.run_cli(prompt, callback, cmd) +end + +return M diff --git a/lua/codereview/ai/providers/utils.lua b/lua/codereview/ai/providers/utils.lua new file mode 100644 index 0000000..7f549fc --- /dev/null +++ b/lua/codereview/ai/providers/utils.lua @@ -0,0 +1,77 @@ +local log = require("codereview.log") +local M = {} + +---Run a CLI command asynchronously, sending the prompt via stdin and collecting output. +---@param prompt string|string[] prompt that will be passed to the AI +---@param callback fun(string?, string?) callback that takes either the AI output as first argument or an error message as second argument. +---@param cmd string[] command to execute that consumes the prompt input to produce an output passed to the callback. +---@return integer Returns |job-id| on success, 0 on invalid arguments (or job table is full), -1 if {cmd}[0] or 'shell' is not executable. +function M.run_cli(prompt, callback, cmd) + log.debug("AI cmd: starting " .. table.concat(cmd, " ")) + local stdout_chunks = {} + local stderr_chunks = {} + local done = false + + local job_id = vim.fn.jobstart(cmd, { + stdout_buffered = true, + stderr_buffered = true, + on_stdout = function(_, data) + if data then + for i, chunk in ipairs(data) do + if i < #data or chunk ~= "" then + table.insert(stdout_chunks, chunk) + end + end + end + end, + on_stderr = function(_, data) + if data then + for i, chunk in ipairs(data) do + if i < #data or chunk ~= "" then + table.insert(stderr_chunks, chunk) + end + end + end + end, + on_exit = function(_, code) + if done then + return + end + done = true + vim.schedule(function() + local stderr_str = table.concat(stderr_chunks, "\n") + if stderr_str ~= "" then + log.warn("AI cmd stderr: " .. stderr_str) + end + local output = table.concat(stdout_chunks, "\n") + if code ~= 0 then + local msg = "Command exited with code " .. code + if output ~= "" then + msg = msg .. "\n" .. output + end + if stderr_str ~= "" then + msg = msg .. "\n" .. stderr_str + end + log.error("AI cmd: " .. msg) + callback(nil, msg) + else + log.debug("AI cmd: completed, output length=" .. #output) + callback(output) + end + end) + end, + }) + + if job_id <= 0 then + local msg = "Failed to start '" .. table.concat(cmd, " ") .. "'. Is it in your PATH?" + log.error("AI cmd: " .. msg) + callback(nil, msg) + return job_id + end + + vim.fn.chansend(job_id, prompt) + vim.fn.chanclose(job_id, "stdin") + return job_id +end + +return M diff --git a/lua/codereview/config.lua b/lua/codereview/config.lua index 242d2b9..3b7b0ff 100644 --- a/lua/codereview/config.lua +++ b/lua/codereview/config.lua @@ -27,19 +27,47 @@ ---@class codereview.config.AI ---@field enabled? boolean enable AI Review (default: `true`) ----@field provider? "claude_cli"|"anthropic"|"openai"|"ollama"|"custom_cmd" AI Provider to use +---@field provider? "claude_cli"|"codex_cli"|"copilot_cli"|"gemini_cli"|"opencode_cli"|"qwen_cli"|"anthropic"|"openai"|"ollama"|"custom_cmd" AI Provider to use ---@field review_level? "info"|"suggestion"|"warning"|"error" controls the verbosity of AI code reviews (default: `info`) ---@field max_file_size? integer skip files larger than N lines (0 = unlimited) (default: 500) ----@field claude_cli? codereview.config.ai.ClaudeCli Claude CLI options +---@field claude_cli? codereview.config.ai.ClaudeCLI Claude CLI options +---@field codex_cli? codereview.config.ai.CodexCLI Codex CLI options +---@field copilot_cli? codereview.config.ai.CopilotCLI Copilot CLI options +---@field gemini_cli? codereview.config.ai.GeminiCLI Gemini CLI options +---@field opencode_cli? codereview.config.ai.OpenCodeCLI OpenCode CLI options +---@field qwen_cli? codereview.config.ai.QwenCLI Qwen CLI options ---@field anthropic? codereview.config.ai.Anthropic Anthropic API options ---@field openai? codereview.config.ai.OpenAI OpenAI API options ---@field ollama? codereview.config.ai.Ollama Ollama options ---@field custom_cmd? codereview.config.ai.CustomCmd Custom command options ----@class codereview.config.ai.ClaudeCli ----@field cmd? string Claude CLI command (default: `claude_cli`) +---@class codereview.config.ai.ClaudeCLI +---@field cmd? string Claude CLI command (default: `claude`) ---@field agent? string Claude Agent (default: `code-review`) +---@class codereview.config.ai.CodexCLI +---@field cmd? string Codex CLI command (default: `codex`) +---@field model? string Codex model name + +---@class codereview.config.ai.CopilotCLI +---@field cmd? string Copilot CLI command (default: `copilot`) +---@field model? string Copilot model name +---@field agent? string specify a custom agent to use + +---@class codereview.config.ai.GeminiCLI +---@field cmd? string Gemini CLI command (default: `gemini`) +---@field model? string Gemini model name + +---@class codereview.config.ai.OpenCodeCLI +---@field cmd? string OpenCode CLI command (default: `opencode`) +---@field model? string model to use in the form of provider/model +---@field agent? string agent to use (default: `plan`) +---@field variant? string model variant (provider-specific reasoning effort, e.g., high, max, minimal) + +---@class codereview.config.ai.QwenCLI +---@field cmd? string Qwen CLI command (default: `qwen`) +---@field model? string Qwen model name + ---@class codereview.config.ai.Anthropic ---@field api_key? string Anthropic API key ---@field model? string Anthropic model name @@ -77,6 +105,11 @@ local defaults = { review_level = "info", max_file_size = 500, claude_cli = { cmd = "claude", agent = "code-review" }, + codex_cli = { cmd = "codex", model = nil }, + copilot_cli = { cmd = "copilot", model = nil, agent = nil }, + gemini_cli = { cmd = "gemini", model = nil }, + opencode_cli = { cmd = "opencode", model = nil, agent = "plan", variant = nil }, + qwen_cli = { cmd = "qwen", model = nil }, anthropic = { api_key = nil, model = "claude-sonnet-4-20250514" }, openai = { api_key = nil, model = "gpt-4o", base_url = nil }, ollama = { model = "llama3", base_url = "http://localhost:11434" }, @@ -112,7 +145,18 @@ local function validate(c) c.ai.review_level = "info" end c.ai.max_file_size = math.max(0, c.ai.max_file_size or 500) - local valid_providers = { claude_cli = true, anthropic = true, openai = true, ollama = true, custom_cmd = true } + local valid_providers = { + claude_cli = true, + codex_cli = true, + copilot_cli = true, + gemini_cli = true, + opencode_cli = true, + qwen_cli = true, + anthropic = true, + openai = true, + ollama = true, + custom_cmd = true, + } if not valid_providers[c.ai.provider] then c.ai.provider = "claude_cli" end diff --git a/tests/codereview/ai/providers/codex_cli_spec.lua b/tests/codereview/ai/providers/codex_cli_spec.lua new file mode 100644 index 0000000..433964a --- /dev/null +++ b/tests/codereview/ai/providers/codex_cli_spec.lua @@ -0,0 +1,22 @@ +package.loaded["codereview.config"] = { + get = function() + return { ai = { enabled = true, provider = "codex_cli", codex_cli = { cmd = "codex", model = nil } } } + end, +} +package.loaded["codereview.log"] = { debug = function() end, warn = function() end, error = function() end } + +local codex_cli = require("codereview.ai.providers.codex_cli") + +describe("ai.providers.codex_cli", function() + describe("build_cmd", function() + it("builds default codex command", function() + assert.same({ "codex", "exec", "--sandbox=read-only", "--" }, codex_cli.build_cmd("codex")) + end) + it("includes model flag when provided", function() + assert.same( + { "codex", "exec", "--sandbox=read-only", "--model", "model", "--" }, + codex_cli.build_cmd("codex", "model") + ) + end) + end) +end) diff --git a/tests/codereview/ai/providers/copilot_cli_spec.lua b/tests/codereview/ai/providers/copilot_cli_spec.lua new file mode 100644 index 0000000..40cd262 --- /dev/null +++ b/tests/codereview/ai/providers/copilot_cli_spec.lua @@ -0,0 +1,30 @@ +package.loaded["codereview.config"] = { + get = function() + return { + ai = { enabled = true, provider = "copilot_cli", copilot_cli = { cmd = "copilot", model = nil, agent = nil } }, + } + end, +} +package.loaded["codereview.log"] = { debug = function() end, warn = function() end, error = function() end } + +local copilot_cli = require("codereview.ai.providers.copilot_cli") + +describe("ai.providers.copilot_cli", function() + describe("build_cmd", function() + it("builds default copilot command", function() + assert.same({ "copilot", "--silent", "--no-auto-update" }, copilot_cli.build_cmd("copilot")) + end) + it("includes model flag when provided", function() + assert.same( + { "copilot", "--silent", "--no-auto-update", "--model", "model" }, + copilot_cli.build_cmd("copilot", "model") + ) + end) + it("includes agent flag when provided", function() + assert.same( + { "copilot", "--silent", "--no-auto-update", "--agent", "agent" }, + copilot_cli.build_cmd("copilot", nil, "agent") + ) + end) + end) +end) diff --git a/tests/codereview/ai/providers/gemini_cli_spec.lua b/tests/codereview/ai/providers/gemini_cli_spec.lua new file mode 100644 index 0000000..03a99b3 --- /dev/null +++ b/tests/codereview/ai/providers/gemini_cli_spec.lua @@ -0,0 +1,21 @@ +package.loaded["codereview.config"] = { + get = function() + return { + ai = { enabled = true, provider = "gemini_cli", gemini_cli = { cmd = "gemini", model = nil } }, + } + end, +} +package.loaded["codereview.log"] = { debug = function() end, warn = function() end, error = function() end } + +local gemini_cli = require("codereview.ai.providers.gemini_cli") + +describe("ai.providers.gemini_cli", function() + describe("build_cmd", function() + it("builds default gemini command", function() + assert.same({ "gemini", "--approval-mode=plan" }, gemini_cli.build_cmd("gemini")) + end) + it("includes model flag when provided", function() + assert.same({ "gemini", "--approval-mode=plan", "--model", "model" }, gemini_cli.build_cmd("gemini", "model")) + end) + end) +end) diff --git a/tests/codereview/ai/providers/opencode_cli_spec.lua b/tests/codereview/ai/providers/opencode_cli_spec.lua new file mode 100644 index 0000000..b2a9f42 --- /dev/null +++ b/tests/codereview/ai/providers/opencode_cli_spec.lua @@ -0,0 +1,34 @@ +package.loaded["codereview.config"] = { + get = function() + return { + ai = { + enabled = true, + provider = "opencode_cli", + opencode_cli = { cmd = "opencode", model = nil, agent = "plan", variant = nil }, + }, + } + end, +} +package.loaded["codereview.log"] = { debug = function() end, warn = function() end, error = function() end } + +local opencode_cli = require("codereview.ai.providers.opencode_cli") + +describe("ai.providers.opencode_cli", function() + describe("build_cmd", function() + it("builds default opencode command", function() + assert.same({ "opencode", "run", "--" }, opencode_cli.build_cmd("opencode")) + end) + it("includes model flag when provided", function() + assert.same({ "opencode", "run", "--model", "model", "--" }, opencode_cli.build_cmd("opencode", "model")) + end) + it("includes agent flag when provided", function() + assert.same({ "opencode", "run", "--agent", "agent", "--" }, opencode_cli.build_cmd("opencode", nil, "agent")) + end) + it("includes variant flag when provided", function() + assert.same( + { "opencode", "run", "--variant", "variant", "--" }, + opencode_cli.build_cmd("opencode", nil, nil, "variant") + ) + end) + end) +end) diff --git a/tests/codereview/ai/providers/qwen_cli_spec.lua b/tests/codereview/ai/providers/qwen_cli_spec.lua new file mode 100644 index 0000000..af4ff45 --- /dev/null +++ b/tests/codereview/ai/providers/qwen_cli_spec.lua @@ -0,0 +1,21 @@ +package.loaded["codereview.config"] = { + get = function() + return { + ai = { enabled = true, provider = "qwen_cli", qwen_cli = { cmd = "qwen", model = nil } }, + } + end, +} +package.loaded["codereview.log"] = { debug = function() end, warn = function() end, error = function() end } + +local qwen_cli = require("codereview.ai.providers.qwen_cli") + +describe("ai.providers.qwen_cli", function() + describe("build_cmd", function() + it("builds default qwen command", function() + assert.same({ "qwen", "--approval-mode=plan" }, qwen_cli.build_cmd("qwen")) + end) + it("includes model flag when provided", function() + assert.same({ "qwen", "--approval-mode=plan", "--model", "model" }, qwen_cli.build_cmd("qwen", "model")) + end) + end) +end)