qfai 1.0.0 → 1.0.1

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.cjs CHANGED
@@ -531,6 +531,7 @@ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
531
531
  "tmp",
532
532
  ".mcp-tools"
533
533
  ]);
534
+ var DEFAULT_GLOB_FILE_LIMIT = 2e4;
534
535
  async function collectFiles(root, options = {}) {
535
536
  const entries = [];
536
537
  if (!await exists2(root)) {
@@ -545,16 +546,29 @@ async function collectFiles(root, options = {}) {
545
546
  return entries;
546
547
  }
547
548
  async function collectFilesByGlobs(root, options) {
549
+ const limit = normalizeLimit(options.limit);
548
550
  if (options.globs.length === 0) {
549
- return [];
551
+ return { files: [], truncated: false, matchedFileCount: 0, limit };
550
552
  }
551
- return (0, import_fast_glob.default)(options.globs, {
553
+ const stream = import_fast_glob.default.stream(options.globs, {
552
554
  cwd: root,
553
555
  ignore: options.ignore ?? [],
554
556
  onlyFiles: true,
555
557
  absolute: true,
556
558
  unique: true
557
559
  });
560
+ const files = [];
561
+ let truncated = false;
562
+ for await (const entry of stream) {
563
+ if (files.length >= limit) {
564
+ truncated = true;
565
+ destroyStream(stream);
566
+ break;
567
+ }
568
+ files.push(String(entry));
569
+ }
570
+ const matchedFileCount = files.length;
571
+ return { files, truncated, matchedFileCount, limit };
558
572
  }
559
573
  async function walk(base, current, ignoreDirs, extensions, out) {
560
574
  const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
@@ -586,6 +600,25 @@ async function exists2(target) {
586
600
  return false;
587
601
  }
588
602
  }
603
+ function normalizeLimit(value) {
604
+ if (typeof value !== "number" || Number.isNaN(value)) {
605
+ return DEFAULT_GLOB_FILE_LIMIT;
606
+ }
607
+ const flooredValue = Math.floor(value);
608
+ if (flooredValue <= 0) {
609
+ return DEFAULT_GLOB_FILE_LIMIT;
610
+ }
611
+ return flooredValue;
612
+ }
613
+ function destroyStream(stream) {
614
+ if (!stream || typeof stream !== "object") {
615
+ return;
616
+ }
617
+ const record2 = stream;
618
+ if (typeof record2.destroy === "function") {
619
+ record2.destroy();
620
+ }
621
+ }
589
622
 
590
623
  // src/core/specLayout.ts
591
624
  var import_promises3 = require("fs/promises");
@@ -1147,15 +1180,18 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
1147
1180
  scan: {
1148
1181
  globs: normalizedGlobs,
1149
1182
  excludeGlobs: mergedExcludeGlobs,
1150
- matchedFileCount: 0
1183
+ matchedFileCount: 0,
1184
+ truncated: false,
1185
+ limit: DEFAULT_GLOB_FILE_LIMIT
1151
1186
  }
1152
1187
  };
1153
1188
  }
1154
- let files = [];
1189
+ let scanResult;
1155
1190
  try {
1156
- files = await collectFilesByGlobs(root, {
1191
+ scanResult = await collectFilesByGlobs(root, {
1157
1192
  globs: normalizedGlobs,
1158
- ignore: mergedExcludeGlobs
1193
+ ignore: mergedExcludeGlobs,
1194
+ limit: DEFAULT_GLOB_FILE_LIMIT
1159
1195
  });
1160
1196
  } catch (error) {
1161
1197
  return {
@@ -1163,13 +1199,15 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
1163
1199
  scan: {
1164
1200
  globs: normalizedGlobs,
1165
1201
  excludeGlobs: mergedExcludeGlobs,
1166
- matchedFileCount: 0
1202
+ matchedFileCount: 0,
1203
+ truncated: false,
1204
+ limit: DEFAULT_GLOB_FILE_LIMIT
1167
1205
  },
1168
1206
  error: formatError3(error)
1169
1207
  };
1170
1208
  }
1171
1209
  const normalizedFiles = Array.from(
1172
- new Set(files.map((file) => import_node_path6.default.normalize(file)))
1210
+ new Set(scanResult.files.map((file) => import_node_path6.default.normalize(file)))
1173
1211
  );
1174
1212
  for (const file of normalizedFiles) {
1175
1213
  const text = await (0, import_promises6.readFile)(file, "utf-8");
@@ -1188,7 +1226,9 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
1188
1226
  scan: {
1189
1227
  globs: normalizedGlobs,
1190
1228
  excludeGlobs: mergedExcludeGlobs,
1191
- matchedFileCount: normalizedFiles.length
1229
+ matchedFileCount: scanResult.matchedFileCount,
1230
+ truncated: scanResult.truncated,
1231
+ limit: scanResult.limit
1192
1232
  }
1193
1233
  };
1194
1234
  }
@@ -1233,8 +1273,8 @@ var import_promises7 = require("fs/promises");
1233
1273
  var import_node_path7 = __toESM(require("path"), 1);
1234
1274
  var import_node_url = require("url");
1235
1275
  async function resolveToolVersion() {
1236
- if ("1.0.0".length > 0) {
1237
- return "1.0.0";
1276
+ if ("1.0.1".length > 0) {
1277
+ return "1.0.1";
1238
1278
  }
1239
1279
  try {
1240
1280
  const packagePath = resolvePackageJsonPath();
@@ -2906,13 +2946,14 @@ async function createReportData(root, validation, configResult) {
2906
2946
  issues: normalizedValidation.issues
2907
2947
  };
2908
2948
  }
2909
- function formatReportMarkdown(data) {
2949
+ function formatReportMarkdown(data, options = {}) {
2910
2950
  const lines = [];
2951
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
2911
2952
  lines.push("# QFAI Report");
2912
2953
  lines.push("");
2913
2954
  lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
2914
- lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
2915
- lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
2955
+ lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
2956
+ lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
2916
2957
  lines.push(`- \u7248: ${data.version}`);
2917
2958
  lines.push("");
2918
2959
  const severityOrder = {
@@ -3051,8 +3092,7 @@ function formatReportMarkdown(data) {
3051
3092
  `#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
3052
3093
  );
3053
3094
  if (item.file) {
3054
- const loc = item.loc?.line ? `:${item.loc.line}` : "";
3055
- out.push(`- file: ${item.file}${loc}`);
3095
+ out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
3056
3096
  }
3057
3097
  if (item.rule) {
3058
3098
  out.push(`- rule: ${item.rule}`);
@@ -3178,6 +3218,11 @@ function formatReportMarkdown(data) {
3178
3218
  lines.push(
3179
3219
  `- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
3180
3220
  );
3221
+ if (data.traceability.testFiles.truncated) {
3222
+ lines.push(
3223
+ `- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
3224
+ );
3225
+ }
3181
3226
  if (data.traceability.sc.missingIds.length === 0) {
3182
3227
  lines.push("- missingIds: (none)");
3183
3228
  } else {
@@ -3187,7 +3232,8 @@ function formatReportMarkdown(data) {
3187
3232
  if (files.length === 0) {
3188
3233
  return id;
3189
3234
  }
3190
- return `${id} (${files.join(", ")})`;
3235
+ const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
3236
+ return `${id} (${formattedFiles.join(", ")})`;
3191
3237
  });
3192
3238
  lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
3193
3239
  }
@@ -3204,7 +3250,8 @@ function formatReportMarkdown(data) {
3204
3250
  if (refs.length === 0) {
3205
3251
  lines.push(`- ${scId}: (none)`);
3206
3252
  } else {
3207
- lines.push(`- ${scId}: ${refs.join(", ")}`);
3253
+ const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
3254
+ lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
3208
3255
  }
3209
3256
  }
3210
3257
  }
@@ -3219,8 +3266,9 @@ function formatReportMarkdown(data) {
3219
3266
  } else {
3220
3267
  for (const item of specScIssues) {
3221
3268
  const location = item.file ?? "(unknown)";
3269
+ const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
3222
3270
  const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
3223
- lines.push(`- ${location}: ${refs}`);
3271
+ lines.push(`- ${formattedLocation}: ${refs}`);
3224
3272
  }
3225
3273
  }
3226
3274
  lines.push("");
@@ -3232,7 +3280,7 @@ function formatReportMarkdown(data) {
3232
3280
  } else {
3233
3281
  for (const spot of hotspots) {
3234
3282
  lines.push(
3235
- `- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
3283
+ `- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
3236
3284
  );
3237
3285
  }
3238
3286
  }
@@ -3389,6 +3437,41 @@ function formatMarkdownTable(headers, rows) {
3389
3437
  const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
3390
3438
  return [formatRow(headers), separator, ...rows.map(formatRow)];
3391
3439
  }
3440
+ function normalizeBaseUrl(value) {
3441
+ if (!value) {
3442
+ return void 0;
3443
+ }
3444
+ const trimmed = value.trim();
3445
+ if (!trimmed) {
3446
+ return void 0;
3447
+ }
3448
+ return trimmed.replace(/\/+$/, "");
3449
+ }
3450
+ function formatPathLink(value, baseUrl) {
3451
+ if (!baseUrl) {
3452
+ return value;
3453
+ }
3454
+ if (value === ".") {
3455
+ return `[${value}](${baseUrl})`;
3456
+ }
3457
+ const encoded = encodePathForUrl(value);
3458
+ if (!encoded) {
3459
+ return value;
3460
+ }
3461
+ return `[${value}](${baseUrl}/${encoded})`;
3462
+ }
3463
+ function formatPathWithLine(value, loc, baseUrl) {
3464
+ const link = formatPathLink(value, baseUrl);
3465
+ const line = loc?.line ? `:${loc.line}` : "";
3466
+ return `${link}${line}`;
3467
+ }
3468
+ function encodePathForUrl(value) {
3469
+ const normalized = value.replace(/\\/g, "/");
3470
+ if (normalized === ".") {
3471
+ return "";
3472
+ }
3473
+ return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
3474
+ }
3392
3475
  function toSortedArray2(values) {
3393
3476
  return Array.from(values).sort((a, b) => a.localeCompare(b));
3394
3477
  }