seaworthycode 1.2.6 → 1.2.7

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/dist/index.js CHANGED
@@ -64,7 +64,7 @@ import { createHash as createHash5, randomBytes } from "crypto";
64
64
  import * as fs8 from "fs";
65
65
  import { join as join11 } from "path";
66
66
  import { createHash as createHash6 } from "crypto";
67
- import { Query as TSQuery2 } from "web-tree-sitter";
67
+ import { Parser as TSParser, Query as TSQuery2 } from "web-tree-sitter";
68
68
  import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync4 } from "fs";
69
69
  import { dirname as dirname3, resolve as pathResolve3 } from "path";
70
70
  import { parse as parseYaml } from "yaml";
@@ -1497,7 +1497,10 @@ var copy = {
1497
1497
  "remediation.resilience.orm-n-plus-one": "Replace per-row queries inside loops with a single batch query using include/eager loading (Prisma include, Sequelize eager loading, SQLAlchemy joinedload, ActiveRecord includes).",
1498
1498
  // Reporting labels
1499
1499
  "reporting.analysisType.pattern": "pattern",
1500
- "reporting.analysisType.semantic": "semantic"
1500
+ "reporting.analysisType.semantic": "semantic",
1501
+ // Collapsed low/info finding groups
1502
+ "reporting.collapsed.summary": (count, files) => `${count} findings across ${files} ${files === 1 ? "file" : "files"}`,
1503
+ "reporting.collapsed.hint": "Run with --all to list each finding, or --output report.html for full detail."
1501
1504
  };
1502
1505
  function getCopy(key, ...args) {
1503
1506
  const value = copy[key];
@@ -1613,7 +1616,8 @@ var ScanRunner = class {
1613
1616
  files,
1614
1617
  meta,
1615
1618
  taintCache: /* @__PURE__ */ new Map(),
1616
- astCache: /* @__PURE__ */ new Map()
1619
+ astCache: /* @__PURE__ */ new Map(),
1620
+ bashAstCache: /* @__PURE__ */ new Map()
1617
1621
  };
1618
1622
  const results = [];
1619
1623
  const checkResults = /* @__PURE__ */ new Map();
@@ -1646,18 +1650,15 @@ var ScanRunner = class {
1646
1650
  }
1647
1651
  }
1648
1652
  }
1649
- if (ctx.astCache) {
1650
- for (const promise of ctx.astCache.values()) {
1651
- try {
1652
- const { tree } = await promise;
1653
- if (tree) {
1654
- tree.delete();
1655
- }
1656
- } catch {
1657
- }
1653
+ ctx.astCache?.clear();
1654
+ for (const [, promise] of ctx.bashAstCache ?? []) {
1655
+ try {
1656
+ const parsed = await promise;
1657
+ parsed.tree.delete();
1658
+ } catch {
1658
1659
  }
1659
- ctx.astCache.clear();
1660
1660
  }
1661
+ ctx.bashAstCache?.clear();
1661
1662
  let findings = sortFindings(
1662
1663
  dedupeDegradedFindings(dedupeCrossCheck(dedupeFindings(results)))
1663
1664
  );
@@ -2427,6 +2428,7 @@ var text = {
2427
2428
  success: (s) => source_default.hex(tokens.success)(s)
2428
2429
  };
2429
2430
  var SEVERITY_ORDER2 = ["critical", "high", "medium", "low", "info"];
2431
+ var COLLAPSIBLE_SEVERITIES = /* @__PURE__ */ new Set(["low", "info"]);
2430
2432
  function checkNameOf(checkId) {
2431
2433
  const short = checkId.replace(/^[^.]+\./, "");
2432
2434
  try {
@@ -2435,9 +2437,49 @@ function checkNameOf(checkId) {
2435
2437
  return short;
2436
2438
  }
2437
2439
  }
2440
+ function analysisTypeSuffix(f) {
2441
+ const rawAnalysisType = f.properties?.analysisType;
2442
+ const analysisTypeValue = rawAnalysisType === "pattern" || rawAnalysisType === "semantic" ? rawAnalysisType : void 0;
2443
+ return analysisTypeValue ? text.meta(` [${getCopy(`reporting.analysisType.${analysisTypeValue}`)}]`) : "";
2444
+ }
2445
+ function renderFinding(f, showIds) {
2446
+ const severityLabel = textForSeverity(f.severity);
2447
+ const baselineLabel = f.baselineState === "new" ? text.success(" [NEW]") : "";
2448
+ const loc = f.file ? text.meta(` (${f.file}:${f.line ?? "-"})`) : "";
2449
+ const confidenceLabel = f.confidence && f.confidence !== "high" ? text.meta(` [${f.confidence} confidence]`) : "";
2450
+ const analysisType = analysisTypeSuffix(f);
2451
+ const name = checkNameOf(f.checkId);
2452
+ const idSuffix = showIds ? text.meta(` \xB7 ${f.checkId}`) : "";
2453
+ const lines = [];
2454
+ lines.push(`${severityLabel}${baselineLabel} ${name}${idSuffix}${analysisType} \u2014 ${f.message}${loc}${confidenceLabel}`);
2455
+ if (f.remediation) {
2456
+ lines.push(` ${text.meta(`\u2192 ${f.remediation}`)}`);
2457
+ }
2458
+ if (f.properties?.suggestion) {
2459
+ lines.push(` ${text.meta(`\u{1F4A1} ${f.properties.suggestion}`)}`);
2460
+ }
2461
+ return lines;
2462
+ }
2463
+ function renderCollapsedGroup(group, showIds) {
2464
+ const first = group[0];
2465
+ const severityLabel = textForSeverity(first.severity);
2466
+ const analysisType = analysisTypeSuffix(first);
2467
+ const name = checkNameOf(first.checkId);
2468
+ const idSuffix = showIds ? text.meta(` \xB7 ${first.checkId}`) : "";
2469
+ const total = group.length;
2470
+ const fileCount = new Set(group.map((f) => f.file).filter(Boolean)).size;
2471
+ const countPhrase = getCopy("reporting.collapsed.summary", total, fileCount);
2472
+ const lines = [];
2473
+ lines.push(`${severityLabel} ${name}${idSuffix}${analysisType} \u2014 ${countPhrase}`);
2474
+ if (first.remediation) {
2475
+ lines.push(` ${text.meta(`\u2192 ${first.remediation}`)}`);
2476
+ }
2477
+ lines.push(` ${text.meta(`\u2192 ${getCopy("reporting.collapsed.hint")}`)}`);
2478
+ return lines;
2479
+ }
2438
2480
  var TerminalReporter = class {
2439
2481
  generate(input) {
2440
- const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], showIds = false } = input;
2482
+ const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], showIds = false, all = false } = input;
2441
2483
  const findings = dedupeDegradedFindings(rawFindings);
2442
2484
  if (findings.length === 0) {
2443
2485
  const lines2 = [];
@@ -2456,22 +2498,30 @@ var TerminalReporter = class {
2456
2498
  const lines = [];
2457
2499
  lines.push(text.heading(getCopy("report.header", targetDir, findings.length)));
2458
2500
  lines.push("");
2459
- for (const f of findings) {
2460
- const severityLabel = textForSeverity(f.severity);
2461
- const baselineLabel = f.baselineState === "new" ? text.success(" [NEW]") : "";
2462
- const loc = f.file ? text.meta(` (${f.file}:${f.line ?? "-"})`) : "";
2463
- const confidenceLabel = f.confidence && f.confidence !== "high" ? text.meta(` [${f.confidence} confidence]`) : "";
2464
- const rawAnalysisType = f.properties?.analysisType;
2465
- const analysisTypeValue = rawAnalysisType === "pattern" || rawAnalysisType === "semantic" ? rawAnalysisType : void 0;
2466
- const analysisType = analysisTypeValue ? text.meta(` [${getCopy(`reporting.analysisType.${analysisTypeValue}`)}]`) : "";
2467
- const name = checkNameOf(f.checkId);
2468
- const idSuffix = showIds ? text.meta(` \xB7 ${f.checkId}`) : "";
2469
- lines.push(`${severityLabel}${baselineLabel} ${name}${idSuffix}${analysisType} \u2014 ${f.message}${loc}${confidenceLabel}`);
2470
- if (f.remediation) {
2471
- lines.push(` ${text.meta(`\u2192 ${f.remediation}`)}`);
2501
+ if (all) {
2502
+ for (const f of findings) {
2503
+ lines.push(...renderFinding(f, showIds));
2472
2504
  }
2473
- if (f.properties?.suggestion) {
2474
- lines.push(` ${text.meta(`\u{1F4A1} ${f.properties.suggestion}`)}`);
2505
+ } else {
2506
+ const collapsibleGroups = /* @__PURE__ */ new Map();
2507
+ for (const f of findings) {
2508
+ if (COLLAPSIBLE_SEVERITIES.has(f.severity)) {
2509
+ const group = collapsibleGroups.get(f.checkId);
2510
+ if (group) {
2511
+ group.push(f);
2512
+ } else {
2513
+ collapsibleGroups.set(f.checkId, [f]);
2514
+ }
2515
+ } else {
2516
+ lines.push(...renderFinding(f, showIds));
2517
+ }
2518
+ }
2519
+ for (const group of collapsibleGroups.values()) {
2520
+ if (group.length === 1) {
2521
+ lines.push(...renderFinding(group[0], showIds));
2522
+ } else {
2523
+ lines.push(...renderCollapsedGroup(group, showIds));
2524
+ }
2475
2525
  }
2476
2526
  }
2477
2527
  lines.push("");
@@ -3582,7 +3632,56 @@ function getOrCreateParser(lang, langKey) {
3582
3632
  }
3583
3633
  return parser;
3584
3634
  }
3585
- async function queryFile(file, queryString, astCache) {
3635
+ async function resolveContent(file, contentCache) {
3636
+ if (contentCache) {
3637
+ const cacheKey = `${file.path}::${file.language}`;
3638
+ let contentPromise = contentCache.get(cacheKey);
3639
+ if (!contentPromise) {
3640
+ contentPromise = file.content().catch(() => {
3641
+ throw new Error("read_failed");
3642
+ });
3643
+ contentCache.set(cacheKey, contentPromise);
3644
+ }
3645
+ return contentPromise;
3646
+ }
3647
+ return file.content();
3648
+ }
3649
+ async function parseFile(file, contentCache) {
3650
+ if (!isLanguageSupported(file.language)) return null;
3651
+ const wasmFilename = LANGUAGE_WASM_MAP[file.language];
3652
+ if (!wasmFilename) return null;
3653
+ try {
3654
+ await ensureParserInit();
3655
+ } catch {
3656
+ return null;
3657
+ }
3658
+ let lang;
3659
+ try {
3660
+ lang = await loadLanguage(file.language);
3661
+ } catch {
3662
+ return null;
3663
+ }
3664
+ if (languageVerified.get(file.language) === false) return null;
3665
+ let source;
3666
+ try {
3667
+ source = await resolveContent(file, contentCache);
3668
+ } catch {
3669
+ return null;
3670
+ }
3671
+ if (source.length === 0 || source.length > MAX_PARSE_BYTES) return null;
3672
+ const freshParser = new Parser();
3673
+ freshParser.setLanguage(lang);
3674
+ try {
3675
+ const tree = freshParser.parse(source);
3676
+ freshParser.delete();
3677
+ if (!tree) return null;
3678
+ return { tree, source };
3679
+ } catch {
3680
+ freshParser.delete();
3681
+ return null;
3682
+ }
3683
+ }
3684
+ async function queryFile(file, queryString, astCache, bashAstCache) {
3586
3685
  if (!isLanguageSupported(file.language)) {
3587
3686
  return { matches: [], degraded: true };
3588
3687
  }
@@ -3610,32 +3709,30 @@ async function queryFile(file, queryString, astCache) {
3610
3709
  return { matches: [], degraded: true, error: `languageVerified is false for ${file.language}` };
3611
3710
  }
3612
3711
  let source;
3712
+ try {
3713
+ source = await resolveContent(file, astCache);
3714
+ } catch {
3715
+ return { matches: [], degraded: true };
3716
+ }
3717
+ if (source.length === 0) {
3718
+ return { matches: [], degraded: false };
3719
+ }
3720
+ if (source.length > MAX_PARSE_BYTES) {
3721
+ return { matches: [], degraded: true };
3722
+ }
3613
3723
  let tree;
3614
- if (astCache) {
3724
+ let ownTree = false;
3725
+ if (file.language === "bash" && bashAstCache) {
3615
3726
  const cacheKey = `${file.path}::${file.language}`;
3616
- let parsePromise = astCache.get(cacheKey);
3727
+ let parsePromise = bashAstCache.get(cacheKey);
3617
3728
  if (!parsePromise) {
3618
3729
  parsePromise = (async () => {
3619
- let s;
3620
- try {
3621
- s = await file.content();
3622
- } catch {
3623
- throw new Error("read_failed");
3624
- }
3625
- if (s.length === 0) {
3626
- return { tree: null, source: s };
3627
- }
3628
- if (s.length > MAX_PARSE_BYTES) {
3629
- throw new Error("file_too_large");
3630
- }
3631
3730
  const parser = getOrCreateParser(lang, file.language);
3632
- const parsed2 = parser.parse(s);
3633
- if (!parsed2) {
3634
- throw new Error("parse_failed");
3635
- }
3636
- return { tree: parsed2, source: s };
3731
+ const parsed2 = parser.parse(source);
3732
+ if (!parsed2) throw new Error("parse_failed");
3733
+ return { tree: parsed2, source };
3637
3734
  })();
3638
- astCache.set(cacheKey, parsePromise);
3735
+ bashAstCache.set(cacheKey, parsePromise);
3639
3736
  }
3640
3737
  let parsed;
3641
3738
  try {
@@ -3643,31 +3740,20 @@ async function queryFile(file, queryString, astCache) {
3643
3740
  } catch {
3644
3741
  return { matches: [], degraded: true };
3645
3742
  }
3646
- if (parsed.source.length === 0) {
3647
- return { matches: [], degraded: false };
3648
- }
3649
- source = parsed.source;
3650
3743
  tree = parsed.tree;
3651
3744
  } else {
3745
+ ownTree = true;
3746
+ const freshParser = new Parser();
3747
+ freshParser.setLanguage(lang);
3652
3748
  try {
3653
- source = await file.content();
3654
- } catch {
3655
- return { matches: [], degraded: true };
3656
- }
3657
- if (source.length === 0) {
3658
- return { matches: [], degraded: false };
3659
- }
3660
- if (source.length > MAX_PARSE_BYTES) {
3661
- return { matches: [], degraded: true };
3662
- }
3663
- try {
3664
- const parser = getOrCreateParser(lang, file.language);
3665
- const parsed = parser.parse(source);
3749
+ const parsed = freshParser.parse(source);
3750
+ freshParser.delete();
3666
3751
  if (!parsed) {
3667
3752
  return { matches: [], degraded: true };
3668
3753
  }
3669
3754
  tree = parsed;
3670
3755
  } catch {
3756
+ freshParser.delete();
3671
3757
  return { matches: [], degraded: true };
3672
3758
  }
3673
3759
  }
@@ -3679,7 +3765,7 @@ async function queryFile(file, queryString, astCache) {
3679
3765
  query = new TSQuery(lang, queryString);
3680
3766
  compiledQueryCache.set(queryCacheKey, query);
3681
3767
  } catch (err) {
3682
- tree.delete();
3768
+ if (ownTree) tree.delete();
3683
3769
  console.error(
3684
3770
  `[seaworthy] Invalid query for ${file.language}:`,
3685
3771
  err.message
@@ -3729,9 +3815,7 @@ async function queryFile(file, queryString, astCache) {
3729
3815
  }
3730
3816
  return true;
3731
3817
  });
3732
- if (!astCache) {
3733
- tree.delete();
3734
- }
3818
+ if (ownTree) tree.delete();
3735
3819
  return { matches, degraded: false };
3736
3820
  }
3737
3821
  function setAnalysisType(finding, analysisType = "pattern") {
@@ -3785,7 +3869,7 @@ function createTreeSitterCheck(options) {
3785
3869
  async (file) => {
3786
3870
  const query = queries[file.language];
3787
3871
  try {
3788
- const result = await queryFile(file, query, ctx.astCache);
3872
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
3789
3873
  if (result.degraded) {
3790
3874
  if (!degradedLanguages.has(file.language)) {
3791
3875
  degradedLanguages.add(file.language);
@@ -11870,36 +11954,21 @@ function runAnalyse(file, table, fileContent, astCache) {
11870
11954
  }
11871
11955
  if (content.length === 0) return { flows: [], degraded: false };
11872
11956
  let tree;
11873
- let ownTree = false;
11957
+ const ownTree = true;
11874
11958
  const cacheKey = `${file.path}::${file.language}`;
11875
- if (astCache) {
11876
- let cached = astCache.get(cacheKey);
11877
- if (!cached) {
11878
- cached = (async () => {
11879
- const parser = getOrCreateParser(lang, file.language);
11880
- const parsed = parser.parse(content);
11881
- if (!parsed) throw new Error("parse failed");
11882
- return { tree: parsed, source: content };
11883
- })();
11884
- astCache.set(cacheKey, cached);
11885
- ownTree = false;
11886
- }
11887
- try {
11888
- const r = await cached;
11889
- tree = r.tree;
11890
- } catch {
11891
- return { flows: [], degraded: true };
11892
- }
11893
- } else {
11894
- try {
11895
- const parser = getOrCreateParser(lang, file.language);
11896
- const parsed = parser.parse(content);
11897
- if (!parsed) return { flows: [], degraded: true };
11898
- tree = parsed;
11899
- ownTree = true;
11900
- } catch {
11901
- return { flows: [], degraded: true };
11902
- }
11959
+ if (astCache && !astCache.has(cacheKey)) {
11960
+ astCache.set(cacheKey, Promise.resolve(content));
11961
+ }
11962
+ const freshParser = new TSParser();
11963
+ freshParser.setLanguage(lang);
11964
+ try {
11965
+ const parsed = freshParser.parse(content);
11966
+ freshParser.delete();
11967
+ if (!parsed) return { flows: [], degraded: true };
11968
+ tree = parsed;
11969
+ } catch {
11970
+ freshParser.delete();
11971
+ return { flows: [], degraded: true };
11903
11972
  }
11904
11973
  const entries = collectEntries(table);
11905
11974
  const taggedByNode = /* @__PURE__ */ new Map();
@@ -12833,7 +12902,7 @@ var check = {
12833
12902
  const _exampleLine = 1;
12834
12903
  for (const file of eligible) {
12835
12904
  const query = QUERIES9[file.language];
12836
- const result = await queryFile(file, query, ctx.astCache);
12905
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
12837
12906
  if (result.degraded) {
12838
12907
  if (!degradedLanguages.has(file.language)) {
12839
12908
  degradedLanguages.add(file.language);
@@ -13836,7 +13905,7 @@ var sqlRlsStaticCheck = {
13836
13905
  const dialect = classifySqlDialect(content);
13837
13906
  if (dialect !== "postgres" && dialect !== "supabase") continue;
13838
13907
  const tableQuery = "(create_table (object_reference name: (identifier) @table_name))";
13839
- const tableResult = await queryFile(file, tableQuery, ctx.astCache);
13908
+ const tableResult = await queryFile(file, tableQuery, ctx.astCache, ctx.bashAstCache);
13840
13909
  if (tableResult.degraded) continue;
13841
13910
  const tables = [];
13842
13911
  for (const match of tableResult.matches) {
@@ -13846,7 +13915,7 @@ var sqlRlsStaticCheck = {
13846
13915
  const columnQuery = `(create_table
13847
13916
  (object_reference name: (identifier) @t_name (#eq? @t_name "${tableNameRaw}"))
13848
13917
  (column_definitions (column_definition name: (identifier) @col_name)))`;
13849
- const columnResult = await queryFile(file, columnQuery, ctx.astCache);
13918
+ const columnResult = await queryFile(file, columnQuery, ctx.astCache, ctx.bashAstCache);
13850
13919
  const columns = columnResult.matches.map((m) => normalizeIdentifier(m.captures.col_name ?? ""));
13851
13920
  const tenantColumns = ["tenant_id", "org_id", "workspace_id", "account_id", "user_id"];
13852
13921
  const isTenantTable = columns.some((c) => tenantColumns.includes(c));
@@ -14295,7 +14364,7 @@ var unprotectedSensitiveSqlColumnsCheck = {
14295
14364
  (identifier) @col_type
14296
14365
  (keyword_text) @col_type
14297
14366
  ])`;
14298
- const result = await queryFile(file, query, ctx.astCache);
14367
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
14299
14368
  if (result.degraded) continue;
14300
14369
  for (const match of result.matches) {
14301
14370
  const colNameRaw = match.captures.col_name;
@@ -14394,7 +14463,7 @@ registry.register({
14394
14463
  continue;
14395
14464
  }
14396
14465
  const lines = code.split("\n");
14397
- const queryResult = await queryFile(file, "(unsafe_block) @unsafe", ctx.astCache);
14466
+ const queryResult = await queryFile(file, "(unsafe_block) @unsafe", ctx.astCache, ctx.bashAstCache);
14398
14467
  if (queryResult.degraded) continue;
14399
14468
  for (let i = 0; i < queryResult.matches.length; i++) {
14400
14469
  const match = queryResult.matches[i];
@@ -14546,50 +14615,51 @@ registry.register({
14546
14615
  }
14547
14616
  const isWebFile = /axum|actix/i.test(code);
14548
14617
  if (!isWebFile) continue;
14549
- const queryResult = await queryFile(file, RUST_QUERY, ctx.astCache);
14618
+ const queryResult = await queryFile(file, RUST_QUERY, ctx.astCache, ctx.bashAstCache);
14550
14619
  if (queryResult.degraded) continue;
14551
- const cacheKey = `${file.path}::${file.language}`;
14552
- const parsePromise = ctx.astCache?.get(cacheKey);
14553
- if (!parsePromise) continue;
14554
- const parsed = await parsePromise;
14555
- if (!parsed || !parsed.tree) continue;
14620
+ const parsed = await parseFile(file, ctx.astCache);
14621
+ if (!parsed) continue;
14556
14622
  const parentMap = /* @__PURE__ */ new Map();
14557
14623
  walk2(parsed.tree.rootNode);
14558
- for (let i = 0; i < queryResult.matches.length; i++) {
14559
- const match = queryResult.matches[i];
14560
- if (!match) continue;
14561
- const node = findNodeAt2(parsed.tree.rootNode, match.startLine, match.startColumn ?? 0);
14562
- if (!node) continue;
14563
- let curr = node;
14564
- let enclosingFunction = null;
14565
- while (curr) {
14566
- if (curr.type === "function_item") {
14567
- enclosingFunction = curr;
14568
- break;
14624
+ try {
14625
+ for (let i = 0; i < queryResult.matches.length; i++) {
14626
+ const match = queryResult.matches[i];
14627
+ if (!match) continue;
14628
+ const node = findNodeAt2(parsed.tree.rootNode, match.startLine, match.startColumn ?? 0);
14629
+ if (!node) continue;
14630
+ let curr = node;
14631
+ let enclosingFunction = null;
14632
+ while (curr) {
14633
+ if (curr.type === "function_item") {
14634
+ enclosingFunction = curr;
14635
+ break;
14636
+ }
14637
+ const next = parentMap.get(curr.id);
14638
+ if (!next) break;
14639
+ curr = next;
14569
14640
  }
14570
- const next = parentMap.get(curr.id);
14571
- if (!next) break;
14572
- curr = next;
14573
- }
14574
- if (enclosingFunction) {
14575
- const isAsync = hasAsyncModifier(enclosingFunction);
14576
- if (isAsync) {
14577
- const finding = {
14578
- id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14579
- checkId: CHECK_ID56,
14580
- category: "resilience",
14581
- severity: "high",
14582
- message: getCopy("checks.rust-handler-panics.message", match.nodeText),
14583
- file: file.relativePath,
14584
- line: match.startLine,
14585
- column: match.startColumn,
14586
- confidence: "high",
14587
- remediation
14588
- };
14589
- setAnalysisType(finding, "pattern");
14590
- findings.push(finding);
14641
+ if (enclosingFunction) {
14642
+ const isAsync = hasAsyncModifier(enclosingFunction);
14643
+ if (isAsync) {
14644
+ const finding = {
14645
+ id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14646
+ checkId: CHECK_ID56,
14647
+ category: "resilience",
14648
+ severity: "high",
14649
+ message: getCopy("checks.rust-handler-panics.message", match.nodeText),
14650
+ file: file.relativePath,
14651
+ line: match.startLine,
14652
+ column: match.startColumn,
14653
+ confidence: "high",
14654
+ remediation
14655
+ };
14656
+ setAnalysisType(finding, "pattern");
14657
+ findings.push(finding);
14658
+ }
14591
14659
  }
14592
14660
  }
14661
+ } finally {
14662
+ parsed.tree.delete();
14593
14663
  }
14594
14664
  }
14595
14665
  return findings;
@@ -16013,7 +16083,8 @@ async function executeScan(args, ctx) {
16013
16083
  licensedTo: ctx.licensedTo ?? "free tier",
16014
16084
  scannedFileCount: result.scannedFileCount,
16015
16085
  skippedChecks: result.skippedChecks,
16016
- showIds: args.showIds ?? args.debug
16086
+ showIds: args.showIds ?? args.debug,
16087
+ all: args.all
16017
16088
  };
16018
16089
  let primaryOutput;
16019
16090
  switch (args.format) {
@@ -16358,7 +16429,8 @@ async function runCli(options) {
16358
16429
  debug: options.debug,
16359
16430
  baseline: options.baseline,
16360
16431
  minConfidence: options.minConfidence,
16361
- showIds: options.showIds
16432
+ showIds: options.showIds,
16433
+ all: options.all
16362
16434
  };
16363
16435
  const updateStart = Date.now();
16364
16436
  const updatePromise = checkUpdate().catch(() => void 0);
@@ -16535,12 +16607,12 @@ var require5 = createRequire4(import.meta.url);
16535
16607
  var { version } = require5("../package.json");
16536
16608
  var cli = cac("seaworthy");
16537
16609
  cli.version(version);
16538
- cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, moonshot, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--show-ids", "Show check IDs alongside names in terminal output").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").action(async (targetDir, options) => {
16610
+ cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, moonshot, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--show-ids", "Show check IDs alongside names in terminal output").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").option("--all", "List every finding; do not collapse low-severity findings").action(async (targetDir, options) => {
16539
16611
  const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;
16540
16612
  const tier = options.tier;
16541
16613
  const baseline = options.baseline === "save" ? "save" : options.baseline;
16542
16614
  const minConfidence = options.minConfidence;
16543
- const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, showIds: options.showIds, debug: options.debug, baseline, minConfidence });
16615
+ const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, showIds: options.showIds, debug: options.debug, baseline, minConfidence, all: options.all });
16544
16616
  if (result.updateMessage) {
16545
16617
  console.error(result.updateMessage);
16546
16618
  }
@@ -16609,6 +16681,7 @@ cli.help(() => {
16609
16681
  --baseline [ref] Compare against baseline file or save (--baseline save)
16610
16682
  --min-confidence <level> Hide findings below confidence (high, medium, low)
16611
16683
  --show-ids Show check IDs alongside names
16684
+ --all List every finding; do not collapse low-severity findings
16612
16685
  --debug Enable debug logging`
16613
16686
  },
16614
16687
  {