Skip to content

release: v0.20.0 - #710

Merged
ajianaz merged 22 commits into
mainfrom
release/v0.20.0
Oct 27, 2026
Merged

ajianaz merged 22 commits into
mainfrom
release/v0.20.0

Conversation

@ajianaz

@ajianaz ajianaz commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

What

Release v0.20.0: version bump and CHANGELOG.

Why

Ships everything merged to develop since v0.19.0: path-scoped ignore.rules, commit gate (hook v2 + claude-guard), Anthropic workload identity federation for Cora Review, temperature-retry for models that reject it, scan_finalize coverage report, quieter scan (clone hints opt-in, --min-severity), custom-rule validation, rule false-positive fixes, --help secret redaction, dependency-audit fixes.

Changes

  • Cargo.toml/Cargo.lock 0.19.0 -> 0.20.0
  • CHANGELOG [Unreleased] -> [0.20.0] - 2026-10-27

Testing

fmt, clippy (-D warnings) clean; cargo test --features tree-sitter: 1367 passed, 0 failed.

ajianaz and others added 22 commits October 27, 2026 16:46
## What

Raise `timeout-minutes` of the Cora Review job from 10 to 30.

## Why

The review action allows up to three LLM attempts of 600 s each, but the
job was cancelled at 10 minutes. Medium sized PRs (about 850 added
lines) were cancelled four times in a row (PRs for #674) although the
same review finishes locally in about 5 minutes with no findings.
Cancelled runs leave the required check red without any result.

## Testing

- Config-only change; the job runs on this PR.
- Local `cora review --base develop` on the affected diff passed.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
)

## What

Part 1 of 3 for #674 (split from #675 so each PR fits the review time
limit).

- `engine/commit_gate.rs`: change-set fingerprint, marker store under
the git dir, `check`, and a fail-closed parser for agent `git commit`
command lines.
- `cora hook verify` (exit 0 validated, 10 blocking, 11 not validated)
and `cora hook claude-guard` (Claude Code PreToolUse).
- `cora review --staged` and `cora.review_finalize` record a verdict
marker for the exact staged diff.

## Why

Agents should always run the judge review before committing; the verdict
must be bound to the exact change set. Part of #674. Follow-ups: part 2
(pre-commit hook v2 and e2e tests), part 3 (skill, docs, changelog).

## Testing

- Unit tests for fingerprinting, marker store, bypass parsing
(`--no-verify` prefixes, `-c core.hooksPath`, chained commands,
pathspecs).
- fmt, clippy `-D warnings` and the full test suite pass locally.

---------

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…in place (#688)

## What

Part 2 of 3 for #674 (part 1 is #676).

- Hook template v2: `cora hook verify --quiet` first (0 pass, 10 deny),
then the fallback `cora review --staged --ci --severity major`; a
tooling error is reported as not a pass.
- `cora hook install` recognises legacy v1 hooks and upgrades them in
place instead of wrapping them a second time.
- 12 end-to-end tests with real temporary git repos (agent verdict
through MCP, hook, guard, upgrade).

## Why

The hook is the fallback when the agent judge was not used, and must not
re-review a diff that was already validated. Part of #674.

## Testing

- Unit tests for the template and legacy detection.
- tests/commit_gate.rs: 12 e2e tests, all passing; fmt, clippy -D
warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

Part 3 of 3 for #674 (parts 1 and 2: #676, #688).

- `/cora-review` skill: review the staged diff before every commit.
- `docs/usage.md`: the three-layer commit gate and the Claude Code guard
snippet.
- `docs/cli-reference.md`: `cora hook verify` and `cora hook
claude-guard`.
- CHANGELOG entry.

## Why

Agents need to know to review before committing, and users need the
setup steps. Closes #674.

## Testing

- Docs and skill text only; the skill is embedded in the binary, so the
build and full test suite were run (pass).

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
… secrets (#690)

## What

- `scripts/hooks/pre-commit` and `scripts/setup-hooks.sh`
(`core.hooksPath`): fmt, clippy when Rust files are staged, then `cora
hook verify` with `cora review --staged --ci --severity major` as
fallback.
- `cora-review.yml`: the secret guard now errors with the name of each
empty secret among CORA_API_KEY, CORA_BASE_URL and CORA_MODEL.
- AGENTS.md: a before-committing line; CHANGELOG entry.

## Why

Uses the commit gate from #674 in this repository, and makes sure a Cora
Review without its secrets can never pass silently.

## Testing

- bash -n on the hook, workflow YAML parses, local review passes.
- The secret guard runs on this PR (secrets are set, so it must pass).

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…run (#691)

## What

- `Config::validate` (so config load and `cora config validate`) now
rejects an invalid regex, an empty or duplicate id, an id shadowing a
built-in rule, an empty message, an empty `languages` list and unknown
`languages` values, naming the rule.
- `languages` accepts language names (`rust`) as well as extensions
(`rs`); `languages` and `exclude` are optional.
- `cora config validate --dry-run [--path DIR]` runs each custom rule
over the project and prints how many lines it matched (0 matches is
flagged).
- `docs/configuration.md` shows the real schema (`rules_engine.custom`,
the free-text `rules:` list, required fields); the 0.19.0 changelog line
says `custom`.

## Why

A mistyped pattern or language made a rule silently inactive while
`config validate` said valid. Closes #684, closes #678.

## Testing

- Unit tests for validation, language normalisation and optional YAML
fields.
- 4 end-to-end tests in tests/custom_rules.rs (invalid regex fails
validation, docs example loads and fires, unknown language, dry run
flags a silent rule).
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…d a reason separator (#692)

## What

- A `cora-ignore` marker on a line that has findings but names none of
them (typo, unknown rule, or explanation text that became part of the
rule name) now prints a warning on stderr with the marker location, what
it named and the rule ids on that line.
- ` -- ` ends the rule list: `cora-ignore: sec-hardcoded-secret -- env
var name` suppresses the finding and keeps the reason in the source.
- Docs (Inline Suppression) and CHANGELOG updated.

## Why

A suppression that silently does nothing makes people widen
`ignore.rules` instead. Closes #679. Not done: warning about stale
markers on clean lines (too noisy for diff reviews where only part of
the file is visible); JSON/SARIF reporting of unmatched markers and a
`--strict-markers` flag can follow.

## Testing

- Unit tests: separator, trailing text, unknown rule, matching markers
and clean-line markers not reported.
- 3 end-to-end tests in tests/inline_ignore.rs reproducing the issue
(warning, finding kept; separator suppresses).
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

Remove the CLA gate from this repo: delete
`.github/workflows/cla-check.yml`, and replace the CLA section in
CONTRIBUTING.md with a single inbound=outbound license paragraph.
(README CLA badge removed if present.)

## Why

Fleet CLA retirement — owner decision 2026-10-22, tracked in
codecoradev/uteke#1548. CLA's only real function (future relicensing of
third-party contributions) is ~zero-value here (2 external contributors
/ 3 commits org-wide, ever); Apache-2.0 inbound=outbound legally
suffices for our open-core model (hosted-service monetization is lawful
without a CLA); CLA-free matches the repos we benchmark against and
removes contributor friction. Re-introduction, if ever needed, is
prospective-only.

## Testing

- `git show --stat HEAD` shows only the CLA-related files changed
- No other workflow touched (cora-review / lint / ci / release intact)
- Branch CI green on this PR

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

Dependency-audit fixes: lockfile refreshes + targeted minor updates for
the vulnerable packages; transitive advisories with no compatible fix
get a documented, dated audit-ignore (`.cargo/audit.toml`).

## Why

Closes #695. The Security audit gates have been red on `develop`
(pre-existing; isolated during the fleet CLA retirement, uteke#1548).
This PR makes the gates honestly green.

## Testing

- `cargo audit` before/after locally (advisory DB refreshed): remaining
findings = none beyond the documented ignores
- JS audits re-run locally; remaining lows documented (not gate-failing)
- CI on this PR: all audit gates expected green

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…rs (#693)

## What

- New optional input `batches_judged` on `cora.scan_finalize`; the
batches are recomputed statelessly from the project, like
`scan_prepare`.
- Result fields: `coverage` (`complete`, `partial`, or `unverified` when
not declared), `total_batches`, `batches_judged`, `files_judged`,
`files_total`.
- A partial run says `Judged 1/11 batches (3/23 files)` and
`should_block` is null instead of false. Undeclared runs keep the
boolean and are labelled unverified.
- The `/cora-review` skill passes `batches_judged`; CHANGELOG updated.

## Why

Judging one batch and finalizing read as a clean verdict for the whole
directory. Closes #685.

## Testing

- Unit tests: partial, complete (dedupe, out-of-range ignored),
unverified, and `scan_finalize` end to end.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…riant expect (#698)

## What

- `bug-todo`: only an upper-case `TODO`, `FIXME`, `HACK` or `XXX` whole
word inside a comment. `pub mod todo;`, `todo_state` and prose like "the
todo list" are no longer flagged.
- `bug-println`: no longer matches `eprintln!` / `eprint!`; `println!`,
`print!` and `dbg!` are still flagged.
- `bug-expect`: flags only `.expect("")`; a non-empty message states an
invariant. `bug-unwrap` still covers bare `unwrap()`.

## Why

22 of 22 `bug-todo` hits and all library `bug-println` hits on a real
workspace were false positives (#681, #682). Closes #681. Refs #682:
treating the modules of a binary crate as program code is not done here
and stays open.

## Testing

- Regression tests for the issue repros: identifier, module name, prose,
lower-case, and the real markers; stdout vs stderr macros; `expect` with
and without a message.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…the stored key (#700)

## What

- The Cora Review job exchanges its GitHub OIDC token for a short-lived
Anthropic access token when the four federation identifiers
(`ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`,
`ANTHROPIC_SERVICE_ACCOUNT_ID`, `ANTHROPIC_WORKSPACE_ID`) are available
as organization or repository secrets or variables. Model:
`ANTHROPIC_MODEL`, default `claude-opus-5-5`.
- With a federation token, API key, base URL and model switch together:
the token is only ever sent to `https://api.anthropic.com/v1`, never to
`CORA_BASE_URL`.
- Without one (variables missing, or the exchange fails: it is only a
warning) the job uses `CORA_API_KEY`, `CORA_BASE_URL` and `CORA_MODEL`
as before, and the guard still fails with an error naming a missing
secret.
- `id-token: write` added to the job permissions; docs
(`docs/examples.md`) and CHANGELOG updated.

## Why

A stored API key can leak and does not expire; the exchanged token lives
for one job. The exchange and a Bearer call to `/v1/chat/completions`
(the way cora sends its key) were verified by the smoke test in the
previous PR.

## Testing

- The smoke workflow passed: token exchange HTTP 200,
`/v1/chat/completions` with Bearer HTTP 200.
- Not testable in this PR: the Cora Review job runs from the base branch
for `pull_request_target`, so this change takes effect on the next PR
after merge. The fallback keeps the required check working if the
exchange fails there (the subject for `pull_request_target` may differ
from `pull_request`).

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

A workflow that runs when it changes and checks, without printing any
token:

1. GitHub OIDC token for audience `https://api.anthropic.com`;
2. exchange for a short-lived Anthropic access token (`/v1/oauth/token`,
federation);
3. one tiny call to `/v1/chat/completions` with `Authorization: Bearer
<token>`, exactly how cora sends its key.

The four identifiers come from repository variables
(`ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`,
`ANTHROPIC_SERVICE_ACCOUNT_ID`, `ANTHROPIC_WORKSPACE_ID`) because this
repository is public.

## Why

Before moving the Cora Review job from a stored API key to workload
identity federation, we need to know the exchanged token is accepted on
the endpoint cora uses.

## Testing

- The job runs on this PR (it needs the repository variables to be set
first, otherwise it fails with a message naming the missing one).

---------

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

- A 400 response that names `temperature` is retried once without that
key, and the model is remembered for the rest of the process (no
repeated failing request, and no extra cost on later chunks). Streaming
and non-streaming share the same path.
- Any other 400 (and every other status) is returned unchanged;
providers that accept `temperature` keep the configured value (default
`0.0`, deterministic).

## Why

`claude-opus-5-5` answers HTTP 400 "`temperature` is deprecated for this
model", so every review through the Anthropic workload identity
federation path (#700) ended as "Review could not complete" while the
check still turned green. Closes #701.

## Testing

- Unit tests: request body without a `temperature` key, recognising the
400, per-model memory.
- Two end-to-end tests against a local fake server: the first request
carries `temperature`, the retry does not and succeeds; a different 400
is not retried (one request only).
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…indings in review_prepare (#703)

## What

- `crypto/hardcoded-secret`: the value after a single `:` can no longer
start with another `:`, so `CancelToken::default()`,
`Token::from_parts(..)` and `ApiKey::generate_random()` (paths) are not
read as `token: <value>`. Real cases (`password = "..."`, `token:
"..."`, JSON `api_key`) are still flagged.
- `cora.review_prepare` runs the same post-processing as
`review_finalize` on `deterministic_findings` (`ignore.rules`, inline
`cora-ignore` markers, context filter), without the findings cap (the
note still explains it), so the agent no longer sees and fixes a finding
that finalize would drop.

## Why

A project had to silence the rule by title, which hid real secrets too,
and an agent changed code to avoid a finding the repository already
ignored (#697).

## Testing

- Regression test for type paths versus real secrets in the scanner.
- Test that an ignored finding is not listed by prepare.
- fmt, clippy -D warnings and the full suite pass.

Closes #697.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…--help (#704)

## What

`--help` no longer prints the values of `CORA_BASE_URL`, `CORA_MODEL`
and `CORA_PROVIDER` (clap `hide_env_values`), like the API key and
GitHub token already.

## Why

A base URL can carry credentials (`user:pass@host`, `?key=...`) and the
other two name internal deployments; `--help` output ends up in logs, CI
output and screenshots. Closes #687.

## Testing

- New test: `CORA_BASE_URL=https://u:pass@gateway.internal/v1?key=...`,
`CORA_MODEL` and `CORA_PROVIDER` never appear in `--help`, `review
--help` or `scan --help`.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…mary (#705)

## What

- `qual-clone` (`.clone()` in Rust, info) is opt-in:
`rules_engine.clone_hints: true` turns it on. A default scan of a Rust
project no longer lists `Arc::clone`-style sites.
- New `cora scan --min-severity <info|minor|major|critical>`: hides
lower findings in every output format and in the count. Blocking is
unchanged (still `hook.min_severity`).
- The scan summary (pretty and JSON output) counts by severity: `found
457 issues (1 critical, 150 minor, 306 info)`.
- Docs (`cli-reference`, `configuration`) and CHANGELOG updated.

## Why

On a 133-file Rust workspace 306 of 457 findings were `qual-clone` info;
the count hid the one critical finding and the `max_findings` cap spent
its slots on noise. Closes #683.

## Testing

- Unit test: `qual_clone_is_opt_in` (off by default, on with
`clone_hints`).
- 3 end-to-end tests in tests/scan_noise.rs: no clone sites by default,
the JSON summary counts by severity, `--min-severity major` hides minor
findings.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
## What

`ignore.rules` entries can now be objects, backwards compatible with
plain strings:

```yaml
ignore:
  rules:
    - "Hardcoded password or secret"          # whole project, unchanged
    - rule: bug-println
      files: ["crates/cli/**", "evals/**"]    # only these paths
      reason: "stdout is the cli interface"   # optional, documentation
```

- `rule` is matched like a plain entry (rule id or title fragment);
`files` uses the `ignore.files` glob syntax.
- Applied in `review`, `scan` and the MCP tools, before the
`max_findings` cap.
- An empty `files` list (or an empty `rule`) is a config error instead
of a rule that applies nowhere.
- Docs (`configuration.md`, new section) and CHANGELOG updated.

## Why

A rule can be noise in one crate and a real design guard in another
(`bug-println` in a CLI vs an embedder-safe core); today the only
options are silencing it everywhere or excluding directories from every
check. Closes #680.

## Testing

- Unit tests: a scoped rule is silenced only under its globs, plain
strings still silence everywhere, `**/file.rs` globs.
- Config tests: YAML with strings and scoped objects (with `reason`)
loads; empty `files` fails validation.
- 2 end-to-end tests (tests/scoped_ignore.rs): silenced in `crates/cli`,
still fires in `crates/core`; plain form unchanged.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…de for bug-println (#707)

## What

`bug-println` now exempts every module of a binary-only package (a
`src/main.rs` and no `src/lib.rs` in the same package), not just
`main.rs`, `src/bin/`, `examples/` and `benches/`. A library package, or
one that has both `main.rs` and `lib.rs`, keeps the rule.

The package is looked up relative to the scan root (`cora scan --path`,
the MCP scan tools) or, for a diff review, the git toplevel, never the
process working directory (found by the pre-commit review of this
change).

## Why

In a binary crate stdout is the program's interface, yet its non-entry
modules still produced 64 `bug-println` hits on a real workspace, while
a core crate must keep guarding against writing to stdout. The
`eprintln!` and `expect("...")` halves of #682 shipped earlier; this is
the remaining part. Closes #682.

## Testing

- Unit test with a temporary package layout: binary-only module exempt,
library module flagged, lib+bin package keeps the rule, entry points and
`examples/` exempt, unknown package flagged.
- End-to-end tests (tests/binary_crate_println.rs): `println!` in
`crates/cli/src/commands.rs` is not reported, in
`crates/core/src/lib.rs` it is, `eprintln!` never is; the same holds for
`cora scan --path proj` run from the parent directory.
- fmt, clippy -D warnings and the full suite pass.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ning data (#686) (#709)

## What

Implements #686 for the MCP agent-as-reviewer scan tools:

- `cora.scan_prepare` + `cora.scan_finalize` now accept the CLI's
selection inputs: `include` (globs), `exclude` (globs, merged with
`config.ignore.files`), `extensions`, `incremental` (same cache as `cora
scan --incremental`), `changed_since` (git rev — files changed since it,
untracked included), and `order` (`source-first` default | `path` legacy
walk order).
- `source-first` ordering: code extensions first, then shallower paths,
then path — deterministic.
- Every prepare response carries planning data: `total_chars`,
`estimated_tokens` (`total_chars/4`), per-batch `files` + `chars`, plus
the effective `order`/`include`/`exclude`.
- Finalize consistency: both tools go through one `scan_selection()` +
`scan_files_for()`; `files_fingerprint` still guards — different
filters/order refuse with "project files changed". `incremental`
finalize depends on the cache not changing between calls.
- `changed_since` implementation: `git diff --name-only --relative
<rev>` + untracked files, filtered into the walked set; revs starting
with `-` are rejected.

## Why

Issue #686: agent mode on a real workspace was unusable — no way to
narrow scope beyond `path`, path-ordered batches put core sources last,
and no size/token estimate before committing to hundreds of thousands of
tokens.

## Testing

- TDD: 5 of 6 new tests RED on the old code, all GREEN after (26/26
agent_tools tests).
- Full suite: `cargo test --features tree-sitter` = 1312 passed, 0
failed (independently re-verified by CTO: 10/10 suites ok).
- Gates: fmt clean; clippy `--all-targets --features tree-sitter -D
warnings` clean (CTO re-ran); pre-commit hook (fmt+clippy+cora code
review) passed at commit.
- Done-when check from the issue: `scan_prepare {"include":
["crates/**/*.rs"], "exclude": ["**/tests/**"]}` returns only matching
files, reports estimated totals, orders sources first — covered by
`include_exclude_filter_and_planning_data_is_reported` against a
fixture.

Fixes #686

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
- Bump version 0.19.0 -> 0.20.0 (Cargo.toml, Cargo.lock)
- CHANGELOG: [Unreleased] -> [0.20.0] - 2026-10-27 (14 entries): Anthropic
  workload identity federation + smoke test, commit gate (hook v2,
  claude-guard, validate-before-commit marker), path-scoped ignore.rules,
  temperature retry (#701), secret redaction in --help (#687), review_prepare
  parity and Token::default() fix (#697), scan clone hints/--min-severity/summary
  (#683), scan_finalize coverage report (#685), rule idiom fixes (#681/#682),
  custom rule validation (#684), cora-ignore warnings (#679), CI fixes
@ajianaz
ajianaz merged commit b40b742 into main Oct 27, 2026
13 checks passed
ajianaz added a commit that referenced this pull request Oct 28, 2026
…lop (#711)

## What

Brings the v0.20.0 release bump back into `develop`: `Cargo.toml` /
`Cargo.lock` 0.19.0 -> 0.20.0 and the CHANGELOG `[Unreleased]` entries
moved under `[0.20.0] - 2026-10-27`, exactly as on `main` (after this PR
the `develop` tree equals `main`).

## Why

The release (#710) bumped the version and cut the changelog on `main`
only. `develop` still said 0.19.0 and kept every shipped entry under
`[Unreleased]`, so the next release would have repeated them.

## Testing

- `git diff origin/main` on this branch is empty.
- `cargo build`; `cora --version` prints 0.20.0. No code change.

Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant