Add code quality issue listing to the CLI - #130
Conversation
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>
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>
There was a problem hiding this comment.
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.
Sent by Cursor Automation: pr-flow
| *page_size, | ||
| scan_id.clone(), | ||
| ) { | ||
| } else if *issues || *code_quality { |
There was a problem hiding this comment.
--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.
| 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()?; |
There was a problem hiding this comment.
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.
| 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"); |
There was a problem hiding this comment.
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/qualityvs/issues/code-qualitywith zero assertion --issues/--sca-issues/--code-qualitymutual exclusion regressed--code-quality --scan-idwrongly calledget_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.
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>
| page: &Option<u16>, | ||
| page_size: &Option<u16>, | ||
| scan_id: &Option<String>, | ||
| project_name: &Option<String>, |
There was a problem hiding this comment.
--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.


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/doghousePR #1715, which fixes the code quality API endpoints (they crashed on non-CWE classifications) and adds alist_code_quality_issuesMCP 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 /--jsonformats as--issues.GET /api/v1/scan/<scan_id>/issues/qualitywhen a scan id is given, otherwiseGET /api/v1/issues/code-quality?project=<name>.utils/api.rs: newget_quality_issues()mirroringget_scan_issues().--issues,--sca-issues, and--code-qualityare now mutually exclusive.corgea listnow resolves the project name viadetermine_project_name()— the same helpercorgea scanuses — instead of the bare current-directory basename. It also accepts an explicit--project-nameflag (mirroringscan). 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.list --code-quality --scan-idstill calledcheck_blocking_rules(hard-exiting on any failure even after the CQ fetch succeeded) and could render Blocking columns driven by non-CQ findings.Issuestruct.The code quality endpoints return the same JSON shape as the security issue endpoints, so no changes to the
Issuestruct were needed.Example
The
Categorycolumn 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).