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.
package/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(src_exports, {
44
44
  resolvePath: () => resolvePath,
45
45
  resolveToolVersion: () => resolveToolVersion,
46
46
  validateContracts: () => validateContracts,
47
+ validateDecisions: () => validateDecisions,
47
48
  validateDefinedIds: () => validateDefinedIds,
48
49
  validateProject: () => validateProject,
49
50
  validateScenarioContent: () => validateScenarioContent,
@@ -479,7 +480,7 @@ function isValidId(value, prefix) {
479
480
  }
480
481
 
481
482
  // src/core/report.ts
482
- var import_promises10 = require("fs/promises");
483
+ var import_promises11 = require("fs/promises");
483
484
 
484
485
  // src/core/discovery.ts
485
486
  var import_node_path3 = __toESM(require("path"), 1);
@@ -575,8 +576,8 @@ var import_promises3 = require("fs/promises");
575
576
  var import_node_path4 = __toESM(require("path"), 1);
576
577
  var import_node_url = require("url");
577
578
  async function resolveToolVersion() {
578
- if ("0.2.9".length > 0) {
579
- return "0.2.9";
579
+ if ("0.3.0".length > 0) {
580
+ return "0.3.0";
580
581
  }
581
582
  try {
582
583
  const packagePath = resolvePackageJsonPath();
@@ -879,29 +880,113 @@ function formatError2(error) {
879
880
  return String(error);
880
881
  }
881
882
  function issue(code, message, severity, file, rule, refs) {
882
- const issue6 = {
883
+ const issue7 = {
884
+ code,
885
+ severity,
886
+ message
887
+ };
888
+ if (file) {
889
+ issue7.file = file;
890
+ }
891
+ if (rule) {
892
+ issue7.rule = rule;
893
+ }
894
+ if (refs && refs.length > 0) {
895
+ issue7.refs = refs;
896
+ }
897
+ return issue7;
898
+ }
899
+
900
+ // src/core/validators/decisions.ts
901
+ var import_promises5 = require("fs/promises");
902
+
903
+ // src/core/parse/adr.ts
904
+ var ADR_ID_RE = /\bADR-\d{4}\b/;
905
+ function extractField(md, key) {
906
+ const pattern = new RegExp(`^\\s*-\\s*${key}:\\s*(.+)\\s*$`, "m");
907
+ return md.match(pattern)?.[1]?.trim();
908
+ }
909
+ function parseAdr(md, file) {
910
+ const adrId = md.match(ADR_ID_RE)?.[0];
911
+ const fields = {};
912
+ const status = extractField(md, "Status");
913
+ const context = extractField(md, "Context");
914
+ const decision = extractField(md, "Decision");
915
+ const consequences = extractField(md, "Consequences");
916
+ const related = extractField(md, "Related");
917
+ if (status) fields.status = status;
918
+ if (context) fields.context = context;
919
+ if (decision) fields.decision = decision;
920
+ if (consequences) fields.consequences = consequences;
921
+ if (related) fields.related = related;
922
+ const parsed = {
923
+ file,
924
+ fields
925
+ };
926
+ if (adrId) {
927
+ parsed.adrId = adrId;
928
+ }
929
+ return parsed;
930
+ }
931
+
932
+ // src/core/validators/decisions.ts
933
+ var REQUIRED_FIELDS = [
934
+ { key: "status", label: "Status" },
935
+ { key: "context", label: "Context" },
936
+ { key: "decision", label: "Decision" },
937
+ { key: "consequences", label: "Consequences" }
938
+ ];
939
+ async function validateDecisions(root, config) {
940
+ const decisionsRoot = resolvePath(root, config, "decisionsDir");
941
+ const files = await collectFiles(decisionsRoot, { extensions: [".md"] });
942
+ if (files.length === 0) {
943
+ return [];
944
+ }
945
+ const issues = [];
946
+ for (const file of files) {
947
+ const text = await (0, import_promises5.readFile)(file, "utf-8");
948
+ const parsed = parseAdr(text, file);
949
+ const missing = REQUIRED_FIELDS.filter(
950
+ (field) => !parsed.fields[field.key]
951
+ );
952
+ if (missing.length > 0) {
953
+ issues.push(
954
+ issue2(
955
+ "QFAI-ADR-001",
956
+ `ADR \u5FC5\u9808\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missing.map((field) => field.label).join(", ")}`,
957
+ "error",
958
+ file,
959
+ "adr.requiredFields"
960
+ )
961
+ );
962
+ }
963
+ }
964
+ return issues;
965
+ }
966
+ function issue2(code, message, severity, file, rule, refs) {
967
+ const issue7 = {
883
968
  code,
884
969
  severity,
885
970
  message
886
971
  };
887
972
  if (file) {
888
- issue6.file = file;
973
+ issue7.file = file;
889
974
  }
890
975
  if (rule) {
891
- issue6.rule = rule;
976
+ issue7.rule = rule;
892
977
  }
893
978
  if (refs && refs.length > 0) {
894
- issue6.refs = refs;
979
+ issue7.refs = refs;
895
980
  }
896
- return issue6;
981
+ return issue7;
897
982
  }
898
983
 
899
984
  // src/core/validators/ids.ts
900
- var import_promises6 = require("fs/promises");
985
+ var import_promises7 = require("fs/promises");
901
986
  var import_node_path6 = __toESM(require("path"), 1);
902
987
 
903
988
  // src/core/contractIndex.ts
904
- var import_promises5 = require("fs/promises");
989
+ var import_promises6 = require("fs/promises");
905
990
  async function buildContractIndex(root, config) {
906
991
  const uiRoot = resolvePath(root, config, "uiContractsDir");
907
992
  const apiRoot = resolvePath(root, config, "apiContractsDir");
@@ -924,7 +1009,7 @@ async function buildContractIndex(root, config) {
924
1009
  }
925
1010
  async function indexUiContracts(files, index) {
926
1011
  for (const file of files) {
927
- const text = await (0, import_promises5.readFile)(file, "utf-8");
1012
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
928
1013
  try {
929
1014
  const doc = parseStructuredContract(file, text);
930
1015
  extractUiContractIds(doc).forEach((id) => record(index, id, file));
@@ -936,7 +1021,7 @@ async function indexUiContracts(files, index) {
936
1021
  }
937
1022
  async function indexApiContracts(files, index) {
938
1023
  for (const file of files) {
939
- const text = await (0, import_promises5.readFile)(file, "utf-8");
1024
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
940
1025
  try {
941
1026
  const doc = parseStructuredContract(file, text);
942
1027
  extractApiContractIds(doc).forEach((id) => record(index, id, file));
@@ -948,7 +1033,7 @@ async function indexApiContracts(files, index) {
948
1033
  }
949
1034
  async function indexDataContracts(files, index) {
950
1035
  for (const file of files) {
951
- const text = await (0, import_promises5.readFile)(file, "utf-8");
1036
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
952
1037
  extractIds(text, "DATA").forEach((id) => record(index, id, file));
953
1038
  }
954
1039
  }
@@ -959,7 +1044,158 @@ function record(index, id, file) {
959
1044
  index.idToFiles.set(id, current);
960
1045
  }
961
1046
 
1047
+ // src/core/parse/gherkin.ts
1048
+ var FEATURE_RE = /^\s*Feature:\s+/;
1049
+ var SCENARIO_RE = /^\s*Scenario:\s*(.+)\s*$/;
1050
+ var TAG_LINE_RE = /^\s*@/;
1051
+ function parseTags(line) {
1052
+ return line.trim().split(/\s+/).filter((tag) => tag.startsWith("@")).map((tag) => tag.replace(/^@/, ""));
1053
+ }
1054
+ function parseGherkinFeature(text, file) {
1055
+ const lines = text.split(/\r?\n/);
1056
+ const scenarios = [];
1057
+ let featurePresent = false;
1058
+ for (let i = 0; i < lines.length; i++) {
1059
+ const line = lines[i] ?? "";
1060
+ if (FEATURE_RE.test(line)) {
1061
+ featurePresent = true;
1062
+ }
1063
+ const match = line.match(SCENARIO_RE);
1064
+ if (!match) continue;
1065
+ const scenarioName = match[1];
1066
+ if (!scenarioName) continue;
1067
+ const tags = [];
1068
+ for (let j = i - 1; j >= 0; j--) {
1069
+ const previous = lines[j] ?? "";
1070
+ if (previous.trim() === "") continue;
1071
+ if (!TAG_LINE_RE.test(previous)) break;
1072
+ tags.unshift(...parseTags(previous));
1073
+ }
1074
+ scenarios.push({ name: scenarioName, line: i + 1, tags });
1075
+ }
1076
+ return { file, featurePresent, scenarios };
1077
+ }
1078
+
1079
+ // src/core/parse/markdown.ts
1080
+ var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1081
+ function parseHeadings(md) {
1082
+ const lines = md.split(/\r?\n/);
1083
+ const headings = [];
1084
+ for (let i = 0; i < lines.length; i++) {
1085
+ const line = lines[i] ?? "";
1086
+ const match = line.match(HEADING_RE);
1087
+ if (!match) continue;
1088
+ const levelToken = match[1];
1089
+ const title = match[2];
1090
+ if (!levelToken || !title) continue;
1091
+ headings.push({
1092
+ level: levelToken.length,
1093
+ title: title.trim(),
1094
+ line: i + 1
1095
+ });
1096
+ }
1097
+ return headings;
1098
+ }
1099
+ function extractH2Sections(md) {
1100
+ const lines = md.split(/\r?\n/);
1101
+ const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1102
+ const sections = /* @__PURE__ */ new Map();
1103
+ for (let i = 0; i < headings.length; i++) {
1104
+ const current = headings[i];
1105
+ if (!current) continue;
1106
+ const next = headings[i + 1];
1107
+ const startLine = current.line + 1;
1108
+ const endLine = (next?.line ?? lines.length + 1) - 1;
1109
+ const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1110
+ sections.set(current.title.trim(), {
1111
+ title: current.title.trim(),
1112
+ startLine,
1113
+ endLine,
1114
+ body
1115
+ });
1116
+ }
1117
+ return sections;
1118
+ }
1119
+
1120
+ // src/core/parse/spec.ts
1121
+ var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
1122
+ var BR_LINE_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s*\((P[0-3])\)\s*(.+)$/;
1123
+ var BR_LINE_ANY_PRIORITY_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s*\((P[^)]+)\)\s*(.+)$/;
1124
+ var BR_LINE_NO_PRIORITY_RE = /^\s*-\s*\[?(BR-\d{4})\]?\s+(?!\()(.*\S.*)$/;
1125
+ var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
1126
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
1127
+ function parseSpec(md, file) {
1128
+ const headings = parseHeadings(md);
1129
+ const h1 = headings.find((heading) => heading.level === 1);
1130
+ const specId = h1?.title.match(SPEC_ID_RE)?.[0];
1131
+ const sections = extractH2Sections(md);
1132
+ const sectionNames = new Set(Array.from(sections.keys()));
1133
+ const brSection = sections.get(BR_SECTION_TITLE);
1134
+ const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
1135
+ const startLine = brSection?.startLine ?? 1;
1136
+ const brs = [];
1137
+ const brsWithoutPriority = [];
1138
+ const brsWithInvalidPriority = [];
1139
+ for (let i = 0; i < brLines.length; i++) {
1140
+ const lineText = brLines[i] ?? "";
1141
+ const lineNumber = startLine + i;
1142
+ const validMatch = lineText.match(BR_LINE_RE);
1143
+ if (validMatch) {
1144
+ const id = validMatch[1];
1145
+ const priority = validMatch[2];
1146
+ const text = validMatch[3];
1147
+ if (!id || !priority || !text) continue;
1148
+ brs.push({
1149
+ id,
1150
+ priority,
1151
+ text: text.trim(),
1152
+ line: lineNumber
1153
+ });
1154
+ continue;
1155
+ }
1156
+ const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
1157
+ if (anyPriorityMatch) {
1158
+ const id = anyPriorityMatch[1];
1159
+ const priority = anyPriorityMatch[2];
1160
+ const text = anyPriorityMatch[3];
1161
+ if (!id || !priority || !text) continue;
1162
+ if (!VALID_PRIORITIES.has(priority)) {
1163
+ brsWithInvalidPriority.push({
1164
+ id,
1165
+ priority,
1166
+ text: text.trim(),
1167
+ line: lineNumber
1168
+ });
1169
+ }
1170
+ continue;
1171
+ }
1172
+ const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
1173
+ if (noPriorityMatch) {
1174
+ const id = noPriorityMatch[1];
1175
+ const text = noPriorityMatch[2];
1176
+ if (!id || !text) continue;
1177
+ brsWithoutPriority.push({
1178
+ id,
1179
+ text: text.trim(),
1180
+ line: lineNumber
1181
+ });
1182
+ }
1183
+ }
1184
+ const parsed = {
1185
+ file,
1186
+ sections: sectionNames,
1187
+ brs,
1188
+ brsWithoutPriority,
1189
+ brsWithInvalidPriority
1190
+ };
1191
+ if (specId) {
1192
+ parsed.specId = specId;
1193
+ }
1194
+ return parsed;
1195
+ }
1196
+
962
1197
  // src/core/validators/ids.ts
1198
+ var SC_TAG_RE = /^SC-\d{4}$/;
963
1199
  async function validateDefinedIds(root, config) {
964
1200
  const issues = [];
965
1201
  const specRoot = resolvePath(root, config, "specDir");
@@ -983,7 +1219,7 @@ async function validateDefinedIds(root, config) {
983
1219
  }
984
1220
  const sorted = Array.from(files).sort();
985
1221
  issues.push(
986
- issue2(
1222
+ issue3(
987
1223
  "QFAI-ID-001",
988
1224
  `ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
989
1225
  "error",
@@ -996,15 +1232,25 @@ async function validateDefinedIds(root, config) {
996
1232
  }
997
1233
  async function collectSpecDefinitionIds(files, out) {
998
1234
  for (const file of files) {
999
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1000
- extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
1001
- extractIds(text, "BR").forEach((id) => recordId(out, id, file));
1235
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1236
+ const parsed = parseSpec(text, file);
1237
+ if (parsed.specId) {
1238
+ recordId(out, parsed.specId, file);
1239
+ }
1240
+ parsed.brs.forEach((br) => recordId(out, br.id, file));
1002
1241
  }
1003
1242
  }
1004
1243
  async function collectScenarioDefinitionIds(files, out) {
1005
1244
  for (const file of files) {
1006
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1007
- extractIds(text, "SC").forEach((id) => recordId(out, id, file));
1245
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1246
+ const parsed = parseGherkinFeature(text, file);
1247
+ for (const scenario of parsed.scenarios) {
1248
+ for (const tag of scenario.tags) {
1249
+ if (SC_TAG_RE.test(tag)) {
1250
+ recordId(out, tag, file);
1251
+ }
1252
+ }
1253
+ }
1008
1254
  }
1009
1255
  }
1010
1256
  function recordId(out, id, file) {
@@ -1018,29 +1264,32 @@ function formatFileList(files, root) {
1018
1264
  return relative.length > 0 ? relative : file;
1019
1265
  }).join(", ");
1020
1266
  }
1021
- function issue2(code, message, severity, file, rule, refs) {
1022
- const issue6 = {
1267
+ function issue3(code, message, severity, file, rule, refs) {
1268
+ const issue7 = {
1023
1269
  code,
1024
1270
  severity,
1025
1271
  message
1026
1272
  };
1027
1273
  if (file) {
1028
- issue6.file = file;
1274
+ issue7.file = file;
1029
1275
  }
1030
1276
  if (rule) {
1031
- issue6.rule = rule;
1277
+ issue7.rule = rule;
1032
1278
  }
1033
1279
  if (refs && refs.length > 0) {
1034
- issue6.refs = refs;
1280
+ issue7.refs = refs;
1035
1281
  }
1036
- return issue6;
1282
+ return issue7;
1037
1283
  }
1038
1284
 
1039
1285
  // src/core/validators/scenario.ts
1040
- var import_promises7 = require("fs/promises");
1286
+ var import_promises8 = require("fs/promises");
1041
1287
  var GIVEN_PATTERN = /\bGiven\b/;
1042
1288
  var WHEN_PATTERN = /\bWhen\b/;
1043
1289
  var THEN_PATTERN = /\bThen\b/;
1290
+ var SC_TAG_RE2 = /^SC-\d{4}$/;
1291
+ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
1292
+ var BR_TAG_RE = /^BR-\d{4}$/;
1044
1293
  async function validateScenarios(root, config) {
1045
1294
  const scenariosRoot = resolvePath(root, config, "scenariosDir");
1046
1295
  const files = await collectFiles(scenariosRoot, {
@@ -1048,7 +1297,7 @@ async function validateScenarios(root, config) {
1048
1297
  });
1049
1298
  if (files.length === 0) {
1050
1299
  return [
1051
- issue3(
1300
+ issue4(
1052
1301
  "QFAI-SC-000",
1053
1302
  "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1054
1303
  "info",
@@ -1059,13 +1308,14 @@ async function validateScenarios(root, config) {
1059
1308
  }
1060
1309
  const issues = [];
1061
1310
  for (const file of files) {
1062
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1311
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1063
1312
  issues.push(...validateScenarioContent(text, file));
1064
1313
  }
1065
1314
  return issues;
1066
1315
  }
1067
1316
  function validateScenarioContent(text, file) {
1068
1317
  const issues = [];
1318
+ const parsed = parseGherkinFeature(text, file);
1069
1319
  const invalidIds = extractInvalidIds(text, [
1070
1320
  "SPEC",
1071
1321
  "BR",
@@ -1077,7 +1327,7 @@ function validateScenarioContent(text, file) {
1077
1327
  ]);
1078
1328
  if (invalidIds.length > 0) {
1079
1329
  issues.push(
1080
- issue3(
1330
+ issue4(
1081
1331
  "QFAI-ID-002",
1082
1332
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1083
1333
  "error",
@@ -1087,41 +1337,56 @@ function validateScenarioContent(text, file) {
1087
1337
  )
1088
1338
  );
1089
1339
  }
1090
- const scIds = extractIds(text, "SC");
1091
- if (scIds.length === 0) {
1092
- issues.push(
1093
- issue3(
1094
- "QFAI-SC-001",
1095
- "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1096
- "error",
1097
- file,
1098
- "scenario.id"
1099
- )
1100
- );
1101
- }
1102
- const specIds = extractIds(text, "SPEC");
1103
- if (specIds.length === 0) {
1340
+ const missingStructure = [];
1341
+ if (!parsed.featurePresent) missingStructure.push("Feature");
1342
+ if (parsed.scenarios.length === 0) missingStructure.push("Scenario");
1343
+ if (missingStructure.length > 0) {
1104
1344
  issues.push(
1105
- issue3(
1106
- "QFAI-SC-002",
1107
- "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1345
+ issue4(
1346
+ "QFAI-SC-006",
1347
+ `Scenario \u30D5\u30A1\u30A4\u30EB\u306B\u5FC5\u8981\u306A\u69CB\u9020\u304C\u3042\u308A\u307E\u305B\u3093: ${missingStructure.join(
1348
+ ", "
1349
+ )}`,
1108
1350
  "error",
1109
1351
  file,
1110
- "scenario.spec"
1352
+ "scenario.structure"
1111
1353
  )
1112
1354
  );
1113
1355
  }
1114
- const brIds = extractIds(text, "BR");
1115
- if (brIds.length === 0) {
1116
- issues.push(
1117
- issue3(
1118
- "QFAI-SC-003",
1119
- "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
1120
- "error",
1121
- file,
1122
- "scenario.br"
1123
- )
1124
- );
1356
+ for (const scenario of parsed.scenarios) {
1357
+ if (scenario.tags.length === 0) {
1358
+ issues.push(
1359
+ issue4(
1360
+ "QFAI-SC-007",
1361
+ `Scenario \u30BF\u30B0\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${scenario.name}`,
1362
+ "error",
1363
+ file,
1364
+ "scenario.tags"
1365
+ )
1366
+ );
1367
+ continue;
1368
+ }
1369
+ const missingTags = [];
1370
+ if (!scenario.tags.some((tag) => SC_TAG_RE2.test(tag))) {
1371
+ missingTags.push("SC");
1372
+ }
1373
+ if (!scenario.tags.some((tag) => SPEC_TAG_RE.test(tag))) {
1374
+ missingTags.push("SPEC");
1375
+ }
1376
+ if (!scenario.tags.some((tag) => BR_TAG_RE.test(tag))) {
1377
+ missingTags.push("BR");
1378
+ }
1379
+ if (missingTags.length > 0) {
1380
+ issues.push(
1381
+ issue4(
1382
+ "QFAI-SC-008",
1383
+ `Scenario \u30BF\u30B0\u306B\u4E0D\u8DB3\u304C\u3042\u308A\u307E\u3059: ${missingTags.join(", ")} (${scenario.name})`,
1384
+ "error",
1385
+ file,
1386
+ "scenario.tagIds"
1387
+ )
1388
+ );
1389
+ }
1125
1390
  }
1126
1391
  const missingSteps = [];
1127
1392
  if (!GIVEN_PATTERN.test(text)) {
@@ -1135,7 +1400,7 @@ function validateScenarioContent(text, file) {
1135
1400
  }
1136
1401
  if (missingSteps.length > 0) {
1137
1402
  issues.push(
1138
- issue3(
1403
+ issue4(
1139
1404
  "QFAI-SC-005",
1140
1405
  `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
1141
1406
  "warning",
@@ -1146,33 +1411,33 @@ function validateScenarioContent(text, file) {
1146
1411
  }
1147
1412
  return issues;
1148
1413
  }
1149
- function issue3(code, message, severity, file, rule, refs) {
1150
- const issue6 = {
1414
+ function issue4(code, message, severity, file, rule, refs) {
1415
+ const issue7 = {
1151
1416
  code,
1152
1417
  severity,
1153
1418
  message
1154
1419
  };
1155
1420
  if (file) {
1156
- issue6.file = file;
1421
+ issue7.file = file;
1157
1422
  }
1158
1423
  if (rule) {
1159
- issue6.rule = rule;
1424
+ issue7.rule = rule;
1160
1425
  }
1161
1426
  if (refs && refs.length > 0) {
1162
- issue6.refs = refs;
1427
+ issue7.refs = refs;
1163
1428
  }
1164
- return issue6;
1429
+ return issue7;
1165
1430
  }
1166
1431
 
1167
1432
  // src/core/validators/spec.ts
1168
- var import_promises8 = require("fs/promises");
1433
+ var import_promises9 = require("fs/promises");
1169
1434
  async function validateSpecs(root, config) {
1170
1435
  const specsRoot = resolvePath(root, config, "specDir");
1171
1436
  const files = await collectSpecFiles(specsRoot);
1172
1437
  if (files.length === 0) {
1173
1438
  const expected = "spec-0001-<slug>.md";
1174
1439
  return [
1175
- issue4(
1440
+ issue5(
1176
1441
  "QFAI-SPEC-000",
1177
1442
  `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}`,
1178
1443
  "info",
@@ -1183,7 +1448,7 @@ async function validateSpecs(root, config) {
1183
1448
  }
1184
1449
  const issues = [];
1185
1450
  for (const file of files) {
1186
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1451
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1187
1452
  issues.push(
1188
1453
  ...validateSpecContent(
1189
1454
  text,
@@ -1196,6 +1461,7 @@ async function validateSpecs(root, config) {
1196
1461
  }
1197
1462
  function validateSpecContent(text, file, requiredSections) {
1198
1463
  const issues = [];
1464
+ const parsed = parseSpec(text, file);
1199
1465
  const invalidIds = extractInvalidIds(text, [
1200
1466
  "SPEC",
1201
1467
  "BR",
@@ -1207,7 +1473,7 @@ function validateSpecContent(text, file, requiredSections) {
1207
1473
  ]);
1208
1474
  if (invalidIds.length > 0) {
1209
1475
  issues.push(
1210
- issue4(
1476
+ issue5(
1211
1477
  "QFAI-ID-002",
1212
1478
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
1213
1479
  "error",
@@ -1217,10 +1483,9 @@ function validateSpecContent(text, file, requiredSections) {
1217
1483
  )
1218
1484
  );
1219
1485
  }
1220
- const specIds = extractIds(text, "SPEC");
1221
- if (specIds.length === 0) {
1486
+ if (!parsed.specId) {
1222
1487
  issues.push(
1223
- issue4(
1488
+ issue5(
1224
1489
  "QFAI-SPEC-001",
1225
1490
  "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1226
1491
  "error",
@@ -1229,10 +1494,9 @@ function validateSpecContent(text, file, requiredSections) {
1229
1494
  )
1230
1495
  );
1231
1496
  }
1232
- const brIds = extractIds(text, "BR");
1233
- if (brIds.length === 0) {
1497
+ if (parsed.brs.length === 0) {
1234
1498
  issues.push(
1235
- issue4(
1499
+ issue5(
1236
1500
  "QFAI-SPEC-002",
1237
1501
  "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1238
1502
  "error",
@@ -1241,10 +1505,34 @@ function validateSpecContent(text, file, requiredSections) {
1241
1505
  )
1242
1506
  );
1243
1507
  }
1508
+ for (const br of parsed.brsWithoutPriority) {
1509
+ issues.push(
1510
+ issue5(
1511
+ "QFAI-BR-001",
1512
+ `BR \u884C\u306B Priority \u304C\u3042\u308A\u307E\u305B\u3093: ${br.id}`,
1513
+ "error",
1514
+ file,
1515
+ "spec.brPriority",
1516
+ [br.id]
1517
+ )
1518
+ );
1519
+ }
1520
+ for (const br of parsed.brsWithInvalidPriority) {
1521
+ issues.push(
1522
+ issue5(
1523
+ "QFAI-BR-002",
1524
+ `BR Priority \u304C\u4E0D\u6B63\u3067\u3059: ${br.id} (${br.priority})`,
1525
+ "error",
1526
+ file,
1527
+ "spec.brPriority",
1528
+ [br.id]
1529
+ )
1530
+ );
1531
+ }
1244
1532
  const scIds = extractIds(text, "SC");
1245
1533
  if (scIds.length > 0) {
1246
1534
  issues.push(
1247
- issue4(
1535
+ issue5(
1248
1536
  "QFAI-SPEC-003",
1249
1537
  "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
1250
1538
  "warning",
@@ -1255,9 +1543,9 @@ function validateSpecContent(text, file, requiredSections) {
1255
1543
  );
1256
1544
  }
1257
1545
  for (const section of requiredSections) {
1258
- if (!text.includes(section)) {
1546
+ if (!parsed.sections.has(section)) {
1259
1547
  issues.push(
1260
- issue4(
1548
+ issue5(
1261
1549
  "QFAI-SPEC-004",
1262
1550
  `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
1263
1551
  "error",
@@ -1269,26 +1557,32 @@ function validateSpecContent(text, file, requiredSections) {
1269
1557
  }
1270
1558
  return issues;
1271
1559
  }
1272
- function issue4(code, message, severity, file, rule, refs) {
1273
- const issue6 = {
1560
+ function issue5(code, message, severity, file, rule, refs) {
1561
+ const issue7 = {
1274
1562
  code,
1275
1563
  severity,
1276
1564
  message
1277
1565
  };
1278
1566
  if (file) {
1279
- issue6.file = file;
1567
+ issue7.file = file;
1280
1568
  }
1281
1569
  if (rule) {
1282
- issue6.rule = rule;
1570
+ issue7.rule = rule;
1283
1571
  }
1284
1572
  if (refs && refs.length > 0) {
1285
- issue6.refs = refs;
1573
+ issue7.refs = refs;
1286
1574
  }
1287
- return issue6;
1575
+ return issue7;
1288
1576
  }
1289
1577
 
1290
1578
  // src/core/validators/traceability.ts
1291
- var import_promises9 = require("fs/promises");
1579
+ var import_promises10 = require("fs/promises");
1580
+ var SC_TAG_RE3 = /^SC-\d{4}$/;
1581
+ var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
1582
+ var BR_TAG_RE2 = /^BR-\d{4}$/;
1583
+ var UI_TAG_RE = /^UI-\d{4}$/;
1584
+ var API_TAG_RE = /^API-\d{4}$/;
1585
+ var DATA_TAG_RE = /^DATA-\d{4}$/;
1292
1586
  async function validateTraceability(root, config) {
1293
1587
  const issues = [];
1294
1588
  const specsRoot = resolvePath(root, config, "specDir");
@@ -1314,11 +1608,13 @@ async function validateTraceability(root, config) {
1314
1608
  const contractIndex = await buildContractIndex(root, config);
1315
1609
  const contractIds = contractIndex.ids;
1316
1610
  for (const file of specFiles) {
1317
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1611
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1318
1612
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1319
- const specIdsInFile = extractIds(text, "SPEC");
1320
- specIdsInFile.forEach((id) => specIds.add(id));
1321
- const brIds = extractIds(text, "BR");
1613
+ const parsed = parseSpec(text, file);
1614
+ if (parsed.specId) {
1615
+ specIds.add(parsed.specId);
1616
+ }
1617
+ const brIds = parsed.brs.map((br) => br.id);
1322
1618
  brIds.forEach((id) => brIdsInSpecs.add(id));
1323
1619
  const referencedContractIds = /* @__PURE__ */ new Set([
1324
1620
  ...extractIds(text, "UI"),
@@ -1330,7 +1626,7 @@ async function validateTraceability(root, config) {
1330
1626
  );
1331
1627
  if (unknownContractIds.length > 0) {
1332
1628
  issues.push(
1333
- issue5(
1629
+ issue6(
1334
1630
  "QFAI-TRACE-009",
1335
1631
  `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1336
1632
  ", "
@@ -1342,37 +1638,54 @@ async function validateTraceability(root, config) {
1342
1638
  )
1343
1639
  );
1344
1640
  }
1345
- for (const specId of specIdsInFile) {
1346
- const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
1641
+ if (parsed.specId) {
1642
+ const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
1347
1643
  brIds.forEach((id) => current.add(id));
1348
- specToBrIds.set(specId, current);
1644
+ specToBrIds.set(parsed.specId, current);
1349
1645
  }
1350
1646
  }
1351
1647
  for (const file of decisionFiles) {
1352
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1648
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1353
1649
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1354
1650
  }
1355
1651
  for (const file of scenarioFiles) {
1356
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1652
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1357
1653
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1358
- const specIdsInScenario = extractIds(text, "SPEC");
1359
- const brIds = extractIds(text, "BR");
1360
- const scIds = extractIds(text, "SC");
1361
- const scenarioIds = [
1362
- ...extractIds(text, "UI"),
1363
- ...extractIds(text, "API"),
1364
- ...extractIds(text, "DATA")
1365
- ];
1366
- brIds.forEach((id) => brIdsInScenarios.add(id));
1367
- scIds.forEach((id) => scIdsInScenarios.add(id));
1368
- scenarioIds.forEach((id) => scenarioContractIds.add(id));
1369
- if (scenarioIds.length > 0) {
1370
- scIds.forEach((id) => scWithContracts.add(id));
1654
+ const parsed = parseGherkinFeature(text, file);
1655
+ const specIdsInScenario = /* @__PURE__ */ new Set();
1656
+ const brIds = /* @__PURE__ */ new Set();
1657
+ const scIds = /* @__PURE__ */ new Set();
1658
+ const scenarioIds = /* @__PURE__ */ new Set();
1659
+ for (const scenario of parsed.scenarios) {
1660
+ for (const tag of scenario.tags) {
1661
+ if (SPEC_TAG_RE2.test(tag)) {
1662
+ specIdsInScenario.add(tag);
1663
+ }
1664
+ if (BR_TAG_RE2.test(tag)) {
1665
+ brIds.add(tag);
1666
+ }
1667
+ if (SC_TAG_RE3.test(tag)) {
1668
+ scIds.add(tag);
1669
+ }
1670
+ if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
1671
+ scenarioIds.add(tag);
1672
+ }
1673
+ }
1674
+ }
1675
+ const specIdsList = Array.from(specIdsInScenario);
1676
+ const brIdsList = Array.from(brIds);
1677
+ const scIdsList = Array.from(scIds);
1678
+ const scenarioIdsList = Array.from(scenarioIds);
1679
+ brIdsList.forEach((id) => brIdsInScenarios.add(id));
1680
+ scIdsList.forEach((id) => scIdsInScenarios.add(id));
1681
+ scenarioIdsList.forEach((id) => scenarioContractIds.add(id));
1682
+ if (scenarioIdsList.length > 0) {
1683
+ scIdsList.forEach((id) => scWithContracts.add(id));
1371
1684
  }
1372
- const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
1685
+ const unknownSpecIds = specIdsList.filter((id) => !specIds.has(id));
1373
1686
  if (unknownSpecIds.length > 0) {
1374
1687
  issues.push(
1375
- issue5(
1688
+ issue6(
1376
1689
  "QFAI-TRACE-005",
1377
1690
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1378
1691
  "error",
@@ -1382,10 +1695,10 @@ async function validateTraceability(root, config) {
1382
1695
  )
1383
1696
  );
1384
1697
  }
1385
- const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
1698
+ const unknownBrIds = brIdsList.filter((id) => !brIdsInSpecs.has(id));
1386
1699
  if (unknownBrIds.length > 0) {
1387
1700
  issues.push(
1388
- issue5(
1701
+ issue6(
1389
1702
  "QFAI-TRACE-006",
1390
1703
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1391
1704
  "error",
@@ -1395,10 +1708,12 @@ async function validateTraceability(root, config) {
1395
1708
  )
1396
1709
  );
1397
1710
  }
1398
- const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
1711
+ const unknownContractIds = scenarioIdsList.filter(
1712
+ (id) => !contractIds.has(id)
1713
+ );
1399
1714
  if (unknownContractIds.length > 0) {
1400
1715
  issues.push(
1401
- issue5(
1716
+ issue6(
1402
1717
  "QFAI-TRACE-008",
1403
1718
  `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1404
1719
  ", "
@@ -1410,23 +1725,23 @@ async function validateTraceability(root, config) {
1410
1725
  )
1411
1726
  );
1412
1727
  }
1413
- if (specIdsInScenario.length > 0) {
1728
+ if (specIdsList.length > 0) {
1414
1729
  const allowedBrIds = /* @__PURE__ */ new Set();
1415
- for (const specId of specIdsInScenario) {
1730
+ for (const specId of specIdsList) {
1416
1731
  const brIdsForSpec = specToBrIds.get(specId);
1417
1732
  if (!brIdsForSpec) {
1418
1733
  continue;
1419
1734
  }
1420
1735
  brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1421
1736
  }
1422
- const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
1737
+ const invalidBrIds = brIdsList.filter((id) => !allowedBrIds.has(id));
1423
1738
  if (invalidBrIds.length > 0) {
1424
1739
  issues.push(
1425
- issue5(
1740
+ issue6(
1426
1741
  "QFAI-TRACE-007",
1427
1742
  `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1428
1743
  ", "
1429
- )} (SPEC: ${specIdsInScenario.join(", ")})`,
1744
+ )} (SPEC: ${specIdsList.join(", ")})`,
1430
1745
  "error",
1431
1746
  file,
1432
1747
  "traceability.scenarioBrUnderSpec",
@@ -1438,7 +1753,7 @@ async function validateTraceability(root, config) {
1438
1753
  }
1439
1754
  if (upstreamIds.size === 0) {
1440
1755
  return [
1441
- issue5(
1756
+ issue6(
1442
1757
  "QFAI-TRACE-000",
1443
1758
  "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1444
1759
  "info",
@@ -1453,7 +1768,7 @@ async function validateTraceability(root, config) {
1453
1768
  );
1454
1769
  if (orphanBrIds.length > 0) {
1455
1770
  issues.push(
1456
- issue5(
1771
+ issue6(
1457
1772
  "QFAI_TRACE_BR_ORPHAN",
1458
1773
  `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1459
1774
  "error",
@@ -1470,7 +1785,7 @@ async function validateTraceability(root, config) {
1470
1785
  );
1471
1786
  if (scWithoutContracts.length > 0) {
1472
1787
  issues.push(
1473
- issue5(
1788
+ issue6(
1474
1789
  "QFAI_TRACE_SC_NO_CONTRACT",
1475
1790
  `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1476
1791
  ", "
@@ -1490,7 +1805,7 @@ async function validateTraceability(root, config) {
1490
1805
  );
1491
1806
  if (orphanContracts.length > 0) {
1492
1807
  issues.push(
1493
- issue5(
1808
+ issue6(
1494
1809
  "QFAI_CONTRACT_ORPHAN",
1495
1810
  `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1496
1811
  "error",
@@ -1518,7 +1833,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1518
1833
  const targetFiles = [...codeFiles, ...testFiles];
1519
1834
  if (targetFiles.length === 0) {
1520
1835
  issues.push(
1521
- issue5(
1836
+ issue6(
1522
1837
  "QFAI-TRACE-001",
1523
1838
  "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1524
1839
  "info",
@@ -1531,7 +1846,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1531
1846
  const pattern = buildIdPattern(Array.from(upstreamIds));
1532
1847
  let found = false;
1533
1848
  for (const file of targetFiles) {
1534
- const text = await (0, import_promises9.readFile)(file, "utf-8");
1849
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1535
1850
  if (pattern.test(text)) {
1536
1851
  found = true;
1537
1852
  break;
@@ -1539,7 +1854,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1539
1854
  }
1540
1855
  if (!found) {
1541
1856
  issues.push(
1542
- issue5(
1857
+ issue6(
1543
1858
  "QFAI-TRACE-002",
1544
1859
  "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1545
1860
  "warning",
@@ -1554,22 +1869,22 @@ function buildIdPattern(ids) {
1554
1869
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1555
1870
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
1556
1871
  }
1557
- function issue5(code, message, severity, file, rule, refs) {
1558
- const issue6 = {
1872
+ function issue6(code, message, severity, file, rule, refs) {
1873
+ const issue7 = {
1559
1874
  code,
1560
1875
  severity,
1561
1876
  message
1562
1877
  };
1563
1878
  if (file) {
1564
- issue6.file = file;
1879
+ issue7.file = file;
1565
1880
  }
1566
1881
  if (rule) {
1567
- issue6.rule = rule;
1882
+ issue7.rule = rule;
1568
1883
  }
1569
1884
  if (refs && refs.length > 0) {
1570
- issue6.refs = refs;
1885
+ issue7.refs = refs;
1571
1886
  }
1572
- return issue6;
1887
+ return issue7;
1573
1888
  }
1574
1889
 
1575
1890
  // src/core/validate.ts
@@ -1580,6 +1895,7 @@ async function validateProject(root, configResult) {
1580
1895
  ...configIssues,
1581
1896
  ...await validateSpecs(root, config),
1582
1897
  ...await validateScenarios(root, config),
1898
+ ...await validateDecisions(root, config),
1583
1899
  ...await validateContracts(root, config),
1584
1900
  ...await validateDefinedIds(root, config),
1585
1901
  ...await validateTraceability(root, config)
@@ -1594,8 +1910,8 @@ async function validateProject(root, configResult) {
1594
1910
  }
1595
1911
  function countIssues(issues) {
1596
1912
  return issues.reduce(
1597
- (acc, issue6) => {
1598
- acc[issue6.severity] += 1;
1913
+ (acc, issue7) => {
1914
+ acc[issue7.severity] += 1;
1599
1915
  return acc;
1600
1916
  },
1601
1917
  { info: 0, warning: 0, error: 0 }
@@ -1766,7 +2082,7 @@ async function collectIds(files) {
1766
2082
  DATA: /* @__PURE__ */ new Set()
1767
2083
  };
1768
2084
  for (const file of files) {
1769
- const text = await (0, import_promises10.readFile)(file, "utf-8");
2085
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1770
2086
  for (const prefix of ID_PREFIXES2) {
1771
2087
  const ids = extractIds(text, prefix);
1772
2088
  ids.forEach((id) => result[prefix].add(id));
@@ -1784,7 +2100,7 @@ async function collectIds(files) {
1784
2100
  async function collectUpstreamIds(files) {
1785
2101
  const ids = /* @__PURE__ */ new Set();
1786
2102
  for (const file of files) {
1787
- const text = await (0, import_promises10.readFile)(file, "utf-8");
2103
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1788
2104
  extractAllIds(text).forEach((id) => ids.add(id));
1789
2105
  }
1790
2106
  return ids;
@@ -1805,7 +2121,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1805
2121
  }
1806
2122
  const pattern = buildIdPattern2(Array.from(upstreamIds));
1807
2123
  for (const file of targetFiles) {
1808
- const text = await (0, import_promises10.readFile)(file, "utf-8");
2124
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1809
2125
  if (pattern.test(text)) {
1810
2126
  return true;
1811
2127
  }
@@ -1827,20 +2143,20 @@ function toSortedArray(values) {
1827
2143
  }
1828
2144
  function buildHotspots(issues) {
1829
2145
  const map = /* @__PURE__ */ new Map();
1830
- for (const issue6 of issues) {
1831
- if (!issue6.file) {
2146
+ for (const issue7 of issues) {
2147
+ if (!issue7.file) {
1832
2148
  continue;
1833
2149
  }
1834
- const current = map.get(issue6.file) ?? {
1835
- file: issue6.file,
2150
+ const current = map.get(issue7.file) ?? {
2151
+ file: issue7.file,
1836
2152
  total: 0,
1837
2153
  error: 0,
1838
2154
  warning: 0,
1839
2155
  info: 0
1840
2156
  };
1841
2157
  current.total += 1;
1842
- current[issue6.severity] += 1;
1843
- map.set(issue6.file, current);
2158
+ current[issue7.severity] += 1;
2159
+ map.set(issue7.file, current);
1844
2160
  }
1845
2161
  return Array.from(map.values()).sort(
1846
2162
  (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
@@ -1862,6 +2178,7 @@ function buildHotspots(issues) {
1862
2178
  resolvePath,
1863
2179
  resolveToolVersion,
1864
2180
  validateContracts,
2181
+ validateDecisions,
1865
2182
  validateDefinedIds,
1866
2183
  validateProject,
1867
2184
  validateScenarioContent,