seaworthycode 1.2.16 → 1.2.17

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
@@ -407,10 +407,11 @@ async function crawl(options) {
407
407
  const { isBinary, isGenerated } = await peekFileHead(fullPath);
408
408
  if (isBinary || isGenerated) continue;
409
409
  if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
410
+ let cachedContent;
410
411
  files.push({
411
412
  path: fullPath,
412
413
  relativePath: relPath,
413
- content: () => fs.readFile(fullPath, "utf-8"),
414
+ content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
414
415
  language: inferLanguage(entry.name),
415
416
  size: stat2.size
416
417
  });
@@ -428,10 +429,11 @@ async function crawl(options) {
428
429
  const { isBinary, isGenerated } = await peekFileHead(fullPath);
429
430
  if (isBinary || isGenerated) continue;
430
431
  if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
432
+ let cachedContent;
431
433
  files.push({
432
434
  path: fullPath,
433
435
  relativePath: relPath,
434
- content: () => fs.readFile(fullPath, "utf-8"),
436
+ content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
435
437
  language: inferLanguage(entry.name),
436
438
  size: stat2.size
437
439
  });
@@ -5985,6 +5987,14 @@ function getMaxFilesPerLlmCheck() {
5985
5987
  }
5986
5988
  return 150;
5987
5989
  }
5990
+ function getMechanicalMaxFiles() {
5991
+ const env2 = process.env.SEAWORTHY_MECHANICAL_MAX_FILES;
5992
+ if (env2 !== void 0) {
5993
+ const n = parseInt(env2, 10);
5994
+ if (!isNaN(n) && n > 0) return n;
5995
+ }
5996
+ return 2e3;
5997
+ }
5988
5998
  function withLLMTimeout(promise, ms) {
5989
5999
  return new Promise((resolve22, reject) => {
5990
6000
  const timerId = setTimeout(() => {
@@ -6028,14 +6038,27 @@ function downgradeSeverity(severity) {
6028
6038
  if (idx < 0 || idx >= SEVERITY_ORDER3.length - 1) return severity;
6029
6039
  return SEVERITY_ORDER3[idx + 1];
6030
6040
  }
6031
- function filterFilesByBudget(files, budget) {
6041
+ function estimateTokens(text22) {
6042
+ return Math.ceil(text22.length / 4);
6043
+ }
6044
+ async function filterFilesByBudget(files, budget) {
6032
6045
  if (budget === void 0 || budget === Infinity) {
6033
6046
  return files;
6034
6047
  }
6035
- return files.filter((file) => {
6036
- const tokens2 = file.size !== void 0 ? Math.ceil(file.size / 4) : budget;
6037
- return tokens2 <= budget;
6038
- });
6048
+ const result = [];
6049
+ for (const file of files) {
6050
+ let tokens2;
6051
+ if (file.size !== void 0) {
6052
+ tokens2 = Math.ceil(file.size / 4);
6053
+ } else {
6054
+ const content = await file.content();
6055
+ tokens2 = estimateTokens(content);
6056
+ }
6057
+ if (tokens2 <= budget) {
6058
+ result.push(file);
6059
+ }
6060
+ }
6061
+ return result;
6039
6062
  }
6040
6063
  async function buildCacheKey(ctx, template, files) {
6041
6064
  if (!ctx.llm) return void 0;
@@ -6086,7 +6109,9 @@ function capProjectScopedFindings(findings) {
6086
6109
  }
6087
6110
  async function runProjectScopedMechanical(eligibleFiles, template) {
6088
6111
  if (eligibleFiles.length === 0) return [];
6089
- const contents = await Promise.all(eligibleFiles.map((f) => f.content()));
6112
+ const maxFiles = getMechanicalMaxFiles();
6113
+ const scanFiles = eligibleFiles.length > maxFiles ? eligibleFiles.slice(0, maxFiles) : eligibleFiles;
6114
+ const contents = await Promise.all(scanFiles.map((f) => f.content()));
6090
6115
  const combined = contents.join("\n");
6091
6116
  let shouldFlag = false;
6092
6117
  const pattern = template.fallbackPatterns[0];
@@ -6113,13 +6138,23 @@ async function runProjectAbsenceChecks(eligibleFiles, template) {
6113
6138
  if (absencePatterns.length === 0 || eligibleFiles.length === 0) {
6114
6139
  return [];
6115
6140
  }
6116
- const contents = await Promise.all(eligibleFiles.map((f) => f.content()));
6141
+ const maxFiles = getMechanicalMaxFiles();
6142
+ const scanFiles = eligibleFiles.length > maxFiles ? eligibleFiles.slice(0, maxFiles) : eligibleFiles;
6117
6143
  const anchorFile = pickProjectAnchorFile(eligibleFiles);
6144
+ const foundIndices = /* @__PURE__ */ new Set();
6145
+ for (const file of scanFiles) {
6146
+ if (foundIndices.size === absencePatterns.length) break;
6147
+ const content = await file.content();
6148
+ for (let i = 0; i < absencePatterns.length; i++) {
6149
+ if (!foundIndices.has(i) && absencePatterns[i].regex.test(content)) {
6150
+ foundIndices.add(i);
6151
+ }
6152
+ }
6153
+ }
6118
6154
  const results = [];
6119
- for (const pattern of absencePatterns) {
6120
- const foundAnywhere = contents.some((content) => pattern.regex.test(content));
6121
- if (!foundAnywhere) {
6122
- results.push(makeMechanicalFinding(template, pattern, anchorFile, 1));
6155
+ for (let i = 0; i < absencePatterns.length; i++) {
6156
+ if (!foundIndices.has(i)) {
6157
+ results.push(makeMechanicalFinding(template, absencePatterns[i], anchorFile, 1));
6123
6158
  }
6124
6159
  }
6125
6160
  return results;
@@ -6131,8 +6166,16 @@ async function runMechanicalCheck(ctx, template) {
6131
6166
  }
6132
6167
  const perFilePatterns = template.fallbackPatterns.filter((p) => !p.absenceBased);
6133
6168
  const projectResults = await runProjectAbsenceChecks(eligibleFiles, template);
6169
+ const maxMechanicalFiles = getMechanicalMaxFiles();
6170
+ let cappedFiles = eligibleFiles;
6171
+ if (cappedFiles.length > maxMechanicalFiles) {
6172
+ console.error(
6173
+ `[seaworthy] ${template.checkId}: ${cappedFiles.length} eligible files \u2014 capping mechanical scan to ${maxMechanicalFiles}`
6174
+ );
6175
+ cappedFiles = cappedFiles.slice(0, maxMechanicalFiles);
6176
+ }
6134
6177
  const fileResults = perFilePatterns.length === 0 ? [] : await runWithConcurrencyCollect(
6135
- eligibleFiles,
6178
+ cappedFiles,
6136
6179
  async (file) => {
6137
6180
  const content = await file.content();
6138
6181
  const lines = content.split("\n");
@@ -6212,7 +6255,7 @@ async function runLLMCheck(ctx, template) {
6212
6255
  return [];
6213
6256
  }
6214
6257
  const proEligibleFiles = ctx.files.filter((f) => isProCheckEligible(f, template));
6215
- let eligibleFiles = filterFilesByBudget(proEligibleFiles, template.tokenBudget);
6258
+ let eligibleFiles = await filterFilesByBudget(proEligibleFiles, template.tokenBudget);
6216
6259
  const maxFiles = getMaxFilesPerLlmCheck();
6217
6260
  if (eligibleFiles.length > maxFiles) {
6218
6261
  console.error(