qfai 0.2.6 → 0.2.8

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
+ validateDefinedIds: () => validateDefinedIds,
47
48
  validateProject: () => validateProject,
48
49
  validateScenarioContent: () => validateScenarioContent,
49
50
  validateScenarios: () => validateScenarios,
@@ -62,7 +63,6 @@ var defaultConfig = {
62
63
  specDir: ".qfai/spec",
63
64
  decisionsDir: ".qfai/spec/decisions",
64
65
  scenariosDir: ".qfai/spec/scenarios",
65
- rulesDir: ".qfai/rules",
66
66
  contractsDir: ".qfai/contracts",
67
67
  uiContractsDir: ".qfai/contracts/ui",
68
68
  apiContractsDir: ".qfai/contracts/api",
@@ -86,7 +86,8 @@ var defaultConfig = {
86
86
  traceability: {
87
87
  brMustHaveSc: true,
88
88
  scMustTouchContracts: true,
89
- allowOrphanContracts: false
89
+ allowOrphanContracts: false,
90
+ unknownContractIdSeverity: "error"
90
91
  }
91
92
  },
92
93
  output: {
@@ -161,13 +162,6 @@ function normalizePaths(raw, configPath, issues) {
161
162
  configPath,
162
163
  issues
163
164
  ),
164
- rulesDir: readString(
165
- raw.rulesDir,
166
- base.rulesDir,
167
- "paths.rulesDir",
168
- configPath,
169
- issues
170
- ),
171
165
  contractsDir: readString(
172
166
  raw.contractsDir,
173
167
  base.contractsDir,
@@ -292,6 +286,13 @@ function normalizeValidation(raw, configPath, issues) {
292
286
  "validation.traceability.allowOrphanContracts",
293
287
  configPath,
294
288
  issues
289
+ ),
290
+ unknownContractIdSeverity: readTraceabilitySeverity(
291
+ traceabilityRaw?.unknownContractIdSeverity,
292
+ base.traceability.unknownContractIdSeverity,
293
+ "validation.traceability.unknownContractIdSeverity",
294
+ configPath,
295
+ issues
295
296
  )
296
297
  }
297
298
  };
@@ -371,6 +372,20 @@ function readFailOn(value, fallback, label, configPath, issues) {
371
372
  }
372
373
  return fallback;
373
374
  }
375
+ function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
376
+ if (value === "warning" || value === "error") {
377
+ return value;
378
+ }
379
+ if (value !== void 0) {
380
+ issues.push(
381
+ configIssue(
382
+ configPath,
383
+ `${label} \u306F warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
384
+ )
385
+ );
386
+ }
387
+ return fallback;
388
+ }
374
389
  function readOutputFormat(value, fallback, label, configPath, issues) {
375
390
  if (value === "text" || value === "json" || value === "github") {
376
391
  return value;
@@ -411,13 +426,15 @@ function isRecord(value) {
411
426
  }
412
427
 
413
428
  // src/core/ids.ts
414
- var ID_PATTERNS = {
415
- SPEC: /\bSPEC-[A-Z0-9-]+\b/g,
416
- BR: /\bBR-[A-Z0-9-]+\b/g,
417
- SC: /\bSC-[A-Z0-9-]+\b/g,
418
- UI: /\bUI-[A-Z0-9-]+\b/g,
419
- API: /\bAPI-[A-Z0-9-]+\b/g,
420
- DATA: /\bDATA-[A-Z0-9-]+\b/g
429
+ var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
430
+ var STRICT_ID_PATTERNS = {
431
+ SPEC: /\bSPEC-\d{4}\b/g,
432
+ BR: /\bBR-\d{4}\b/g,
433
+ SC: /\bSC-\d{4}\b/g,
434
+ UI: /\bUI-\d{4}\b/g,
435
+ API: /\bAPI-\d{4}\b/g,
436
+ DATA: /\bDATA-\d{4}\b/g,
437
+ ADR: /\bADR-\d{4}\b/g
421
438
  };
422
439
  var LOOSE_ID_PATTERNS = {
423
440
  SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
@@ -425,16 +442,17 @@ var LOOSE_ID_PATTERNS = {
425
442
  SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
426
443
  UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
427
444
  API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
428
- DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
445
+ DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi,
446
+ ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
429
447
  };
430
448
  function extractIds(text, prefix) {
431
- const pattern = ID_PATTERNS[prefix];
449
+ const pattern = STRICT_ID_PATTERNS[prefix];
432
450
  const matches = text.match(pattern);
433
451
  return unique(matches ?? []);
434
452
  }
435
453
  function extractAllIds(text) {
436
454
  const all = [];
437
- Object.keys(ID_PATTERNS).forEach((prefix) => {
455
+ ID_PREFIXES.forEach((prefix) => {
438
456
  all.push(...extractIds(text, prefix));
439
457
  });
440
458
  return unique(all);
@@ -455,13 +473,13 @@ function unique(values) {
455
473
  return Array.from(new Set(values));
456
474
  }
457
475
  function isValidId(value, prefix) {
458
- const pattern = ID_PATTERNS[prefix];
476
+ const pattern = STRICT_ID_PATTERNS[prefix];
459
477
  const strict = new RegExp(pattern.source);
460
478
  return strict.test(value);
461
479
  }
462
480
 
463
481
  // src/core/report.ts
464
- var import_promises8 = require("fs/promises");
482
+ var import_promises10 = require("fs/promises");
465
483
 
466
484
  // src/core/discovery.ts
467
485
  var import_node_path3 = __toESM(require("path"), 1);
@@ -557,8 +575,8 @@ var import_promises3 = require("fs/promises");
557
575
  var import_node_path4 = __toESM(require("path"), 1);
558
576
  var import_node_url = require("url");
559
577
  async function resolveToolVersion() {
560
- if ("0.2.6".length > 0) {
561
- return "0.2.6";
578
+ if ("0.2.9".length > 0) {
579
+ return "0.2.9";
562
580
  }
563
581
  try {
564
582
  const packagePath = resolvePackageJsonPath();
@@ -578,8 +596,50 @@ function resolvePackageJsonPath() {
578
596
 
579
597
  // src/core/validators/contracts.ts
580
598
  var import_promises4 = require("fs/promises");
599
+
600
+ // src/core/contracts.ts
581
601
  var import_node_path5 = __toESM(require("path"), 1);
582
602
  var import_yaml2 = require("yaml");
603
+ function parseStructuredContract(file, text) {
604
+ const ext = import_node_path5.default.extname(file).toLowerCase();
605
+ if (ext === ".json") {
606
+ return JSON.parse(text);
607
+ }
608
+ return (0, import_yaml2.parse)(text);
609
+ }
610
+ function extractUiContractIds(doc) {
611
+ const id = typeof doc.id === "string" ? doc.id : "";
612
+ return extractIds(id, "UI");
613
+ }
614
+ function extractApiContractIds(doc) {
615
+ const operationIds = /* @__PURE__ */ new Set();
616
+ collectOperationIds(doc, operationIds);
617
+ const ids = /* @__PURE__ */ new Set();
618
+ for (const operationId of operationIds) {
619
+ extractIds(operationId, "API").forEach((id) => ids.add(id));
620
+ }
621
+ return Array.from(ids);
622
+ }
623
+ function collectOperationIds(value, out) {
624
+ if (!value || typeof value !== "object") {
625
+ return;
626
+ }
627
+ if (Array.isArray(value)) {
628
+ for (const item of value) {
629
+ collectOperationIds(item, out);
630
+ }
631
+ return;
632
+ }
633
+ for (const [key, entry] of Object.entries(value)) {
634
+ if (key === "operationId" && typeof entry === "string") {
635
+ out.add(entry);
636
+ continue;
637
+ }
638
+ collectOperationIds(entry, out);
639
+ }
640
+ }
641
+
642
+ // src/core/validators/contracts.ts
583
643
  var SQL_DANGEROUS_PATTERNS = [
584
644
  { pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
585
645
  { pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
@@ -628,12 +688,13 @@ async function validateUiContracts(uiRoot) {
628
688
  "SC",
629
689
  "UI",
630
690
  "API",
631
- "DATA"
691
+ "DATA",
692
+ "ADR"
632
693
  ]);
633
694
  if (invalidIds.length > 0) {
634
695
  issues.push(
635
696
  issue(
636
- "QFAI_ID_INVALID_FORMAT",
697
+ "QFAI-ID-002",
637
698
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
638
699
  "error",
639
700
  file,
@@ -642,30 +703,32 @@ async function validateUiContracts(uiRoot) {
642
703
  )
643
704
  );
644
705
  }
706
+ let doc;
645
707
  try {
646
- const doc = (0, import_yaml2.parse)(text);
647
- const id = typeof doc.id === "string" ? doc.id : "";
648
- if (!id.startsWith("UI-")) {
649
- issues.push(
650
- issue(
651
- "QFAI-UI-001",
652
- "UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
653
- "error",
654
- file,
655
- "contracts.ui.id"
656
- )
657
- );
658
- }
708
+ doc = parseStructuredContract(file, text);
659
709
  } catch (error) {
660
710
  issues.push(
661
711
  issue(
662
- "QFAI-UI-002",
663
- `UI YAML \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
712
+ "QFAI-CONTRACT-001",
713
+ `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
664
714
  "error",
665
715
  file,
666
716
  "contracts.ui.parse"
667
717
  )
668
718
  );
719
+ continue;
720
+ }
721
+ const uiIds = extractUiContractIds(doc);
722
+ if (uiIds.length === 0) {
723
+ issues.push(
724
+ issue(
725
+ "QFAI-CONTRACT-002",
726
+ `UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
727
+ "error",
728
+ file,
729
+ "contracts.ui.id"
730
+ )
731
+ );
669
732
  }
670
733
  }
671
734
  return issues;
@@ -692,12 +755,13 @@ async function validateApiContracts(apiRoot) {
692
755
  "SC",
693
756
  "UI",
694
757
  "API",
695
- "DATA"
758
+ "DATA",
759
+ "ADR"
696
760
  ]);
697
761
  if (invalidIds.length > 0) {
698
762
  issues.push(
699
763
  issue(
700
- "QFAI_ID_INVALID_FORMAT",
764
+ "QFAI-ID-002",
701
765
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
702
766
  "error",
703
767
  file,
@@ -706,29 +770,43 @@ async function validateApiContracts(apiRoot) {
706
770
  )
707
771
  );
708
772
  }
773
+ let doc;
709
774
  try {
710
- const doc = parseStructured(file, text);
711
- if (!doc || !hasOpenApi(doc)) {
712
- issues.push(
713
- issue(
714
- "QFAI-API-001",
715
- "OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
716
- "error",
717
- file,
718
- "contracts.api.openapi"
719
- )
720
- );
721
- }
775
+ doc = parseStructuredContract(file, text);
722
776
  } catch (error) {
723
777
  issues.push(
724
778
  issue(
725
- "QFAI-API-002",
726
- `API \u5B9A\u7FA9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
779
+ "QFAI-CONTRACT-001",
780
+ `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
727
781
  "error",
728
782
  file,
729
783
  "contracts.api.parse"
730
784
  )
731
785
  );
786
+ continue;
787
+ }
788
+ if (!hasOpenApi(doc)) {
789
+ issues.push(
790
+ issue(
791
+ "QFAI-API-001",
792
+ "OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
793
+ "error",
794
+ file,
795
+ "contracts.api.openapi"
796
+ )
797
+ );
798
+ }
799
+ const apiIds = extractApiContractIds(doc);
800
+ if (apiIds.length === 0) {
801
+ issues.push(
802
+ issue(
803
+ "QFAI-CONTRACT-002",
804
+ `API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
805
+ "error",
806
+ file,
807
+ "contracts.api.id"
808
+ )
809
+ );
732
810
  }
733
811
  }
734
812
  return issues;
@@ -755,12 +833,13 @@ async function validateDataContracts(dataRoot) {
755
833
  "SC",
756
834
  "UI",
757
835
  "API",
758
- "DATA"
836
+ "DATA",
837
+ "ADR"
759
838
  ]);
760
839
  if (invalidIds.length > 0) {
761
840
  issues.push(
762
841
  issue(
763
- "QFAI_ID_INVALID_FORMAT",
842
+ "QFAI-ID-002",
764
843
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
765
844
  "error",
766
845
  file,
@@ -790,13 +869,6 @@ function lintSql(text, file) {
790
869
  }
791
870
  return issues;
792
871
  }
793
- function parseStructured(file, text) {
794
- const ext = import_node_path5.default.extname(file).toLowerCase();
795
- if (ext === ".json") {
796
- return JSON.parse(text);
797
- }
798
- return (0, import_yaml2.parse)(text);
799
- }
800
872
  function hasOpenApi(doc) {
801
873
  return typeof doc.openapi === "string" && doc.openapi.length > 0;
802
874
  }
@@ -807,25 +879,165 @@ function formatError2(error) {
807
879
  return String(error);
808
880
  }
809
881
  function issue(code, message, severity, file, rule, refs) {
810
- const issue5 = {
882
+ const issue6 = {
811
883
  code,
812
884
  severity,
813
885
  message
814
886
  };
815
887
  if (file) {
816
- issue5.file = file;
888
+ issue6.file = file;
817
889
  }
818
890
  if (rule) {
819
- issue5.rule = rule;
891
+ issue6.rule = rule;
820
892
  }
821
893
  if (refs && refs.length > 0) {
822
- issue5.refs = refs;
894
+ issue6.refs = refs;
823
895
  }
824
- return issue5;
896
+ return issue6;
825
897
  }
826
898
 
827
- // src/core/validators/scenario.ts
899
+ // src/core/validators/ids.ts
900
+ var import_promises6 = require("fs/promises");
901
+ var import_node_path6 = __toESM(require("path"), 1);
902
+
903
+ // src/core/contractIndex.ts
828
904
  var import_promises5 = require("fs/promises");
905
+ async function buildContractIndex(root, config) {
906
+ const uiRoot = resolvePath(root, config, "uiContractsDir");
907
+ const apiRoot = resolvePath(root, config, "apiContractsDir");
908
+ const dataRoot = resolvePath(root, config, "dataContractsDir");
909
+ const [uiFiles, apiFiles, dataFiles] = await Promise.all([
910
+ collectUiContractFiles(uiRoot),
911
+ collectApiContractFiles(apiRoot),
912
+ collectDataContractFiles(dataRoot)
913
+ ]);
914
+ const index = {
915
+ ids: /* @__PURE__ */ new Set(),
916
+ idToFiles: /* @__PURE__ */ new Map(),
917
+ files: { ui: uiFiles, api: apiFiles, data: dataFiles },
918
+ structuredParseFailedFiles: /* @__PURE__ */ new Set()
919
+ };
920
+ await indexUiContracts(uiFiles, index);
921
+ await indexApiContracts(apiFiles, index);
922
+ await indexDataContracts(dataFiles, index);
923
+ return index;
924
+ }
925
+ async function indexUiContracts(files, index) {
926
+ for (const file of files) {
927
+ const text = await (0, import_promises5.readFile)(file, "utf-8");
928
+ try {
929
+ const doc = parseStructuredContract(file, text);
930
+ extractUiContractIds(doc).forEach((id) => record(index, id, file));
931
+ } catch {
932
+ index.structuredParseFailedFiles.add(file);
933
+ extractIds(text, "UI").forEach((id) => record(index, id, file));
934
+ }
935
+ }
936
+ }
937
+ async function indexApiContracts(files, index) {
938
+ for (const file of files) {
939
+ const text = await (0, import_promises5.readFile)(file, "utf-8");
940
+ try {
941
+ const doc = parseStructuredContract(file, text);
942
+ extractApiContractIds(doc).forEach((id) => record(index, id, file));
943
+ } catch {
944
+ index.structuredParseFailedFiles.add(file);
945
+ extractIds(text, "API").forEach((id) => record(index, id, file));
946
+ }
947
+ }
948
+ }
949
+ async function indexDataContracts(files, index) {
950
+ for (const file of files) {
951
+ const text = await (0, import_promises5.readFile)(file, "utf-8");
952
+ extractIds(text, "DATA").forEach((id) => record(index, id, file));
953
+ }
954
+ }
955
+ function record(index, id, file) {
956
+ index.ids.add(id);
957
+ const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
958
+ current.add(file);
959
+ index.idToFiles.set(id, current);
960
+ }
961
+
962
+ // src/core/validators/ids.ts
963
+ async function validateDefinedIds(root, config) {
964
+ const issues = [];
965
+ const specRoot = resolvePath(root, config, "specDir");
966
+ const scenarioRoot = resolvePath(root, config, "scenariosDir");
967
+ const specFiles = await collectSpecFiles(specRoot);
968
+ const scenarioFiles = await collectFiles(scenarioRoot, {
969
+ extensions: [".feature"]
970
+ });
971
+ const defined = /* @__PURE__ */ new Map();
972
+ await collectSpecDefinitionIds(specFiles, defined);
973
+ await collectScenarioDefinitionIds(scenarioFiles, defined);
974
+ const contractIndex = await buildContractIndex(root, config);
975
+ for (const [id, files] of contractIndex.idToFiles.entries()) {
976
+ for (const file of files) {
977
+ recordId(defined, id, file);
978
+ }
979
+ }
980
+ for (const [id, files] of defined.entries()) {
981
+ if (files.size <= 1) {
982
+ continue;
983
+ }
984
+ const sorted = Array.from(files).sort();
985
+ issues.push(
986
+ issue2(
987
+ "QFAI-ID-001",
988
+ `ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
989
+ "error",
990
+ sorted[0],
991
+ "id.duplicate"
992
+ )
993
+ );
994
+ }
995
+ return issues;
996
+ }
997
+ async function collectSpecDefinitionIds(files, out) {
998
+ 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));
1002
+ }
1003
+ }
1004
+ async function collectScenarioDefinitionIds(files, out) {
1005
+ 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));
1008
+ }
1009
+ }
1010
+ function recordId(out, id, file) {
1011
+ const current = out.get(id) ?? /* @__PURE__ */ new Set();
1012
+ current.add(file);
1013
+ out.set(id, current);
1014
+ }
1015
+ function formatFileList(files, root) {
1016
+ return files.map((file) => {
1017
+ const relative = import_node_path6.default.relative(root, file);
1018
+ return relative.length > 0 ? relative : file;
1019
+ }).join(", ");
1020
+ }
1021
+ function issue2(code, message, severity, file, rule, refs) {
1022
+ const issue6 = {
1023
+ code,
1024
+ severity,
1025
+ message
1026
+ };
1027
+ if (file) {
1028
+ issue6.file = file;
1029
+ }
1030
+ if (rule) {
1031
+ issue6.rule = rule;
1032
+ }
1033
+ if (refs && refs.length > 0) {
1034
+ issue6.refs = refs;
1035
+ }
1036
+ return issue6;
1037
+ }
1038
+
1039
+ // src/core/validators/scenario.ts
1040
+ var import_promises7 = require("fs/promises");
829
1041
  var GIVEN_PATTERN = /\bGiven\b/;
830
1042
  var WHEN_PATTERN = /\bWhen\b/;
831
1043
  var THEN_PATTERN = /\bThen\b/;
@@ -836,7 +1048,7 @@ async function validateScenarios(root, config) {
836
1048
  });
837
1049
  if (files.length === 0) {
838
1050
  return [
839
- issue2(
1051
+ issue3(
840
1052
  "QFAI-SC-000",
841
1053
  "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
842
1054
  "info",
@@ -847,7 +1059,7 @@ async function validateScenarios(root, config) {
847
1059
  }
848
1060
  const issues = [];
849
1061
  for (const file of files) {
850
- const text = await (0, import_promises5.readFile)(file, "utf-8");
1062
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
851
1063
  issues.push(...validateScenarioContent(text, file));
852
1064
  }
853
1065
  return issues;
@@ -860,12 +1072,13 @@ function validateScenarioContent(text, file) {
860
1072
  "SC",
861
1073
  "UI",
862
1074
  "API",
863
- "DATA"
1075
+ "DATA",
1076
+ "ADR"
864
1077
  ]);
865
1078
  if (invalidIds.length > 0) {
866
1079
  issues.push(
867
- issue2(
868
- "QFAI_ID_INVALID_FORMAT",
1080
+ issue3(
1081
+ "QFAI-ID-002",
869
1082
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
870
1083
  "error",
871
1084
  file,
@@ -877,7 +1090,7 @@ function validateScenarioContent(text, file) {
877
1090
  const scIds = extractIds(text, "SC");
878
1091
  if (scIds.length === 0) {
879
1092
  issues.push(
880
- issue2(
1093
+ issue3(
881
1094
  "QFAI-SC-001",
882
1095
  "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
883
1096
  "error",
@@ -889,7 +1102,7 @@ function validateScenarioContent(text, file) {
889
1102
  const specIds = extractIds(text, "SPEC");
890
1103
  if (specIds.length === 0) {
891
1104
  issues.push(
892
- issue2(
1105
+ issue3(
893
1106
  "QFAI-SC-002",
894
1107
  "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
895
1108
  "error",
@@ -901,7 +1114,7 @@ function validateScenarioContent(text, file) {
901
1114
  const brIds = extractIds(text, "BR");
902
1115
  if (brIds.length === 0) {
903
1116
  issues.push(
904
- issue2(
1117
+ issue3(
905
1118
  "QFAI-SC-003",
906
1119
  "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
907
1120
  "error",
@@ -922,7 +1135,7 @@ function validateScenarioContent(text, file) {
922
1135
  }
923
1136
  if (missingSteps.length > 0) {
924
1137
  issues.push(
925
- issue2(
1138
+ issue3(
926
1139
  "QFAI-SC-005",
927
1140
  `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
928
1141
  "warning",
@@ -933,33 +1146,33 @@ function validateScenarioContent(text, file) {
933
1146
  }
934
1147
  return issues;
935
1148
  }
936
- function issue2(code, message, severity, file, rule, refs) {
937
- const issue5 = {
1149
+ function issue3(code, message, severity, file, rule, refs) {
1150
+ const issue6 = {
938
1151
  code,
939
1152
  severity,
940
1153
  message
941
1154
  };
942
1155
  if (file) {
943
- issue5.file = file;
1156
+ issue6.file = file;
944
1157
  }
945
1158
  if (rule) {
946
- issue5.rule = rule;
1159
+ issue6.rule = rule;
947
1160
  }
948
1161
  if (refs && refs.length > 0) {
949
- issue5.refs = refs;
1162
+ issue6.refs = refs;
950
1163
  }
951
- return issue5;
1164
+ return issue6;
952
1165
  }
953
1166
 
954
1167
  // src/core/validators/spec.ts
955
- var import_promises6 = require("fs/promises");
1168
+ var import_promises8 = require("fs/promises");
956
1169
  async function validateSpecs(root, config) {
957
1170
  const specsRoot = resolvePath(root, config, "specDir");
958
1171
  const files = await collectSpecFiles(specsRoot);
959
1172
  if (files.length === 0) {
960
1173
  const expected = "spec-0001-<slug>.md";
961
1174
  return [
962
- issue3(
1175
+ issue4(
963
1176
  "QFAI-SPEC-000",
964
1177
  `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}`,
965
1178
  "info",
@@ -970,7 +1183,7 @@ async function validateSpecs(root, config) {
970
1183
  }
971
1184
  const issues = [];
972
1185
  for (const file of files) {
973
- const text = await (0, import_promises6.readFile)(file, "utf-8");
1186
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
974
1187
  issues.push(
975
1188
  ...validateSpecContent(
976
1189
  text,
@@ -989,12 +1202,13 @@ function validateSpecContent(text, file, requiredSections) {
989
1202
  "SC",
990
1203
  "UI",
991
1204
  "API",
992
- "DATA"
1205
+ "DATA",
1206
+ "ADR"
993
1207
  ]);
994
1208
  if (invalidIds.length > 0) {
995
1209
  issues.push(
996
- issue3(
997
- "QFAI_ID_INVALID_FORMAT",
1210
+ issue4(
1211
+ "QFAI-ID-002",
998
1212
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
999
1213
  "error",
1000
1214
  file,
@@ -1006,7 +1220,7 @@ function validateSpecContent(text, file, requiredSections) {
1006
1220
  const specIds = extractIds(text, "SPEC");
1007
1221
  if (specIds.length === 0) {
1008
1222
  issues.push(
1009
- issue3(
1223
+ issue4(
1010
1224
  "QFAI-SPEC-001",
1011
1225
  "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1012
1226
  "error",
@@ -1018,7 +1232,7 @@ function validateSpecContent(text, file, requiredSections) {
1018
1232
  const brIds = extractIds(text, "BR");
1019
1233
  if (brIds.length === 0) {
1020
1234
  issues.push(
1021
- issue3(
1235
+ issue4(
1022
1236
  "QFAI-SPEC-002",
1023
1237
  "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1024
1238
  "error",
@@ -1030,7 +1244,7 @@ function validateSpecContent(text, file, requiredSections) {
1030
1244
  const scIds = extractIds(text, "SC");
1031
1245
  if (scIds.length > 0) {
1032
1246
  issues.push(
1033
- issue3(
1247
+ issue4(
1034
1248
  "QFAI-SPEC-003",
1035
1249
  "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
1036
1250
  "warning",
@@ -1043,7 +1257,7 @@ function validateSpecContent(text, file, requiredSections) {
1043
1257
  for (const section of requiredSections) {
1044
1258
  if (!text.includes(section)) {
1045
1259
  issues.push(
1046
- issue3(
1260
+ issue4(
1047
1261
  "QFAI-SPEC-004",
1048
1262
  `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
1049
1263
  "error",
@@ -1055,26 +1269,26 @@ function validateSpecContent(text, file, requiredSections) {
1055
1269
  }
1056
1270
  return issues;
1057
1271
  }
1058
- function issue3(code, message, severity, file, rule, refs) {
1059
- const issue5 = {
1272
+ function issue4(code, message, severity, file, rule, refs) {
1273
+ const issue6 = {
1060
1274
  code,
1061
1275
  severity,
1062
1276
  message
1063
1277
  };
1064
1278
  if (file) {
1065
- issue5.file = file;
1279
+ issue6.file = file;
1066
1280
  }
1067
1281
  if (rule) {
1068
- issue5.rule = rule;
1282
+ issue6.rule = rule;
1069
1283
  }
1070
1284
  if (refs && refs.length > 0) {
1071
- issue5.refs = refs;
1285
+ issue6.refs = refs;
1072
1286
  }
1073
- return issue5;
1287
+ return issue6;
1074
1288
  }
1075
1289
 
1076
1290
  // src/core/validators/traceability.ts
1077
- var import_promises7 = require("fs/promises");
1291
+ var import_promises9 = require("fs/promises");
1078
1292
  async function validateTraceability(root, config) {
1079
1293
  const issues = [];
1080
1294
  const specsRoot = resolvePath(root, config, "specDir");
@@ -1090,36 +1304,141 @@ async function validateTraceability(root, config) {
1090
1304
  extensions: [".feature"]
1091
1305
  });
1092
1306
  const upstreamIds = /* @__PURE__ */ new Set();
1307
+ const specIds = /* @__PURE__ */ new Set();
1093
1308
  const brIdsInSpecs = /* @__PURE__ */ new Set();
1094
1309
  const brIdsInScenarios = /* @__PURE__ */ new Set();
1095
1310
  const scIdsInScenarios = /* @__PURE__ */ new Set();
1096
1311
  const scenarioContractIds = /* @__PURE__ */ new Set();
1097
1312
  const scWithContracts = /* @__PURE__ */ new Set();
1098
- for (const file of [...specFiles, ...decisionFiles]) {
1099
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1313
+ const specToBrIds = /* @__PURE__ */ new Map();
1314
+ const contractIndex = await buildContractIndex(root, config);
1315
+ const contractIds = contractIndex.ids;
1316
+ for (const file of specFiles) {
1317
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1318
+ 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");
1322
+ brIds.forEach((id) => brIdsInSpecs.add(id));
1323
+ const referencedContractIds = /* @__PURE__ */ new Set([
1324
+ ...extractIds(text, "UI"),
1325
+ ...extractIds(text, "API"),
1326
+ ...extractIds(text, "DATA")
1327
+ ]);
1328
+ const unknownContractIds = Array.from(referencedContractIds).filter(
1329
+ (id) => !contractIds.has(id)
1330
+ );
1331
+ if (unknownContractIds.length > 0) {
1332
+ issues.push(
1333
+ issue5(
1334
+ "QFAI-TRACE-009",
1335
+ `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1336
+ ", "
1337
+ )}`,
1338
+ "error",
1339
+ file,
1340
+ "traceability.specContractExists",
1341
+ unknownContractIds
1342
+ )
1343
+ );
1344
+ }
1345
+ for (const specId of specIdsInFile) {
1346
+ const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
1347
+ brIds.forEach((id) => current.add(id));
1348
+ specToBrIds.set(specId, current);
1349
+ }
1350
+ }
1351
+ for (const file of decisionFiles) {
1352
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1100
1353
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1101
- extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
1102
1354
  }
1103
1355
  for (const file of scenarioFiles) {
1104
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1356
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1105
1357
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1358
+ const specIdsInScenario = extractIds(text, "SPEC");
1106
1359
  const brIds = extractIds(text, "BR");
1107
- brIds.forEach((id) => brIdsInScenarios.add(id));
1108
1360
  const scIds = extractIds(text, "SC");
1109
- scIds.forEach((id) => scIdsInScenarios.add(id));
1110
- const contractIds = [
1361
+ const scenarioIds = [
1111
1362
  ...extractIds(text, "UI"),
1112
1363
  ...extractIds(text, "API"),
1113
1364
  ...extractIds(text, "DATA")
1114
1365
  ];
1115
- contractIds.forEach((id) => scenarioContractIds.add(id));
1116
- if (contractIds.length > 0) {
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) {
1117
1370
  scIds.forEach((id) => scWithContracts.add(id));
1118
1371
  }
1372
+ const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
1373
+ if (unknownSpecIds.length > 0) {
1374
+ issues.push(
1375
+ issue5(
1376
+ "QFAI-TRACE-005",
1377
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1378
+ "error",
1379
+ file,
1380
+ "traceability.scenarioSpecExists",
1381
+ unknownSpecIds
1382
+ )
1383
+ );
1384
+ }
1385
+ const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
1386
+ if (unknownBrIds.length > 0) {
1387
+ issues.push(
1388
+ issue5(
1389
+ "QFAI-TRACE-006",
1390
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1391
+ "error",
1392
+ file,
1393
+ "traceability.scenarioBrExists",
1394
+ unknownBrIds
1395
+ )
1396
+ );
1397
+ }
1398
+ const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
1399
+ if (unknownContractIds.length > 0) {
1400
+ issues.push(
1401
+ issue5(
1402
+ "QFAI-TRACE-008",
1403
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1404
+ ", "
1405
+ )}`,
1406
+ config.validation.traceability.unknownContractIdSeverity,
1407
+ file,
1408
+ "traceability.scenarioContractExists",
1409
+ unknownContractIds
1410
+ )
1411
+ );
1412
+ }
1413
+ if (specIdsInScenario.length > 0) {
1414
+ const allowedBrIds = /* @__PURE__ */ new Set();
1415
+ for (const specId of specIdsInScenario) {
1416
+ const brIdsForSpec = specToBrIds.get(specId);
1417
+ if (!brIdsForSpec) {
1418
+ continue;
1419
+ }
1420
+ brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1421
+ }
1422
+ const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
1423
+ if (invalidBrIds.length > 0) {
1424
+ issues.push(
1425
+ issue5(
1426
+ "QFAI-TRACE-007",
1427
+ `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1428
+ ", "
1429
+ )} (SPEC: ${specIdsInScenario.join(", ")})`,
1430
+ "error",
1431
+ file,
1432
+ "traceability.scenarioBrUnderSpec",
1433
+ invalidBrIds
1434
+ )
1435
+ );
1436
+ }
1437
+ }
1119
1438
  }
1120
1439
  if (upstreamIds.size === 0) {
1121
1440
  return [
1122
- issue4(
1441
+ issue5(
1123
1442
  "QFAI-TRACE-000",
1124
1443
  "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1125
1444
  "info",
@@ -1134,7 +1453,7 @@ async function validateTraceability(root, config) {
1134
1453
  );
1135
1454
  if (orphanBrIds.length > 0) {
1136
1455
  issues.push(
1137
- issue4(
1456
+ issue5(
1138
1457
  "QFAI_TRACE_BR_ORPHAN",
1139
1458
  `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1140
1459
  "error",
@@ -1151,7 +1470,7 @@ async function validateTraceability(root, config) {
1151
1470
  );
1152
1471
  if (scWithoutContracts.length > 0) {
1153
1472
  issues.push(
1154
- issue4(
1473
+ issue5(
1155
1474
  "QFAI_TRACE_SC_NO_CONTRACT",
1156
1475
  `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1157
1476
  ", "
@@ -1165,14 +1484,13 @@ async function validateTraceability(root, config) {
1165
1484
  }
1166
1485
  }
1167
1486
  if (!config.validation.traceability.allowOrphanContracts) {
1168
- const contractIds = await collectContractIds(root, config);
1169
1487
  if (contractIds.size > 0) {
1170
1488
  const orphanContracts = Array.from(contractIds).filter(
1171
1489
  (id) => !scenarioContractIds.has(id)
1172
1490
  );
1173
1491
  if (orphanContracts.length > 0) {
1174
1492
  issues.push(
1175
- issue4(
1493
+ issue5(
1176
1494
  "QFAI_CONTRACT_ORPHAN",
1177
1495
  `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1178
1496
  "error",
@@ -1189,27 +1507,6 @@ async function validateTraceability(root, config) {
1189
1507
  );
1190
1508
  return issues;
1191
1509
  }
1192
- async function collectContractIds(root, config) {
1193
- const contractIds = /* @__PURE__ */ new Set();
1194
- const uiRoot = resolvePath(root, config, "uiContractsDir");
1195
- const apiRoot = resolvePath(root, config, "apiContractsDir");
1196
- const dataRoot = resolvePath(root, config, "dataContractsDir");
1197
- const uiFiles = await collectUiContractFiles(uiRoot);
1198
- const apiFiles = await collectApiContractFiles(apiRoot);
1199
- const dataFiles = await collectDataContractFiles(dataRoot);
1200
- await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
1201
- await collectIdsFromFiles(apiFiles, ["API"], contractIds);
1202
- await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
1203
- return contractIds;
1204
- }
1205
- async function collectIdsFromFiles(files, prefixes, out) {
1206
- for (const file of files) {
1207
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1208
- for (const prefix of prefixes) {
1209
- extractIds(text, prefix).forEach((id) => out.add(id));
1210
- }
1211
- }
1212
- }
1213
1510
  async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1214
1511
  const issues = [];
1215
1512
  const codeFiles = await collectFiles(srcRoot, {
@@ -1221,7 +1518,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1221
1518
  const targetFiles = [...codeFiles, ...testFiles];
1222
1519
  if (targetFiles.length === 0) {
1223
1520
  issues.push(
1224
- issue4(
1521
+ issue5(
1225
1522
  "QFAI-TRACE-001",
1226
1523
  "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1227
1524
  "info",
@@ -1234,7 +1531,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1234
1531
  const pattern = buildIdPattern(Array.from(upstreamIds));
1235
1532
  let found = false;
1236
1533
  for (const file of targetFiles) {
1237
- const text = await (0, import_promises7.readFile)(file, "utf-8");
1534
+ const text = await (0, import_promises9.readFile)(file, "utf-8");
1238
1535
  if (pattern.test(text)) {
1239
1536
  found = true;
1240
1537
  break;
@@ -1242,7 +1539,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1242
1539
  }
1243
1540
  if (!found) {
1244
1541
  issues.push(
1245
- issue4(
1542
+ issue5(
1246
1543
  "QFAI-TRACE-002",
1247
1544
  "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1248
1545
  "warning",
@@ -1257,22 +1554,22 @@ function buildIdPattern(ids) {
1257
1554
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1258
1555
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
1259
1556
  }
1260
- function issue4(code, message, severity, file, rule, refs) {
1261
- const issue5 = {
1557
+ function issue5(code, message, severity, file, rule, refs) {
1558
+ const issue6 = {
1262
1559
  code,
1263
1560
  severity,
1264
1561
  message
1265
1562
  };
1266
1563
  if (file) {
1267
- issue5.file = file;
1564
+ issue6.file = file;
1268
1565
  }
1269
1566
  if (rule) {
1270
- issue5.rule = rule;
1567
+ issue6.rule = rule;
1271
1568
  }
1272
1569
  if (refs && refs.length > 0) {
1273
- issue5.refs = refs;
1570
+ issue6.refs = refs;
1274
1571
  }
1275
- return issue5;
1572
+ return issue6;
1276
1573
  }
1277
1574
 
1278
1575
  // src/core/validate.ts
@@ -1284,6 +1581,7 @@ async function validateProject(root, configResult) {
1284
1581
  ...await validateSpecs(root, config),
1285
1582
  ...await validateScenarios(root, config),
1286
1583
  ...await validateContracts(root, config),
1584
+ ...await validateDefinedIds(root, config),
1287
1585
  ...await validateTraceability(root, config)
1288
1586
  ];
1289
1587
  const toolVersion = await resolveToolVersion();
@@ -1296,8 +1594,8 @@ async function validateProject(root, configResult) {
1296
1594
  }
1297
1595
  function countIssues(issues) {
1298
1596
  return issues.reduce(
1299
- (acc, issue5) => {
1300
- acc[issue5.severity] += 1;
1597
+ (acc, issue6) => {
1598
+ acc[issue6.severity] += 1;
1301
1599
  return acc;
1302
1600
  },
1303
1601
  { info: 0, warning: 0, error: 0 }
@@ -1305,7 +1603,7 @@ function countIssues(issues) {
1305
1603
  }
1306
1604
 
1307
1605
  // src/core/report.ts
1308
- var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
1606
+ var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
1309
1607
  async function createReportData(root, validation, configResult) {
1310
1608
  const resolved = configResult ?? await loadConfig(root);
1311
1609
  const config = resolved.config;
@@ -1313,7 +1611,6 @@ async function createReportData(root, validation, configResult) {
1313
1611
  const specRoot = resolvePath(root, config, "specDir");
1314
1612
  const decisionsRoot = resolvePath(root, config, "decisionsDir");
1315
1613
  const scenariosRoot = resolvePath(root, config, "scenariosDir");
1316
- const rulesRoot = resolvePath(root, config, "rulesDir");
1317
1614
  const apiRoot = resolvePath(root, config, "apiContractsDir");
1318
1615
  const uiRoot = resolvePath(root, config, "uiContractsDir");
1319
1616
  const dbRoot = resolvePath(root, config, "dataContractsDir");
@@ -1326,7 +1623,6 @@ async function createReportData(root, validation, configResult) {
1326
1623
  const decisionFiles = await collectFiles(decisionsRoot, {
1327
1624
  extensions: [".md"]
1328
1625
  });
1329
- const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
1330
1626
  const {
1331
1627
  api: apiFiles,
1332
1628
  ui: uiFiles,
@@ -1336,7 +1632,6 @@ async function createReportData(root, validation, configResult) {
1336
1632
  ...specFiles,
1337
1633
  ...scenarioFiles,
1338
1634
  ...decisionFiles,
1339
- ...ruleFiles,
1340
1635
  ...apiFiles,
1341
1636
  ...uiFiles,
1342
1637
  ...dbFiles
@@ -1362,7 +1657,6 @@ async function createReportData(root, validation, configResult) {
1362
1657
  specs: specFiles.length,
1363
1658
  scenarios: scenarioFiles.length,
1364
1659
  decisions: decisionFiles.length,
1365
- rules: ruleFiles.length,
1366
1660
  contracts: {
1367
1661
  api: apiFiles.length,
1368
1662
  ui: uiFiles.length,
@@ -1397,7 +1691,6 @@ function formatReportMarkdown(data) {
1397
1691
  lines.push(`- specs: ${data.summary.specs}`);
1398
1692
  lines.push(`- scenarios: ${data.summary.scenarios}`);
1399
1693
  lines.push(`- decisions: ${data.summary.decisions}`);
1400
- lines.push(`- rules: ${data.summary.rules}`);
1401
1694
  lines.push(
1402
1695
  `- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
1403
1696
  );
@@ -1433,7 +1726,7 @@ function formatReportMarkdown(data) {
1433
1726
  lines.push("");
1434
1727
  lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
1435
1728
  const traceIssues = data.issues.filter(
1436
- (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
1729
+ (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-") || item.code === "QFAI_CONTRACT_ORPHAN"
1437
1730
  );
1438
1731
  if (traceIssues.length === 0) {
1439
1732
  lines.push("- (none)");
@@ -1473,8 +1766,8 @@ async function collectIds(files) {
1473
1766
  DATA: /* @__PURE__ */ new Set()
1474
1767
  };
1475
1768
  for (const file of files) {
1476
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1477
- for (const prefix of ID_PREFIXES) {
1769
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1770
+ for (const prefix of ID_PREFIXES2) {
1478
1771
  const ids = extractIds(text, prefix);
1479
1772
  ids.forEach((id) => result[prefix].add(id));
1480
1773
  }
@@ -1491,7 +1784,7 @@ async function collectIds(files) {
1491
1784
  async function collectUpstreamIds(files) {
1492
1785
  const ids = /* @__PURE__ */ new Set();
1493
1786
  for (const file of files) {
1494
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1787
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1495
1788
  extractAllIds(text).forEach((id) => ids.add(id));
1496
1789
  }
1497
1790
  return ids;
@@ -1512,7 +1805,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1512
1805
  }
1513
1806
  const pattern = buildIdPattern2(Array.from(upstreamIds));
1514
1807
  for (const file of targetFiles) {
1515
- const text = await (0, import_promises8.readFile)(file, "utf-8");
1808
+ const text = await (0, import_promises10.readFile)(file, "utf-8");
1516
1809
  if (pattern.test(text)) {
1517
1810
  return true;
1518
1811
  }
@@ -1534,20 +1827,20 @@ function toSortedArray(values) {
1534
1827
  }
1535
1828
  function buildHotspots(issues) {
1536
1829
  const map = /* @__PURE__ */ new Map();
1537
- for (const issue5 of issues) {
1538
- if (!issue5.file) {
1830
+ for (const issue6 of issues) {
1831
+ if (!issue6.file) {
1539
1832
  continue;
1540
1833
  }
1541
- const current = map.get(issue5.file) ?? {
1542
- file: issue5.file,
1834
+ const current = map.get(issue6.file) ?? {
1835
+ file: issue6.file,
1543
1836
  total: 0,
1544
1837
  error: 0,
1545
1838
  warning: 0,
1546
1839
  info: 0
1547
1840
  };
1548
1841
  current.total += 1;
1549
- current[issue5.severity] += 1;
1550
- map.set(issue5.file, current);
1842
+ current[issue6.severity] += 1;
1843
+ map.set(issue6.file, current);
1551
1844
  }
1552
1845
  return Array.from(map.values()).sort(
1553
1846
  (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
@@ -1569,6 +1862,7 @@ function buildHotspots(issues) {
1569
1862
  resolvePath,
1570
1863
  resolveToolVersion,
1571
1864
  validateContracts,
1865
+ validateDefinedIds,
1572
1866
  validateProject,
1573
1867
  validateScenarioContent,
1574
1868
  validateScenarios,