qfai 0.3.1 → 0.3.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.
@@ -160,7 +160,7 @@ function report(copied, skipped, dryRun, label) {
160
160
  }
161
161
 
162
162
  // src/cli/commands/report.ts
163
- var import_promises13 = require("fs/promises");
163
+ var import_promises15 = require("fs/promises");
164
164
  var import_node_path14 = __toESM(require("path"), 1);
165
165
 
166
166
  // src/core/config.ts
@@ -497,11 +497,11 @@ function isRecord(value) {
497
497
  }
498
498
 
499
499
  // src/core/report.ts
500
- var import_promises12 = require("fs/promises");
500
+ var import_promises14 = require("fs/promises");
501
501
  var import_node_path13 = __toESM(require("path"), 1);
502
502
 
503
503
  // src/core/discovery.ts
504
- var import_node_path6 = __toESM(require("path"), 1);
504
+ var import_promises5 = require("fs/promises");
505
505
 
506
506
  // src/core/fs.ts
507
507
  var import_promises3 = require("fs/promises");
@@ -558,25 +558,50 @@ async function exists2(target) {
558
558
  }
559
559
  }
560
560
 
561
- // src/core/discovery.ts
562
- var SPEC_PACK_DIR_PATTERN = /^spec-\d{3}$/;
563
- async function collectSpecPackDirs(specsRoot) {
564
- const files = await collectFiles(specsRoot, { extensions: [".md"] });
565
- const packs = /* @__PURE__ */ new Set();
566
- for (const file of files) {
567
- if (isSpecPackFile(file, "spec.md")) {
568
- packs.add(import_node_path6.default.dirname(file));
561
+ // src/core/specLayout.ts
562
+ var import_promises4 = require("fs/promises");
563
+ var import_node_path6 = __toESM(require("path"), 1);
564
+ var SPEC_DIR_RE = /^spec-\d{4}$/;
565
+ async function collectSpecEntries(specsRoot) {
566
+ const dirs = await listSpecDirs(specsRoot);
567
+ const entries = dirs.map((dir) => ({
568
+ dir,
569
+ specPath: import_node_path6.default.join(dir, "spec.md"),
570
+ deltaPath: import_node_path6.default.join(dir, "delta.md"),
571
+ scenarioPath: import_node_path6.default.join(dir, "scenario.md")
572
+ }));
573
+ return entries.sort((a, b) => a.dir.localeCompare(b.dir));
574
+ }
575
+ async function listSpecDirs(specsRoot) {
576
+ try {
577
+ const items = await (0, import_promises4.readdir)(specsRoot, { withFileTypes: true });
578
+ return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => import_node_path6.default.join(specsRoot, name));
579
+ } catch (error2) {
580
+ if (isMissingFileError(error2)) {
581
+ return [];
569
582
  }
583
+ throw error2;
584
+ }
585
+ }
586
+ function isMissingFileError(error2) {
587
+ if (!error2 || typeof error2 !== "object") {
588
+ return false;
570
589
  }
571
- return Array.from(packs).sort();
590
+ return error2.code === "ENOENT";
591
+ }
592
+
593
+ // src/core/discovery.ts
594
+ async function collectSpecPackDirs(specsRoot) {
595
+ const entries = await collectSpecEntries(specsRoot);
596
+ return entries.map((entry) => entry.dir);
572
597
  }
573
598
  async function collectSpecFiles(specsRoot) {
574
- const files = await collectFiles(specsRoot, { extensions: [".md"] });
575
- return files.filter((file) => isSpecPackFile(file, "spec.md"));
599
+ const entries = await collectSpecEntries(specsRoot);
600
+ return filterExisting(entries.map((entry) => entry.specPath));
576
601
  }
577
602
  async function collectScenarioFiles(specsRoot) {
578
- const files = await collectFiles(specsRoot, { extensions: [".md"] });
579
- return files.filter((file) => isSpecPackFile(file, "scenario.md"));
603
+ const entries = await collectSpecEntries(specsRoot);
604
+ return filterExisting(entries.map((entry) => entry.scenarioPath));
580
605
  }
581
606
  async function collectUiContractFiles(uiRoot) {
582
607
  return collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
@@ -595,12 +620,22 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
595
620
  ]);
596
621
  return { ui, api, db };
597
622
  }
598
- function isSpecPackFile(filePath, baseName) {
599
- if (import_node_path6.default.basename(filePath).toLowerCase() !== baseName) {
623
+ async function filterExisting(files) {
624
+ const existing = [];
625
+ for (const file of files) {
626
+ if (await exists3(file)) {
627
+ existing.push(file);
628
+ }
629
+ }
630
+ return existing;
631
+ }
632
+ async function exists3(target) {
633
+ try {
634
+ await (0, import_promises5.access)(target);
635
+ return true;
636
+ } catch {
600
637
  return false;
601
638
  }
602
- const dirName = import_node_path6.default.basename(import_node_path6.default.dirname(filePath)).toLowerCase();
603
- return SPEC_PACK_DIR_PATTERN.test(dirName);
604
639
  }
605
640
 
606
641
  // src/core/ids.ts
@@ -660,16 +695,16 @@ function isValidId(value, prefix) {
660
695
  var VALIDATION_SCHEMA_VERSION = "0.2";
661
696
 
662
697
  // src/core/version.ts
663
- var import_promises4 = require("fs/promises");
698
+ var import_promises6 = require("fs/promises");
664
699
  var import_node_path7 = __toESM(require("path"), 1);
665
700
  var import_node_url2 = require("url");
666
701
  async function resolveToolVersion() {
667
- if ("0.3.1".length > 0) {
668
- return "0.3.1";
702
+ if ("0.3.2".length > 0) {
703
+ return "0.3.2";
669
704
  }
670
705
  try {
671
706
  const packagePath = resolvePackageJsonPath();
672
- const raw = await (0, import_promises4.readFile)(packagePath, "utf-8");
707
+ const raw = await (0, import_promises6.readFile)(packagePath, "utf-8");
673
708
  const parsed = JSON.parse(raw);
674
709
  const version = typeof parsed.version === "string" ? parsed.version : "";
675
710
  return version.length > 0 ? version : "unknown";
@@ -684,7 +719,7 @@ function resolvePackageJsonPath() {
684
719
  }
685
720
 
686
721
  // src/core/validators/contracts.ts
687
- var import_promises5 = require("fs/promises");
722
+ var import_promises7 = require("fs/promises");
688
723
  var import_node_path9 = __toESM(require("path"), 1);
689
724
 
690
725
  // src/core/contracts.ts
@@ -762,7 +797,7 @@ async function validateUiContracts(uiRoot) {
762
797
  }
763
798
  const issues = [];
764
799
  for (const file of files) {
765
- const text = await (0, import_promises5.readFile)(file, "utf-8");
800
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
766
801
  const invalidIds = extractInvalidIds(text, [
767
802
  "SPEC",
768
803
  "BR",
@@ -829,7 +864,7 @@ async function validateApiContracts(apiRoot) {
829
864
  }
830
865
  const issues = [];
831
866
  for (const file of files) {
832
- const text = await (0, import_promises5.readFile)(file, "utf-8");
867
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
833
868
  const invalidIds = extractInvalidIds(text, [
834
869
  "SPEC",
835
870
  "BR",
@@ -907,7 +942,7 @@ async function validateDataContracts(dataRoot) {
907
942
  }
908
943
  const issues = [];
909
944
  for (const file of files) {
910
- const text = await (0, import_promises5.readFile)(file, "utf-8");
945
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
911
946
  const invalidIds = extractInvalidIds(text, [
912
947
  "SPEC",
913
948
  "BR",
@@ -978,7 +1013,7 @@ function issue(code, message, severity, file, rule, refs) {
978
1013
  }
979
1014
 
980
1015
  // src/core/validators/delta.ts
981
- var import_promises6 = require("fs/promises");
1016
+ var import_promises8 = require("fs/promises");
982
1017
  var import_node_path10 = __toESM(require("path"), 1);
983
1018
  var SECTION_RE = /^##\s+変更区分/m;
984
1019
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
@@ -996,9 +1031,9 @@ async function validateDeltas(root, config) {
996
1031
  const deltaPath = import_node_path10.default.join(pack, "delta.md");
997
1032
  let text;
998
1033
  try {
999
- text = await (0, import_promises6.readFile)(deltaPath, "utf-8");
1034
+ text = await (0, import_promises8.readFile)(deltaPath, "utf-8");
1000
1035
  } catch (error2) {
1001
- if (isMissingFileError(error2)) {
1036
+ if (isMissingFileError2(error2)) {
1002
1037
  issues.push(
1003
1038
  issue2(
1004
1039
  "QFAI-DELTA-001",
@@ -1043,7 +1078,7 @@ async function validateDeltas(root, config) {
1043
1078
  }
1044
1079
  return issues;
1045
1080
  }
1046
- function isMissingFileError(error2) {
1081
+ function isMissingFileError2(error2) {
1047
1082
  if (!error2 || typeof error2 !== "object") {
1048
1083
  return false;
1049
1084
  }
@@ -1068,11 +1103,11 @@ function issue2(code, message, severity, file, rule, refs) {
1068
1103
  }
1069
1104
 
1070
1105
  // src/core/validators/ids.ts
1071
- var import_promises8 = require("fs/promises");
1106
+ var import_promises10 = require("fs/promises");
1072
1107
  var import_node_path12 = __toESM(require("path"), 1);
1073
1108
 
1074
1109
  // src/core/contractIndex.ts
1075
- var import_promises7 = require("fs/promises");
1110
+ var import_promises9 = require("fs/promises");
1076
1111
  var import_node_path11 = __toESM(require("path"), 1);
1077
1112
  async function buildContractIndex(root, config) {
1078
1113
  const contractsRoot = resolvePath(root, config, "contractsDir");
@@ -1097,7 +1132,7 @@ async function buildContractIndex(root, config) {
1097
1132
  }
1098
1133
  async function indexUiContracts(files, index) {
1099
1134
  for (const file of files) {
1100
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1135
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1101
1136
  try {
1102
1137
  const doc = parseStructuredContract(file, text);
1103
1138
  extractUiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1109,7 +1144,7 @@ async function indexUiContracts(files, index) {
1109
1144
  }
1110
1145
  async function indexApiContracts(files, index) {
1111
1146
  for (const file of files) {
1112
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1147
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1113
1148
  try {
1114
1149
  const doc = parseStructuredContract(file, text);
1115
1150
  extractApiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1121,7 +1156,7 @@ async function indexApiContracts(files, index) {
1121
1156
  }
1122
1157
  async function indexDataContracts(files, index) {
1123
1158
  for (const file of files) {
1124
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1159
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1125
1160
  extractIds(text, "DATA").forEach((id) => record(index, id, file));
1126
1161
  }
1127
1162
  }
@@ -1132,66 +1167,6 @@ function record(index, id, file) {
1132
1167
  index.idToFiles.set(id, current);
1133
1168
  }
1134
1169
 
1135
- // src/core/parse/gherkin.ts
1136
- var FEATURE_RE = /^\s*Feature:\s+/;
1137
- var SCENARIO_RE = /^\s*Scenario(?: Outline)?:\s*(.+)\s*$/;
1138
- var TAG_LINE_RE = /^\s*@/;
1139
- function parseTags(line) {
1140
- return line.trim().split(/\s+/).filter((tag) => tag.startsWith("@")).map((tag) => tag.replace(/^@/, ""));
1141
- }
1142
- function parseGherkinFeature(text, file) {
1143
- const lines = text.split(/\r?\n/);
1144
- const scenarios = [];
1145
- let featurePresent = false;
1146
- let featureTags = [];
1147
- let pendingTags = [];
1148
- let current = null;
1149
- const flush = () => {
1150
- if (!current) return;
1151
- scenarios.push({
1152
- ...current,
1153
- body: current.body.trim()
1154
- });
1155
- current = null;
1156
- };
1157
- for (let i = 0; i < lines.length; i++) {
1158
- const line = lines[i] ?? "";
1159
- const trimmed = line.trim();
1160
- if (TAG_LINE_RE.test(trimmed)) {
1161
- pendingTags.push(...parseTags(trimmed));
1162
- continue;
1163
- }
1164
- if (FEATURE_RE.test(trimmed)) {
1165
- featurePresent = true;
1166
- featureTags = [...pendingTags];
1167
- pendingTags = [];
1168
- continue;
1169
- }
1170
- const match = trimmed.match(SCENARIO_RE);
1171
- if (match) {
1172
- const scenarioName = match[1]?.trim();
1173
- if (!scenarioName) {
1174
- continue;
1175
- }
1176
- flush();
1177
- current = {
1178
- name: scenarioName,
1179
- line: i + 1,
1180
- tags: [...featureTags, ...pendingTags],
1181
- body: ""
1182
- };
1183
- pendingTags = [];
1184
- continue;
1185
- }
1186
- if (current) {
1187
- current.body += `${line}
1188
- `;
1189
- }
1190
- }
1191
- flush();
1192
- return { file, featurePresent, scenarios };
1193
- }
1194
-
1195
1170
  // src/core/parse/markdown.ts
1196
1171
  var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1197
1172
  function parseHeadings(md) {
@@ -1310,8 +1285,162 @@ function parseSpec(md, file) {
1310
1285
  return parsed;
1311
1286
  }
1312
1287
 
1313
- // src/core/validators/ids.ts
1288
+ // src/core/gherkin/parse.ts
1289
+ var import_gherkin = require("@cucumber/gherkin");
1290
+ var import_node_crypto = require("crypto");
1291
+ function parseGherkin(source, uri) {
1292
+ const errors = [];
1293
+ const uuidFn = () => (0, import_node_crypto.randomUUID)();
1294
+ const builder = new import_gherkin.AstBuilder(uuidFn);
1295
+ const matcher = new import_gherkin.GherkinClassicTokenMatcher();
1296
+ const parser = new import_gherkin.Parser(builder, matcher);
1297
+ try {
1298
+ const gherkinDocument = parser.parse(source);
1299
+ gherkinDocument.uri = uri;
1300
+ return { gherkinDocument, errors };
1301
+ } catch (error2) {
1302
+ errors.push(formatError3(error2));
1303
+ return { gherkinDocument: null, errors };
1304
+ }
1305
+ }
1306
+ function formatError3(error2) {
1307
+ if (error2 instanceof Error) {
1308
+ return error2.message;
1309
+ }
1310
+ return String(error2);
1311
+ }
1312
+
1313
+ // src/core/scenarioModel.ts
1314
+ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
1314
1315
  var SC_TAG_RE = /^SC-\d{4}$/;
1316
+ var BR_TAG_RE = /^BR-\d{4}$/;
1317
+ var UI_TAG_RE = /^UI-\d{4}$/;
1318
+ var API_TAG_RE = /^API-\d{4}$/;
1319
+ var DATA_TAG_RE = /^DATA-\d{4}$/;
1320
+ function parseScenarioDocument(text, uri) {
1321
+ const { gherkinDocument, errors } = parseGherkin(text, uri);
1322
+ if (!gherkinDocument) {
1323
+ return { document: null, errors };
1324
+ }
1325
+ const feature = gherkinDocument.feature;
1326
+ if (!feature) {
1327
+ return {
1328
+ document: { uri, featureTags: [], scenarios: [] },
1329
+ errors
1330
+ };
1331
+ }
1332
+ const featureTags = collectTagNames(feature.tags);
1333
+ const scenarios = collectScenarioNodes(feature, featureTags);
1334
+ return {
1335
+ document: {
1336
+ uri,
1337
+ featureName: feature.name,
1338
+ featureTags,
1339
+ scenarios
1340
+ },
1341
+ errors
1342
+ };
1343
+ }
1344
+ function buildScenarioAtoms(document) {
1345
+ return document.scenarios.map((scenario) => {
1346
+ const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
1347
+ const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
1348
+ const brIds = unique2(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
1349
+ const contractIds = /* @__PURE__ */ new Set();
1350
+ scenario.tags.forEach((tag) => {
1351
+ if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
1352
+ contractIds.add(tag);
1353
+ }
1354
+ });
1355
+ for (const step of scenario.steps) {
1356
+ for (const text of collectStepTexts(step)) {
1357
+ extractIds(text, "UI").forEach((id) => contractIds.add(id));
1358
+ extractIds(text, "API").forEach((id) => contractIds.add(id));
1359
+ extractIds(text, "DATA").forEach((id) => contractIds.add(id));
1360
+ }
1361
+ }
1362
+ const atom = {
1363
+ uri: document.uri,
1364
+ featureName: document.featureName ?? "",
1365
+ scenarioName: scenario.name,
1366
+ kind: scenario.kind,
1367
+ brIds,
1368
+ contractIds: Array.from(contractIds).sort()
1369
+ };
1370
+ if (scenario.line !== void 0) {
1371
+ atom.line = scenario.line;
1372
+ }
1373
+ if (specIds.length === 1) {
1374
+ const specId = specIds[0];
1375
+ if (specId) {
1376
+ atom.specId = specId;
1377
+ }
1378
+ }
1379
+ if (scIds.length === 1) {
1380
+ const scId = scIds[0];
1381
+ if (scId) {
1382
+ atom.scId = scId;
1383
+ }
1384
+ }
1385
+ return atom;
1386
+ });
1387
+ }
1388
+ function collectScenarioNodes(feature, featureTags) {
1389
+ const scenarios = [];
1390
+ for (const child of feature.children) {
1391
+ if (child.scenario) {
1392
+ scenarios.push(buildScenarioNode(child.scenario, featureTags, []));
1393
+ }
1394
+ if (child.rule) {
1395
+ const ruleTags = collectTagNames(child.rule.tags);
1396
+ for (const ruleChild of child.rule.children) {
1397
+ if (ruleChild.scenario) {
1398
+ scenarios.push(
1399
+ buildScenarioNode(ruleChild.scenario, featureTags, ruleTags)
1400
+ );
1401
+ }
1402
+ }
1403
+ }
1404
+ }
1405
+ return scenarios;
1406
+ }
1407
+ function buildScenarioNode(scenario, featureTags, ruleTags) {
1408
+ const tags = [...featureTags, ...ruleTags, ...collectTagNames(scenario.tags)];
1409
+ const kind = scenario.examples.length > 0 ? "ScenarioOutline" : "Scenario";
1410
+ return {
1411
+ name: scenario.name,
1412
+ kind,
1413
+ line: scenario.location?.line,
1414
+ tags,
1415
+ steps: scenario.steps
1416
+ };
1417
+ }
1418
+ function collectTagNames(tags) {
1419
+ return tags.map((tag) => tag.name.replace(/^@/, ""));
1420
+ }
1421
+ function collectStepTexts(step) {
1422
+ const texts = [];
1423
+ if (step.text) {
1424
+ texts.push(step.text);
1425
+ }
1426
+ if (step.docString?.content) {
1427
+ texts.push(step.docString.content);
1428
+ }
1429
+ if (step.dataTable?.rows) {
1430
+ for (const row of step.dataTable.rows) {
1431
+ for (const cell of row.cells) {
1432
+ texts.push(cell.value);
1433
+ }
1434
+ }
1435
+ }
1436
+ return texts;
1437
+ }
1438
+ function unique2(values) {
1439
+ return Array.from(new Set(values));
1440
+ }
1441
+
1442
+ // src/core/validators/ids.ts
1443
+ var SC_TAG_RE2 = /^SC-\d{4}$/;
1315
1444
  async function validateDefinedIds(root, config) {
1316
1445
  const issues = [];
1317
1446
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -1345,7 +1474,7 @@ async function validateDefinedIds(root, config) {
1345
1474
  }
1346
1475
  async function collectSpecDefinitionIds(files, out) {
1347
1476
  for (const file of files) {
1348
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1477
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1349
1478
  const parsed = parseSpec(text, file);
1350
1479
  if (parsed.specId) {
1351
1480
  recordId(out, parsed.specId, file);
@@ -1355,11 +1484,14 @@ async function collectSpecDefinitionIds(files, out) {
1355
1484
  }
1356
1485
  async function collectScenarioDefinitionIds(files, out) {
1357
1486
  for (const file of files) {
1358
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1359
- const parsed = parseGherkinFeature(text, file);
1360
- for (const scenario of parsed.scenarios) {
1487
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1488
+ const { document, errors } = parseScenarioDocument(text, file);
1489
+ if (!document || errors.length > 0) {
1490
+ continue;
1491
+ }
1492
+ for (const scenario of document.scenarios) {
1361
1493
  for (const tag of scenario.tags) {
1362
- if (SC_TAG_RE.test(tag)) {
1494
+ if (SC_TAG_RE2.test(tag)) {
1363
1495
  recordId(out, tag, file);
1364
1496
  }
1365
1497
  }
@@ -1396,21 +1528,23 @@ function issue3(code, message, severity, file, rule, refs) {
1396
1528
  }
1397
1529
 
1398
1530
  // src/core/validators/scenario.ts
1399
- var import_promises9 = require("fs/promises");
1531
+ var import_promises11 = require("fs/promises");
1400
1532
  var GIVEN_PATTERN = /\bGiven\b/;
1401
1533
  var WHEN_PATTERN = /\bWhen\b/;
1402
1534
  var THEN_PATTERN = /\bThen\b/;
1403
- var SC_TAG_RE2 = /^SC-\d{4}$/;
1404
- var SPEC_TAG_RE = /^SPEC-\d{4}$/;
1405
- var BR_TAG_RE = /^BR-\d{4}$/;
1535
+ var SC_TAG_RE3 = /^SC-\d{4}$/;
1536
+ var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1537
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
1406
1538
  async function validateScenarios(root, config) {
1407
1539
  const specsRoot = resolvePath(root, config, "specsDir");
1408
- const files = await collectScenarioFiles(specsRoot);
1409
- if (files.length === 0) {
1540
+ const entries = await collectSpecEntries(specsRoot);
1541
+ if (entries.length === 0) {
1542
+ const expected = "spec-0001/scenario.md";
1543
+ const legacy = "spec-001/scenario.md";
1410
1544
  return [
1411
1545
  issue4(
1412
1546
  "QFAI-SC-000",
1413
- "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1547
+ `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)`,
1414
1548
  "info",
1415
1549
  specsRoot,
1416
1550
  "scenario.files"
@@ -1418,15 +1552,31 @@ async function validateScenarios(root, config) {
1418
1552
  ];
1419
1553
  }
1420
1554
  const issues = [];
1421
- for (const file of files) {
1422
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1423
- issues.push(...validateScenarioContent(text, file));
1555
+ for (const entry of entries) {
1556
+ let text;
1557
+ try {
1558
+ text = await (0, import_promises11.readFile)(entry.scenarioPath, "utf-8");
1559
+ } catch (error2) {
1560
+ if (isMissingFileError3(error2)) {
1561
+ issues.push(
1562
+ issue4(
1563
+ "QFAI-SC-001",
1564
+ "scenario.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1565
+ "error",
1566
+ entry.scenarioPath,
1567
+ "scenario.exists"
1568
+ )
1569
+ );
1570
+ continue;
1571
+ }
1572
+ throw error2;
1573
+ }
1574
+ issues.push(...validateScenarioContent(text, entry.scenarioPath));
1424
1575
  }
1425
1576
  return issues;
1426
1577
  }
1427
1578
  function validateScenarioContent(text, file) {
1428
1579
  const issues = [];
1429
- const parsed = parseGherkinFeature(text, file);
1430
1580
  const invalidIds = extractInvalidIds(text, [
1431
1581
  "SPEC",
1432
1582
  "BR",
@@ -1448,9 +1598,47 @@ function validateScenarioContent(text, file) {
1448
1598
  )
1449
1599
  );
1450
1600
  }
1601
+ const { document, errors } = parseScenarioDocument(text, file);
1602
+ if (!document || errors.length > 0) {
1603
+ issues.push(
1604
+ issue4(
1605
+ "QFAI-SC-010",
1606
+ `Gherkin \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${errors.join(", ") || "unknown"}`,
1607
+ "error",
1608
+ file,
1609
+ "scenario.parse"
1610
+ )
1611
+ );
1612
+ return issues;
1613
+ }
1614
+ const featureSpecTags = document.featureTags.filter(
1615
+ (tag) => SPEC_TAG_RE2.test(tag)
1616
+ );
1617
+ if (featureSpecTags.length === 0) {
1618
+ issues.push(
1619
+ issue4(
1620
+ "QFAI-SC-009",
1621
+ "Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1622
+ "error",
1623
+ file,
1624
+ "scenario.featureSpec"
1625
+ )
1626
+ );
1627
+ } else if (featureSpecTags.length > 1) {
1628
+ issues.push(
1629
+ issue4(
1630
+ "QFAI-SC-009",
1631
+ `Feature \u306E SPEC \u30BF\u30B0\u304C\u8907\u6570\u3042\u308A\u307E\u3059: ${featureSpecTags.join(", ")}`,
1632
+ "error",
1633
+ file,
1634
+ "scenario.featureSpec",
1635
+ featureSpecTags
1636
+ )
1637
+ );
1638
+ }
1451
1639
  const missingStructure = [];
1452
- if (!parsed.featurePresent) missingStructure.push("Feature");
1453
- if (parsed.scenarios.length === 0) missingStructure.push("Scenario");
1640
+ if (!document.featureName) missingStructure.push("Feature");
1641
+ if (document.scenarios.length === 0) missingStructure.push("Scenario");
1454
1642
  if (missingStructure.length > 0) {
1455
1643
  issues.push(
1456
1644
  issue4(
@@ -1464,7 +1652,7 @@ function validateScenarioContent(text, file) {
1464
1652
  )
1465
1653
  );
1466
1654
  }
1467
- for (const scenario of parsed.scenarios) {
1655
+ for (const scenario of document.scenarios) {
1468
1656
  if (scenario.tags.length === 0) {
1469
1657
  issues.push(
1470
1658
  issue4(
@@ -1478,16 +1666,16 @@ function validateScenarioContent(text, file) {
1478
1666
  continue;
1479
1667
  }
1480
1668
  const missingTags = [];
1481
- const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
1669
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE3.test(tag));
1482
1670
  if (scTags.length === 0) {
1483
1671
  missingTags.push("SC(0\u4EF6)");
1484
1672
  } else if (scTags.length > 1) {
1485
1673
  missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
1486
1674
  }
1487
- if (!scenario.tags.some((tag) => SPEC_TAG_RE.test(tag))) {
1675
+ if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
1488
1676
  missingTags.push("SPEC");
1489
1677
  }
1490
- if (!scenario.tags.some((tag) => BR_TAG_RE.test(tag))) {
1678
+ if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
1491
1679
  missingTags.push("BR");
1492
1680
  }
1493
1681
  if (missingTags.length > 0) {
@@ -1502,15 +1690,16 @@ function validateScenarioContent(text, file) {
1502
1690
  );
1503
1691
  }
1504
1692
  }
1505
- for (const scenario of parsed.scenarios) {
1693
+ for (const scenario of document.scenarios) {
1506
1694
  const missingSteps = [];
1507
- if (!GIVEN_PATTERN.test(scenario.body)) {
1695
+ const keywords = scenario.steps.map((step) => step.keyword.trim());
1696
+ if (!keywords.some((keyword) => GIVEN_PATTERN.test(keyword))) {
1508
1697
  missingSteps.push("Given");
1509
1698
  }
1510
- if (!WHEN_PATTERN.test(scenario.body)) {
1699
+ if (!keywords.some((keyword) => WHEN_PATTERN.test(keyword))) {
1511
1700
  missingSteps.push("When");
1512
1701
  }
1513
- if (!THEN_PATTERN.test(scenario.body)) {
1702
+ if (!keywords.some((keyword) => THEN_PATTERN.test(keyword))) {
1514
1703
  missingSteps.push("Then");
1515
1704
  }
1516
1705
  if (missingSteps.length > 0) {
@@ -1544,18 +1733,25 @@ function issue4(code, message, severity, file, rule, refs) {
1544
1733
  }
1545
1734
  return issue7;
1546
1735
  }
1736
+ function isMissingFileError3(error2) {
1737
+ if (!error2 || typeof error2 !== "object") {
1738
+ return false;
1739
+ }
1740
+ return error2.code === "ENOENT";
1741
+ }
1547
1742
 
1548
1743
  // src/core/validators/spec.ts
1549
- var import_promises10 = require("fs/promises");
1744
+ var import_promises12 = require("fs/promises");
1550
1745
  async function validateSpecs(root, config) {
1551
1746
  const specsRoot = resolvePath(root, config, "specsDir");
1552
- const files = await collectSpecFiles(specsRoot);
1553
- if (files.length === 0) {
1554
- const expected = "spec-001/spec.md";
1747
+ const entries = await collectSpecEntries(specsRoot);
1748
+ if (entries.length === 0) {
1749
+ const expected = "spec-0001/spec.md";
1750
+ const legacy = "spec-001/spec.md";
1555
1751
  return [
1556
1752
  issue5(
1557
1753
  "QFAI-SPEC-000",
1558
- `Spec \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}`,
1754
+ `Spec \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)`,
1559
1755
  "info",
1560
1756
  specsRoot,
1561
1757
  "spec.files"
@@ -1563,12 +1759,29 @@ async function validateSpecs(root, config) {
1563
1759
  ];
1564
1760
  }
1565
1761
  const issues = [];
1566
- for (const file of files) {
1567
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1762
+ for (const entry of entries) {
1763
+ let text;
1764
+ try {
1765
+ text = await (0, import_promises12.readFile)(entry.specPath, "utf-8");
1766
+ } catch (error2) {
1767
+ if (isMissingFileError4(error2)) {
1768
+ issues.push(
1769
+ issue5(
1770
+ "QFAI-SPEC-005",
1771
+ "spec.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1772
+ "error",
1773
+ entry.specPath,
1774
+ "spec.exists"
1775
+ )
1776
+ );
1777
+ continue;
1778
+ }
1779
+ throw error2;
1780
+ }
1568
1781
  issues.push(
1569
1782
  ...validateSpecContent(
1570
1783
  text,
1571
- file,
1784
+ entry.specPath,
1572
1785
  config.validation.require.specSections
1573
1786
  )
1574
1787
  );
@@ -1690,15 +1903,18 @@ function issue5(code, message, severity, file, rule, refs) {
1690
1903
  }
1691
1904
  return issue7;
1692
1905
  }
1906
+ function isMissingFileError4(error2) {
1907
+ if (!error2 || typeof error2 !== "object") {
1908
+ return false;
1909
+ }
1910
+ return error2.code === "ENOENT";
1911
+ }
1693
1912
 
1694
1913
  // src/core/validators/traceability.ts
1695
- var import_promises11 = require("fs/promises");
1696
- var SC_TAG_RE3 = /^SC-\d{4}$/;
1697
- var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1698
- var BR_TAG_RE2 = /^BR-\d{4}$/;
1699
- var UI_TAG_RE = /^UI-\d{4}$/;
1700
- var API_TAG_RE = /^API-\d{4}$/;
1701
- var DATA_TAG_RE = /^DATA-\d{4}$/;
1914
+ var import_promises13 = require("fs/promises");
1915
+ var SC_TAG_RE4 = /^SC-\d{4}$/;
1916
+ var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
1917
+ var BR_TAG_RE3 = /^BR-\d{4}$/;
1702
1918
  async function validateTraceability(root, config) {
1703
1919
  const issues = [];
1704
1920
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -1717,7 +1933,7 @@ async function validateTraceability(root, config) {
1717
1933
  const contractIndex = await buildContractIndex(root, config);
1718
1934
  const contractIds = contractIndex.ids;
1719
1935
  for (const file of specFiles) {
1720
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1936
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1721
1937
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1722
1938
  const parsed = parseSpec(text, file);
1723
1939
  if (parsed.specId) {
@@ -1754,106 +1970,99 @@ async function validateTraceability(root, config) {
1754
1970
  }
1755
1971
  }
1756
1972
  for (const file of scenarioFiles) {
1757
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1973
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1758
1974
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1759
- const parsed = parseGherkinFeature(text, file);
1760
- const specIdsInScenario = /* @__PURE__ */ new Set();
1761
- const brIds = /* @__PURE__ */ new Set();
1762
- const scIds = /* @__PURE__ */ new Set();
1763
- const scenarioIds = /* @__PURE__ */ new Set();
1764
- for (const scenario of parsed.scenarios) {
1765
- for (const tag of scenario.tags) {
1766
- if (SPEC_TAG_RE2.test(tag)) {
1767
- specIdsInScenario.add(tag);
1768
- }
1769
- if (BR_TAG_RE2.test(tag)) {
1770
- brIds.add(tag);
1771
- }
1772
- if (SC_TAG_RE3.test(tag)) {
1773
- scIds.add(tag);
1774
- }
1775
- if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
1776
- scenarioIds.add(tag);
1777
- }
1778
- }
1779
- }
1780
- const specIdsList = Array.from(specIdsInScenario);
1781
- const brIdsList = Array.from(brIds);
1782
- const scIdsList = Array.from(scIds);
1783
- const scenarioIdsList = Array.from(scenarioIds);
1784
- brIdsList.forEach((id) => brIdsInScenarios.add(id));
1785
- scIdsList.forEach((id) => scIdsInScenarios.add(id));
1786
- scenarioIdsList.forEach((id) => scenarioContractIds.add(id));
1787
- if (scenarioIdsList.length > 0) {
1788
- scIdsList.forEach((id) => scWithContracts.add(id));
1789
- }
1790
- const unknownSpecIds = specIdsList.filter((id) => !specIds.has(id));
1791
- if (unknownSpecIds.length > 0) {
1792
- issues.push(
1793
- issue6(
1794
- "QFAI-TRACE-005",
1795
- `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1796
- "error",
1797
- file,
1798
- "traceability.scenarioSpecExists",
1799
- unknownSpecIds
1800
- )
1801
- );
1802
- }
1803
- const unknownBrIds = brIdsList.filter((id) => !brIdsInSpecs.has(id));
1804
- if (unknownBrIds.length > 0) {
1805
- issues.push(
1806
- issue6(
1807
- "QFAI-TRACE-006",
1808
- `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1809
- "error",
1810
- file,
1811
- "traceability.scenarioBrExists",
1812
- unknownBrIds
1813
- )
1814
- );
1815
- }
1816
- const unknownContractIds = scenarioIdsList.filter(
1817
- (id) => !contractIds.has(id)
1818
- );
1819
- if (unknownContractIds.length > 0) {
1820
- issues.push(
1821
- issue6(
1822
- "QFAI-TRACE-008",
1823
- `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1824
- ", "
1825
- )}`,
1826
- config.validation.traceability.unknownContractIdSeverity,
1827
- file,
1828
- "traceability.scenarioContractExists",
1829
- unknownContractIds
1830
- )
1831
- );
1975
+ const { document, errors } = parseScenarioDocument(text, file);
1976
+ if (!document || errors.length > 0) {
1977
+ continue;
1832
1978
  }
1833
- if (specIdsList.length > 0) {
1834
- const allowedBrIds = /* @__PURE__ */ new Set();
1835
- for (const specId of specIdsList) {
1836
- const brIdsForSpec = specToBrIds.get(specId);
1837
- if (!brIdsForSpec) {
1838
- continue;
1839
- }
1840
- brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1979
+ const atoms = buildScenarioAtoms(document);
1980
+ for (const [index, scenario] of document.scenarios.entries()) {
1981
+ const atom = atoms[index];
1982
+ if (!atom) {
1983
+ continue;
1984
+ }
1985
+ const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
1986
+ const brTags = scenario.tags.filter((tag) => BR_TAG_RE3.test(tag));
1987
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE4.test(tag));
1988
+ brTags.forEach((id) => brIdsInScenarios.add(id));
1989
+ scTags.forEach((id) => scIdsInScenarios.add(id));
1990
+ atom.contractIds.forEach((id) => scenarioContractIds.add(id));
1991
+ if (atom.contractIds.length > 0) {
1992
+ scTags.forEach((id) => scWithContracts.add(id));
1841
1993
  }
1842
- const invalidBrIds = brIdsList.filter((id) => !allowedBrIds.has(id));
1843
- if (invalidBrIds.length > 0) {
1994
+ const unknownSpecIds = specTags.filter((id) => !specIds.has(id));
1995
+ if (unknownSpecIds.length > 0) {
1844
1996
  issues.push(
1845
1997
  issue6(
1846
- "QFAI-TRACE-007",
1847
- `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1998
+ "QFAI-TRACE-005",
1999
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(
1848
2000
  ", "
1849
- )} (SPEC: ${specIdsList.join(", ")})`,
2001
+ )} (${scenario.name})`,
1850
2002
  "error",
1851
2003
  file,
1852
- "traceability.scenarioBrUnderSpec",
1853
- invalidBrIds
2004
+ "traceability.scenarioSpecExists",
2005
+ unknownSpecIds
1854
2006
  )
1855
2007
  );
1856
2008
  }
2009
+ const unknownBrIds = brTags.filter((id) => !brIdsInSpecs.has(id));
2010
+ if (unknownBrIds.length > 0) {
2011
+ issues.push(
2012
+ issue6(
2013
+ "QFAI-TRACE-006",
2014
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(
2015
+ ", "
2016
+ )} (${scenario.name})`,
2017
+ "error",
2018
+ file,
2019
+ "traceability.scenarioBrExists",
2020
+ unknownBrIds
2021
+ )
2022
+ );
2023
+ }
2024
+ const unknownContractIds = atom.contractIds.filter(
2025
+ (id) => !contractIds.has(id)
2026
+ );
2027
+ if (unknownContractIds.length > 0) {
2028
+ issues.push(
2029
+ issue6(
2030
+ "QFAI-TRACE-008",
2031
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
2032
+ ", "
2033
+ )} (${scenario.name})`,
2034
+ config.validation.traceability.unknownContractIdSeverity,
2035
+ file,
2036
+ "traceability.scenarioContractExists",
2037
+ unknownContractIds
2038
+ )
2039
+ );
2040
+ }
2041
+ if (specTags.length > 0 && brTags.length > 0) {
2042
+ const allowedBrIds = /* @__PURE__ */ new Set();
2043
+ for (const specId of specTags) {
2044
+ const brIdsForSpec = specToBrIds.get(specId);
2045
+ if (!brIdsForSpec) {
2046
+ continue;
2047
+ }
2048
+ brIdsForSpec.forEach((id) => allowedBrIds.add(id));
2049
+ }
2050
+ const invalidBrIds = brTags.filter((id) => !allowedBrIds.has(id));
2051
+ if (invalidBrIds.length > 0) {
2052
+ issues.push(
2053
+ issue6(
2054
+ "QFAI-TRACE-007",
2055
+ `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
2056
+ ", "
2057
+ )} (SPEC: ${specTags.join(", ")}) (${scenario.name})`,
2058
+ "error",
2059
+ file,
2060
+ "traceability.scenarioBrUnderSpec",
2061
+ invalidBrIds
2062
+ )
2063
+ );
2064
+ }
2065
+ }
1857
2066
  }
1858
2067
  }
1859
2068
  if (upstreamIds.size === 0) {
@@ -1951,7 +2160,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1951
2160
  const pattern = buildIdPattern(Array.from(upstreamIds));
1952
2161
  let found = false;
1953
2162
  for (const file of targetFiles) {
1954
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2163
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1955
2164
  if (pattern.test(text)) {
1956
2165
  found = true;
1957
2166
  break;
@@ -2178,7 +2387,7 @@ async function collectIds(files) {
2178
2387
  DATA: /* @__PURE__ */ new Set()
2179
2388
  };
2180
2389
  for (const file of files) {
2181
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2390
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2182
2391
  for (const prefix of ID_PREFIXES2) {
2183
2392
  const ids = extractIds(text, prefix);
2184
2393
  ids.forEach((id) => result[prefix].add(id));
@@ -2196,7 +2405,7 @@ async function collectIds(files) {
2196
2405
  async function collectUpstreamIds(files) {
2197
2406
  const ids = /* @__PURE__ */ new Set();
2198
2407
  for (const file of files) {
2199
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2408
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2200
2409
  extractAllIds(text).forEach((id) => ids.add(id));
2201
2410
  }
2202
2411
  return ids;
@@ -2217,7 +2426,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
2217
2426
  }
2218
2427
  const pattern = buildIdPattern2(Array.from(upstreamIds));
2219
2428
  for (const file of targetFiles) {
2220
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2429
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2221
2430
  if (pattern.test(text)) {
2222
2431
  return true;
2223
2432
  }
@@ -2269,7 +2478,7 @@ async function runReport(options) {
2269
2478
  try {
2270
2479
  validation = await readValidationResult(inputPath);
2271
2480
  } catch (err) {
2272
- if (isMissingFileError2(err)) {
2481
+ if (isMissingFileError5(err)) {
2273
2482
  error(
2274
2483
  [
2275
2484
  `qfai report: \u5165\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${inputPath}`,
@@ -2292,8 +2501,8 @@ async function runReport(options) {
2292
2501
  const defaultOut = options.format === "json" ? import_node_path14.default.join(outRoot, "report.json") : import_node_path14.default.join(outRoot, "report.md");
2293
2502
  const out = options.outPath ?? defaultOut;
2294
2503
  const outPath = import_node_path14.default.isAbsolute(out) ? out : import_node_path14.default.resolve(root, out);
2295
- await (0, import_promises13.mkdir)(import_node_path14.default.dirname(outPath), { recursive: true });
2296
- await (0, import_promises13.writeFile)(outPath, `${output}
2504
+ await (0, import_promises15.mkdir)(import_node_path14.default.dirname(outPath), { recursive: true });
2505
+ await (0, import_promises15.writeFile)(outPath, `${output}
2297
2506
  `, "utf-8");
2298
2507
  info(
2299
2508
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -2301,7 +2510,7 @@ async function runReport(options) {
2301
2510
  info(`wrote report: ${outPath}`);
2302
2511
  }
2303
2512
  async function readValidationResult(inputPath) {
2304
- const raw = await (0, import_promises13.readFile)(inputPath, "utf-8");
2513
+ const raw = await (0, import_promises15.readFile)(inputPath, "utf-8");
2305
2514
  const parsed = JSON.parse(raw);
2306
2515
  if (!isValidationResult(parsed)) {
2307
2516
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -2333,7 +2542,7 @@ function isValidationResult(value) {
2333
2542
  }
2334
2543
  return typeof counts.info === "number" && typeof counts.warning === "number" && typeof counts.error === "number";
2335
2544
  }
2336
- function isMissingFileError2(error2) {
2545
+ function isMissingFileError5(error2) {
2337
2546
  if (!error2 || typeof error2 !== "object") {
2338
2547
  return false;
2339
2548
  }
@@ -2342,7 +2551,7 @@ function isMissingFileError2(error2) {
2342
2551
  }
2343
2552
 
2344
2553
  // src/cli/commands/validate.ts
2345
- var import_promises14 = require("fs/promises");
2554
+ var import_promises16 = require("fs/promises");
2346
2555
  var import_node_path15 = __toESM(require("path"), 1);
2347
2556
 
2348
2557
  // src/cli/lib/failOn.ts
@@ -2408,8 +2617,8 @@ function emitGitHub(issue7) {
2408
2617
  }
2409
2618
  async function emitJson(result, root, jsonPath) {
2410
2619
  const abs = import_node_path15.default.isAbsolute(jsonPath) ? jsonPath : import_node_path15.default.resolve(root, jsonPath);
2411
- await (0, import_promises14.mkdir)(import_node_path15.default.dirname(abs), { recursive: true });
2412
- await (0, import_promises14.writeFile)(abs, `${JSON.stringify(result, null, 2)}
2620
+ await (0, import_promises16.mkdir)(import_node_path15.default.dirname(abs), { recursive: true });
2621
+ await (0, import_promises16.writeFile)(abs, `${JSON.stringify(result, null, 2)}
2413
2622
  `, "utf-8");
2414
2623
  }
2415
2624