qfai 0.4.0 → 0.4.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.
Files changed (161) hide show
  1. package/README.md +6 -1
  2. package/assets/init/.qfai/README.md +2 -1
  3. package/assets/init/.qfai/prompts/README.md +1 -0
  4. package/assets/init/.qfai/prompts/qfai-generate-test-globs.md +29 -0
  5. package/assets/init/root/qfai.config.yaml +6 -0
  6. package/assets/init/root/tests/qfai-traceability.sample.test.ts +2 -2
  7. package/dist/cli/index.cjs +337 -117
  8. package/dist/cli/index.cjs.map +1 -1
  9. package/dist/cli/index.d.ts +0 -2
  10. package/dist/cli/index.mjs +337 -117
  11. package/dist/cli/index.mjs.map +1 -1
  12. package/dist/index.cjs +327 -107
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +23 -9
  15. package/dist/index.d.ts +156 -2
  16. package/dist/index.mjs +327 -107
  17. package/dist/index.mjs.map +1 -1
  18. package/package.json +2 -1
  19. package/dist/cli/commands/init.d.ts +0 -8
  20. package/dist/cli/commands/init.d.ts.map +0 -1
  21. package/dist/cli/commands/init.js +0 -30
  22. package/dist/cli/commands/init.js.map +0 -1
  23. package/dist/cli/commands/report.d.ts +0 -7
  24. package/dist/cli/commands/report.d.ts.map +0 -1
  25. package/dist/cli/commands/report.js +0 -80
  26. package/dist/cli/commands/report.js.map +0 -1
  27. package/dist/cli/commands/validate.d.ts +0 -9
  28. package/dist/cli/commands/validate.d.ts.map +0 -1
  29. package/dist/cli/commands/validate.js +0 -57
  30. package/dist/cli/commands/validate.js.map +0 -1
  31. package/dist/cli/index.d.ts.map +0 -1
  32. package/dist/cli/index.js +0 -7
  33. package/dist/cli/index.js.map +0 -1
  34. package/dist/cli/lib/args.d.ts +0 -18
  35. package/dist/cli/lib/args.d.ts.map +0 -1
  36. package/dist/cli/lib/args.js +0 -98
  37. package/dist/cli/lib/args.js.map +0 -1
  38. package/dist/cli/lib/assets.d.ts +0 -2
  39. package/dist/cli/lib/assets.d.ts.map +0 -1
  40. package/dist/cli/lib/assets.js +0 -24
  41. package/dist/cli/lib/assets.js.map +0 -1
  42. package/dist/cli/lib/failOn.d.ts +0 -5
  43. package/dist/cli/lib/failOn.d.ts.map +0 -1
  44. package/dist/cli/lib/failOn.js +0 -10
  45. package/dist/cli/lib/failOn.js.map +0 -1
  46. package/dist/cli/lib/fs.d.ts +0 -11
  47. package/dist/cli/lib/fs.d.ts.map +0 -1
  48. package/dist/cli/lib/fs.js +0 -91
  49. package/dist/cli/lib/fs.js.map +0 -1
  50. package/dist/cli/lib/logger.d.ts +0 -4
  51. package/dist/cli/lib/logger.d.ts.map +0 -1
  52. package/dist/cli/lib/logger.js +0 -10
  53. package/dist/cli/lib/logger.js.map +0 -1
  54. package/dist/cli/main.d.ts +0 -2
  55. package/dist/cli/main.d.ts.map +0 -1
  56. package/dist/cli/main.js +0 -66
  57. package/dist/cli/main.js.map +0 -1
  58. package/dist/core/config.d.ts +0 -46
  59. package/dist/core/config.d.ts.map +0 -1
  60. package/dist/core/config.js +0 -222
  61. package/dist/core/config.js.map +0 -1
  62. package/dist/core/contractIndex.d.ts +0 -13
  63. package/dist/core/contractIndex.d.ts.map +0 -1
  64. package/dist/core/contractIndex.js +0 -66
  65. package/dist/core/contractIndex.js.map +0 -1
  66. package/dist/core/contracts.d.ts +0 -5
  67. package/dist/core/contracts.d.ts.map +0 -1
  68. package/dist/core/contracts.js +0 -42
  69. package/dist/core/contracts.js.map +0 -1
  70. package/dist/core/discovery.d.ts +0 -14
  71. package/dist/core/discovery.d.ts.map +0 -1
  72. package/dist/core/discovery.js +0 -55
  73. package/dist/core/discovery.js.map +0 -1
  74. package/dist/core/fs.d.ts +0 -6
  75. package/dist/core/fs.d.ts.map +0 -1
  76. package/dist/core/fs.js +0 -55
  77. package/dist/core/fs.js.map +0 -1
  78. package/dist/core/gherkin/parse.d.ts +0 -7
  79. package/dist/core/gherkin/parse.d.ts.map +0 -1
  80. package/dist/core/gherkin/parse.js +0 -25
  81. package/dist/core/gherkin/parse.js.map +0 -1
  82. package/dist/core/ids.d.ts +0 -6
  83. package/dist/core/ids.d.ts.map +0 -1
  84. package/dist/core/ids.js +0 -52
  85. package/dist/core/ids.js.map +0 -1
  86. package/dist/core/index.d.ts +0 -13
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/index.js +0 -13
  89. package/dist/core/index.js.map +0 -1
  90. package/dist/core/parse/adr.d.ts +0 -13
  91. package/dist/core/parse/adr.d.ts.map +0 -1
  92. package/dist/core/parse/adr.js +0 -33
  93. package/dist/core/parse/adr.js.map +0 -1
  94. package/dist/core/parse/gherkin.d.ts +0 -12
  95. package/dist/core/parse/gherkin.d.ts.map +0 -1
  96. package/dist/core/parse/gherkin.js +0 -22
  97. package/dist/core/parse/gherkin.js.map +0 -1
  98. package/dist/core/parse/markdown.d.ts +0 -14
  99. package/dist/core/parse/markdown.d.ts.map +0 -1
  100. package/dist/core/parse/markdown.js +0 -45
  101. package/dist/core/parse/markdown.js.map +0 -1
  102. package/dist/core/parse/spec.d.ts +0 -28
  103. package/dist/core/parse/spec.d.ts.map +0 -1
  104. package/dist/core/parse/spec.js +0 -80
  105. package/dist/core/parse/spec.js.map +0 -1
  106. package/dist/core/report.d.ts +0 -41
  107. package/dist/core/report.d.ts.map +0 -1
  108. package/dist/core/report.js +0 -260
  109. package/dist/core/report.js.map +0 -1
  110. package/dist/core/scenarioModel.d.ts +0 -33
  111. package/dist/core/scenarioModel.d.ts.map +0 -1
  112. package/dist/core/scenarioModel.js +0 -130
  113. package/dist/core/scenarioModel.js.map +0 -1
  114. package/dist/core/specLayout.d.ts +0 -8
  115. package/dist/core/specLayout.d.ts.map +0 -1
  116. package/dist/core/specLayout.js +0 -36
  117. package/dist/core/specLayout.js.map +0 -1
  118. package/dist/core/traceability.d.ts +0 -12
  119. package/dist/core/traceability.d.ts.map +0 -1
  120. package/dist/core/traceability.js +0 -70
  121. package/dist/core/traceability.js.map +0 -1
  122. package/dist/core/types.d.ts +0 -25
  123. package/dist/core/types.d.ts.map +0 -1
  124. package/dist/core/types.js +0 -2
  125. package/dist/core/types.js.map +0 -1
  126. package/dist/core/validate.d.ts +0 -4
  127. package/dist/core/validate.d.ts.map +0 -1
  128. package/dist/core/validate.js +0 -34
  129. package/dist/core/validate.js.map +0 -1
  130. package/dist/core/validators/contracts.d.ts +0 -5
  131. package/dist/core/validators/contracts.d.ts.map +0 -1
  132. package/dist/core/validators/contracts.js +0 -162
  133. package/dist/core/validators/contracts.js.map +0 -1
  134. package/dist/core/validators/delta.d.ts +0 -4
  135. package/dist/core/validators/delta.d.ts.map +0 -1
  136. package/dist/core/validators/delta.js +0 -68
  137. package/dist/core/validators/delta.js.map +0 -1
  138. package/dist/core/validators/ids.d.ts +0 -4
  139. package/dist/core/validators/ids.d.ts.map +0 -1
  140. package/dist/core/validators/ids.js +0 -88
  141. package/dist/core/validators/ids.js.map +0 -1
  142. package/dist/core/validators/scenario.d.ts +0 -5
  143. package/dist/core/validators/scenario.d.ts.map +0 -1
  144. package/dist/core/validators/scenario.js +0 -140
  145. package/dist/core/validators/scenario.js.map +0 -1
  146. package/dist/core/validators/spec.d.ts +0 -5
  147. package/dist/core/validators/spec.d.ts.map +0 -1
  148. package/dist/core/validators/spec.js +0 -94
  149. package/dist/core/validators/spec.js.map +0 -1
  150. package/dist/core/validators/traceability.d.ts +0 -4
  151. package/dist/core/validators/traceability.d.ts.map +0 -1
  152. package/dist/core/validators/traceability.js +0 -190
  153. package/dist/core/validators/traceability.js.map +0 -1
  154. package/dist/core/version.d.ts +0 -2
  155. package/dist/core/version.d.ts.map +0 -1
  156. package/dist/core/version.js +0 -25
  157. package/dist/core/version.js.map +0 -1
  158. package/dist/index.d.ts.map +0 -1
  159. package/dist/index.js +0 -2
  160. package/dist/index.js.map +0 -1
  161. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -178,7 +178,7 @@ function report(copied, skipped, dryRun, label) {
178
178
 
179
179
  // src/cli/commands/report.ts
180
180
  var import_promises16 = require("fs/promises");
181
- var import_node_path14 = __toESM(require("path"), 1);
181
+ var import_node_path15 = __toESM(require("path"), 1);
182
182
 
183
183
  // src/core/config.ts
184
184
  var import_promises2 = require("fs/promises");
@@ -211,6 +211,8 @@ var defaultConfig = {
211
211
  brMustHaveSc: true,
212
212
  scMustTouchContracts: true,
213
213
  scMustHaveTest: true,
214
+ testFileGlobs: [],
215
+ testFileExcludeGlobs: [],
214
216
  scNoTestSeverity: "error",
215
217
  allowOrphanContracts: false,
216
218
  unknownContractIdSeverity: "error"
@@ -398,6 +400,20 @@ function normalizeValidation(raw, configPath, issues) {
398
400
  configPath,
399
401
  issues
400
402
  ),
403
+ testFileGlobs: readStringArray(
404
+ traceabilityRaw?.testFileGlobs,
405
+ base.traceability.testFileGlobs,
406
+ "validation.traceability.testFileGlobs",
407
+ configPath,
408
+ issues
409
+ ),
410
+ testFileExcludeGlobs: readStringArray(
411
+ traceabilityRaw?.testFileExcludeGlobs,
412
+ base.traceability.testFileExcludeGlobs,
413
+ "validation.traceability.testFileExcludeGlobs",
414
+ configPath,
415
+ issues
416
+ ),
401
417
  scNoTestSeverity: readTraceabilitySeverity(
402
418
  traceabilityRaw?.scNoTestSeverity,
403
419
  base.traceability.scNoTestSeverity,
@@ -531,7 +547,7 @@ function isRecord(value) {
531
547
 
532
548
  // src/core/report.ts
533
549
  var import_promises15 = require("fs/promises");
534
- var import_node_path13 = __toESM(require("path"), 1);
550
+ var import_node_path14 = __toESM(require("path"), 1);
535
551
 
536
552
  // src/core/discovery.ts
537
553
  var import_promises5 = require("fs/promises");
@@ -539,6 +555,7 @@ var import_promises5 = require("fs/promises");
539
555
  // src/core/fs.ts
540
556
  var import_promises3 = require("fs/promises");
541
557
  var import_node_path5 = __toESM(require("path"), 1);
558
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
542
559
  var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
543
560
  "node_modules",
544
561
  ".git",
@@ -560,6 +577,18 @@ async function collectFiles(root, options = {}) {
560
577
  await walk(root, root, ignoreDirs, extensions, entries);
561
578
  return entries;
562
579
  }
580
+ async function collectFilesByGlobs(root, options) {
581
+ if (options.globs.length === 0) {
582
+ return [];
583
+ }
584
+ return (0, import_fast_glob.default)(options.globs, {
585
+ cwd: root,
586
+ ignore: options.ignore ?? [],
587
+ onlyFiles: true,
588
+ absolute: true,
589
+ unique: true
590
+ });
591
+ }
563
592
  async function walk(base, current, ignoreDirs, extensions, out) {
564
593
  const items = await (0, import_promises3.readdir)(current, { withFileTypes: true });
565
594
  for (const item of items) {
@@ -726,6 +755,7 @@ function isValidId(value, prefix) {
726
755
 
727
756
  // src/core/traceability.ts
728
757
  var import_promises6 = require("fs/promises");
758
+ var import_node_path7 = __toESM(require("path"), 1);
729
759
 
730
760
  // src/core/gherkin/parse.ts
731
761
  var import_gherkin = require("@cucumber/gherkin");
@@ -883,6 +913,27 @@ function unique2(values) {
883
913
 
884
914
  // src/core/traceability.ts
885
915
  var SC_TAG_RE2 = /^SC-\d{4}$/;
916
+ var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
917
+ var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
918
+ "**/node_modules/**",
919
+ "**/.git/**",
920
+ "**/.qfai/**",
921
+ "**/dist/**",
922
+ "**/build/**",
923
+ "**/coverage/**",
924
+ "**/.next/**",
925
+ "**/out/**"
926
+ ];
927
+ function extractAnnotatedScIds(text) {
928
+ const ids = /* @__PURE__ */ new Set();
929
+ for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
930
+ const suffix = match[1];
931
+ if (suffix) {
932
+ ids.add(`SC-${suffix}`);
933
+ }
934
+ }
935
+ return Array.from(ids);
936
+ }
886
937
  async function collectScIdsFromScenarioFiles(scenarioFiles) {
887
938
  const scIds = /* @__PURE__ */ new Set();
888
939
  for (const file of scenarioFiles) {
@@ -901,14 +952,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
901
952
  }
902
953
  return scIds;
903
954
  }
904
- async function collectScTestReferences(testsRoot) {
955
+ async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
956
+ const sources = /* @__PURE__ */ new Map();
957
+ for (const file of scenarioFiles) {
958
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
959
+ const { document, errors } = parseScenarioDocument(text, file);
960
+ if (!document || errors.length > 0) {
961
+ continue;
962
+ }
963
+ for (const scenario of document.scenarios) {
964
+ for (const tag of scenario.tags) {
965
+ if (!SC_TAG_RE2.test(tag)) {
966
+ continue;
967
+ }
968
+ const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
969
+ current.add(file);
970
+ sources.set(tag, current);
971
+ }
972
+ }
973
+ }
974
+ return sources;
975
+ }
976
+ async function collectScTestReferences(root, globs, excludeGlobs) {
905
977
  const refs = /* @__PURE__ */ new Map();
906
- const testFiles = await collectFiles(testsRoot, {
907
- extensions: [".ts", ".tsx", ".js", ".jsx"]
908
- });
909
- for (const file of testFiles) {
978
+ const normalizedGlobs = normalizeGlobs(globs);
979
+ const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
980
+ const mergedExcludeGlobs = Array.from(
981
+ /* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
982
+ );
983
+ if (normalizedGlobs.length === 0) {
984
+ return {
985
+ refs,
986
+ scan: {
987
+ globs: normalizedGlobs,
988
+ excludeGlobs: mergedExcludeGlobs,
989
+ matchedFileCount: 0
990
+ }
991
+ };
992
+ }
993
+ let files = [];
994
+ try {
995
+ files = await collectFilesByGlobs(root, {
996
+ globs: normalizedGlobs,
997
+ ignore: mergedExcludeGlobs
998
+ });
999
+ } catch (error2) {
1000
+ return {
1001
+ refs,
1002
+ scan: {
1003
+ globs: normalizedGlobs,
1004
+ excludeGlobs: mergedExcludeGlobs,
1005
+ matchedFileCount: 0
1006
+ },
1007
+ error: formatError3(error2)
1008
+ };
1009
+ }
1010
+ const normalizedFiles = Array.from(
1011
+ new Set(files.map((file) => import_node_path7.default.normalize(file)))
1012
+ );
1013
+ for (const file of normalizedFiles) {
910
1014
  const text = await (0, import_promises6.readFile)(file, "utf-8");
911
- const scIds = extractIds(text, "SC");
1015
+ const scIds = extractAnnotatedScIds(text);
912
1016
  if (scIds.length === 0) {
913
1017
  continue;
914
1018
  }
@@ -918,7 +1022,14 @@ async function collectScTestReferences(testsRoot) {
918
1022
  refs.set(scId, current);
919
1023
  }
920
1024
  }
921
- return refs;
1025
+ return {
1026
+ refs,
1027
+ scan: {
1028
+ globs: normalizedGlobs,
1029
+ excludeGlobs: mergedExcludeGlobs,
1030
+ matchedFileCount: normalizedFiles.length
1031
+ }
1032
+ };
922
1033
  }
923
1034
  function buildScCoverage(scIds, refs) {
924
1035
  const sortedScIds = toSortedArray(scIds);
@@ -946,14 +1057,23 @@ function buildScCoverage(scIds, refs) {
946
1057
  function toSortedArray(values) {
947
1058
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
948
1059
  }
1060
+ function normalizeGlobs(globs) {
1061
+ return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
1062
+ }
1063
+ function formatError3(error2) {
1064
+ if (error2 instanceof Error) {
1065
+ return error2.message;
1066
+ }
1067
+ return String(error2);
1068
+ }
949
1069
 
950
1070
  // src/core/version.ts
951
1071
  var import_promises7 = require("fs/promises");
952
- var import_node_path7 = __toESM(require("path"), 1);
1072
+ var import_node_path8 = __toESM(require("path"), 1);
953
1073
  var import_node_url2 = require("url");
954
1074
  async function resolveToolVersion() {
955
- if ("0.4.0".length > 0) {
956
- return "0.4.0";
1075
+ if ("0.4.2".length > 0) {
1076
+ return "0.4.2";
957
1077
  }
958
1078
  try {
959
1079
  const packagePath = resolvePackageJsonPath();
@@ -968,18 +1088,18 @@ async function resolveToolVersion() {
968
1088
  function resolvePackageJsonPath() {
969
1089
  const base = __filename;
970
1090
  const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
971
- return import_node_path7.default.resolve(import_node_path7.default.dirname(basePath), "../../package.json");
1091
+ return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
972
1092
  }
973
1093
 
974
1094
  // src/core/validators/contracts.ts
975
1095
  var import_promises8 = require("fs/promises");
976
- var import_node_path9 = __toESM(require("path"), 1);
1096
+ var import_node_path10 = __toESM(require("path"), 1);
977
1097
 
978
1098
  // src/core/contracts.ts
979
- var import_node_path8 = __toESM(require("path"), 1);
1099
+ var import_node_path9 = __toESM(require("path"), 1);
980
1100
  var import_yaml2 = require("yaml");
981
1101
  function parseStructuredContract(file, text) {
982
- const ext = import_node_path8.default.extname(file).toLowerCase();
1102
+ const ext = import_node_path9.default.extname(file).toLowerCase();
983
1103
  if (ext === ".json") {
984
1104
  return JSON.parse(text);
985
1105
  }
@@ -1030,9 +1150,9 @@ var SQL_DANGEROUS_PATTERNS = [
1030
1150
  async function validateContracts(root, config) {
1031
1151
  const issues = [];
1032
1152
  const contractsRoot = resolvePath(root, config, "contractsDir");
1033
- issues.push(...await validateUiContracts(import_node_path9.default.join(contractsRoot, "ui")));
1034
- issues.push(...await validateApiContracts(import_node_path9.default.join(contractsRoot, "api")));
1035
- issues.push(...await validateDataContracts(import_node_path9.default.join(contractsRoot, "db")));
1153
+ issues.push(...await validateUiContracts(import_node_path10.default.join(contractsRoot, "ui")));
1154
+ issues.push(...await validateApiContracts(import_node_path10.default.join(contractsRoot, "api")));
1155
+ issues.push(...await validateDataContracts(import_node_path10.default.join(contractsRoot, "db")));
1036
1156
  return issues;
1037
1157
  }
1038
1158
  async function validateUiContracts(uiRoot) {
@@ -1079,7 +1199,7 @@ async function validateUiContracts(uiRoot) {
1079
1199
  issues.push(
1080
1200
  issue(
1081
1201
  "QFAI-CONTRACT-001",
1082
- `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError3(error2)})`,
1202
+ `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
1083
1203
  "error",
1084
1204
  file,
1085
1205
  "contracts.ui.parse"
@@ -1146,7 +1266,7 @@ async function validateApiContracts(apiRoot) {
1146
1266
  issues.push(
1147
1267
  issue(
1148
1268
  "QFAI-CONTRACT-001",
1149
- `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError3(error2)})`,
1269
+ `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
1150
1270
  "error",
1151
1271
  file,
1152
1272
  "contracts.api.parse"
@@ -1241,7 +1361,7 @@ function lintSql(text, file) {
1241
1361
  function hasOpenApi(doc) {
1242
1362
  return typeof doc.openapi === "string" && doc.openapi.length > 0;
1243
1363
  }
1244
- function formatError3(error2) {
1364
+ function formatError4(error2) {
1245
1365
  if (error2 instanceof Error) {
1246
1366
  return error2.message;
1247
1367
  }
@@ -1267,7 +1387,7 @@ function issue(code, message, severity, file, rule, refs) {
1267
1387
 
1268
1388
  // src/core/validators/delta.ts
1269
1389
  var import_promises9 = require("fs/promises");
1270
- var import_node_path10 = __toESM(require("path"), 1);
1390
+ var import_node_path11 = __toESM(require("path"), 1);
1271
1391
  var SECTION_RE = /^##\s+変更区分/m;
1272
1392
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
1273
1393
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -1281,7 +1401,7 @@ async function validateDeltas(root, config) {
1281
1401
  }
1282
1402
  const issues = [];
1283
1403
  for (const pack of packs) {
1284
- const deltaPath = import_node_path10.default.join(pack, "delta.md");
1404
+ const deltaPath = import_node_path11.default.join(pack, "delta.md");
1285
1405
  let text;
1286
1406
  try {
1287
1407
  text = await (0, import_promises9.readFile)(deltaPath, "utf-8");
@@ -1357,16 +1477,16 @@ function issue2(code, message, severity, file, rule, refs) {
1357
1477
 
1358
1478
  // src/core/validators/ids.ts
1359
1479
  var import_promises11 = require("fs/promises");
1360
- var import_node_path12 = __toESM(require("path"), 1);
1480
+ var import_node_path13 = __toESM(require("path"), 1);
1361
1481
 
1362
1482
  // src/core/contractIndex.ts
1363
1483
  var import_promises10 = require("fs/promises");
1364
- var import_node_path11 = __toESM(require("path"), 1);
1484
+ var import_node_path12 = __toESM(require("path"), 1);
1365
1485
  async function buildContractIndex(root, config) {
1366
1486
  const contractsRoot = resolvePath(root, config, "contractsDir");
1367
- const uiRoot = import_node_path11.default.join(contractsRoot, "ui");
1368
- const apiRoot = import_node_path11.default.join(contractsRoot, "api");
1369
- const dataRoot = import_node_path11.default.join(contractsRoot, "db");
1487
+ const uiRoot = import_node_path12.default.join(contractsRoot, "ui");
1488
+ const apiRoot = import_node_path12.default.join(contractsRoot, "api");
1489
+ const dataRoot = import_node_path12.default.join(contractsRoot, "db");
1370
1490
  const [uiFiles, apiFiles, dataFiles] = await Promise.all([
1371
1491
  collectUiContractFiles(uiRoot),
1372
1492
  collectApiContractFiles(apiRoot),
@@ -1604,7 +1724,7 @@ function recordId(out, id, file) {
1604
1724
  }
1605
1725
  function formatFileList(files, root) {
1606
1726
  return files.map((file) => {
1607
- const relative = import_node_path12.default.relative(root, file);
1727
+ const relative = import_node_path13.default.relative(root, file);
1608
1728
  return relative.length > 0 ? relative : file;
1609
1729
  }).join(", ");
1610
1730
  }
@@ -1633,7 +1753,6 @@ var WHEN_PATTERN = /\bWhen\b/;
1633
1753
  var THEN_PATTERN = /\bThen\b/;
1634
1754
  var SC_TAG_RE4 = /^SC-\d{4}$/;
1635
1755
  var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1636
- var BR_TAG_RE2 = /^BR-\d{4}$/;
1637
1756
  async function validateScenarios(root, config) {
1638
1757
  const specsRoot = resolvePath(root, config, "specsDir");
1639
1758
  const entries = await collectSpecEntries(specsRoot);
@@ -1713,17 +1832,7 @@ function validateScenarioContent(text, file) {
1713
1832
  const featureSpecTags = document.featureTags.filter(
1714
1833
  (tag) => SPEC_TAG_RE2.test(tag)
1715
1834
  );
1716
- if (featureSpecTags.length === 0) {
1717
- issues.push(
1718
- issue4(
1719
- "QFAI-SC-009",
1720
- "Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1721
- "error",
1722
- file,
1723
- "scenario.featureSpec"
1724
- )
1725
- );
1726
- } else if (featureSpecTags.length > 1) {
1835
+ if (featureSpecTags.length > 1) {
1727
1836
  issues.push(
1728
1837
  issue4(
1729
1838
  "QFAI-SC-009",
@@ -1751,17 +1860,6 @@ function validateScenarioContent(text, file) {
1751
1860
  )
1752
1861
  );
1753
1862
  }
1754
- if (document.scenarios.length > 1) {
1755
- issues.push(
1756
- issue4(
1757
- "QFAI-SC-011",
1758
- `Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
1759
- "error",
1760
- file,
1761
- "scenario.single"
1762
- )
1763
- );
1764
- }
1765
1863
  for (const scenario of document.scenarios) {
1766
1864
  if (scenario.tags.length === 0) {
1767
1865
  issues.push(
@@ -1782,12 +1880,6 @@ function validateScenarioContent(text, file) {
1782
1880
  } else if (scTags.length > 1) {
1783
1881
  missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
1784
1882
  }
1785
- if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
1786
- missingTags.push("SPEC");
1787
- }
1788
- if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
1789
- missingTags.push("BR");
1790
- }
1791
1883
  if (missingTags.length > 0) {
1792
1884
  issues.push(
1793
1885
  issue4(
@@ -2022,9 +2114,8 @@ function isMissingFileError4(error2) {
2022
2114
 
2023
2115
  // src/core/validators/traceability.ts
2024
2116
  var import_promises14 = require("fs/promises");
2025
- var SC_TAG_RE5 = /^SC-\d{4}$/;
2026
2117
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2027
- var BR_TAG_RE3 = /^BR-\d{4}$/;
2118
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
2028
2119
  async function validateTraceability(root, config) {
2029
2120
  const issues = [];
2030
2121
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -2051,28 +2142,6 @@ async function validateTraceability(root, config) {
2051
2142
  }
2052
2143
  const brIds = parsed.brs.map((br) => br.id);
2053
2144
  brIds.forEach((id) => brIdsInSpecs.add(id));
2054
- const referencedContractIds = /* @__PURE__ */ new Set([
2055
- ...extractIds(text, "UI"),
2056
- ...extractIds(text, "API"),
2057
- ...extractIds(text, "DATA")
2058
- ]);
2059
- const unknownContractIds = Array.from(referencedContractIds).filter(
2060
- (id) => !contractIds.has(id)
2061
- );
2062
- if (unknownContractIds.length > 0) {
2063
- issues.push(
2064
- issue6(
2065
- "QFAI-TRACE-009",
2066
- `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
2067
- ", "
2068
- )}`,
2069
- "error",
2070
- file,
2071
- "traceability.specContractExists",
2072
- unknownContractIds
2073
- )
2074
- );
2075
- }
2076
2145
  if (parsed.specId) {
2077
2146
  const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
2078
2147
  brIds.forEach((id) => current.add(id));
@@ -2087,16 +2156,42 @@ async function validateTraceability(root, config) {
2087
2156
  continue;
2088
2157
  }
2089
2158
  const atoms = buildScenarioAtoms(document);
2159
+ const scIdsInFile = /* @__PURE__ */ new Set();
2090
2160
  for (const [index, scenario] of document.scenarios.entries()) {
2091
2161
  const atom = atoms[index];
2092
2162
  if (!atom) {
2093
2163
  continue;
2094
2164
  }
2095
2165
  const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
2096
- const brTags = scenario.tags.filter((tag) => BR_TAG_RE3.test(tag));
2097
- const scTags = scenario.tags.filter((tag) => SC_TAG_RE5.test(tag));
2166
+ const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
2167
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
2168
+ if (specTags.length === 0) {
2169
+ issues.push(
2170
+ issue6(
2171
+ "QFAI-TRACE-014",
2172
+ `Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
2173
+ "error",
2174
+ file,
2175
+ "traceability.scenarioSpecRequired"
2176
+ )
2177
+ );
2178
+ }
2179
+ if (brTags.length === 0) {
2180
+ issues.push(
2181
+ issue6(
2182
+ "QFAI-TRACE-015",
2183
+ `Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
2184
+ "error",
2185
+ file,
2186
+ "traceability.scenarioBrRequired"
2187
+ )
2188
+ );
2189
+ }
2098
2190
  brTags.forEach((id) => brIdsInScenarios.add(id));
2099
- scTags.forEach((id) => scIdsInScenarios.add(id));
2191
+ scTags.forEach((id) => {
2192
+ scIdsInScenarios.add(id);
2193
+ scIdsInFile.add(id);
2194
+ });
2100
2195
  atom.contractIds.forEach((id) => scenarioContractIds.add(id));
2101
2196
  if (atom.contractIds.length > 0) {
2102
2197
  scTags.forEach((id) => scWithContracts.add(id));
@@ -2174,6 +2269,22 @@ async function validateTraceability(root, config) {
2174
2269
  }
2175
2270
  }
2176
2271
  }
2272
+ if (scIdsInFile.size !== 1) {
2273
+ const invalidScIds = Array.from(scIdsInFile).sort(
2274
+ (a, b) => a.localeCompare(b)
2275
+ );
2276
+ const detail = invalidScIds.length === 0 ? "SC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" : `\u8907\u6570\u306E SC \u304C\u5B58\u5728\u3057\u307E\u3059: ${invalidScIds.join(", ")}`;
2277
+ issues.push(
2278
+ issue6(
2279
+ "QFAI-TRACE-012",
2280
+ `Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
2281
+ "error",
2282
+ file,
2283
+ "traceability.specScOneToOne",
2284
+ invalidScIds
2285
+ )
2286
+ );
2287
+ }
2177
2288
  }
2178
2289
  if (upstreamIds.size === 0) {
2179
2290
  return [
@@ -2222,21 +2333,62 @@ async function validateTraceability(root, config) {
2222
2333
  );
2223
2334
  }
2224
2335
  }
2225
- if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
2226
- const scTestRefs = await collectScTestReferences(testsRoot);
2227
- const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
2228
- const refs = scTestRefs.get(id);
2229
- return !refs || refs.size === 0;
2230
- });
2231
- if (scWithoutTests.length > 0) {
2336
+ const scRefsResult = await collectScTestReferences(
2337
+ root,
2338
+ config.validation.traceability.testFileGlobs,
2339
+ config.validation.traceability.testFileExcludeGlobs
2340
+ );
2341
+ const scTestRefs = scRefsResult.refs;
2342
+ const testFileScan = scRefsResult.scan;
2343
+ const hasScenarios = scIdsInScenarios.size > 0;
2344
+ const hasGlobConfig = testFileScan.globs.length > 0;
2345
+ const hasMatchedTests = testFileScan.matchedFileCount > 0;
2346
+ if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
2347
+ const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
2348
+ issues.push(
2349
+ issue6(
2350
+ "QFAI-TRACE-013",
2351
+ `\u30C6\u30B9\u30C8\u63A2\u7D22 glob \u304C\u672A\u8A2D\u5B9A/\u4E0D\u6B63/\u4E00\u81F4\u30D5\u30A1\u30A4\u30EB0\u306E\u305F\u3081 SC\u2192Test \u3092\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002${detail}`,
2352
+ "error",
2353
+ testsRoot,
2354
+ "traceability.testFileGlobs"
2355
+ )
2356
+ );
2357
+ } else {
2358
+ if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
2359
+ const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
2360
+ const refs = scTestRefs.get(id);
2361
+ return !refs || refs.size === 0;
2362
+ });
2363
+ if (scWithoutTests.length > 0) {
2364
+ issues.push(
2365
+ issue6(
2366
+ "QFAI-TRACE-010",
2367
+ `SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
2368
+ ", "
2369
+ )}\u3002testFileGlobs \u306B\u4E00\u81F4\u3059\u308B\u30C6\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u3078 QFAI:SC-xxxx \u3092\u8A18\u8F09\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
2370
+ config.validation.traceability.scNoTestSeverity,
2371
+ testsRoot,
2372
+ "traceability.scMustHaveTest",
2373
+ scWithoutTests
2374
+ )
2375
+ );
2376
+ }
2377
+ }
2378
+ const unknownScIds = Array.from(scTestRefs.keys()).filter(
2379
+ (id) => !scIdsInScenarios.has(id)
2380
+ );
2381
+ if (unknownScIds.length > 0) {
2232
2382
  issues.push(
2233
2383
  issue6(
2234
- "QFAI-TRACE-010",
2235
- `SC \u304C tests \u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(", ")}\u3002tests/ \u914D\u4E0B\u306E\u30C6\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\uFF08.ts/.tsx/.js/.jsx\uFF09\u306B SC ID \u3092\u30B3\u30E1\u30F3\u30C8\u307E\u305F\u306F\u30B3\u30FC\u30C9\u3067\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
2236
- config.validation.traceability.scNoTestSeverity,
2384
+ "QFAI-TRACE-011",
2385
+ `\u30C6\u30B9\u30C8\u304C\u672A\u77E5\u306E SC \u3092\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownScIds.join(
2386
+ ", "
2387
+ )}`,
2388
+ "error",
2237
2389
  testsRoot,
2238
- "traceability.scMustHaveTest",
2239
- scWithoutTests
2390
+ "traceability.scUnknownInTests",
2391
+ unknownScIds
2240
2392
  )
2241
2393
  );
2242
2394
  }
@@ -2299,8 +2451,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
2299
2451
  issues.push(
2300
2452
  issue6(
2301
2453
  "QFAI-TRACE-002",
2302
- "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
2303
- "warning",
2454
+ "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\uFF08\u53C2\u8003\u60C5\u5831\uFF09\u3002",
2455
+ "info",
2304
2456
  srcRoot,
2305
2457
  "traceability.codeReferences"
2306
2458
  )
@@ -2343,11 +2495,24 @@ async function validateProject(root, configResult) {
2343
2495
  ...await validateDefinedIds(root, config),
2344
2496
  ...await validateTraceability(root, config)
2345
2497
  ];
2498
+ const specsRoot = resolvePath(root, config, "specsDir");
2499
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
2500
+ const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
2501
+ const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
2502
+ root,
2503
+ config.validation.traceability.testFileGlobs,
2504
+ config.validation.traceability.testFileExcludeGlobs
2505
+ );
2506
+ const scCoverage = buildScCoverage(scIds, scTestRefs);
2346
2507
  const toolVersion = await resolveToolVersion();
2347
2508
  return {
2348
2509
  toolVersion,
2349
2510
  issues,
2350
- counts: countIssues(issues)
2511
+ counts: countIssues(issues),
2512
+ traceability: {
2513
+ sc: scCoverage,
2514
+ testFiles
2515
+ }
2351
2516
  };
2352
2517
  }
2353
2518
  function countIssues(issues) {
@@ -2368,9 +2533,9 @@ async function createReportData(root, validation, configResult) {
2368
2533
  const configPath = resolved.configPath;
2369
2534
  const specsRoot = resolvePath(root, config, "specsDir");
2370
2535
  const contractsRoot = resolvePath(root, config, "contractsDir");
2371
- const apiRoot = import_node_path13.default.join(contractsRoot, "api");
2372
- const uiRoot = import_node_path13.default.join(contractsRoot, "ui");
2373
- const dbRoot = import_node_path13.default.join(contractsRoot, "db");
2536
+ const apiRoot = import_node_path14.default.join(contractsRoot, "api");
2537
+ const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
2538
+ const dbRoot = import_node_path14.default.join(contractsRoot, "db");
2374
2539
  const srcRoot = resolvePath(root, config, "srcDir");
2375
2540
  const testsRoot = resolvePath(root, config, "testsDir");
2376
2541
  const specFiles = await collectSpecFiles(specsRoot);
@@ -2397,8 +2562,15 @@ async function createReportData(root, validation, configResult) {
2397
2562
  testsRoot
2398
2563
  );
2399
2564
  const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
2400
- const scTestRefs = await collectScTestReferences(testsRoot);
2401
- const scCoverage = buildScCoverage(scIds, scTestRefs);
2565
+ const scRefsResult = await collectScTestReferences(
2566
+ root,
2567
+ config.validation.traceability.testFileGlobs,
2568
+ config.validation.traceability.testFileExcludeGlobs
2569
+ );
2570
+ const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
2571
+ const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
2572
+ const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
2573
+ const scSourceRecord = mapToSortedRecord(scSources);
2402
2574
  const resolvedValidation = validation ?? await validateProject(root, resolved);
2403
2575
  const version = await resolveToolVersion();
2404
2576
  return {
@@ -2428,7 +2600,9 @@ async function createReportData(root, validation, configResult) {
2428
2600
  traceability: {
2429
2601
  upstreamIdsFound: upstreamIds.size,
2430
2602
  referencedInCodeOrTests: traceability,
2431
- sc: scCoverage
2603
+ sc: scCoverage,
2604
+ scSources: scSourceRecord,
2605
+ testFiles
2432
2606
  },
2433
2607
  issues: resolvedValidation.issues
2434
2608
  };
@@ -2469,10 +2643,29 @@ function formatReportMarkdown(data) {
2469
2643
  lines.push(`- total: ${data.traceability.sc.total}`);
2470
2644
  lines.push(`- covered: ${data.traceability.sc.covered}`);
2471
2645
  lines.push(`- missing: ${data.traceability.sc.missing}`);
2646
+ lines.push(
2647
+ `- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
2648
+ );
2649
+ lines.push(
2650
+ `- testFileExcludeGlobs: ${formatList(
2651
+ data.traceability.testFiles.excludeGlobs
2652
+ )}`
2653
+ );
2654
+ lines.push(
2655
+ `- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
2656
+ );
2472
2657
  if (data.traceability.sc.missingIds.length === 0) {
2473
2658
  lines.push("- missingIds: (none)");
2474
2659
  } else {
2475
- lines.push(`- missingIds: ${data.traceability.sc.missingIds.join(", ")}`);
2660
+ const sources = data.traceability.scSources;
2661
+ const missingWithSources = data.traceability.sc.missingIds.map((id) => {
2662
+ const files = sources[id] ?? [];
2663
+ if (files.length === 0) {
2664
+ return id;
2665
+ }
2666
+ return `${id} (${files.join(", ")})`;
2667
+ });
2668
+ lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
2476
2669
  }
2477
2670
  lines.push("");
2478
2671
  lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
@@ -2491,6 +2684,20 @@ function formatReportMarkdown(data) {
2491
2684
  }
2492
2685
  }
2493
2686
  lines.push("");
2687
+ lines.push("## Spec:SC=1:1 \u9055\u53CD");
2688
+ const specScIssues = data.issues.filter(
2689
+ (item) => item.code === "QFAI-TRACE-012"
2690
+ );
2691
+ if (specScIssues.length === 0) {
2692
+ lines.push("- (none)");
2693
+ } else {
2694
+ for (const item of specScIssues) {
2695
+ const location = item.file ?? "(unknown)";
2696
+ const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
2697
+ lines.push(`- ${location}: ${refs}`);
2698
+ }
2699
+ }
2700
+ lines.push("");
2494
2701
  lines.push("## Hotspots");
2495
2702
  const hotspots = buildHotspots(data.issues);
2496
2703
  if (hotspots.length === 0) {
@@ -2601,9 +2808,22 @@ function formatIdLine(label, values) {
2601
2808
  }
2602
2809
  return `- ${label}: ${values.join(", ")}`;
2603
2810
  }
2811
+ function formatList(values) {
2812
+ if (values.length === 0) {
2813
+ return "(none)";
2814
+ }
2815
+ return values.join(", ");
2816
+ }
2604
2817
  function toSortedArray2(values) {
2605
2818
  return Array.from(values).sort((a, b) => a.localeCompare(b));
2606
2819
  }
2820
+ function mapToSortedRecord(values) {
2821
+ const record2 = {};
2822
+ for (const [key, files] of values.entries()) {
2823
+ record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
2824
+ }
2825
+ return record2;
2826
+ }
2607
2827
  function buildHotspots(issues) {
2608
2828
  const map = /* @__PURE__ */ new Map();
2609
2829
  for (const issue7 of issues) {
@@ -2628,10 +2848,10 @@ function buildHotspots(issues) {
2628
2848
 
2629
2849
  // src/cli/commands/report.ts
2630
2850
  async function runReport(options) {
2631
- const root = import_node_path14.default.resolve(options.root);
2851
+ const root = import_node_path15.default.resolve(options.root);
2632
2852
  const configResult = await loadConfig(root);
2633
2853
  const input = configResult.config.output.validateJsonPath;
2634
- const inputPath = import_node_path14.default.isAbsolute(input) ? input : import_node_path14.default.resolve(root, input);
2854
+ const inputPath = import_node_path15.default.isAbsolute(input) ? input : import_node_path15.default.resolve(root, input);
2635
2855
  let validation;
2636
2856
  try {
2637
2857
  validation = await readValidationResult(inputPath);
@@ -2656,10 +2876,10 @@ async function runReport(options) {
2656
2876
  const data = await createReportData(root, validation, configResult);
2657
2877
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
2658
2878
  const outRoot = resolvePath(root, configResult.config, "outDir");
2659
- const defaultOut = options.format === "json" ? import_node_path14.default.join(outRoot, "report.json") : import_node_path14.default.join(outRoot, "report.md");
2879
+ const defaultOut = options.format === "json" ? import_node_path15.default.join(outRoot, "report.json") : import_node_path15.default.join(outRoot, "report.md");
2660
2880
  const out = options.outPath ?? defaultOut;
2661
- const outPath = import_node_path14.default.isAbsolute(out) ? out : import_node_path14.default.resolve(root, out);
2662
- await (0, import_promises16.mkdir)(import_node_path14.default.dirname(outPath), { recursive: true });
2881
+ const outPath = import_node_path15.default.isAbsolute(out) ? out : import_node_path15.default.resolve(root, out);
2882
+ await (0, import_promises16.mkdir)(import_node_path15.default.dirname(outPath), { recursive: true });
2663
2883
  await (0, import_promises16.writeFile)(outPath, `${output}
2664
2884
  `, "utf-8");
2665
2885
  info(
@@ -2702,7 +2922,7 @@ function isMissingFileError5(error2) {
2702
2922
 
2703
2923
  // src/cli/commands/validate.ts
2704
2924
  var import_promises17 = require("fs/promises");
2705
- var import_node_path15 = __toESM(require("path"), 1);
2925
+ var import_node_path16 = __toESM(require("path"), 1);
2706
2926
 
2707
2927
  // src/cli/lib/failOn.ts
2708
2928
  function shouldFail(result, failOn) {
@@ -2717,7 +2937,7 @@ function shouldFail(result, failOn) {
2717
2937
 
2718
2938
  // src/cli/commands/validate.ts
2719
2939
  async function runValidate(options) {
2720
- const root = import_node_path15.default.resolve(options.root);
2940
+ const root = import_node_path16.default.resolve(options.root);
2721
2941
  const configResult = await loadConfig(root);
2722
2942
  const result = await validateProject(root, configResult);
2723
2943
  const format = options.format ?? "text";
@@ -2766,8 +2986,8 @@ function emitGitHub(issue7) {
2766
2986
  );
2767
2987
  }
2768
2988
  async function emitJson(result, root, jsonPath) {
2769
- const abs = import_node_path15.default.isAbsolute(jsonPath) ? jsonPath : import_node_path15.default.resolve(root, jsonPath);
2770
- await (0, import_promises17.mkdir)(import_node_path15.default.dirname(abs), { recursive: true });
2989
+ const abs = import_node_path16.default.isAbsolute(jsonPath) ? jsonPath : import_node_path16.default.resolve(root, jsonPath);
2990
+ await (0, import_promises17.mkdir)(import_node_path16.default.dirname(abs), { recursive: true });
2771
2991
  await (0, import_promises17.writeFile)(abs, `${JSON.stringify(result, null, 2)}
2772
2992
  `, "utf-8");
2773
2993
  }