Skip to content

Commit 87157e2

Browse files
Merge pull request #46 from CodeAnt-AI/pranav/scans-dismiss-fp-overrides
Duplicate code, dismissed/FP markers, and issue overrides in scans (0.5.8)
2 parents b309575 + c7e9d5c commit 87157e2

19 files changed

Lines changed: 524 additions & 47 deletions

‎changelog.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## [0.5.9] - 02/10/2026
4+
- Added `duplicate_code` to `scans results` / `findings repo`
5+
- Dismissed and false-positive flags are kept on IaC and dead-code findings, and shown as `metadata.dismissed` / `metadata.false_positive`
6+
- Added `scans overrides` (and the `codeant_scans_overrides` MCP tool) for user false-positive, confidence, and severity overrides
7+
- `scans dismissed` accepts every analysis type and no longer drops SCA dismissals
8+
- SCA severity is read from the advisory rating instead of defaulting to medium
9+
- Extra dead-code file paths no longer end in the result file name
10+
- Removed a debug dump of full scan responses to stderr
11+
- Fixed numeric options with defaults (`scans results --limit`, `scans history --limit`, PR comment `--limit`) being parsed in the wrong radix; numeric options now reject non-integers, negative values, zero counts, and values above the documented maximum
12+
- The dismissal comment (`metadata.comment_for_dismiss`) and secrets with a `FALSE_POSITIVE` confidence are reported in `metadata`
13+
314
## [0.5.8] - 02/10/2026
415
- `findings antipatterns` and VM/container `findings cloud list` are paged (`--limit`, `--offset`, `--all`), so large organizations and scans no longer fail with a 502
516

‎findings.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ codeant findings repo \
4949
--output codeant.sarif
5050
```
5151

52-
Supported repo types are `sast`, `sca`, `secrets`, `iac`, `dead_code`, `sbom`, `anti_patterns`, `docstring`, and `complex_functions`. Use `--types all` for all of them. Formats are `json`, `sarif`, `csv`, `md`, and `table`; JSON is the default.
52+
Supported repo types are `sast`, `sca`, `secrets`, `iac`, `dead_code`, `duplicate_code`, `sbom`, `anti_patterns`, `docstring`, and `complex_functions`. Use `--types all` for all of them. Formats are `json`, `sarif`, `csv`, `md`, and `table`; JSON is the default.
5353

54-
Use `--filter-dismissed` to exclude dismissed findings and `--no-false-positives` to exclude false positives. `--path`, `--check`, `--limit`, and `--offset` support agent-friendly filtering and pagination.
54+
Use `--filter-dismissed` to exclude dismissed findings and `--no-false-positives` to exclude false positives, including ones users marked in the app. Without those flags, such findings are returned with `metadata.dismissed` or `metadata.false_positive`. `--path`, `--check`, `--limit`, and `--offset` support agent-friendly filtering and pagination.
5555

5656
## Organization Hotlist findings
5757

‎mcp.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ The CodeAnt CLI ships an MCP (Model Context Protocol) server that exposes CodeAn
1515
| `codeant_scans_history` | read | Recent scan runs for a repo. |
1616
| `codeant_scans_get` | read | Severity/category summary for one scan. |
1717
| `codeant_scans_results` | read | Full findings (SAST, SCA, secrets, IaC, …) for one scan. |
18-
| `codeant_scans_dismissed` | read | Dismissed alerts for a repo. |
18+
| `codeant_scans_dismissed` | read | Dismissed alerts for a repo (any analysis type; `sast` and `anti_patterns` are accepted as aliases). |
19+
| `codeant_scans_overrides` | read | User false-positive, confidence, and severity overrides for a repo. |
1920
| `codeant_hotlist_list` | read | Prioritized organization-wide Hotlist findings with stable IDs. |
2021
| `codeant_hotlist_get` | read | One complete Hotlist finding by stable ID. |
2122
| `codeant_findings_antipatterns` | read | Anti-pattern findings across selected or all organization repos (paged: `limit`, `offset`, `all`). |

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeant-cli",
3-
"version": "0.5.8",
3+
"version": "0.5.9",
44
"description": "Code review CLI tool",
55
"type": "module",
66
"repository": {
@@ -36,6 +36,7 @@
3636
"./scans/fetch-results": "./src/scans/fetchScanResults.js",
3737
"./scans/fetch-advanced-results": "./src/scans/fetchAdvancedScanResults.js",
3838
"./scans/dismissed-alerts": "./src/scans/fetchDismissedAlerts.js",
39+
"./scans/issue-overrides": "./src/scans/fetchIssueOverrides.js",
3940
"./hotlist": "./src/hotlist/client.js",
4041
"./findings/cloud": "./src/findings/cloud.js",
4142
"./findings/pentest": "./src/findings/pentest.js",

‎scans.md‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,12 @@ codeant scans results --repo <owner/repo> [options]
135135
| `--repo <repo>` | **(required)** Repository in `owner/repo` format |
136136
| `--scan <sha>` | Specific commit SHA to use |
137137
| `--branch <name>` | Resolve latest scan on this branch |
138-
| `--types <list>` | Comma-separated types: `sast`, `sca`, `secrets`, `iac`, `dead_code`, `sbom`, `anti_patterns`, `docstring`, `complex_functions`, `all` (default: `all`) |
138+
| `--types <list>` | Comma-separated types: `sast`, `sca`, `secrets`, `iac`, `dead_code`, `duplicate_code`, `sbom`, `anti_patterns`, `docstring`, `complex_functions`, `all` (default: `all`) |
139139
| `--severity <list>` | Filter by severity (e.g. `critical,high`) |
140140
| `--path <glob>` | Filter by file path glob |
141141
| `--check <regex>` | Filter by check ID or name (regex) |
142-
| `--include-dismissed` | Include dismissed findings (excluded by default) |
142+
| `--filter-dismissed` | Exclude dismissed findings (included by default, marked `metadata.dismissed`) |
143+
| `--no-false-positives` | Exclude false positives, including ones users marked in the app (included by default, marked `metadata.false_positive`) |
143144
| `--format <fmt>` | Output format: `json`, `sarif`, `csv`, `md`, `table` (default: `json`) |
144145
| `--output <path>` | Write output to file instead of stdout |
145146
| `--fields <list>` | Project findings to a subset of fields (comma-separated) |
@@ -173,8 +174,8 @@ codeant scans results --repo acme/backend --format md
173174
# Output as SARIF to a file
174175
codeant scans results --repo acme/backend --format sarif --output results.sarif
175176

176-
# Include dismissed findings
177-
codeant scans results --repo acme/backend --include-dismissed
177+
# Only open findings: drop dismissed findings and false positives
178+
codeant scans results --repo acme/backend --filter-dismissed --no-false-positives
178179

179180
# Paginate through results
180181
codeant scans results --repo acme/backend --limit 50 --offset 100
@@ -206,7 +207,7 @@ codeant scans dismissed --repo <owner/repo> [options]
206207
| Option | Description |
207208
|--------|-------------|
208209
| `--repo <repo>` | **(required)** Repository in `owner/repo` format |
209-
| `--analysis-type <type>` | Analysis type: `security` or `secrets` (default: `security`) |
210+
| `--analysis-type <type>` | `security`, `secrets`, `sca`, `iac`, `antipatterns`, `docstring`, `complex_functions`, `dead_code`, or `duplicate_code` (default: `security`). Result-type names `sast` and `anti_patterns` are accepted too. |
210211

211212
**Examples:**
212213

@@ -220,6 +221,16 @@ codeant scans dismissed --repo acme/backend --analysis-type secrets
220221

221222
---
222223

224+
### `scans overrides`
225+
226+
List the per-finding overrides users set in the CodeAnt app: Mark/Unmark false positive (`security`, `iac`), secrets confidence (`secrets`), and Change Severity (`security`, `sca`). `scans results` already applies them.
227+
228+
```bash
229+
codeant scans overrides --repo <owner/repo> [--analysis-type security|secrets|iac|sca]
230+
```
231+
232+
---
233+
223234
### `scans start-scan`
224235

225236
Trigger a new analysis run for a repository.

‎src/commands/scans/dismissed.js‎

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
import { fetchDismissedAlerts } from '../../scans/fetchDismissedAlerts.js';
2+
import { fetchIssueOverrides } from '../../scans/fetchIssueOverrides.js';
23

3-
/**
4-
* codeant scans dismissed --repo <repo> [--analysis-type security|secrets]
5-
*/
6-
export async function runDismissed({ repo, analysisType = 'security' } = {}) {
4+
// Result-type names (`scans results --types`) → the names dismissals and
5+
// overrides are stored under.
6+
const STORE_ANALYSIS_TYPES = {
7+
sast: 'security',
8+
security_issues: 'security',
9+
anti_patterns: 'antipatterns',
10+
};
11+
12+
export function storeAnalysisType(analysisType) {
13+
return STORE_ANALYSIS_TYPES[analysisType] || analysisType;
14+
}
15+
16+
function requireRepo(repo) {
717
if (!repo) {
818
const err = new Error('--repo is required');
919
err.exitCode = 1;
1020
throw err;
1121
}
22+
}
23+
24+
/**
25+
* codeant scans dismissed --repo <repo> [--analysis-type <type>]
26+
*/
27+
export async function runDismissed({ repo, analysisType = 'security' } = {}) {
28+
requireRepo(repo);
29+
const type = storeAnalysisType(analysisType);
1230

13-
const result = await fetchDismissedAlerts(repo, analysisType);
31+
const result = await fetchDismissedAlerts(repo, type);
1432
if (!result.success) {
1533
const err = new Error(result.error || 'Failed to fetch dismissed alerts');
1634
err.exitCode = 1;
@@ -19,8 +37,30 @@ export async function runDismissed({ repo, analysisType = 'security' } = {}) {
1937

2038
return {
2139
repo,
22-
analysis_type: analysisType,
40+
analysis_type: type,
2341
total: result.dismissedAlerts.length,
2442
dismissed_alerts: result.dismissedAlerts,
2543
};
2644
}
45+
46+
/**
47+
* codeant scans overrides --repo <repo> [--analysis-type security|secrets|iac|sca]
48+
*/
49+
export async function runOverrides({ repo, analysisType = 'security' } = {}) {
50+
requireRepo(repo);
51+
const type = storeAnalysisType(analysisType);
52+
53+
const result = await fetchIssueOverrides(repo, type);
54+
if (!result.success) {
55+
const err = new Error(result.error || 'Failed to fetch issue overrides');
56+
err.exitCode = 1;
57+
throw err;
58+
}
59+
60+
return {
61+
repo,
62+
analysis_type: type,
63+
total: Object.keys(result.overrides).length,
64+
overrides: result.overrides,
65+
};
66+
}

‎src/commands/scans/index.js‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { runRepos } from './repos.js';
33
import { runHistory } from './history.js';
44
import { runGet } from './get.js';
55
import { runResults } from './results.js';
6-
import { runDismissed } from './dismissed.js';
6+
import { runDismissed, runOverrides } from './dismissed.js';
77
import { runStartScan } from './start-scan.js';
88
import { setQuiet, setNoColor } from './lib/log.js';
99
import { setNoColor as tableSetNoColor } from './formatters/table.js';
10+
import { intOption } from '../../utils/intOption.js';
11+
12+
const toCount = intOption({ min: 1 });
13+
const toOffset = intOption();
1014

1115
/**
1216
* Register all `codeant scans <verb>` subcommands.
@@ -37,7 +41,7 @@ export default function registerScansCommands(program, { runCmd }) {
3741
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
3842
.option('--branch <name>', 'Filter by branch name')
3943
.option('--since <iso>', 'Show scans since ISO date')
40-
.option('--limit <n>', 'Max results (default: 20)', parseInt, 20)
44+
.option('--limit <n>', 'Max results (default: 20)', toCount, 20)
4145
.action((opts) =>
4246
runCmd(() => runHistory({ repo: opts.repo, branch: opts.branch, since: opts.since, limit: opts.limit }))
4347
);
@@ -65,7 +69,7 @@ export default function registerScansCommands(program, { runCmd }) {
6569
.option('--branch <name>', 'Resolve latest scan on this branch')
6670
.option(
6771
'--types <list>',
68-
'Comma-separated types: sast,sca,secrets,iac,dead_code,sbom,anti_patterns,docstring,complex_functions,all',
72+
'Comma-separated types: sast,sca,secrets,iac,dead_code,duplicate_code,sbom,anti_patterns,docstring,complex_functions,all',
6973
'all'
7074
)
7175
.option('--severity <list>', 'Filter by severity (e.g. critical,high)')
@@ -76,8 +80,8 @@ export default function registerScansCommands(program, { runCmd }) {
7680
.option('--format <fmt>', 'Output format: json|sarif|csv|md|table (default: json)', 'json')
7781
.option('--output <path>', 'Write output to file instead of stdout')
7882
.option('--fields <list>', 'Project findings to subset of fields (comma-separated)')
79-
.option('--limit <n>', 'Max findings per page (default: 100)', parseInt, 100)
80-
.option('--offset <n>', 'Pagination offset (default: 0)', parseInt, 0)
83+
.option('--limit <n>', 'Max findings per page (default: 100)', toCount, 100)
84+
.option('--offset <n>', 'Pagination offset (default: 0)', toOffset, 0)
8185
.option('--fail-fast', 'Exit 3 on first category fetch failure')
8286
.option('--no-color', 'Disable ANSI color (auto-disabled when not a TTY)')
8387
.option('--quiet', 'Suppress progress output on stderr')
@@ -117,11 +121,25 @@ export default function registerScansCommands(program, { runCmd }) {
117121
.command('dismissed')
118122
.description('List dismissed alerts for a repository')
119123
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
120-
.option('--analysis-type <type>', 'Analysis type: security|secrets (default: security)', 'security')
124+
.option(
125+
'--analysis-type <type>',
126+
'Analysis type: security|secrets|sca|iac|antipatterns|docstring|complex_functions|dead_code|duplicate_code (default: security)',
127+
'security'
128+
)
121129
.action((opts) =>
122130
runCmd(() => runDismissed({ repo: opts.repo, analysisType: opts.analysisType }))
123131
);
124132

133+
// ── overrides ──────────────────────────────────────────────────────────────
134+
scans
135+
.command('overrides')
136+
.description('List user overrides (false positive, confidence, severity) for a repository')
137+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
138+
.option('--analysis-type <type>', 'Analysis type: security|secrets|iac|sca (default: security)', 'security')
139+
.action((opts) =>
140+
runCmd(() => runOverrides({ repo: opts.repo, analysisType: opts.analysisType }))
141+
);
142+
125143
// ── start-scan ─────────────────────────────────────────────────────────────
126144
scans
127145
.command('start-scan')

‎src/commands/scans/lib/categories.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
fetchSecretsResults,
1111
fetchIacResults,
1212
fetchDeadCodeResults,
13+
fetchDuplicateCodeResults,
1314
} from '../../../scans/fetchAdvancedScanResults.js';
1415

1516
/**
@@ -26,6 +27,7 @@ export const CATEGORIES = {
2627
secrets: { fetcher: fetchSecretsResults, kind: 'secret' },
2728
iac: { fetcher: fetchIacResults, kind: 'config' },
2829
dead_code: { fetcher: fetchDeadCodeResults, kind: 'code' },
30+
duplicate_code: { fetcher: fetchDuplicateCodeResults, kind: 'code' },
2931
};
3032

3133
/**

‎src/commands/scans/lib/normalize.js‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ export function normalizeIssue(issue, category) {
8787
} else if (category === 'sca') {
8888
if (issue.cvss_score !== undefined) metadata.cvss_score = issue.cvss_score;
8989
if (issue.fix_version) metadata.fix_version = issue.fix_version;
90+
} else if (category === 'duplicate_code') {
91+
if (issue.duplicate_group !== undefined) metadata.duplicate_group = issue.duplicate_group;
92+
if (issue.duplicate_locations) metadata.duplicate_locations = issue.duplicate_locations;
93+
}
94+
95+
// Backend dispositions, present when dismissed / false positives are not filtered out.
96+
if (issue.is_dismissed === true) {
97+
metadata.dismissed = true;
98+
if (issue.reason_for_dismiss) metadata.reason_for_dismiss = issue.reason_for_dismiss;
99+
if (issue.comment_for_dismiss) metadata.comment_for_dismiss = issue.comment_for_dismiss;
100+
}
101+
// Secrets from older backends only carry the FALSE_POSITIVE confidence.
102+
if (issue.is_false_positive === true || String(issue.confidence_score || '').toUpperCase() === 'FALSE_POSITIVE') {
103+
metadata.false_positive = true;
90104
}
91105

92106
const id = buildId(category, filePath, lineNumber, checkId, message);

0 commit comments

Comments
 (0)