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
@@ -155,7 +155,7 @@ function report(copied, skipped, dryRun, label) {
155
155
 
156
156
  // src/cli/commands/report.ts
157
157
  import { mkdir as mkdir2, readFile as readFile12, writeFile } from "fs/promises";
158
- import path14 from "path";
158
+ import path15 from "path";
159
159
 
160
160
  // src/core/config.ts
161
161
  import { readFile } from "fs/promises";
@@ -188,6 +188,8 @@ var defaultConfig = {
188
188
  brMustHaveSc: true,
189
189
  scMustTouchContracts: true,
190
190
  scMustHaveTest: true,
191
+ testFileGlobs: [],
192
+ testFileExcludeGlobs: [],
191
193
  scNoTestSeverity: "error",
192
194
  allowOrphanContracts: false,
193
195
  unknownContractIdSeverity: "error"
@@ -375,6 +377,20 @@ function normalizeValidation(raw, configPath, issues) {
375
377
  configPath,
376
378
  issues
377
379
  ),
380
+ testFileGlobs: readStringArray(
381
+ traceabilityRaw?.testFileGlobs,
382
+ base.traceability.testFileGlobs,
383
+ "validation.traceability.testFileGlobs",
384
+ configPath,
385
+ issues
386
+ ),
387
+ testFileExcludeGlobs: readStringArray(
388
+ traceabilityRaw?.testFileExcludeGlobs,
389
+ base.traceability.testFileExcludeGlobs,
390
+ "validation.traceability.testFileExcludeGlobs",
391
+ configPath,
392
+ issues
393
+ ),
378
394
  scNoTestSeverity: readTraceabilitySeverity(
379
395
  traceabilityRaw?.scNoTestSeverity,
380
396
  base.traceability.scNoTestSeverity,
@@ -508,7 +524,7 @@ function isRecord(value) {
508
524
 
509
525
  // src/core/report.ts
510
526
  import { readFile as readFile11 } from "fs/promises";
511
- import path13 from "path";
527
+ import path14 from "path";
512
528
 
513
529
  // src/core/discovery.ts
514
530
  import { access as access3 } from "fs/promises";
@@ -516,6 +532,7 @@ import { access as access3 } from "fs/promises";
516
532
  // src/core/fs.ts
517
533
  import { access as access2, readdir as readdir2 } from "fs/promises";
518
534
  import path5 from "path";
535
+ import fg from "fast-glob";
519
536
  var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
520
537
  "node_modules",
521
538
  ".git",
@@ -537,6 +554,18 @@ async function collectFiles(root, options = {}) {
537
554
  await walk(root, root, ignoreDirs, extensions, entries);
538
555
  return entries;
539
556
  }
557
+ async function collectFilesByGlobs(root, options) {
558
+ if (options.globs.length === 0) {
559
+ return [];
560
+ }
561
+ return fg(options.globs, {
562
+ cwd: root,
563
+ ignore: options.ignore ?? [],
564
+ onlyFiles: true,
565
+ absolute: true,
566
+ unique: true
567
+ });
568
+ }
540
569
  async function walk(base, current, ignoreDirs, extensions, out) {
541
570
  const items = await readdir2(current, { withFileTypes: true });
542
571
  for (const item of items) {
@@ -703,6 +732,7 @@ function isValidId(value, prefix) {
703
732
 
704
733
  // src/core/traceability.ts
705
734
  import { readFile as readFile2 } from "fs/promises";
735
+ import path7 from "path";
706
736
 
707
737
  // src/core/gherkin/parse.ts
708
738
  import {
@@ -864,6 +894,27 @@ function unique2(values) {
864
894
 
865
895
  // src/core/traceability.ts
866
896
  var SC_TAG_RE2 = /^SC-\d{4}$/;
897
+ var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
898
+ var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
899
+ "**/node_modules/**",
900
+ "**/.git/**",
901
+ "**/.qfai/**",
902
+ "**/dist/**",
903
+ "**/build/**",
904
+ "**/coverage/**",
905
+ "**/.next/**",
906
+ "**/out/**"
907
+ ];
908
+ function extractAnnotatedScIds(text) {
909
+ const ids = /* @__PURE__ */ new Set();
910
+ for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
911
+ const suffix = match[1];
912
+ if (suffix) {
913
+ ids.add(`SC-${suffix}`);
914
+ }
915
+ }
916
+ return Array.from(ids);
917
+ }
867
918
  async function collectScIdsFromScenarioFiles(scenarioFiles) {
868
919
  const scIds = /* @__PURE__ */ new Set();
869
920
  for (const file of scenarioFiles) {
@@ -882,14 +933,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
882
933
  }
883
934
  return scIds;
884
935
  }
885
- async function collectScTestReferences(testsRoot) {
936
+ async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
937
+ const sources = /* @__PURE__ */ new Map();
938
+ for (const file of scenarioFiles) {
939
+ const text = await readFile2(file, "utf-8");
940
+ const { document, errors } = parseScenarioDocument(text, file);
941
+ if (!document || errors.length > 0) {
942
+ continue;
943
+ }
944
+ for (const scenario of document.scenarios) {
945
+ for (const tag of scenario.tags) {
946
+ if (!SC_TAG_RE2.test(tag)) {
947
+ continue;
948
+ }
949
+ const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
950
+ current.add(file);
951
+ sources.set(tag, current);
952
+ }
953
+ }
954
+ }
955
+ return sources;
956
+ }
957
+ async function collectScTestReferences(root, globs, excludeGlobs) {
886
958
  const refs = /* @__PURE__ */ new Map();
887
- const testFiles = await collectFiles(testsRoot, {
888
- extensions: [".ts", ".tsx", ".js", ".jsx"]
889
- });
890
- for (const file of testFiles) {
959
+ const normalizedGlobs = normalizeGlobs(globs);
960
+ const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
961
+ const mergedExcludeGlobs = Array.from(
962
+ /* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
963
+ );
964
+ if (normalizedGlobs.length === 0) {
965
+ return {
966
+ refs,
967
+ scan: {
968
+ globs: normalizedGlobs,
969
+ excludeGlobs: mergedExcludeGlobs,
970
+ matchedFileCount: 0
971
+ }
972
+ };
973
+ }
974
+ let files = [];
975
+ try {
976
+ files = await collectFilesByGlobs(root, {
977
+ globs: normalizedGlobs,
978
+ ignore: mergedExcludeGlobs
979
+ });
980
+ } catch (error2) {
981
+ return {
982
+ refs,
983
+ scan: {
984
+ globs: normalizedGlobs,
985
+ excludeGlobs: mergedExcludeGlobs,
986
+ matchedFileCount: 0
987
+ },
988
+ error: formatError3(error2)
989
+ };
990
+ }
991
+ const normalizedFiles = Array.from(
992
+ new Set(files.map((file) => path7.normalize(file)))
993
+ );
994
+ for (const file of normalizedFiles) {
891
995
  const text = await readFile2(file, "utf-8");
892
- const scIds = extractIds(text, "SC");
996
+ const scIds = extractAnnotatedScIds(text);
893
997
  if (scIds.length === 0) {
894
998
  continue;
895
999
  }
@@ -899,7 +1003,14 @@ async function collectScTestReferences(testsRoot) {
899
1003
  refs.set(scId, current);
900
1004
  }
901
1005
  }
902
- return refs;
1006
+ return {
1007
+ refs,
1008
+ scan: {
1009
+ globs: normalizedGlobs,
1010
+ excludeGlobs: mergedExcludeGlobs,
1011
+ matchedFileCount: normalizedFiles.length
1012
+ }
1013
+ };
903
1014
  }
904
1015
  function buildScCoverage(scIds, refs) {
905
1016
  const sortedScIds = toSortedArray(scIds);
@@ -927,14 +1038,23 @@ function buildScCoverage(scIds, refs) {
927
1038
  function toSortedArray(values) {
928
1039
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
929
1040
  }
1041
+ function normalizeGlobs(globs) {
1042
+ return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
1043
+ }
1044
+ function formatError3(error2) {
1045
+ if (error2 instanceof Error) {
1046
+ return error2.message;
1047
+ }
1048
+ return String(error2);
1049
+ }
930
1050
 
931
1051
  // src/core/version.ts
932
1052
  import { readFile as readFile3 } from "fs/promises";
933
- import path7 from "path";
1053
+ import path8 from "path";
934
1054
  import { fileURLToPath as fileURLToPath2 } from "url";
935
1055
  async function resolveToolVersion() {
936
- if ("0.4.0".length > 0) {
937
- return "0.4.0";
1056
+ if ("0.4.2".length > 0) {
1057
+ return "0.4.2";
938
1058
  }
939
1059
  try {
940
1060
  const packagePath = resolvePackageJsonPath();
@@ -949,18 +1069,18 @@ async function resolveToolVersion() {
949
1069
  function resolvePackageJsonPath() {
950
1070
  const base = import.meta.url;
951
1071
  const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
952
- return path7.resolve(path7.dirname(basePath), "../../package.json");
1072
+ return path8.resolve(path8.dirname(basePath), "../../package.json");
953
1073
  }
954
1074
 
955
1075
  // src/core/validators/contracts.ts
956
1076
  import { readFile as readFile4 } from "fs/promises";
957
- import path9 from "path";
1077
+ import path10 from "path";
958
1078
 
959
1079
  // src/core/contracts.ts
960
- import path8 from "path";
1080
+ import path9 from "path";
961
1081
  import { parse as parseYaml2 } from "yaml";
962
1082
  function parseStructuredContract(file, text) {
963
- const ext = path8.extname(file).toLowerCase();
1083
+ const ext = path9.extname(file).toLowerCase();
964
1084
  if (ext === ".json") {
965
1085
  return JSON.parse(text);
966
1086
  }
@@ -1011,9 +1131,9 @@ var SQL_DANGEROUS_PATTERNS = [
1011
1131
  async function validateContracts(root, config) {
1012
1132
  const issues = [];
1013
1133
  const contractsRoot = resolvePath(root, config, "contractsDir");
1014
- issues.push(...await validateUiContracts(path9.join(contractsRoot, "ui")));
1015
- issues.push(...await validateApiContracts(path9.join(contractsRoot, "api")));
1016
- issues.push(...await validateDataContracts(path9.join(contractsRoot, "db")));
1134
+ issues.push(...await validateUiContracts(path10.join(contractsRoot, "ui")));
1135
+ issues.push(...await validateApiContracts(path10.join(contractsRoot, "api")));
1136
+ issues.push(...await validateDataContracts(path10.join(contractsRoot, "db")));
1017
1137
  return issues;
1018
1138
  }
1019
1139
  async function validateUiContracts(uiRoot) {
@@ -1060,7 +1180,7 @@ async function validateUiContracts(uiRoot) {
1060
1180
  issues.push(
1061
1181
  issue(
1062
1182
  "QFAI-CONTRACT-001",
1063
- `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError3(error2)})`,
1183
+ `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
1064
1184
  "error",
1065
1185
  file,
1066
1186
  "contracts.ui.parse"
@@ -1127,7 +1247,7 @@ async function validateApiContracts(apiRoot) {
1127
1247
  issues.push(
1128
1248
  issue(
1129
1249
  "QFAI-CONTRACT-001",
1130
- `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError3(error2)})`,
1250
+ `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
1131
1251
  "error",
1132
1252
  file,
1133
1253
  "contracts.api.parse"
@@ -1222,7 +1342,7 @@ function lintSql(text, file) {
1222
1342
  function hasOpenApi(doc) {
1223
1343
  return typeof doc.openapi === "string" && doc.openapi.length > 0;
1224
1344
  }
1225
- function formatError3(error2) {
1345
+ function formatError4(error2) {
1226
1346
  if (error2 instanceof Error) {
1227
1347
  return error2.message;
1228
1348
  }
@@ -1248,7 +1368,7 @@ function issue(code, message, severity, file, rule, refs) {
1248
1368
 
1249
1369
  // src/core/validators/delta.ts
1250
1370
  import { readFile as readFile5 } from "fs/promises";
1251
- import path10 from "path";
1371
+ import path11 from "path";
1252
1372
  var SECTION_RE = /^##\s+変更区分/m;
1253
1373
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
1254
1374
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -1262,7 +1382,7 @@ async function validateDeltas(root, config) {
1262
1382
  }
1263
1383
  const issues = [];
1264
1384
  for (const pack of packs) {
1265
- const deltaPath = path10.join(pack, "delta.md");
1385
+ const deltaPath = path11.join(pack, "delta.md");
1266
1386
  let text;
1267
1387
  try {
1268
1388
  text = await readFile5(deltaPath, "utf-8");
@@ -1338,16 +1458,16 @@ function issue2(code, message, severity, file, rule, refs) {
1338
1458
 
1339
1459
  // src/core/validators/ids.ts
1340
1460
  import { readFile as readFile7 } from "fs/promises";
1341
- import path12 from "path";
1461
+ import path13 from "path";
1342
1462
 
1343
1463
  // src/core/contractIndex.ts
1344
1464
  import { readFile as readFile6 } from "fs/promises";
1345
- import path11 from "path";
1465
+ import path12 from "path";
1346
1466
  async function buildContractIndex(root, config) {
1347
1467
  const contractsRoot = resolvePath(root, config, "contractsDir");
1348
- const uiRoot = path11.join(contractsRoot, "ui");
1349
- const apiRoot = path11.join(contractsRoot, "api");
1350
- const dataRoot = path11.join(contractsRoot, "db");
1468
+ const uiRoot = path12.join(contractsRoot, "ui");
1469
+ const apiRoot = path12.join(contractsRoot, "api");
1470
+ const dataRoot = path12.join(contractsRoot, "db");
1351
1471
  const [uiFiles, apiFiles, dataFiles] = await Promise.all([
1352
1472
  collectUiContractFiles(uiRoot),
1353
1473
  collectApiContractFiles(apiRoot),
@@ -1585,7 +1705,7 @@ function recordId(out, id, file) {
1585
1705
  }
1586
1706
  function formatFileList(files, root) {
1587
1707
  return files.map((file) => {
1588
- const relative = path12.relative(root, file);
1708
+ const relative = path13.relative(root, file);
1589
1709
  return relative.length > 0 ? relative : file;
1590
1710
  }).join(", ");
1591
1711
  }
@@ -1614,7 +1734,6 @@ var WHEN_PATTERN = /\bWhen\b/;
1614
1734
  var THEN_PATTERN = /\bThen\b/;
1615
1735
  var SC_TAG_RE4 = /^SC-\d{4}$/;
1616
1736
  var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1617
- var BR_TAG_RE2 = /^BR-\d{4}$/;
1618
1737
  async function validateScenarios(root, config) {
1619
1738
  const specsRoot = resolvePath(root, config, "specsDir");
1620
1739
  const entries = await collectSpecEntries(specsRoot);
@@ -1694,17 +1813,7 @@ function validateScenarioContent(text, file) {
1694
1813
  const featureSpecTags = document.featureTags.filter(
1695
1814
  (tag) => SPEC_TAG_RE2.test(tag)
1696
1815
  );
1697
- if (featureSpecTags.length === 0) {
1698
- issues.push(
1699
- issue4(
1700
- "QFAI-SC-009",
1701
- "Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1702
- "error",
1703
- file,
1704
- "scenario.featureSpec"
1705
- )
1706
- );
1707
- } else if (featureSpecTags.length > 1) {
1816
+ if (featureSpecTags.length > 1) {
1708
1817
  issues.push(
1709
1818
  issue4(
1710
1819
  "QFAI-SC-009",
@@ -1732,17 +1841,6 @@ function validateScenarioContent(text, file) {
1732
1841
  )
1733
1842
  );
1734
1843
  }
1735
- if (document.scenarios.length > 1) {
1736
- issues.push(
1737
- issue4(
1738
- "QFAI-SC-011",
1739
- `Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
1740
- "error",
1741
- file,
1742
- "scenario.single"
1743
- )
1744
- );
1745
- }
1746
1844
  for (const scenario of document.scenarios) {
1747
1845
  if (scenario.tags.length === 0) {
1748
1846
  issues.push(
@@ -1763,12 +1861,6 @@ function validateScenarioContent(text, file) {
1763
1861
  } else if (scTags.length > 1) {
1764
1862
  missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
1765
1863
  }
1766
- if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
1767
- missingTags.push("SPEC");
1768
- }
1769
- if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
1770
- missingTags.push("BR");
1771
- }
1772
1864
  if (missingTags.length > 0) {
1773
1865
  issues.push(
1774
1866
  issue4(
@@ -2003,9 +2095,8 @@ function isMissingFileError4(error2) {
2003
2095
 
2004
2096
  // src/core/validators/traceability.ts
2005
2097
  import { readFile as readFile10 } from "fs/promises";
2006
- var SC_TAG_RE5 = /^SC-\d{4}$/;
2007
2098
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2008
- var BR_TAG_RE3 = /^BR-\d{4}$/;
2099
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
2009
2100
  async function validateTraceability(root, config) {
2010
2101
  const issues = [];
2011
2102
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -2032,28 +2123,6 @@ async function validateTraceability(root, config) {
2032
2123
  }
2033
2124
  const brIds = parsed.brs.map((br) => br.id);
2034
2125
  brIds.forEach((id) => brIdsInSpecs.add(id));
2035
- const referencedContractIds = /* @__PURE__ */ new Set([
2036
- ...extractIds(text, "UI"),
2037
- ...extractIds(text, "API"),
2038
- ...extractIds(text, "DATA")
2039
- ]);
2040
- const unknownContractIds = Array.from(referencedContractIds).filter(
2041
- (id) => !contractIds.has(id)
2042
- );
2043
- if (unknownContractIds.length > 0) {
2044
- issues.push(
2045
- issue6(
2046
- "QFAI-TRACE-009",
2047
- `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
2048
- ", "
2049
- )}`,
2050
- "error",
2051
- file,
2052
- "traceability.specContractExists",
2053
- unknownContractIds
2054
- )
2055
- );
2056
- }
2057
2126
  if (parsed.specId) {
2058
2127
  const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
2059
2128
  brIds.forEach((id) => current.add(id));
@@ -2068,16 +2137,42 @@ async function validateTraceability(root, config) {
2068
2137
  continue;
2069
2138
  }
2070
2139
  const atoms = buildScenarioAtoms(document);
2140
+ const scIdsInFile = /* @__PURE__ */ new Set();
2071
2141
  for (const [index, scenario] of document.scenarios.entries()) {
2072
2142
  const atom = atoms[index];
2073
2143
  if (!atom) {
2074
2144
  continue;
2075
2145
  }
2076
2146
  const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
2077
- const brTags = scenario.tags.filter((tag) => BR_TAG_RE3.test(tag));
2078
- const scTags = scenario.tags.filter((tag) => SC_TAG_RE5.test(tag));
2147
+ const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
2148
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
2149
+ if (specTags.length === 0) {
2150
+ issues.push(
2151
+ issue6(
2152
+ "QFAI-TRACE-014",
2153
+ `Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
2154
+ "error",
2155
+ file,
2156
+ "traceability.scenarioSpecRequired"
2157
+ )
2158
+ );
2159
+ }
2160
+ if (brTags.length === 0) {
2161
+ issues.push(
2162
+ issue6(
2163
+ "QFAI-TRACE-015",
2164
+ `Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
2165
+ "error",
2166
+ file,
2167
+ "traceability.scenarioBrRequired"
2168
+ )
2169
+ );
2170
+ }
2079
2171
  brTags.forEach((id) => brIdsInScenarios.add(id));
2080
- scTags.forEach((id) => scIdsInScenarios.add(id));
2172
+ scTags.forEach((id) => {
2173
+ scIdsInScenarios.add(id);
2174
+ scIdsInFile.add(id);
2175
+ });
2081
2176
  atom.contractIds.forEach((id) => scenarioContractIds.add(id));
2082
2177
  if (atom.contractIds.length > 0) {
2083
2178
  scTags.forEach((id) => scWithContracts.add(id));
@@ -2155,6 +2250,22 @@ async function validateTraceability(root, config) {
2155
2250
  }
2156
2251
  }
2157
2252
  }
2253
+ if (scIdsInFile.size !== 1) {
2254
+ const invalidScIds = Array.from(scIdsInFile).sort(
2255
+ (a, b) => a.localeCompare(b)
2256
+ );
2257
+ 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(", ")}`;
2258
+ issues.push(
2259
+ issue6(
2260
+ "QFAI-TRACE-012",
2261
+ `Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
2262
+ "error",
2263
+ file,
2264
+ "traceability.specScOneToOne",
2265
+ invalidScIds
2266
+ )
2267
+ );
2268
+ }
2158
2269
  }
2159
2270
  if (upstreamIds.size === 0) {
2160
2271
  return [
@@ -2203,21 +2314,62 @@ async function validateTraceability(root, config) {
2203
2314
  );
2204
2315
  }
2205
2316
  }
2206
- if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
2207
- const scTestRefs = await collectScTestReferences(testsRoot);
2208
- const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
2209
- const refs = scTestRefs.get(id);
2210
- return !refs || refs.size === 0;
2211
- });
2212
- if (scWithoutTests.length > 0) {
2317
+ const scRefsResult = await collectScTestReferences(
2318
+ root,
2319
+ config.validation.traceability.testFileGlobs,
2320
+ config.validation.traceability.testFileExcludeGlobs
2321
+ );
2322
+ const scTestRefs = scRefsResult.refs;
2323
+ const testFileScan = scRefsResult.scan;
2324
+ const hasScenarios = scIdsInScenarios.size > 0;
2325
+ const hasGlobConfig = testFileScan.globs.length > 0;
2326
+ const hasMatchedTests = testFileScan.matchedFileCount > 0;
2327
+ if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
2328
+ const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
2329
+ issues.push(
2330
+ issue6(
2331
+ "QFAI-TRACE-013",
2332
+ `\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}`,
2333
+ "error",
2334
+ testsRoot,
2335
+ "traceability.testFileGlobs"
2336
+ )
2337
+ );
2338
+ } else {
2339
+ if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
2340
+ const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
2341
+ const refs = scTestRefs.get(id);
2342
+ return !refs || refs.size === 0;
2343
+ });
2344
+ if (scWithoutTests.length > 0) {
2345
+ issues.push(
2346
+ issue6(
2347
+ "QFAI-TRACE-010",
2348
+ `SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
2349
+ ", "
2350
+ )}\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`,
2351
+ config.validation.traceability.scNoTestSeverity,
2352
+ testsRoot,
2353
+ "traceability.scMustHaveTest",
2354
+ scWithoutTests
2355
+ )
2356
+ );
2357
+ }
2358
+ }
2359
+ const unknownScIds = Array.from(scTestRefs.keys()).filter(
2360
+ (id) => !scIdsInScenarios.has(id)
2361
+ );
2362
+ if (unknownScIds.length > 0) {
2213
2363
  issues.push(
2214
2364
  issue6(
2215
- "QFAI-TRACE-010",
2216
- `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`,
2217
- config.validation.traceability.scNoTestSeverity,
2365
+ "QFAI-TRACE-011",
2366
+ `\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(
2367
+ ", "
2368
+ )}`,
2369
+ "error",
2218
2370
  testsRoot,
2219
- "traceability.scMustHaveTest",
2220
- scWithoutTests
2371
+ "traceability.scUnknownInTests",
2372
+ unknownScIds
2221
2373
  )
2222
2374
  );
2223
2375
  }
@@ -2280,8 +2432,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
2280
2432
  issues.push(
2281
2433
  issue6(
2282
2434
  "QFAI-TRACE-002",
2283
- "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
2284
- "warning",
2435
+ "\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",
2436
+ "info",
2285
2437
  srcRoot,
2286
2438
  "traceability.codeReferences"
2287
2439
  )
@@ -2324,11 +2476,24 @@ async function validateProject(root, configResult) {
2324
2476
  ...await validateDefinedIds(root, config),
2325
2477
  ...await validateTraceability(root, config)
2326
2478
  ];
2479
+ const specsRoot = resolvePath(root, config, "specsDir");
2480
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
2481
+ const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
2482
+ const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
2483
+ root,
2484
+ config.validation.traceability.testFileGlobs,
2485
+ config.validation.traceability.testFileExcludeGlobs
2486
+ );
2487
+ const scCoverage = buildScCoverage(scIds, scTestRefs);
2327
2488
  const toolVersion = await resolveToolVersion();
2328
2489
  return {
2329
2490
  toolVersion,
2330
2491
  issues,
2331
- counts: countIssues(issues)
2492
+ counts: countIssues(issues),
2493
+ traceability: {
2494
+ sc: scCoverage,
2495
+ testFiles
2496
+ }
2332
2497
  };
2333
2498
  }
2334
2499
  function countIssues(issues) {
@@ -2349,9 +2514,9 @@ async function createReportData(root, validation, configResult) {
2349
2514
  const configPath = resolved.configPath;
2350
2515
  const specsRoot = resolvePath(root, config, "specsDir");
2351
2516
  const contractsRoot = resolvePath(root, config, "contractsDir");
2352
- const apiRoot = path13.join(contractsRoot, "api");
2353
- const uiRoot = path13.join(contractsRoot, "ui");
2354
- const dbRoot = path13.join(contractsRoot, "db");
2517
+ const apiRoot = path14.join(contractsRoot, "api");
2518
+ const uiRoot = path14.join(contractsRoot, "ui");
2519
+ const dbRoot = path14.join(contractsRoot, "db");
2355
2520
  const srcRoot = resolvePath(root, config, "srcDir");
2356
2521
  const testsRoot = resolvePath(root, config, "testsDir");
2357
2522
  const specFiles = await collectSpecFiles(specsRoot);
@@ -2378,8 +2543,15 @@ async function createReportData(root, validation, configResult) {
2378
2543
  testsRoot
2379
2544
  );
2380
2545
  const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
2381
- const scTestRefs = await collectScTestReferences(testsRoot);
2382
- const scCoverage = buildScCoverage(scIds, scTestRefs);
2546
+ const scRefsResult = await collectScTestReferences(
2547
+ root,
2548
+ config.validation.traceability.testFileGlobs,
2549
+ config.validation.traceability.testFileExcludeGlobs
2550
+ );
2551
+ const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
2552
+ const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
2553
+ const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
2554
+ const scSourceRecord = mapToSortedRecord(scSources);
2383
2555
  const resolvedValidation = validation ?? await validateProject(root, resolved);
2384
2556
  const version = await resolveToolVersion();
2385
2557
  return {
@@ -2409,7 +2581,9 @@ async function createReportData(root, validation, configResult) {
2409
2581
  traceability: {
2410
2582
  upstreamIdsFound: upstreamIds.size,
2411
2583
  referencedInCodeOrTests: traceability,
2412
- sc: scCoverage
2584
+ sc: scCoverage,
2585
+ scSources: scSourceRecord,
2586
+ testFiles
2413
2587
  },
2414
2588
  issues: resolvedValidation.issues
2415
2589
  };
@@ -2450,10 +2624,29 @@ function formatReportMarkdown(data) {
2450
2624
  lines.push(`- total: ${data.traceability.sc.total}`);
2451
2625
  lines.push(`- covered: ${data.traceability.sc.covered}`);
2452
2626
  lines.push(`- missing: ${data.traceability.sc.missing}`);
2627
+ lines.push(
2628
+ `- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
2629
+ );
2630
+ lines.push(
2631
+ `- testFileExcludeGlobs: ${formatList(
2632
+ data.traceability.testFiles.excludeGlobs
2633
+ )}`
2634
+ );
2635
+ lines.push(
2636
+ `- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
2637
+ );
2453
2638
  if (data.traceability.sc.missingIds.length === 0) {
2454
2639
  lines.push("- missingIds: (none)");
2455
2640
  } else {
2456
- lines.push(`- missingIds: ${data.traceability.sc.missingIds.join(", ")}`);
2641
+ const sources = data.traceability.scSources;
2642
+ const missingWithSources = data.traceability.sc.missingIds.map((id) => {
2643
+ const files = sources[id] ?? [];
2644
+ if (files.length === 0) {
2645
+ return id;
2646
+ }
2647
+ return `${id} (${files.join(", ")})`;
2648
+ });
2649
+ lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
2457
2650
  }
2458
2651
  lines.push("");
2459
2652
  lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
@@ -2472,6 +2665,20 @@ function formatReportMarkdown(data) {
2472
2665
  }
2473
2666
  }
2474
2667
  lines.push("");
2668
+ lines.push("## Spec:SC=1:1 \u9055\u53CD");
2669
+ const specScIssues = data.issues.filter(
2670
+ (item) => item.code === "QFAI-TRACE-012"
2671
+ );
2672
+ if (specScIssues.length === 0) {
2673
+ lines.push("- (none)");
2674
+ } else {
2675
+ for (const item of specScIssues) {
2676
+ const location = item.file ?? "(unknown)";
2677
+ const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
2678
+ lines.push(`- ${location}: ${refs}`);
2679
+ }
2680
+ }
2681
+ lines.push("");
2475
2682
  lines.push("## Hotspots");
2476
2683
  const hotspots = buildHotspots(data.issues);
2477
2684
  if (hotspots.length === 0) {
@@ -2582,9 +2789,22 @@ function formatIdLine(label, values) {
2582
2789
  }
2583
2790
  return `- ${label}: ${values.join(", ")}`;
2584
2791
  }
2792
+ function formatList(values) {
2793
+ if (values.length === 0) {
2794
+ return "(none)";
2795
+ }
2796
+ return values.join(", ");
2797
+ }
2585
2798
  function toSortedArray2(values) {
2586
2799
  return Array.from(values).sort((a, b) => a.localeCompare(b));
2587
2800
  }
2801
+ function mapToSortedRecord(values) {
2802
+ const record2 = {};
2803
+ for (const [key, files] of values.entries()) {
2804
+ record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
2805
+ }
2806
+ return record2;
2807
+ }
2588
2808
  function buildHotspots(issues) {
2589
2809
  const map = /* @__PURE__ */ new Map();
2590
2810
  for (const issue7 of issues) {
@@ -2609,10 +2829,10 @@ function buildHotspots(issues) {
2609
2829
 
2610
2830
  // src/cli/commands/report.ts
2611
2831
  async function runReport(options) {
2612
- const root = path14.resolve(options.root);
2832
+ const root = path15.resolve(options.root);
2613
2833
  const configResult = await loadConfig(root);
2614
2834
  const input = configResult.config.output.validateJsonPath;
2615
- const inputPath = path14.isAbsolute(input) ? input : path14.resolve(root, input);
2835
+ const inputPath = path15.isAbsolute(input) ? input : path15.resolve(root, input);
2616
2836
  let validation;
2617
2837
  try {
2618
2838
  validation = await readValidationResult(inputPath);
@@ -2637,10 +2857,10 @@ async function runReport(options) {
2637
2857
  const data = await createReportData(root, validation, configResult);
2638
2858
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
2639
2859
  const outRoot = resolvePath(root, configResult.config, "outDir");
2640
- const defaultOut = options.format === "json" ? path14.join(outRoot, "report.json") : path14.join(outRoot, "report.md");
2860
+ const defaultOut = options.format === "json" ? path15.join(outRoot, "report.json") : path15.join(outRoot, "report.md");
2641
2861
  const out = options.outPath ?? defaultOut;
2642
- const outPath = path14.isAbsolute(out) ? out : path14.resolve(root, out);
2643
- await mkdir2(path14.dirname(outPath), { recursive: true });
2862
+ const outPath = path15.isAbsolute(out) ? out : path15.resolve(root, out);
2863
+ await mkdir2(path15.dirname(outPath), { recursive: true });
2644
2864
  await writeFile(outPath, `${output}
2645
2865
  `, "utf-8");
2646
2866
  info(
@@ -2683,7 +2903,7 @@ function isMissingFileError5(error2) {
2683
2903
 
2684
2904
  // src/cli/commands/validate.ts
2685
2905
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
2686
- import path15 from "path";
2906
+ import path16 from "path";
2687
2907
 
2688
2908
  // src/cli/lib/failOn.ts
2689
2909
  function shouldFail(result, failOn) {
@@ -2698,7 +2918,7 @@ function shouldFail(result, failOn) {
2698
2918
 
2699
2919
  // src/cli/commands/validate.ts
2700
2920
  async function runValidate(options) {
2701
- const root = path15.resolve(options.root);
2921
+ const root = path16.resolve(options.root);
2702
2922
  const configResult = await loadConfig(root);
2703
2923
  const result = await validateProject(root, configResult);
2704
2924
  const format = options.format ?? "text";
@@ -2747,8 +2967,8 @@ function emitGitHub(issue7) {
2747
2967
  );
2748
2968
  }
2749
2969
  async function emitJson(result, root, jsonPath) {
2750
- const abs = path15.isAbsolute(jsonPath) ? jsonPath : path15.resolve(root, jsonPath);
2751
- await mkdir3(path15.dirname(abs), { recursive: true });
2970
+ const abs = path16.isAbsolute(jsonPath) ? jsonPath : path16.resolve(root, jsonPath);
2971
+ await mkdir3(path16.dirname(abs), { recursive: true });
2752
2972
  await writeFile2(abs, `${JSON.stringify(result, null, 2)}
2753
2973
  `, "utf-8");
2754
2974
  }