Skip to content

Commit 52a88d4

Browse files
Merge pull request #90 from CodeDeficient/claude/project-thread-36cl1x-report-flag
Add --report=<path> and --no-report
2 parents 9db3f3d + 399a8fc commit 52a88d4

4 files changed

Lines changed: 43 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes will be documented in this file.
55
## [Unreleased]
66

77
### Added
8+
- `--report=<path>` writes the JSON report somewhere other than `./ai-slop-report.json`, and `--no-report` skips it, so a scan no longer has to leave a file in the working tree.
89
- `swallowed_error` (medium): a `catch` block whose only statements are `console` calls. The error is logged and the code carries on as if the operation succeeded. A comment in the block explaining why that is safe, a rethrow, or any other statement (such as `setError(e)`) means it is not reported, and so does a fallback after the `try`, as for `empty_catch`.
910

1011
### Changed

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ npx karpeslop@latest -- -dash-file.ts # paths that start with "-"
6262
npx karpeslop@latest --help # list every rule
6363
```
6464

65-
A JSON report is written to `./ai-slop-report.json`.
65+
A JSON report is written to `./ai-slop-report.json`. `--report=<path>` writes it elsewhere, and `--no-report` skips it.
6666

6767
### Exit codes
6868

‎ai-slop-detector.ts‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,20 @@ export class ConfigError extends Error {}
270270
/** A command-line flag with a value KarpeSlop does not accept. */
271271
export class UsageError extends Error {}
272272

273-
const KNOWN_FLAGS = new Set(['--help', '-h', '/?', '--version', '-v', '--quiet', '-q', '--strict', '-s']);
273+
const KNOWN_FLAGS = new Set(['--help', '-h', '/?', '--version', '-v', '--quiet', '-q', '--strict', '-s', '--no-report']);
274274

275275
/** Flags KarpeSlop does not know. A typo such as `--stirct` would otherwise run without the option. */
276276
export function unknownFlags(flagArgs: readonly string[]): string[] {
277-
return flagArgs.filter(arg => !KNOWN_FLAGS.has(arg) && !arg.startsWith('--fail-on='));
277+
return flagArgs.filter(arg => !KNOWN_FLAGS.has(arg) && !arg.startsWith('--fail-on=') && !arg.startsWith('--report='));
278+
}
279+
280+
/** Where to write the JSON report: `--report=<path>`, nowhere with `--no-report`, else ./ai-slop-report.json. */
281+
export function reportPathFrom(flagArgs: readonly string[], rootDir: string): string | undefined {
282+
const flag = flagArgs.filter(arg => arg.startsWith('--report=')).at(-1);
283+
const value = flag?.slice('--report='.length);
284+
if (value === '') throw new UsageError('--report needs a file path, e.g. --report=reports/karpeslop.json');
285+
if (flagArgs.includes('--no-report')) return undefined;
286+
return path.resolve(rootDir, value ?? 'ai-slop-report.json');
278287
}
279288

280289
/** The severity named by `--fail-on=<severity>`, high when absent. Throws on an unknown value. */
@@ -1054,6 +1063,7 @@ class AISlopDetector {
10541063
issues: consolidatedIssues
10551064
};
10561065

1066+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
10571067
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
10581068
console.log(`\nReport written to ${outputPath}`);
10591069
}
@@ -1082,6 +1092,8 @@ Options:
10821092
--strict, -s Exit with code 2 if critical findings (wrong imports, nonexistent packages) are present
10831093
--fail-on=<severity>
10841094
Lowest severity that fails the run with exit 1: critical, high (default), medium or low
1095+
--report=<path> Write the JSON report to <path> (default ./ai-slop-report.json)
1096+
--no-report Do not write the JSON report
10851097
--version, -v Show version information
10861098
-- End of flags; treat remaining args as paths (even if they start with -)
10871099
@@ -1133,8 +1145,10 @@ async function runIfMain() {
11331145
process.exit(1);
11341146
}
11351147
let failOn: Severity;
1148+
let reportPath: string | undefined;
11361149
try {
11371150
failOn = failOnFrom(flagArgs);
1151+
reportPath = reportPathFrom(flagArgs, rootDir);
11381152
} catch (error) {
11391153
if (!(error instanceof UsageError)) throw error;
11401154
console.error(`KarpeSlop: ${error.message}`);
@@ -1145,8 +1159,7 @@ async function runIfMain() {
11451159

11461160
try {
11471161
const issues = await detector.detect(quiet);
1148-
const outputPath = path.join(rootDir, 'ai-slop-report.json');
1149-
detector.exportResults(outputPath);
1162+
if (reportPath !== undefined) detector.exportResults(reportPath);
11501163

11511164
const criticalIssues = issues.filter(i => i.severity === 'critical');
11521165
if (strict && criticalIssues.length > 0) {

‎tests/ai-slop-detector.behavior.test.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isRegistryBackedLockfileEntry,
1717
parseVersionRange,
1818
pLimit,
19+
reportPathFrom,
1920
shouldAnalyzePathInQuietMode,
2021
splitCliArgs,
2122
uniqueRealFiles,
@@ -637,6 +638,29 @@ test('a path that cannot be scanned gives a one-line error, not a stack trace',
637638
assert.equal(/\n\s+at /.test(result.stderr), false, result.stderr);
638639
});
639640

641+
test('the report path comes from --report=<path>, and --no-report writes none', () => {
642+
assert.equal(reportPathFrom([], '/repo'), path.resolve('/repo', 'ai-slop-report.json'));
643+
assert.equal(reportPathFrom(['--report=out/k.json'], '/repo'), path.resolve('/repo', 'out/k.json'));
644+
assert.equal(reportPathFrom(['--report=a.json', '--no-report'], '/repo'), undefined);
645+
assert.throws(() => reportPathFrom(['--report='], '/repo'), /--report needs a file path/);
646+
assert.throws(() => reportPathFrom(['--report=', '--no-report'], '/repo'), /--report needs a file path/);
647+
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'karpeslop-report-'));
648+
fs.writeFileSync(path.join(projectDir, 'index.ts'), 'export const a = 1;\n', 'utf-8');
649+
const run = (...flags: string[]) => spawnSync(
650+
process.execPath,
651+
['--import', import.meta.resolve('tsx'), path.resolve(process.cwd(), 'ai-slop-detector.ts'), ...flags],
652+
{ encoding: 'utf-8', cwd: projectDir, env: { ...process.env, KARPESLOP_OFFLINE: '1' } }
653+
);
654+
try {
655+
assert.equal(run('--no-report').status, 0);
656+
assert.equal(fs.existsSync(path.join(projectDir, 'ai-slop-report.json')), false);
657+
assert.equal(run('--report=reports/karpeslop.json').status, 0);
658+
assert.equal(fs.existsSync(path.join(projectDir, 'reports', 'karpeslop.json')), true);
659+
} finally {
660+
fs.rmSync(projectDir, { recursive: true, force: true });
661+
}
662+
});
663+
640664
test('unknown flags are rejected instead of silently ignored', () => {
641665
assert.deepEqual(unknownFlags(['--quiet', '-s', '--fail-on=low']), []);
642666
assert.deepEqual(unknownFlags(['--stirct', '--json', '-dash-file.ts']), ['--stirct', '--json', '-dash-file.ts']);

0 commit comments

Comments
 (0)