qfai 1.0.0 → 1.0.2

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");
@@ -597,7 +630,7 @@ async function collectSpecEntries(specsRoot) {
597
630
  dir,
598
631
  specPath: import_node_path3.default.join(dir, "spec.md"),
599
632
  deltaPath: import_node_path3.default.join(dir, "delta.md"),
600
- scenarioPath: import_node_path3.default.join(dir, "scenario.md")
633
+ scenarioPath: import_node_path3.default.join(dir, "scenario.feature")
601
634
  }));
602
635
  return entries.sort((a, b) => a.dir.localeCompare(b.dir));
603
636
  }
@@ -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.2".length > 0) {
1277
+ return "1.0.2";
1238
1278
  }
1239
1279
  try {
1240
1280
  const packagePath = resolvePackageJsonPath();
@@ -1922,12 +1962,11 @@ async function validateScenarios(root, config) {
1922
1962
  const specsRoot = resolvePath(root, config, "specsDir");
1923
1963
  const entries = await collectSpecEntries(specsRoot);
1924
1964
  if (entries.length === 0) {
1925
- const expected = "spec-0001/scenario.md";
1926
- const legacy = "spec-001/scenario.md";
1965
+ const expected = "spec-0001/scenario.feature";
1927
1966
  return [
1928
1967
  issue4(
1929
1968
  "QFAI-SC-000",
1930
- `Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected} (${legacy} \u306F\u975E\u5BFE\u5FDC)`,
1969
+ `Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}`,
1931
1970
  "info",
1932
1971
  specsRoot,
1933
1972
  "scenario.files"
@@ -1944,7 +1983,7 @@ async function validateScenarios(root, config) {
1944
1983
  issues.push(
1945
1984
  issue4(
1946
1985
  "QFAI-SC-001",
1947
- "scenario.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1986
+ "scenario.feature \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1948
1987
  "error",
1949
1988
  entry.scenarioPath,
1950
1989
  "scenario.exists"
@@ -2906,13 +2945,14 @@ async function createReportData(root, validation, configResult) {
2906
2945
  issues: normalizedValidation.issues
2907
2946
  };
2908
2947
  }
2909
- function formatReportMarkdown(data) {
2948
+ function formatReportMarkdown(data, options = {}) {
2910
2949
  const lines = [];
2950
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
2911
2951
  lines.push("# QFAI Report");
2912
2952
  lines.push("");
2913
2953
  lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
2914
- lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
2915
- lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
2954
+ lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
2955
+ lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
2916
2956
  lines.push(`- \u7248: ${data.version}`);
2917
2957
  lines.push("");
2918
2958
  const severityOrder = {
@@ -3051,8 +3091,7 @@ function formatReportMarkdown(data) {
3051
3091
  `#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
3052
3092
  );
3053
3093
  if (item.file) {
3054
- const loc = item.loc?.line ? `:${item.loc.line}` : "";
3055
- out.push(`- file: ${item.file}${loc}`);
3094
+ out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
3056
3095
  }
3057
3096
  if (item.rule) {
3058
3097
  out.push(`- rule: ${item.rule}`);
@@ -3178,6 +3217,11 @@ function formatReportMarkdown(data) {
3178
3217
  lines.push(
3179
3218
  `- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
3180
3219
  );
3220
+ if (data.traceability.testFiles.truncated) {
3221
+ lines.push(
3222
+ `- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
3223
+ );
3224
+ }
3181
3225
  if (data.traceability.sc.missingIds.length === 0) {
3182
3226
  lines.push("- missingIds: (none)");
3183
3227
  } else {
@@ -3187,7 +3231,8 @@ function formatReportMarkdown(data) {
3187
3231
  if (files.length === 0) {
3188
3232
  return id;
3189
3233
  }
3190
- return `${id} (${files.join(", ")})`;
3234
+ const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
3235
+ return `${id} (${formattedFiles.join(", ")})`;
3191
3236
  });
3192
3237
  lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
3193
3238
  }
@@ -3204,7 +3249,8 @@ function formatReportMarkdown(data) {
3204
3249
  if (refs.length === 0) {
3205
3250
  lines.push(`- ${scId}: (none)`);
3206
3251
  } else {
3207
- lines.push(`- ${scId}: ${refs.join(", ")}`);
3252
+ const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
3253
+ lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
3208
3254
  }
3209
3255
  }
3210
3256
  }
@@ -3219,8 +3265,9 @@ function formatReportMarkdown(data) {
3219
3265
  } else {
3220
3266
  for (const item of specScIssues) {
3221
3267
  const location = item.file ?? "(unknown)";
3268
+ const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
3222
3269
  const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
3223
- lines.push(`- ${location}: ${refs}`);
3270
+ lines.push(`- ${formattedLocation}: ${refs}`);
3224
3271
  }
3225
3272
  }
3226
3273
  lines.push("");
@@ -3232,7 +3279,7 @@ function formatReportMarkdown(data) {
3232
3279
  } else {
3233
3280
  for (const spot of hotspots) {
3234
3281
  lines.push(
3235
- `- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
3282
+ `- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
3236
3283
  );
3237
3284
  }
3238
3285
  }
@@ -3389,6 +3436,41 @@ function formatMarkdownTable(headers, rows) {
3389
3436
  const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
3390
3437
  return [formatRow(headers), separator, ...rows.map(formatRow)];
3391
3438
  }
3439
+ function normalizeBaseUrl(value) {
3440
+ if (!value) {
3441
+ return void 0;
3442
+ }
3443
+ const trimmed = value.trim();
3444
+ if (!trimmed) {
3445
+ return void 0;
3446
+ }
3447
+ return trimmed.replace(/\/+$/, "");
3448
+ }
3449
+ function formatPathLink(value, baseUrl) {
3450
+ if (!baseUrl) {
3451
+ return value;
3452
+ }
3453
+ if (value === ".") {
3454
+ return `[${value}](${baseUrl})`;
3455
+ }
3456
+ const encoded = encodePathForUrl(value);
3457
+ if (!encoded) {
3458
+ return value;
3459
+ }
3460
+ return `[${value}](${baseUrl}/${encoded})`;
3461
+ }
3462
+ function formatPathWithLine(value, loc, baseUrl) {
3463
+ const link = formatPathLink(value, baseUrl);
3464
+ const line = loc?.line ? `:${loc.line}` : "";
3465
+ return `${link}${line}`;
3466
+ }
3467
+ function encodePathForUrl(value) {
3468
+ const normalized = value.replace(/\\/g, "/");
3469
+ if (normalized === ".") {
3470
+ return "";
3471
+ }
3472
+ return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
3473
+ }
3392
3474
  function toSortedArray2(values) {
3393
3475
  return Array.from(values).sort((a, b) => a.localeCompare(b));
3394
3476
  }