qfai 0.2.5 → 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.
Files changed (128) hide show
  1. package/README.md +1 -1
  2. package/assets/init/.qfai/README.md +42 -0
  3. package/assets/init/.qfai/contracts/README.md +61 -0
  4. package/assets/init/{qfai → .qfai}/contracts/api/api-0001-sample.yaml +3 -0
  5. package/assets/init/{qfai → .qfai}/contracts/db/db-0001-sample.sql +3 -2
  6. package/assets/init/.qfai/contracts/ui/ui-0001-sample.yaml +6 -0
  7. package/assets/init/.qfai/out/README.md +17 -0
  8. package/assets/init/.qfai/prompts/README.md +32 -0
  9. package/assets/init/{qfai → .qfai}/prompts/makeBusinessFlow.md +1 -1
  10. package/assets/init/{qfai → .qfai}/prompts/makeOverview.md +1 -1
  11. package/assets/init/.qfai/spec/README.md +80 -0
  12. package/assets/init/.qfai/spec/decisions/ADR-0001.md +9 -0
  13. package/assets/init/.qfai/spec/decisions/README.md +36 -0
  14. package/assets/init/.qfai/spec/scenarios/scenarios.feature +6 -0
  15. package/assets/init/.qfai/spec/spec-0001-sample.md +36 -0
  16. package/assets/init/root/qfai.config.yaml +8 -8
  17. package/dist/cli/index.cjs +498 -206
  18. package/dist/cli/index.cjs.map +1 -1
  19. package/dist/cli/index.d.ts +0 -2
  20. package/dist/cli/index.mjs +495 -203
  21. package/dist/cli/index.mjs.map +1 -1
  22. package/dist/index.cjs +471 -177
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +7 -4
  25. package/dist/index.d.ts +135 -2
  26. package/dist/index.mjs +470 -177
  27. package/dist/index.mjs.map +1 -1
  28. package/package.json +1 -1
  29. package/assets/init/qfai/README.md +0 -6
  30. package/assets/init/qfai/contracts/ui/ui-0001-sample.yaml +0 -4
  31. package/assets/init/qfai/spec/decisions/ADR-0001.md +0 -7
  32. package/assets/init/qfai/spec/scenarios.feature +0 -6
  33. package/assets/init/qfai/spec/spec-0001-sample.md +0 -29
  34. package/dist/cli/commands/init.d.ts +0 -8
  35. package/dist/cli/commands/init.d.ts.map +0 -1
  36. package/dist/cli/commands/init.js +0 -30
  37. package/dist/cli/commands/init.js.map +0 -1
  38. package/dist/cli/commands/report.d.ts +0 -8
  39. package/dist/cli/commands/report.d.ts.map +0 -1
  40. package/dist/cli/commands/report.js +0 -83
  41. package/dist/cli/commands/report.js.map +0 -1
  42. package/dist/cli/commands/validate.d.ts +0 -10
  43. package/dist/cli/commands/validate.d.ts.map +0 -1
  44. package/dist/cli/commands/validate.js +0 -66
  45. package/dist/cli/commands/validate.js.map +0 -1
  46. package/dist/cli/index.d.ts.map +0 -1
  47. package/dist/cli/index.js +0 -7
  48. package/dist/cli/index.js.map +0 -1
  49. package/dist/cli/lib/args.d.ts +0 -19
  50. package/dist/cli/lib/args.d.ts.map +0 -1
  51. package/dist/cli/lib/args.js +0 -107
  52. package/dist/cli/lib/args.js.map +0 -1
  53. package/dist/cli/lib/assets.d.ts +0 -2
  54. package/dist/cli/lib/assets.d.ts.map +0 -1
  55. package/dist/cli/lib/assets.js +0 -8
  56. package/dist/cli/lib/assets.js.map +0 -1
  57. package/dist/cli/lib/failOn.d.ts +0 -5
  58. package/dist/cli/lib/failOn.d.ts.map +0 -1
  59. package/dist/cli/lib/failOn.js +0 -10
  60. package/dist/cli/lib/failOn.js.map +0 -1
  61. package/dist/cli/lib/fs.d.ts +0 -11
  62. package/dist/cli/lib/fs.d.ts.map +0 -1
  63. package/dist/cli/lib/fs.js +0 -91
  64. package/dist/cli/lib/fs.js.map +0 -1
  65. package/dist/cli/lib/logger.d.ts +0 -4
  66. package/dist/cli/lib/logger.d.ts.map +0 -1
  67. package/dist/cli/lib/logger.js +0 -10
  68. package/dist/cli/lib/logger.js.map +0 -1
  69. package/dist/cli/main.d.ts +0 -2
  70. package/dist/cli/main.d.ts.map +0 -1
  71. package/dist/cli/main.js +0 -73
  72. package/dist/cli/main.js.map +0 -1
  73. package/dist/core/config.d.ts +0 -46
  74. package/dist/core/config.d.ts.map +0 -1
  75. package/dist/core/config.js +0 -224
  76. package/dist/core/config.js.map +0 -1
  77. package/dist/core/discovery.d.ts +0 -11
  78. package/dist/core/discovery.d.ts.map +0 -1
  79. package/dist/core/discovery.js +0 -31
  80. package/dist/core/discovery.js.map +0 -1
  81. package/dist/core/fs.d.ts +0 -6
  82. package/dist/core/fs.d.ts.map +0 -1
  83. package/dist/core/fs.js +0 -55
  84. package/dist/core/fs.js.map +0 -1
  85. package/dist/core/ids.d.ts +0 -5
  86. package/dist/core/ids.d.ts.map +0 -1
  87. package/dist/core/ids.js +0 -49
  88. package/dist/core/ids.js.map +0 -1
  89. package/dist/core/index.d.ts +0 -11
  90. package/dist/core/index.d.ts.map +0 -1
  91. package/dist/core/index.js +0 -11
  92. package/dist/core/index.js.map +0 -1
  93. package/dist/core/report.d.ts +0 -41
  94. package/dist/core/report.d.ts.map +0 -1
  95. package/dist/core/report.js +0 -238
  96. package/dist/core/report.js.map +0 -1
  97. package/dist/core/types.d.ts +0 -27
  98. package/dist/core/types.d.ts.map +0 -1
  99. package/dist/core/types.js +0 -2
  100. package/dist/core/types.js.map +0 -1
  101. package/dist/core/validate.d.ts +0 -4
  102. package/dist/core/validate.d.ts.map +0 -1
  103. package/dist/core/validate.js +0 -32
  104. package/dist/core/validate.js.map +0 -1
  105. package/dist/core/validators/contracts.d.ts +0 -5
  106. package/dist/core/validators/contracts.d.ts.map +0 -1
  107. package/dist/core/validators/contracts.js +0 -157
  108. package/dist/core/validators/contracts.js.map +0 -1
  109. package/dist/core/validators/scenario.d.ts +0 -5
  110. package/dist/core/validators/scenario.d.ts.map +0 -1
  111. package/dist/core/validators/scenario.js +0 -82
  112. package/dist/core/validators/scenario.js.map +0 -1
  113. package/dist/core/validators/spec.d.ts +0 -5
  114. package/dist/core/validators/spec.d.ts.map +0 -1
  115. package/dist/core/validators/spec.js +0 -69
  116. package/dist/core/validators/spec.js.map +0 -1
  117. package/dist/core/validators/traceability.d.ts +0 -4
  118. package/dist/core/validators/traceability.d.ts.map +0 -1
  119. package/dist/core/validators/traceability.js +0 -148
  120. package/dist/core/validators/traceability.js.map +0 -1
  121. package/dist/core/version.d.ts +0 -2
  122. package/dist/core/version.d.ts.map +0 -1
  123. package/dist/core/version.js +0 -25
  124. package/dist/core/version.js.map +0 -1
  125. package/dist/index.d.ts.map +0 -1
  126. package/dist/index.js +0 -2
  127. package/dist/index.js.map +0 -1
  128. package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.mjs CHANGED
@@ -4,14 +4,13 @@ import path from "path";
4
4
  import { parse as parseYaml } from "yaml";
5
5
  var defaultConfig = {
6
6
  paths: {
7
- specDir: "qfai/spec",
8
- decisionsDir: "qfai/spec/decisions",
9
- scenariosDir: "qfai/spec",
10
- rulesDir: "qfai/rules",
11
- contractsDir: "qfai/contracts",
12
- uiContractsDir: "qfai/contracts/ui",
13
- apiContractsDir: "qfai/contracts/api",
14
- dataContractsDir: "qfai/contracts/db",
7
+ specDir: ".qfai/spec",
8
+ decisionsDir: ".qfai/spec/decisions",
9
+ scenariosDir: ".qfai/spec/scenarios",
10
+ contractsDir: ".qfai/contracts",
11
+ uiContractsDir: ".qfai/contracts/ui",
12
+ apiContractsDir: ".qfai/contracts/api",
13
+ dataContractsDir: ".qfai/contracts/db",
15
14
  srcDir: "src",
16
15
  testsDir: "tests"
17
16
  },
@@ -31,7 +30,8 @@ var defaultConfig = {
31
30
  traceability: {
32
31
  brMustHaveSc: true,
33
32
  scMustTouchContracts: true,
34
- allowOrphanContracts: false
33
+ allowOrphanContracts: false,
34
+ unknownContractIdSeverity: "error"
35
35
  }
36
36
  },
37
37
  output: {
@@ -106,13 +106,6 @@ function normalizePaths(raw, configPath, issues) {
106
106
  configPath,
107
107
  issues
108
108
  ),
109
- rulesDir: readString(
110
- raw.rulesDir,
111
- base.rulesDir,
112
- "paths.rulesDir",
113
- configPath,
114
- issues
115
- ),
116
109
  contractsDir: readString(
117
110
  raw.contractsDir,
118
111
  base.contractsDir,
@@ -237,6 +230,13 @@ function normalizeValidation(raw, configPath, issues) {
237
230
  "validation.traceability.allowOrphanContracts",
238
231
  configPath,
239
232
  issues
233
+ ),
234
+ unknownContractIdSeverity: readTraceabilitySeverity(
235
+ traceabilityRaw?.unknownContractIdSeverity,
236
+ base.traceability.unknownContractIdSeverity,
237
+ "validation.traceability.unknownContractIdSeverity",
238
+ configPath,
239
+ issues
240
240
  )
241
241
  }
242
242
  };
@@ -316,6 +316,20 @@ function readFailOn(value, fallback, label, configPath, issues) {
316
316
  }
317
317
  return fallback;
318
318
  }
319
+ function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
320
+ if (value === "warning" || value === "error") {
321
+ return value;
322
+ }
323
+ if (value !== void 0) {
324
+ issues.push(
325
+ configIssue(
326
+ configPath,
327
+ `${label} \u306F warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
328
+ )
329
+ );
330
+ }
331
+ return fallback;
332
+ }
319
333
  function readOutputFormat(value, fallback, label, configPath, issues) {
320
334
  if (value === "text" || value === "json" || value === "github") {
321
335
  return value;
@@ -356,13 +370,15 @@ function isRecord(value) {
356
370
  }
357
371
 
358
372
  // src/core/ids.ts
359
- var ID_PATTERNS = {
360
- SPEC: /\bSPEC-[A-Z0-9-]+\b/g,
361
- BR: /\bBR-[A-Z0-9-]+\b/g,
362
- SC: /\bSC-[A-Z0-9-]+\b/g,
363
- UI: /\bUI-[A-Z0-9-]+\b/g,
364
- API: /\bAPI-[A-Z0-9-]+\b/g,
365
- DATA: /\bDATA-[A-Z0-9-]+\b/g
373
+ var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
374
+ var STRICT_ID_PATTERNS = {
375
+ SPEC: /\bSPEC-\d{4}\b/g,
376
+ BR: /\bBR-\d{4}\b/g,
377
+ SC: /\bSC-\d{4}\b/g,
378
+ UI: /\bUI-\d{4}\b/g,
379
+ API: /\bAPI-\d{4}\b/g,
380
+ DATA: /\bDATA-\d{4}\b/g,
381
+ ADR: /\bADR-\d{4}\b/g
366
382
  };
367
383
  var LOOSE_ID_PATTERNS = {
368
384
  SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
@@ -370,16 +386,17 @@ var LOOSE_ID_PATTERNS = {
370
386
  SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
371
387
  UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
372
388
  API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
373
- DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
389
+ DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi,
390
+ ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
374
391
  };
375
392
  function extractIds(text, prefix) {
376
- const pattern = ID_PATTERNS[prefix];
393
+ const pattern = STRICT_ID_PATTERNS[prefix];
377
394
  const matches = text.match(pattern);
378
395
  return unique(matches ?? []);
379
396
  }
380
397
  function extractAllIds(text) {
381
398
  const all = [];
382
- Object.keys(ID_PATTERNS).forEach((prefix) => {
399
+ ID_PREFIXES.forEach((prefix) => {
383
400
  all.push(...extractIds(text, prefix));
384
401
  });
385
402
  return unique(all);
@@ -400,13 +417,13 @@ function unique(values) {
400
417
  return Array.from(new Set(values));
401
418
  }
402
419
  function isValidId(value, prefix) {
403
- const pattern = ID_PATTERNS[prefix];
420
+ const pattern = STRICT_ID_PATTERNS[prefix];
404
421
  const strict = new RegExp(pattern.source);
405
422
  return strict.test(value);
406
423
  }
407
424
 
408
425
  // src/core/report.ts
409
- import { readFile as readFile7 } from "fs/promises";
426
+ import { readFile as readFile9 } from "fs/promises";
410
427
 
411
428
  // src/core/discovery.ts
412
429
  import path3 from "path";
@@ -467,8 +484,7 @@ async function exists(target) {
467
484
  }
468
485
 
469
486
  // src/core/discovery.ts
470
- var LEGACY_SPEC_NAME = "spec.md";
471
- var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/i;
487
+ var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/;
472
488
  async function collectSpecFiles(specRoot) {
473
489
  const files = await collectFiles(specRoot, { extensions: [".md"] });
474
490
  return files.filter((file) => isSpecFile(file));
@@ -492,7 +508,7 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
492
508
  }
493
509
  function isSpecFile(filePath) {
494
510
  const name = path3.basename(filePath).toLowerCase();
495
- return name === LEGACY_SPEC_NAME || SPEC_NAMED_PATTERN.test(name);
511
+ return SPEC_NAMED_PATTERN.test(name);
496
512
  }
497
513
 
498
514
  // src/core/types.ts
@@ -503,8 +519,8 @@ import { readFile as readFile2 } from "fs/promises";
503
519
  import path4 from "path";
504
520
  import { fileURLToPath } from "url";
505
521
  async function resolveToolVersion() {
506
- if ("0.2.5".length > 0) {
507
- return "0.2.5";
522
+ if ("0.2.9".length > 0) {
523
+ return "0.2.9";
508
524
  }
509
525
  try {
510
526
  const packagePath = resolvePackageJsonPath();
@@ -524,8 +540,50 @@ function resolvePackageJsonPath() {
524
540
 
525
541
  // src/core/validators/contracts.ts
526
542
  import { readFile as readFile3 } from "fs/promises";
543
+
544
+ // src/core/contracts.ts
527
545
  import path5 from "path";
528
546
  import { parse as parseYaml2 } from "yaml";
547
+ function parseStructuredContract(file, text) {
548
+ const ext = path5.extname(file).toLowerCase();
549
+ if (ext === ".json") {
550
+ return JSON.parse(text);
551
+ }
552
+ return parseYaml2(text);
553
+ }
554
+ function extractUiContractIds(doc) {
555
+ const id = typeof doc.id === "string" ? doc.id : "";
556
+ return extractIds(id, "UI");
557
+ }
558
+ function extractApiContractIds(doc) {
559
+ const operationIds = /* @__PURE__ */ new Set();
560
+ collectOperationIds(doc, operationIds);
561
+ const ids = /* @__PURE__ */ new Set();
562
+ for (const operationId of operationIds) {
563
+ extractIds(operationId, "API").forEach((id) => ids.add(id));
564
+ }
565
+ return Array.from(ids);
566
+ }
567
+ function collectOperationIds(value, out) {
568
+ if (!value || typeof value !== "object") {
569
+ return;
570
+ }
571
+ if (Array.isArray(value)) {
572
+ for (const item of value) {
573
+ collectOperationIds(item, out);
574
+ }
575
+ return;
576
+ }
577
+ for (const [key, entry] of Object.entries(value)) {
578
+ if (key === "operationId" && typeof entry === "string") {
579
+ out.add(entry);
580
+ continue;
581
+ }
582
+ collectOperationIds(entry, out);
583
+ }
584
+ }
585
+
586
+ // src/core/validators/contracts.ts
529
587
  var SQL_DANGEROUS_PATTERNS = [
530
588
  { pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
531
589
  { pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
@@ -574,12 +632,13 @@ async function validateUiContracts(uiRoot) {
574
632
  "SC",
575
633
  "UI",
576
634
  "API",
577
- "DATA"
635
+ "DATA",
636
+ "ADR"
578
637
  ]);
579
638
  if (invalidIds.length > 0) {
580
639
  issues.push(
581
640
  issue(
582
- "QFAI_ID_INVALID_FORMAT",
641
+ "QFAI-ID-002",
583
642
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
584
643
  "error",
585
644
  file,
@@ -588,30 +647,32 @@ async function validateUiContracts(uiRoot) {
588
647
  )
589
648
  );
590
649
  }
650
+ let doc;
591
651
  try {
592
- const doc = parseYaml2(text);
593
- const id = typeof doc.id === "string" ? doc.id : "";
594
- if (!id.startsWith("UI-")) {
595
- issues.push(
596
- issue(
597
- "QFAI-UI-001",
598
- "UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
599
- "error",
600
- file,
601
- "contracts.ui.id"
602
- )
603
- );
604
- }
652
+ doc = parseStructuredContract(file, text);
605
653
  } catch (error) {
606
654
  issues.push(
607
655
  issue(
608
- "QFAI-UI-002",
609
- `UI YAML \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
656
+ "QFAI-CONTRACT-001",
657
+ `UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
610
658
  "error",
611
659
  file,
612
660
  "contracts.ui.parse"
613
661
  )
614
662
  );
663
+ continue;
664
+ }
665
+ const uiIds = extractUiContractIds(doc);
666
+ if (uiIds.length === 0) {
667
+ issues.push(
668
+ issue(
669
+ "QFAI-CONTRACT-002",
670
+ `UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
671
+ "error",
672
+ file,
673
+ "contracts.ui.id"
674
+ )
675
+ );
615
676
  }
616
677
  }
617
678
  return issues;
@@ -638,12 +699,13 @@ async function validateApiContracts(apiRoot) {
638
699
  "SC",
639
700
  "UI",
640
701
  "API",
641
- "DATA"
702
+ "DATA",
703
+ "ADR"
642
704
  ]);
643
705
  if (invalidIds.length > 0) {
644
706
  issues.push(
645
707
  issue(
646
- "QFAI_ID_INVALID_FORMAT",
708
+ "QFAI-ID-002",
647
709
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
648
710
  "error",
649
711
  file,
@@ -652,29 +714,43 @@ async function validateApiContracts(apiRoot) {
652
714
  )
653
715
  );
654
716
  }
717
+ let doc;
655
718
  try {
656
- const doc = parseStructured(file, text);
657
- if (!doc || !hasOpenApi(doc)) {
658
- issues.push(
659
- issue(
660
- "QFAI-API-001",
661
- "OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
662
- "error",
663
- file,
664
- "contracts.api.openapi"
665
- )
666
- );
667
- }
719
+ doc = parseStructuredContract(file, text);
668
720
  } catch (error) {
669
721
  issues.push(
670
722
  issue(
671
- "QFAI-API-002",
672
- `API \u5B9A\u7FA9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
723
+ "QFAI-CONTRACT-001",
724
+ `API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
673
725
  "error",
674
726
  file,
675
727
  "contracts.api.parse"
676
728
  )
677
729
  );
730
+ continue;
731
+ }
732
+ if (!hasOpenApi(doc)) {
733
+ issues.push(
734
+ issue(
735
+ "QFAI-API-001",
736
+ "OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
737
+ "error",
738
+ file,
739
+ "contracts.api.openapi"
740
+ )
741
+ );
742
+ }
743
+ const apiIds = extractApiContractIds(doc);
744
+ if (apiIds.length === 0) {
745
+ issues.push(
746
+ issue(
747
+ "QFAI-CONTRACT-002",
748
+ `API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
749
+ "error",
750
+ file,
751
+ "contracts.api.id"
752
+ )
753
+ );
678
754
  }
679
755
  }
680
756
  return issues;
@@ -701,12 +777,13 @@ async function validateDataContracts(dataRoot) {
701
777
  "SC",
702
778
  "UI",
703
779
  "API",
704
- "DATA"
780
+ "DATA",
781
+ "ADR"
705
782
  ]);
706
783
  if (invalidIds.length > 0) {
707
784
  issues.push(
708
785
  issue(
709
- "QFAI_ID_INVALID_FORMAT",
786
+ "QFAI-ID-002",
710
787
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
711
788
  "error",
712
789
  file,
@@ -736,13 +813,6 @@ function lintSql(text, file) {
736
813
  }
737
814
  return issues;
738
815
  }
739
- function parseStructured(file, text) {
740
- const ext = path5.extname(file).toLowerCase();
741
- if (ext === ".json") {
742
- return JSON.parse(text);
743
- }
744
- return parseYaml2(text);
745
- }
746
816
  function hasOpenApi(doc) {
747
817
  return typeof doc.openapi === "string" && doc.openapi.length > 0;
748
818
  }
@@ -753,25 +823,165 @@ function formatError2(error) {
753
823
  return String(error);
754
824
  }
755
825
  function issue(code, message, severity, file, rule, refs) {
756
- const issue5 = {
826
+ const issue6 = {
757
827
  code,
758
828
  severity,
759
829
  message
760
830
  };
761
831
  if (file) {
762
- issue5.file = file;
832
+ issue6.file = file;
763
833
  }
764
834
  if (rule) {
765
- issue5.rule = rule;
835
+ issue6.rule = rule;
766
836
  }
767
837
  if (refs && refs.length > 0) {
768
- issue5.refs = refs;
838
+ issue6.refs = refs;
769
839
  }
770
- return issue5;
840
+ return issue6;
771
841
  }
772
842
 
773
- // src/core/validators/scenario.ts
843
+ // src/core/validators/ids.ts
844
+ import { readFile as readFile5 } from "fs/promises";
845
+ import path6 from "path";
846
+
847
+ // src/core/contractIndex.ts
774
848
  import { readFile as readFile4 } from "fs/promises";
849
+ async function buildContractIndex(root, config) {
850
+ const uiRoot = resolvePath(root, config, "uiContractsDir");
851
+ const apiRoot = resolvePath(root, config, "apiContractsDir");
852
+ const dataRoot = resolvePath(root, config, "dataContractsDir");
853
+ const [uiFiles, apiFiles, dataFiles] = await Promise.all([
854
+ collectUiContractFiles(uiRoot),
855
+ collectApiContractFiles(apiRoot),
856
+ collectDataContractFiles(dataRoot)
857
+ ]);
858
+ const index = {
859
+ ids: /* @__PURE__ */ new Set(),
860
+ idToFiles: /* @__PURE__ */ new Map(),
861
+ files: { ui: uiFiles, api: apiFiles, data: dataFiles },
862
+ structuredParseFailedFiles: /* @__PURE__ */ new Set()
863
+ };
864
+ await indexUiContracts(uiFiles, index);
865
+ await indexApiContracts(apiFiles, index);
866
+ await indexDataContracts(dataFiles, index);
867
+ return index;
868
+ }
869
+ async function indexUiContracts(files, index) {
870
+ for (const file of files) {
871
+ const text = await readFile4(file, "utf-8");
872
+ try {
873
+ const doc = parseStructuredContract(file, text);
874
+ extractUiContractIds(doc).forEach((id) => record(index, id, file));
875
+ } catch {
876
+ index.structuredParseFailedFiles.add(file);
877
+ extractIds(text, "UI").forEach((id) => record(index, id, file));
878
+ }
879
+ }
880
+ }
881
+ async function indexApiContracts(files, index) {
882
+ for (const file of files) {
883
+ const text = await readFile4(file, "utf-8");
884
+ try {
885
+ const doc = parseStructuredContract(file, text);
886
+ extractApiContractIds(doc).forEach((id) => record(index, id, file));
887
+ } catch {
888
+ index.structuredParseFailedFiles.add(file);
889
+ extractIds(text, "API").forEach((id) => record(index, id, file));
890
+ }
891
+ }
892
+ }
893
+ async function indexDataContracts(files, index) {
894
+ for (const file of files) {
895
+ const text = await readFile4(file, "utf-8");
896
+ extractIds(text, "DATA").forEach((id) => record(index, id, file));
897
+ }
898
+ }
899
+ function record(index, id, file) {
900
+ index.ids.add(id);
901
+ const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
902
+ current.add(file);
903
+ index.idToFiles.set(id, current);
904
+ }
905
+
906
+ // src/core/validators/ids.ts
907
+ async function validateDefinedIds(root, config) {
908
+ const issues = [];
909
+ const specRoot = resolvePath(root, config, "specDir");
910
+ const scenarioRoot = resolvePath(root, config, "scenariosDir");
911
+ const specFiles = await collectSpecFiles(specRoot);
912
+ const scenarioFiles = await collectFiles(scenarioRoot, {
913
+ extensions: [".feature"]
914
+ });
915
+ const defined = /* @__PURE__ */ new Map();
916
+ await collectSpecDefinitionIds(specFiles, defined);
917
+ await collectScenarioDefinitionIds(scenarioFiles, defined);
918
+ const contractIndex = await buildContractIndex(root, config);
919
+ for (const [id, files] of contractIndex.idToFiles.entries()) {
920
+ for (const file of files) {
921
+ recordId(defined, id, file);
922
+ }
923
+ }
924
+ for (const [id, files] of defined.entries()) {
925
+ if (files.size <= 1) {
926
+ continue;
927
+ }
928
+ const sorted = Array.from(files).sort();
929
+ issues.push(
930
+ issue2(
931
+ "QFAI-ID-001",
932
+ `ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
933
+ "error",
934
+ sorted[0],
935
+ "id.duplicate"
936
+ )
937
+ );
938
+ }
939
+ return issues;
940
+ }
941
+ async function collectSpecDefinitionIds(files, out) {
942
+ for (const file of files) {
943
+ const text = await readFile5(file, "utf-8");
944
+ extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
945
+ extractIds(text, "BR").forEach((id) => recordId(out, id, file));
946
+ }
947
+ }
948
+ async function collectScenarioDefinitionIds(files, out) {
949
+ for (const file of files) {
950
+ const text = await readFile5(file, "utf-8");
951
+ extractIds(text, "SC").forEach((id) => recordId(out, id, file));
952
+ }
953
+ }
954
+ function recordId(out, id, file) {
955
+ const current = out.get(id) ?? /* @__PURE__ */ new Set();
956
+ current.add(file);
957
+ out.set(id, current);
958
+ }
959
+ function formatFileList(files, root) {
960
+ return files.map((file) => {
961
+ const relative = path6.relative(root, file);
962
+ return relative.length > 0 ? relative : file;
963
+ }).join(", ");
964
+ }
965
+ function issue2(code, message, severity, file, rule, refs) {
966
+ const issue6 = {
967
+ code,
968
+ severity,
969
+ message
970
+ };
971
+ if (file) {
972
+ issue6.file = file;
973
+ }
974
+ if (rule) {
975
+ issue6.rule = rule;
976
+ }
977
+ if (refs && refs.length > 0) {
978
+ issue6.refs = refs;
979
+ }
980
+ return issue6;
981
+ }
982
+
983
+ // src/core/validators/scenario.ts
984
+ import { readFile as readFile6 } from "fs/promises";
775
985
  var GIVEN_PATTERN = /\bGiven\b/;
776
986
  var WHEN_PATTERN = /\bWhen\b/;
777
987
  var THEN_PATTERN = /\bThen\b/;
@@ -782,7 +992,7 @@ async function validateScenarios(root, config) {
782
992
  });
783
993
  if (files.length === 0) {
784
994
  return [
785
- issue2(
995
+ issue3(
786
996
  "QFAI-SC-000",
787
997
  "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
788
998
  "info",
@@ -793,7 +1003,7 @@ async function validateScenarios(root, config) {
793
1003
  }
794
1004
  const issues = [];
795
1005
  for (const file of files) {
796
- const text = await readFile4(file, "utf-8");
1006
+ const text = await readFile6(file, "utf-8");
797
1007
  issues.push(...validateScenarioContent(text, file));
798
1008
  }
799
1009
  return issues;
@@ -806,12 +1016,13 @@ function validateScenarioContent(text, file) {
806
1016
  "SC",
807
1017
  "UI",
808
1018
  "API",
809
- "DATA"
1019
+ "DATA",
1020
+ "ADR"
810
1021
  ]);
811
1022
  if (invalidIds.length > 0) {
812
1023
  issues.push(
813
- issue2(
814
- "QFAI_ID_INVALID_FORMAT",
1024
+ issue3(
1025
+ "QFAI-ID-002",
815
1026
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
816
1027
  "error",
817
1028
  file,
@@ -823,7 +1034,7 @@ function validateScenarioContent(text, file) {
823
1034
  const scIds = extractIds(text, "SC");
824
1035
  if (scIds.length === 0) {
825
1036
  issues.push(
826
- issue2(
1037
+ issue3(
827
1038
  "QFAI-SC-001",
828
1039
  "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
829
1040
  "error",
@@ -835,7 +1046,7 @@ function validateScenarioContent(text, file) {
835
1046
  const specIds = extractIds(text, "SPEC");
836
1047
  if (specIds.length === 0) {
837
1048
  issues.push(
838
- issue2(
1049
+ issue3(
839
1050
  "QFAI-SC-002",
840
1051
  "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
841
1052
  "error",
@@ -847,7 +1058,7 @@ function validateScenarioContent(text, file) {
847
1058
  const brIds = extractIds(text, "BR");
848
1059
  if (brIds.length === 0) {
849
1060
  issues.push(
850
- issue2(
1061
+ issue3(
851
1062
  "QFAI-SC-003",
852
1063
  "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
853
1064
  "error",
@@ -868,7 +1079,7 @@ function validateScenarioContent(text, file) {
868
1079
  }
869
1080
  if (missingSteps.length > 0) {
870
1081
  issues.push(
871
- issue2(
1082
+ issue3(
872
1083
  "QFAI-SC-005",
873
1084
  `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
874
1085
  "warning",
@@ -879,34 +1090,35 @@ function validateScenarioContent(text, file) {
879
1090
  }
880
1091
  return issues;
881
1092
  }
882
- function issue2(code, message, severity, file, rule, refs) {
883
- const issue5 = {
1093
+ function issue3(code, message, severity, file, rule, refs) {
1094
+ const issue6 = {
884
1095
  code,
885
1096
  severity,
886
1097
  message
887
1098
  };
888
1099
  if (file) {
889
- issue5.file = file;
1100
+ issue6.file = file;
890
1101
  }
891
1102
  if (rule) {
892
- issue5.rule = rule;
1103
+ issue6.rule = rule;
893
1104
  }
894
1105
  if (refs && refs.length > 0) {
895
- issue5.refs = refs;
1106
+ issue6.refs = refs;
896
1107
  }
897
- return issue5;
1108
+ return issue6;
898
1109
  }
899
1110
 
900
1111
  // src/core/validators/spec.ts
901
- import { readFile as readFile5 } from "fs/promises";
1112
+ import { readFile as readFile7 } from "fs/promises";
902
1113
  async function validateSpecs(root, config) {
903
1114
  const specsRoot = resolvePath(root, config, "specDir");
904
1115
  const files = await collectSpecFiles(specsRoot);
905
1116
  if (files.length === 0) {
1117
+ const expected = "spec-0001-<slug>.md";
906
1118
  return [
907
- issue3(
1119
+ issue4(
908
1120
  "QFAI-SPEC-000",
909
- "Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1121
+ `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}`,
910
1122
  "info",
911
1123
  specsRoot,
912
1124
  "spec.files"
@@ -915,7 +1127,7 @@ async function validateSpecs(root, config) {
915
1127
  }
916
1128
  const issues = [];
917
1129
  for (const file of files) {
918
- const text = await readFile5(file, "utf-8");
1130
+ const text = await readFile7(file, "utf-8");
919
1131
  issues.push(
920
1132
  ...validateSpecContent(
921
1133
  text,
@@ -934,12 +1146,13 @@ function validateSpecContent(text, file, requiredSections) {
934
1146
  "SC",
935
1147
  "UI",
936
1148
  "API",
937
- "DATA"
1149
+ "DATA",
1150
+ "ADR"
938
1151
  ]);
939
1152
  if (invalidIds.length > 0) {
940
1153
  issues.push(
941
- issue3(
942
- "QFAI_ID_INVALID_FORMAT",
1154
+ issue4(
1155
+ "QFAI-ID-002",
943
1156
  `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
944
1157
  "error",
945
1158
  file,
@@ -951,7 +1164,7 @@ function validateSpecContent(text, file, requiredSections) {
951
1164
  const specIds = extractIds(text, "SPEC");
952
1165
  if (specIds.length === 0) {
953
1166
  issues.push(
954
- issue3(
1167
+ issue4(
955
1168
  "QFAI-SPEC-001",
956
1169
  "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
957
1170
  "error",
@@ -963,7 +1176,7 @@ function validateSpecContent(text, file, requiredSections) {
963
1176
  const brIds = extractIds(text, "BR");
964
1177
  if (brIds.length === 0) {
965
1178
  issues.push(
966
- issue3(
1179
+ issue4(
967
1180
  "QFAI-SPEC-002",
968
1181
  "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
969
1182
  "error",
@@ -975,7 +1188,7 @@ function validateSpecContent(text, file, requiredSections) {
975
1188
  const scIds = extractIds(text, "SC");
976
1189
  if (scIds.length > 0) {
977
1190
  issues.push(
978
- issue3(
1191
+ issue4(
979
1192
  "QFAI-SPEC-003",
980
1193
  "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
981
1194
  "warning",
@@ -988,7 +1201,7 @@ function validateSpecContent(text, file, requiredSections) {
988
1201
  for (const section of requiredSections) {
989
1202
  if (!text.includes(section)) {
990
1203
  issues.push(
991
- issue3(
1204
+ issue4(
992
1205
  "QFAI-SPEC-004",
993
1206
  `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
994
1207
  "error",
@@ -1000,26 +1213,26 @@ function validateSpecContent(text, file, requiredSections) {
1000
1213
  }
1001
1214
  return issues;
1002
1215
  }
1003
- function issue3(code, message, severity, file, rule, refs) {
1004
- const issue5 = {
1216
+ function issue4(code, message, severity, file, rule, refs) {
1217
+ const issue6 = {
1005
1218
  code,
1006
1219
  severity,
1007
1220
  message
1008
1221
  };
1009
1222
  if (file) {
1010
- issue5.file = file;
1223
+ issue6.file = file;
1011
1224
  }
1012
1225
  if (rule) {
1013
- issue5.rule = rule;
1226
+ issue6.rule = rule;
1014
1227
  }
1015
1228
  if (refs && refs.length > 0) {
1016
- issue5.refs = refs;
1229
+ issue6.refs = refs;
1017
1230
  }
1018
- return issue5;
1231
+ return issue6;
1019
1232
  }
1020
1233
 
1021
1234
  // src/core/validators/traceability.ts
1022
- import { readFile as readFile6 } from "fs/promises";
1235
+ import { readFile as readFile8 } from "fs/promises";
1023
1236
  async function validateTraceability(root, config) {
1024
1237
  const issues = [];
1025
1238
  const specsRoot = resolvePath(root, config, "specDir");
@@ -1035,36 +1248,141 @@ async function validateTraceability(root, config) {
1035
1248
  extensions: [".feature"]
1036
1249
  });
1037
1250
  const upstreamIds = /* @__PURE__ */ new Set();
1251
+ const specIds = /* @__PURE__ */ new Set();
1038
1252
  const brIdsInSpecs = /* @__PURE__ */ new Set();
1039
1253
  const brIdsInScenarios = /* @__PURE__ */ new Set();
1040
1254
  const scIdsInScenarios = /* @__PURE__ */ new Set();
1041
1255
  const scenarioContractIds = /* @__PURE__ */ new Set();
1042
1256
  const scWithContracts = /* @__PURE__ */ new Set();
1043
- for (const file of [...specFiles, ...decisionFiles]) {
1044
- const text = await readFile6(file, "utf-8");
1257
+ const specToBrIds = /* @__PURE__ */ new Map();
1258
+ const contractIndex = await buildContractIndex(root, config);
1259
+ const contractIds = contractIndex.ids;
1260
+ for (const file of specFiles) {
1261
+ const text = await readFile8(file, "utf-8");
1262
+ extractAllIds(text).forEach((id) => upstreamIds.add(id));
1263
+ const specIdsInFile = extractIds(text, "SPEC");
1264
+ specIdsInFile.forEach((id) => specIds.add(id));
1265
+ const brIds = extractIds(text, "BR");
1266
+ brIds.forEach((id) => brIdsInSpecs.add(id));
1267
+ const referencedContractIds = /* @__PURE__ */ new Set([
1268
+ ...extractIds(text, "UI"),
1269
+ ...extractIds(text, "API"),
1270
+ ...extractIds(text, "DATA")
1271
+ ]);
1272
+ const unknownContractIds = Array.from(referencedContractIds).filter(
1273
+ (id) => !contractIds.has(id)
1274
+ );
1275
+ if (unknownContractIds.length > 0) {
1276
+ issues.push(
1277
+ issue5(
1278
+ "QFAI-TRACE-009",
1279
+ `Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1280
+ ", "
1281
+ )}`,
1282
+ "error",
1283
+ file,
1284
+ "traceability.specContractExists",
1285
+ unknownContractIds
1286
+ )
1287
+ );
1288
+ }
1289
+ for (const specId of specIdsInFile) {
1290
+ const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
1291
+ brIds.forEach((id) => current.add(id));
1292
+ specToBrIds.set(specId, current);
1293
+ }
1294
+ }
1295
+ for (const file of decisionFiles) {
1296
+ const text = await readFile8(file, "utf-8");
1045
1297
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1046
- extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
1047
1298
  }
1048
1299
  for (const file of scenarioFiles) {
1049
- const text = await readFile6(file, "utf-8");
1300
+ const text = await readFile8(file, "utf-8");
1050
1301
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
1302
+ const specIdsInScenario = extractIds(text, "SPEC");
1051
1303
  const brIds = extractIds(text, "BR");
1052
- brIds.forEach((id) => brIdsInScenarios.add(id));
1053
1304
  const scIds = extractIds(text, "SC");
1054
- scIds.forEach((id) => scIdsInScenarios.add(id));
1055
- const contractIds = [
1305
+ const scenarioIds = [
1056
1306
  ...extractIds(text, "UI"),
1057
1307
  ...extractIds(text, "API"),
1058
1308
  ...extractIds(text, "DATA")
1059
1309
  ];
1060
- contractIds.forEach((id) => scenarioContractIds.add(id));
1061
- if (contractIds.length > 0) {
1310
+ brIds.forEach((id) => brIdsInScenarios.add(id));
1311
+ scIds.forEach((id) => scIdsInScenarios.add(id));
1312
+ scenarioIds.forEach((id) => scenarioContractIds.add(id));
1313
+ if (scenarioIds.length > 0) {
1062
1314
  scIds.forEach((id) => scWithContracts.add(id));
1063
1315
  }
1316
+ const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
1317
+ if (unknownSpecIds.length > 0) {
1318
+ issues.push(
1319
+ issue5(
1320
+ "QFAI-TRACE-005",
1321
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
1322
+ "error",
1323
+ file,
1324
+ "traceability.scenarioSpecExists",
1325
+ unknownSpecIds
1326
+ )
1327
+ );
1328
+ }
1329
+ const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
1330
+ if (unknownBrIds.length > 0) {
1331
+ issues.push(
1332
+ issue5(
1333
+ "QFAI-TRACE-006",
1334
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
1335
+ "error",
1336
+ file,
1337
+ "traceability.scenarioBrExists",
1338
+ unknownBrIds
1339
+ )
1340
+ );
1341
+ }
1342
+ const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
1343
+ if (unknownContractIds.length > 0) {
1344
+ issues.push(
1345
+ issue5(
1346
+ "QFAI-TRACE-008",
1347
+ `Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
1348
+ ", "
1349
+ )}`,
1350
+ config.validation.traceability.unknownContractIdSeverity,
1351
+ file,
1352
+ "traceability.scenarioContractExists",
1353
+ unknownContractIds
1354
+ )
1355
+ );
1356
+ }
1357
+ if (specIdsInScenario.length > 0) {
1358
+ const allowedBrIds = /* @__PURE__ */ new Set();
1359
+ for (const specId of specIdsInScenario) {
1360
+ const brIdsForSpec = specToBrIds.get(specId);
1361
+ if (!brIdsForSpec) {
1362
+ continue;
1363
+ }
1364
+ brIdsForSpec.forEach((id) => allowedBrIds.add(id));
1365
+ }
1366
+ const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
1367
+ if (invalidBrIds.length > 0) {
1368
+ issues.push(
1369
+ issue5(
1370
+ "QFAI-TRACE-007",
1371
+ `Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
1372
+ ", "
1373
+ )} (SPEC: ${specIdsInScenario.join(", ")})`,
1374
+ "error",
1375
+ file,
1376
+ "traceability.scenarioBrUnderSpec",
1377
+ invalidBrIds
1378
+ )
1379
+ );
1380
+ }
1381
+ }
1064
1382
  }
1065
1383
  if (upstreamIds.size === 0) {
1066
1384
  return [
1067
- issue4(
1385
+ issue5(
1068
1386
  "QFAI-TRACE-000",
1069
1387
  "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1070
1388
  "info",
@@ -1079,7 +1397,7 @@ async function validateTraceability(root, config) {
1079
1397
  );
1080
1398
  if (orphanBrIds.length > 0) {
1081
1399
  issues.push(
1082
- issue4(
1400
+ issue5(
1083
1401
  "QFAI_TRACE_BR_ORPHAN",
1084
1402
  `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1085
1403
  "error",
@@ -1096,7 +1414,7 @@ async function validateTraceability(root, config) {
1096
1414
  );
1097
1415
  if (scWithoutContracts.length > 0) {
1098
1416
  issues.push(
1099
- issue4(
1417
+ issue5(
1100
1418
  "QFAI_TRACE_SC_NO_CONTRACT",
1101
1419
  `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1102
1420
  ", "
@@ -1110,14 +1428,13 @@ async function validateTraceability(root, config) {
1110
1428
  }
1111
1429
  }
1112
1430
  if (!config.validation.traceability.allowOrphanContracts) {
1113
- const contractIds = await collectContractIds(root, config);
1114
1431
  if (contractIds.size > 0) {
1115
1432
  const orphanContracts = Array.from(contractIds).filter(
1116
1433
  (id) => !scenarioContractIds.has(id)
1117
1434
  );
1118
1435
  if (orphanContracts.length > 0) {
1119
1436
  issues.push(
1120
- issue4(
1437
+ issue5(
1121
1438
  "QFAI_CONTRACT_ORPHAN",
1122
1439
  `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1123
1440
  "error",
@@ -1134,27 +1451,6 @@ async function validateTraceability(root, config) {
1134
1451
  );
1135
1452
  return issues;
1136
1453
  }
1137
- async function collectContractIds(root, config) {
1138
- const contractIds = /* @__PURE__ */ new Set();
1139
- const uiRoot = resolvePath(root, config, "uiContractsDir");
1140
- const apiRoot = resolvePath(root, config, "apiContractsDir");
1141
- const dataRoot = resolvePath(root, config, "dataContractsDir");
1142
- const uiFiles = await collectUiContractFiles(uiRoot);
1143
- const apiFiles = await collectApiContractFiles(apiRoot);
1144
- const dataFiles = await collectDataContractFiles(dataRoot);
1145
- await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
1146
- await collectIdsFromFiles(apiFiles, ["API"], contractIds);
1147
- await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
1148
- return contractIds;
1149
- }
1150
- async function collectIdsFromFiles(files, prefixes, out) {
1151
- for (const file of files) {
1152
- const text = await readFile6(file, "utf-8");
1153
- for (const prefix of prefixes) {
1154
- extractIds(text, prefix).forEach((id) => out.add(id));
1155
- }
1156
- }
1157
- }
1158
1454
  async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1159
1455
  const issues = [];
1160
1456
  const codeFiles = await collectFiles(srcRoot, {
@@ -1166,7 +1462,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1166
1462
  const targetFiles = [...codeFiles, ...testFiles];
1167
1463
  if (targetFiles.length === 0) {
1168
1464
  issues.push(
1169
- issue4(
1465
+ issue5(
1170
1466
  "QFAI-TRACE-001",
1171
1467
  "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1172
1468
  "info",
@@ -1179,7 +1475,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1179
1475
  const pattern = buildIdPattern(Array.from(upstreamIds));
1180
1476
  let found = false;
1181
1477
  for (const file of targetFiles) {
1182
- const text = await readFile6(file, "utf-8");
1478
+ const text = await readFile8(file, "utf-8");
1183
1479
  if (pattern.test(text)) {
1184
1480
  found = true;
1185
1481
  break;
@@ -1187,7 +1483,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1187
1483
  }
1188
1484
  if (!found) {
1189
1485
  issues.push(
1190
- issue4(
1486
+ issue5(
1191
1487
  "QFAI-TRACE-002",
1192
1488
  "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1193
1489
  "warning",
@@ -1202,22 +1498,22 @@ function buildIdPattern(ids) {
1202
1498
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1203
1499
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
1204
1500
  }
1205
- function issue4(code, message, severity, file, rule, refs) {
1206
- const issue5 = {
1501
+ function issue5(code, message, severity, file, rule, refs) {
1502
+ const issue6 = {
1207
1503
  code,
1208
1504
  severity,
1209
1505
  message
1210
1506
  };
1211
1507
  if (file) {
1212
- issue5.file = file;
1508
+ issue6.file = file;
1213
1509
  }
1214
1510
  if (rule) {
1215
- issue5.rule = rule;
1511
+ issue6.rule = rule;
1216
1512
  }
1217
1513
  if (refs && refs.length > 0) {
1218
- issue5.refs = refs;
1514
+ issue6.refs = refs;
1219
1515
  }
1220
- return issue5;
1516
+ return issue6;
1221
1517
  }
1222
1518
 
1223
1519
  // src/core/validate.ts
@@ -1229,6 +1525,7 @@ async function validateProject(root, configResult) {
1229
1525
  ...await validateSpecs(root, config),
1230
1526
  ...await validateScenarios(root, config),
1231
1527
  ...await validateContracts(root, config),
1528
+ ...await validateDefinedIds(root, config),
1232
1529
  ...await validateTraceability(root, config)
1233
1530
  ];
1234
1531
  const toolVersion = await resolveToolVersion();
@@ -1241,8 +1538,8 @@ async function validateProject(root, configResult) {
1241
1538
  }
1242
1539
  function countIssues(issues) {
1243
1540
  return issues.reduce(
1244
- (acc, issue5) => {
1245
- acc[issue5.severity] += 1;
1541
+ (acc, issue6) => {
1542
+ acc[issue6.severity] += 1;
1246
1543
  return acc;
1247
1544
  },
1248
1545
  { info: 0, warning: 0, error: 0 }
@@ -1250,7 +1547,7 @@ function countIssues(issues) {
1250
1547
  }
1251
1548
 
1252
1549
  // src/core/report.ts
1253
- var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
1550
+ var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
1254
1551
  async function createReportData(root, validation, configResult) {
1255
1552
  const resolved = configResult ?? await loadConfig(root);
1256
1553
  const config = resolved.config;
@@ -1258,7 +1555,6 @@ async function createReportData(root, validation, configResult) {
1258
1555
  const specRoot = resolvePath(root, config, "specDir");
1259
1556
  const decisionsRoot = resolvePath(root, config, "decisionsDir");
1260
1557
  const scenariosRoot = resolvePath(root, config, "scenariosDir");
1261
- const rulesRoot = resolvePath(root, config, "rulesDir");
1262
1558
  const apiRoot = resolvePath(root, config, "apiContractsDir");
1263
1559
  const uiRoot = resolvePath(root, config, "uiContractsDir");
1264
1560
  const dbRoot = resolvePath(root, config, "dataContractsDir");
@@ -1271,7 +1567,6 @@ async function createReportData(root, validation, configResult) {
1271
1567
  const decisionFiles = await collectFiles(decisionsRoot, {
1272
1568
  extensions: [".md"]
1273
1569
  });
1274
- const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
1275
1570
  const {
1276
1571
  api: apiFiles,
1277
1572
  ui: uiFiles,
@@ -1281,7 +1576,6 @@ async function createReportData(root, validation, configResult) {
1281
1576
  ...specFiles,
1282
1577
  ...scenarioFiles,
1283
1578
  ...decisionFiles,
1284
- ...ruleFiles,
1285
1579
  ...apiFiles,
1286
1580
  ...uiFiles,
1287
1581
  ...dbFiles
@@ -1307,7 +1601,6 @@ async function createReportData(root, validation, configResult) {
1307
1601
  specs: specFiles.length,
1308
1602
  scenarios: scenarioFiles.length,
1309
1603
  decisions: decisionFiles.length,
1310
- rules: ruleFiles.length,
1311
1604
  contracts: {
1312
1605
  api: apiFiles.length,
1313
1606
  ui: uiFiles.length,
@@ -1342,7 +1635,6 @@ function formatReportMarkdown(data) {
1342
1635
  lines.push(`- specs: ${data.summary.specs}`);
1343
1636
  lines.push(`- scenarios: ${data.summary.scenarios}`);
1344
1637
  lines.push(`- decisions: ${data.summary.decisions}`);
1345
- lines.push(`- rules: ${data.summary.rules}`);
1346
1638
  lines.push(
1347
1639
  `- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
1348
1640
  );
@@ -1378,7 +1670,7 @@ function formatReportMarkdown(data) {
1378
1670
  lines.push("");
1379
1671
  lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
1380
1672
  const traceIssues = data.issues.filter(
1381
- (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
1673
+ (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-") || item.code === "QFAI_CONTRACT_ORPHAN"
1382
1674
  );
1383
1675
  if (traceIssues.length === 0) {
1384
1676
  lines.push("- (none)");
@@ -1418,8 +1710,8 @@ async function collectIds(files) {
1418
1710
  DATA: /* @__PURE__ */ new Set()
1419
1711
  };
1420
1712
  for (const file of files) {
1421
- const text = await readFile7(file, "utf-8");
1422
- for (const prefix of ID_PREFIXES) {
1713
+ const text = await readFile9(file, "utf-8");
1714
+ for (const prefix of ID_PREFIXES2) {
1423
1715
  const ids = extractIds(text, prefix);
1424
1716
  ids.forEach((id) => result[prefix].add(id));
1425
1717
  }
@@ -1436,7 +1728,7 @@ async function collectIds(files) {
1436
1728
  async function collectUpstreamIds(files) {
1437
1729
  const ids = /* @__PURE__ */ new Set();
1438
1730
  for (const file of files) {
1439
- const text = await readFile7(file, "utf-8");
1731
+ const text = await readFile9(file, "utf-8");
1440
1732
  extractAllIds(text).forEach((id) => ids.add(id));
1441
1733
  }
1442
1734
  return ids;
@@ -1457,7 +1749,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1457
1749
  }
1458
1750
  const pattern = buildIdPattern2(Array.from(upstreamIds));
1459
1751
  for (const file of targetFiles) {
1460
- const text = await readFile7(file, "utf-8");
1752
+ const text = await readFile9(file, "utf-8");
1461
1753
  if (pattern.test(text)) {
1462
1754
  return true;
1463
1755
  }
@@ -1479,20 +1771,20 @@ function toSortedArray(values) {
1479
1771
  }
1480
1772
  function buildHotspots(issues) {
1481
1773
  const map = /* @__PURE__ */ new Map();
1482
- for (const issue5 of issues) {
1483
- if (!issue5.file) {
1774
+ for (const issue6 of issues) {
1775
+ if (!issue6.file) {
1484
1776
  continue;
1485
1777
  }
1486
- const current = map.get(issue5.file) ?? {
1487
- file: issue5.file,
1778
+ const current = map.get(issue6.file) ?? {
1779
+ file: issue6.file,
1488
1780
  total: 0,
1489
1781
  error: 0,
1490
1782
  warning: 0,
1491
1783
  info: 0
1492
1784
  };
1493
1785
  current.total += 1;
1494
- current[issue5.severity] += 1;
1495
- map.set(issue5.file, current);
1786
+ current[issue6.severity] += 1;
1787
+ map.set(issue6.file, current);
1496
1788
  }
1497
1789
  return Array.from(map.values()).sort(
1498
1790
  (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
@@ -1513,6 +1805,7 @@ export {
1513
1805
  resolvePath,
1514
1806
  resolveToolVersion,
1515
1807
  validateContracts,
1808
+ validateDefinedIds,
1516
1809
  validateProject,
1517
1810
  validateScenarioContent,
1518
1811
  validateScenarios,