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
1 change: 1 addition & 0 deletions .github/workflows/cla-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
jobs:
cla-check:
runs-on: ubuntu-latest
if: "!contains(fromJSON('[\"app/dependabot\", \"app/renovate\", \"github-actions[bot]\"]'), github.event.pull_request.user.login)"
steps:
- name: Fetch & check CLA signature
id: check
Expand Down
5 changes: 3 additions & 2 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ semantic search engine (Brain Mode).
```bash
cargo build # Build (debug)
cargo build --release # Build (release)
cargo test # Run all 708 tests (default) / 714 (tree-sitter)
cargo test # Run all 934 tests (default) / 934 (tree-sitter)
cargo clippy --all-targets -- -D warnings # Lint (strict -D warnings)
cargo fmt --all -- --check # Format check
```
Expand Down Expand Up @@ -74,6 +74,7 @@ src/
│ ├── llm.rs # LLM API interaction
│ ├── types.rs # Severity, finding, and result types
│ ├── diff_parser.rs # Diff → FileChunk parsing
│ ├── enclosing.rs # Enclosing-scope control-flow context for review prompts
│ ├── chunker.rs # Auto-chunking large diffs
│ ├── profiles.rs # Quality profiles (strict/balanced/lax)
│ ├── quality_gate.rs # Quality gate thresholds + pass/fail
Expand Down Expand Up @@ -125,7 +126,7 @@ src/
## Testing

```bash
cargo test # 708 tests (default) / 714 (tree-sitter)
cargo test # 934 tests (default) / 934 (tree-sitter)
# 637 unit tests
# 16 CLI integration tests
# 6 config tests
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.14.0] - 2026-08-28

### Fixed

- **Empty LLM responses from reasoning models.** Models like GLM can spend the entire `max_tokens` budget on chain-of-thought and return `content: ""` with `finish_reason: "length"`, which previously surfaced as a misleading `EOF while parsing` error. Cora now reads `finish_reason`/`reasoning_content`, automatically retries with a doubled budget (up to 32768), salvages JSON from reasoning text as a last resort, and reports an explicit "EMPTY response" error when nothing is recoverable (#536).

- **Dead-code false positives from cross-crate method calls.** Method calls inside Rust `impl` blocks were never walked for call edges, and call targets stored raw AST text (`self.export_full`) that could not join against symbol names — 557 false positives on a 5-crate workspace (#519).
- **Index root mismatch between CLI and MCP.** Running `cora index` inside a workspace member crate created a separate project row from the one MCP resolved, so `index_status` reported 0 symbols despite a populated DB. Root resolution now prefers a `[workspace]` Cargo.toml and never climbs past a `.git` boundary (#522).
- **`ignore.files` was not honored by the index.** Skip patterns only invalidated fingerprints; matched files were still indexed and surfaced in dead-code/review findings. They are now excluded from indexing entirely (#521).

### Changed

- **Default `max_tokens` raised from 4096 to 8192** to give reasoning models headroom above their chain-of-thought (#536).

### Changed

- **Default `max_tokens` raised from 4096 to 8192** to give reasoning models headroom above their chain-of-thought (#536).
- **Dead-code now skips public API surface by default** (`pub`/`export` items) — new `--include-pub` flag and MCP `include_pub_api` parameter opt back in (#520).
- **Review prompts include enclosing control-flow scope.** Hunks touching branching constructs get the enclosing function from the post-image (120-line cap), plus an always-on guardrail against unverified reachability claims (#523).
- **Incremental re-index reports honestly.** No-op runs print "Index up to date" with stored totals instead of "Indexed 0 symbols"; MCP `index_status` carries a root-mismatch hint (#522).
- **Relicensed from MIT to Apache-2.0.** All 18 CodeCoraDev repositories now
standardize on Apache-2.0 for patent grant protection and open-core model
compatibility. Added CLA (Individual + Corporate) for contributor copyright
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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-code"
version = "0.13.0"
version = "0.14.0"
edition = "2024"
description = "CLI-first AI code review — BYOK, diff/scan/branch, pre-commit hooks"
license = "Apache-2.0"
Expand Down
25 changes: 25 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ Security-related areas of the codebase:
- `src/hook/` — Pre-commit hook integration
- `src/index/` — File access and SQLite storage

## Threat Model: Adversarial Source-Code Comments (ALIBI)

LLM-based reviewers are vulnerable to adversarial comments in the code under
review that steer reviewer reasoning without changing program behavior —
attack success exceeds 90% across 125 real-world vulnerabilities, with
fabricated tool-result claims ("sanitizer passed", "already validated") being
the most effective vector (arXiv:2607.24964).

**Prompt-level defenses (telling the model to ignore comments) are proven
ineffective against adaptive attacks.** Cora therefore uses architectural
defenses:

- **Claim flagging (always on)** — added comments asserting verification or
tool results are detected heuristically and injected into review context as
*untrusted claims*, never as facts.
- **Comment sanitization (opt-in)** — set `review.sanitize-comments: true` in
`.cora.yaml` to strip comment bodies from added diff lines before the LLM
sees them. Line structure is preserved (`[comment removed]` markers), so
findings still map to real line numbers. Deterministic scanners (rules,
secrets, security patterns) always run on the *unsanitized* diff.
- Sanitization is heuristic (line-comment markers `//`, leading `#`, `--`,
`;`); block comments (`/* */`, `"""..."""`) are not currently stripped.

Relevant code: `src/engine/comment_sanitizer.rs`.

## Responsible Disclosure

We follow responsible disclosure principles:
Expand Down
3 changes: 2 additions & 1 deletion docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ See [Code Intelligence](./code-intelligence) for detailed usage.
| `cora affected` `<files...>` | Find test files affected by source changes |
| `cora affected --stdin` | Read changed files from stdin (pipe from `git diff --name-only`) |
| `cora affected --filter` `"*test*"` | Custom test file glob pattern |
| `cora dead-code` | Detect dead code — functions/methods with zero callers |
| `cora dead-code` | Detect dead code — functions/methods with zero callers (public API surface skipped by default; honors ignore.files via the index) |
| `cora dead-code --include-pub` | Include public API surface (pub/export items) in results |
| `cora dead-code --include-tests` | Include test functions in results |
| `cora dead-code --min-lines N` | Filter out tiny functions |
| `cora query` `"main -> *"` | Query the code graph with simple patterns |
Expand Down
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ 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
Expand Down
6 changes: 5 additions & 1 deletion src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,15 @@ pub async fn execute_scan(
// 2b. Run index-powered deterministic scans (unused imports, dead code)
// These work even without LLM and add findings to the final report.
let root_abs = root.canonicalize().unwrap_or_else(|_| root.clone());
// Same merged exclusion set as `cora index` (#521).
let mut index_skip = config.ignore.files.clone();
index_skip.extend(config.rules_config.index_skip_files.iter().cloned());
index_skip.dedup();
let index_findings = crate::engine::index_scanner::scan_project_index(
&root_abs,
&files,
config.rules_config.max_findings,
&config.rules_config.index_skip_files,
&index_skip,
);
if !index_findings.is_empty() {
eprintln!(
Expand Down
10 changes: 7 additions & 3 deletions src/commands/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ pub fn run_watch(
// Load skip patterns + brain embedding backend from config
let config =
crate::config::loader::load_config(config_path, None, None, None, None, false).ok();
let skip_patterns: Option<Vec<String>> = config
.as_ref()
.map(|c| c.rules_config.index_skip_files.clone());
// Same merged exclusion set as `cora index` (#521).
let skip_patterns: Option<Vec<String>> = config.as_ref().map(|c| {
let mut pats = c.ignore.files.clone();
pats.extend(c.rules_config.index_skip_files.iter().cloned());
pats.dedup();
pats
});

// Resolve embedding backend
let brain_mode = config
Expand Down
20 changes: 17 additions & 3 deletions src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub struct Config {
pub cache_ttl: u64,
/// Static analysis context injection for reviews.
pub static_analysis: StaticAnalysisConfig,
/// Strip comments from added diff lines before the LLM sees them
/// (ALIBI defense, arXiv:2607.24964).
pub sanitize_comments: bool,
/// Rule engine configuration.
pub rules_config: RulesConfig,
/// Context chain configuration — cross-file dependency extraction.
Expand Down Expand Up @@ -143,10 +146,11 @@ impl Default for Config {
response_format: "none".to_string(),
review_system_prompt_override: None,
review_system_prompt_file: None,
sanitize_comments: false,
scan_system_prompt_override: None,
scan_system_prompt_file: None,
temperature: 0.0,
max_tokens: 4096,
max_tokens: 8192, // #536: reasoning models need headroom above chain-of-thought
max_tokens_param: "auto".to_string(),
timeout: 600,
cache_ttl: 1440, // 24h in minutes
Expand Down Expand Up @@ -404,6 +408,10 @@ pub struct ReviewSection {
/// Static analysis context injection (e.g., clippy output).
#[serde(skip_serializing_if = "Option::is_none")]
pub static_analysis: Option<StaticAnalysisConfig>,
/// Strip comments from added diff lines before the LLM sees them
/// (ALIBI defense, arXiv:2607.24964).
#[serde(skip_serializing_if = "Option::is_none")]
pub sanitize_comments: Option<bool>,
/// Context chain configuration (cross-file dependency extraction).
#[serde(skip_serializing_if = "Option::is_none")]
pub context_chain: Option<crate::engine::context::types::ContextConfig>,
Expand Down Expand Up @@ -658,6 +666,9 @@ impl CoraFile {
if let Some(sa) = &r.static_analysis {
config.static_analysis.clone_from(sa);
}
if let Some(v) = r.sanitize_comments {
config.sanitize_comments = v;
}
if let Some(cc) = &r.context_chain {
config.context_chain.clone_from(cc);
}
Expand Down Expand Up @@ -1211,6 +1222,7 @@ review:
system_prompt: None,
system_prompt_file: None,
static_analysis: None,
sanitize_comments: None,
context_chain: None,
}),
..Default::default()
Expand All @@ -1228,6 +1240,7 @@ review:
system_prompt: Some("Custom prompt here.".to_string()),
system_prompt_file: None,
static_analysis: None,
sanitize_comments: None,
context_chain: None,
}),
..Default::default()
Expand All @@ -1248,6 +1261,7 @@ review:
system_prompt: None,
system_prompt_file: Some("prompts/review.md".to_string()),
static_analysis: None,
sanitize_comments: None,
context_chain: None,
}),
..Default::default()
Expand Down Expand Up @@ -1326,7 +1340,7 @@ scan:
#[test]
fn config_default_max_tokens() {
let cfg = Config::default();
assert_eq!(cfg.max_tokens, 4096);
assert_eq!(cfg.max_tokens, 8192);
}

#[test]
Expand Down Expand Up @@ -1388,7 +1402,7 @@ llm:
cora.merge_into(&mut cfg).unwrap();
assert_eq!(cfg.temperature, 0.7);
// Other LLM fields should remain at defaults
assert_eq!(cfg.max_tokens, 4096);
assert_eq!(cfg.max_tokens, 8192);
assert_eq!(cfg.timeout, 600);
assert_eq!(cfg.cache_ttl, 1440);
}
Expand Down
Loading
Loading