From cd26703a6ee6835870e4435446357f2eeb2c19d9 Mon Sep 17 00:00:00 2001 From: CTO Hermes Date: Sat, 30 May 2026 19:06:24 +0700 Subject: [PATCH 1/2] feat: add missing CLI flags + fix all documentation Flags added: - review --commit : review git commit/range - review --diff-file : review from diff file - review --quiet: suppress non-essential output - review --severity : filter by min severity - scan --focus : override focus areas - config show: display resolved config - config set : update config values Documentation fixes: - README: cargo install cora-cli, fix config path, remove non-existent flags (--branch, --full, --commit HEAD), remove Homebrew (tap doesn't exist), fix env vars table - Website docs: fix all 10 inconsistencies across 7 pages - Fix test version assertion (use env var, not hardcoded) Fixes version test to use CARGO_PKG_VERSION env var --- Cargo.lock | 2 +- README.md | 223 ++++++++++-------- src/commands/config_cmd.rs | 187 +++++++++++++++ src/commands/mod.rs | 1 + src/commands/review.rs | 44 +++- src/commands/scan.rs | 11 +- src/git/diff.rs | 13 + src/main.rs | 77 +++++- tests/cli_basic.rs | 2 +- .../routes/docs/cli-reference/+page.svelte | 82 +++++-- .../routes/docs/configuration/+page.svelte | 30 ++- website/src/routes/docs/examples/+page.svelte | 19 +- .../routes/docs/getting-started/+page.svelte | 21 +- .../src/routes/docs/installation/+page.svelte | 6 +- .../src/routes/docs/providers/+page.svelte | 10 +- website/src/routes/docs/usage/+page.svelte | 106 +++++---- 16 files changed, 633 insertions(+), 201 deletions(-) create mode 100644 src/commands/config_cmd.rs diff --git a/Cargo.lock b/Cargo.lock index 74984e2..93be71a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,7 +253,7 @@ dependencies = [ [[package]] name = "cora-cli" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "assert_cmd", diff --git a/README.md b/README.md index a24ddcf..6b3ba1f 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,6 @@ cargo install cora-cli ``` -### Homebrew - -```bash -brew tap ajianaz/tap -brew install cora-cli -``` - ### Binary Download Download the latest release from [GitHub Releases](https://github.com/ajianaz/cora-cli/releases): @@ -84,19 +77,13 @@ export ANTHROPIC_API_KEY="sk-ant-..." cora review ``` -### 3. Review a Specific File - -```bash -cora review src/main.rs -``` - -### 4. Review the Last Commit +### 3. Review a Specific Commit ```bash cora review --commit HEAD ``` -### 5. Scan the Entire Project +### 4. Scan the Entire Project ```bash cora scan . @@ -109,32 +96,48 @@ cora scan . Review code changes using an LLM. ```bash -# Review staged files +# Review staged files (default) cora review -# Review specific files -cora review src/main.rs src/lib.rs +# Review staged explicitly +cora review --staged + +# Review unstaged changes +cora review --unstaged + +# Review unpushed commits +cora review --unpushed -# Review a range of commits +# Review against a branch +cora review --base develop + +# Review a commit or range +cora review --commit HEAD cora review --commit HEAD~3..HEAD -# Review a pull request diff +# Review from a diff file cora review --diff-file pr.diff # Use a specific model cora review --model gpt-4o -# Output as SARIF (for CI) -cora review --output sarif --output-file results.sarif +# Filter by severity +cora review --severity major -# Output as JSON -cora review --output json +# Quiet mode (minimal output) +cora review --quiet -# Set severity threshold -cora review --severity warning +# Stream response +cora review --stream -# Quiet mode (machine-readable) -cora review --quiet +# Output as SARIF +cora review --format sarif + +# Review and upload SARIF to GitHub Code Scanning +cora review --base develop --upload + +# Output as JSON +cora review --format json ``` ### `cora scan` @@ -145,14 +148,14 @@ Scan files for code quality issues without requiring git context. # Scan current directory cora scan . -# Scan specific files -cora scan src/**/*.rs - -# Scan with custom rules focus +# Scan with focus areas cora scan . --focus security,performance # Exclude patterns -cora scan . --exclude "tests/**" --exclude "examples/**" +cora scan . --exclude "tests/**" --exclude "vendor/**" + +# Incremental (only changed files) +cora scan . --incremental ``` ### `cora config` @@ -163,12 +166,55 @@ Manage configuration. # Show current configuration cora config show -# Initialize config file -cora config init +# Set model +cora config set model gpt-4o + +# Set provider +cora config set provider anthropic +``` + +### `cora init` + +Create a `.cora.yaml` config file in your project. + +```bash +cora init +cora init --force +``` + +### `cora auth` + +Manage API key authentication. + +```bash +cora auth login +cora auth status +cora auth remove +``` + +### `cora hook` + +Manage git hooks. + +```bash +cora hook install +cora hook uninstall +``` + +### `cora providers` + +List detected AI providers. -# Set a configuration value -cora config set model claude-sonnet-4-20250514 -cora config set severity error +```bash +cora providers +``` + +### `cora upload-sarif` + +Upload a SARIF file to GitHub Code Scanning. + +```bash +cora upload-sarif results.sarif ``` ### `cora completion` @@ -176,65 +222,56 @@ cora config set severity error Generate shell completions. ```bash -cora completion bash > ~/.cora-completion.bash -cora completion zsh > ~/.cora-completion.zsh -cora completion fish > ~/.cora-completion.fish +cora completion bash +cora completion zsh +cora completion fish ``` ## ⚙️ Configuration -Create a `.cora.yaml` in your project root or `~/.config/cora/config.yaml` globally: +Create a `.cora.yaml` in your project root. API keys are stored at `~/.cora/config.toml`. ```yaml # .cora.yaml -model: gpt-4o -temperature: 0.1 - -# Provider configuration -provider: - name: openai # openai | anthropic | google | custom - base_url: null # Override API endpoint (for custom/self-hosted) - api_key_env: OPENAI_API_KEY # Environment variable for API key # Review settings review: - severity: warning # error | warning | info | note - focus: # Focus areas (empty = all) - - security - - performance - - maintainability - ignore: - patterns: - - "tests/**" - - "vendor/**" - - "*.generated.*" - rules: - - "line-too-long" - max_tokens: 4096 - language: en # Response language - -# Output settings -output: - format: colored # colored | plain | json | sarif - file: null # Output to file instead of stdout - -# Git settings -git: - auto_stage: false # Auto-stage files after review - base_branch: main # Base branch for PR reviews + severity: warning # minimum severity level + max_issues: 20 # max issues to report + focus: security,performance # focus areas + +# Patterns to ignore +ignore: + - "vendor/**" + - "*.min.js" + - "migrations/**" + +# Provider-specific model overrides +providers: + openai: + model: gpt-4o + anthropic: + model: claude-sonnet-4-20250514 ``` ### Environment Variables -| Variable | Description | Default | -|----------|-------------|---------| -| `OPENAI_API_KEY` | OpenAI API key | — | -| `ANTHROPIC_API_KEY` | Anthropic API key | — | -| `GOOGLE_API_KEY` | Google AI API key | — | -| `CORa_MODEL` | Override model | — | -| `CORa_PROVIDER` | Override provider | — | -| `CORa_CONFIG` | Path to config file | `.cora.yaml` | -| `CORa_LOG_LEVEL` | Log verbosity | `info` | +| Variable | Description | +|----------|-------------| +| `CORA_API_KEY` | Generic API key | +| `OPENAI_API_KEY` | OpenAI API key | +| `ANTHROPIC_API_KEY` | Anthropic API key | +| `GROQ_API_KEY` | Groq API key | +| `ZAI_API_KEY` | Z.AI API key | +| `CORA_PROVIDER` | Override provider | +| `CORA_MODEL` | Override model | +| `CORA_BASE_URL` | Override API base URL | +| `CORA_CONFIG` | Config file path | +| `CORA_FORMAT` | Output format | +| `CORA_NO_COLOR` | Disable colors | +| `GITHUB_TOKEN` | GitHub token for SARIF upload | +| `GITHUB_REPOSITORY` | GitHub repo for SARIF upload | +| `GITHUB_REF` | GitHub ref for SARIF upload | ## 🔗 CI/CD Integration @@ -263,9 +300,7 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | - cora review --commit origin/main..HEAD \ - --output sarif \ - --output-file results.sarif + cora review --base origin/develop --format sarif --output-file results.sarif - name: Upload SARIF to GitHub Code Scanning if: always() @@ -284,7 +319,7 @@ code-review: before_script: - cargo install cora-cli script: - - cora review --commit origin/main..HEAD --severity error + - cora review --base origin/develop --severity critical variables: OPENAI_API_KEY: $CI_OPENAI_API_KEY rules: @@ -311,7 +346,7 @@ Or add it manually to `.git/hooks/pre-commit`: ```bash #!/bin/sh # cora-cli pre-commit hook -cora review --quiet --severity error +cora review --quiet --severity critical if [ $? -ne 0 ]; then echo "❌ Code review found critical issues. Commit blocked." echo " Run 'cora review' to see details, or use 'git commit --no-verify' to skip." @@ -319,18 +354,6 @@ if [ $? -ne 0 ]; then fi ``` -### With [pre-commit](https://pre-commit.com) framework - -```yaml -# .pre-commit-config.yaml -repos: - - repo: https://github.com/ajianaz/cora-cli - rev: v0.1.0 - hooks: - - id: cora-review - args: ['--severity', 'warning'] -``` - ## 🆚 Positioning: How Cora Compares | Feature | **cora-cli** | AI Agent IDE Tools | Standard Linters | diff --git a/src/commands/config_cmd.rs b/src/commands/config_cmd.rs new file mode 100644 index 0000000..0de968b --- /dev/null +++ b/src/commands/config_cmd.rs @@ -0,0 +1,187 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use colored::Colorize; + +use crate::config::loader; + +/// Execute `cora config show` — print the current resolved configuration. +pub fn execute_config_show() -> Result<()> { + let config = loader::load_config(None, None, None, None, None, None, false)?; + + println!("{}", "╔══════════════════════════════════════════╗".cyan().bold()); + println!("{}", "║ Current Configuration ║".cyan().bold()); + println!("{}", "╚══════════════════════════════════════════╝".cyan().bold()); + println!(); + + println!( + "{} {}", + "provider:".bold(), + config.provider.provider.green() + ); + println!( + "{} {}", + " model:".dimmed(), + config.provider.model.green() + ); + println!( + "{} {}", + " base_url:".dimmed(), + config.provider.base_url.green() + ); + + println!( + "{} {}", + "focus:".bold(), + config + .focus + .iter() + .map(|f| format!("{}", f.green())) + .collect::>() + .join(", ") + ); + + if !config.rules.is_empty() { + println!( + "{} {}", + "rules:".bold(), + config + .rules + .iter() + .map(|r| format!("{}", r.green())) + .collect::>() + .join(", ") + ); + } + + println!( + "{} {}", + "hook:".bold(), + format!( + "mode={} min_severity={} max_diff_size={}", + config.hook.mode.yellow(), + config.hook.min_severity.yellow(), + config.hook.max_diff_size + ) + ); + + println!( + "{} {}", + "output:".bold(), + format!( + "format={} color={}", + config.output.format.green(), + config.output.color + ) + ); + + println!( + "{} {}", + "ignore files:".bold(), + if config.ignore.files.is_empty() { + "(none)".to_string().dimmed().to_string() + } else { + config + .ignore + .files + .iter() + .map(|f| format!("{}", f.dimmed())) + .collect::>() + .join(", ") + } + ); + + Ok(()) +} + +/// Execute `cora config set ` — write a key-value pair to ~/.cora/config.toml. +/// +/// Supported keys: model, provider, format, severity. +pub fn execute_config_set(key: &str, value: &str) -> Result<()> { + // Validate the key + match key { + "model" | "provider" | "format" | "severity" => {} + _ => { + anyhow::bail!( + "unsupported key: {key}\nSupported keys: model, provider, format, severity" + ); + } + } + + let dir = cora_config_dir()?; + std::fs::create_dir_all(&dir) + .with_context(|| format!("failed to create {}", dir.display()))?; + + let path = dir.join("config.toml"); + + // Load existing config.toml content if it exists + let mut table = if path.is_file() { + let content = std::fs::read_to_string(&path) + .with_context(|| format!("failed to read {}", path.display()))?; + content + .parse::() + .unwrap_or_default() + } else { + toml::Table::new() + }; + + // Map the key to the appropriate TOML structure + match key { + "model" => { + let provider = table + .entry("provider") + .or_insert_with(|| toml::Value::Table(toml::Table::new())); + if let toml::Value::Table(p) = provider { + p.insert("model".to_string(), toml::Value::String(value.to_string())); + } + } + "provider" => { + let provider = table + .entry("provider") + .or_insert_with(|| toml::Value::Table(toml::Table::new())); + if let toml::Value::Table(p) = provider { + p.insert("provider".to_string(), toml::Value::String(value.to_string())); + } + } + "format" => { + let output = table + .entry("output") + .or_insert_with(|| toml::Value::Table(toml::Table::new())); + if let toml::Value::Table(o) = output { + o.insert("format".to_string(), toml::Value::String(value.to_string())); + } + } + "severity" => { + let hook = table + .entry("hook") + .or_insert_with(|| toml::Value::Table(toml::Table::new())); + if let toml::Value::Table(h) = hook { + h.insert( + "min_severity".to_string(), + toml::Value::String(value.to_string()), + ); + } + } + _ => unreachable!(), + } + + let content = table.to_string(); + std::fs::write(&path, content) + .with_context(|| format!("failed to write {}", path.display()))?; + + println!( + "{} Set {} = {} in {}", + "✓".green().bold(), + key.bold(), + value.green(), + path.display() + ); + + Ok(()) +} + +/// Get the cora config directory: ~/.cora/ +fn cora_config_dir() -> Result { + let home = dirs::home_dir().context("cannot determine home directory")?; + Ok(home.join(".cora")) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a76dd9e..7b0c797 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ pub mod auth; pub mod completion; +pub mod config_cmd; pub mod hook_cmd; pub mod init; pub mod providers; diff --git a/src/commands/review.rs b/src/commands/review.rs index f5b28c8..4fdeca2 100644 --- a/src/commands/review.rs +++ b/src/commands/review.rs @@ -1,8 +1,9 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use colored::Colorize; use tracing::debug; use crate::config::schema::Config; +use crate::engine::Severity; use crate::formatters::{OutputFormat, formatter_for}; use crate::git; @@ -19,12 +20,20 @@ pub struct ReviewOptions { pub unpushed: bool, /// Review diff against a base branch. pub base: Option, + /// Review diff from a git commit reference (e.g. HEAD, HEAD~3..HEAD, abc123). + pub commit: Option, + /// Read diff from a file instead of git. + pub diff_file: Option, /// Review unstaged changes. pub unstaged: bool, /// Maximum diff size before refusing (0 = use config default). pub max_diff_size: Option, /// Stream LLM response tokens to stdout in real-time. pub stream: bool, + /// Suppress all output except the formatted review result. + pub quiet: bool, + /// Minimum severity level for filtering issues. + pub severity: Option, } /// Result of a review command execution. @@ -49,6 +58,12 @@ pub async fn execute_review( let diff = get_diff(opts, config)?; if diff.trim().is_empty() { + if opts.quiet { + return Ok(ReviewResult { + exit_code: EXIT_OK, + output: String::new(), + }); + } let output = format!("{}\n", "No changes to review.".yellow()); return Ok(ReviewResult { exit_code: EXIT_OK, @@ -77,11 +92,21 @@ pub async fn execute_review( crate::engine::review::review_diff_with_stream(config, llm_config, &diff, opts.stream) .await?; - // 4. Format output + // 4. Filter by severity if specified + let min_severity = if let Some(ref sev) = opts.severity { + Severity::from_str_lossy(sev) + } else { + config.hook.min_severity_level() + }; + + let mut filtered_response = response.clone(); + filtered_response.issues.retain(|i| i.severity <= min_severity); + + // 5. Format output let formatter = formatter_for(format); - let output = formatter.format_review(&response)?; + let output = formatter.format_review(&filtered_response)?; - // 5. Return exit code + // 6. Return exit code let exit_code = if response.should_block && config.hook.mode == "block" { EXIT_BLOCKED } else { @@ -93,6 +118,17 @@ pub async fn execute_review( /// Get the diff based on the provided options. fn get_diff(opts: &ReviewOptions, _config: &Config) -> Result { + if let Some(ref diff_file) = opts.diff_file { + let path = std::path::Path::new(diff_file); + if !path.exists() { + anyhow::bail!("diff file not found: {}", diff_file); + } + return std::fs::read_to_string(path) + .with_context(|| format!("failed to read diff file: {}", diff_file)); + } + if let Some(ref commit_ref) = opts.commit { + return git::get_commit_diff(commit_ref); + } if opts.staged { git::get_staged_diff() } else if opts.unpushed { diff --git a/src/commands/scan.rs b/src/commands/scan.rs index 398f1a4..1df67d9 100644 --- a/src/commands/scan.rs +++ b/src/commands/scan.rs @@ -20,6 +20,8 @@ pub struct ScanOptions { pub extensions: Vec, /// Only scan files changed since last scan. pub incremental: bool, + /// Focus areas for review (overrides config). + pub focus: Vec, } /// Execute the scan command. @@ -46,6 +48,13 @@ pub async fn execute_scan( let mut exclude = config.ignore.files.clone(); exclude.extend(opts.exclude.clone()); + // Merge focus areas: CLI --focus overrides config + let effective_focus = if opts.focus.is_empty() { + config.focus.clone() + } else { + opts.focus.clone() + }; + debug!(root = %root.display(), "starting scan"); // 1. Walk and collect files @@ -107,7 +116,7 @@ pub async fn execute_scan( let (issues, _summary, tokens) = crate::engine::llm::scan_files( llm_config, &files_content, - &config.focus, + &effective_focus, &config.rules, ) .await?; diff --git a/src/git/diff.rs b/src/git/diff.rs index 3178e83..00eb8c2 100644 --- a/src/git/diff.rs +++ b/src/git/diff.rs @@ -45,6 +45,19 @@ pub fn get_unpushed_diff() -> Result { git_cmd(&["log", "-p", "@{u}..HEAD"]) } +/// Get the diff for a commit reference. +/// +/// - If the ref contains `..` (e.g. `HEAD~3..HEAD`), uses `git diff `. +/// - Otherwise (e.g. `HEAD`, `abc123`), uses `git show --format=""` to +/// get just the diff without commit metadata. +pub fn get_commit_diff(ref_str: &str) -> Result { + if ref_str.contains("..") { + git_cmd(&["diff", ref_str]) + } else { + git_cmd(&["show", ref_str, "--format="]) + } +} + /// Get the current branch name. pub fn get_current_branch() -> Result { let output = Command::new("git") diff --git a/src/main.rs b/src/main.rs index d6bf9f5..37aab73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ mod formatters; mod git; mod hook; -use commands::{auth, completion, hook_cmd, init, providers, review, scan, upload}; +use commands::{auth, completion, config_cmd, hook_cmd, init, providers, review, scan, upload}; use config::loader; use formatters::OutputFormat; @@ -91,6 +91,14 @@ enum Command { #[clap(long)] base: Option, + /// Review changes from a git commit ref (e.g. HEAD, HEAD~3..HEAD, abc123) + #[clap(long)] + commit: Option, + + /// Read diff from a file instead of git + #[clap(long)] + diff_file: Option, + /// Review unstaged changes (working tree) #[clap(long)] unstaged: bool, @@ -99,6 +107,14 @@ enum Command { #[clap(long)] stream: bool, + /// Suppress all output except the formatted review result + #[clap(long, short)] + quiet: bool, + + /// Filter results by minimum severity (info, minor, major, critical) + #[clap(long, value_parser = ["info", "minor", "major", "critical"])] + severity: Option, + /// Upload SARIF output to GitHub Code Scanning after review /// (implies --format sarif) #[clap(long)] @@ -138,6 +154,10 @@ enum Command { /// Only scan files changed since last scan (uses ~/.cora/scan-cache.json) #[clap(long)] incremental: bool, + + /// Focus areas for review (overrides config) + #[clap(long)] + focus: Vec, }, /// Upload a SARIF file to GitHub Code Scanning @@ -177,6 +197,12 @@ enum Command { action: AuthAction, }, + /// View or set configuration + Config { + #[command(subcommand)] + action: ConfigAction, + }, + /// List detected/available LLM providers Providers, @@ -205,6 +231,19 @@ enum AuthAction { Remove, } +#[derive(Subcommand, Debug)] +enum ConfigAction { + /// Show the current resolved configuration + Show, + /// Set a configuration value (keys: model, provider, format, severity) + Set { + /// Configuration key to set + key: String, + /// Value to assign + value: String, + }, +} + #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); @@ -238,8 +277,12 @@ async fn main() -> Result<()> { staged, unpushed, base, + commit, + diff_file, unstaged, stream, + quiet, + severity, upload, repo, ref_name, @@ -251,8 +294,12 @@ async fn main() -> Result<()> { staged, unpushed, base, + commit, + diff_file, unstaged, stream, + quiet, + severity, upload, repo, ref_name, @@ -267,6 +314,7 @@ async fn main() -> Result<()> { exclude, extensions, incremental, + focus, } => { cmd_scan( &cli.global, @@ -276,6 +324,7 @@ async fn main() -> Result<()> { exclude, extensions, incremental, + focus, }, ) .await? @@ -320,6 +369,18 @@ async fn main() -> Result<()> { } 0 } + Command::Config { action } => { + match action { + ConfigAction::Show => { + config_cmd::execute_config_show()?; + 0 + } + ConfigAction::Set { key, value } => { + config_cmd::execute_config_set(&key, &value)?; + 0 + } + } + } Command::Providers => { providers::execute_providers()?; 0 @@ -338,8 +399,12 @@ struct ReviewOpts { staged: bool, unpushed: bool, base: Option, + commit: Option, + diff_file: Option, unstaged: bool, stream: bool, + quiet: bool, + severity: Option, upload: bool, repo: Option, ref_name: Option, @@ -353,6 +418,7 @@ struct ScanOpts { exclude: Vec, extensions: Vec, incremental: bool, + focus: Vec, } /// Handle the `review` subcommand. @@ -380,13 +446,17 @@ async fn cmd_review(globals: &GlobalOptions, opts: ReviewOpts) -> Result { staged: opts.staged, unpushed: opts.unpushed, base: opts.base.clone(), + commit: opts.commit.clone(), + diff_file: opts.diff_file.clone(), unstaged: opts.unstaged, max_diff_size: None, stream: opts.stream, + quiet: opts.quiet, + severity: opts.severity.clone(), }; - // When streaming, show a simpler message (no spinner, since we print tokens live) - if opts.stream { + // When streaming and not quiet, show a simpler message + if opts.stream && !opts.quiet { eprintln!( "{}", format!( @@ -473,6 +543,7 @@ async fn cmd_scan(globals: &GlobalOptions, opts: ScanOpts) -> Result { exclude: opts.exclude, extensions: opts.extensions, incremental: opts.incremental, + focus: opts.focus, }; scan::execute_scan(&config, &llm_config, &scan_opts, format).await diff --git a/tests/cli_basic.rs b/tests/cli_basic.rs index 0519664..2717669 100644 --- a/tests/cli_basic.rs +++ b/tests/cli_basic.rs @@ -18,7 +18,7 @@ fn cli_version_returns_version_string() { .arg("--version") .assert() .success() - .stdout(predicate::str::contains("0.1.0")); + .stdout(predicate::str::contains(env!("CARGO_PKG_VERSION"))); } #[test] diff --git a/website/src/routes/docs/cli-reference/+page.svelte b/website/src/routes/docs/cli-reference/+page.svelte index 77ef7e8..d5d4814 100644 --- a/website/src/routes/docs/cli-reference/+page.svelte +++ b/website/src/routes/docs/cli-reference/+page.svelte @@ -47,8 +47,28 @@ Override config file path (default: .cora.yaml) - --json - Output results as JSON + --format <fmt> + Output format: pretty, json, compact, sarif + + + --no-color + Disable colored output + + + --provider <name> + Override provider + + + --model <name> + Override model + + + --base-url <url> + Override API base URL + + + --api-key <key> + Override API key --verbose @@ -78,25 +98,53 @@ cora init Create .cora.yaml config file + + cora review + Review code changes (default: staged files) + cora review --staged - Review staged git changes (default) + Review staged git changes explicitly + + + cora review --unstaged + Review unstaged working changes + + + cora review --unpushed + Review unpushed commits - cora review --branch <name> + cora review --base <branch> Compare current branch against target - cora review --diff <base>..<head> - Review specific diff range + cora review --commit <ref> + Review specific commit or range + + + cora review --diff-file <path> + Review from a diff file + + + cora review --upload + Review and upload SARIF to GitHub Code Scanning + + + cora scan <path> + Scan files for issues - cora review --file <path> - Review single file + cora scan . [--incremental] + Scan only changed files - cora scan [--incremental] - Full project scan + cora config show + Show resolved configuration + + + cora config set <key> <value> + Set a config value cora hook install @@ -115,17 +163,21 @@ Check current auth status - cora providers - List supported AI providers + cora auth remove + Remove stored API key - cora completion <shell> - Generate shell completions (bash/zsh/fish/powershell) + cora providers + List detected AI providers cora upload-sarif <file> Upload SARIF to GitHub Code Scanning + + cora completion <shell> + Generate shell completions (bash/zsh/fish) + @@ -158,7 +210,7 @@
# Compare your feature branch against main
- $ cora review --branch main + $ cora review --base main
diff --git a/website/src/routes/docs/configuration/+page.svelte b/website/src/routes/docs/configuration/+page.svelte index 5e9d3e4..981b53e 100644 --- a/website/src/routes/docs/configuration/+page.svelte +++ b/website/src/routes/docs/configuration/+page.svelte @@ -70,7 +70,7 @@
# cora project config
 review:
-  severity: warning          # minimum severity: info, warning, error
+  severity: warning          # minimum severity: info, minor, major, critical
   max_issues: 20             # max issues to report
   focus: security,performance  # focus areas
 
@@ -119,6 +119,30 @@
 					CORA_BASE_URL
 					Custom API base URL
 				
+				
+					CORA_CONFIG
+					Path to config file
+				
+				
+					CORA_FORMAT
+					Output format (pretty, json, compact, sarif)
+				
+				
+					CORA_NO_COLOR
+					Disable colored output
+				
+				
+					GITHUB_TOKEN
+					GitHub token for SARIF upload
+				
+				
+					GITHUB_REPOSITORY
+					GitHub repo for SARIF upload
+				
+				
+					GITHUB_REF
+					GitHub ref for SARIF upload
+				
 			
 		
 	
@@ -150,7 +174,9 @@ GROQ_API_KEY=gsk_... # Ollama (local, no key needed) -OLLAMA_BASE_URL=http://localhost:11434 +OLLAMA_HOST=http://localhost:11434 +# Optional: OLLAMA_API_KEY if your Ollama instance requires auth +OLLAMA_API_KEY=... # Z.AI ZAI_API_KEY=... diff --git a/website/src/routes/docs/examples/+page.svelte b/website/src/routes/docs/examples/+page.svelte index 5e966eb..bc550dd 100644 --- a/website/src/routes/docs/examples/+page.svelte +++ b/website/src/routes/docs/examples/+page.svelte @@ -41,7 +41,10 @@
- $ CORA_API_KEY=sk-xxx cora review --staged +# Review staged changes (default)
+ $ cora review

+ # Or review with explicit flags
+ $ cora review --staged
@@ -60,7 +63,7 @@
- $ cora review --branch main + $ cora review --base main
@@ -149,12 +152,12 @@ steps: - uses: actions/checkout@v4 - name: Install cora - run: cargo install cora + run: cargo install cora-cli - name: Run AI code review env: {@html 'CORA_API_KEY: ${{ secrets.CORA_API_KEY }}'} CORA_PROVIDER: openai - run: cora review --branch main --fail-on error + run: cora review --base main --format sarif @@ -197,10 +200,10 @@
- # Generate SARIF report and upload
- $ cora review --staged --output sarif > results.sarif && \
-   cora upload-sarif results.sarif

- Uploaded 3 findings to GitHub Code Scanning + # Generate SARIF report and upload
+ $ cora review --base main --format sarif > results.sarif && \\
+   cora upload-sarif results.sarif

+ Uploaded 3 findings to GitHub Code Scanning
diff --git a/website/src/routes/docs/getting-started/+page.svelte b/website/src/routes/docs/getting-started/+page.svelte index a152047..775aaf7 100644 --- a/website/src/routes/docs/getting-started/+page.svelte +++ b/website/src/routes/docs/getting-started/+page.svelte @@ -34,7 +34,7 @@
1
Install cora — Single binary via Cargo: - cargo install cora + cargo install cora-cli
@@ -57,7 +57,7 @@
4
Review — Analyze your staged changes: - cora review --staged + cora review
@@ -120,24 +120,25 @@
# .cora.yaml
-
provider: openai
-
model: gpt-4o
+
review:
+
severity: warning
+
focus: security,performance
-
custom_prompt: |
-
Focus on security vulnerabilities and
-
performance issues. Ignore style linting.
+
providers:
+
openai:
+
model: gpt-4o
- provider — Which LLM provider to use (openai, anthropic, groq, ollama, zai) + review.severity — Minimum severity level (info, minor, major, critical)
- model — Specific model name (e.g., gpt-4o, claude-sonnet-4-20250514) + review.focus — Focus areas for review (e.g., security, performance)
- custom_prompt — Override the default review prompt to focus on specific concerns + providers — Provider-specific model overrides (openai, anthropic, groq, ollama, zai)
diff --git a/website/src/routes/docs/installation/+page.svelte b/website/src/routes/docs/installation/+page.svelte index 2c62fbe..d57f4a5 100644 --- a/website/src/routes/docs/installation/+page.svelte +++ b/website/src/routes/docs/installation/+page.svelte @@ -31,7 +31,7 @@

cora is a Rust binary. You have two installation paths:

- Via Cargo (recommended) — Requires Rust 1.70 or later with Cargo installed + Via Cargo (recommended) — Requires Rust 1.85 or later with Cargo installed
Binary download — No Rust required; just download and place in your PATH @@ -51,7 +51,7 @@
-
$ cargo install cora
+
$ cargo install cora-cli
@@ -157,7 +157,7 @@
- Via Cargo: cargo install cora --force + Via Cargo: cargo install cora-cli --force
Via Binary: Download the latest release from GitHub and replace the existing binary diff --git a/website/src/routes/docs/providers/+page.svelte b/website/src/routes/docs/providers/+page.svelte index 058f734..7ae143b 100644 --- a/website/src/routes/docs/providers/+page.svelte +++ b/website/src/routes/docs/providers/+page.svelte @@ -46,8 +46,8 @@ OpenAI - gpt-4o - OPENAI_API_KEY + gpt-4o-mini + OPENAI_API_KEY OPENAI_BASE_URL @@ -58,7 +58,7 @@ Groq - llama-3.3-70b-versatile + llama-3.1-8b-instant GROQ_API_KEY GROQ_BASE_URL @@ -66,7 +66,7 @@ Ollama llama3.1 — (local) - OLLAMA_BASE_URL (default: http://localhost:11434) + OLLAMA_HOST (default: http://localhost:11434) Z.AI @@ -94,7 +94,7 @@ 'ANTHROPIC_API_KEY \u2192 uses Anthropic', 'GROQ_API_KEY \u2192 uses Groq', 'ZAI_API_KEY \u2192 uses Z.AI', - 'OLLAMA_BASE_URL \u2192 uses Ollama (localhost)' + 'OLLAMA_HOST \u2192 uses Ollama (localhost)' ] as item, i}
{i + 1}
diff --git a/website/src/routes/docs/usage/+page.svelte b/website/src/routes/docs/usage/+page.svelte index e43cccb..4f62231 100644 --- a/website/src/routes/docs/usage/+page.svelte +++ b/website/src/routes/docs/usage/+page.svelte @@ -28,7 +28,7 @@

Review Modes

-

cora supports four review modes, each suited to a different workflow:

+

cora supports multiple review modes, each suited to a different workflow:

@@ -41,6 +41,12 @@ + + + + + + @@ -48,22 +54,34 @@ - - + + + + + + + + + + + + + + - - - - + + + + - - - - + + + +
Default(no flag)Tries staged first, then unpushedQuick feedback
Staged --stagedPre-commit review
Branch--branchUnstaged--unstagedUnstaged working changesReview work in progress
Unpushed--unpushedCommits not yet pushedReview before push
Base Branch--base <branch> Diff against base branch PR review workflow
Full--fullEntire repositoryComprehensive auditCommit--commit <ref>Specific commit or rangeReview specific changes
Incremental--incrementalOnly new/changed filesLarge codebasesDiff File--diff-file <path>External diff fileReview patch files
@@ -76,17 +94,20 @@
-
# Review staged changes
-
$ cora review --staged
+
# Review staged changes (default)
+
$ cora review
+
+
# Review against a branch
+
$ cora review --base main
-
# Review against main branch
-
$ cora review --branch main
+
# Review a specific commit
+
$ cora review --commit HEAD
-
# Full project scan
-
$ cora review --full
+
# Review from a diff file
+
$ cora review --diff-file pr.diff
-
# Incremental (only changed files)
-
$ cora review --incremental
+
# Full project scan (use cora scan)
+
$ cora scan .
@@ -141,7 +162,7 @@

Configuration File

-

The .cora.yaml file provides persistent configuration. Place it in your project root or use ~/.cora/config.yaml for global settings.

+

The .cora.yaml file provides persistent configuration. Place it in your project root. API keys are stored at ~/.cora/config.toml.

@@ -150,30 +171,19 @@
-
# .cora.yaml — full example
-
-
# LLM provider: openai, anthropic, groq, ollama, zai
-
provider: openai
-
-
# Model name
-
model: gpt-4o
-
-
# Custom base URL (for proxies or self-hosted endpoints)
-
# base_url: https://api.example.com/v1
-
-
# Custom review prompt
-
# custom_prompt: |
-
# Focus on security and performance only.
-
-
# File patterns to include
-
# include:
-
# - "src/**"
-
# - "lib/**"
-
-
# File patterns to exclude
-
# exclude:
-
# - "**/*.test.ts"
-
# - "**/node_modules/**"
+
# .cora.yaml — example
+
+
review:
+
severity: warning
+
focus: security,performance
+
+
ignore:
+
- "vendor/**"
+
- "*.min.js"
+
+
providers:
+
openai:
+
model: gpt-4o
@@ -288,19 +298,19 @@
• - Use --staged as a pre-commit hook for the fastest feedback loop + Use cora review with no flags for the fastest pre-commit feedback
• - Combine --format json with --branch main in CI pipelines + Combine --format json with --base main in CI pipelines
• - Use --incremental for large codebases — only changed files are analyzed + Use cora scan . --incremental for large codebases — only changed files are analyzed
• - Set custom_prompt to match your team's coding standards + Use --quiet for minimal output and --severity to filter by severity level
• From 90ff275f51840b0e969f1103954051486c588842 Mon Sep 17 00:00:00 2001 From: CTO Hermes Date: Sat, 30 May 2026 19:09:51 +0700 Subject: [PATCH 2/2] fix: resolve clippy format_in_format_args + cargo fmt --- src/commands/config_cmd.rs | 55 +++++++++++++++++++------------------- src/commands/review.rs | 4 ++- src/main.rs | 20 +++++++------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/commands/config_cmd.rs b/src/commands/config_cmd.rs index 0de968b..e02c116 100644 --- a/src/commands/config_cmd.rs +++ b/src/commands/config_cmd.rs @@ -9,9 +9,20 @@ use crate::config::loader; pub fn execute_config_show() -> Result<()> { let config = loader::load_config(None, None, None, None, None, None, false)?; - println!("{}", "╔══════════════════════════════════════════╗".cyan().bold()); - println!("{}", "║ Current Configuration ║".cyan().bold()); - println!("{}", "╚══════════════════════════════════════════╝".cyan().bold()); + println!( + "{}", + "╔══════════════════════════════════════════╗".cyan().bold() + ); + println!( + "{}", + "║ Current Configuration ║" + .cyan() + .bold() + ); + println!( + "{}", + "╚══════════════════════════════════════════╝".cyan().bold() + ); println!(); println!( @@ -19,11 +30,7 @@ pub fn execute_config_show() -> Result<()> { "provider:".bold(), config.provider.provider.green() ); - println!( - "{} {}", - " model:".dimmed(), - config.provider.model.green() - ); + println!("{} {}", " model:".dimmed(), config.provider.model.green()); println!( "{} {}", " base_url:".dimmed(), @@ -55,24 +62,18 @@ pub fn execute_config_show() -> Result<()> { } println!( - "{} {}", + "{} mode={} min_severity={} max_diff_size={}", "hook:".bold(), - format!( - "mode={} min_severity={} max_diff_size={}", - config.hook.mode.yellow(), - config.hook.min_severity.yellow(), - config.hook.max_diff_size - ) + config.hook.mode.yellow(), + config.hook.min_severity.yellow(), + config.hook.max_diff_size ); println!( - "{} {}", + "{} format={} color={}", "output:".bold(), - format!( - "format={} color={}", - config.output.format.green(), - config.output.color - ) + config.output.format.green(), + config.output.color ); println!( @@ -109,8 +110,7 @@ pub fn execute_config_set(key: &str, value: &str) -> Result<()> { } let dir = cora_config_dir()?; - std::fs::create_dir_all(&dir) - .with_context(|| format!("failed to create {}", dir.display()))?; + std::fs::create_dir_all(&dir).with_context(|| format!("failed to create {}", dir.display()))?; let path = dir.join("config.toml"); @@ -118,9 +118,7 @@ pub fn execute_config_set(key: &str, value: &str) -> Result<()> { let mut table = if path.is_file() { let content = std::fs::read_to_string(&path) .with_context(|| format!("failed to read {}", path.display()))?; - content - .parse::() - .unwrap_or_default() + content.parse::().unwrap_or_default() } else { toml::Table::new() }; @@ -140,7 +138,10 @@ pub fn execute_config_set(key: &str, value: &str) -> Result<()> { .entry("provider") .or_insert_with(|| toml::Value::Table(toml::Table::new())); if let toml::Value::Table(p) = provider { - p.insert("provider".to_string(), toml::Value::String(value.to_string())); + p.insert( + "provider".to_string(), + toml::Value::String(value.to_string()), + ); } } "format" => { diff --git a/src/commands/review.rs b/src/commands/review.rs index 4fdeca2..5de7b33 100644 --- a/src/commands/review.rs +++ b/src/commands/review.rs @@ -100,7 +100,9 @@ pub async fn execute_review( }; let mut filtered_response = response.clone(); - filtered_response.issues.retain(|i| i.severity <= min_severity); + filtered_response + .issues + .retain(|i| i.severity <= min_severity); // 5. Format output let formatter = formatter_for(format); diff --git a/src/main.rs b/src/main.rs index 37aab73..6001370 100644 --- a/src/main.rs +++ b/src/main.rs @@ -369,18 +369,16 @@ async fn main() -> Result<()> { } 0 } - Command::Config { action } => { - match action { - ConfigAction::Show => { - config_cmd::execute_config_show()?; - 0 - } - ConfigAction::Set { key, value } => { - config_cmd::execute_config_set(&key, &value)?; - 0 - } + Command::Config { action } => match action { + ConfigAction::Show => { + config_cmd::execute_config_show()?; + 0 } - } + ConfigAction::Set { key, value } => { + config_cmd::execute_config_set(&key, &value)?; + 0 + } + }, Command::Providers => { providers::execute_providers()?; 0