From e27faf58a4df79a6758291989fd9471caa7199d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A0nh=20Nh=C3=A2n?= <60387689+NhanAZ@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:11:02 +0700 Subject: [PATCH] feat(cli): add --min-severity filtering option --- ROADMAP.md | 2 +- src/cli/index.ts | 25 +++++++++++++++++++++---- src/scanner/index.ts | 15 +++++++++++++-- tests/scanner/scanner.test.ts | 14 ++++++++++++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f8d4e83..656070d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -25,7 +25,7 @@ ## v0.3.0 - Polish - [x] Python ecosystem support (`requirements.txt`, `pyproject.toml`) -- [ ] Severity filtering (`--min-severity`) +- [x] Severity filtering (`--min-severity`) - [ ] File/path exclusion patterns - [ ] Performance optimization for large repositories - [ ] More comprehensive test fixtures diff --git a/src/cli/index.ts b/src/cli/index.ts index 41f91a6..45153d8 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -27,12 +27,13 @@ Usage: Scan options: --json Output findings as JSON + --min-severity Filter by minimum severity (info, warning, error) Examples: opk scan opk scan ./src opk scan --json - opk scan ./src --json`; + opk scan ./src --min-severity error`; process.stdout.write(help + '\n'); } @@ -66,15 +67,31 @@ async function main(): Promise { } let scanPath = process.cwd(); + let minSeverity: 'info' | 'warning' | 'error' = 'info'; + for (let i = 1; i < args.length; i++) { const arg = args[i]; - if (arg !== '--json' && !arg.startsWith('-')) { + if (arg === '--min-severity') { + if (i + 1 < args.length) { + const val = args[i + 1]; + if (val === 'info' || val === 'warning' || val === 'error') { + minSeverity = val; + i++; // skip value + continue; + } else { + process.stderr.write(`Error: Invalid --min-severity value: ${val}. Expected info, warning, or error.\n`); + process.exit(2); + } + } else { + process.stderr.write('Error: --min-severity requires a value (info, warning, or error).\n'); + process.exit(2); + } + } else if (arg !== '--json' && !arg.startsWith('-')) { scanPath = path.resolve(arg); - break; } } - const result = await scan(scanPath); + const result = await scan(scanPath, minSeverity); if (useJson) { process.stdout.write(formatJson(result) + '\n'); diff --git a/src/scanner/index.ts b/src/scanner/index.ts index dfbdbc5..1be2228 100644 --- a/src/scanner/index.ts +++ b/src/scanner/index.ts @@ -69,7 +69,10 @@ function loadConfig(rootDir: string): OpkConfig | null { } } -export async function scan(rootDir: string): Promise { +export async function scan( + rootDir: string, + minSeverity: 'info' | 'warning' | 'error' = 'info' +): Promise { const startTime = Date.now(); const absoluteRoot = path.resolve(rootDir); @@ -112,8 +115,16 @@ export async function scan(rootDir: string): Promise { const durationMs = Date.now() - startTime; + const severityLevels = { info: 0, warning: 1, error: 2 }; + const minLevel = severityLevels[minSeverity] ?? 0; + + const filteredFindings = allFindings.filter((finding) => { + const findingLevel = severityLevels[finding.severity] ?? 0; + return findingLevel >= minLevel; + }); + return { - findings: allFindings, + findings: filteredFindings, scannedFiles: files.length, rulesRun, durationMs, diff --git a/tests/scanner/scanner.test.ts b/tests/scanner/scanner.test.ts index dcc4595..e0f895e 100644 --- a/tests/scanner/scanner.test.ts +++ b/tests/scanner/scanner.test.ts @@ -14,4 +14,18 @@ test('Scanner Integration', async (t) => { // There are no other findings in this folder. assert.strictEqual(result.findings.length, 0); }); + + await t.test('should filter findings by minimum severity', async () => { + // We scan prompt-artifacts which contains OPK-002 (warning). + const rootDir = path.join(FIXTURES_DIR, 'prompt-artifacts'); + + // Default (info) should include warnings + const defaultResult = await scan(rootDir); + assert.ok(defaultResult.findings.length > 0); + assert.strictEqual(defaultResult.findings[0].severity, 'warning'); + + // Error minSeverity should filter out warnings + const errorResult = await scan(rootDir, 'error'); + assert.strictEqual(errorResult.findings.length, 0); + }); });