Skip to content

Add code quality issue listing to the CLI - #130

Open
Ibrahimrahhal wants to merge 5 commits into
mainfrom
cursor/expose-code-quality-findings-c31f
Open

Add code quality issue listing to the CLI#130
Ibrahimrahhal wants to merge 5 commits into
mainfrom
cursor/expose-code-quality-findings-c31f

Conversation

@Ibrahimrahhal

@Ibrahimrahhal Ibrahimrahhal commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

The CLI previously only exposed security (SAST) and SCA findings — code quality findings (detected_by == "code-quality" on the backend) were not reachable from the CLI at all. This adds first-class support for listing them.

Companion backend/MCP changes are in Corgea/doghouse PR #1715, which fixes the code quality API endpoints (they crashed on non-CWE classifications) and adds a list_code_quality_issues MCP tool. This CLI PR consumes those endpoints.

Changes

  • corgea list --code-quality (alias --quality, short -q): lists code quality findings for the current project or a specific --scan-id, in the same table / --json formats as --issues.
    • Hits GET /api/v1/scan/<scan_id>/issues/quality when a scan id is given, otherwise GET /api/v1/issues/code-quality?project=<name>.
  • utils/api.rs: new get_quality_issues() mirroring get_scan_issues().
  • Validation: --issues, --sca-issues, and --code-quality are now mutually exclusive.
  • Project name resolution: corgea list now resolves the project name via determine_project_name() — the same helper corgea scan uses — instead of the bare current-directory basename. It also accepts an explicit --project-name flag (mirroring scan). Scans are stored under the Git‑remote‑derived project name, so the previous basename lookup returned "Project not found" whenever the checkout directory differed from the repository name (e.g. Git worktrees). Applies to --issues, --sca-issues, --code-quality, and the default scan listing.
  • Blocking rules: blocking-rules enrichment is now gated to security listings. Previously list --code-quality --scan-id still called check_blocking_rules (hard-exiting on any failure even after the CQ fetch succeeded) and could render Blocking columns driven by non-CQ findings.
  • Test: added a unit test asserting a code quality issue response (label classification with a null description) deserializes into the shared Issue struct.

The code quality endpoints return the same JSON shape as the security issue endpoints, so no changes to the Issue struct were needed.

Example

corgea list --code-quality
corgea list --quality --scan-id <scan_id>
corgea list --code-quality --project-name my-repo --json

The Category column shows the code quality label (e.g. Maintainability) in place of a CWE id.

Testing

  • cargo clippy --all-targets -- -D warnings — clean (matches CI's ./harness ci).
  • ./harness test — all tests pass (includes the new deserialization test).

Note: building required a Rust stable toolchain newer than 1.83 (a transitive dependency needs edition 2024); CI already uses dtolnay/rust-toolchain@stable, so this is unaffected.

Open in Web Open in Cursor 

cursoragent and others added 2 commits July 20, 2026 09:17
Add 'corgea list --code-quality' (alias --quality) to list code quality
findings, mirroring --issues but hitting the code quality endpoints
(/scan/<id>/issues/quality and /issues/code-quality).

- Add get_quality_issues() to the API client.
- Deserialize the new 'type' discriminator on issues (optional).
- Make --issues, --sca-issues, and --code-quality mutually exclusive.
- Add a deserialization test for code quality issue responses.

Co-authored-by: ibrahim <ibrahim@corgea.com>
Code quality and security issues come from separate endpoints, so the CLI
does not need a 'type' discriminator on the Issue struct. Keeps the CLI in
sync with the API response, which no longer emits the field.

Co-authored-by: ibrahim <ibrahim@corgea.com>
@Ibrahimrahhal
Ibrahimrahhal marked this pull request as ready for review July 20, 2026 09:39
CI runs clippy with -D warnings; adding the --code-quality flag pushed
list::run to 8 arguments (limit 7). Allow the lint here, matching the
existing pattern used in blast.rs and deps/ecosystems/evaluate.rs.

Co-authored-by: ibrahim <ibrahim@corgea.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Production review on c72c3d6 (vs main). Three merge blockers below: CQ listing incorrectly inherits security blocking-rule failure modes, the new HTTP client ignores status codes during a companion-API rollout, and the only new test does not lock the endpoint contract.

Also confirm the doghouse companion (PR body cites #1715) is deployed before shipping this CLI — the feature hard-depends on those endpoints being healthy.

Open in Web View Automation 

Sent by Cursor Automation: pr-flow

Comment thread src/list.rs
*page_size,
scan_id.clone(),
) {
} else if *issues || *code_quality {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--code-quality inherits security blocking-rule side effects (including hard failure).

Folding CQ into the *issues branch means corgea list --code-quality --scan-id ... still runs check_blocking_rules (lines 162-189) and exits 1 on any failure, even after get_quality_issues already succeeded.

Impact: A blocking-rules API blip (or security-only block: true) makes CQ listing unusable, or adds empty Blocking columns driven by non-CQ findings.

Fix: Gate enrichment on security listing only, e.g. if scan_id.is_some() && !*code_quality { ... }. Or, if CQ rows can legitimately be blocked, soft-fail the enrichment (log + continue) and set render_blocking_rules only when at least one returned CQ issue id is in the blocking map. Add a regression test for --code-quality --scan-id that stubs blocking-rules failure and asserts the CQ table/JSON still prints.

Comment thread src/utils/api.rs
Comment on lines +546 to +553
let response = match client.get(&url).send() {
Ok(res) => {
check_for_warnings(res.headers(), res.status());
res
}
Err(e) => return Err(format!("Failed to send request: {}", e).into()),
};
let response_text = response.text()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New client ignores HTTP status; non-JSON errors become opaque parse/auth failures.

get_quality_issues always reads the body and serde_json::from_strs with no status().is_success() check (unlike get_sca_issues in this same file, which maps 404/non-2xx explicitly).

Impact: During companion rollout (or any 401/403/404/500 HTML/JSON error body), users get Failed to parse response then the generic check-token/connection path in list.rs, and the 404 string match for scan/project-does-not-exist never fires. A wrong path (/issues/quality vs /issues/code-quality) is easy to miss.

Fix: Mirror get_sca_issues: inspect status before parse; map 404/401/5xx to distinct errors that list.rs can surface. Prefer client.get(&endpoint).query(...) so project names are encoded and page_size cannot be appended as &page_size= without a ? when page is None.

Comment thread src/utils/api.rs
Comment on lines +1196 to +1227
fn deserializes_code_quality_issue_response() {
// Code quality issues carry a free-form classification label (no CWE) and
// must deserialize into the same Issue struct used for security issues.
let body = r#"{
"status": "ok",
"page": 1,
"total_pages": 1,
"total_issues": 1,
"issues": [
{
"id": "11111111-1111-1111-1111-111111111111",
"urgency": "ME",
"created_at": "2026-01-01T00:00:00Z",
"status": "open",
"classification": {
"id": "Maintainability",
"name": "Maintainability",
"description": null
},
"location": {
"file": {"name": "app.py", "language": "python", "path": "app/app.py"},
"project": {"name": "proj", "branch": "main", "git_sha": "abc"},
"line_number": 20
},
"auto_triage": {"false_positive_detection": {"status": "valid"}},
"auto_fix_suggestion": {"status": "no_fix"}
}
]
}"#;

let parsed: ProjectIssuesResponse =
serde_json::from_str(body).expect("should parse code quality response");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test passes without exercising the new behavior (endpoint contract / CLI wiring).

deserializes_code_quality_issue_response only round-trips a hand-written JSON fixture into ProjectIssuesResponse. It would still pass if:

  • scan path were /scan/{id}/issues/code-quality (or project path /issues/quality) — the PR uses asymmetric /scan/.../issues/quality vs /issues/code-quality with zero assertion
  • --issues / --sca-issues / --code-quality mutual exclusion regressed
  • --code-quality --scan-id wrongly called get_scan_issues

Impact: False confidence for a net-new production API surface.

Fix: Add a unit/integration test that builds the request URL (or uses a mock HTTP server) and asserts both endpoint variants + query params; add a clap/CLI test that --issues --code-quality exits 1 and that --code-quality selects get_quality_issues.

Comment thread src/list.rs
corgea list used the current directory basename as the project key, while
corgea scan stores projects under determine_project_name (which prefers
the Git remote repository name). This caused 'Project not found' when the
checkout directory differed from the repo name, including Git worktrees.

Use the same determine_project_name helper for list so the lookup key
matches what scans are stored under. Applies to --issues, --sca-issues,
--code-quality, and the default scan listing.

Co-authored-by: ibrahim <ibrahim@corgea.com>
- Add a --project-name flag to 'corgea list', matching 'corgea scan', and
  thread it through determine_project_name so users can override the
  resolved project key explicitly.
- Gate blocking-rules enrichment to security listings only. Previously
  'corgea list --code-quality --scan-id' ran check_blocking_rules and
  hard-exited on any failure (even after the CQ fetch succeeded), and
  could render Blocking columns driven by non-CQ findings.

Co-authored-by: ibrahim <ibrahim@corgea.com>
Comment thread src/list.rs
page: &Option<u16>,
page_size: &Option<u16>,
scan_id: &Option<String>,
project_name: &Option<String>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--project-name is ignored in SCA mode, so corgea list --sca-issues --project-name foo does not filter by foo. Handling this combination explicitly would prevent the flag from implying filtering that does not occur.

@yhoztak yhoztak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.

5 participants