codeant-cli 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeant-cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "description": "Code review CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@ export default function Login() {
11
11
  const { exit } = useApp();
12
12
  const [status, setStatus] = useState('opening');
13
13
  const [error, setError] = useState(null);
14
+ const [loginUrl, setLoginUrl] = useState('');
14
15
 
15
16
  useEffect(() => {
16
17
  // Check if already logged in
@@ -24,6 +25,7 @@ export default function Login() {
24
25
  const token = randomUUID();
25
26
  const baseUrl = getBaseUrl();
26
27
  const loginUrl = `https://app.codeant.ai?ideLoginToken=${token}`;
28
+ setLoginUrl(loginUrl);
27
29
  const pollUrl = `${baseUrl}/extension/login/status?apiKey=${token}`;
28
30
 
29
31
  // Open browser
@@ -94,7 +96,7 @@ export default function Login() {
94
96
  return React.createElement(
95
97
  Box,
96
98
  { flexDirection: 'column', padding: 1 },
97
- React.createElement(Text, { color: 'cyan' }, 'Waiting for login...'),
99
+ React.createElement(Text, { color: 'cyan' }, `Waiting for login. Open this URL in your browser: ${loginUrl}`),
98
100
  React.createElement(Text, { color: 'gray' }, 'Complete the login in your browser.'),
99
101
  React.createElement(Text, { color: 'gray' }, 'Checking every 10 seconds. Timeout in 10 minutes.')
100
102
  );
@@ -71,7 +71,8 @@ export default function registerScansCommands(program, { runCmd }) {
71
71
  .option('--severity <list>', 'Filter by severity (e.g. critical,high)')
72
72
  .option('--path <glob>', 'Filter by file path glob')
73
73
  .option('--check <regex>', 'Filter by check ID or name (regex)')
74
- .option('--include-dismissed', 'Include dismissed findings (excluded by default)')
74
+ .option('--filter-dismissed', 'Exclude dismissed findings (default: false)')
75
+ .option('--no-false-positives', 'Exclude false positives (default: included)')
75
76
  .option('--format <fmt>', 'Output format: json|sarif|csv|md|table (default: json)', 'json')
76
77
  .option('--output <path>', 'Write output to file instead of stdout')
77
78
  .option('--fields <list>', 'Project findings to subset of fields (comma-separated)')
@@ -96,7 +97,8 @@ export default function registerScansCommands(program, { runCmd }) {
96
97
  severity: opts.severity,
97
98
  path: opts.path,
98
99
  check: opts.check,
99
- includeDismissed: opts.includeDismissed || false,
100
+ filterDismissed: opts.filterDismissed || false,
101
+ includeFalsePositives: opts.falsePositives ?? true,
100
102
  format: opts.format,
101
103
  output: opts.output,
102
104
  fields: opts.fields,
@@ -1,29 +1,22 @@
1
1
  import { minimatch } from 'minimatch';
2
- import { isDismissed, findDismissMatch } from './dismissMatch.js';
3
2
  import { normalizeSeverity } from './normalize.js';
4
3
 
5
- const SEV_RANK = { critical: 5, high: 4, medium: 3, low: 2, info: 1, unknown: 0 };
6
-
7
4
  /**
8
- * Apply all filters to findings in-place (returns new array).
5
+ * Apply client-side filters to findings (returns new array).
6
+ * Dismissed / false-positive filtering is handled exclusively by the backend.
9
7
  *
10
8
  * @param {Array} findings - NormalizedFinding[]
11
9
  * @param {object} opts
12
- * @param {string[]|null} opts.severity - allowed severity levels (e.g. ['critical','high'])
13
- * @param {string|null} opts.pathGlob - minimatch glob for file_path
10
+ * @param {string[]|null} opts.severity - allowed severity levels (e.g. ['critical','high'])
11
+ * @param {string|null} opts.pathGlob - minimatch glob for file_path
14
12
  * @param {string|null} opts.checkRegex - regex applied to check_id + check_name
15
- * @param {Array} opts.dismissedAlerts - from fetchDismissedAlerts()
16
- * @param {boolean} opts.includeDismissed
17
- * @returns {Array} filtered NormalizedFinding[] (dismissed field annotated)
13
+ * @returns {Array} filtered NormalizedFinding[]
18
14
  */
19
15
  export function applyFilters(findings, {
20
16
  severity = null,
21
17
  pathGlob = null,
22
18
  checkRegex = null,
23
- dismissedAlerts = [],
24
- includeDismissed = false,
25
19
  } = {}) {
26
- // Pre-compile regex — strip Python/PCRE inline flag (?i) and fold into JS flag
27
20
  let checkRe = null;
28
21
  if (checkRegex) {
29
22
  try {
@@ -37,35 +30,15 @@ export function applyFilters(findings, {
37
30
  }
38
31
  }
39
32
 
40
- // Severity set
41
33
  const sevSet = severity && severity.length > 0
42
34
  ? new Set(severity.map((s) => normalizeSeverity(s)))
43
35
  : null;
44
36
 
45
37
  const result = [];
46
38
  for (const f of findings) {
47
- // Annotate dismiss status
48
- const match = findDismissMatch(f, dismissedAlerts);
49
- f.dismissed = match !== null;
50
- if (match) {
51
- f.dismiss_info = {
52
- reason: match.reason_for_dismiss || null,
53
- comment: match.comment_for_dismiss || null,
54
- };
55
- }
56
-
57
- // Filter dismissed
58
- if (f.dismissed && !includeDismissed) continue;
59
-
60
- // Filter by severity
61
39
  if (sevSet && !sevSet.has(f.severity)) continue;
62
-
63
- // Filter by path glob
64
40
  if (pathGlob && !minimatch(f.file_path, pathGlob, { matchBase: true })) continue;
65
-
66
- // Filter by check regex
67
41
  if (checkRe && !checkRe.test(f.check_id) && !checkRe.test(f.check_name)) continue;
68
-
69
42
  result.push(f);
70
43
  }
71
44
  return result;
@@ -106,8 +106,6 @@ export function normalizeIssue(issue, category) {
106
106
  cve: cve ? String(cve) : null,
107
107
  package: packageInfo,
108
108
  metadata,
109
- dismissed: false,
110
- dismiss_info: null,
111
109
  };
112
110
  }
113
111
 
@@ -7,7 +7,6 @@ import { paginate } from './lib/paginate.js';
7
7
  import { emit } from './lib/emit.js';
8
8
  import { progress, logError } from './lib/log.js';
9
9
  import { FORMATTERS } from './formatters/index.js';
10
- import { fetchDismissedAlerts } from '../../scans/fetchDismissedAlerts.js';
11
10
 
12
11
  /**
13
12
  * codeant scans results — full orchestration.
@@ -23,7 +22,8 @@ export async function runResults(opts = {}) {
23
22
  severity,
24
23
  path: pathGlob,
25
24
  check: checkRegex,
26
- includeDismissed = false,
25
+ filterDismissed = false,
26
+ includeFalsePositives = true,
27
27
  format = 'json',
28
28
  output: outputPath = null,
29
29
  fields = null,
@@ -53,16 +53,12 @@ export async function runResults(opts = {}) {
53
53
  // 2. Parse types
54
54
  const categories = parseTypes(types);
55
55
 
56
- // 3. Fetch in parallel + dismissed alerts
56
+ // 3. Fetch in parallel
57
57
  progress(`fetching ${categories.map((c) => c.key).join(', ')}…`);
58
- const [settled, dismissedResult] = await Promise.all([
59
- Promise.allSettled(categories.map((c) => c.fetcher(repo, scanMeta.commit_id))),
60
- includeDismissed
61
- ? Promise.resolve({ success: true, dismissedAlerts: [] })
62
- : fetchDismissedAlerts(repo, 'security'),
63
- ]);
64
-
65
- const dismissedAlerts = dismissedResult.success ? (dismissedResult.dismissedAlerts ?? []) : [];
58
+ const fetchOpts = { filterDismissed, includeFalsePositives };
59
+ const settled = await Promise.allSettled(
60
+ categories.map((c) => c.fetcher(repo, scanMeta.commit_id, fetchOpts))
61
+ );
66
62
 
67
63
  // 4. Collect findings + errors
68
64
  const allFindings = [];
@@ -100,8 +96,6 @@ export async function runResults(opts = {}) {
100
96
  severity: severityList,
101
97
  pathGlob: pathGlob || null,
102
98
  checkRegex: checkRegex || null,
103
- dismissedAlerts,
104
- includeDismissed,
105
99
  });
106
100
 
107
101
  // 6. Sort
@@ -115,7 +109,8 @@ export async function runResults(opts = {}) {
115
109
  severity: severityList,
116
110
  path: pathGlob || null,
117
111
  check: checkRegex || null,
118
- include_dismissed: includeDismissed,
112
+ filter_dismissed: filterDismissed,
113
+ include_false_positives: includeFalsePositives,
119
114
  };
120
115
 
121
116
  // Rebuild summary from all filtered (pre-page) findings
@@ -153,7 +153,7 @@ function ErrorView({ message, onBack, canGoBack }) {
153
153
 
154
154
  // ─── Main ScanCenter ─────────────────────────────────────────────────────────
155
155
 
156
- export default function ScanCenter() {
156
+ export default function ScanCenter({ filterDismissed = false, includeFalsePositives = true }) {
157
157
  const { exit } = useApp();
158
158
 
159
159
  const [step, setStep] = useState(STEPS.LOADING);
@@ -198,7 +198,7 @@ export default function ScanCenter() {
198
198
 
199
199
  // ── Step 5: result type selected → fetch exactly one endpoint ──
200
200
  const handleSelectResultType = (item) =>
201
- _handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults });
201
+ _handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults, filterDismissed, includeFalsePositives });
202
202
 
203
203
  // ── Back navigation ──
204
204
  const goBack = {
package/src/index.js CHANGED
@@ -211,8 +211,13 @@ program
211
211
  program
212
212
  .command('scan-center')
213
213
  .description('Browse scan results interactively')
214
- .action(() => {
215
- render(React.createElement(ScanCenter));
214
+ .option('--filter-dismissed', 'Exclude dismissed alerts from results (default: false)')
215
+ .option('--no-false-positives', 'Exclude false positives from results (default: included)')
216
+ .action((options) => {
217
+ render(React.createElement(ScanCenter, {
218
+ filterDismissed: options.filterDismissed ?? false,
219
+ includeFalsePositives: options.falsePositives ?? true,
220
+ }));
216
221
  });
217
222
 
218
223
  program
@@ -2,7 +2,7 @@ import { fetchScanResults } from '../scans/fetchScanResults.js';
2
2
  import { fetchAdvancedScanResults } from '../scans/fetchAdvancedScanResults.js';
3
3
  import { fetchDismissedAlerts } from '../scans/fetchDismissedAlerts.js';
4
4
 
5
- export async function handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults }) {
5
+ export async function handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults, filterDismissed = false, includeFalsePositives = true }) {
6
6
  const rt = item.value;
7
7
  setSelectedResultType(rt);
8
8
  setStep(STEPS.LOADING);
@@ -10,12 +10,13 @@ export async function handleSelectResultType({ STEPS, item, selectedRepo, select
10
10
 
11
11
  const repo = selectedRepo.full_name;
12
12
  const commitId = selectedScan.commitId;
13
+ const fetchOpts = { filterDismissed, includeFalsePositives };
13
14
  let res;
14
15
 
15
16
  if (rt.kind === 'basic') {
16
- res = await fetchScanResults(repo, commitId, rt.value);
17
+ res = await fetchScanResults(repo, commitId, rt.value, fetchOpts);
17
18
  } else if (rt.kind === 'advanced') {
18
- res = await fetchAdvancedScanResults(repo, commitId, rt.value);
19
+ res = await fetchAdvancedScanResults(repo, commitId, rt.value, fetchOpts);
19
20
  } else if (rt.value === 'dismissed_alerts') {
20
21
  const r = await fetchDismissedAlerts(repo, 'security');
21
22
  res = r.success ? { success: true, issues: r.dismissedAlerts } : r;
@@ -200,9 +200,12 @@ function normalizeAdvancedIssue(item, resultType) {
200
200
  * @param {string} repo - "org/repo-name"
201
201
  * @param {string} commitId - 40-char commit SHA
202
202
  * @param {string} resultType - one of ADVANCED_RESULT_TYPES values
203
+ * @param {{ filterDismissed?: boolean, includeFalsePositives?: boolean }} [opts]
203
204
  * @returns {Promise<{ success: boolean, issues?: Array, healthyPackages?: Array, status?: string, error?: string }>}
204
205
  */
205
- export async function fetchAdvancedScanResults(repo, commitId, resultType) {
206
+ export async function fetchAdvancedScanResults(repo, commitId, resultType, opts = {}) {
207
+ const { filterDismissed = false, includeFalsePositives = true } = opts;
208
+
206
209
  if (!Object.values(ADVANCED_RESULT_TYPES).includes(resultType)) {
207
210
  return {
208
211
  success: false,
@@ -215,6 +218,8 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
215
218
  repo,
216
219
  commit_id: commitId,
217
220
  result_type: resultType,
221
+ filter_dismissed: filterDismissed,
222
+ include_false_positives: includeFalsePositives,
218
223
  });
219
224
 
220
225
  if (!response) {
@@ -294,8 +299,8 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
294
299
  }
295
300
  }
296
301
 
297
- // Filter secrets false positives
298
- if (resultType === ADVANCED_RESULT_TYPES.SECRETS) {
302
+ // Filter secrets false positives only when caller explicitly excludes them
303
+ if (resultType === ADVANCED_RESULT_TYPES.SECRETS && !includeFalsePositives) {
299
304
  issues = issues.filter((issue) => String(issue.confidence_score || '').toLowerCase() !== 'false_positive');
300
305
  }
301
306
 
@@ -309,17 +314,17 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
309
314
  }
310
315
  }
311
316
 
312
- export const fetchScaResults = (repo, commitId) =>
313
- fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SCA);
317
+ export const fetchScaResults = (repo, commitId, opts) =>
318
+ fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SCA, opts);
314
319
 
315
- export const fetchSbomResults = (repo, commitId) =>
316
- fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SBOM);
320
+ export const fetchSbomResults = (repo, commitId, opts) =>
321
+ fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SBOM, opts);
317
322
 
318
- export const fetchSecretsResults = (repo, commitId) =>
319
- fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SECRETS);
323
+ export const fetchSecretsResults = (repo, commitId, opts) =>
324
+ fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SECRETS, opts);
320
325
 
321
- export const fetchIacResults = (repo, commitId) =>
322
- fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.IAC);
326
+ export const fetchIacResults = (repo, commitId, opts) =>
327
+ fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.IAC, opts);
323
328
 
324
- export const fetchDeadCodeResults = (repo, commitId) =>
325
- fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.DEAD_CODE);
329
+ export const fetchDeadCodeResults = (repo, commitId, opts) =>
330
+ fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.DEAD_CODE, opts);
@@ -49,9 +49,12 @@ function extractCleanPath(fullPath, commitId, suffix = null) {
49
49
  * @param {string} repo - "org/repo-name"
50
50
  * @param {string} commitId - 40-char commit SHA
51
51
  * @param {string} resultType - one of VALID_RESULT_TYPES values
52
+ * @param {{ filterDismissed?: boolean, includeFalsePositives?: boolean }} [opts]
52
53
  * @returns {Promise<{ success: boolean, issues?: Array, status?: string, error?: string }>}
53
54
  */
54
- export async function fetchScanResults(repo, commitId, resultType) {
55
+ export async function fetchScanResults(repo, commitId, resultType, opts = {}) {
56
+ const { filterDismissed = false, includeFalsePositives = true } = opts;
57
+
55
58
  if (!Object.values(VALID_RESULT_TYPES).includes(resultType)) {
56
59
  return {
57
60
  success: false,
@@ -64,6 +67,8 @@ export async function fetchScanResults(repo, commitId, resultType) {
64
67
  repo,
65
68
  commit_id: commitId,
66
69
  result_type: resultType,
70
+ filter_dismissed: filterDismissed,
71
+ include_false_positives: includeFalsePositives,
67
72
  });
68
73
 
69
74
  if (!response) {
@@ -102,14 +107,14 @@ export async function fetchScanResults(repo, commitId, resultType) {
102
107
  }
103
108
  }
104
109
 
105
- export const fetchSastResults = (repo, commitId) =>
106
- fetchScanResults(repo, commitId, VALID_RESULT_TYPES.SECURITY_ISSUES);
110
+ export const fetchSastResults = (repo, commitId, opts) =>
111
+ fetchScanResults(repo, commitId, VALID_RESULT_TYPES.SECURITY_ISSUES, opts);
107
112
 
108
- export const fetchAntiPatternsResults = (repo, commitId) =>
109
- fetchScanResults(repo, commitId, VALID_RESULT_TYPES.ANTI_PATTERNS);
113
+ export const fetchAntiPatternsResults = (repo, commitId, opts) =>
114
+ fetchScanResults(repo, commitId, VALID_RESULT_TYPES.ANTI_PATTERNS, opts);
110
115
 
111
- export const fetchDocstringResults = (repo, commitId) =>
112
- fetchScanResults(repo, commitId, VALID_RESULT_TYPES.DOCSTRING);
116
+ export const fetchDocstringResults = (repo, commitId, opts) =>
117
+ fetchScanResults(repo, commitId, VALID_RESULT_TYPES.DOCSTRING, opts);
113
118
 
114
- export const fetchComplexFunctionsResults = (repo, commitId) =>
115
- fetchScanResults(repo, commitId, VALID_RESULT_TYPES.COMPLEX_FUNCTIONS);
119
+ export const fetchComplexFunctionsResults = (repo, commitId, opts) =>
120
+ fetchScanResults(repo, commitId, VALID_RESULT_TYPES.COMPLEX_FUNCTIONS, opts);