Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/fix-findings-pagination-warning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codacy/codacy-cloud-cli": patch
---

Fix `findings`'s pagination warning silently not firing when the API response omits `pagination.total`: the guard now also checks for a remaining `cursor`, so a trailing page of results is no longer hidden from the `--limit` hint.
5 changes: 5 additions & 0 deletions .changeset/format-standards-analysing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codacy/codacy-cloud-cli": patch
---

`formatStandards()` (used by `repository`'s Open Pull Requests table, `pull-request`'s Up to Standards row, and `pull-requests`' ✓ column) now shows a dim `⋯` while a pull request is still being analysed, instead of falling through to a hard ✗ on gate data that isn't final yet.
5 changes: 5 additions & 0 deletions .changeset/pull-requests-list-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codacy/codacy-cloud-cli": minor
---

New `pull-requests` (`prs`) command: lists pull requests for a repository, with the same analysis-gated columns as `repository`'s "Open Pull Requests" table. `-q, --search` and `-B, --base` filter by free text (title/author handle) and target branch, mapping to the API's `textQuery`/`targetBranch` params; `-S, --state` filters by open (default) or closed.
5 changes: 5 additions & 0 deletions .changeset/pull-requests-table-polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codacy/codacy-cloud-cli": patch
---

Fix PR complexity showing as no data, and polish the `pull-requests` table. Complexity is now read from the API's nested `quality` object, which is where the pull-request endpoints actually return it — `pull-requests`, `pull-request` and `repository` all previously rendered it as empty. The `pull-requests` table now leads with the up-to-standards column, orders metrics the same way `repositories` does (issues, complexity, duplication, coverage), hides the Coverage column when no listed PR has coverage data, shows `-` instead of `N/A` for metrics with no value, and no longer signs a zero issue count (`0` instead of `-0`). `--output json` now includes the quality and coverage `resultReasons`, so consumers can see which gates passed or failed.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Supported providers: GitHub (`gh`), GitLab (`gl`), Bitbucket (`bb`).
| `findings [provider] [org] [repo]` | Show security findings for a repository or organization |
| `finding <provider> <org> <id>` | Show details for a single security finding, or ignore/unignore it |
| `pull-request [provider] [org] [repo] <pr>` | Show PR analysis, issues, diff coverage, and changed files; or reanalyze it (optionally waiting for results) |
| `pull-requests [provider] [org] [repo]` | List pull requests for a repository, with analysis data and text/branch filters |
| `tools [provider] [org] [repo]` | List analysis tools configured for a repository |
| `tool [provider] [org] [repo] <tool>` | Enable, disable, or configure an analysis tool |
| `patterns [provider] [org] [repo] <tool>` | List patterns for a tool, or bulk enable/disable them |
Expand Down
3 changes: 3 additions & 0 deletions SPECS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ _No pending tasks._ All commands implemented.
| `ls` | N/A | ✅ Done | [ls.md](commands/ls.md) |
| `directories` | `dirs` | ✅ Done | [directories.md](commands/directories.md) |
| `pull-request` | `pr` | ✅ Done (--diff + Diff Coverage Summary added) | [pull-request.md](commands/pull-request.md) |
| `pull-requests` | `prs` | ✅ Done | [pull-requests.md](commands/pull-requests.md) |
| `issues` | `is` | ✅ Done | [issues.md](commands/issues.md) |
| `issue` | `iss` | ✅ Done | [issue.md](commands/issue.md) |
| `findings` | `fins` | ✅ Done | [findings.md](commands/findings.md) |
Expand Down Expand Up @@ -80,3 +81,5 @@ _No pending tasks._ All commands implemented.
| 2026-07-24 | Security (CWE-150, HackerOne): neutralize terminal control characters in human-readable output. New `src/utils/sanitize.ts` (`sanitizeText`) strips C0 (0x00–0x1F except TAB/LF), DEL (0x7F) and C1 (0x80–0x9F) — CR included — replacing each with visible caret/`\xNN` notation so a crafted PR can't inject ANSI/OSC sequences to repaint or hide findings, spoof gate status, or drive terminal side effects (OSC 52 clipboard, OSC 8 hyperlinks). Applied *before* the CLI's own `ansis` styling (can't sanitize at the console boundary — that would strip the CLI's legitimate colours, and allow-listing SGR would still pass attacker SGR through). Covers every render path: shared helpers in `utils/formatting.ts` (issue cards/detail, code context, CVE block, dependency chains, version segments) plus `pull-request` (About table, Files, diff-coverage, annotated diff line/hunk/path), `findings`/`finding`, `issues` (overview tables, noise suggestions), `issue`, `repository` (About, PRs, overview), `ls`/`directories`. JSON output left intact (JSON encoding already escapes control bytes). New `src/utils/sanitize.test.ts` + pull-request table/diff regression tests (12 new tests, 490 total) |
| 2026-07-24 | (OD-296) Vulnerable/affected functions for SCA issues: `CommitIssue.advisoryInformation` (added server-side in API `57.3.0`, bumped from pinned `56.2.9`) is now read and rendered. `issues`/`issue`/`pull-request` default cards show a compact "Vulnerable functions: fn1, fn2 (+N more)" line (`printIssueCard`); `issue`/`pull-request --issue` detail views show the full block with advisory ID + published date (`printAdvisoryBlock`, wired into `printIssueCodeContext`). Not shown for ignored issues (`IgnoredIssue` has no `advisoryInformation`). Gated purely on `advisoryInformation` presence. Added to all three commands' JSON `pickDeep` whitelists (10 new tests, 488 total). Originally scoped to the `findings` command, but `SrmItem` has no equivalent field anywhere server-side — redirected to `issues`, where the data already existed in the public API. |
| 2026-07-28 | (OD-296, findings side) `SrmItem` gained its own `advisoryInformation` field server-side (bumped pinned API `57.3.0` → `57.3.9`), closing the gap noted on 2026-07-24. `findings` (list) now shows the same compact "Vulnerable functions: fn1, fn2 (+N more)" line as `issues`, via the newly-exported `summarizeFunctions`. `finding` (detail) shows the full `printAdvisoryBlock` — but only when there's no linked Codacy issue, since `printIssueCodeContext` already renders the equivalent block from `issue.advisoryInformation` in that case; this is what makes vulnerable functions visible for SCA/dependency findings (and any other non-Codacy-source finding) that have no linked issue to borrow it from at all. Added to both commands' JSON `pickDeep` whitelists (6 new tests, 494 total) |
| 2026-07-28 | (OD-378) New `pull-requests` (`prs`) command — the plural counterpart to `pull-request`, listing PRs for a repository with the same analysis-gated table columns as `repository`'s "Open Pull Requests" section (reuses `buildGateStatus`/`formatStandards`/`formatPrIssues`/`formatPrCoverage`/`formatDelta`). `--search-text`/`-q` and `--branch`/`-b` map to the API's `textQuery`/`targetBranch` params added in OD-376; the classification param (`search`, Merged vs. last-updated) is deliberately not exposed — different axis, out of scope. `[provider] [org] [repo]` auto-detect via `resolveRepoArgs`, paginate-to-`--limit` loop matching `findings`. Registered in `src/index.ts` (10 new tests, 516 total) |
| 2026-07-30 | (OD-378, review follow-up) `pull-requests` table polish + a real data bug. **Bug:** Complexity rendered as "no data" on every PR because the API omits the flat top-level `deltaComplexity` and only returns `quality.deltaComplexity` (while still sending a top-level `deltaClonesCount`) — new shared `prQualityMetric(pr, key)` in `utils/formatting.ts` reads the nested `quality` value first and falls back to the flat field; also applied to `repository`'s Open PR table and `pull-request`'s Analysis section, which had the same bug. **Layout:** `✓` moved to the first column; metric order now matches `repositories` (issues → complexity → duplication → coverage); the Coverage column is dropped entirely when no listed PR has a coverage value (new `hasAnyPrCoverage()` — repos without coverage return `diffCoverage.cause` and no numbers on any PR); missing metric values now render as a dim `-` instead of `N/A` in `formatDelta`/`formatPrCoverage`/`formatPrIssues`, matching `formatStandards`/`formatCountCell`/`formatCoverageCell`; and a zero issue count renders as a bare `0` rather than `+0`/`-0` (`-0` read as a negative), matching what `pull-request`'s Files table and `formatDelta` already did. **JSON:** added `quality.resultReasons`/`coverage.resultReasons` (Codacy review suggestion — they drive the per-metric gate coloring, so consumers need them to see which gates passed/failed) plus the `quality.*` metric mirrors the table actually renders (23 new tests, 544 total) |
68 changes: 68 additions & 0 deletions SPECS/commands/pull-requests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# `pull-requests` Command Spec

**Status:** ✅ Done (2026-07-28)

## Purpose

List pull requests for a repository, with analysis data — the plural counterpart to `pull-request` (which shows a single PR by number). Supports a free-text search filter, a target-branch filter, and an open/closed state filter.

## Usage

```
codacy pull-requests [provider] [organization] [repository]
codacy pull-requests # auto-detect from git remote
codacy pull-requests gh my-org my-repo
codacy prs gh my-org my-repo --search "fix flaky"
codacy prs gh my-org my-repo --base main
codacy prs gh my-org my-repo --state closed
codacy prs gh my-org my-repo --output json
```

## Options

| Option | Short | Description |
|---|---|---|
| `--search <text>` | `-q` | Filter by free-text search matched against the PR title or author handle (maps to the API's `textQuery` param) |
| `--base <name>` | `-B` | Filter by target (base) branch name (maps to the API's `targetBranch` param) |
| `--state <state>` | `-S` | `open` (default) or `closed` — maps to the API's `search` classification param (see below) |
| `--limit <n>` | `-n` | Maximum number of pull requests to return (default: 100, max: 1000) |

`--search`/`-q` and `--base`/`-B` match the flag names already established elsewhere in the CLI (`findings`/`patterns` use `-q, --search`; `--base` avoids colliding with `-b, --branch`'s different meaning — "the analysed branch" — in `ls`/`directories`/`issues`).

## State filtering

The API's `search` param (not exposed directly) toggles between a `last-updated` classification (default) and a `merged` one; leaving it `undefined` returns closed/merged PRs mixed in with open ones with no way to tell them apart (no Status column in this command's table) — the same problem `repository.ts`'s "Open Pull Requests" table works around by filtering `status === "open" || "Open"` client-side. This command instead always passes an explicit `search` value:

- `--state open` (default) → `search = "last-updated"`
- `--state closed` → `search = "merged"` — named `closed` on the CLI, not `merged`, because that API classification also returns closed-but-not-merged PRs, so `"merged"` would be a factually wrong label to expose.

## API Endpoint

[`listRepositoryPullRequests`](https://api.codacy.com/api/api-docs#listrepositorypullrequests) — `AnalysisService.listRepositoryPullRequests(provider, org, repository, limit, cursor, search, textQuery, targetBranch)`.

Pages through `cursor` up to `--limit` (page size capped at 100 per request), same loop shape as `findings`/`issues`. The pagination-warning guard checks `total > items.length || hasMore` (not just `total`) — the API can omit `pagination.total`, and falling back to `total = items.length` would otherwise mask a page still pending behind a live `cursor`. The same latent bug (guard was `total > items.length` alone) was fixed in `findings.ts` at the same time, since that's where this loop shape was copied from.

## Output

Table format, columns:

| ✓ | # | Title | Branches | Issues | Complexity | Duplication | Coverage | Updated |
|---|---|---|---|---|---|---|---|---|

- **✓** is the leading column — the gate verdict is the first thing you scan a PR list for, so it reads before the number and title. It's dim `⋯` while `isAnalysing` is true (rather than falling through to a hard ✗ for gate data that isn't final yet), otherwise ✓/✗/`-` from quality + coverage `isUpToStandards` — a shared fix in `formatStandards()` (`utils/formatting.ts`), so it also applies to `repository`'s and `pull-request`'s uses of the same helper.
- **Branches** shows `originBranch → targetBranch` (truncated at 30) — same pairing `pull-request.ts`'s About section uses, so the "Branch" label doesn't collide with `repository.ts`'s Open PR table (which shows `originBranch` alone under the same header) or ambiguously imply which branch `--base` filters on.
- **Issues**, **Complexity**, **Duplication**, **Coverage** reuse the same shared helpers as `repository`'s "Open Pull Requests" table (`buildGateStatus`, `formatPrIssues`, `formatPrCoverage`, `formatDelta`) and `pull-request`'s own Analysis section — gate-colored the same way. The metric order matches the `repositories` command (issues → complexity → duplication → coverage) so the same four metrics read in the same order across the CLI.
- **Complexity/Duplication read `quality.deltaComplexity`/`quality.deltaClonesCount` first**, via the shared `prQualityMetric()` helper. The API populates the flat top-level fields and the nested `quality` object inconsistently — the pull-request endpoints return `quality.deltaComplexity` but omit the top-level `deltaComplexity` (while still sending a top-level `deltaClonesCount`), so reading only the flat field rendered every PR's complexity as "no data". `quality` is the newer, structured shape (same direction as `coverage` vs. the deprecated top-level coverage fields), so it wins with the flat field as fallback. Applied to `repository` and `pull-request` too, which had the same bug.
- **Coverage is hidden entirely when no listed PR has a coverage value** (`hasAnyPrCoverage()`). A repo without coverage set up returns `diffCoverage.cause` (e.g. `MissingRequirements`) and no numbers on any PR, so the column would otherwise be a full column of `-`. The helper lives next to `formatPrCoverage` so both agree on what counts as "has data".
- **A zero issue count renders as a bare `0`**, not `+0`/`-0` — `-0` reads as a negative number, and the sign adds nothing when nothing was added or fixed. Matches the rule `pull-request.ts`'s Files table and `formatFileDelta` already use; `formatDelta` already did the same for complexity/duplication.
- **Missing values render as a dim `-`, not `N/A`** — in `formatDelta`, `formatPrCoverage`, and `formatPrIssues`, matching the convention already used by `formatStandards`, `formatCountCell`, and `formatCoverageCell`. Being shared helpers, this also applies to `repository`'s Open PR table and `pull-request`'s Analysis section. (`N/A` survives elsewhere in the CLI for non-metric fields — grades, dates, author names, default branch — which is a separate sweep.)
- Shows pagination warning (suggesting `--limit`, `--search`, `--base`, `--state`) if more results exist than were fetched.
- JSON whitelist includes `pullRequest.originBranch`/`targetBranch` (both rendered in the Branches column), `coverage.isUpToStandards`/`quality.isUpToStandards` (needed to reproduce the ✓ column programmatically), the `quality.*` mirrors of the flat metric fields (the API omits some of the top-level ones, and these are what the table actually renders), and `quality.resultReasons`/`coverage.resultReasons` (which drive the per-metric gate coloring — without them a JSON consumer can't tell which gate passed or failed). It drops `pullRequest.status`/`pullRequest.owner.name` (neither rendered anywhere in the table) — matching the `pickDeep` convention in `AGENTS.md` ("only includes fields that correspond to what's shown in the console table").

## Tests

File: `src/commands/pull-requests.test.ts` — 19 tests.

Manually verified against `gh codacy codacy-website`: `--base main` → 0 results (repo's PRs all target `master`), `--base master` → matches the unfiltered count, `--search "AI"` → narrows to exactly the one matching title, `--output json` shape correct, and `[provider] [org] [repo]` auto-detect from the git remote works from inside a real checkout.

Also verified against `gh codacy codacy-worker` (a repo with no coverage set up): PR #1218 renders Complexity `+21` (previously `N/A`, since the API omits the flat `deltaComplexity`), the Coverage column is dropped from the table, and `codacy pr gh codacy codacy-worker 1218` shows the same `+21` in its Analysis section.
4 changes: 2 additions & 2 deletions package-lock.json

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

Loading
Loading