qfai 0.3.1 → 0.3.3

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.
@@ -109,12 +109,29 @@ async function exists(target) {
109
109
  }
110
110
 
111
111
  // src/cli/lib/assets.ts
112
+ var import_node_fs = require("fs");
112
113
  var import_node_path2 = __toESM(require("path"), 1);
113
114
  var import_node_url = require("url");
114
115
  function getInitAssetsDir() {
115
116
  const base = __filename;
116
117
  const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
117
- return import_node_path2.default.resolve(import_node_path2.default.dirname(basePath), "../../assets/init");
118
+ const baseDir = import_node_path2.default.dirname(basePath);
119
+ const candidates = [
120
+ import_node_path2.default.resolve(baseDir, "../../../assets/init"),
121
+ import_node_path2.default.resolve(baseDir, "../../assets/init")
122
+ ];
123
+ for (const candidate of candidates) {
124
+ if ((0, import_node_fs.existsSync)(candidate)) {
125
+ return candidate;
126
+ }
127
+ }
128
+ throw new Error(
129
+ [
130
+ "init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
131
+ "\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
132
+ ...candidates.map((candidate) => `- ${candidate}`)
133
+ ].join("\n")
134
+ );
118
135
  }
119
136
 
120
137
  // src/cli/lib/logger.ts
@@ -160,7 +177,7 @@ function report(copied, skipped, dryRun, label) {
160
177
  }
161
178
 
162
179
  // src/cli/commands/report.ts
163
- var import_promises13 = require("fs/promises");
180
+ var import_promises15 = require("fs/promises");
164
181
  var import_node_path14 = __toESM(require("path"), 1);
165
182
 
166
183
  // src/core/config.ts
@@ -497,11 +514,11 @@ function isRecord(value) {
497
514
  }
498
515
 
499
516
  // src/core/report.ts
500
- var import_promises12 = require("fs/promises");
517
+ var import_promises14 = require("fs/promises");
501
518
  var import_node_path13 = __toESM(require("path"), 1);
502
519
 
503
520
  // src/core/discovery.ts
504
- var import_node_path6 = __toESM(require("path"), 1);
521
+ var import_promises5 = require("fs/promises");
505
522
 
506
523
  // src/core/fs.ts
507
524
  var import_promises3 = require("fs/promises");
@@ -558,25 +575,50 @@ async function exists2(target) {
558
575
  }
559
576
  }
560
577
 
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));
578
+ // src/core/specLayout.ts
579
+ var import_promises4 = require("fs/promises");
580
+ var import_node_path6 = __toESM(require("path"), 1);
581
+ var SPEC_DIR_RE = /^spec-\d{4}$/;
582
+ async function collectSpecEntries(specsRoot) {
583
+ const dirs = await listSpecDirs(specsRoot);
584
+ const entries = dirs.map((dir) => ({
585
+ dir,
586
+ specPath: import_node_path6.default.join(dir, "spec.md"),
587
+ deltaPath: import_node_path6.default.join(dir, "delta.md"),
588
+ scenarioPath: import_node_path6.default.join(dir, "scenario.md")
589
+ }));
590
+ return entries.sort((a, b) => a.dir.localeCompare(b.dir));
591
+ }
592
+ async function listSpecDirs(specsRoot) {
593
+ try {
594
+ const items = await (0, import_promises4.readdir)(specsRoot, { withFileTypes: true });
595
+ 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));
596
+ } catch (error2) {
597
+ if (isMissingFileError(error2)) {
598
+ return [];
569
599
  }
600
+ throw error2;
601
+ }
602
+ }
603
+ function isMissingFileError(error2) {
604
+ if (!error2 || typeof error2 !== "object") {
605
+ return false;
570
606
  }
571
- return Array.from(packs).sort();
607
+ return error2.code === "ENOENT";
608
+ }
609
+
610
+ // src/core/discovery.ts
611
+ async function collectSpecPackDirs(specsRoot) {
612
+ const entries = await collectSpecEntries(specsRoot);
613
+ return entries.map((entry) => entry.dir);
572
614
  }
573
615
  async function collectSpecFiles(specsRoot) {
574
- const files = await collectFiles(specsRoot, { extensions: [".md"] });
575
- return files.filter((file) => isSpecPackFile(file, "spec.md"));
616
+ const entries = await collectSpecEntries(specsRoot);
617
+ return filterExisting(entries.map((entry) => entry.specPath));
576
618
  }
577
619
  async function collectScenarioFiles(specsRoot) {
578
- const files = await collectFiles(specsRoot, { extensions: [".md"] });
579
- return files.filter((file) => isSpecPackFile(file, "scenario.md"));
620
+ const entries = await collectSpecEntries(specsRoot);
621
+ return filterExisting(entries.map((entry) => entry.scenarioPath));
580
622
  }
581
623
  async function collectUiContractFiles(uiRoot) {
582
624
  return collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
@@ -595,12 +637,22 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
595
637
  ]);
596
638
  return { ui, api, db };
597
639
  }
598
- function isSpecPackFile(filePath, baseName) {
599
- if (import_node_path6.default.basename(filePath).toLowerCase() !== baseName) {
640
+ async function filterExisting(files) {
641
+ const existing = [];
642
+ for (const file of files) {
643
+ if (await exists3(file)) {
644
+ existing.push(file);
645
+ }
646
+ }
647
+ return existing;
648
+ }
649
+ async function exists3(target) {
650
+ try {
651
+ await (0, import_promises5.access)(target);
652
+ return true;
653
+ } catch {
600
654
  return false;
601
655
  }
602
- const dirName = import_node_path6.default.basename(import_node_path6.default.dirname(filePath)).toLowerCase();
603
- return SPEC_PACK_DIR_PATTERN.test(dirName);
604
656
  }
605
657
 
606
658
  // src/core/ids.ts
@@ -660,16 +712,16 @@ function isValidId(value, prefix) {
660
712
  var VALIDATION_SCHEMA_VERSION = "0.2";
661
713
 
662
714
  // src/core/version.ts
663
- var import_promises4 = require("fs/promises");
715
+ var import_promises6 = require("fs/promises");
664
716
  var import_node_path7 = __toESM(require("path"), 1);
665
717
  var import_node_url2 = require("url");
666
718
  async function resolveToolVersion() {
667
- if ("0.3.1".length > 0) {
668
- return "0.3.1";
719
+ if ("0.3.3".length > 0) {
720
+ return "0.3.3";
669
721
  }
670
722
  try {
671
723
  const packagePath = resolvePackageJsonPath();
672
- const raw = await (0, import_promises4.readFile)(packagePath, "utf-8");
724
+ const raw = await (0, import_promises6.readFile)(packagePath, "utf-8");
673
725
  const parsed = JSON.parse(raw);
674
726
  const version = typeof parsed.version === "string" ? parsed.version : "";
675
727
  return version.length > 0 ? version : "unknown";
@@ -684,7 +736,7 @@ function resolvePackageJsonPath() {
684
736
  }
685
737
 
686
738
  // src/core/validators/contracts.ts
687
- var import_promises5 = require("fs/promises");
739
+ var import_promises7 = require("fs/promises");
688
740
  var import_node_path9 = __toESM(require("path"), 1);
689
741
 
690
742
  // src/core/contracts.ts
@@ -762,7 +814,7 @@ async function validateUiContracts(uiRoot) {
762
814
  }
763
815
  const issues = [];
764
816
  for (const file of files) {
765
- const text = await (0, import_promises5.readFile)(file, "utf-8");
817
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
766
818
  const invalidIds = extractInvalidIds(text, [
767
819
  "SPEC",
768
820
  "BR",
@@ -829,7 +881,7 @@ async function validateApiContracts(apiRoot) {
829
881
  }
830
882
  const issues = [];
831
883
  for (const file of files) {
832
- const text = await (0, import_promises5.readFile)(file, "utf-8");
884
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
833
885
  const invalidIds = extractInvalidIds(text, [
834
886
  "SPEC",
835
887
  "BR",
@@ -907,7 +959,7 @@ async function validateDataContracts(dataRoot) {
907
959
  }
908
960
  const issues = [];
909
961
  for (const file of files) {
910
- const text = await (0, import_promises5.readFile)(file, "utf-8");
962
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
911
963
  const invalidIds = extractInvalidIds(text, [
912
964
  "SPEC",
913
965
  "BR",
@@ -978,7 +1030,7 @@ function issue(code, message, severity, file, rule, refs) {
978
1030
  }
979
1031
 
980
1032
  // src/core/validators/delta.ts
981
- var import_promises6 = require("fs/promises");
1033
+ var import_promises8 = require("fs/promises");
982
1034
  var import_node_path10 = __toESM(require("path"), 1);
983
1035
  var SECTION_RE = /^##\s+変更区分/m;
984
1036
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
@@ -996,9 +1048,9 @@ async function validateDeltas(root, config) {
996
1048
  const deltaPath = import_node_path10.default.join(pack, "delta.md");
997
1049
  let text;
998
1050
  try {
999
- text = await (0, import_promises6.readFile)(deltaPath, "utf-8");
1051
+ text = await (0, import_promises8.readFile)(deltaPath, "utf-8");
1000
1052
  } catch (error2) {
1001
- if (isMissingFileError(error2)) {
1053
+ if (isMissingFileError2(error2)) {
1002
1054
  issues.push(
1003
1055
  issue2(
1004
1056
  "QFAI-DELTA-001",
@@ -1043,7 +1095,7 @@ async function validateDeltas(root, config) {
1043
1095
  }
1044
1096
  return issues;
1045
1097
  }
1046
- function isMissingFileError(error2) {
1098
+ function isMissingFileError2(error2) {
1047
1099
  if (!error2 || typeof error2 !== "object") {
1048
1100
  return false;
1049
1101
  }
@@ -1068,11 +1120,11 @@ function issue2(code, message, severity, file, rule, refs) {
1068
1120
  }
1069
1121
 
1070
1122
  // src/core/validators/ids.ts
1071
- var import_promises8 = require("fs/promises");
1123
+ var import_promises10 = require("fs/promises");
1072
1124
  var import_node_path12 = __toESM(require("path"), 1);
1073
1125
 
1074
1126
  // src/core/contractIndex.ts
1075
- var import_promises7 = require("fs/promises");
1127
+ var import_promises9 = require("fs/promises");
1076
1128
  var import_node_path11 = __toESM(require("path"), 1);
1077
1129
  async function buildContractIndex(root, config) {
1078
1130
  const contractsRoot = resolvePath(root, config, "contractsDir");
@@ -1097,7 +1149,7 @@ async function buildContractIndex(root, config) {
1097
1149
  }
1098
1150
  async function indexUiContracts(files, index) {
1099
1151
  for (const file of files) {
1100
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1152
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1101
1153
  try {
1102
1154
  const doc = parseStructuredContract(file, text);
1103
1155
  extractUiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1109,7 +1161,7 @@ async function indexUiContracts(files, index) {
1109
1161
  }
1110
1162
  async function indexApiContracts(files, index) {
1111
1163
  for (const file of files) {
1112
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1164
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1113
1165
  try {
1114
1166
  const doc = parseStructuredContract(file, text);
1115
1167
  extractApiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1121,7 +1173,7 @@ async function indexApiContracts(files, index) {
1121
1173
  }
1122
1174
  async function indexDataContracts(files, index) {
1123
1175
  for (const file of files) {
1124
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1176
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1125
1177
  extractIds(text, "DATA").forEach((id) => record(index, id, file));
1126
1178
  }
1127
1179
  }
@@ -1132,66 +1184,6 @@ function record(index, id, file) {
1132
1184
  index.idToFiles.set(id, current);
1133
1185
  }
1134
1186
 
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
1187
  // src/core/parse/markdown.ts
1196
1188
  var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1197
1189
  function parseHeadings(md) {
@@ -1310,8 +1302,162 @@ function parseSpec(md, file) {
1310
1302
  return parsed;
1311
1303
  }
1312
1304
 
1313
- // src/core/validators/ids.ts
1305
+ // src/core/gherkin/parse.ts
1306
+ var import_gherkin = require("@cucumber/gherkin");
1307
+ var import_node_crypto = require("crypto");
1308
+ function parseGherkin(source, uri) {
1309
+ const errors = [];
1310
+ const uuidFn = () => (0, import_node_crypto.randomUUID)();
1311
+ const builder = new import_gherkin.AstBuilder(uuidFn);
1312
+ const matcher = new import_gherkin.GherkinClassicTokenMatcher();
1313
+ const parser = new import_gherkin.Parser(builder, matcher);
1314
+ try {
1315
+ const gherkinDocument = parser.parse(source);
1316
+ gherkinDocument.uri = uri;
1317
+ return { gherkinDocument, errors };
1318
+ } catch (error2) {
1319
+ errors.push(formatError3(error2));
1320
+ return { gherkinDocument: null, errors };
1321
+ }
1322
+ }
1323
+ function formatError3(error2) {
1324
+ if (error2 instanceof Error) {
1325
+ return error2.message;
1326
+ }
1327
+ return String(error2);
1328
+ }
1329
+
1330
+ // src/core/scenarioModel.ts
1331
+ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
1314
1332
  var SC_TAG_RE = /^SC-\d{4}$/;
1333
+ var BR_TAG_RE = /^BR-\d{4}$/;
1334
+ var UI_TAG_RE = /^UI-\d{4}$/;
1335
+ var API_TAG_RE = /^API-\d{4}$/;
1336
+ var DATA_TAG_RE = /^DATA-\d{4}$/;
1337
+ function parseScenarioDocument(text, uri) {
1338
+ const { gherkinDocument, errors } = parseGherkin(text, uri);
1339
+ if (!gherkinDocument) {
1340
+ return { document: null, errors };
1341
+ }
1342
+ const feature = gherkinDocument.feature;
1343
+ if (!feature) {
1344
+ return {
1345
+ document: { uri, featureTags: [], scenarios: [] },
1346
+ errors
1347
+ };
1348
+ }
1349
+ const featureTags = collectTagNames(feature.tags);
1350
+ const scenarios = collectScenarioNodes(feature, featureTags);
1351
+ return {
1352
+ document: {
1353
+ uri,
1354
+ featureName: feature.name,
1355
+ featureTags,
1356
+ scenarios
1357
+ },
1358
+ errors
1359
+ };
1360
+ }
1361
+ function buildScenarioAtoms(document) {
1362
+ return document.scenarios.map((scenario) => {
1363
+ const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
1364
+ const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
1365
+ const brIds = unique2(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
1366
+ const contractIds = /* @__PURE__ */ new Set();
1367
+ scenario.tags.forEach((tag) => {
1368
+ if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
1369
+ contractIds.add(tag);
1370
+ }
1371
+ });
1372
+ for (const step of scenario.steps) {
1373
+ for (const text of collectStepTexts(step)) {
1374
+ extractIds(text, "UI").forEach((id) => contractIds.add(id));
1375
+ extractIds(text, "API").forEach((id) => contractIds.add(id));
1376
+ extractIds(text, "DATA").forEach((id) => contractIds.add(id));
1377
+ }
1378
+ }
1379
+ const atom = {
1380
+ uri: document.uri,
1381
+ featureName: document.featureName ?? "",
1382
+ scenarioName: scenario.name,
1383
+ kind: scenario.kind,
1384
+ brIds,
1385
+ contractIds: Array.from(contractIds).sort()
1386
+ };
1387
+ if (scenario.line !== void 0) {
1388
+ atom.line = scenario.line;
1389
+ }
1390
+ if (specIds.length === 1) {
1391
+ const specId = specIds[0];
1392
+ if (specId) {
1393
+ atom.specId = specId;
1394
+ }
1395
+ }
1396
+ if (scIds.length === 1) {
1397
+ const scId = scIds[0];
1398
+ if (scId) {
1399
+ atom.scId = scId;
1400
+ }
1401
+ }
1402
+ return atom;
1403
+ });
1404
+ }
1405
+ function collectScenarioNodes(feature, featureTags) {
1406
+ const scenarios = [];
1407
+ for (const child of feature.children) {
1408
+ if (child.scenario) {
1409
+ scenarios.push(buildScenarioNode(child.scenario, featureTags, []));
1410
+ }
1411
+ if (child.rule) {
1412
+ const ruleTags = collectTagNames(child.rule.tags);
1413
+ for (const ruleChild of child.rule.children) {
1414
+ if (ruleChild.scenario) {
1415
+ scenarios.push(
1416
+ buildScenarioNode(ruleChild.scenario, featureTags, ruleTags)
1417
+ );
1418
+ }
1419
+ }
1420
+ }
1421
+ }
1422
+ return scenarios;
1423
+ }
1424
+ function buildScenarioNode(scenario, featureTags, ruleTags) {
1425
+ const tags = [...featureTags, ...ruleTags, ...collectTagNames(scenario.tags)];
1426
+ const kind = scenario.examples.length > 0 ? "ScenarioOutline" : "Scenario";
1427
+ return {
1428
+ name: scenario.name,
1429
+ kind,
1430
+ line: scenario.location?.line,
1431
+ tags,
1432
+ steps: scenario.steps
1433
+ };
1434
+ }
1435
+ function collectTagNames(tags) {
1436
+ return tags.map((tag) => tag.name.replace(/^@/, ""));
1437
+ }
1438
+ function collectStepTexts(step) {
1439
+ const texts = [];
1440
+ if (step.text) {
1441
+ texts.push(step.text);
1442
+ }
1443
+ if (step.docString?.content) {
1444
+ texts.push(step.docString.content);
1445
+ }
1446
+ if (step.dataTable?.rows) {
1447
+ for (const row of step.dataTable.rows) {
1448
+ for (const cell of row.cells) {
1449
+ texts.push(cell.value);
1450
+ }
1451
+ }
1452
+ }
1453
+ return texts;
1454
+ }
1455
+ function unique2(values) {
1456
+ return Array.from(new Set(values));
1457
+ }
1458
+
1459
+ // src/core/validators/ids.ts
1460
+ var SC_TAG_RE2 = /^SC-\d{4}$/;
1315
1461
  async function validateDefinedIds(root, config) {
1316
1462
  const issues = [];
1317
1463
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -1345,7 +1491,7 @@ async function validateDefinedIds(root, config) {
1345
1491
  }
1346
1492
  async function collectSpecDefinitionIds(files, out) {
1347
1493
  for (const file of files) {
1348
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1494
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1349
1495
  const parsed = parseSpec(text, file);
1350
1496
  if (parsed.specId) {
1351
1497
  recordId(out, parsed.specId, file);
@@ -1355,11 +1501,14 @@ async function collectSpecDefinitionIds(files, out) {
1355
1501
  }
1356
1502
  async function collectScenarioDefinitionIds(files, out) {
1357
1503
  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) {
1504
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1505
+ const { document, errors } = parseScenarioDocument(text, file);
1506
+ if (!document || errors.length > 0) {
1507
+ continue;
1508
+ }
1509
+ for (const scenario of document.scenarios) {
1361
1510
  for (const tag of scenario.tags) {
1362
- if (SC_TAG_RE.test(tag)) {
1511
+ if (SC_TAG_RE2.test(tag)) {
1363
1512
  recordId(out, tag, file);
1364
1513
  }
1365
1514
  }
@@ -1396,21 +1545,23 @@ function issue3(code, message, severity, file, rule, refs) {
1396
1545
  }
1397
1546
 
1398
1547
  // src/core/validators/scenario.ts
1399
- var import_promises9 = require("fs/promises");
1548
+ var import_promises11 = require("fs/promises");
1400
1549
  var GIVEN_PATTERN = /\bGiven\b/;
1401
1550
  var WHEN_PATTERN = /\bWhen\b/;
1402
1551
  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}$/;
1552
+ var SC_TAG_RE3 = /^SC-\d{4}$/;
1553
+ var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1554
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
1406
1555
  async function validateScenarios(root, config) {
1407
1556
  const specsRoot = resolvePath(root, config, "specsDir");
1408
- const files = await collectScenarioFiles(specsRoot);
1409
- if (files.length === 0) {
1557
+ const entries = await collectSpecEntries(specsRoot);
1558
+ if (entries.length === 0) {
1559
+ const expected = "spec-0001/scenario.md";
1560
+ const legacy = "spec-001/scenario.md";
1410
1561
  return [
1411
1562
  issue4(
1412
1563
  "QFAI-SC-000",
1413
- "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1564
+ `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
1565
  "info",
1415
1566
  specsRoot,
1416
1567
  "scenario.files"
@@ -1418,15 +1569,31 @@ async function validateScenarios(root, config) {
1418
1569
  ];
1419
1570
  }
1420
1571
  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));
1572
+ for (const entry of entries) {
1573
+ let text;
1574
+ try {
1575
+ text = await (0, import_promises11.readFile)(entry.scenarioPath, "utf-8");
1576
+ } catch (error2) {
1577
+ if (isMissingFileError3(error2)) {
1578
+ issues.push(
1579
+ issue4(
1580
+ "QFAI-SC-001",
1581
+ "scenario.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1582
+ "error",
1583
+ entry.scenarioPath,
1584
+ "scenario.exists"
1585
+ )
1586
+ );
1587
+ continue;
1588
+ }
1589
+ throw error2;
1590
+ }
1591
+ issues.push(...validateScenarioContent(text, entry.scenarioPath));
1424
1592
  }
1425
1593
  return issues;
1426
1594
  }
1427
1595
  function validateScenarioContent(text, file) {
1428
1596
  const issues = [];
1429
- const parsed = parseGherkinFeature(text, file);
1430
1597
  const invalidIds = extractInvalidIds(text, [
1431
1598
  "SPEC",
1432
1599
  "BR",
@@ -1448,9 +1615,47 @@ function validateScenarioContent(text, file) {
1448
1615
  )
1449
1616
  );
1450
1617
  }
1618
+ const { document, errors } = parseScenarioDocument(text, file);
1619
+ if (!document || errors.length > 0) {
1620
+ issues.push(
1621
+ issue4(
1622
+ "QFAI-SC-010",
1623
+ `Gherkin \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${errors.join(", ") || "unknown"}`,
1624
+ "error",
1625
+ file,
1626
+ "scenario.parse"
1627
+ )
1628
+ );
1629
+ return issues;
1630
+ }
1631
+ const featureSpecTags = document.featureTags.filter(
1632
+ (tag) => SPEC_TAG_RE2.test(tag)
1633
+ );
1634
+ if (featureSpecTags.length === 0) {
1635
+ issues.push(
1636
+ issue4(
1637
+ "QFAI-SC-009",
1638
+ "Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1639
+ "error",
1640
+ file,
1641
+ "scenario.featureSpec"
1642
+ )
1643
+ );
1644
+ } else if (featureSpecTags.length > 1) {
1645
+ issues.push(
1646
+ issue4(
1647
+ "QFAI-SC-009",
1648
+ `Feature \u306E SPEC \u30BF\u30B0\u304C\u8907\u6570\u3042\u308A\u307E\u3059: ${featureSpecTags.join(", ")}`,
1649
+ "error",
1650
+ file,
1651
+ "scenario.featureSpec",
1652
+ featureSpecTags
1653
+ )
1654
+ );
1655
+ }
1451
1656
  const missingStructure = [];
1452
- if (!parsed.featurePresent) missingStructure.push("Feature");
1453
- if (parsed.scenarios.length === 0) missingStructure.push("Scenario");
1657
+ if (!document.featureName) missingStructure.push("Feature");
1658
+ if (document.scenarios.length === 0) missingStructure.push("Scenario");
1454
1659
  if (missingStructure.length > 0) {
1455
1660
  issues.push(
1456
1661
  issue4(
@@ -1464,7 +1669,7 @@ function validateScenarioContent(text, file) {
1464
1669
  )
1465
1670
  );
1466
1671
  }
1467
- for (const scenario of parsed.scenarios) {
1672
+ for (const scenario of document.scenarios) {
1468
1673
  if (scenario.tags.length === 0) {
1469
1674
  issues.push(
1470
1675
  issue4(
@@ -1478,16 +1683,16 @@ function validateScenarioContent(text, file) {
1478
1683
  continue;
1479
1684
  }
1480
1685
  const missingTags = [];
1481
- const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
1686
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE3.test(tag));
1482
1687
  if (scTags.length === 0) {
1483
1688
  missingTags.push("SC(0\u4EF6)");
1484
1689
  } else if (scTags.length > 1) {
1485
1690
  missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
1486
1691
  }
1487
- if (!scenario.tags.some((tag) => SPEC_TAG_RE.test(tag))) {
1692
+ if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
1488
1693
  missingTags.push("SPEC");
1489
1694
  }
1490
- if (!scenario.tags.some((tag) => BR_TAG_RE.test(tag))) {
1695
+ if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
1491
1696
  missingTags.push("BR");
1492
1697
  }
1493
1698
  if (missingTags.length > 0) {
@@ -1502,15 +1707,16 @@ function validateScenarioContent(text, file) {
1502
1707
  );
1503
1708
  }
1504
1709
  }
1505
- for (const scenario of parsed.scenarios) {
1710
+ for (const scenario of document.scenarios) {
1506
1711
  const missingSteps = [];
1507
- if (!GIVEN_PATTERN.test(scenario.body)) {
1712
+ const keywords = scenario.steps.map((step) => step.keyword.trim());
1713
+ if (!keywords.some((keyword) => GIVEN_PATTERN.test(keyword))) {
1508
1714
  missingSteps.push("Given");
1509
1715
  }
1510
- if (!WHEN_PATTERN.test(scenario.body)) {
1716
+ if (!keywords.some((keyword) => WHEN_PATTERN.test(keyword))) {
1511
1717
  missingSteps.push("When");
1512
1718
  }
1513
- if (!THEN_PATTERN.test(scenario.body)) {
1719
+ if (!keywords.some((keyword) => THEN_PATTERN.test(keyword))) {
1514
1720
  missingSteps.push("Then");
1515
1721
  }
1516
1722
  if (missingSteps.length > 0) {
@@ -1544,18 +1750,25 @@ function issue4(code, message, severity, file, rule, refs) {
1544
1750
  }
1545
1751
  return issue7;
1546
1752
  }
1753
+ function isMissingFileError3(error2) {
1754
+ if (!error2 || typeof error2 !== "object") {
1755
+ return false;
1756
+ }
1757
+ return error2.code === "ENOENT";
1758
+ }
1547
1759
 
1548
1760
  // src/core/validators/spec.ts
1549
- var import_promises10 = require("fs/promises");
1761
+ var import_promises12 = require("fs/promises");
1550
1762
  async function validateSpecs(root, config) {
1551
1763
  const specsRoot = resolvePath(root, config, "specsDir");
1552
- const files = await collectSpecFiles(specsRoot);
1553
- if (files.length === 0) {
1554
- const expected = "spec-001/spec.md";
1764
+ const entries = await collectSpecEntries(specsRoot);
1765
+ if (entries.length === 0) {
1766
+ const expected = "spec-0001/spec.md";
1767
+ const legacy = "spec-001/spec.md";
1555
1768
  return [
1556
1769
  issue5(
1557
1770
  "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}`,
1771
+ `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
1772
  "info",
1560
1773
  specsRoot,
1561
1774
  "spec.files"
@@ -1563,12 +1776,29 @@ async function validateSpecs(root, config) {
1563
1776
  ];
1564
1777
  }
1565
1778
  const issues = [];
1566
- for (const file of files) {
1567
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1779
+ for (const entry of entries) {
1780
+ let text;
1781
+ try {
1782
+ text = await (0, import_promises12.readFile)(entry.specPath, "utf-8");
1783
+ } catch (error2) {
1784
+ if (isMissingFileError4(error2)) {
1785
+ issues.push(
1786
+ issue5(
1787
+ "QFAI-SPEC-005",
1788
+ "spec.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1789
+ "error",
1790
+ entry.specPath,
1791
+ "spec.exists"
1792
+ )
1793
+ );
1794
+ continue;
1795
+ }
1796
+ throw error2;
1797
+ }
1568
1798
  issues.push(
1569
1799
  ...validateSpecContent(
1570
1800
  text,
1571
- file,
1801
+ entry.specPath,
1572
1802
  config.validation.require.specSections
1573
1803
  )
1574
1804
  );
@@ -1690,15 +1920,18 @@ function issue5(code, message, severity, file, rule, refs) {
1690
1920
  }
1691
1921
  return issue7;
1692
1922
  }
1923
+ function isMissingFileError4(error2) {
1924
+ if (!error2 || typeof error2 !== "object") {
1925
+ return false;
1926
+ }
1927
+ return error2.code === "ENOENT";
1928
+ }
1693
1929
 
1694
1930
  // 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}$/;
1931
+ var import_promises13 = require("fs/promises");
1932
+ var SC_TAG_RE4 = /^SC-\d{4}$/;
1933
+ var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
1934
+ var BR_TAG_RE3 = /^BR-\d{4}$/;
1702
1935
  async function validateTraceability(root, config) {
1703
1936
  const issues = [];
1704
1937
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -1717,7 +1950,7 @@ async function validateTraceability(root, config) {
1717
1950
  const contractIndex = await buildContractIndex(root, config);
1718
1951
  const contractIds = contractIndex.ids;
1719
1952
  for (const file of specFiles) {
1720
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1953
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1721
1954
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1722
1955
  const parsed = parseSpec(text, file);
1723
1956
  if (parsed.specId) {
@@ -1754,106 +1987,99 @@ async function validateTraceability(root, config) {
1754
1987
  }
1755
1988
  }
1756
1989
  for (const file of scenarioFiles) {
1757
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1990
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1758
1991
  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
- );
1992
+ const { document, errors } = parseScenarioDocument(text, file);
1993
+ if (!document || errors.length > 0) {
1994
+ continue;
1832
1995
  }
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));
1996
+ const atoms = buildScenarioAtoms(document);
1997
+ for (const [index, scenario] of document.scenarios.entries()) {
1998
+ const atom = atoms[index];
1999
+ if (!atom) {
2000
+ continue;
2001
+ }
2002
+ const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
2003
+ const brTags = scenario.tags.filter((tag) => BR_TAG_RE3.test(tag));
2004
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE4.test(tag));
2005
+ brTags.forEach((id) => brIdsInScenarios.add(id));
2006
+ scTags.forEach((id) => scIdsInScenarios.add(id));
2007
+ atom.contractIds.forEach((id) => scenarioContractIds.add(id));
2008
+ if (atom.contractIds.length > 0) {
2009
+ scTags.forEach((id) => scWithContracts.add(id));
1841
2010
  }
1842
- const invalidBrIds = brIdsList.filter((id) => !allowedBrIds.has(id));
1843
- if (invalidBrIds.length > 0) {
2011
+ const unknownSpecIds = specTags.filter((id) => !specIds.has(id));
2012
+ if (unknownSpecIds.length > 0) {
1844
2013
  issues.push(
1845
2014
  issue6(
1846
- "QFAI-TRACE-007",
1847
- `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
2015
+ "QFAI-TRACE-005",
2016
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(
1848
2017
  ", "
1849
- )} (SPEC: ${specIdsList.join(", ")})`,
2018
+ )} (${scenario.name})`,
1850
2019
  "error",
1851
2020
  file,
1852
- "traceability.scenarioBrUnderSpec",
1853
- invalidBrIds
2021
+ "traceability.scenarioSpecExists",
2022
+ unknownSpecIds
1854
2023
  )
1855
2024
  );
1856
2025
  }
2026
+ const unknownBrIds = brTags.filter((id) => !brIdsInSpecs.has(id));
2027
+ if (unknownBrIds.length > 0) {
2028
+ issues.push(
2029
+ issue6(
2030
+ "QFAI-TRACE-006",
2031
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(
2032
+ ", "
2033
+ )} (${scenario.name})`,
2034
+ "error",
2035
+ file,
2036
+ "traceability.scenarioBrExists",
2037
+ unknownBrIds
2038
+ )
2039
+ );
2040
+ }
2041
+ const unknownContractIds = atom.contractIds.filter(
2042
+ (id) => !contractIds.has(id)
2043
+ );
2044
+ if (unknownContractIds.length > 0) {
2045
+ issues.push(
2046
+ issue6(
2047
+ "QFAI-TRACE-008",
2048
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
2049
+ ", "
2050
+ )} (${scenario.name})`,
2051
+ config.validation.traceability.unknownContractIdSeverity,
2052
+ file,
2053
+ "traceability.scenarioContractExists",
2054
+ unknownContractIds
2055
+ )
2056
+ );
2057
+ }
2058
+ if (specTags.length > 0 && brTags.length > 0) {
2059
+ const allowedBrIds = /* @__PURE__ */ new Set();
2060
+ for (const specId of specTags) {
2061
+ const brIdsForSpec = specToBrIds.get(specId);
2062
+ if (!brIdsForSpec) {
2063
+ continue;
2064
+ }
2065
+ brIdsForSpec.forEach((id) => allowedBrIds.add(id));
2066
+ }
2067
+ const invalidBrIds = brTags.filter((id) => !allowedBrIds.has(id));
2068
+ if (invalidBrIds.length > 0) {
2069
+ issues.push(
2070
+ issue6(
2071
+ "QFAI-TRACE-007",
2072
+ `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
2073
+ ", "
2074
+ )} (SPEC: ${specTags.join(", ")}) (${scenario.name})`,
2075
+ "error",
2076
+ file,
2077
+ "traceability.scenarioBrUnderSpec",
2078
+ invalidBrIds
2079
+ )
2080
+ );
2081
+ }
2082
+ }
1857
2083
  }
1858
2084
  }
1859
2085
  if (upstreamIds.size === 0) {
@@ -1951,7 +2177,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1951
2177
  const pattern = buildIdPattern(Array.from(upstreamIds));
1952
2178
  let found = false;
1953
2179
  for (const file of targetFiles) {
1954
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2180
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1955
2181
  if (pattern.test(text)) {
1956
2182
  found = true;
1957
2183
  break;
@@ -2178,7 +2404,7 @@ async function collectIds(files) {
2178
2404
  DATA: /* @__PURE__ */ new Set()
2179
2405
  };
2180
2406
  for (const file of files) {
2181
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2407
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2182
2408
  for (const prefix of ID_PREFIXES2) {
2183
2409
  const ids = extractIds(text, prefix);
2184
2410
  ids.forEach((id) => result[prefix].add(id));
@@ -2196,7 +2422,7 @@ async function collectIds(files) {
2196
2422
  async function collectUpstreamIds(files) {
2197
2423
  const ids = /* @__PURE__ */ new Set();
2198
2424
  for (const file of files) {
2199
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2425
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2200
2426
  extractAllIds(text).forEach((id) => ids.add(id));
2201
2427
  }
2202
2428
  return ids;
@@ -2217,7 +2443,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
2217
2443
  }
2218
2444
  const pattern = buildIdPattern2(Array.from(upstreamIds));
2219
2445
  for (const file of targetFiles) {
2220
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2446
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2221
2447
  if (pattern.test(text)) {
2222
2448
  return true;
2223
2449
  }
@@ -2269,7 +2495,7 @@ async function runReport(options) {
2269
2495
  try {
2270
2496
  validation = await readValidationResult(inputPath);
2271
2497
  } catch (err) {
2272
- if (isMissingFileError2(err)) {
2498
+ if (isMissingFileError5(err)) {
2273
2499
  error(
2274
2500
  [
2275
2501
  `qfai report: \u5165\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${inputPath}`,
@@ -2292,8 +2518,8 @@ async function runReport(options) {
2292
2518
  const defaultOut = options.format === "json" ? import_node_path14.default.join(outRoot, "report.json") : import_node_path14.default.join(outRoot, "report.md");
2293
2519
  const out = options.outPath ?? defaultOut;
2294
2520
  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}
2521
+ await (0, import_promises15.mkdir)(import_node_path14.default.dirname(outPath), { recursive: true });
2522
+ await (0, import_promises15.writeFile)(outPath, `${output}
2297
2523
  `, "utf-8");
2298
2524
  info(
2299
2525
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -2301,7 +2527,7 @@ async function runReport(options) {
2301
2527
  info(`wrote report: ${outPath}`);
2302
2528
  }
2303
2529
  async function readValidationResult(inputPath) {
2304
- const raw = await (0, import_promises13.readFile)(inputPath, "utf-8");
2530
+ const raw = await (0, import_promises15.readFile)(inputPath, "utf-8");
2305
2531
  const parsed = JSON.parse(raw);
2306
2532
  if (!isValidationResult(parsed)) {
2307
2533
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -2333,7 +2559,7 @@ function isValidationResult(value) {
2333
2559
  }
2334
2560
  return typeof counts.info === "number" && typeof counts.warning === "number" && typeof counts.error === "number";
2335
2561
  }
2336
- function isMissingFileError2(error2) {
2562
+ function isMissingFileError5(error2) {
2337
2563
  if (!error2 || typeof error2 !== "object") {
2338
2564
  return false;
2339
2565
  }
@@ -2342,7 +2568,7 @@ function isMissingFileError2(error2) {
2342
2568
  }
2343
2569
 
2344
2570
  // src/cli/commands/validate.ts
2345
- var import_promises14 = require("fs/promises");
2571
+ var import_promises16 = require("fs/promises");
2346
2572
  var import_node_path15 = __toESM(require("path"), 1);
2347
2573
 
2348
2574
  // src/cli/lib/failOn.ts
@@ -2408,8 +2634,8 @@ function emitGitHub(issue7) {
2408
2634
  }
2409
2635
  async function emitJson(result, root, jsonPath) {
2410
2636
  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)}
2637
+ await (0, import_promises16.mkdir)(import_node_path15.default.dirname(abs), { recursive: true });
2638
+ await (0, import_promises16.writeFile)(abs, `${JSON.stringify(result, null, 2)}
2413
2639
  `, "utf-8");
2414
2640
  }
2415
2641