Skip to content

feat: quality gate — threshold-based pass/fail for CI enforcement #205

Description

@ajianaz

Summary

Add a Quality Gate mechanism that evaluates review findings against configurable thresholds to produce a definitive PASS/FAIL result. Currently, Cora review produces findings and comments, but the CI gate only checks "did cora run successfully?" — not "did cora find something that should block merge?".

Motivation

  • Actionable CI gate — today Cora always "passes" CI even if it finds critical bugs. Teams cannot enforce code quality standards.
  • SonarQube proven model — Quality Gates are the Core CLI structure and commands (review, scan, init, auth, hook) #1 reason teams adopt SonarQube. It transforms "information" into "enforcement".
  • Enterprise readiness — no enterprise will adopt a review tool that cannot enforce standards. This is table stakes.
  • Current gap: .cora.yaml has on_violation (CI mode) but it's binary — either warn or fail. No nuance per severity/category.

Proposed Design

.cora.yaml Configuration

quality_gate:
  enabled: true

  # Global thresholds — any of these exceeded = FAIL
  thresholds:
    max_critical: 0           # 0 critical issues allowed
    max_high: 3               # max 3 high issues
    max_medium: 10            # max 10 medium issues
    max_security: 0           # 0 security findings allowed (all severities)
    min_quality_score: 7.0    # AI-assigned quality score (0-10)

  # 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 gate
    complexity:
      action: warn
      max_findings: 10

  # New vs existing — only count NEW issues
  # Requires baseline file from previous review
  baseline: .cora/baseline.sarif
  count_new_only: false       # default: count all findings

  # Branding
  pass_message: "✅ CodeCora Quality Gate: PASSED"
  fail_message: "❌ CodeCora Quality Gate: FAILED — {count} issues above threshold"

CLI Output

$ cora review

[secrets] Scanning 23 files... ✅ No secrets detected
[review]  Analyzing diff (1,247 lines)...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  QUALITY GATE RESULT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Status:     ❌ FAILED
  Score:      6.5/10 (threshold: 7.0)
  Findings:   2 critical, 1 high, 4 medium
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Threshold Check:
    ✅ max_critical (0)    → 2 found   ❌ EXCEEDED
    ✅ max_high (3)        → 1 found   ✅ OK
    ✅ max_security (0)    → 0 found   ✅ OK
    ✅ min_quality (7.0)   → 6.5       ❌ BELOW
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Category Check:
    ✅ security    → 0 findings  ✅ PASS (block)
    ✅ performance → 1 finding   ✅ PASS (warn)
    ❌ bug_risk    → 2 findings  ❌ FAIL (block, max 3) 
    ⏭  style       → skipped    (ignore)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  
  CI Exit Code: 1 (gate failed)

SARIF Extension

Add a properties block to the SARIF run for gate status:

{
  "runs": [{
    "tool": { "driver": { "name": "CodeCora" } },
    "results": [...],
    "properties": {
      "qualityGate": {
        "status": "FAILED",
        "score": 6.5,
        "thresholds": { "maxCritical": 0, "maxHigh": 3 },
        "actuals":   { "critical": 2, "high": 1, "medium": 4 },
        "failedChecks": ["max_critical", "min_quality_score"]
      }
    }
  }]
}

GitHub PR Comment

When integrated with CI, Cora posts a structured gate comment:

## ❌ CodeCora Quality Gate: FAILED

| Metric | Threshold | Actual | Status |
|--------|-----------|--------|--------|
| Critical | 0 | 2 ||
| High | 3 | 1 ||
| Security | 0 | 0 ||
| Quality Score | 7.0 | 6.5 ||

<details><summary>📋 7 findings (expand)</summary>
... existing finding details ...
</details>

Baseline Support (Optional Enhancement)

# Save current review as baseline
cora review --save-baseline .cora/baseline.sarif

# Future reviews only count NEW issues not in baseline
# This allows incremental quality improvement without blocking on existing debt

Architecture

src/
├── engine/
│   ├── quality_gate.rs      # NEW: Gate evaluation logic
│   │   ├── struct QualityGateConfig
│   │   ├── struct GateResult
│   │   ├── fn evaluate(findings, config) -> GateResult
│   │   └── fn format_output(result) -> String
│   └── ...
├── config/
│   └── cora_yaml.rs         # MODIFY: Parse quality_gate section
├── output/
│   └── sarif.rs             # MODIFY: Add gate properties to SARIF
└── commands/
    └── review.rs            # MODIFY: Evaluate gate after review, set exit code

Implementation Checklist

  • Create src/engine/quality_gate.rs with config structs and evaluation logic
  • Add quality_gate section to .cora.yaml parsing
  • Implement threshold checking (global + per-category)
  • Implement quality score extraction from AI review response
  • Add gate result to CLI output (formatted table)
  • Add gate properties to SARIF output
  • Set CLI exit code based on gate result (0=pass, 1=fail)
  • Add --no-gate flag to bypass gate (review-only mode)
  • Add --gate-only flag to evaluate gate against existing SARIF (no re-review)
  • Update CI composite action to interpret gate status
  • Add baseline support (--save-baseline, count_new_only)
  • Write unit tests for threshold evaluation
  • Write integration test: gate pass, gate fail, partial categories
  • Update README and docs

Out of Scope (Future)

  • Historical gate tracking (trend over time) — depends on Issue #N (tech debt metrics)
  • Organization-level gate policies — v0.6+ (enterprise feature)
  • Gate exemptions per PR label — v0.5.x

Dependencies

  • Requires AI review to produce structured severity ratings (partially exists)
  • Secrets scanner findings (Issue #N) feed into security category

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions