qfai 0.2.9 → 0.3.1

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,8 +160,8 @@ function report(copied, skipped, dryRun, label) {
160
160
  }
161
161
 
162
162
  // src/cli/commands/report.ts
163
- var import_promises12 = require("fs/promises");
164
- var import_node_path10 = __toESM(require("path"), 1);
163
+ var import_promises13 = require("fs/promises");
164
+ var import_node_path14 = __toESM(require("path"), 1);
165
165
 
166
166
  // src/core/config.ts
167
167
  var import_promises2 = require("fs/promises");
@@ -169,13 +169,11 @@ var import_node_path4 = __toESM(require("path"), 1);
169
169
  var import_yaml = require("yaml");
170
170
  var defaultConfig = {
171
171
  paths: {
172
- specDir: ".qfai/spec",
173
- decisionsDir: ".qfai/spec/decisions",
174
- scenariosDir: ".qfai/spec/scenarios",
175
172
  contractsDir: ".qfai/contracts",
176
- uiContractsDir: ".qfai/contracts/ui",
177
- apiContractsDir: ".qfai/contracts/api",
178
- dataContractsDir: ".qfai/contracts/db",
173
+ specsDir: ".qfai/specs",
174
+ rulesDir: ".qfai/rules",
175
+ outDir: ".qfai/out",
176
+ promptsDir: ".qfai/prompts",
179
177
  srcDir: "src",
180
178
  testsDir: "tests"
181
179
  },
@@ -200,8 +198,7 @@ var defaultConfig = {
200
198
  }
201
199
  },
202
200
  output: {
203
- format: "text",
204
- jsonPath: ".qfai/out/validate.json"
201
+ validateJsonPath: ".qfai/out/validate.json"
205
202
  }
206
203
  };
207
204
  function getConfigPath(root) {
@@ -250,27 +247,6 @@ function normalizePaths(raw, configPath, issues) {
250
247
  return base;
251
248
  }
252
249
  return {
253
- specDir: readString(
254
- raw.specDir,
255
- base.specDir,
256
- "paths.specDir",
257
- configPath,
258
- issues
259
- ),
260
- decisionsDir: readString(
261
- raw.decisionsDir,
262
- base.decisionsDir,
263
- "paths.decisionsDir",
264
- configPath,
265
- issues
266
- ),
267
- scenariosDir: readString(
268
- raw.scenariosDir,
269
- base.scenariosDir,
270
- "paths.scenariosDir",
271
- configPath,
272
- issues
273
- ),
274
250
  contractsDir: readString(
275
251
  raw.contractsDir,
276
252
  base.contractsDir,
@@ -278,24 +254,31 @@ function normalizePaths(raw, configPath, issues) {
278
254
  configPath,
279
255
  issues
280
256
  ),
281
- uiContractsDir: readString(
282
- raw.uiContractsDir,
283
- base.uiContractsDir,
284
- "paths.uiContractsDir",
257
+ specsDir: readString(
258
+ raw.specsDir,
259
+ base.specsDir,
260
+ "paths.specsDir",
261
+ configPath,
262
+ issues
263
+ ),
264
+ rulesDir: readString(
265
+ raw.rulesDir,
266
+ base.rulesDir,
267
+ "paths.rulesDir",
285
268
  configPath,
286
269
  issues
287
270
  ),
288
- apiContractsDir: readString(
289
- raw.apiContractsDir,
290
- base.apiContractsDir,
291
- "paths.apiContractsDir",
271
+ outDir: readString(
272
+ raw.outDir,
273
+ base.outDir,
274
+ "paths.outDir",
292
275
  configPath,
293
276
  issues
294
277
  ),
295
- dataContractsDir: readString(
296
- raw.dataContractsDir,
297
- base.dataContractsDir,
298
- "paths.dataContractsDir",
278
+ promptsDir: readString(
279
+ raw.promptsDir,
280
+ base.promptsDir,
281
+ "paths.promptsDir",
299
282
  configPath,
300
283
  issues
301
284
  ),
@@ -418,17 +401,10 @@ function normalizeOutput(raw, configPath, issues) {
418
401
  return base;
419
402
  }
420
403
  return {
421
- format: readOutputFormat(
422
- raw.format,
423
- base.format,
424
- "output.format",
425
- configPath,
426
- issues
427
- ),
428
- jsonPath: readString(
429
- raw.jsonPath,
430
- base.jsonPath,
431
- "output.jsonPath",
404
+ validateJsonPath: readString(
405
+ raw.validateJsonPath,
406
+ base.validateJsonPath,
407
+ "output.validateJsonPath",
432
408
  configPath,
433
409
  issues
434
410
  )
@@ -495,20 +471,6 @@ function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
495
471
  }
496
472
  return fallback;
497
473
  }
498
- function readOutputFormat(value, fallback, label, configPath, issues) {
499
- if (value === "text" || value === "json" || value === "github") {
500
- return value;
501
- }
502
- if (value !== void 0) {
503
- issues.push(
504
- configIssue(
505
- configPath,
506
- `${label} \u306F text|json|github \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
507
- )
508
- );
509
- }
510
- return fallback;
511
- }
512
474
  function configIssue(file, message) {
513
475
  return {
514
476
  code: "QFAI_CONFIG_INVALID",
@@ -535,7 +497,8 @@ function isRecord(value) {
535
497
  }
536
498
 
537
499
  // src/core/report.ts
538
- var import_promises11 = require("fs/promises");
500
+ var import_promises12 = require("fs/promises");
501
+ var import_node_path13 = __toESM(require("path"), 1);
539
502
 
540
503
  // src/core/discovery.ts
541
504
  var import_node_path6 = __toESM(require("path"), 1);
@@ -596,10 +559,24 @@ async function exists2(target) {
596
559
  }
597
560
 
598
561
  // src/core/discovery.ts
599
- var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/;
600
- async function collectSpecFiles(specRoot) {
601
- const files = await collectFiles(specRoot, { extensions: [".md"] });
602
- return files.filter((file) => isSpecFile(file));
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));
569
+ }
570
+ }
571
+ return Array.from(packs).sort();
572
+ }
573
+ async function collectSpecFiles(specsRoot) {
574
+ const files = await collectFiles(specsRoot, { extensions: [".md"] });
575
+ return files.filter((file) => isSpecPackFile(file, "spec.md"));
576
+ }
577
+ async function collectScenarioFiles(specsRoot) {
578
+ const files = await collectFiles(specsRoot, { extensions: [".md"] });
579
+ return files.filter((file) => isSpecPackFile(file, "scenario.md"));
603
580
  }
604
581
  async function collectUiContractFiles(uiRoot) {
605
582
  return collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
@@ -618,9 +595,12 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
618
595
  ]);
619
596
  return { ui, api, db };
620
597
  }
621
- function isSpecFile(filePath) {
622
- const name = import_node_path6.default.basename(filePath).toLowerCase();
623
- return SPEC_NAMED_PATTERN.test(name);
598
+ function isSpecPackFile(filePath, baseName) {
599
+ if (import_node_path6.default.basename(filePath).toLowerCase() !== baseName) {
600
+ return false;
601
+ }
602
+ const dirName = import_node_path6.default.basename(import_node_path6.default.dirname(filePath)).toLowerCase();
603
+ return SPEC_PACK_DIR_PATTERN.test(dirName);
624
604
  }
625
605
 
626
606
  // src/core/ids.ts
@@ -684,8 +664,8 @@ var import_promises4 = require("fs/promises");
684
664
  var import_node_path7 = __toESM(require("path"), 1);
685
665
  var import_node_url2 = require("url");
686
666
  async function resolveToolVersion() {
687
- if ("0.2.9".length > 0) {
688
- return "0.2.9";
667
+ if ("0.3.1".length > 0) {
668
+ return "0.3.1";
689
669
  }
690
670
  try {
691
671
  const packagePath = resolvePackageJsonPath();
@@ -705,6 +685,7 @@ function resolvePackageJsonPath() {
705
685
 
706
686
  // src/core/validators/contracts.ts
707
687
  var import_promises5 = require("fs/promises");
688
+ var import_node_path9 = __toESM(require("path"), 1);
708
689
 
709
690
  // src/core/contracts.ts
710
691
  var import_node_path8 = __toESM(require("path"), 1);
@@ -760,19 +741,10 @@ var SQL_DANGEROUS_PATTERNS = [
760
741
  ];
761
742
  async function validateContracts(root, config) {
762
743
  const issues = [];
763
- issues.push(
764
- ...await validateUiContracts(resolvePath(root, config, "uiContractsDir"))
765
- );
766
- issues.push(
767
- ...await validateApiContracts(
768
- resolvePath(root, config, "apiContractsDir")
769
- )
770
- );
771
- issues.push(
772
- ...await validateDataContracts(
773
- resolvePath(root, config, "dataContractsDir")
774
- )
775
- );
744
+ const contractsRoot = resolvePath(root, config, "contractsDir");
745
+ issues.push(...await validateUiContracts(import_node_path9.default.join(contractsRoot, "ui")));
746
+ issues.push(...await validateApiContracts(import_node_path9.default.join(contractsRoot, "api")));
747
+ issues.push(...await validateDataContracts(import_node_path9.default.join(contractsRoot, "db")));
776
748
  return issues;
777
749
  }
778
750
  async function validateUiContracts(uiRoot) {
@@ -988,33 +960,125 @@ function formatError2(error2) {
988
960
  return String(error2);
989
961
  }
990
962
  function issue(code, message, severity, file, rule, refs) {
991
- const issue6 = {
963
+ const issue7 = {
992
964
  code,
993
965
  severity,
994
966
  message
995
967
  };
996
968
  if (file) {
997
- issue6.file = file;
969
+ issue7.file = file;
998
970
  }
999
971
  if (rule) {
1000
- issue6.rule = rule;
972
+ issue7.rule = rule;
1001
973
  }
1002
974
  if (refs && refs.length > 0) {
1003
- issue6.refs = refs;
975
+ issue7.refs = refs;
1004
976
  }
1005
- return issue6;
977
+ return issue7;
978
+ }
979
+
980
+ // src/core/validators/delta.ts
981
+ var import_promises6 = require("fs/promises");
982
+ var import_node_path10 = __toESM(require("path"), 1);
983
+ var SECTION_RE = /^##\s+変更区分/m;
984
+ var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
985
+ var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
986
+ var COMPAT_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Compatibility\b/m;
987
+ var CHANGE_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Change\/Improvement\b/m;
988
+ async function validateDeltas(root, config) {
989
+ const specsRoot = resolvePath(root, config, "specsDir");
990
+ const packs = await collectSpecPackDirs(specsRoot);
991
+ if (packs.length === 0) {
992
+ return [];
993
+ }
994
+ const issues = [];
995
+ for (const pack of packs) {
996
+ const deltaPath = import_node_path10.default.join(pack, "delta.md");
997
+ let text;
998
+ try {
999
+ text = await (0, import_promises6.readFile)(deltaPath, "utf-8");
1000
+ } catch (error2) {
1001
+ if (isMissingFileError(error2)) {
1002
+ issues.push(
1003
+ issue2(
1004
+ "QFAI-DELTA-001",
1005
+ "delta.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1006
+ "error",
1007
+ deltaPath,
1008
+ "delta.exists"
1009
+ )
1010
+ );
1011
+ continue;
1012
+ }
1013
+ throw error2;
1014
+ }
1015
+ const hasSection = SECTION_RE.test(text);
1016
+ const hasCompatibility = COMPAT_LINE_RE.test(text);
1017
+ const hasChange = CHANGE_LINE_RE.test(text);
1018
+ if (!hasSection || !hasCompatibility || !hasChange) {
1019
+ issues.push(
1020
+ issue2(
1021
+ "QFAI-DELTA-002",
1022
+ "delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
1023
+ "error",
1024
+ deltaPath,
1025
+ "delta.section"
1026
+ )
1027
+ );
1028
+ continue;
1029
+ }
1030
+ const compatibilityChecked = COMPAT_CHECKED_RE.test(text);
1031
+ const changeChecked = CHANGE_CHECKED_RE.test(text);
1032
+ if (compatibilityChecked === changeChecked) {
1033
+ issues.push(
1034
+ issue2(
1035
+ "QFAI-DELTA-003",
1036
+ "delta.md \u306E\u5909\u66F4\u533A\u5206\u306F\u3069\u3061\u3089\u304B1\u3064\u3060\u3051\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4E21\u65B9ON/\u4E21\u65B9OFF\u306F\u7121\u52B9\u3067\u3059\uFF09\u3002",
1037
+ "error",
1038
+ deltaPath,
1039
+ "delta.classification"
1040
+ )
1041
+ );
1042
+ }
1043
+ }
1044
+ return issues;
1045
+ }
1046
+ function isMissingFileError(error2) {
1047
+ if (!error2 || typeof error2 !== "object") {
1048
+ return false;
1049
+ }
1050
+ return error2.code === "ENOENT";
1051
+ }
1052
+ function issue2(code, message, severity, file, rule, refs) {
1053
+ const issue7 = {
1054
+ code,
1055
+ severity,
1056
+ message
1057
+ };
1058
+ if (file) {
1059
+ issue7.file = file;
1060
+ }
1061
+ if (rule) {
1062
+ issue7.rule = rule;
1063
+ }
1064
+ if (refs && refs.length > 0) {
1065
+ issue7.refs = refs;
1066
+ }
1067
+ return issue7;
1006
1068
  }
1007
1069
 
1008
1070
  // src/core/validators/ids.ts
1009
- var import_promises7 = require("fs/promises");
1010
- var import_node_path9 = __toESM(require("path"), 1);
1071
+ var import_promises8 = require("fs/promises");
1072
+ var import_node_path12 = __toESM(require("path"), 1);
1011
1073
 
1012
1074
  // src/core/contractIndex.ts
1013
- var import_promises6 = require("fs/promises");
1075
+ var import_promises7 = require("fs/promises");
1076
+ var import_node_path11 = __toESM(require("path"), 1);
1014
1077
  async function buildContractIndex(root, config) {
1015
- const uiRoot = resolvePath(root, config, "uiContractsDir");
1016
- const apiRoot = resolvePath(root, config, "apiContractsDir");
1017
- const dataRoot = resolvePath(root, config, "dataContractsDir");
1078
+ const contractsRoot = resolvePath(root, config, "contractsDir");
1079
+ const uiRoot = import_node_path11.default.join(contractsRoot, "ui");
1080
+ const apiRoot = import_node_path11.default.join(contractsRoot, "api");
1081
+ const dataRoot = import_node_path11.default.join(contractsRoot, "db");
1018
1082
  const [uiFiles, apiFiles, dataFiles] = await Promise.all([
1019
1083
  collectUiContractFiles(uiRoot),
1020
1084
  collectApiContractFiles(apiRoot),
@@ -1033,7 +1097,7 @@ async function buildContractIndex(root, config) {
1033
1097
  }
1034
1098
  async function indexUiContracts(files, index) {
1035
1099
  for (const file of files) {
1036
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1100
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1037
1101
  try {
1038
1102
  const doc = parseStructuredContract(file, text);
1039
1103
  extractUiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1045,7 +1109,7 @@ async function indexUiContracts(files, index) {
1045
1109
  }
1046
1110
  async function indexApiContracts(files, index) {
1047
1111
  for (const file of files) {
1048
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1112
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1049
1113
  try {
1050
1114
  const doc = parseStructuredContract(file, text);
1051
1115
  extractApiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1057,7 +1121,7 @@ async function indexApiContracts(files, index) {
1057
1121
  }
1058
1122
  async function indexDataContracts(files, index) {
1059
1123
  for (const file of files) {
1060
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1124
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1061
1125
  extractIds(text, "DATA").forEach((id) => record(index, id, file));
1062
1126
  }
1063
1127
  }
@@ -1068,15 +1132,191 @@ function record(index, id, file) {
1068
1132
  index.idToFiles.set(id, current);
1069
1133
  }
1070
1134
 
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
+ // src/core/parse/markdown.ts
1196
+ var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1197
+ function parseHeadings(md) {
1198
+ const lines = md.split(/\r?\n/);
1199
+ const headings = [];
1200
+ for (let i = 0; i < lines.length; i++) {
1201
+ const line = lines[i] ?? "";
1202
+ const match = line.match(HEADING_RE);
1203
+ if (!match) continue;
1204
+ const levelToken = match[1];
1205
+ const title = match[2];
1206
+ if (!levelToken || !title) continue;
1207
+ headings.push({
1208
+ level: levelToken.length,
1209
+ title: title.trim(),
1210
+ line: i + 1
1211
+ });
1212
+ }
1213
+ return headings;
1214
+ }
1215
+ function extractH2Sections(md) {
1216
+ const lines = md.split(/\r?\n/);
1217
+ const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1218
+ const sections = /* @__PURE__ */ new Map();
1219
+ for (let i = 0; i < headings.length; i++) {
1220
+ const current = headings[i];
1221
+ if (!current) continue;
1222
+ const next = headings[i + 1];
1223
+ const startLine = current.line + 1;
1224
+ const endLine = (next?.line ?? lines.length + 1) - 1;
1225
+ const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1226
+ sections.set(current.title.trim(), {
1227
+ title: current.title.trim(),
1228
+ startLine,
1229
+ endLine,
1230
+ body
1231
+ });
1232
+ }
1233
+ return sections;
1234
+ }
1235
+
1236
+ // src/core/parse/spec.ts
1237
+ var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
1238
+ var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
1239
+ var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
1240
+ var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
1241
+ var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
1242
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
1243
+ function parseSpec(md, file) {
1244
+ const headings = parseHeadings(md);
1245
+ const h1 = headings.find((heading) => heading.level === 1);
1246
+ const specId = h1?.title.match(SPEC_ID_RE)?.[0];
1247
+ const sections = extractH2Sections(md);
1248
+ const sectionNames = new Set(Array.from(sections.keys()));
1249
+ const brSection = sections.get(BR_SECTION_TITLE);
1250
+ const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
1251
+ const startLine = brSection?.startLine ?? 1;
1252
+ const brs = [];
1253
+ const brsWithoutPriority = [];
1254
+ const brsWithInvalidPriority = [];
1255
+ for (let i = 0; i < brLines.length; i++) {
1256
+ const lineText = brLines[i] ?? "";
1257
+ const lineNumber = startLine + i;
1258
+ const validMatch = lineText.match(BR_LINE_RE);
1259
+ if (validMatch) {
1260
+ const id = validMatch[1];
1261
+ const priority = validMatch[2];
1262
+ const text = validMatch[3];
1263
+ if (!id || !priority || !text) continue;
1264
+ brs.push({
1265
+ id,
1266
+ priority,
1267
+ text: text.trim(),
1268
+ line: lineNumber
1269
+ });
1270
+ continue;
1271
+ }
1272
+ const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
1273
+ if (anyPriorityMatch) {
1274
+ const id = anyPriorityMatch[1];
1275
+ const priority = anyPriorityMatch[2];
1276
+ const text = anyPriorityMatch[3];
1277
+ if (!id || !priority || !text) continue;
1278
+ if (!VALID_PRIORITIES.has(priority)) {
1279
+ brsWithInvalidPriority.push({
1280
+ id,
1281
+ priority,
1282
+ text: text.trim(),
1283
+ line: lineNumber
1284
+ });
1285
+ }
1286
+ continue;
1287
+ }
1288
+ const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
1289
+ if (noPriorityMatch) {
1290
+ const id = noPriorityMatch[1];
1291
+ const text = noPriorityMatch[2];
1292
+ if (!id || !text) continue;
1293
+ brsWithoutPriority.push({
1294
+ id,
1295
+ text: text.trim(),
1296
+ line: lineNumber
1297
+ });
1298
+ }
1299
+ }
1300
+ const parsed = {
1301
+ file,
1302
+ sections: sectionNames,
1303
+ brs,
1304
+ brsWithoutPriority,
1305
+ brsWithInvalidPriority
1306
+ };
1307
+ if (specId) {
1308
+ parsed.specId = specId;
1309
+ }
1310
+ return parsed;
1311
+ }
1312
+
1071
1313
  // src/core/validators/ids.ts
1314
+ var SC_TAG_RE = /^SC-\d{4}$/;
1072
1315
  async function validateDefinedIds(root, config) {
1073
1316
  const issues = [];
1074
- const specRoot = resolvePath(root, config, "specDir");
1075
- const scenarioRoot = resolvePath(root, config, "scenariosDir");
1076
- const specFiles = await collectSpecFiles(specRoot);
1077
- const scenarioFiles = await collectFiles(scenarioRoot, {
1078
- extensions: [".feature"]
1079
- });
1317
+ const specsRoot = resolvePath(root, config, "specsDir");
1318
+ const specFiles = await collectSpecFiles(specsRoot);
1319
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
1080
1320
  const defined = /* @__PURE__ */ new Map();
1081
1321
  await collectSpecDefinitionIds(specFiles, defined);
1082
1322
  await collectScenarioDefinitionIds(scenarioFiles, defined);
@@ -1092,7 +1332,7 @@ async function validateDefinedIds(root, config) {
1092
1332
  }
1093
1333
  const sorted = Array.from(files).sort();
1094
1334
  issues.push(
1095
- issue2(
1335
+ issue3(
1096
1336
  "QFAI-ID-001",
1097
1337
  `ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
1098
1338
  "error",
@@ -1105,15 +1345,25 @@ async function validateDefinedIds(root, config) {
1105
1345
  }
1106
1346
  async function collectSpecDefinitionIds(files, out) {
1107
1347
  for (const file of files) {
1108
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1109
- extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
1110
- extractIds(text, "BR").forEach((id) => recordId(out, id, file));
1348
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1349
+ const parsed = parseSpec(text, file);
1350
+ if (parsed.specId) {
1351
+ recordId(out, parsed.specId, file);
1352
+ }
1353
+ parsed.brs.forEach((br) => recordId(out, br.id, file));
1111
1354
  }
1112
1355
  }
1113
1356
  async function collectScenarioDefinitionIds(files, out) {
1114
1357
  for (const file of files) {
1115
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1116
- extractIds(text, "SC").forEach((id) => recordId(out, id, file));
1358
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1359
+ const parsed = parseGherkinFeature(text, file);
1360
+ for (const scenario of parsed.scenarios) {
1361
+ for (const tag of scenario.tags) {
1362
+ if (SC_TAG_RE.test(tag)) {
1363
+ recordId(out, tag, file);
1364
+ }
1365
+ }
1366
+ }
1117
1367
  }
1118
1368
  }
1119
1369
  function recordId(out, id, file) {
@@ -1123,58 +1373,60 @@ function recordId(out, id, file) {
1123
1373
  }
1124
1374
  function formatFileList(files, root) {
1125
1375
  return files.map((file) => {
1126
- const relative = import_node_path9.default.relative(root, file);
1376
+ const relative = import_node_path12.default.relative(root, file);
1127
1377
  return relative.length > 0 ? relative : file;
1128
1378
  }).join(", ");
1129
1379
  }
1130
- function issue2(code, message, severity, file, rule, refs) {
1131
- const issue6 = {
1380
+ function issue3(code, message, severity, file, rule, refs) {
1381
+ const issue7 = {
1132
1382
  code,
1133
1383
  severity,
1134
1384
  message
1135
1385
  };
1136
1386
  if (file) {
1137
- issue6.file = file;
1387
+ issue7.file = file;
1138
1388
  }
1139
1389
  if (rule) {
1140
- issue6.rule = rule;
1390
+ issue7.rule = rule;
1141
1391
  }
1142
1392
  if (refs && refs.length > 0) {
1143
- issue6.refs = refs;
1393
+ issue7.refs = refs;
1144
1394
  }
1145
- return issue6;
1395
+ return issue7;
1146
1396
  }
1147
1397
 
1148
1398
  // src/core/validators/scenario.ts
1149
- var import_promises8 = require("fs/promises");
1399
+ var import_promises9 = require("fs/promises");
1150
1400
  var GIVEN_PATTERN = /\bGiven\b/;
1151
1401
  var WHEN_PATTERN = /\bWhen\b/;
1152
1402
  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}$/;
1153
1406
  async function validateScenarios(root, config) {
1154
- const scenariosRoot = resolvePath(root, config, "scenariosDir");
1155
- const files = await collectFiles(scenariosRoot, {
1156
- extensions: [".feature"]
1157
- });
1407
+ const specsRoot = resolvePath(root, config, "specsDir");
1408
+ const files = await collectScenarioFiles(specsRoot);
1158
1409
  if (files.length === 0) {
1159
1410
  return [
1160
- issue3(
1411
+ issue4(
1161
1412
  "QFAI-SC-000",
1162
1413
  "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1163
1414
  "info",
1164
- scenariosRoot,
1415
+ specsRoot,
1165
1416
  "scenario.files"
1166
1417
  )
1167
1418
  ];
1168
1419
  }
1169
1420
  const issues = [];
1170
1421
  for (const file of files) {
1171
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1422
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1172
1423
  issues.push(...validateScenarioContent(text, file));
1173
1424
  }
1174
1425
  return issues;
1175
1426
  }
1176
1427
  function validateScenarioContent(text, file) {
1177
1428
  const issues = [];
1429
+ const parsed = parseGherkinFeature(text, file);
1178
1430
  const invalidIds = extractInvalidIds(text, [
1179
1431
  "SPEC",
1180
1432
  "BR",
@@ -1186,7 +1438,7 @@ function validateScenarioContent(text, file) {
1186
1438
  ]);
1187
1439
  if (invalidIds.length > 0) {
1188
1440
  issues.push(
1189
- issue3(
1441
+ issue4(
1190
1442
  "QFAI-ID-002",
1191
1443
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1192
1444
  "error",
@@ -1196,94 +1448,114 @@ function validateScenarioContent(text, file) {
1196
1448
  )
1197
1449
  );
1198
1450
  }
1199
- const scIds = extractIds(text, "SC");
1200
- if (scIds.length === 0) {
1451
+ const missingStructure = [];
1452
+ if (!parsed.featurePresent) missingStructure.push("Feature");
1453
+ if (parsed.scenarios.length === 0) missingStructure.push("Scenario");
1454
+ if (missingStructure.length > 0) {
1201
1455
  issues.push(
1202
- issue3(
1203
- "QFAI-SC-001",
1204
- "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1205
- "error",
1206
- file,
1207
- "scenario.id"
1208
- )
1209
- );
1210
- }
1211
- const specIds = extractIds(text, "SPEC");
1212
- if (specIds.length === 0) {
1213
- issues.push(
1214
- issue3(
1215
- "QFAI-SC-002",
1216
- "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1217
- "error",
1218
- file,
1219
- "scenario.spec"
1220
- )
1221
- );
1222
- }
1223
- const brIds = extractIds(text, "BR");
1224
- if (brIds.length === 0) {
1225
- issues.push(
1226
- issue3(
1227
- "QFAI-SC-003",
1228
- "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1456
+ issue4(
1457
+ "QFAI-SC-006",
1458
+ `Scenario \u30D5\u30A1\u30A4\u30EB\u306B\u5FC5\u8981\u306A\u69CB\u9020\u304C\u3042\u308A\u307E\u305B\u3093: ${missingStructure.join(
1459
+ ", "
1460
+ )}`,
1229
1461
  "error",
1230
1462
  file,
1231
- "scenario.br"
1463
+ "scenario.structure"
1232
1464
  )
1233
1465
  );
1234
1466
  }
1235
- const missingSteps = [];
1236
- if (!GIVEN_PATTERN.test(text)) {
1237
- missingSteps.push("Given");
1238
- }
1239
- if (!WHEN_PATTERN.test(text)) {
1240
- missingSteps.push("When");
1241
- }
1242
- if (!THEN_PATTERN.test(text)) {
1243
- missingSteps.push("Then");
1467
+ for (const scenario of parsed.scenarios) {
1468
+ if (scenario.tags.length === 0) {
1469
+ issues.push(
1470
+ issue4(
1471
+ "QFAI-SC-007",
1472
+ `Scenario \u30BF\u30B0\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${scenario.name}`,
1473
+ "error",
1474
+ file,
1475
+ "scenario.tags"
1476
+ )
1477
+ );
1478
+ continue;
1479
+ }
1480
+ const missingTags = [];
1481
+ const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
1482
+ if (scTags.length === 0) {
1483
+ missingTags.push("SC(0\u4EF6)");
1484
+ } else if (scTags.length > 1) {
1485
+ missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
1486
+ }
1487
+ if (!scenario.tags.some((tag) => SPEC_TAG_RE.test(tag))) {
1488
+ missingTags.push("SPEC");
1489
+ }
1490
+ if (!scenario.tags.some((tag) => BR_TAG_RE.test(tag))) {
1491
+ missingTags.push("BR");
1492
+ }
1493
+ if (missingTags.length > 0) {
1494
+ issues.push(
1495
+ issue4(
1496
+ "QFAI-SC-008",
1497
+ `Scenario \u30BF\u30B0\u306B\u4E0D\u8DB3\u304C\u3042\u308A\u307E\u3059: ${missingTags.join(", ")} (${scenario.name})`,
1498
+ "error",
1499
+ file,
1500
+ "scenario.tagIds"
1501
+ )
1502
+ );
1503
+ }
1244
1504
  }
1245
- if (missingSteps.length > 0) {
1246
- issues.push(
1247
- issue3(
1248
- "QFAI-SC-005",
1249
- `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
1250
- "warning",
1251
- file,
1252
- "scenario.steps"
1253
- )
1254
- );
1505
+ for (const scenario of parsed.scenarios) {
1506
+ const missingSteps = [];
1507
+ if (!GIVEN_PATTERN.test(scenario.body)) {
1508
+ missingSteps.push("Given");
1509
+ }
1510
+ if (!WHEN_PATTERN.test(scenario.body)) {
1511
+ missingSteps.push("When");
1512
+ }
1513
+ if (!THEN_PATTERN.test(scenario.body)) {
1514
+ missingSteps.push("Then");
1515
+ }
1516
+ if (missingSteps.length > 0) {
1517
+ issues.push(
1518
+ issue4(
1519
+ "QFAI-SC-005",
1520
+ `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")} (${scenario.name})`,
1521
+ "warning",
1522
+ file,
1523
+ "scenario.steps"
1524
+ )
1525
+ );
1526
+ }
1255
1527
  }
1256
1528
  return issues;
1257
1529
  }
1258
- function issue3(code, message, severity, file, rule, refs) {
1259
- const issue6 = {
1530
+ function issue4(code, message, severity, file, rule, refs) {
1531
+ const issue7 = {
1260
1532
  code,
1261
1533
  severity,
1262
1534
  message
1263
1535
  };
1264
1536
  if (file) {
1265
- issue6.file = file;
1537
+ issue7.file = file;
1266
1538
  }
1267
1539
  if (rule) {
1268
- issue6.rule = rule;
1540
+ issue7.rule = rule;
1269
1541
  }
1270
1542
  if (refs && refs.length > 0) {
1271
- issue6.refs = refs;
1543
+ issue7.refs = refs;
1272
1544
  }
1273
- return issue6;
1545
+ return issue7;
1274
1546
  }
1275
1547
 
1276
1548
  // src/core/validators/spec.ts
1277
- var import_promises9 = require("fs/promises");
1549
+ var import_promises10 = require("fs/promises");
1278
1550
  async function validateSpecs(root, config) {
1279
- const specsRoot = resolvePath(root, config, "specDir");
1551
+ const specsRoot = resolvePath(root, config, "specsDir");
1280
1552
  const files = await collectSpecFiles(specsRoot);
1281
1553
  if (files.length === 0) {
1282
- const expected = "spec-0001-<slug>.md";
1554
+ const expected = "spec-001/spec.md";
1283
1555
  return [
1284
- issue4(
1556
+ issue5(
1285
1557
  "QFAI-SPEC-000",
1286
- `Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}`,
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}`,
1287
1559
  "info",
1288
1560
  specsRoot,
1289
1561
  "spec.files"
@@ -1292,7 +1564,7 @@ async function validateSpecs(root, config) {
1292
1564
  }
1293
1565
  const issues = [];
1294
1566
  for (const file of files) {
1295
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1567
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1296
1568
  issues.push(
1297
1569
  ...validateSpecContent(
1298
1570
  text,
@@ -1305,6 +1577,7 @@ async function validateSpecs(root, config) {
1305
1577
  }
1306
1578
  function validateSpecContent(text, file, requiredSections) {
1307
1579
  const issues = [];
1580
+ const parsed = parseSpec(text, file);
1308
1581
  const invalidIds = extractInvalidIds(text, [
1309
1582
  "SPEC",
1310
1583
  "BR",
@@ -1316,7 +1589,7 @@ function validateSpecContent(text, file, requiredSections) {
1316
1589
  ]);
1317
1590
  if (invalidIds.length > 0) {
1318
1591
  issues.push(
1319
- issue4(
1592
+ issue5(
1320
1593
  "QFAI-ID-002",
1321
1594
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1322
1595
  "error",
@@ -1326,10 +1599,9 @@ function validateSpecContent(text, file, requiredSections) {
1326
1599
  )
1327
1600
  );
1328
1601
  }
1329
- const specIds = extractIds(text, "SPEC");
1330
- if (specIds.length === 0) {
1602
+ if (!parsed.specId) {
1331
1603
  issues.push(
1332
- issue4(
1604
+ issue5(
1333
1605
  "QFAI-SPEC-001",
1334
1606
  "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1335
1607
  "error",
@@ -1338,10 +1610,9 @@ function validateSpecContent(text, file, requiredSections) {
1338
1610
  )
1339
1611
  );
1340
1612
  }
1341
- const brIds = extractIds(text, "BR");
1342
- if (brIds.length === 0) {
1613
+ if (parsed.brs.length === 0) {
1343
1614
  issues.push(
1344
- issue4(
1615
+ issue5(
1345
1616
  "QFAI-SPEC-002",
1346
1617
  "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1347
1618
  "error",
@@ -1350,10 +1621,34 @@ function validateSpecContent(text, file, requiredSections) {
1350
1621
  )
1351
1622
  );
1352
1623
  }
1624
+ for (const br of parsed.brsWithoutPriority) {
1625
+ issues.push(
1626
+ issue5(
1627
+ "QFAI-BR-001",
1628
+ `BR \u884C\u306B Priority \u304C\u3042\u308A\u307E\u305B\u3093: ${br.id}`,
1629
+ "error",
1630
+ file,
1631
+ "spec.brPriority",
1632
+ [br.id]
1633
+ )
1634
+ );
1635
+ }
1636
+ for (const br of parsed.brsWithInvalidPriority) {
1637
+ issues.push(
1638
+ issue5(
1639
+ "QFAI-BR-002",
1640
+ `BR Priority \u304C\u4E0D\u6B63\u3067\u3059: ${br.id} (${br.priority})`,
1641
+ "error",
1642
+ file,
1643
+ "spec.brPriority",
1644
+ [br.id]
1645
+ )
1646
+ );
1647
+ }
1353
1648
  const scIds = extractIds(text, "SC");
1354
1649
  if (scIds.length > 0) {
1355
1650
  issues.push(
1356
- issue4(
1651
+ issue5(
1357
1652
  "QFAI-SPEC-003",
1358
1653
  "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
1359
1654
  "warning",
@@ -1364,9 +1659,9 @@ function validateSpecContent(text, file, requiredSections) {
1364
1659
  );
1365
1660
  }
1366
1661
  for (const section of requiredSections) {
1367
- if (!text.includes(section)) {
1662
+ if (!parsed.sections.has(section)) {
1368
1663
  issues.push(
1369
- issue4(
1664
+ issue5(
1370
1665
  "QFAI-SPEC-004",
1371
1666
  `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
1372
1667
  "error",
@@ -1378,40 +1673,39 @@ function validateSpecContent(text, file, requiredSections) {
1378
1673
  }
1379
1674
  return issues;
1380
1675
  }
1381
- function issue4(code, message, severity, file, rule, refs) {
1382
- const issue6 = {
1676
+ function issue5(code, message, severity, file, rule, refs) {
1677
+ const issue7 = {
1383
1678
  code,
1384
1679
  severity,
1385
1680
  message
1386
1681
  };
1387
1682
  if (file) {
1388
- issue6.file = file;
1683
+ issue7.file = file;
1389
1684
  }
1390
1685
  if (rule) {
1391
- issue6.rule = rule;
1686
+ issue7.rule = rule;
1392
1687
  }
1393
1688
  if (refs && refs.length > 0) {
1394
- issue6.refs = refs;
1689
+ issue7.refs = refs;
1395
1690
  }
1396
- return issue6;
1691
+ return issue7;
1397
1692
  }
1398
1693
 
1399
1694
  // src/core/validators/traceability.ts
1400
- var import_promises10 = require("fs/promises");
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}$/;
1401
1702
  async function validateTraceability(root, config) {
1402
1703
  const issues = [];
1403
- const specsRoot = resolvePath(root, config, "specDir");
1404
- const decisionsRoot = resolvePath(root, config, "decisionsDir");
1405
- const scenariosRoot = resolvePath(root, config, "scenariosDir");
1704
+ const specsRoot = resolvePath(root, config, "specsDir");
1406
1705
  const srcRoot = resolvePath(root, config, "srcDir");
1407
1706
  const testsRoot = resolvePath(root, config, "testsDir");
1408
1707
  const specFiles = await collectSpecFiles(specsRoot);
1409
- const decisionFiles = await collectFiles(decisionsRoot, {
1410
- extensions: [".md"]
1411
- });
1412
- const scenarioFiles = await collectFiles(scenariosRoot, {
1413
- extensions: [".feature"]
1414
- });
1708
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
1415
1709
  const upstreamIds = /* @__PURE__ */ new Set();
1416
1710
  const specIds = /* @__PURE__ */ new Set();
1417
1711
  const brIdsInSpecs = /* @__PURE__ */ new Set();
@@ -1423,11 +1717,13 @@ async function validateTraceability(root, config) {
1423
1717
  const contractIndex = await buildContractIndex(root, config);
1424
1718
  const contractIds = contractIndex.ids;
1425
1719
  for (const file of specFiles) {
1426
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1720
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1427
1721
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1428
- const specIdsInFile = extractIds(text, "SPEC");
1429
- specIdsInFile.forEach((id) => specIds.add(id));
1430
- const brIds = extractIds(text, "BR");
1722
+ const parsed = parseSpec(text, file);
1723
+ if (parsed.specId) {
1724
+ specIds.add(parsed.specId);
1725
+ }
1726
+ const brIds = parsed.brs.map((br) => br.id);
1431
1727
  brIds.forEach((id) => brIdsInSpecs.add(id));
1432
1728
  const referencedContractIds = /* @__PURE__ */ new Set([
1433
1729
  ...extractIds(text, "UI"),
@@ -1439,7 +1735,7 @@ async function validateTraceability(root, config) {
1439
1735
  );
1440
1736
  if (unknownContractIds.length > 0) {
1441
1737
  issues.push(
1442
- issue5(
1738
+ issue6(
1443
1739
  "QFAI-TRACE-009",
1444
1740
  `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1445
1741
  ", "
@@ -1451,37 +1747,50 @@ async function validateTraceability(root, config) {
1451
1747
  )
1452
1748
  );
1453
1749
  }
1454
- for (const specId of specIdsInFile) {
1455
- const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
1750
+ if (parsed.specId) {
1751
+ const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
1456
1752
  brIds.forEach((id) => current.add(id));
1457
- specToBrIds.set(specId, current);
1753
+ specToBrIds.set(parsed.specId, current);
1458
1754
  }
1459
1755
  }
1460
- for (const file of decisionFiles) {
1461
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1462
- extractAllIds(text).forEach((id) => upstreamIds.add(id));
1463
- }
1464
1756
  for (const file of scenarioFiles) {
1465
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1757
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1466
1758
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1467
- const specIdsInScenario = extractIds(text, "SPEC");
1468
- const brIds = extractIds(text, "BR");
1469
- const scIds = extractIds(text, "SC");
1470
- const scenarioIds = [
1471
- ...extractIds(text, "UI"),
1472
- ...extractIds(text, "API"),
1473
- ...extractIds(text, "DATA")
1474
- ];
1475
- brIds.forEach((id) => brIdsInScenarios.add(id));
1476
- scIds.forEach((id) => scIdsInScenarios.add(id));
1477
- scenarioIds.forEach((id) => scenarioContractIds.add(id));
1478
- if (scenarioIds.length > 0) {
1479
- scIds.forEach((id) => scWithContracts.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));
1480
1789
  }
1481
- const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
1790
+ const unknownSpecIds = specIdsList.filter((id) => !specIds.has(id));
1482
1791
  if (unknownSpecIds.length > 0) {
1483
1792
  issues.push(
1484
- issue5(
1793
+ issue6(
1485
1794
  "QFAI-TRACE-005",
1486
1795
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1487
1796
  "error",
@@ -1491,10 +1800,10 @@ async function validateTraceability(root, config) {
1491
1800
  )
1492
1801
  );
1493
1802
  }
1494
- const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
1803
+ const unknownBrIds = brIdsList.filter((id) => !brIdsInSpecs.has(id));
1495
1804
  if (unknownBrIds.length > 0) {
1496
1805
  issues.push(
1497
- issue5(
1806
+ issue6(
1498
1807
  "QFAI-TRACE-006",
1499
1808
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1500
1809
  "error",
@@ -1504,10 +1813,12 @@ async function validateTraceability(root, config) {
1504
1813
  )
1505
1814
  );
1506
1815
  }
1507
- const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
1816
+ const unknownContractIds = scenarioIdsList.filter(
1817
+ (id) => !contractIds.has(id)
1818
+ );
1508
1819
  if (unknownContractIds.length > 0) {
1509
1820
  issues.push(
1510
- issue5(
1821
+ issue6(
1511
1822
  "QFAI-TRACE-008",
1512
1823
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1513
1824
  ", "
@@ -1519,23 +1830,23 @@ async function validateTraceability(root, config) {
1519
1830
  )
1520
1831
  );
1521
1832
  }
1522
- if (specIdsInScenario.length > 0) {
1833
+ if (specIdsList.length > 0) {
1523
1834
  const allowedBrIds = /* @__PURE__ */ new Set();
1524
- for (const specId of specIdsInScenario) {
1835
+ for (const specId of specIdsList) {
1525
1836
  const brIdsForSpec = specToBrIds.get(specId);
1526
1837
  if (!brIdsForSpec) {
1527
1838
  continue;
1528
1839
  }
1529
1840
  brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1530
1841
  }
1531
- const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
1842
+ const invalidBrIds = brIdsList.filter((id) => !allowedBrIds.has(id));
1532
1843
  if (invalidBrIds.length > 0) {
1533
1844
  issues.push(
1534
- issue5(
1845
+ issue6(
1535
1846
  "QFAI-TRACE-007",
1536
1847
  `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1537
1848
  ", "
1538
- )} (SPEC: ${specIdsInScenario.join(", ")})`,
1849
+ )} (SPEC: ${specIdsList.join(", ")})`,
1539
1850
  "error",
1540
1851
  file,
1541
1852
  "traceability.scenarioBrUnderSpec",
@@ -1547,7 +1858,7 @@ async function validateTraceability(root, config) {
1547
1858
  }
1548
1859
  if (upstreamIds.size === 0) {
1549
1860
  return [
1550
- issue5(
1861
+ issue6(
1551
1862
  "QFAI-TRACE-000",
1552
1863
  "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1553
1864
  "info",
@@ -1562,7 +1873,7 @@ async function validateTraceability(root, config) {
1562
1873
  );
1563
1874
  if (orphanBrIds.length > 0) {
1564
1875
  issues.push(
1565
- issue5(
1876
+ issue6(
1566
1877
  "QFAI_TRACE_BR_ORPHAN",
1567
1878
  `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1568
1879
  "error",
@@ -1579,13 +1890,13 @@ async function validateTraceability(root, config) {
1579
1890
  );
1580
1891
  if (scWithoutContracts.length > 0) {
1581
1892
  issues.push(
1582
- issue5(
1893
+ issue6(
1583
1894
  "QFAI_TRACE_SC_NO_CONTRACT",
1584
1895
  `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1585
1896
  ", "
1586
1897
  )}`,
1587
1898
  "error",
1588
- scenariosRoot,
1899
+ specsRoot,
1589
1900
  "traceability.scMustTouchContracts",
1590
1901
  scWithoutContracts
1591
1902
  )
@@ -1599,11 +1910,11 @@ async function validateTraceability(root, config) {
1599
1910
  );
1600
1911
  if (orphanContracts.length > 0) {
1601
1912
  issues.push(
1602
- issue5(
1913
+ issue6(
1603
1914
  "QFAI_CONTRACT_ORPHAN",
1604
1915
  `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1605
1916
  "error",
1606
- scenariosRoot,
1917
+ specsRoot,
1607
1918
  "traceability.allowOrphanContracts",
1608
1919
  orphanContracts
1609
1920
  )
@@ -1627,7 +1938,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1627
1938
  const targetFiles = [...codeFiles, ...testFiles];
1628
1939
  if (targetFiles.length === 0) {
1629
1940
  issues.push(
1630
- issue5(
1941
+ issue6(
1631
1942
  "QFAI-TRACE-001",
1632
1943
  "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1633
1944
  "info",
@@ -1640,7 +1951,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1640
1951
  const pattern = buildIdPattern(Array.from(upstreamIds));
1641
1952
  let found = false;
1642
1953
  for (const file of targetFiles) {
1643
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1954
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1644
1955
  if (pattern.test(text)) {
1645
1956
  found = true;
1646
1957
  break;
@@ -1648,7 +1959,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1648
1959
  }
1649
1960
  if (!found) {
1650
1961
  issues.push(
1651
- issue5(
1962
+ issue6(
1652
1963
  "QFAI-TRACE-002",
1653
1964
  "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1654
1965
  "warning",
@@ -1663,22 +1974,22 @@ function buildIdPattern(ids) {
1663
1974
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1664
1975
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
1665
1976
  }
1666
- function issue5(code, message, severity, file, rule, refs) {
1667
- const issue6 = {
1977
+ function issue6(code, message, severity, file, rule, refs) {
1978
+ const issue7 = {
1668
1979
  code,
1669
1980
  severity,
1670
1981
  message
1671
1982
  };
1672
1983
  if (file) {
1673
- issue6.file = file;
1984
+ issue7.file = file;
1674
1985
  }
1675
1986
  if (rule) {
1676
- issue6.rule = rule;
1987
+ issue7.rule = rule;
1677
1988
  }
1678
1989
  if (refs && refs.length > 0) {
1679
- issue6.refs = refs;
1990
+ issue7.refs = refs;
1680
1991
  }
1681
- return issue6;
1992
+ return issue7;
1682
1993
  }
1683
1994
 
1684
1995
  // src/core/validate.ts
@@ -1688,6 +1999,7 @@ async function validateProject(root, configResult) {
1688
1999
  const issues = [
1689
2000
  ...configIssues,
1690
2001
  ...await validateSpecs(root, config),
2002
+ ...await validateDeltas(root, config),
1691
2003
  ...await validateScenarios(root, config),
1692
2004
  ...await validateContracts(root, config),
1693
2005
  ...await validateDefinedIds(root, config),
@@ -1703,8 +2015,8 @@ async function validateProject(root, configResult) {
1703
2015
  }
1704
2016
  function countIssues(issues) {
1705
2017
  return issues.reduce(
1706
- (acc, issue6) => {
1707
- acc[issue6.severity] += 1;
2018
+ (acc, issue7) => {
2019
+ acc[issue7.severity] += 1;
1708
2020
  return acc;
1709
2021
  },
1710
2022
  { info: 0, warning: 0, error: 0 }
@@ -1717,21 +2029,15 @@ async function createReportData(root, validation, configResult) {
1717
2029
  const resolved = configResult ?? await loadConfig(root);
1718
2030
  const config = resolved.config;
1719
2031
  const configPath = resolved.configPath;
1720
- const specRoot = resolvePath(root, config, "specDir");
1721
- const decisionsRoot = resolvePath(root, config, "decisionsDir");
1722
- const scenariosRoot = resolvePath(root, config, "scenariosDir");
1723
- const apiRoot = resolvePath(root, config, "apiContractsDir");
1724
- const uiRoot = resolvePath(root, config, "uiContractsDir");
1725
- const dbRoot = resolvePath(root, config, "dataContractsDir");
2032
+ const specsRoot = resolvePath(root, config, "specsDir");
2033
+ const contractsRoot = resolvePath(root, config, "contractsDir");
2034
+ const apiRoot = import_node_path13.default.join(contractsRoot, "api");
2035
+ const uiRoot = import_node_path13.default.join(contractsRoot, "ui");
2036
+ const dbRoot = import_node_path13.default.join(contractsRoot, "db");
1726
2037
  const srcRoot = resolvePath(root, config, "srcDir");
1727
2038
  const testsRoot = resolvePath(root, config, "testsDir");
1728
- const specFiles = await collectSpecFiles(specRoot);
1729
- const scenarioFiles = await collectFiles(scenariosRoot, {
1730
- extensions: [".feature"]
1731
- });
1732
- const decisionFiles = await collectFiles(decisionsRoot, {
1733
- extensions: [".md"]
1734
- });
2039
+ const specFiles = await collectSpecFiles(specsRoot);
2040
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
1735
2041
  const {
1736
2042
  api: apiFiles,
1737
2043
  ui: uiFiles,
@@ -1740,7 +2046,6 @@ async function createReportData(root, validation, configResult) {
1740
2046
  const idsByPrefix = await collectIds([
1741
2047
  ...specFiles,
1742
2048
  ...scenarioFiles,
1743
- ...decisionFiles,
1744
2049
  ...apiFiles,
1745
2050
  ...uiFiles,
1746
2051
  ...dbFiles
@@ -1765,7 +2070,6 @@ async function createReportData(root, validation, configResult) {
1765
2070
  summary: {
1766
2071
  specs: specFiles.length,
1767
2072
  scenarios: scenarioFiles.length,
1768
- decisions: decisionFiles.length,
1769
2073
  contracts: {
1770
2074
  api: apiFiles.length,
1771
2075
  ui: uiFiles.length,
@@ -1799,7 +2103,6 @@ function formatReportMarkdown(data) {
1799
2103
  lines.push("## \u6982\u8981");
1800
2104
  lines.push(`- specs: ${data.summary.specs}`);
1801
2105
  lines.push(`- scenarios: ${data.summary.scenarios}`);
1802
- lines.push(`- decisions: ${data.summary.decisions}`);
1803
2106
  lines.push(
1804
2107
  `- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
1805
2108
  );
@@ -1875,7 +2178,7 @@ async function collectIds(files) {
1875
2178
  DATA: /* @__PURE__ */ new Set()
1876
2179
  };
1877
2180
  for (const file of files) {
1878
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2181
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1879
2182
  for (const prefix of ID_PREFIXES2) {
1880
2183
  const ids = extractIds(text, prefix);
1881
2184
  ids.forEach((id) => result[prefix].add(id));
@@ -1893,7 +2196,7 @@ async function collectIds(files) {
1893
2196
  async function collectUpstreamIds(files) {
1894
2197
  const ids = /* @__PURE__ */ new Set();
1895
2198
  for (const file of files) {
1896
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2199
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1897
2200
  extractAllIds(text).forEach((id) => ids.add(id));
1898
2201
  }
1899
2202
  return ids;
@@ -1914,7 +2217,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1914
2217
  }
1915
2218
  const pattern = buildIdPattern2(Array.from(upstreamIds));
1916
2219
  for (const file of targetFiles) {
1917
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2220
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1918
2221
  if (pattern.test(text)) {
1919
2222
  return true;
1920
2223
  }
@@ -1936,20 +2239,20 @@ function toSortedArray(values) {
1936
2239
  }
1937
2240
  function buildHotspots(issues) {
1938
2241
  const map = /* @__PURE__ */ new Map();
1939
- for (const issue6 of issues) {
1940
- if (!issue6.file) {
2242
+ for (const issue7 of issues) {
2243
+ if (!issue7.file) {
1941
2244
  continue;
1942
2245
  }
1943
- const current = map.get(issue6.file) ?? {
1944
- file: issue6.file,
2246
+ const current = map.get(issue7.file) ?? {
2247
+ file: issue7.file,
1945
2248
  total: 0,
1946
2249
  error: 0,
1947
2250
  warning: 0,
1948
2251
  info: 0
1949
2252
  };
1950
2253
  current.total += 1;
1951
- current[issue6.severity] += 1;
1952
- map.set(issue6.file, current);
2254
+ current[issue7.severity] += 1;
2255
+ map.set(issue7.file, current);
1953
2256
  }
1954
2257
  return Array.from(map.values()).sort(
1955
2258
  (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
@@ -1958,21 +2261,22 @@ function buildHotspots(issues) {
1958
2261
 
1959
2262
  // src/cli/commands/report.ts
1960
2263
  async function runReport(options) {
1961
- const root = import_node_path10.default.resolve(options.root);
2264
+ const root = import_node_path14.default.resolve(options.root);
1962
2265
  const configResult = await loadConfig(root);
1963
- const input = options.jsonPath ?? configResult.config.output.jsonPath;
1964
- const inputPath = import_node_path10.default.isAbsolute(input) ? input : import_node_path10.default.resolve(root, input);
2266
+ const input = configResult.config.output.validateJsonPath;
2267
+ const inputPath = import_node_path14.default.isAbsolute(input) ? input : import_node_path14.default.resolve(root, input);
1965
2268
  let validation;
1966
2269
  try {
1967
2270
  validation = await readValidationResult(inputPath);
1968
2271
  } catch (err) {
1969
- if (isMissingFileError(err)) {
2272
+ if (isMissingFileError2(err)) {
1970
2273
  error(
1971
2274
  [
1972
2275
  `qfai report: \u5165\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${inputPath}`,
1973
2276
  "",
1974
- "\u307E\u305A validate.json \u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B:",
1975
- ` qfai validate --json-path ${input}`,
2277
+ "\u307E\u305A qfai validate \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B:",
2278
+ " qfai validate",
2279
+ "\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u51FA\u529B\u5148: .qfai/out/validate.json\uFF09",
1976
2280
  "",
1977
2281
  "GitHub Actions \u30C6\u30F3\u30D7\u30EC\u3092\u4F7F\u3063\u3066\u3044\u308B\u5834\u5408\u306F\u3001workflow \u306E validate \u30B8\u30E7\u30D6\u3092\u5148\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
1978
2282
  ].join("\n")
@@ -1984,11 +2288,12 @@ async function runReport(options) {
1984
2288
  }
1985
2289
  const data = await createReportData(root, validation, configResult);
1986
2290
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
1987
- const defaultOut = options.format === "json" ? ".qfai/out/report.json" : ".qfai/out/report.md";
2291
+ const outRoot = resolvePath(root, configResult.config, "outDir");
2292
+ const defaultOut = options.format === "json" ? import_node_path14.default.join(outRoot, "report.json") : import_node_path14.default.join(outRoot, "report.md");
1988
2293
  const out = options.outPath ?? defaultOut;
1989
- const outPath = import_node_path10.default.isAbsolute(out) ? out : import_node_path10.default.resolve(root, out);
1990
- await (0, import_promises12.mkdir)(import_node_path10.default.dirname(outPath), { recursive: true });
1991
- await (0, import_promises12.writeFile)(outPath, `${output}
2294
+ 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}
1992
2297
  `, "utf-8");
1993
2298
  info(
1994
2299
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -1996,7 +2301,7 @@ async function runReport(options) {
1996
2301
  info(`wrote report: ${outPath}`);
1997
2302
  }
1998
2303
  async function readValidationResult(inputPath) {
1999
- const raw = await (0, import_promises12.readFile)(inputPath, "utf-8");
2304
+ const raw = await (0, import_promises13.readFile)(inputPath, "utf-8");
2000
2305
  const parsed = JSON.parse(raw);
2001
2306
  if (!isValidationResult(parsed)) {
2002
2307
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -2028,7 +2333,7 @@ function isValidationResult(value) {
2028
2333
  }
2029
2334
  return typeof counts.info === "number" && typeof counts.warning === "number" && typeof counts.error === "number";
2030
2335
  }
2031
- function isMissingFileError(error2) {
2336
+ function isMissingFileError2(error2) {
2032
2337
  if (!error2 || typeof error2 !== "object") {
2033
2338
  return false;
2034
2339
  }
@@ -2037,8 +2342,8 @@ function isMissingFileError(error2) {
2037
2342
  }
2038
2343
 
2039
2344
  // src/cli/commands/validate.ts
2040
- var import_promises13 = require("fs/promises");
2041
- var import_node_path11 = __toESM(require("path"), 1);
2345
+ var import_promises14 = require("fs/promises");
2346
+ var import_node_path15 = __toESM(require("path"), 1);
2042
2347
 
2043
2348
  // src/cli/lib/failOn.ts
2044
2349
  function shouldFail(result, failOn) {
@@ -2053,24 +2358,17 @@ function shouldFail(result, failOn) {
2053
2358
 
2054
2359
  // src/cli/commands/validate.ts
2055
2360
  async function runValidate(options) {
2056
- const root = import_node_path11.default.resolve(options.root);
2361
+ const root = import_node_path15.default.resolve(options.root);
2057
2362
  const configResult = await loadConfig(root);
2058
2363
  const result = await validateProject(root, configResult);
2059
- const format = options.format ?? configResult.config.output.format;
2060
- const explicitJsonPath = options.jsonPath;
2364
+ const format = options.format ?? "text";
2061
2365
  if (format === "text") {
2062
2366
  emitText(result);
2063
2367
  }
2064
2368
  if (format === "github") {
2065
2369
  result.issues.forEach(emitGitHub);
2066
2370
  }
2067
- const shouldWriteJson = format === "json" || explicitJsonPath !== void 0;
2068
- if (shouldWriteJson) {
2069
- const jsonPath = format === "json" ? options.jsonPath ?? configResult.config.output.jsonPath : explicitJsonPath;
2070
- if (jsonPath) {
2071
- await emitJson(result, root, jsonPath);
2072
- }
2073
- }
2371
+ await emitJson(result, root, configResult.config.output.validateJsonPath);
2074
2372
  const failOn = resolveFailOn(options, configResult.config.validation.failOn);
2075
2373
  return shouldFail(result, failOn) ? 1 : 0;
2076
2374
  }
@@ -2097,21 +2395,21 @@ function emitText(result) {
2097
2395
  `
2098
2396
  );
2099
2397
  }
2100
- function emitGitHub(issue6) {
2101
- const level = issue6.severity === "error" ? "error" : issue6.severity === "warning" ? "warning" : "notice";
2102
- const file = issue6.file ? `file=${issue6.file}` : "";
2103
- const line = issue6.loc?.line ? `,line=${issue6.loc.line}` : "";
2104
- const column = issue6.loc?.column ? `,col=${issue6.loc.column}` : "";
2398
+ function emitGitHub(issue7) {
2399
+ const level = issue7.severity === "error" ? "error" : issue7.severity === "warning" ? "warning" : "notice";
2400
+ const file = issue7.file ? `file=${issue7.file}` : "";
2401
+ const line = issue7.loc?.line ? `,line=${issue7.loc.line}` : "";
2402
+ const column = issue7.loc?.column ? `,col=${issue7.loc.column}` : "";
2105
2403
  const location = file ? ` ${file}${line}${column}` : "";
2106
2404
  process.stdout.write(
2107
- `::${level}${location}::${issue6.code}: ${issue6.message}
2405
+ `::${level}${location}::${issue7.code}: ${issue7.message}
2108
2406
  `
2109
2407
  );
2110
2408
  }
2111
2409
  async function emitJson(result, root, jsonPath) {
2112
- const abs = import_node_path11.default.isAbsolute(jsonPath) ? jsonPath : import_node_path11.default.resolve(root, jsonPath);
2113
- await (0, import_promises13.mkdir)(import_node_path11.default.dirname(abs), { recursive: true });
2114
- await (0, import_promises13.writeFile)(abs, `${JSON.stringify(result, null, 2)}
2410
+ 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)}
2115
2413
  `, "utf-8");
2116
2414
  }
2117
2415
 
@@ -2171,15 +2469,6 @@ function parseArgs(argv, cwd) {
2171
2469
  i += 1;
2172
2470
  break;
2173
2471
  }
2174
- case "--json-path":
2175
- {
2176
- const next = args[i + 1];
2177
- if (next) {
2178
- options.jsonPath = next;
2179
- }
2180
- }
2181
- i += 1;
2182
- break;
2183
2472
  case "--out":
2184
2473
  {
2185
2474
  const next = args[i + 1];
@@ -2210,7 +2499,7 @@ function applyFormatOption(command, value, options) {
2210
2499
  return;
2211
2500
  }
2212
2501
  if (command === "validate") {
2213
- if (value === "text" || value === "json" || value === "github") {
2502
+ if (value === "text" || value === "github") {
2214
2503
  options.validateFormat = value;
2215
2504
  }
2216
2505
  return;
@@ -2218,7 +2507,7 @@ function applyFormatOption(command, value, options) {
2218
2507
  if (value === "md" || value === "json") {
2219
2508
  options.reportFormat = value;
2220
2509
  }
2221
- if (value === "text" || value === "json" || value === "github") {
2510
+ if (value === "text" || value === "github") {
2222
2511
  options.validateFormat = value;
2223
2512
  }
2224
2513
  }
@@ -2244,15 +2533,13 @@ async function run(argv, cwd) {
2244
2533
  root: options.root,
2245
2534
  strict: options.strict,
2246
2535
  format: options.validateFormat,
2247
- ...options.failOn !== void 0 ? { failOn: options.failOn } : {},
2248
- ...options.jsonPath !== void 0 ? { jsonPath: options.jsonPath } : {}
2536
+ ...options.failOn !== void 0 ? { failOn: options.failOn } : {}
2249
2537
  });
2250
2538
  return;
2251
2539
  case "report":
2252
2540
  await runReport({
2253
2541
  root: options.root,
2254
2542
  format: options.reportFormat,
2255
- ...options.jsonPath !== void 0 ? { jsonPath: options.jsonPath } : {},
2256
2543
  ...options.reportOut !== void 0 ? { outPath: options.reportOut } : {}
2257
2544
  });
2258
2545
  return;
@@ -2274,14 +2561,13 @@ Options:
2274
2561
  --root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
2275
2562
  --dir <path> init \u306E\u51FA\u529B\u5148
2276
2563
  --force \u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u3092\u4E0A\u66F8\u304D
2277
- --yes init: \u975E\u5BFE\u8A71\u3067\u30C7\u30D5\u30A9\u30EB\u30C8\u3092\u63A1\u7528\uFF08\u73FE\u5728\u306F\u975E\u5BFE\u8A71\u304C\u65E2\u5B9A\u3001\u5C06\u6765\u306E\u5BFE\u8A71\u5C0E\u5165\u6642\u3082\u81EA\u52D5Yes\uFF09
2564
+ --yes init: \u4E88\u7D04\u30D5\u30E9\u30B0\uFF08\u73FE\u72B6\u306F\u975E\u5BFE\u8A71\u306E\u305F\u3081\u6319\u52D5\u5DEE\u306A\u3057\u3002\u5C06\u6765\u306E\u5BFE\u8A71\u5C0E\u5165\u6642\u306B\u81EA\u52D5Yes\uFF09
2278
2565
  --dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
2279
- --format <text|json|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
2566
+ --format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
2280
2567
  --format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
2281
- --strict validate: warning \u4EE5\u4E0A\u3067 exit 1
2568
+ --strict validate: warning \u4EE5\u4E0A\u3067 exit 1
2282
2569
  --fail-on <error|warning|never> validate: \u5931\u6557\u6761\u4EF6
2283
- --json-path <path> validate: JSON \u51FA\u529B\u5148 / report: validate JSON \u5165\u529B
2284
- --out <path> report: \u51FA\u529B\u5148
2570
+ --out <path> report: \u51FA\u529B\u5148
2285
2571
  -h, --help \u30D8\u30EB\u30D7\u8868\u793A
2286
2572
  `;
2287
2573
  }