qfai 0.2.9 → 0.3.0

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.
@@ -137,7 +137,7 @@ function report(copied, skipped, dryRun, label) {
137
137
  }
138
138
 
139
139
  // src/cli/commands/report.ts
140
- import { mkdir as mkdir2, readFile as readFile10, writeFile } from "fs/promises";
140
+ import { mkdir as mkdir2, readFile as readFile11, writeFile } from "fs/promises";
141
141
  import path10 from "path";
142
142
 
143
143
  // src/core/config.ts
@@ -512,7 +512,7 @@ function isRecord(value) {
512
512
  }
513
513
 
514
514
  // src/core/report.ts
515
- import { readFile as readFile9 } from "fs/promises";
515
+ import { readFile as readFile10 } from "fs/promises";
516
516
 
517
517
  // src/core/discovery.ts
518
518
  import path6 from "path";
@@ -661,8 +661,8 @@ import { readFile as readFile2 } from "fs/promises";
661
661
  import path7 from "path";
662
662
  import { fileURLToPath as fileURLToPath2 } from "url";
663
663
  async function resolveToolVersion() {
664
- if ("0.2.9".length > 0) {
665
- return "0.2.9";
664
+ if ("0.3.0".length > 0) {
665
+ return "0.3.0";
666
666
  }
667
667
  try {
668
668
  const packagePath = resolvePackageJsonPath();
@@ -965,29 +965,113 @@ function formatError2(error2) {
965
965
  return String(error2);
966
966
  }
967
967
  function issue(code, message, severity, file, rule, refs) {
968
- const issue6 = {
968
+ const issue7 = {
969
969
  code,
970
970
  severity,
971
971
  message
972
972
  };
973
973
  if (file) {
974
- issue6.file = file;
974
+ issue7.file = file;
975
975
  }
976
976
  if (rule) {
977
- issue6.rule = rule;
977
+ issue7.rule = rule;
978
978
  }
979
979
  if (refs && refs.length > 0) {
980
- issue6.refs = refs;
980
+ issue7.refs = refs;
981
981
  }
982
- return issue6;
982
+ return issue7;
983
+ }
984
+
985
+ // src/core/validators/decisions.ts
986
+ import { readFile as readFile4 } from "fs/promises";
987
+
988
+ // src/core/parse/adr.ts
989
+ var ADR_ID_RE = /\bADR-\d{4}\b/;
990
+ function extractField(md, key) {
991
+ const pattern = new RegExp(`^\\s*-\\s*${key}:\\s*(.+)\\s*$`, "m");
992
+ return md.match(pattern)?.[1]?.trim();
993
+ }
994
+ function parseAdr(md, file) {
995
+ const adrId = md.match(ADR_ID_RE)?.[0];
996
+ const fields = {};
997
+ const status = extractField(md, "Status");
998
+ const context = extractField(md, "Context");
999
+ const decision = extractField(md, "Decision");
1000
+ const consequences = extractField(md, "Consequences");
1001
+ const related = extractField(md, "Related");
1002
+ if (status) fields.status = status;
1003
+ if (context) fields.context = context;
1004
+ if (decision) fields.decision = decision;
1005
+ if (consequences) fields.consequences = consequences;
1006
+ if (related) fields.related = related;
1007
+ const parsed = {
1008
+ file,
1009
+ fields
1010
+ };
1011
+ if (adrId) {
1012
+ parsed.adrId = adrId;
1013
+ }
1014
+ return parsed;
1015
+ }
1016
+
1017
+ // src/core/validators/decisions.ts
1018
+ var REQUIRED_FIELDS = [
1019
+ { key: "status", label: "Status" },
1020
+ { key: "context", label: "Context" },
1021
+ { key: "decision", label: "Decision" },
1022
+ { key: "consequences", label: "Consequences" }
1023
+ ];
1024
+ async function validateDecisions(root, config) {
1025
+ const decisionsRoot = resolvePath(root, config, "decisionsDir");
1026
+ const files = await collectFiles(decisionsRoot, { extensions: [".md"] });
1027
+ if (files.length === 0) {
1028
+ return [];
1029
+ }
1030
+ const issues = [];
1031
+ for (const file of files) {
1032
+ const text = await readFile4(file, "utf-8");
1033
+ const parsed = parseAdr(text, file);
1034
+ const missing = REQUIRED_FIELDS.filter(
1035
+ (field) => !parsed.fields[field.key]
1036
+ );
1037
+ if (missing.length > 0) {
1038
+ issues.push(
1039
+ issue2(
1040
+ "QFAI-ADR-001",
1041
+ `ADR \u5FC5\u9808\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missing.map((field) => field.label).join(", ")}`,
1042
+ "error",
1043
+ file,
1044
+ "adr.requiredFields"
1045
+ )
1046
+ );
1047
+ }
1048
+ }
1049
+ return issues;
1050
+ }
1051
+ function issue2(code, message, severity, file, rule, refs) {
1052
+ const issue7 = {
1053
+ code,
1054
+ severity,
1055
+ message
1056
+ };
1057
+ if (file) {
1058
+ issue7.file = file;
1059
+ }
1060
+ if (rule) {
1061
+ issue7.rule = rule;
1062
+ }
1063
+ if (refs && refs.length > 0) {
1064
+ issue7.refs = refs;
1065
+ }
1066
+ return issue7;
983
1067
  }
984
1068
 
985
1069
  // src/core/validators/ids.ts
986
- import { readFile as readFile5 } from "fs/promises";
1070
+ import { readFile as readFile6 } from "fs/promises";
987
1071
  import path9 from "path";
988
1072
 
989
1073
  // src/core/contractIndex.ts
990
- import { readFile as readFile4 } from "fs/promises";
1074
+ import { readFile as readFile5 } from "fs/promises";
991
1075
  async function buildContractIndex(root, config) {
992
1076
  const uiRoot = resolvePath(root, config, "uiContractsDir");
993
1077
  const apiRoot = resolvePath(root, config, "apiContractsDir");
@@ -1010,7 +1094,7 @@ async function buildContractIndex(root, config) {
1010
1094
  }
1011
1095
  async function indexUiContracts(files, index) {
1012
1096
  for (const file of files) {
1013
- const text = await readFile4(file, "utf-8");
1097
+ const text = await readFile5(file, "utf-8");
1014
1098
  try {
1015
1099
  const doc = parseStructuredContract(file, text);
1016
1100
  extractUiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1022,7 +1106,7 @@ async function indexUiContracts(files, index) {
1022
1106
  }
1023
1107
  async function indexApiContracts(files, index) {
1024
1108
  for (const file of files) {
1025
- const text = await readFile4(file, "utf-8");
1109
+ const text = await readFile5(file, "utf-8");
1026
1110
  try {
1027
1111
  const doc = parseStructuredContract(file, text);
1028
1112
  extractApiContractIds(doc).forEach((id) => record(index, id, file));
@@ -1034,7 +1118,7 @@ async function indexApiContracts(files, index) {
1034
1118
  }
1035
1119
  async function indexDataContracts(files, index) {
1036
1120
  for (const file of files) {
1037
- const text = await readFile4(file, "utf-8");
1121
+ const text = await readFile5(file, "utf-8");
1038
1122
  extractIds(text, "DATA").forEach((id) => record(index, id, file));
1039
1123
  }
1040
1124
  }
@@ -1045,7 +1129,158 @@ function record(index, id, file) {
1045
1129
  index.idToFiles.set(id, current);
1046
1130
  }
1047
1131
 
1132
+ // src/core/parse/gherkin.ts
1133
+ var FEATURE_RE = /^\s*Feature:\s+/;
1134
+ var SCENARIO_RE = /^\s*Scenario:\s*(.+)\s*$/;
1135
+ var TAG_LINE_RE = /^\s*@/;
1136
+ function parseTags(line) {
1137
+ return line.trim().split(/\s+/).filter((tag) => tag.startsWith("@")).map((tag) => tag.replace(/^@/, ""));
1138
+ }
1139
+ function parseGherkinFeature(text, file) {
1140
+ const lines = text.split(/\r?\n/);
1141
+ const scenarios = [];
1142
+ let featurePresent = false;
1143
+ for (let i = 0; i < lines.length; i++) {
1144
+ const line = lines[i] ?? "";
1145
+ if (FEATURE_RE.test(line)) {
1146
+ featurePresent = true;
1147
+ }
1148
+ const match = line.match(SCENARIO_RE);
1149
+ if (!match) continue;
1150
+ const scenarioName = match[1];
1151
+ if (!scenarioName) continue;
1152
+ const tags = [];
1153
+ for (let j = i - 1; j >= 0; j--) {
1154
+ const previous = lines[j] ?? "";
1155
+ if (previous.trim() === "") continue;
1156
+ if (!TAG_LINE_RE.test(previous)) break;
1157
+ tags.unshift(...parseTags(previous));
1158
+ }
1159
+ scenarios.push({ name: scenarioName, line: i + 1, tags });
1160
+ }
1161
+ return { file, featurePresent, scenarios };
1162
+ }
1163
+
1164
+ // src/core/parse/markdown.ts
1165
+ var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1166
+ function parseHeadings(md) {
1167
+ const lines = md.split(/\r?\n/);
1168
+ const headings = [];
1169
+ for (let i = 0; i < lines.length; i++) {
1170
+ const line = lines[i] ?? "";
1171
+ const match = line.match(HEADING_RE);
1172
+ if (!match) continue;
1173
+ const levelToken = match[1];
1174
+ const title = match[2];
1175
+ if (!levelToken || !title) continue;
1176
+ headings.push({
1177
+ level: levelToken.length,
1178
+ title: title.trim(),
1179
+ line: i + 1
1180
+ });
1181
+ }
1182
+ return headings;
1183
+ }
1184
+ function extractH2Sections(md) {
1185
+ const lines = md.split(/\r?\n/);
1186
+ const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1187
+ const sections = /* @__PURE__ */ new Map();
1188
+ for (let i = 0; i < headings.length; i++) {
1189
+ const current = headings[i];
1190
+ if (!current) continue;
1191
+ const next = headings[i + 1];
1192
+ const startLine = current.line + 1;
1193
+ const endLine = (next?.line ?? lines.length + 1) - 1;
1194
+ const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1195
+ sections.set(current.title.trim(), {
1196
+ title: current.title.trim(),
1197
+ startLine,
1198
+ endLine,
1199
+ body
1200
+ });
1201
+ }
1202
+ return sections;
1203
+ }
1204
+
1205
+ // src/core/parse/spec.ts
1206
+ var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
1207
+ var BR_LINE_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s*\((P[0-3])\)\s*(.+)$/;
1208
+ var BR_LINE_ANY_PRIORITY_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s*\((P[^)]+)\)\s*(.+)$/;
1209
+ var BR_LINE_NO_PRIORITY_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s+(?!\()(.*\S.*)$/;
1210
+ var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
1211
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
1212
+ function parseSpec(md, file) {
1213
+ const headings = parseHeadings(md);
1214
+ const h1 = headings.find((heading) => heading.level === 1);
1215
+ const specId = h1?.title.match(SPEC_ID_RE)?.[0];
1216
+ const sections = extractH2Sections(md);
1217
+ const sectionNames = new Set(Array.from(sections.keys()));
1218
+ const brSection = sections.get(BR_SECTION_TITLE);
1219
+ const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
1220
+ const startLine = brSection?.startLine ?? 1;
1221
+ const brs = [];
1222
+ const brsWithoutPriority = [];
1223
+ const brsWithInvalidPriority = [];
1224
+ for (let i = 0; i < brLines.length; i++) {
1225
+ const lineText = brLines[i] ?? "";
1226
+ const lineNumber = startLine + i;
1227
+ const validMatch = lineText.match(BR_LINE_RE);
1228
+ if (validMatch) {
1229
+ const id = validMatch[1];
1230
+ const priority = validMatch[2];
1231
+ const text = validMatch[3];
1232
+ if (!id || !priority || !text) continue;
1233
+ brs.push({
1234
+ id,
1235
+ priority,
1236
+ text: text.trim(),
1237
+ line: lineNumber
1238
+ });
1239
+ continue;
1240
+ }
1241
+ const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
1242
+ if (anyPriorityMatch) {
1243
+ const id = anyPriorityMatch[1];
1244
+ const priority = anyPriorityMatch[2];
1245
+ const text = anyPriorityMatch[3];
1246
+ if (!id || !priority || !text) continue;
1247
+ if (!VALID_PRIORITIES.has(priority)) {
1248
+ brsWithInvalidPriority.push({
1249
+ id,
1250
+ priority,
1251
+ text: text.trim(),
1252
+ line: lineNumber
1253
+ });
1254
+ }
1255
+ continue;
1256
+ }
1257
+ const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
1258
+ if (noPriorityMatch) {
1259
+ const id = noPriorityMatch[1];
1260
+ const text = noPriorityMatch[2];
1261
+ if (!id || !text) continue;
1262
+ brsWithoutPriority.push({
1263
+ id,
1264
+ text: text.trim(),
1265
+ line: lineNumber
1266
+ });
1267
+ }
1268
+ }
1269
+ const parsed = {
1270
+ file,
1271
+ sections: sectionNames,
1272
+ brs,
1273
+ brsWithoutPriority,
1274
+ brsWithInvalidPriority
1275
+ };
1276
+ if (specId) {
1277
+ parsed.specId = specId;
1278
+ }
1279
+ return parsed;
1280
+ }
1281
+
1048
1282
  // src/core/validators/ids.ts
1283
+ var SC_TAG_RE = /^SC-\d{4}$/;
1049
1284
  async function validateDefinedIds(root, config) {
1050
1285
  const issues = [];
1051
1286
  const specRoot = resolvePath(root, config, "specDir");
@@ -1069,7 +1304,7 @@ async function validateDefinedIds(root, config) {
1069
1304
  }
1070
1305
  const sorted = Array.from(files).sort();
1071
1306
  issues.push(
1072
- issue2(
1307
+ issue3(
1073
1308
  "QFAI-ID-001",
1074
1309
  `ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
1075
1310
  "error",
@@ -1082,15 +1317,25 @@ async function validateDefinedIds(root, config) {
1082
1317
  }
1083
1318
  async function collectSpecDefinitionIds(files, out) {
1084
1319
  for (const file of files) {
1085
- const text = await readFile5(file, "utf-8");
1086
- extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
1087
- extractIds(text, "BR").forEach((id) => recordId(out, id, file));
1320
+ const text = await readFile6(file, "utf-8");
1321
+ const parsed = parseSpec(text, file);
1322
+ if (parsed.specId) {
1323
+ recordId(out, parsed.specId, file);
1324
+ }
1325
+ parsed.brs.forEach((br) => recordId(out, br.id, file));
1088
1326
  }
1089
1327
  }
1090
1328
  async function collectScenarioDefinitionIds(files, out) {
1091
1329
  for (const file of files) {
1092
- const text = await readFile5(file, "utf-8");
1093
- extractIds(text, "SC").forEach((id) => recordId(out, id, file));
1330
+ const text = await readFile6(file, "utf-8");
1331
+ const parsed = parseGherkinFeature(text, file);
1332
+ for (const scenario of parsed.scenarios) {
1333
+ for (const tag of scenario.tags) {
1334
+ if (SC_TAG_RE.test(tag)) {
1335
+ recordId(out, tag, file);
1336
+ }
1337
+ }
1338
+ }
1094
1339
  }
1095
1340
  }
1096
1341
  function recordId(out, id, file) {
@@ -1104,29 +1349,32 @@ function formatFileList(files, root) {
1104
1349
  return relative.length > 0 ? relative : file;
1105
1350
  }).join(", ");
1106
1351
  }
1107
- function issue2(code, message, severity, file, rule, refs) {
1108
- const issue6 = {
1352
+ function issue3(code, message, severity, file, rule, refs) {
1353
+ const issue7 = {
1109
1354
  code,
1110
1355
  severity,
1111
1356
  message
1112
1357
  };
1113
1358
  if (file) {
1114
- issue6.file = file;
1359
+ issue7.file = file;
1115
1360
  }
1116
1361
  if (rule) {
1117
- issue6.rule = rule;
1362
+ issue7.rule = rule;
1118
1363
  }
1119
1364
  if (refs && refs.length > 0) {
1120
- issue6.refs = refs;
1365
+ issue7.refs = refs;
1121
1366
  }
1122
- return issue6;
1367
+ return issue7;
1123
1368
  }
1124
1369
 
1125
1370
  // src/core/validators/scenario.ts
1126
- import { readFile as readFile6 } from "fs/promises";
1371
+ import { readFile as readFile7 } from "fs/promises";
1127
1372
  var GIVEN_PATTERN = /\bGiven\b/;
1128
1373
  var WHEN_PATTERN = /\bWhen\b/;
1129
1374
  var THEN_PATTERN = /\bThen\b/;
1375
+ var SC_TAG_RE2 = /^SC-\d{4}$/;
1376
+ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
1377
+ var BR_TAG_RE = /^BR-\d{4}$/;
1130
1378
  async function validateScenarios(root, config) {
1131
1379
  const scenariosRoot = resolvePath(root, config, "scenariosDir");
1132
1380
  const files = await collectFiles(scenariosRoot, {
@@ -1134,7 +1382,7 @@ async function validateScenarios(root, config) {
1134
1382
  });
1135
1383
  if (files.length === 0) {
1136
1384
  return [
1137
- issue3(
1385
+ issue4(
1138
1386
  "QFAI-SC-000",
1139
1387
  "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1140
1388
  "info",
@@ -1145,13 +1393,14 @@ async function validateScenarios(root, config) {
1145
1393
  }
1146
1394
  const issues = [];
1147
1395
  for (const file of files) {
1148
- const text = await readFile6(file, "utf-8");
1396
+ const text = await readFile7(file, "utf-8");
1149
1397
  issues.push(...validateScenarioContent(text, file));
1150
1398
  }
1151
1399
  return issues;
1152
1400
  }
1153
1401
  function validateScenarioContent(text, file) {
1154
1402
  const issues = [];
1403
+ const parsed = parseGherkinFeature(text, file);
1155
1404
  const invalidIds = extractInvalidIds(text, [
1156
1405
  "SPEC",
1157
1406
  "BR",
@@ -1163,7 +1412,7 @@ function validateScenarioContent(text, file) {
1163
1412
  ]);
1164
1413
  if (invalidIds.length > 0) {
1165
1414
  issues.push(
1166
- issue3(
1415
+ issue4(
1167
1416
  "QFAI-ID-002",
1168
1417
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1169
1418
  "error",
@@ -1173,41 +1422,56 @@ function validateScenarioContent(text, file) {
1173
1422
  )
1174
1423
  );
1175
1424
  }
1176
- const scIds = extractIds(text, "SC");
1177
- if (scIds.length === 0) {
1178
- issues.push(
1179
- issue3(
1180
- "QFAI-SC-001",
1181
- "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1182
- "error",
1183
- file,
1184
- "scenario.id"
1185
- )
1186
- );
1187
- }
1188
- const specIds = extractIds(text, "SPEC");
1189
- if (specIds.length === 0) {
1425
+ const missingStructure = [];
1426
+ if (!parsed.featurePresent) missingStructure.push("Feature");
1427
+ if (parsed.scenarios.length === 0) missingStructure.push("Scenario");
1428
+ if (missingStructure.length > 0) {
1190
1429
  issues.push(
1191
- issue3(
1192
- "QFAI-SC-002",
1193
- "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1430
+ issue4(
1431
+ "QFAI-SC-006",
1432
+ `Scenario \u30D5\u30A1\u30A4\u30EB\u306B\u5FC5\u8981\u306A\u69CB\u9020\u304C\u3042\u308A\u307E\u305B\u3093: ${missingStructure.join(
1433
+ ", "
1434
+ )}`,
1194
1435
  "error",
1195
1436
  file,
1196
- "scenario.spec"
1437
+ "scenario.structure"
1197
1438
  )
1198
1439
  );
1199
1440
  }
1200
- const brIds = extractIds(text, "BR");
1201
- if (brIds.length === 0) {
1202
- issues.push(
1203
- issue3(
1204
- "QFAI-SC-003",
1205
- "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1206
- "error",
1207
- file,
1208
- "scenario.br"
1209
- )
1210
- );
1441
+ for (const scenario of parsed.scenarios) {
1442
+ if (scenario.tags.length === 0) {
1443
+ issues.push(
1444
+ issue4(
1445
+ "QFAI-SC-007",
1446
+ `Scenario \u30BF\u30B0\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${scenario.name}`,
1447
+ "error",
1448
+ file,
1449
+ "scenario.tags"
1450
+ )
1451
+ );
1452
+ continue;
1453
+ }
1454
+ const missingTags = [];
1455
+ if (!scenario.tags.some((tag) => SC_TAG_RE2.test(tag))) {
1456
+ missingTags.push("SC");
1457
+ }
1458
+ if (!scenario.tags.some((tag) => SPEC_TAG_RE.test(tag))) {
1459
+ missingTags.push("SPEC");
1460
+ }
1461
+ if (!scenario.tags.some((tag) => BR_TAG_RE.test(tag))) {
1462
+ missingTags.push("BR");
1463
+ }
1464
+ if (missingTags.length > 0) {
1465
+ issues.push(
1466
+ issue4(
1467
+ "QFAI-SC-008",
1468
+ `Scenario \u30BF\u30B0\u306B\u4E0D\u8DB3\u304C\u3042\u308A\u307E\u3059: ${missingTags.join(", ")} (${scenario.name})`,
1469
+ "error",
1470
+ file,
1471
+ "scenario.tagIds"
1472
+ )
1473
+ );
1474
+ }
1211
1475
  }
1212
1476
  const missingSteps = [];
1213
1477
  if (!GIVEN_PATTERN.test(text)) {
@@ -1221,7 +1485,7 @@ function validateScenarioContent(text, file) {
1221
1485
  }
1222
1486
  if (missingSteps.length > 0) {
1223
1487
  issues.push(
1224
- issue3(
1488
+ issue4(
1225
1489
  "QFAI-SC-005",
1226
1490
  `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
1227
1491
  "warning",
@@ -1232,33 +1496,33 @@ function validateScenarioContent(text, file) {
1232
1496
  }
1233
1497
  return issues;
1234
1498
  }
1235
- function issue3(code, message, severity, file, rule, refs) {
1236
- const issue6 = {
1499
+ function issue4(code, message, severity, file, rule, refs) {
1500
+ const issue7 = {
1237
1501
  code,
1238
1502
  severity,
1239
1503
  message
1240
1504
  };
1241
1505
  if (file) {
1242
- issue6.file = file;
1506
+ issue7.file = file;
1243
1507
  }
1244
1508
  if (rule) {
1245
- issue6.rule = rule;
1509
+ issue7.rule = rule;
1246
1510
  }
1247
1511
  if (refs && refs.length > 0) {
1248
- issue6.refs = refs;
1512
+ issue7.refs = refs;
1249
1513
  }
1250
- return issue6;
1514
+ return issue7;
1251
1515
  }
1252
1516
 
1253
1517
  // src/core/validators/spec.ts
1254
- import { readFile as readFile7 } from "fs/promises";
1518
+ import { readFile as readFile8 } from "fs/promises";
1255
1519
  async function validateSpecs(root, config) {
1256
1520
  const specsRoot = resolvePath(root, config, "specDir");
1257
1521
  const files = await collectSpecFiles(specsRoot);
1258
1522
  if (files.length === 0) {
1259
1523
  const expected = "spec-0001-<slug>.md";
1260
1524
  return [
1261
- issue4(
1525
+ issue5(
1262
1526
  "QFAI-SPEC-000",
1263
1527
  `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}`,
1264
1528
  "info",
@@ -1269,7 +1533,7 @@ async function validateSpecs(root, config) {
1269
1533
  }
1270
1534
  const issues = [];
1271
1535
  for (const file of files) {
1272
- const text = await readFile7(file, "utf-8");
1536
+ const text = await readFile8(file, "utf-8");
1273
1537
  issues.push(
1274
1538
  ...validateSpecContent(
1275
1539
  text,
@@ -1282,6 +1546,7 @@ async function validateSpecs(root, config) {
1282
1546
  }
1283
1547
  function validateSpecContent(text, file, requiredSections) {
1284
1548
  const issues = [];
1549
+ const parsed = parseSpec(text, file);
1285
1550
  const invalidIds = extractInvalidIds(text, [
1286
1551
  "SPEC",
1287
1552
  "BR",
@@ -1293,7 +1558,7 @@ function validateSpecContent(text, file, requiredSections) {
1293
1558
  ]);
1294
1559
  if (invalidIds.length > 0) {
1295
1560
  issues.push(
1296
- issue4(
1561
+ issue5(
1297
1562
  "QFAI-ID-002",
1298
1563
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1299
1564
  "error",
@@ -1303,10 +1568,9 @@ function validateSpecContent(text, file, requiredSections) {
1303
1568
  )
1304
1569
  );
1305
1570
  }
1306
- const specIds = extractIds(text, "SPEC");
1307
- if (specIds.length === 0) {
1571
+ if (!parsed.specId) {
1308
1572
  issues.push(
1309
- issue4(
1573
+ issue5(
1310
1574
  "QFAI-SPEC-001",
1311
1575
  "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1312
1576
  "error",
@@ -1315,10 +1579,9 @@ function validateSpecContent(text, file, requiredSections) {
1315
1579
  )
1316
1580
  );
1317
1581
  }
1318
- const brIds = extractIds(text, "BR");
1319
- if (brIds.length === 0) {
1582
+ if (parsed.brs.length === 0) {
1320
1583
  issues.push(
1321
- issue4(
1584
+ issue5(
1322
1585
  "QFAI-SPEC-002",
1323
1586
  "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1324
1587
  "error",
@@ -1327,10 +1590,34 @@ function validateSpecContent(text, file, requiredSections) {
1327
1590
  )
1328
1591
  );
1329
1592
  }
1593
+ for (const br of parsed.brsWithoutPriority) {
1594
+ issues.push(
1595
+ issue5(
1596
+ "QFAI-BR-001",
1597
+ `BR \u884C\u306B Priority \u304C\u3042\u308A\u307E\u305B\u3093: ${br.id}`,
1598
+ "error",
1599
+ file,
1600
+ "spec.brPriority",
1601
+ [br.id]
1602
+ )
1603
+ );
1604
+ }
1605
+ for (const br of parsed.brsWithInvalidPriority) {
1606
+ issues.push(
1607
+ issue5(
1608
+ "QFAI-BR-002",
1609
+ `BR Priority \u304C\u4E0D\u6B63\u3067\u3059: ${br.id} (${br.priority})`,
1610
+ "error",
1611
+ file,
1612
+ "spec.brPriority",
1613
+ [br.id]
1614
+ )
1615
+ );
1616
+ }
1330
1617
  const scIds = extractIds(text, "SC");
1331
1618
  if (scIds.length > 0) {
1332
1619
  issues.push(
1333
- issue4(
1620
+ issue5(
1334
1621
  "QFAI-SPEC-003",
1335
1622
  "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
1336
1623
  "warning",
@@ -1341,9 +1628,9 @@ function validateSpecContent(text, file, requiredSections) {
1341
1628
  );
1342
1629
  }
1343
1630
  for (const section of requiredSections) {
1344
- if (!text.includes(section)) {
1631
+ if (!parsed.sections.has(section)) {
1345
1632
  issues.push(
1346
- issue4(
1633
+ issue5(
1347
1634
  "QFAI-SPEC-004",
1348
1635
  `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
1349
1636
  "error",
@@ -1355,26 +1642,32 @@ function validateSpecContent(text, file, requiredSections) {
1355
1642
  }
1356
1643
  return issues;
1357
1644
  }
1358
- function issue4(code, message, severity, file, rule, refs) {
1359
- const issue6 = {
1645
+ function issue5(code, message, severity, file, rule, refs) {
1646
+ const issue7 = {
1360
1647
  code,
1361
1648
  severity,
1362
1649
  message
1363
1650
  };
1364
1651
  if (file) {
1365
- issue6.file = file;
1652
+ issue7.file = file;
1366
1653
  }
1367
1654
  if (rule) {
1368
- issue6.rule = rule;
1655
+ issue7.rule = rule;
1369
1656
  }
1370
1657
  if (refs && refs.length > 0) {
1371
- issue6.refs = refs;
1658
+ issue7.refs = refs;
1372
1659
  }
1373
- return issue6;
1660
+ return issue7;
1374
1661
  }
1375
1662
 
1376
1663
  // src/core/validators/traceability.ts
1377
- import { readFile as readFile8 } from "fs/promises";
1664
+ import { readFile as readFile9 } from "fs/promises";
1665
+ var SC_TAG_RE3 = /^SC-\d{4}$/;
1666
+ var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1667
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
1668
+ var UI_TAG_RE = /^UI-\d{4}$/;
1669
+ var API_TAG_RE = /^API-\d{4}$/;
1670
+ var DATA_TAG_RE = /^DATA-\d{4}$/;
1378
1671
  async function validateTraceability(root, config) {
1379
1672
  const issues = [];
1380
1673
  const specsRoot = resolvePath(root, config, "specDir");
@@ -1400,11 +1693,13 @@ async function validateTraceability(root, config) {
1400
1693
  const contractIndex = await buildContractIndex(root, config);
1401
1694
  const contractIds = contractIndex.ids;
1402
1695
  for (const file of specFiles) {
1403
- const text = await readFile8(file, "utf-8");
1696
+ const text = await readFile9(file, "utf-8");
1404
1697
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1405
- const specIdsInFile = extractIds(text, "SPEC");
1406
- specIdsInFile.forEach((id) => specIds.add(id));
1407
- const brIds = extractIds(text, "BR");
1698
+ const parsed = parseSpec(text, file);
1699
+ if (parsed.specId) {
1700
+ specIds.add(parsed.specId);
1701
+ }
1702
+ const brIds = parsed.brs.map((br) => br.id);
1408
1703
  brIds.forEach((id) => brIdsInSpecs.add(id));
1409
1704
  const referencedContractIds = /* @__PURE__ */ new Set([
1410
1705
  ...extractIds(text, "UI"),
@@ -1416,7 +1711,7 @@ async function validateTraceability(root, config) {
1416
1711
  );
1417
1712
  if (unknownContractIds.length > 0) {
1418
1713
  issues.push(
1419
- issue5(
1714
+ issue6(
1420
1715
  "QFAI-TRACE-009",
1421
1716
  `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1422
1717
  ", "
@@ -1428,37 +1723,54 @@ async function validateTraceability(root, config) {
1428
1723
  )
1429
1724
  );
1430
1725
  }
1431
- for (const specId of specIdsInFile) {
1432
- const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
1726
+ if (parsed.specId) {
1727
+ const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
1433
1728
  brIds.forEach((id) => current.add(id));
1434
- specToBrIds.set(specId, current);
1729
+ specToBrIds.set(parsed.specId, current);
1435
1730
  }
1436
1731
  }
1437
1732
  for (const file of decisionFiles) {
1438
- const text = await readFile8(file, "utf-8");
1733
+ const text = await readFile9(file, "utf-8");
1439
1734
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1440
1735
  }
1441
1736
  for (const file of scenarioFiles) {
1442
- const text = await readFile8(file, "utf-8");
1737
+ const text = await readFile9(file, "utf-8");
1443
1738
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1444
- const specIdsInScenario = extractIds(text, "SPEC");
1445
- const brIds = extractIds(text, "BR");
1446
- const scIds = extractIds(text, "SC");
1447
- const scenarioIds = [
1448
- ...extractIds(text, "UI"),
1449
- ...extractIds(text, "API"),
1450
- ...extractIds(text, "DATA")
1451
- ];
1452
- brIds.forEach((id) => brIdsInScenarios.add(id));
1453
- scIds.forEach((id) => scIdsInScenarios.add(id));
1454
- scenarioIds.forEach((id) => scenarioContractIds.add(id));
1455
- if (scenarioIds.length > 0) {
1456
- scIds.forEach((id) => scWithContracts.add(id));
1739
+ const parsed = parseGherkinFeature(text, file);
1740
+ const specIdsInScenario = /* @__PURE__ */ new Set();
1741
+ const brIds = /* @__PURE__ */ new Set();
1742
+ const scIds = /* @__PURE__ */ new Set();
1743
+ const scenarioIds = /* @__PURE__ */ new Set();
1744
+ for (const scenario of parsed.scenarios) {
1745
+ for (const tag of scenario.tags) {
1746
+ if (SPEC_TAG_RE2.test(tag)) {
1747
+ specIdsInScenario.add(tag);
1748
+ }
1749
+ if (BR_TAG_RE2.test(tag)) {
1750
+ brIds.add(tag);
1751
+ }
1752
+ if (SC_TAG_RE3.test(tag)) {
1753
+ scIds.add(tag);
1754
+ }
1755
+ if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
1756
+ scenarioIds.add(tag);
1757
+ }
1758
+ }
1759
+ }
1760
+ const specIdsList = Array.from(specIdsInScenario);
1761
+ const brIdsList = Array.from(brIds);
1762
+ const scIdsList = Array.from(scIds);
1763
+ const scenarioIdsList = Array.from(scenarioIds);
1764
+ brIdsList.forEach((id) => brIdsInScenarios.add(id));
1765
+ scIdsList.forEach((id) => scIdsInScenarios.add(id));
1766
+ scenarioIdsList.forEach((id) => scenarioContractIds.add(id));
1767
+ if (scenarioIdsList.length > 0) {
1768
+ scIdsList.forEach((id) => scWithContracts.add(id));
1457
1769
  }
1458
- const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
1770
+ const unknownSpecIds = specIdsList.filter((id) => !specIds.has(id));
1459
1771
  if (unknownSpecIds.length > 0) {
1460
1772
  issues.push(
1461
- issue5(
1773
+ issue6(
1462
1774
  "QFAI-TRACE-005",
1463
1775
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1464
1776
  "error",
@@ -1468,10 +1780,10 @@ async function validateTraceability(root, config) {
1468
1780
  )
1469
1781
  );
1470
1782
  }
1471
- const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
1783
+ const unknownBrIds = brIdsList.filter((id) => !brIdsInSpecs.has(id));
1472
1784
  if (unknownBrIds.length > 0) {
1473
1785
  issues.push(
1474
- issue5(
1786
+ issue6(
1475
1787
  "QFAI-TRACE-006",
1476
1788
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1477
1789
  "error",
@@ -1481,10 +1793,12 @@ async function validateTraceability(root, config) {
1481
1793
  )
1482
1794
  );
1483
1795
  }
1484
- const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
1796
+ const unknownContractIds = scenarioIdsList.filter(
1797
+ (id) => !contractIds.has(id)
1798
+ );
1485
1799
  if (unknownContractIds.length > 0) {
1486
1800
  issues.push(
1487
- issue5(
1801
+ issue6(
1488
1802
  "QFAI-TRACE-008",
1489
1803
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1490
1804
  ", "
@@ -1496,23 +1810,23 @@ async function validateTraceability(root, config) {
1496
1810
  )
1497
1811
  );
1498
1812
  }
1499
- if (specIdsInScenario.length > 0) {
1813
+ if (specIdsList.length > 0) {
1500
1814
  const allowedBrIds = /* @__PURE__ */ new Set();
1501
- for (const specId of specIdsInScenario) {
1815
+ for (const specId of specIdsList) {
1502
1816
  const brIdsForSpec = specToBrIds.get(specId);
1503
1817
  if (!brIdsForSpec) {
1504
1818
  continue;
1505
1819
  }
1506
1820
  brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1507
1821
  }
1508
- const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
1822
+ const invalidBrIds = brIdsList.filter((id) => !allowedBrIds.has(id));
1509
1823
  if (invalidBrIds.length > 0) {
1510
1824
  issues.push(
1511
- issue5(
1825
+ issue6(
1512
1826
  "QFAI-TRACE-007",
1513
1827
  `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1514
1828
  ", "
1515
- )} (SPEC: ${specIdsInScenario.join(", ")})`,
1829
+ )} (SPEC: ${specIdsList.join(", ")})`,
1516
1830
  "error",
1517
1831
  file,
1518
1832
  "traceability.scenarioBrUnderSpec",
@@ -1524,7 +1838,7 @@ async function validateTraceability(root, config) {
1524
1838
  }
1525
1839
  if (upstreamIds.size === 0) {
1526
1840
  return [
1527
- issue5(
1841
+ issue6(
1528
1842
  "QFAI-TRACE-000",
1529
1843
  "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1530
1844
  "info",
@@ -1539,7 +1853,7 @@ async function validateTraceability(root, config) {
1539
1853
  );
1540
1854
  if (orphanBrIds.length > 0) {
1541
1855
  issues.push(
1542
- issue5(
1856
+ issue6(
1543
1857
  "QFAI_TRACE_BR_ORPHAN",
1544
1858
  `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1545
1859
  "error",
@@ -1556,7 +1870,7 @@ async function validateTraceability(root, config) {
1556
1870
  );
1557
1871
  if (scWithoutContracts.length > 0) {
1558
1872
  issues.push(
1559
- issue5(
1873
+ issue6(
1560
1874
  "QFAI_TRACE_SC_NO_CONTRACT",
1561
1875
  `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1562
1876
  ", "
@@ -1576,7 +1890,7 @@ async function validateTraceability(root, config) {
1576
1890
  );
1577
1891
  if (orphanContracts.length > 0) {
1578
1892
  issues.push(
1579
- issue5(
1893
+ issue6(
1580
1894
  "QFAI_CONTRACT_ORPHAN",
1581
1895
  `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1582
1896
  "error",
@@ -1604,7 +1918,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1604
1918
  const targetFiles = [...codeFiles, ...testFiles];
1605
1919
  if (targetFiles.length === 0) {
1606
1920
  issues.push(
1607
- issue5(
1921
+ issue6(
1608
1922
  "QFAI-TRACE-001",
1609
1923
  "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1610
1924
  "info",
@@ -1617,7 +1931,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1617
1931
  const pattern = buildIdPattern(Array.from(upstreamIds));
1618
1932
  let found = false;
1619
1933
  for (const file of targetFiles) {
1620
- const text = await readFile8(file, "utf-8");
1934
+ const text = await readFile9(file, "utf-8");
1621
1935
  if (pattern.test(text)) {
1622
1936
  found = true;
1623
1937
  break;
@@ -1625,7 +1939,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1625
1939
  }
1626
1940
  if (!found) {
1627
1941
  issues.push(
1628
- issue5(
1942
+ issue6(
1629
1943
  "QFAI-TRACE-002",
1630
1944
  "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1631
1945
  "warning",
@@ -1640,22 +1954,22 @@ function buildIdPattern(ids) {
1640
1954
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1641
1955
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
1642
1956
  }
1643
- function issue5(code, message, severity, file, rule, refs) {
1644
- const issue6 = {
1957
+ function issue6(code, message, severity, file, rule, refs) {
1958
+ const issue7 = {
1645
1959
  code,
1646
1960
  severity,
1647
1961
  message
1648
1962
  };
1649
1963
  if (file) {
1650
- issue6.file = file;
1964
+ issue7.file = file;
1651
1965
  }
1652
1966
  if (rule) {
1653
- issue6.rule = rule;
1967
+ issue7.rule = rule;
1654
1968
  }
1655
1969
  if (refs && refs.length > 0) {
1656
- issue6.refs = refs;
1970
+ issue7.refs = refs;
1657
1971
  }
1658
- return issue6;
1972
+ return issue7;
1659
1973
  }
1660
1974
 
1661
1975
  // src/core/validate.ts
@@ -1666,6 +1980,7 @@ async function validateProject(root, configResult) {
1666
1980
  ...configIssues,
1667
1981
  ...await validateSpecs(root, config),
1668
1982
  ...await validateScenarios(root, config),
1983
+ ...await validateDecisions(root, config),
1669
1984
  ...await validateContracts(root, config),
1670
1985
  ...await validateDefinedIds(root, config),
1671
1986
  ...await validateTraceability(root, config)
@@ -1680,8 +1995,8 @@ async function validateProject(root, configResult) {
1680
1995
  }
1681
1996
  function countIssues(issues) {
1682
1997
  return issues.reduce(
1683
- (acc, issue6) => {
1684
- acc[issue6.severity] += 1;
1998
+ (acc, issue7) => {
1999
+ acc[issue7.severity] += 1;
1685
2000
  return acc;
1686
2001
  },
1687
2002
  { info: 0, warning: 0, error: 0 }
@@ -1852,7 +2167,7 @@ async function collectIds(files) {
1852
2167
  DATA: /* @__PURE__ */ new Set()
1853
2168
  };
1854
2169
  for (const file of files) {
1855
- const text = await readFile9(file, "utf-8");
2170
+ const text = await readFile10(file, "utf-8");
1856
2171
  for (const prefix of ID_PREFIXES2) {
1857
2172
  const ids = extractIds(text, prefix);
1858
2173
  ids.forEach((id) => result[prefix].add(id));
@@ -1870,7 +2185,7 @@ async function collectIds(files) {
1870
2185
  async function collectUpstreamIds(files) {
1871
2186
  const ids = /* @__PURE__ */ new Set();
1872
2187
  for (const file of files) {
1873
- const text = await readFile9(file, "utf-8");
2188
+ const text = await readFile10(file, "utf-8");
1874
2189
  extractAllIds(text).forEach((id) => ids.add(id));
1875
2190
  }
1876
2191
  return ids;
@@ -1891,7 +2206,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1891
2206
  }
1892
2207
  const pattern = buildIdPattern2(Array.from(upstreamIds));
1893
2208
  for (const file of targetFiles) {
1894
- const text = await readFile9(file, "utf-8");
2209
+ const text = await readFile10(file, "utf-8");
1895
2210
  if (pattern.test(text)) {
1896
2211
  return true;
1897
2212
  }
@@ -1913,20 +2228,20 @@ function toSortedArray(values) {
1913
2228
  }
1914
2229
  function buildHotspots(issues) {
1915
2230
  const map = /* @__PURE__ */ new Map();
1916
- for (const issue6 of issues) {
1917
- if (!issue6.file) {
2231
+ for (const issue7 of issues) {
2232
+ if (!issue7.file) {
1918
2233
  continue;
1919
2234
  }
1920
- const current = map.get(issue6.file) ?? {
1921
- file: issue6.file,
2235
+ const current = map.get(issue7.file) ?? {
2236
+ file: issue7.file,
1922
2237
  total: 0,
1923
2238
  error: 0,
1924
2239
  warning: 0,
1925
2240
  info: 0
1926
2241
  };
1927
2242
  current.total += 1;
1928
- current[issue6.severity] += 1;
1929
- map.set(issue6.file, current);
2243
+ current[issue7.severity] += 1;
2244
+ map.set(issue7.file, current);
1930
2245
  }
1931
2246
  return Array.from(map.values()).sort(
1932
2247
  (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
@@ -1973,7 +2288,7 @@ async function runReport(options) {
1973
2288
  info(`wrote report: ${outPath}`);
1974
2289
  }
1975
2290
  async function readValidationResult(inputPath) {
1976
- const raw = await readFile10(inputPath, "utf-8");
2291
+ const raw = await readFile11(inputPath, "utf-8");
1977
2292
  const parsed = JSON.parse(raw);
1978
2293
  if (!isValidationResult(parsed)) {
1979
2294
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -2074,14 +2389,14 @@ function emitText(result) {
2074
2389
  `
2075
2390
  );
2076
2391
  }
2077
- function emitGitHub(issue6) {
2078
- const level = issue6.severity === "error" ? "error" : issue6.severity === "warning" ? "warning" : "notice";
2079
- const file = issue6.file ? `file=${issue6.file}` : "";
2080
- const line = issue6.loc?.line ? `,line=${issue6.loc.line}` : "";
2081
- const column = issue6.loc?.column ? `,col=${issue6.loc.column}` : "";
2392
+ function emitGitHub(issue7) {
2393
+ const level = issue7.severity === "error" ? "error" : issue7.severity === "warning" ? "warning" : "notice";
2394
+ const file = issue7.file ? `file=${issue7.file}` : "";
2395
+ const line = issue7.loc?.line ? `,line=${issue7.loc.line}` : "";
2396
+ const column = issue7.loc?.column ? `,col=${issue7.loc.column}` : "";
2082
2397
  const location = file ? ` ${file}${line}${column}` : "";
2083
2398
  process.stdout.write(
2084
- `::${level}${location}::${issue6.code}: ${issue6.message}
2399
+ `::${level}${location}::${issue7.code}: ${issue7.message}
2085
2400
  `
2086
2401
  );
2087
2402
  }