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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 28 additions & 48 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,55 +129,35 @@ jobs:
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: Generate release notes
- name: Extract changelog for this version
id: changelog
run: |
cat > release-notes.md << 'EOF'
## What's New in VERSION

### 🚀 Features

- **AI Code Review** — diff, branch comparison, full project scan
- **BYOK** — bring your own API key (OpenAI, Anthropic, Groq, Ollama, Z.AI)
- **Pre-commit Hooks** — `cora hook install` for automatic review on every commit
- **Incremental Scan** — `cora scan --incremental` with SHA256 content hash cache
- **SARIF Output** — upload findings to GitHub Code Scanning
- **Streaming** — real-time review output with `--stream`
- **Shell Completions** — `cora completion bash|zsh|fish|powershell`
- **5 Providers** — OpenAI, Anthropic, Groq, Ollama, Z.AI with auto-detection
- **Project Config** — `.cora.yaml` per-project configuration
- **MIT License** — fully open source

### 📦 Platforms

| Platform | File |
|----------|------|
| Linux (x86_64) | `cora-x86_64-unknown-linux-gnu-VERSION.tar.gz` |
| Linux (ARM64) | `cora-aarch64-unknown-linux-gnu-VERSION.tar.gz` |
| macOS (Apple Silicon) | `cora-aarch64-apple-darwin-VERSION.tar.gz` |
| Windows (x86_64) | `cora-x86_64-pc-windows-msvc-VERSION.zip` |

### 🚀 Quick Start

```bash
# Install via cargo
cargo install cora

# Or download binary (extracts as 'cora')
curl -fsSL https://github.com/ajianaz/cora-cli/releases/latest/download/cora-x86_64-unknown-linux-gnu-VERSION.tar.gz | tar xz
chmod +x cora
sudo mv cora /usr/local/bin/

# Init project
cora init

# Review staged changes
CORA_API_KEY=your-key cora review --staged
```

**Full changelog:** https://github.com/ajianaz/cora-cli/blob/main/CHANGELOG.md

EOF
sed -i "s/VERSION/${{ steps.version.outputs.VERSION }}/g" release-notes.md
CHANGELOG_EXCERPT=$(sed -n '/^## \['"${{ steps.version.outputs.VERSION }}"'\]/,/^## \[/p' CHANGELOG.md | sed '$d' | tail -n +2)
{
printf '## What'\''s New in %s\n\n' "${{ steps.version.outputs.VERSION }}"
printf '%s\n\n' "$CHANGELOG_EXCERPT"
printf '### 📦 Platforms\n\n'
printf '| Platform | File |\n'
printf '|----------|------|\n'
printf '| Linux (x86_64) | `cora-x86_64-unknown-linux-gnu-%s.tar.gz` |\n' "${{ steps.version.outputs.VERSION }}"
printf '| Linux (ARM64) | `cora-aarch64-unknown-linux-gnu-%s.tar.gz` |\n' "${{ steps.version.outputs.VERSION }}"
printf '| macOS (Apple Silicon) | `cora-aarch64-apple-darwin-%s.tar.gz` |\n' "${{ steps.version.outputs.VERSION }}"
printf '| Windows (x86_64) | `cora-x86_64-pc-windows-msvc-%s.zip` |\n\n' "${{ steps.version.outputs.VERSION }}"
printf '### 🚀 Quick Start\n\n'
printf '```bash\n'
printf '# Install via cargo\n'
printf 'cargo install cora\n\n'
printf '# Or download binary (extracts as '\''cora'\'')\n'
printf 'curl -fsSL https://github.com/ajianaz/cora-cli/releases/latest/download/cora-x86_64-unknown-linux-gnu-%s.tar.gz | tar xz\n' "${{ steps.version.outputs.VERSION }}"
printf 'chmod +x cora\n'
printf 'sudo mv cora /usr/local/bin/\n\n'
printf '# Init project\n'
printf 'cora init\n\n'
printf '# Review staged changes\n'
printf 'CORA_API_KEY=your-key cora review --staged\n'
printf '```\n\n'
printf '**Full changelog:** https://github.com/ajianaz/cora-cli/blob/develop/CHANGELOG.md\n'
} > release-notes.md

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
Expand Down
122 changes: 122 additions & 0 deletions AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# AGENT.md — AI-Assisted Development Onboarding

## Project Overview

**cora-cli** is a Rust CLI tool for AI-powered code review. Bring Your Own Keys (BYOK) —
no managed API, no cloud service. Runs locally against diffs, scans, or branches.

- **License:** MIT
- **Edition:** Rust 2024 (MSRV 1.85)
- **Repo:** `ajianaz/cora-cli`
- **Default branch:** `develop`

## Build & Development Commands

```bash
cargo build # Build (debug)
cargo build --release # Build (release)
cargo test # Run all 151 tests
cargo clippy # Lint
cargo fmt # Format check (use -- --check for CI)
```

## Code Structure (`src/`)

```
src/
├── main.rs # Entry point, CLI argument parsing
├── commands/ # Subcommand handlers
│ ├── mod.rs
│ ├── review.rs # cora review (diff-based review)
│ ├── scan.rs # cora scan (full-file scan)
│ ├── config_cmd.rs # cora config (show/validate)
│ ├── auth.rs # cora auth (API key management)
│ ├── hook_cmd.rs # cora hook (pre-commit hook install/uninstall)
│ ├── init.rs # cora init (project scaffolding)
│ ├── upload.rs # cora upload (review upload)
│ ├── completion.rs # Shell completion generation
│ └── providers.rs # cora providers (list providers)
├── config/
│ ├── mod.rs
│ ├── schema.rs # Config structs & defaults
│ ├── loader.rs # Config loading & priority chain
│ └── providers.rs # LLM provider definitions
├── engine/
│ ├── mod.rs
│ ├── review.rs # Review orchestration logic
│ ├── scanner.rs # File scanning engine
│ ├── llm.rs # LLM API interaction
│ └── types.rs # Severity, finding, and result types
├── formatters/ # Output format implementations
│ ├── mod.rs
│ ├── pretty.rs # Human-readable terminal output
│ ├── compact.rs # Compact single-line output
│ ├── json_fmt.rs # JSON output
│ └── sarif.rs # SARIF format (CI integration)
├── git/
│ ├── mod.rs
│ ├── diff.rs # Git diff parsing
│ └── files.rs # File listing & filtering
└── hook/
├── mod.rs
├── install.rs # Hook install/uninstall logic
└── template.rs # Hook script templates
```

## Key Files

| File | Purpose |
|---|---|
| `commands/config_cmd.rs` | Config subcommand — display, validate, path resolution |
| `config/loader.rs` | Config loading with full priority chain resolution |
| `config/schema.rs` | All config structs, defaults, serde annotations |
| `commands/init.rs` | Project scaffolding, `.cora.yaml` generation |

## Testing

```bash
cargo test # 151 tests total
# 129 unit tests
# 16 CLI integration tests
# 6 config tests
cargo test --no-verify # Skip pre-commit hooks (avoids timeout in hooks)
```

Use `--no-verify` when running tests through pre-commit hooks to prevent nested
hook execution and timeouts.

## CI/CD

Three GitHub Actions workflows in `.github/workflows/`:

1. **ci.yml** — PR checks: build, test, clippy, fmt on push to `develop` and PRs
2. **release.yml** — Triggered by `v*` tags; builds for 4 platforms (Linux x86_64,
macOS x86_64, macOS ARM64, Windows x86_64), publishes to crates.io
3. **deploy-website.yml** — Deploys project website/docs

## Config System

### Priority Chain (highest to lowest)

1. **CLI flags** (`--provider`, `--model`, etc.)
2. **Environment variables** (`CORA_PROVIDER`, `CORA_MODEL`, etc.)
3. **Project config** (`.cora.yaml` in repo root)
4. **Global config** (`~/.cora/config.yaml`)
5. **Built-in defaults** (defined in `config/schema.rs`)

### Auth Separation

API keys live in a separate `auth.toml` file (`~/.cora/auth.toml`), not in
`.cora.yaml`. This prevents accidental key leakage via committed config files.

## Design Decisions

- **Severity comparison uses `<=` not `>=`**: Severity filtering uses `<=` because
the ordinal maps info=1, warning=2, error=3 — so `--severity warning` means
"include warning and below (info)", not "warning and above (error)". This is
intentional; the mapping is defined in `engine/types.rs`.
- **TOML → YAML migration**: Config format migrated from TOML to YAML for better
readability and broader tooling support. The loader only reads YAML now.
- **Auth/config separation**: API keys are deliberately stored in `auth.toml`
rather than the main config to allow `.cora.yaml` to be safely committed to
repositories without exposing secrets.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.3] - 2026-06-01

### Added

- `cora config set --global` — write config to `~/.cora/config.yaml` instead of project `.cora.yaml`
Expand Down Expand Up @@ -86,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Cross-platform** — Linux (x86_64, ARM64), macOS (Apple Silicon), Windows (x86_64)
- **MIT License** — fully open source

[Unreleased]: https://github.com/ajianaz/cora-cli/compare/v0.1.2...develop
[Unreleased]: https://github.com/ajianaz/cora-cli/compare/v0.1.3...develop
[0.1.3]: https://github.com/ajianaz/cora-cli/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/ajianaz/cora-cli/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/ajianaz/cora-cli/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/ajianaz/cora-cli/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cora-cli"
version = "0.1.2"
version = "0.1.3"
edition = "2024"
description = "CLI-first AI code review — BYOK, diff/scan/branch, pre-commit hooks"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ cora init
### 3. Review Staged Changes

```bash
cora review
cora review --staged
```

### 4. Review the Last Commit
Expand Down
6 changes: 6 additions & 0 deletions website/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ declare global {
}
}

// Allow ?raw imports for markdown files
declare module '*?raw' {
const content: string;
export default content;
}

export {};
1 change: 1 addition & 0 deletions website/src/routes/docs/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{ href: '/docs/providers', label: 'Providers' },
{ href: '/docs/examples', label: 'Examples' },
{ href: '/docs/cli-reference', label: 'CLI Reference' },
{ href: '/docs/changelog', label: 'Changelog' },
];
</script>

Expand Down
113 changes: 113 additions & 0 deletions website/src/routes/docs/changelog/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts">
import { onMount } from 'svelte';
import changelogRaw from '../../../CHANGELOG.md?raw';

interface ChangelogSection {
version: string;
date: string;
url: string;
categories: { type: string; items: string[] }[];
}

const REPO = 'https://github.com/ajianaz/cora-cli';

function parseChangelog(md: string): ChangelogSection[] {
const sections: ChangelogSection[] = [];
// Split into version sections at ## headers
const blocks = md.split(/^## \[/m);
for (const block of blocks) {
const headerMatch = block.match(/^\[([^\]]+)\]\s*(?:-\s*)?(\d{4}-\d{2}-\d{2})/);
if (!headerMatch) continue;
const version = headerMatch[1];
const date = headerMatch[2];

// Skip Unreleased if empty (no ### headers with content)
if (version === 'Unreleased' && !block.match(/^###\s+\w+/m)) continue;

const categories: { type: string; items: string[] }[] = [];
const catBlocks = block.split(/^###\s+/m);
for (const catBlock of catBlocks) {
const catMatch = catBlock.match(/^(\w[\w\s]*?)(?:\s*)\n([\s\S]*)$/);
if (!catMatch) continue;
const type = catMatch[1].trim();
const items = catMatch[2]
.split('\n')
.map((l) => l.replace(/^-\s*/, '').trim())
.filter(Boolean);
if (items.length > 0) {
categories.push({ type, items });
}
}

// Build compare URL
let url: string;
if (version === 'Unreleased') {
url = `${REPO}/compare/v0.1.3...develop`;
} else {
url = `${REPO}/releases/tag/v${version}`;
}

sections.push({ version, date, url, categories });
}
return sections;
}

const releases = parseChangelog(changelogRaw);

onMount(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('revealed');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
document.querySelectorAll('.scroll-reveal').forEach((el) => observer.observe(el));
return () => observer.disconnect();
});
</script>

<svelte:head>
<title>Changelog — cora docs</title>
<meta name="description" content="cora-cli changelog — release history and version notes." />
</svelte:head>

<div class="docs-content">
<h1 class="scroll-reveal">Changelog</h1>
<p class="scroll-reveal text-[var(--muted-foreground)]">
For the full changelog, see the
<a href="{REPO}/blob/develop/CHANGELOG.md" class="text-[var(--accent)] hover:underline" target="_blank" rel="noopener noreferrer">repository</a>.
</p>

{#each releases as release, i}
<section class="docs-section scroll-reveal">
<h2 class="flex items-center gap-3">
{#if release.version === 'Unreleased'}
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 text-xs font-medium rounded-full bg-[var(--accent)]/15 text-[var(--accent)] border border-[var(--accent)]/25">
Unreleased
</span>
{:else}
<a href={release.url} class="text-[var(--accent)] hover:underline no-underline" target="_blank" rel="noopener noreferrer">
v{release.version}
</a>
{/if}
<span class="text-sm font-normal text-[var(--muted-foreground)]">{release.date}</span>
</h2>

{#each release.categories as category}
<div class="mt-4">
<h3 class="text-sm font-semibold uppercase tracking-wider text-[var(--muted-foreground)] mb-2">{category.type}</h3>
<ul class="list-disc list-outside ml-4 flex flex-col gap-1.5">
{#each category.items as item}
<li class="text-[var(--foreground)] text-sm leading-relaxed pl-1">{@html item}</li>
{/each}
</ul>
</div>
{/each}
</section>
{/each}
</div>
Loading
Loading