Configuration
cora uses a layered config system with clear separation of concerns. Later sources override earlier ones.
File Roles
| File | Contents | Used by |
|---|---|---|
~/.cora/auth.toml | API key only (secret, chmod 600) | Local dev |
~/.cora/config.yaml | Provider, model, base_url, focus, hook, output, etc. | Global default |
.cora.yaml | Per-project config overrides | Project + CI |
CORA_API_KEY env var | API key for CI/one-shot | CI only |
Config Resolution Order
Settings are resolved in this order (highest priority first):
- CLI flags —
--provider,--model,--base-url, etc. - Environment variables —
CORA_PROVIDER,CORA_MODEL,CORA_BASE_URL - .cora.yaml — Project root config file
- ~/.cora/config.yaml — Global config
- Auto-detect — Provider-specific env vars (
OPENAI_API_KEY,ZAI_API_KEY, etc.) - Built-in defaults — Sensible defaults for all settings
After all sources are merged, config values are validated at load time: out-of-range values (e.g. temperature: 5), unsupported formats (output.format: prety), and misspelled keys (quailty_gate, temprature) fail loudly with a clear message instead of being silently ignored. This applies to temperature (0.0–2.0), max_tokens/timeout (≥1), max_tokens_param, response_format, output.format, hook.mode/on_violation/min_severity, provider.base_url, and profile weight/action/tone/detail_level.
API Key Resolution
--api-keyflag (one-shot)CORA_API_KEYenv var (CI)~/.cora/auth.toml(local dev)- Provider-specific env vars (
OPENAI_API_KEY,ANTHROPIC_API_KEY, etc.)
.cora.yaml Example
Create this file in your project root. Run cora init to generate it.
# cora project config
provider:
provider: openai
model: gpt-4o
base_url: https://api.openai.com/v1
llm:
temperature: 0
max_tokens: 4096
max_tokens_param: auto # auto | max_tokens | max_output_tokens | max_completion_tokens
timeout: 120
cache_ttl: 1440
review:
system_prompt: "You are a senior code reviewer."
# system_prompt_file: ./review-prompt.md
response_format: json_object
# Strip comments from added diff lines before the LLM sees them
# (ALIBI defense, arXiv:2607.24964). Claim flagging is always on.
sanitize_comments: false
static_analysis:
auto_clippy: false # auto-run `cargo clippy` (Rust only)
clippy_output_file: "" # or read clippy output from file
focus: security, performance, bugs
hook:
mode: warn
min_severity: major
max_diff_size: 51200
on_violation: warn # warn | disallow (blocks commit if violations found)
ignore:
files:
- "vendor/**"
- "*.generated.ts"
rules: [] # rule IDs to skip (e.g. ["no-unwrap", "sql-injection"])
output:
format: pretty # pretty | json | compact | sarif
color: true # ANSI colors in terminal output
quality_gate:
enabled: true
thresholds:
max_critical: 0
max_security: 0
bundling:
max_chars_per_group: 60000
max_files_per_group: 20
strategy: smart # smart | flat
coalesce_by_directory: true
coalesce_by_language: true
analysis:
entry_point_patterns:
- "*Handler"
- "resolve_*"
brain:
embedding: auto # auto | hashing | pretrained
profile: clean-code # security-first | performance | clean-code | beginner-friendly | minimal | rust-strict | typescript-strict | go-pragmaticEnvironment Variables
| Variable | Description |
|---|---|
CORA_API_KEY | API key for CI (overrides auth.toml) |
CORA_PROVIDER | Active provider (openai, anthropic, groq, ollama, zai) |
CORA_MODEL | Model name override |
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 |
CORA_NO_CACHE | Skip diff-hash review cache (same as --no-cache) |
GITHUB_TOKEN | GitHub token for SARIF upload |
GITHUB_REPOSITORY | GitHub repo for SARIF upload |
GITHUB_REF | GitHub ref for SARIF upload |
Provider-Specific Env Vars
Each provider has its own API key variable. cora checks these for auto-detection.
# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1
# Anthropic
ANTHROPIC_API_KEY=sk-ant-...
# Groq
GROQ_API_KEY=gsk_...
# Ollama (local, no key needed)
OLLAMA_HOST=http://localhost:11434
# Optional: OLLAMA_API_KEY if your Ollama instance requires auth
OLLAMA_API_KEY=...
# Z.AI
ZAI_API_KEY=...Diff-Hash Caching
cora caches review results by diff hash in ~/.cache/cora/reviews/. If you re-review the same diff, the cached result is returned instantly.
| Setting | Description |
|---|---|
llm.cache_ttl | TTL in minutes (default: 1440 / 24h) |
--no-cache or CORA_NO_CACHE=1 | Bypass cache |
Custom System Prompts
Override the default system prompt for review or scan commands to match your project's coding standards and review criteria.
review:
system_prompt: "Focus on Rust idioms and error handling."
# Or load from a file:
system_prompt_file: ./prompts/review.md
scan:
system_prompt: "Check for OWASP Top 10 vulnerabilities."
system_prompt_file: ./prompts/scan.mdIf both system_prompt and system_prompt_file are set, the file takes precedence.
Response Format (JSON Mode)
Opt into structured JSON output from the LLM by setting review.response_format to json_object. This instructs the LLM to return valid JSON, enabling machine-readable parsing and pipeline integration.
review:
response_format: json_objectRequires provider support for structured output. Works with OpenAI, Anthropic, and compatible APIs.
Anti-Hallucination
cora uses two mechanisms to prevent the LLM from fabricating findings:
- File path injection — Actual file paths are embedded in the prompt, anchoring the LLM to real files in the diff.
- Post-parse filtering — After parsing, any reported file paths or line numbers that don't exist in the actual diff are discarded.
Cross-File Review Context
To review a change accurately, cora injects cross-file context alongside the diff — it doesn't review the diff in isolation. This is deterministic (no extra LLM calls) and bounded by a token budget so cost stays predictable.
Two axes are resolved:
- Outbound — what the changed code calls/imports (function/type definitions the diff references).
- Inbound (blast radius) — who calls the changed code. If a PR modifies a function signature or type, its call-sites across the repo are surfaced so breaking changes can be flagged.
When the budget can't fit a full definition, a thin signature slice is injected instead of skipping it, so more symbols fit under the same budget.
review:
context_chain:
enabled: true # master switch for cross-file context
max_context_tokens: 5000 # budget (~4 chars/token) for injected context
follow_depth: 1 # outbound resolution depth (1 = direct refs only)
include_tests: true # resolve test files via naming convention
include_callers: true # resolve callers of changed code (blast radius)
use_brain: true # enrich prompt with symbol-index intelligence
impact_depth: 2 # blast-radius traversal depth (2 = callers of callers)
prefer_index: true # prefer symbol index (FTS5 + call graph) over regex| Field | Default | Notes |
|---|---|---|
enabled | true | Disable to review the diff only. |
max_context_tokens | 5000 | Approx. 20 KB of code injected. |
follow_depth | 1 | Outbound recursion depth (1 = direct references). |
include_tests | true | Map changed source to its test files. |
include_callers | true | Inbound caller resolution. When the symbol index is unavailable (regex fallback): gitignore-aware file scan bounded to ≤400 files and ≤3 call-sites per symbol. When the index is available (default path, prefer_index: true): up to 20 call-sites per symbol, filtered by ignore.files patterns. |
use_brain | true | Enrich prompt with symbol-index intelligence (impact analysis, affected tests, semantic search). Only active when cora index has been run. |
impact_depth | 2 | Blast-radius traversal depth. See Impact depth guidance below. |
prefer_index | true | Prefer symbol index (FTS5 + call graph) over regex scanning for outbound resolution. |
Impact Depth Guidance
impact_depth controls how many levels up the call graph the blast-radius traversal follows:
1— direct callers only. Cheapest, misses indirect breakage.2— callers of callers. Recommended default — covers the common layered case (handler → service → helper).3— deep blast radius for strongly layered codebases (handler → service → repository → helper). Higher token cost.4+— rarely worth it. The traversal is BFS with cycle protection, so it is always safe, but on real codebases the caller count grows quickly with depth: heavily-used utility functions and entry points turn into hubs with hundreds of callers, and the injected context fills with noise rather than signal.
Why not "unlimited"? Setting a very large depth is technically valid — traversal stops on its own once every reachable caller is visited — but on any non-trivial repo it surfaces the entire transitive caller closure. The prompt budget (max_context_tokens) then truncates the output arbitrarily, so you pay full traversal cost for context the LLM never sees.
How to choose:
- Small / flat repo → leave at
2. - Layered repo (handler/service/repository split) where bugs manifest several layers from the root cause →
3for that repo's.cora.yaml. - Want precision rather than reach → keep
2and rely oncora impactinteractively with--depthto explore specific symbols on demand.
Note:
impact_depthonly governs the automatic blast-radius context injected duringcora review. The interactivecora impact/cora tracecommands default to--depth 3and are unaffected by this setting.
Quality Gate
Quality gate evaluates review findings against configurable thresholds to produce a PASS/FAIL result. This is useful for CI enforcement — block merges when code quality drops below your standards.
quality_gate:
enabled: true
# Global thresholds — any exceeded = FAIL
thresholds:
max_critical: 0 # 0 critical issues allowed
max_major: 3 # max 3 major issues (disabled by default)
max_minor: 10 # max 10 minor issues (disabled by default)
max_security: 0 # 0 security findings allowed
# Per-category overrides
categories:
security:
action: block # block = any finding → CI fail
max_findings: 0
performance:
action: warn # warn = comment only, don't fail CI
max_findings: 5
bug_risk:
action: block
max_findings: 3
style:
action: ignore # skip entirely — don't count toward gateHow It Works
After review, findings are counted by severity and category
Each threshold is checked against actual counts
Category actions determine enforcement:
- block — exceed threshold = gate FAIL (exit code 2)
- warn — report but don't fail gate
- ignore — skip entirely
Actions are validated enums — case-insensitive (
block,Block,BLOCKall work), and an unknown value (e.g.blok) fails at config load instead of silently becoming blocking. A disabled gate (enabled: false) never fails.Overall gate status: PASSED or FAILED
CLI Output
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
QUALITY GATE RESULT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Status: ❌ FAILED
Findings: 2 critical, 1 major, 4 minor, 0 info
Threshold Checks:
❌ max_critical → 2 found ❌ EXCEEDED
✅ max_major → 1 found ✅ OK
✅ max_security → 0 found ✅ OK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Exit Codes
| Code | Meaning |
|---|---|
| 0 | Gate passed, no issues |
| 2 | Gate failed (threshold exceeded) |
Default Behavior
When quality_gate.enabled is false (default), quality gate is skipped. The existing --ci flag and hook.on_violation settings continue to work as before.
Debt Tracking
cora tracks review findings over time and reports quality trends. After each review, a lightweight JSON snapshot is saved to .cora/history/. Use cora debt to view the aggregated report.
debt:
enabled: true # enable auto-save of review snapshots
history_dir: .cora/history # directory for snapshot files
retention_days: 90 # auto-cleanup old snapshotsCLI Usage
cora debt # show debt report table
cora debt --json # machine-readable JSON
cora debt --trend # quality score trend graph
cora debt --badge # shields.io badge JSON
cora debt --estimate # show estimated fix time
cora debt --since v0.4.5 # filter by git tag or dateQuality Score
Ranges from 0–10 (10 = no issues). Penalties per finding:
| Severity | Penalty |
|---|---|
| Critical | -2.0 |
| Major | -1.0 |
| Minor | -0.3 |
| Info | -0.1 |
Badge Integration
Use cora debt --badge output as a shields.io endpoint:
[]()Secrets Pre-Scan
cora runs a deterministic secrets scan before the AI review. 12 built-in patterns detect leaked credentials:
| Pattern | Severity |
|---|---|
AWS Access Key (AKIA...) | Critical |
GitHub Token (ghp_/gho_/ghu_) | Critical |
OpenAI API Key (sk-/sk-proj-) | Critical |
Anthropic API Key (sk-ant-) | Critical |
| Private Key Block | Critical |
| JWT Token | Major |
| And more (Groq, xAI, Slack, Stripe, Google) | Varies |
Secrets are automatically masked in output (e.g. AKIA****CDEF). Test/spec/fixture files are auto-skipped.
Static Security Scanner
cora runs a static security scan on added lines before the AI review. 11 built-in patterns detect common vulnerabilities:
| Pattern | Category | Severity |
|---|---|---|
| MD5 used for password hashing | Weak crypto | Major |
| SHA-1 used for password hashing | Weak crypto | Major |
| Weak hash algorithm (MD5/SHA1) | Weak crypto | Minor |
| Hardcoded password or secret | Hardcoded secret | Critical |
| SQL injection via string concatenation | Injection | Critical |
| eval() with dynamic input | Injection | Critical |
| Command injection via exec/system | Injection | Critical |
| Hardcoded role or permission check | Auth | Major |
| Debug mode enabled | Config | Major |
| CORS wildcard allows all origins | Config | Major |
| SSL certificate verification disabled | Crypto | Critical |
Test files are automatically skipped. Findings are injected into the LLM prompt as additional context.
What the hardcoded-secret check does (and does not) flag
The check looks for a secret-named key (password, secret, token, api_key, ...) assigned a literal value, and ignores values that are references:
- Flagged: quoted literals (
password = "...", typed declarations, Go:=, JSON"password": "..."), SQLPASSWORD '...', literal defaults inside env reads (os.getenv("X", "literal"),process.env.X || "literal"), shell${VAR:-literal}, bare values in.env/.properties/.ini/ shell / YAML files when they look like a secret. - Not flagged: env reads and other expressions (
process.env.X,os.getenv("X")), identifiers and member accesses,${VAR}/$VARinterpolation, empty strings, UI bindings, placeholders such as%s/$1/. - Known limit: an unquoted YAML value made only of letters (
password: correcthorsebattery) is not flagged. It cannot be told apart from a reference to another key (secret: kubernetes,token: optional) without flooding Kubernetes/Compose files with false positives. Quote the value, or give it a digit or symbol, and it is detected. Usecora-ignore: <rule>(see below) for a finding you accept.
Language-Specific Analyzers
cora detects the languages in your diff and injects tailored review guidance:
| Language | Guidance |
|---|---|
| Dart / Flutter | Widget lifecycle, state management, async patterns, null safety |
| Svelte / TypeScript | Reactivity, store patterns, SSR considerations, type safety |
| Go | Error handling, concurrency, goroutine leaks, interface design |
| Rust | Ownership, lifetimes, error handling, unsafe usage, idioms |
| Python | Type hints, async patterns, security (pickle/eval), common pitfalls |
No configuration needed — language context is auto-detected from file extensions in the diff.
Quality Profiles
cora includes built-in quality profiles for different review focus:
| Profile | Description |
|---|---|
security-first | Strict security focus — zero tolerance for vulnerabilities |
performance | Focus on speed, memory, and allocation patterns — best for hot-path code |
clean-code | (default) Broad quality — readability, naming, complexity — best for team projects |
beginner-friendly | Gentle review — focus on common mistakes and learning opportunities |
minimal | Only critical + security — best for quick PRs and hotfixes |
rust-strict | Rust-specific: unsafe, unwrap, panic, lifetime, error handling, idiomatic patterns |
typescript-strict | TypeScript-specific: any types, null safety, proper typing, async patterns |
go-pragmatic | Go-specific: error handling, goroutine safety, interface design, idiomatic Go |
Run cora profile list to see all profiles. Cora auto-detects the best profile based on your project's primary language (Rust → rust-strict, Go → go-pragmatic, others → clean-code).
Set in .cora.yaml:
profile: security-firstCustom Rule Engine
Write your own regex-based rules under rules_engine.custom in .cora.yaml:
rules_engine:
custom:
- id: no-unwrap
pattern: "\\.unwrap\\(\\)"
severity: minor
message: "Avoid unwrap() in production code — use proper error handling"
languages: ["rs"]
exclude: ["tests/**"]
- id: no-console-log
pattern: "console\\.log\\("
severity: minor
message: "Remove console.log before merging"
languages: ["ts", "js"]| Field | Required | Description |
|---|---|---|
id | yes | Unique rule id; must not collide with a built-in rule id (it is what ignore.rules and cora-ignore name) |
pattern | yes | A regex matched against each line; an invalid regex is a config error |
severity | yes | critical, major, minor or info |
message | yes | The finding text |
languages | no (default ["all"]) | File extensions ("rs", "py", "ts") or language names ("rust", "typescript"); "all" or "*" matches every file. An unknown value is a config error |
exclude | no (default none) | Glob patterns of paths to skip |
The top-level rules: key is a different thing: a list of free-text instructions for the LLM reviewer (strings), not rule objects.
Rules run during the deterministic pre-scan phase (no LLM needed). cora config validate rejects an invalid pattern, a duplicate id, an unknown language or an empty message with the rule id. cora config validate --dry-run [--path DIR] also runs every custom rule over the project and prints how many lines each one matched, so a rule that matches nothing is visible.
Index Scanner Configuration
Control how index-based scanners (unused imports, dead code, breaking changes) behave — including file skip patterns to reduce false positives on bundler entry points and config files.
rules_engine:
enabled: true
max_findings: 50
index_skip_files:
- "*.config.ts"
- "vite.config.*"
- "webpack.config.*"
- "tailwind.config.*"
- "next.config.*"
- "src/main.ts"
- "src/index.tsx"
- "src/app.tsx"
- "build.rs"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable/disable the rule engine |
max_findings | int | 50 | Max deterministic findings shown per run; Critical and Major are never hidden (0 or null = unlimited). See below |
include_tests | bool | false | Scan test code like any other code (see "Test code" below) |
clone_hints | bool | false | Run the qual-clone hint for Rust .clone() calls (info; noisy on idiomatic Rust, so off by default) |
index_skip_files | [string] | (see below) | Glob patterns for files to skip during index scanning |
Test code
Tests legitimately unwrap(), println!, clone, use http:// fixtures and fake credentials, so by default the rules treat test code differently:
- What counts as test code: whole files under test paths (
tests/,*_test.*,*.test.ts,spec/,__tests__/,fixtures/,examples/), Rust#[cfg(test)]modules and#[test]functions (found by brace matching), and, in a diff, a hunk insidemod tests. - What is skipped there: the built-in rules
bug-unwrap,bug-expect,bug-println,qual-clone,qual-error-ignore,sec-hardcoded-urlandsec-hardcoded-secret. The secret scanners only run their high-confidence patterns (AWS keys, GitHub tokens, private keys, ...) on test code, so a real credential committed in a test is still found. bug-printlnis also skipped in program entry points (main.rs,src/bin/,examples/,benches/). Other CLI modules keep the rule; useignore.rulesto silence it there.- Not exempt: custom rules,
bug-todo, and every other pattern. In particular, injection,eval, weak-crypto and similar security patterns still apply inside inline test blocks, so wrapping code in a test-looking block does not hide it. (Whole test files by path keep the older behavior: only high-confidence secrets are scanned.) - Set
rules_engine.include_tests: trueto scan test code like any other code. Detection is conservative: when it cannot tell, the line stays scanned. A#[cfg(test)]region is only trusted when brace matching and indentation agree (block comments are skipped; a region that never closes, or has an item starting at or left of its own indentation, is ignored rather than extended over the code after it), and a diff that does not show themod testsline is scanned normally.
What max_findings caps
max_findings caps the deterministic findings only (rule engine, secrets, security, and index scanners; the findings that carry a rule_id). LLM issues are never capped.
- The cap is applied after suppression (
ignore.rulesand inlinecora-ignore:markers), so suppressed findings never use up slots and hide real ones. - Findings are kept highest severity first (Critical, Major, Minor, Info); ties keep their original order. Critical and Major findings are never hidden: the cap limits the noise (Minor, Info) and never drops below the number of serious findings, so a project with 60 Major findings sees all 60 (and no Minor ones). Because the most severe findings always survive, the cap never changes the blocking decision.
- When findings are cut,
cora scanandcora reviewprint one line on stderr (⚠ N more deterministic findings not shown (rules.max_findings = 50; critical and major are never hidden; raise it or set 0/null to show all)), and thecora scansummary mentions it. JSON, SARIF and compact output on stdout are unchanged. 0ornullmeans unlimited. For large diffs thatcora reviewsplits into chunks, the cap applies per chunk.
Default index_skip_files patterns (bundled with cora):
*.config.ts,*.config.js,*.config.mjs— bundler/build config filesvite.config.*,next.config.*,tailwind.config.*— framework config filessrc/main.ts,src/index.tsx,src/app.tsx,src/main.rs,src/lib.rs— app entry pointsbuild.rs— Rust build scripts
Why skip these? Entry-point files and bundler configs often import symbols that are consumed by the bundler/compiler, not by other source code. Index scanners detect these as "unused" because there are no code-level references. Skipping them eliminates the most common source of false positives.
Glob patterns support: exact match (main.rs), wildcard suffix (*.config.ts), wildcard prefix (vite.*), and any-directory (**/main.ts).
Brain Embedding Backend
Control which embedding backend Brain Mode uses for vector search. Selectable at runtime — no recompilation needed to switch.
brain:
embedding: auto # auto | hashing | pretrained| Value | Dimensions | Description |
|---|---|---|
auto (default) | 256 or 768 | Best available — uses pretrained if compiled with pretrained-embed feature, otherwise hashing |
hashing | 256 | Force zero-dependency bag-of-tokens hashing. Always available, no model download |
pretrained | 768 | Force nomic-embed-code distilled embeddings. Requires pretrained-embed feature at build time |
⚠️ Cannot mix dimensions. 256d and 768d vectors cannot coexist in the same usearch index. After changing this setting, run
cora index --rebuildto regenerate embeddings with the new backend.
Note: If you select
pretrainedbut cora was built without thepretrained-embedfeature, it falls back tohashingwith a warning.
Inline Suppression (cora-ignore)
ignore.rules hides a finding across the whole project (substring match on title or type, plus exact match on rule id). To suppress one finding at one location, put a marker in a source comment:
const bytesPerToken = 4; // cora-ignore: Hardcoded password or secret in variable
// cora-ignore: Rule A, Rule B (comment-only line: applies to the next line)
const next = compute();- Syntax:
cora-ignore: <rule>[, <rule>...], matched as a substring of the line, so it works after//,#,--, inside/* */or<!-- -->, etc. The marker is case-insensitive. <rule>is the finding title or its rule id (e.g.sec-hardcoded-secret,index-dead-code), matched exactly and case-insensitively (not a substring, unlikeignore.rules). Ids are stable, so prefer them over titles.- Finding a rule's id: deterministic findings carry a
rule_id, shown as(rule: <id>)in pretty output,(<id>)at the end of compact lines, asrule_idin JSON, and asproperties.coraRuleIdin SARIF. LLM findings have no id; use their title. - If an LLM finding and a scanner finding on the same line describe the same problem (they share a topic word, e.g. both are about a hardcoded password/secret), only the LLM finding is reported, and a marker (or
ignore.rulesentry) naming the scanner rule's id or title still suppresses it. If the LLM finding is about something else (say SQL injection next to a hardcoded secret), both are reported and a marker only suppresses the one it names. - Scope: the same line; plus the next line when the marker line contains only a comment.
- A bare
cora-ignore(no:or empty rule list) suppresses nothing, so every suppression names its rule. - The rule list runs to the end of the line (or the comment terminator). Put an explanation after
--:// cora-ignore: sec-hardcoded-secret -- env var name, not a value. Text without the separator becomes part of the rule name. - A marker on a line that has findings but names none of them (a typo, an unknown rule, trailing text) prints
warning: cora-ignore at FILE:LINE matched no finding ...on stderr, listing the rule ids that are on that line. - Applies to both deterministic scanner and LLM findings, in both
cora reviewandcora scan. Other rules on the same line and the rest of the project stay visible. Works alongsideignore.rulesandignore.files. cora reviewreads markers from the diff (added and context lines); a marker outside the diff's context window is not seen.cora scanreads markers from the full file contents.cora scanalso runs the deterministic secrets and security scanners on every scanned file and appliesignore.rules, so those findings are reported even when the LLM call fails.
Path-scoped ignore rules
ignore.rules entries can be plain strings (the rule is silenced everywhere) or objects that silence a rule only under some paths:
ignore:
rules:
- "Hardcoded password or secret" # whole project
- rule: bug-println
files: ["crates/cli/**", "evals/**"] # only these paths
- rule: bug-expect
files: ["**/subprocess.rs"]
reason: "expects state a local invariant" # optional, for reviewersrule is matched like a plain entry (rule id or title fragment). files uses the same globs as ignore.files and must not be empty (an empty list is a config error). It applies to review, scan and the MCP tools, before the max_findings cap. reason is documentation for the people reading the config.
Ignore Files
Exclude files or directories from all cora operations — review, scan, and indexing. This is the broadest exclusion mechanism.
ignore:
files:
- "vendor/**"
- "*.min.js"
- "**/generated/**"
- "*.lock"| Pattern | Matches |
|---|---|
src/main.ts | Exact path — only src/main.ts |
*.config.ts | Suffix wildcard — any file ending in .config.ts |
vite.config.* | Prefix wildcard — vite.config.js, vite.config.ts |
**/main.ts | Any-dir name — src/main.ts, app/main.ts, a/b/main.ts |
**/phaser/** | Any-dir wildcard — any path containing a phaser/ directory |
src/engine/** | Prefix-dir — everything under src/engine/ |
**/*.test.ts | Double wildcard ext — any .test.ts file anywhere |
Auto-skipped by default (gitignore-aware): node_modules/, target/, .git/, dist/, build/.
ignore.filesvsrules_engine.index_skip_files:ignore.filesexcludes files from everything (review, scan, index).rules_engine.index_skip_filesexcludes files from index scanners only (dead code, unused imports) — they're still reviewed by the LLM.
Static Analysis
Run language-specific static analysis tools automatically during review and feed their output to the LLM for better findings.
review:
static_analysis:
auto_clippy: false # auto-run `cargo clippy` (Rust only)
clippy_output_file: "" # or read clippy output from a file| Field | Default | Description |
|---|---|---|
auto_clippy | false | Auto-run cargo clippy --message-format=json and inject warnings into review context (Rust projects only) |
clippy_output_file | "" | Read clippy JSON output from a file instead of running clippy (useful for CI where clippy runs separately) |
Bundling
Deprecation notice: as of the current release,
cora scandoes not read this section — batch sizing is governed by--batch-files(default 20) and an internal ~60,000-character batch budget. These keys are parsed but have no effect oncora scan. They are kept for backward compatibility; do not rely on them.
bundling:
max_chars_per_group: 60000 # max source characters per LLM batch
max_files_per_group: 20 # max files per batch
strategy: smart # grouping strategy: smart | flat
coalesce_by_directory: true # merge small batches from the same directory
coalesce_by_language: true # merge small batches with the same language| Field | Default | Description |
|---|---|---|
max_chars_per_group | 60000 | Soft limit on source characters per batch |
max_files_per_group | 20 | Max files per batch before splitting |
strategy | smart | Grouping strategy: smart (coalesce by directory + language within limits) or flat (first-fit by character count, legacy) |
coalesce_by_directory | true | Merge small batches from the same directory into one LLM call |
coalesce_by_language | true | Merge small batches with the same primary language |
Analysis
Configure entry-point symbol patterns for architecture and call-graph analysis. Entry points are treated as roots when tracing execution paths and detecting dead code.
analysis:
entry_point_patterns:
- "*Handler"
- "resolve_*"
- "*Middleware"
- "main"| Field | Default | Description |
|---|---|---|
entry_point_patterns | [] | Glob patterns identifying entry-point symbols (used by dead-code, trace, and arch commands to avoid false positives on intentionally-unreachable functions) |
Tech Debt Tracker
cora tracks review history and calculates tech debt metrics over time.
Config
debt:
enabled: true # default: true
history_dir: .cora/history # snapshot storage
retention_days: 90 # auto-cleanup old snapshotsCLI
cora debt # Show debt report table
cora debt --json # Machine-readable JSON output
cora debt --trend # ASCII quality score graph
cora debt --since 2026-06-01 # Filter by date
cora debt --since v0.4.5 # Filter by git tag
cora debt --branch develop # Filter by branchSnapshots are auto-saved after every review (best-effort, never fails the review).
MCP Server
cora includes a built-in MCP (Model Context Protocol) server that exposes rules and config to AI coding agents like Claude Code, Cursor, Copilot, and Windsurf.
Start the server
cora mcp # Start MCP server
cora serve # Start MCP server + auto-reindex on startup (ensures fresh index)Available tools
| Tool | Description |
|---|---|
cora.list_rules | List all rules, security patterns, and secret patterns |
cora.check_snippet | Check a code snippet against deterministic scanners (no LLM) |
cora.get_quality_gate | Get quality gate config and thresholds |
cora.get_config | Get effective project config (no secrets exposed) |
cora.list_profiles | List all quality profiles |
cora.search_symbols | Search the symbol index (requires cora index) |
cora.find_callers | Find all callers of a symbol (reverse call graph) |
cora.find_impact | Analyze blast radius of changing a symbol |
cora.find_affected_tests | Find test files affected by changed source files |
cora.index_status | Check if a symbol index exists and get statistics |
cora.review_diff | Review a git diff using cora's full pipeline (makes LLM call) |
cora.review_prepare | Agent-as-reviewer step 1: prompts, valid_files and deterministic findings for a diff. No LLM in cora |
cora.review_finalize | Agent-as-reviewer step 2: merge and validate the agent's findings, apply rules and gate. No LLM call |
cora.scan_prepare | Agent-as-reviewer scan step 1: one batch of project files (paged) plus deterministic findings |
cora.scan_finalize | Agent-as-reviewer scan step 2: merge and validate the agent's findings for the whole scan |
cora.get_debt | Get tech debt report from review history |
cora.get_project_info | Get project context (repo, branch, cora version, index status) |
cora.get_memory | Recall project patterns from Uteke (requires uteke CLI) |
cora.brain_search | Hybrid code search: FTS5 + vector + graph → RRF fusion |
cora.install | Detect installed AI agents and configure cora as MCP server |
cora.dead_code | Find potentially dead code (functions with no callers) |
cora.query | Query the code graph with simple patterns (e.g. main -> *) |
Configure in Claude Code
Add to your project's .claude/settings.json:
{
"mcpServers": {
"cora": {
"command": "cora",
"args": ["mcp"]
}
}
}Configure in Cursor
Add to .cursor/mcp.json:
{
"mcpServers": {
"cora": {
"command": "cora",
"args": ["mcp"]
}
}
}The MCP server communicates via JSON-RPC 2.0 over stdio — no HTTP server needed.