@kernlang/cli 3.1.9 → 3.3.4

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.
@@ -1,14 +1,54 @@
1
1
  import { clearTemplates, registerTemplate, VALID_TARGETS } from '@kernlang/core';
2
- import { analyzeTaint, buildLLMPrompt, buildReviewInstructions, checkEnforcement, checkSpecFiles, clearReviewCache, dedup, exportKernIR, formatEnforcement, formatReport, formatSARIF, formatSummary, getRuleRegistry, isLLMAvailable, linkToNodes, resolveImportGraph, reviewFile, reviewGraph, runESLint, runLLMReview, runTSCDiagnosticsFromPaths, specViolationsToFindings, } from '@kernlang/review';
3
- import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
4
- import { basename, relative, resolve } from 'path';
5
- import { collectTsFilesFlat, hasFlag, loadConfig, parseAndSurface, parseFlag } from '../shared.js';
2
+ import { analyzeTaint, buildLLMPrompt, buildReviewInstructions, checkEnforcement, checkSpecFiles, clearReviewCache, dedup, exportKernIR, formatEnforcement, formatReport, formatSARIF, formatSARIFWithMetadata, formatSummary, getRuleRegistry, isLLMAvailable, linkToNodes, ReviewHealthBuilder, resolveImportGraph, reviewFile, reviewGraph, runESLint, runLLMReview, runTSCDiagnosticsFromPaths, specViolationsToFindings, } from '@kernlang/review';
3
+ import { execFileSync } from 'child_process';
4
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
5
+ import { basename, dirname, relative, resolve } from 'path';
6
+ import { withOptionalRemoteRepo } from '../remote-repo.js';
7
+ import { compareReportsToBaseline, createReviewBaseline, filterReportsToNewFindings, getReviewBaselineKeyForFinding, parseReviewBaseline, } from '../review-baseline.js';
8
+ import { collectTsFilesFlat, hasFlag, loadConfig, parseAndSurface, parseFlag, parseFlagOrNext } from '../shared.js';
9
+ /**
10
+ * Pick a safe default diff base for bare `kern review` inside a git repo.
11
+ * Tries `origin/main`, then `origin/master`, then `HEAD~1`, returning the
12
+ * first ref that `git rev-parse --verify` accepts. Returns undefined when
13
+ * not in a git repo or no suitable ref exists (e.g. single-commit repo).
14
+ *
15
+ * Exported for testability.
16
+ */
17
+ export function detectAutoDiffBase(cwd = process.cwd()) {
18
+ const verify = (ref) => {
19
+ try {
20
+ execFileSync('git', ['rev-parse', '--verify', '--quiet', ref], {
21
+ cwd,
22
+ stdio: ['ignore', 'ignore', 'ignore'],
23
+ });
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ };
30
+ // Require this to actually be a git repo before trying refs.
31
+ try {
32
+ execFileSync('git', ['rev-parse', '--is-inside-work-tree'], {
33
+ cwd,
34
+ stdio: ['ignore', 'ignore', 'ignore'],
35
+ });
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ for (const candidate of ['origin/main', 'origin/master', 'HEAD~1']) {
41
+ if (verify(candidate))
42
+ return candidate;
43
+ }
44
+ return undefined;
45
+ }
6
46
  // ── Review pipeline ──────────────────────────────────────────────────────
7
47
  async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
8
- const { graphMode, batchMode, llmMode, cloudMode, securityMode, mcpMode, specMode, fixMode, autofixMode, lintMode, exportKern, enforce, jsonOutput, sarifOutput, maxDepth, batchSize, tsconfigPath, specFile, minCoverageArg, maxComplexityArg, maxErrorsArg, maxWarningsArg, } = modes;
48
+ const { graphMode, batchMode, llmMode, cloudMode, securityMode, mcpMode, specMode, fixMode, autofixMode, lintMode, skipGenerated, exportKern, enforce, jsonOutput, sarifOutput, maxDepth, batchSize, tsconfigPath, specFile, minCoverageArg, maxComplexityArg, maxErrorsArg, maxWarningsArg, baseline, writeBaselinePath, newOnly, } = modes;
9
49
  let reports = [];
10
50
  if (graphMode && entryFilePaths.length > 0) {
11
- const graphOpts = { maxDepth, tsConfigFilePath: tsconfigPath ? resolve(tsconfigPath) : undefined };
51
+ const graphOpts = { maxDepth, tsConfigFilePath: tsconfigPath };
12
52
  const graph = resolveImportGraph(entryFilePaths, graphOpts);
13
53
  console.log(` Graph: ${graph.totalFiles} files resolved (${graph.skipped} skipped, depth ${maxDepth})`);
14
54
  reports = reviewGraph(entryFilePaths, reviewConfig, graphOpts);
@@ -40,6 +80,14 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
40
80
  }
41
81
  }
42
82
  }
83
+ if (skipGenerated) {
84
+ const before = reports.length;
85
+ reports = reports.filter((r) => !r.generated);
86
+ const dropped = before - reports.length;
87
+ if (dropped > 0 && !jsonOutput && !sarifOutput) {
88
+ console.log(` Skipped ${dropped} generated file(s). Use --include-generated to review them.`);
89
+ }
90
+ }
43
91
  if (reports.length === 0) {
44
92
  console.log(' No reviewable files found (.ts/.tsx/.py/.kern).');
45
93
  return { reports, exitCode: 0 };
@@ -306,7 +354,13 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
306
354
  }
307
355
  }
308
356
  else {
309
- // No API key — AI CLI tool IS the reviewer
357
+ // No API key — emit machine-readable context for an upstream AI CLI
358
+ // (claude/codex/gemini) to consume as the reviewer. Without a banner
359
+ // this looks like "--llm did nothing" to someone running it standalone.
360
+ console.log(' LLM review: KERN_LLM_API_KEY not set — emitting LLM-prompt context.');
361
+ console.log(' Pipe to an AI CLI: kern review --llm <file> | claude');
362
+ console.log(' Or set an API key: export KERN_LLM_API_KEY=<key>');
363
+ console.log('');
310
364
  for (const report of reports) {
311
365
  const rel = relative(process.cwd(), report.filePath);
312
366
  if (report.findings.length > 0) {
@@ -518,7 +572,10 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
518
572
  // Lint mode
519
573
  if (lintMode) {
520
574
  const filePaths = reports.map((r) => r.filePath).filter((f) => existsSync(f));
521
- const eslintFindings = await runESLint(filePaths, process.cwd());
575
+ // Collect lint-phase health across runESLint + runTSCDiagnosticsFromPaths; merge onto every
576
+ // report at the end so "ESLint not installed" shows up in the review header, not just console.
577
+ const lintHealth = new ReviewHealthBuilder();
578
+ const eslintFindings = await runESLint(filePaths, process.cwd(), lintHealth);
522
579
  if (eslintFindings.length > 0) {
523
580
  console.log(` ESLint: ${eslintFindings.length} findings`);
524
581
  for (const report of reports) {
@@ -530,7 +587,7 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
530
587
  else {
531
588
  console.log(' ESLint: no findings (or not installed)');
532
589
  }
533
- const tscFindings = runTSCDiagnosticsFromPaths(filePaths);
590
+ const tscFindings = runTSCDiagnosticsFromPaths(filePaths, lintHealth);
534
591
  if (tscFindings.length > 0) {
535
592
  console.log(` tsc: ${tscFindings.length} findings`);
536
593
  for (const report of reports) {
@@ -542,10 +599,43 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
542
599
  else {
543
600
  console.log(' tsc: no findings');
544
601
  }
602
+ // Fold lint-phase health into each report's existing health (builder dedupes by key so merging
603
+ // a skipped-ESLint note on a report that already has an fs-project fallback keeps both).
604
+ const lintHealthBuilt = lintHealth.build();
605
+ if (lintHealthBuilt) {
606
+ for (const report of reports) {
607
+ const merged = new ReviewHealthBuilder();
608
+ for (const e of report.health?.entries ?? [])
609
+ merged.note(e);
610
+ for (const e of lintHealthBuilt.entries)
611
+ merged.note(e);
612
+ report.health = merged.build();
613
+ }
614
+ }
615
+ }
616
+ let baselineComparison;
617
+ let reportsForOutput = reports;
618
+ let reportsForEnforcement = reports;
619
+ if (baseline) {
620
+ baselineComparison = compareReportsToBaseline(reports, baseline);
621
+ reportsForEnforcement = filterReportsToNewFindings(reports, baselineComparison);
622
+ if (newOnly) {
623
+ reportsForOutput = reportsForEnforcement;
624
+ }
625
+ }
626
+ if (writeBaselinePath) {
627
+ const baselineDir = dirname(writeBaselinePath);
628
+ if (baselineDir && baselineDir !== '.') {
629
+ mkdirSync(baselineDir, { recursive: true });
630
+ }
631
+ writeFileSync(writeBaselinePath, `${JSON.stringify(createReviewBaseline(reports), null, 2)}\n`);
632
+ if (!jsonOutput && !sarifOutput) {
633
+ console.log(` Baseline written: ${writeBaselinePath}`);
634
+ }
545
635
  }
546
636
  // Output
547
637
  if (jsonOutput) {
548
- const enriched = reports.map((report) => {
638
+ const enriched = reportsForOutput.map((report) => {
549
639
  const llmPrompt = buildLLMPrompt(report.inferred, report.templateMatches);
550
640
  const kernIR = exportKernIR(report.inferred, report.templateMatches);
551
641
  return { ...report, kernIR, llmPrompt };
@@ -553,16 +643,40 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
553
643
  console.log(JSON.stringify(enriched.length === 1 ? enriched[0] : enriched, null, 2));
554
644
  }
555
645
  else if (sarifOutput) {
556
- console.log(formatSARIF(reports));
646
+ if (baselineComparison) {
647
+ console.log(formatSARIFWithMetadata(reportsForOutput, {
648
+ getBaselineStatus: (report, finding) => {
649
+ const key = getReviewBaselineKeyForFinding(report.filePath, finding);
650
+ if (baselineComparison.knownKeys.has(key))
651
+ return 'existing';
652
+ if (baselineComparison.newKeys.has(key))
653
+ return 'new';
654
+ return undefined;
655
+ },
656
+ }));
657
+ }
658
+ else if (reportsForOutput.some((report) => (report.suppressedFindings?.length ?? 0) > 0)) {
659
+ console.log(formatSARIFWithMetadata(reportsForOutput));
660
+ }
661
+ else {
662
+ console.log(formatSARIF(reportsForOutput));
663
+ }
557
664
  }
558
665
  else {
559
- for (const report of reports) {
666
+ for (const report of reportsForOutput) {
560
667
  console.log('');
561
668
  console.log(formatReport(report, reviewConfig));
562
669
  }
563
- if (reports.length > 1) {
670
+ if (reportsForOutput.length > 1) {
671
+ console.log('');
672
+ console.log(formatSummary(reportsForOutput));
673
+ }
674
+ if (baselineComparison) {
564
675
  console.log('');
565
- console.log(formatSummary(reports));
676
+ console.log(` Baseline: ${baselineComparison.knownCount} existing, ${baselineComparison.newCount} new, ${baselineComparison.resolvedCount} resolved`);
677
+ if (newOnly) {
678
+ console.log(' Output: showing only new findings compared to baseline');
679
+ }
566
680
  }
567
681
  const hasThresholds = minCoverageArg !== undefined ||
568
682
  maxComplexityArg !== undefined ||
@@ -571,7 +685,7 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
571
685
  if (enforce || hasThresholds) {
572
686
  console.log('');
573
687
  let allPassed = true;
574
- for (const report of reports) {
688
+ for (const report of reportsForEnforcement) {
575
689
  const result = checkEnforcement(report, reviewConfig);
576
690
  if (!result.passed) {
577
691
  allPassed = false;
@@ -581,7 +695,8 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
581
695
  }
582
696
  }
583
697
  if (allPassed) {
584
- console.log(` Enforcement: PASS (all files checked against thresholds)`);
698
+ const suffix = baselineComparison ? ' on new findings vs baseline' : '';
699
+ console.log(` Enforcement: PASS (all files checked against thresholds${suffix})`);
585
700
  }
586
701
  else {
587
702
  return { reports, exitCode: 1 };
@@ -591,7 +706,7 @@ async function runReviewPipeline(reviewConfig, entryFilePaths, modes) {
591
706
  return { reports, exitCode: 0 };
592
707
  }
593
708
  // ── Review command entry point ───────────────────────────────────────────
594
- export async function runReview(args) {
709
+ async function runReviewLocal(args) {
595
710
  const jsonOutput = hasFlag(args, '--json');
596
711
  const sarifOutput = hasFlag(args, '--sarif', '--format=sarif');
597
712
  const recursive = hasFlag(args, '--recursive', '-r');
@@ -606,15 +721,46 @@ export async function runReview(args) {
606
721
  const fixMode = hasFlag(args, '--fix');
607
722
  const autofixMode = hasFlag(args, '--autofix');
608
723
  const lintMode = hasFlag(args, '--lint');
724
+ // Phase 6: generated files skipped by default — bugs in compiler output
725
+ // belong to the compiler, not the user, and inference re-fires every
726
+ // handler-size/handler-heavy rule on transpiled function bodies. Opt back
727
+ // in with --include-generated. --skip-generated stays accepted as a no-op
728
+ // so CI configs that pass it explicitly don't break.
729
+ const includeGenerated = hasFlag(args, '--include-generated');
730
+ const skipGenerated = !includeGenerated;
609
731
  const graphMode = hasFlag(args, '--graph') || recursive;
610
732
  const batchMode = hasFlag(args, '--batch');
611
733
  const maxDepth = Number(parseFlag(args, '--max-depth') ?? 3);
612
734
  const batchSize = Number(parseFlag(args, '--batch-size') ?? 20);
613
- const tsconfigPath = parseFlag(args, '--tsconfig');
735
+ const explicitTsconfigPath = parseFlag(args, '--tsconfig');
736
+ // Only resolve when explicitly passed — per-file auto-discovery (via findTsConfig in the review engine)
737
+ // is usually right in monorepos, where the root tsconfig is a solution-only references file.
738
+ const tsconfigPath = explicitTsconfigPath ? resolve(explicitTsconfigPath) : undefined;
739
+ // Warn when the user explicitly points --tsconfig at a solution-only (references-only) file.
740
+ // ts-morph will load it but the resulting Project has no compilerOptions, which silently
741
+ // degrades review quality (no jsx, no paths, no strict). Tell the user before they waste a run.
742
+ if (tsconfigPath && existsSync(tsconfigPath)) {
743
+ try {
744
+ const raw = readFileSync(tsconfigPath, 'utf-8');
745
+ const stripped = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, '');
746
+ const parsed = JSON.parse(stripped);
747
+ const hasCompilerOptions = parsed.compilerOptions && Object.keys(parsed.compilerOptions).length > 0;
748
+ const hasReferences = Array.isArray(parsed.references) && parsed.references.length > 0;
749
+ if (!hasCompilerOptions && hasReferences) {
750
+ console.warn(` Warning: --tsconfig ${tsconfigPath} is a solution-only file (references-only, no compilerOptions).`);
751
+ console.warn(` Review quality will be degraded (no jsx/paths/strict). Point --tsconfig at a per-package tsconfig instead, or omit --tsconfig to let kern-review discover the nearest one per file.`);
752
+ }
753
+ }
754
+ catch {
755
+ // Bad JSON / unreadable — ts-morph will surface a clearer error during loading.
756
+ }
757
+ }
614
758
  const minCoverageArg = parseFlag(args, '--min-coverage');
615
759
  const minCoverage = minCoverageArg ? Number(minCoverageArg) : undefined;
616
760
  const maxComplexityArg = parseFlag(args, '--max-complexity');
617
761
  const maxComplexity = maxComplexityArg ? Number(maxComplexityArg) : 15;
762
+ const maxHandlerLinesArg = parseFlag(args, '--max-handler-lines');
763
+ const maxHandlerLines = maxHandlerLinesArg ? Number(maxHandlerLinesArg) : undefined;
618
764
  const maxErrorsArg = parseFlag(args, '--max-errors');
619
765
  const maxErrors = maxErrorsArg ? Number(maxErrorsArg) : 0;
620
766
  const maxWarningsArg = parseFlag(args, '--max-warnings');
@@ -623,6 +769,13 @@ export async function runReview(args) {
623
769
  const minConfidenceArg = parseFlag(args, '--min-confidence');
624
770
  const minConfidence = minConfidenceArg ? Number(minConfidenceArg) : undefined;
625
771
  const disableRuleArgs = args.filter((a) => a.startsWith('--disable-rule=')).map((a) => a.split('=')[1]);
772
+ const baselinePath = parseFlagOrNext(args, '--baseline');
773
+ const writeBaselinePath = parseFlagOrNext(args, '--write-baseline');
774
+ const newOnly = hasFlag(args, '--new-only');
775
+ if (newOnly && !baselinePath) {
776
+ console.error('--new-only requires --baseline=<file.json>');
777
+ process.exit(1);
778
+ }
626
779
  const rulesDirs = [];
627
780
  for (let i = 0; i < args.length; i++) {
628
781
  if (args[i] === '--rules-dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
@@ -637,9 +790,37 @@ export async function runReview(args) {
637
790
  const strict = strictArg === '--strict' ? 'inline' : strictArg === '--strict=all' ? 'all' : false;
638
791
  const strictParse = hasFlag(args, '--strict-parse');
639
792
  const listRules = hasFlag(args, '--list-rules');
640
- const diffBase = args.find((a) => a.startsWith('--diff'))
641
- ? parseFlag(args, '--diff') || args[args.indexOf('--diff') + 1] || 'origin/main'
793
+ let diffBase = args.some((a) => a === '--diff' || a.startsWith('--diff'))
794
+ ? parseFlagOrNext(args, '--diff') || 'origin/main'
642
795
  : undefined;
796
+ const fullMode = hasFlag(args, '--full');
797
+ if (fullMode && diffBase) {
798
+ console.error(' --full and --diff are mutually exclusive.');
799
+ process.exit(1);
800
+ }
801
+ let baseline;
802
+ if (baselinePath) {
803
+ const resolvedBaselinePath = resolve(baselinePath);
804
+ if (!existsSync(resolvedBaselinePath)) {
805
+ console.error(`Baseline not found: ${baselinePath}`);
806
+ process.exit(1);
807
+ }
808
+ let rawBaseline;
809
+ try {
810
+ rawBaseline = readFileSync(resolvedBaselinePath, 'utf-8');
811
+ }
812
+ catch (err) {
813
+ console.error(`Failed to read baseline ${baselinePath}: ${err.message}`);
814
+ process.exit(1);
815
+ }
816
+ try {
817
+ baseline = parseReviewBaseline(rawBaseline);
818
+ }
819
+ catch (err) {
820
+ console.error(`Failed to parse baseline ${baselinePath}: ${err.message}`);
821
+ process.exit(1);
822
+ }
823
+ }
643
824
  // --list-rules
644
825
  if (listRules) {
645
826
  const reviewCfg = loadConfig();
@@ -664,8 +845,60 @@ export async function runReview(args) {
664
845
  process.exit(0);
665
846
  }
666
847
  // Diff mode
667
- const reviewInputs = args.filter((a) => !a.startsWith('--') && a !== 'review');
848
+ const flagsWithValues = new Set([
849
+ '--spec',
850
+ '--diff',
851
+ '--git',
852
+ '--ref',
853
+ '--rules-dir',
854
+ '--tsconfig',
855
+ '--target',
856
+ '--max-depth',
857
+ '--batch-size',
858
+ '--min-coverage',
859
+ '--max-complexity',
860
+ '--max-errors',
861
+ '--max-warnings',
862
+ '--min-confidence',
863
+ '--baseline',
864
+ '--write-baseline',
865
+ ]);
866
+ const reviewInputs = [];
867
+ for (let i = 0; i < args.length; i++) {
868
+ const arg = args[i];
869
+ if (arg === 'review')
870
+ continue;
871
+ if (flagsWithValues.has(arg)) {
872
+ i++;
873
+ continue;
874
+ }
875
+ if (arg.startsWith('--'))
876
+ continue;
877
+ reviewInputs.push(arg);
878
+ }
668
879
  let reviewInput = reviewInputs[0];
880
+ const remoteUrl = parseFlagOrNext(args, '--git');
881
+ if (remoteUrl && !reviewInput && !diffBase) {
882
+ reviewInput = '.';
883
+ }
884
+ // Phase 5: diff-scoped by default.
885
+ // Bare `kern review` (no path, no --diff, no --full, no --git) inside a git
886
+ // repo defaults to reviewing changes vs the upstream branch. `--full` opts
887
+ // back into a cwd-wide scan. Explicit paths are unchanged — `kern review
888
+ // src/` still scans src/ in full. This keeps `kern review` quiet by default
889
+ // on large codebases without breaking CI invocations that pass a path.
890
+ if (!reviewInput && !diffBase && !remoteUrl && !fullMode) {
891
+ const autoBase = detectAutoDiffBase();
892
+ if (autoBase) {
893
+ diffBase = autoBase;
894
+ if (!hasFlag(args, '--json') && !hasFlag(args, '--sarif')) {
895
+ console.log(` No path given — reviewing changes vs ${autoBase}. Use --full to scan the whole tree, or pass a path.\n`);
896
+ }
897
+ }
898
+ }
899
+ if (fullMode && !reviewInput) {
900
+ reviewInput = '.';
901
+ }
669
902
  if (diffBase && !reviewInput) {
670
903
  try {
671
904
  const { execFileSync } = await import('child_process');
@@ -675,13 +908,17 @@ export async function runReview(args) {
675
908
  })
676
909
  .trim()
677
910
  .split('\n')
678
- .filter((f) => f.endsWith('.ts') || f.endsWith('.tsx'))
911
+ .filter((f) => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.kern'))
679
912
  .filter((f) => !f.endsWith('.d.ts') && !f.endsWith('.test.ts'));
913
+ const machineOutput = hasFlag(args, '--json') || hasFlag(args, '--sarif');
680
914
  if (diffFiles.length === 0) {
681
- console.log(` No changed .ts/.tsx files since ${diffBase}`);
915
+ if (!machineOutput)
916
+ console.log(` No changed .ts/.tsx/.kern files since ${diffBase}`);
682
917
  process.exit(0);
683
918
  }
684
- console.log(` Reviewing ${diffFiles.length} changed files (diff from ${diffBase})\n`);
919
+ if (!machineOutput) {
920
+ console.log(` Reviewing ${diffFiles.length} changed files (diff from ${diffBase})\n`);
921
+ }
685
922
  const diffRanges = new Map();
686
923
  try {
687
924
  const unifiedDiff = execFileSync('git', ['diff', '--unified=0', '--diff-filter=ACMR', sanitizedBase], {
@@ -720,8 +957,11 @@ export async function runReview(args) {
720
957
  }
721
958
  }
722
959
  if (!reviewInput) {
723
- console.error('Usage: kern review <file.ts|dir> [--security] [--mcp] [--llm] [--spec file.kern] [--cloud]');
724
- console.error(' [--diff base] [--json] [--sarif] [--recursive] [--enforce] [--strict-parse] [--fix] [--autofix] [--rules-dir <dir>]');
960
+ console.error('Usage: kern review [file|dir] [--full] [--diff base] [--git=<url>] [--security] [--mcp] [--llm] [--spec file.kern] [--cloud] [--baseline=file.json] [--new-only]');
961
+ console.error(' [--write-baseline=file.json] [--json] [--sarif] [--recursive] [--enforce] [--strict-parse] [--fix] [--autofix] [--rules-dir <dir>] [--include-generated]');
962
+ console.error('');
963
+ console.error(' Default (inside git): reviews changes vs origin/main. Use --full to scan the whole tree.');
964
+ console.error(' Default skips generated files (src/generated/, files with @generated stamps). Use --include-generated to audit them.');
725
965
  process.exit(1);
726
966
  }
727
967
  if (reviewInput !== '__diff__') {
@@ -750,6 +990,7 @@ export async function runReview(args) {
750
990
  minCoverage: minCoverage ?? 0,
751
991
  enforceTemplates: enforce,
752
992
  maxComplexity: maxComplexity ?? reviewCfg.review.maxComplexity,
993
+ maxHandlerLines,
753
994
  maxErrors,
754
995
  maxWarnings,
755
996
  target: reviewCfg.target,
@@ -759,6 +1000,14 @@ export async function runReview(args) {
759
1000
  rulesDirs: rulesDirs.length > 0 ? rulesDirs : undefined,
760
1001
  strict,
761
1002
  strictParse,
1003
+ tsConfigFilePath: tsconfigPath,
1004
+ publicApi: reviewCfg.review.publicApi.files.length > 0 || reviewCfg.review.publicApi.symbols.length > 0
1005
+ ? {
1006
+ files: reviewCfg.review.publicApi.files,
1007
+ symbols: reviewCfg.review.publicApi.symbols,
1008
+ projectRoot: process.cwd(),
1009
+ }
1010
+ : undefined,
762
1011
  };
763
1012
  // Load templates for review
764
1013
  if (reviewCfg.templates && reviewCfg.templates.length > 0) {
@@ -831,6 +1080,7 @@ export async function runReview(args) {
831
1080
  fixMode,
832
1081
  autofixMode,
833
1082
  lintMode,
1083
+ skipGenerated,
834
1084
  exportKern,
835
1085
  enforce,
836
1086
  jsonOutput,
@@ -845,6 +1095,9 @@ export async function runReview(args) {
845
1095
  maxErrorsArg,
846
1096
  maxWarningsArg,
847
1097
  showConfidence,
1098
+ baseline,
1099
+ writeBaselinePath: writeBaselinePath ? resolve(writeBaselinePath) : undefined,
1100
+ newOnly,
848
1101
  };
849
1102
  const noCache = hasFlag(args, '--no-cache');
850
1103
  if (noCache) {
@@ -879,4 +1132,15 @@ export async function runReview(args) {
879
1132
  process.exit(result.exitCode);
880
1133
  }
881
1134
  }
1135
+ export async function runReview(args) {
1136
+ const diffBase = args.some((a) => a === '--diff' || a.startsWith('--diff'))
1137
+ ? parseFlagOrNext(args, '--diff') || 'origin/main'
1138
+ : undefined;
1139
+ await withOptionalRemoteRepo(args, {
1140
+ commandName: 'review',
1141
+ fullClone: Boolean(diffBase),
1142
+ }, async () => {
1143
+ await runReviewLocal(args);
1144
+ });
1145
+ }
882
1146
  //# sourceMappingURL=review.js.map