qfai 1.2.13 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +4 -4
  2. package/assets/init/.qfai/assistant/agents/atdd-api-implementer.md +47 -0
  3. package/assets/init/.qfai/assistant/agents/atdd-e2e-implementer.md +46 -0
  4. package/assets/init/.qfai/assistant/agents/atdd-integration-implementer.md +47 -0
  5. package/assets/init/.qfai/assistant/agents/doc-steward.md +38 -0
  6. package/assets/init/.qfai/assistant/agents/orchestrator.md +45 -0
  7. package/assets/init/.qfai/assistant/agents/researcher.md +1 -0
  8. package/assets/init/.qfai/assistant/agents/reviewer.md +43 -0
  9. package/assets/init/.qfai/assistant/agents/runtime-gatekeeper.md +2 -1
  10. package/assets/init/.qfai/assistant/agents/test-engineer.md +1 -1
  11. package/assets/init/.qfai/assistant/agents/test-volume-estimator.md +46 -0
  12. package/assets/init/.qfai/assistant/instructions/agent-selection.md +6 -0
  13. package/assets/init/.qfai/assistant/instructions/workflow.md +16 -0
  14. package/assets/init/.qfai/assistant/prompts/qfai-atdd.md +112 -20
  15. package/assets/init/.qfai/assistant/prompts/qfai-discuss.md +25 -0
  16. package/assets/init/.qfai/assistant/prompts/qfai-prototyping.md +30 -1
  17. package/assets/init/.qfai/assistant/prompts/qfai-require.md +24 -0
  18. package/assets/init/.qfai/assistant/prompts/qfai-spec.md +41 -0
  19. package/assets/init/.qfai/assistant/prompts/qfai-tdd-green.md +48 -12
  20. package/assets/init/.qfai/assistant/prompts/qfai-tdd-red.md +38 -2
  21. package/assets/init/.qfai/assistant/prompts/qfai-tdd-refactor.md +40 -3
  22. package/assets/init/.qfai/evidence/README.md +2 -1
  23. package/assets/init/.qfai/specs/README.md +17 -5
  24. package/assets/init/root/.claude/agents/atdd-api-implementer.md +17 -0
  25. package/assets/init/root/.claude/agents/atdd-e2e-implementer.md +17 -0
  26. package/assets/init/root/.claude/agents/atdd-integration-implementer.md +17 -0
  27. package/assets/init/root/.claude/agents/doc-steward.md +17 -0
  28. package/assets/init/root/.claude/agents/orchestrator.md +17 -0
  29. package/assets/init/root/.claude/agents/researcher.md +17 -0
  30. package/assets/init/root/.claude/agents/reviewer.md +17 -0
  31. package/assets/init/root/.claude/agents/test-volume-estimator.md +17 -0
  32. package/dist/cli/index.cjs +465 -55
  33. package/dist/cli/index.cjs.map +1 -1
  34. package/dist/cli/index.mjs +442 -32
  35. package/dist/cli/index.mjs.map +1 -1
  36. package/dist/index.cjs +415 -34
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +8 -3
  39. package/dist/index.d.ts +8 -3
  40. package/dist/index.mjs +398 -17
  41. package/dist/index.mjs.map +1 -1
  42. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1217,8 +1217,8 @@ function isValidId(value, prefix) {
1217
1217
  }
1218
1218
 
1219
1219
  // src/core/report.ts
1220
- var import_promises18 = require("fs/promises");
1221
- var import_node_path17 = __toESM(require("path"), 1);
1220
+ var import_promises19 = require("fs/promises");
1221
+ var import_node_path18 = __toESM(require("path"), 1);
1222
1222
 
1223
1223
  // src/core/contractIndex.ts
1224
1224
  var import_promises6 = require("fs/promises");
@@ -1952,8 +1952,8 @@ var import_promises8 = require("fs/promises");
1952
1952
  var import_node_path9 = __toESM(require("path"), 1);
1953
1953
  var import_node_url = require("url");
1954
1954
  async function resolveToolVersion() {
1955
- if ("1.2.13".length > 0) {
1956
- return "1.2.13";
1955
+ if ("1.3.0".length > 0) {
1956
+ return "1.3.0";
1957
1957
  }
1958
1958
  try {
1959
1959
  const packagePath = resolvePackageJsonPath();
@@ -2781,6 +2781,24 @@ async function validateCaseCatalogues(root, config) {
2781
2781
  // src/core/validators/delta.ts
2782
2782
  var import_promises11 = require("fs/promises");
2783
2783
  var import_node_path12 = __toESM(require("path"), 1);
2784
+ var CHANGE_TYPE_PRIMARY_RE = /^\s*[-*]?\s*change_type_primary\s*:\s*(.+)\s*$/im;
2785
+ var CHANGE_TYPE_TAGS_RE = /^\s*[-*]?\s*change_type_tags\s*:\s*(.*)\s*$/im;
2786
+ var DO_NOT_RE = /^\s*[-*]?\s*do_not\s*:/im;
2787
+ var TEMPTATION_RE = /^\s*[-*]?\s*temptation\s*:/im;
2788
+ var ALLOWED_CHANGE_TYPE_PRIMARY = /* @__PURE__ */ new Set([
2789
+ "initial",
2790
+ "behavior",
2791
+ "structural",
2792
+ "ops"
2793
+ ]);
2794
+ var ALLOWED_CHANGE_TYPE_TAGS = /* @__PURE__ */ new Set([
2795
+ "@ui",
2796
+ "@api",
2797
+ "@db",
2798
+ "@nfr",
2799
+ "@docs",
2800
+ "@test"
2801
+ ]);
2784
2802
  async function validateDeltas(root, config) {
2785
2803
  const specsRoot = resolvePath(root, config, "specsDir");
2786
2804
  const packs = await collectSpecPackDirs(specsRoot);
@@ -2842,10 +2860,8 @@ async function validateDeltas(root, config) {
2842
2860
  )
2843
2861
  );
2844
2862
  } else {
2845
- const hasRejected = /^\s*(?:[-*]\s*)?rejected\s*:/im.test(
2846
- decisionRecords.body
2847
- );
2848
- if (!hasRejected) {
2863
+ const rejectedBlocks = extractRejectedBlocks(decisionRecords.body);
2864
+ if (rejectedBlocks.length === 0) {
2849
2865
  issues.push(
2850
2866
  issue(
2851
2867
  "QFAI-DELTA-101",
@@ -2858,6 +2874,28 @@ async function validateDeltas(root, config) {
2858
2874
  "rejected \u3092\u6700\u4F4E1\u4EF6\u8A18\u8F09\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2859
2875
  )
2860
2876
  );
2877
+ } else {
2878
+ const hasDoNot = rejectedBlocks.some((block) => DO_NOT_RE.test(block));
2879
+ const hasTemptation = rejectedBlocks.some(
2880
+ (block) => TEMPTATION_RE.test(block)
2881
+ );
2882
+ if (!hasDoNot || !hasTemptation) {
2883
+ const missing = [];
2884
+ if (!hasDoNot) missing.push("do_not");
2885
+ if (!hasTemptation) missing.push("temptation");
2886
+ issues.push(
2887
+ issue(
2888
+ "QFAI-DELTA-204",
2889
+ `Decision Records \u306B ${missing.join(" / ")} \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`,
2890
+ "warning",
2891
+ deltaPath,
2892
+ "delta.rejectedDetails",
2893
+ void 0,
2894
+ "change",
2895
+ "rejected \u306B\u306F do_not / temptation \u3092\u6700\u4F4E1\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
2896
+ )
2897
+ );
2898
+ }
2861
2899
  }
2862
2900
  }
2863
2901
  if (changeLog && decisionRecords) {
@@ -2876,6 +2914,54 @@ async function validateDeltas(root, config) {
2876
2914
  );
2877
2915
  }
2878
2916
  }
2917
+ if (changeLog) {
2918
+ const blocks = extractChangeLogBlocks(text, changeLog);
2919
+ for (const block of blocks) {
2920
+ const primary = extractChangeTypePrimary(block);
2921
+ if (!primary) {
2922
+ issues.push(
2923
+ issue(
2924
+ "QFAI-DELTA-201",
2925
+ "Change Log \u306E CL \u30D6\u30ED\u30C3\u30AF\u306B change_type_primary \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
2926
+ "warning",
2927
+ deltaPath,
2928
+ "delta.changeTypePrimary",
2929
+ void 0,
2930
+ "change",
2931
+ "change_type_primary \u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2932
+ )
2933
+ );
2934
+ } else if (!isAllowedChangeTypePrimary(primary)) {
2935
+ issues.push(
2936
+ issue(
2937
+ "QFAI-DELTA-202",
2938
+ `change_type_primary \u304C\u4E0D\u6B63\u3067\u3059: ${primary}`,
2939
+ "warning",
2940
+ deltaPath,
2941
+ "delta.changeTypePrimary",
2942
+ void 0,
2943
+ "change",
2944
+ "change_type_primary \u306F Initial | Behavior | Structural | Ops \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2945
+ )
2946
+ );
2947
+ }
2948
+ const invalidTags = extractInvalidChangeTypeTags(block);
2949
+ if (invalidTags && invalidTags.length > 0) {
2950
+ issues.push(
2951
+ issue(
2952
+ "QFAI-DELTA-203",
2953
+ `change_type_tags \u306B\u7121\u52B9\u306A\u30BF\u30B0\u304C\u3042\u308A\u307E\u3059: ${invalidTags.join(", ")}`,
2954
+ "warning",
2955
+ deltaPath,
2956
+ "delta.changeTypeTags",
2957
+ void 0,
2958
+ "change",
2959
+ "change_type_tags \u306F @ui @api @db @nfr @docs @test \u306E\u307F\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2960
+ )
2961
+ );
2962
+ }
2963
+ }
2964
+ }
2879
2965
  }
2880
2966
  return issues;
2881
2967
  }
@@ -2888,6 +2974,98 @@ function findSection(sections, title) {
2888
2974
  }
2889
2975
  return null;
2890
2976
  }
2977
+ function extractChangeLogBlocks(text, changeLog) {
2978
+ const lines = text.split(/\r?\n/);
2979
+ const headings = parseHeadings(text).filter(
2980
+ (heading) => heading.level === 3 && heading.line >= changeLog.startLine && heading.line <= changeLog.endLine
2981
+ );
2982
+ if (headings.length === 0) {
2983
+ return [changeLog.body];
2984
+ }
2985
+ const blocks = [];
2986
+ for (let i = 0; i < headings.length; i += 1) {
2987
+ const current = headings[i];
2988
+ if (!current) continue;
2989
+ const next = headings[i + 1];
2990
+ const startLine = current.line + 1;
2991
+ const endLine = Math.min(
2992
+ changeLog.endLine,
2993
+ (next?.line ?? changeLog.endLine + 1) - 1
2994
+ );
2995
+ if (startLine > endLine) {
2996
+ blocks.push("");
2997
+ continue;
2998
+ }
2999
+ blocks.push(lines.slice(startLine - 1, endLine).join("\n"));
3000
+ }
3001
+ return blocks;
3002
+ }
3003
+ function extractChangeTypePrimary(block) {
3004
+ const match = block.match(CHANGE_TYPE_PRIMARY_RE);
3005
+ const value = match?.[1]?.trim() ?? "";
3006
+ return value.length > 0 ? value : null;
3007
+ }
3008
+ function extractRejectedBlocks(sectionBody) {
3009
+ const lines = sectionBody.split(/\r?\n/);
3010
+ const blocks = [];
3011
+ let currentIndent = null;
3012
+ let buffer = [];
3013
+ const flush = () => {
3014
+ if (currentIndent === null) {
3015
+ return;
3016
+ }
3017
+ blocks.push(buffer.join("\n"));
3018
+ buffer = [];
3019
+ currentIndent = null;
3020
+ };
3021
+ for (let i = 0; i < lines.length; i += 1) {
3022
+ const line = lines[i] ?? "";
3023
+ const match = line.match(/^(\s*)(?:[-*]\s*)?rejected\s*:(.*)$/i);
3024
+ if (match) {
3025
+ flush();
3026
+ const inlineValue = (match[2] ?? "").trim();
3027
+ if (inlineValue.length > 0) {
3028
+ blocks.push(inlineValue);
3029
+ currentIndent = null;
3030
+ } else {
3031
+ currentIndent = (match[1] ?? "").length;
3032
+ }
3033
+ continue;
3034
+ }
3035
+ if (currentIndent === null) {
3036
+ continue;
3037
+ }
3038
+ const indentMatch = line.match(/^(\s*)/);
3039
+ const indent = (indentMatch?.[1] ?? "").length;
3040
+ if (line.trim().length > 0 && indent <= currentIndent) {
3041
+ flush();
3042
+ i -= 1;
3043
+ continue;
3044
+ }
3045
+ buffer.push(line);
3046
+ }
3047
+ flush();
3048
+ return blocks;
3049
+ }
3050
+ function extractInvalidChangeTypeTags(block) {
3051
+ const match = block.match(CHANGE_TYPE_TAGS_RE);
3052
+ if (!match) {
3053
+ return null;
3054
+ }
3055
+ const raw = match[1]?.trim() ?? "";
3056
+ if (raw.length === 0) {
3057
+ return [];
3058
+ }
3059
+ const tags = raw.split(/[\s,]+/).map((tag) => tag.trim()).filter((tag) => tag.length > 0);
3060
+ const invalid = tags.filter((tag) => !isAllowedChangeTypeTag(tag));
3061
+ return invalid;
3062
+ }
3063
+ function isAllowedChangeTypePrimary(value) {
3064
+ return ALLOWED_CHANGE_TYPE_PRIMARY.has(value.trim().toLowerCase());
3065
+ }
3066
+ function isAllowedChangeTypeTag(value) {
3067
+ return ALLOWED_CHANGE_TYPE_TAGS.has(value.trim().toLowerCase());
3068
+ }
2891
3069
 
2892
3070
  // src/core/validators/ids.ts
2893
3071
  var import_promises12 = require("fs/promises");
@@ -3709,12 +3887,45 @@ function validateSpecContent(text, file, requiredSections) {
3709
3887
  return issues;
3710
3888
  }
3711
3889
 
3712
- // src/core/validators/traceability.ts
3890
+ // src/core/validators/atddLedger.ts
3713
3891
  var import_promises16 = require("fs/promises");
3892
+ var import_node_path17 = __toESM(require("path"), 1);
3893
+ async function validateAtddCoverageLedgers(root, config) {
3894
+ const specsRoot = resolvePath(root, config, "specsDir");
3895
+ const entries = await collectSpecEntries(specsRoot);
3896
+ if (entries.length === 0) {
3897
+ return [];
3898
+ }
3899
+ const issues = [];
3900
+ for (const entry of entries) {
3901
+ const ledgerPath = import_node_path17.default.join(entry.dir, "atdd", "coverage-ledger.md");
3902
+ try {
3903
+ await (0, import_promises16.access)(ledgerPath);
3904
+ } catch (error) {
3905
+ if (isMissingFileError2(error)) {
3906
+ issues.push(
3907
+ issue(
3908
+ "QFAI-ATDD-001",
3909
+ "ATDD coverage-ledger.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
3910
+ "warning",
3911
+ ledgerPath,
3912
+ "atddLedger.exists"
3913
+ )
3914
+ );
3915
+ continue;
3916
+ }
3917
+ throw error;
3918
+ }
3919
+ }
3920
+ return issues;
3921
+ }
3922
+
3923
+ // src/core/validators/traceability.ts
3924
+ var import_promises17 = require("fs/promises");
3714
3925
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
3715
3926
  var BR_TAG_RE2 = /^BR-\d{4}-\d{4}$/;
3716
3927
  var SAMPLE_LIMIT = 20;
3717
- async function validateTraceability(root, config) {
3928
+ async function validateTraceability(root, config, phase) {
3718
3929
  const issues = [];
3719
3930
  const specsRoot = resolvePath(root, config, "specsDir");
3720
3931
  const srcRoot = resolvePath(root, config, "srcDir");
@@ -3735,7 +3946,7 @@ async function validateTraceability(root, config) {
3735
3946
  const contractIndex = await buildContractIndex(root, config);
3736
3947
  const contractIds = contractIndex.ids;
3737
3948
  for (const file of specFiles) {
3738
- const text = await (0, import_promises16.readFile)(file, "utf-8");
3949
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
3739
3950
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3740
3951
  const parsed = parseSpec(text, file);
3741
3952
  if (parsed.specId) {
@@ -3808,7 +4019,7 @@ async function validateTraceability(root, config) {
3808
4019
  }
3809
4020
  }
3810
4021
  for (const file of scenarioFiles) {
3811
- const text = await (0, import_promises16.readFile)(file, "utf-8");
4022
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
3812
4023
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3813
4024
  const scenarioContractRefs = parseContractRefs(text, {
3814
4025
  allowCommentPrefix: true
@@ -4144,18 +4355,22 @@ async function validateTraceability(root, config) {
4144
4355
  );
4145
4356
  } else {
4146
4357
  if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
4358
+ const ignoredLayers = phase === "atdd" ? /* @__PURE__ */ new Set(["unit", "component"]) : /* @__PURE__ */ new Set();
4147
4359
  const enforcedLayers = /* @__PURE__ */ new Set();
4148
4360
  for (const [scId, refs] of scTestRefs.entries()) {
4149
4361
  if (!refs || refs.size === 0) {
4150
4362
  continue;
4151
4363
  }
4152
4364
  const layer = scIdToLayer.get(scId);
4153
- if (layer) {
4365
+ if (layer && !ignoredLayers.has(layer)) {
4154
4366
  enforcedLayers.add(layer);
4155
4367
  }
4156
4368
  }
4157
4369
  const deferredLayers = [];
4158
4370
  for (const [layer, scIds] of layerToScIds.entries()) {
4371
+ if (ignoredLayers.has(layer)) {
4372
+ continue;
4373
+ }
4159
4374
  const missing = Array.from(scIds).filter((id) => {
4160
4375
  const refs = scTestRefs.get(id);
4161
4376
  return !refs || refs.size === 0;
@@ -4244,7 +4459,7 @@ async function collectScenarioSpecInfo(entries) {
4244
4459
  for (const entry of entries) {
4245
4460
  let specText = "";
4246
4461
  try {
4247
- specText = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
4462
+ specText = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
4248
4463
  } catch {
4249
4464
  specText = "";
4250
4465
  }
@@ -4289,7 +4504,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
4289
4504
  const pattern = buildIdPattern(Array.from(upstreamIds));
4290
4505
  let found = false;
4291
4506
  for (const file of targetFiles) {
4292
- const text = await (0, import_promises16.readFile)(file, "utf-8");
4507
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
4293
4508
  if (pattern.test(text)) {
4294
4509
  found = true;
4295
4510
  break;
@@ -4314,9 +4529,139 @@ function buildIdPattern(ids) {
4314
4529
  }
4315
4530
 
4316
4531
  // src/core/validators/traceabilityMatrix.ts
4317
- var import_promises17 = require("fs/promises");
4532
+ var import_promises18 = require("fs/promises");
4533
+
4534
+ // src/core/traceabilityMatrix.ts
4535
+ var SC_ID_RE = /SC-\d{4}-\d{4}/g;
4536
+ function parseTraceabilityMatrixStatus(matrixText) {
4537
+ const lines = matrixText.split(/\r?\n/);
4538
+ for (let i = 0; i < lines.length; i += 1) {
4539
+ const headerLine = lines[i];
4540
+ const separatorLine = lines[i + 1];
4541
+ if (!headerLine || !separatorLine) {
4542
+ continue;
4543
+ }
4544
+ if (!headerLine.includes("|") || !separatorLine.includes("|")) {
4545
+ continue;
4546
+ }
4547
+ if (!isSeparatorRow(separatorLine)) {
4548
+ continue;
4549
+ }
4550
+ const headers = splitTableRow(headerLine);
4551
+ if (headers.length === 0) {
4552
+ continue;
4553
+ }
4554
+ const scIndex = findHeaderIndex(headers, ["SC", "Scenario"]);
4555
+ const statusIndex = findHeaderIndex(headers, ["Status", "status"]);
4556
+ if (scIndex === -1) {
4557
+ continue;
4558
+ }
4559
+ const statusBySc = /* @__PURE__ */ new Map();
4560
+ const invalidStatusValues = [];
4561
+ const hasStatusColumn = statusIndex !== -1;
4562
+ for (let row = i + 2; row < lines.length; row += 1) {
4563
+ const line = lines[row];
4564
+ if (!line || !line.includes("|")) {
4565
+ break;
4566
+ }
4567
+ if (isSeparatorRow(line)) {
4568
+ continue;
4569
+ }
4570
+ const cells = splitTableRow(line);
4571
+ const scCell = cells[scIndex] ?? "";
4572
+ const scIds = extractScIds(scCell);
4573
+ if (scIds.length === 0) {
4574
+ continue;
4575
+ }
4576
+ const rawStatus = hasStatusColumn ? cells[statusIndex] ?? "" : "";
4577
+ const normalizedStatus = normalizeStatus(rawStatus);
4578
+ if (rawStatus && !normalizedStatus) {
4579
+ invalidStatusValues.push(rawStatus);
4580
+ continue;
4581
+ }
4582
+ for (const scId of scIds) {
4583
+ const nextStatus = normalizedStatus ?? "implemented";
4584
+ const current = statusBySc.get(scId);
4585
+ if (current === "implemented") {
4586
+ continue;
4587
+ }
4588
+ if (nextStatus === "implemented") {
4589
+ statusBySc.set(scId, "implemented");
4590
+ } else if (!current) {
4591
+ statusBySc.set(scId, "planned");
4592
+ }
4593
+ }
4594
+ }
4595
+ return { statusBySc, invalidStatusValues, hasStatusColumn };
4596
+ }
4597
+ return {
4598
+ statusBySc: /* @__PURE__ */ new Map(),
4599
+ invalidStatusValues: [],
4600
+ hasStatusColumn: false
4601
+ };
4602
+ }
4603
+ function splitTableRow(line) {
4604
+ const normalized = normalizeTableRow(line);
4605
+ if (!normalized) {
4606
+ return [];
4607
+ }
4608
+ return normalized.split("|").map((cell) => cell.trim());
4609
+ }
4610
+ function findHeaderIndex(headers, targets) {
4611
+ const normalized = headers.map((header) => header.toLowerCase());
4612
+ for (const target of targets) {
4613
+ const index = normalized.indexOf(target.toLowerCase());
4614
+ if (index !== -1) {
4615
+ return index;
4616
+ }
4617
+ }
4618
+ return -1;
4619
+ }
4620
+ function extractScIds(text) {
4621
+ const matches = text.match(SC_ID_RE);
4622
+ if (!matches) {
4623
+ return [];
4624
+ }
4625
+ return Array.from(new Set(matches));
4626
+ }
4627
+ function normalizeStatus(value) {
4628
+ const normalized = value.trim().toLowerCase();
4629
+ if (!normalized) {
4630
+ return null;
4631
+ }
4632
+ if (normalized === "implemented") {
4633
+ return "implemented";
4634
+ }
4635
+ if (normalized === "planned") {
4636
+ return "planned";
4637
+ }
4638
+ return null;
4639
+ }
4640
+ function isSeparatorRow(line) {
4641
+ const normalized = normalizeTableRow(line);
4642
+ if (!normalized) {
4643
+ return false;
4644
+ }
4645
+ return /^[-\s:|]+$/.test(normalized.trim());
4646
+ }
4647
+ function normalizeTableRow(line) {
4648
+ const trimmed = line.trim();
4649
+ if (!trimmed.includes("|")) {
4650
+ return null;
4651
+ }
4652
+ let normalized = trimmed;
4653
+ if (normalized.startsWith("|")) {
4654
+ normalized = normalized.slice(1);
4655
+ }
4656
+ if (normalized.endsWith("|")) {
4657
+ normalized = normalized.slice(0, -1);
4658
+ }
4659
+ return normalized;
4660
+ }
4661
+
4662
+ // src/core/validators/traceabilityMatrix.ts
4318
4663
  var SC_TAG_RE5 = /^SC-\d{4}-\d{4}$/;
4319
- async function validateTraceabilityMatrices(root, config) {
4664
+ async function validateTraceabilityMatrices(root, config, phase) {
4320
4665
  const specsRoot = resolvePath(root, config, "specsDir");
4321
4666
  const entries = await collectSpecEntries(specsRoot);
4322
4667
  if (entries.length === 0) {
@@ -4326,7 +4671,7 @@ async function validateTraceabilityMatrices(root, config) {
4326
4671
  for (const entry of entries) {
4327
4672
  let matrixText;
4328
4673
  try {
4329
- matrixText = await (0, import_promises17.readFile)(entry.traceabilityMatrixPath, "utf-8");
4674
+ matrixText = await (0, import_promises18.readFile)(entry.traceabilityMatrixPath, "utf-8");
4330
4675
  } catch (error) {
4331
4676
  if (isMissingFileError2(error)) {
4332
4677
  issues.push(
@@ -4361,9 +4706,42 @@ async function validateTraceabilityMatrices(root, config) {
4361
4706
  )
4362
4707
  );
4363
4708
  }
4709
+ const statusResult = parseTraceabilityMatrixStatus(matrixText);
4710
+ if (statusResult.hasStatusColumn && statusResult.invalidStatusValues.length > 0) {
4711
+ const invalid = Array.from(new Set(statusResult.invalidStatusValues));
4712
+ issues.push(
4713
+ issue(
4714
+ "QFAI-RTM-004",
4715
+ `traceability-matrix \u306E status \u304C\u4E0D\u6B63\u3067\u3059: ${invalid.join(", ")}`,
4716
+ "error",
4717
+ entry.traceabilityMatrixPath,
4718
+ "traceabilityMatrix.status",
4719
+ invalid
4720
+ )
4721
+ );
4722
+ }
4723
+ const isStrictPhase = phase === "full" || phase === "tdd";
4724
+ if (statusResult.hasStatusColumn && isStrictPhase) {
4725
+ const planned = Array.from(statusResult.statusBySc.entries()).filter(([, status]) => status === "planned").map(([scId]) => scId);
4726
+ if (planned.length > 0) {
4727
+ const uniquePlanned = Array.from(new Set(planned));
4728
+ issues.push(
4729
+ issue(
4730
+ "QFAI-RTM-005",
4731
+ `traceability-matrix \u306B planned \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059: ${uniquePlanned.join(
4732
+ ", "
4733
+ )} (phase=${phase})`,
4734
+ "warning",
4735
+ entry.traceabilityMatrixPath,
4736
+ "traceabilityMatrix.statusPlanned",
4737
+ uniquePlanned
4738
+ )
4739
+ );
4740
+ }
4741
+ }
4364
4742
  let specText = "";
4365
4743
  try {
4366
- specText = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
4744
+ specText = await (0, import_promises18.readFile)(entry.specPath, "utf-8");
4367
4745
  } catch {
4368
4746
  specText = "";
4369
4747
  }
@@ -4374,13 +4752,13 @@ async function validateTraceabilityMatrices(root, config) {
4374
4752
  const acIds = new Set(specText ? extractIds(specText, "AC") : []);
4375
4753
  const caseIds = new Set(specText ? extractIds(specText, "CASE") : []);
4376
4754
  try {
4377
- const caseText = await (0, import_promises17.readFile)(entry.caseCataloguePath, "utf-8");
4755
+ const caseText = await (0, import_promises18.readFile)(entry.caseCataloguePath, "utf-8");
4378
4756
  extractIds(caseText, "CASE").forEach((id) => caseIds.add(id));
4379
4757
  } catch {
4380
4758
  }
4381
4759
  let scenarioText = "";
4382
4760
  try {
4383
- scenarioText = await (0, import_promises17.readFile)(entry.scenarioPath, "utf-8");
4761
+ scenarioText = await (0, import_promises18.readFile)(entry.scenarioPath, "utf-8");
4384
4762
  } catch {
4385
4763
  scenarioText = "";
4386
4764
  }
@@ -4451,20 +4829,22 @@ async function validateTraceabilityMatrices(root, config) {
4451
4829
  }
4452
4830
 
4453
4831
  // src/core/validate.ts
4454
- async function validateProject(root, configResult) {
4832
+ async function validateProject(root, configResult, options = {}) {
4455
4833
  const resolved = configResult ?? await loadConfig(root);
4456
4834
  const { config, issues: configIssues } = resolved;
4835
+ const phase = options.phase ?? "full";
4457
4836
  const issues = [
4458
4837
  ...configIssues,
4459
4838
  ...await validatePromptsIntegrity(root, config),
4460
4839
  ...await validateSpecs(root, config),
4461
4840
  ...await validateDeltas(root, config),
4462
4841
  ...await validateScenarios(root, config),
4842
+ ...phase === "atdd" ? await validateAtddCoverageLedgers(root, config) : [],
4463
4843
  ...await validateCaseCatalogues(root, config),
4464
4844
  ...await validateContracts(root, config),
4465
- ...await validateTraceabilityMatrices(root, config),
4845
+ ...await validateTraceabilityMatrices(root, config, phase),
4466
4846
  ...await validateDefinedIds(root, config),
4467
- ...await validateTraceability(root, config)
4847
+ ...await validateTraceability(root, config, phase)
4468
4848
  ];
4469
4849
  const specsRoot = resolvePath(root, config, "specsDir");
4470
4850
  const scenarioFiles = await collectScenarioFiles(specsRoot);
@@ -4478,6 +4858,7 @@ async function validateProject(root, configResult) {
4478
4858
  const toolVersion = await resolveToolVersion();
4479
4859
  return {
4480
4860
  toolVersion,
4861
+ phase,
4481
4862
  issues,
4482
4863
  counts: countIssues(issues),
4483
4864
  traceability: {
@@ -4512,15 +4893,15 @@ var REPORT_GUARDRAILS_MAX = 20;
4512
4893
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
4513
4894
  var SC_TAG_RE6 = /^SC-\d{4}-\d{4}$/;
4514
4895
  async function createReportData(root, validation, configResult) {
4515
- const resolvedRoot = import_node_path17.default.resolve(root);
4896
+ const resolvedRoot = import_node_path18.default.resolve(root);
4516
4897
  const resolved = configResult ?? await loadConfig(resolvedRoot);
4517
4898
  const config = resolved.config;
4518
4899
  const configPath = resolved.configPath;
4519
4900
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
4520
4901
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
4521
- const apiRoot = import_node_path17.default.join(contractsRoot, "api");
4522
- const uiRoot = import_node_path17.default.join(contractsRoot, "ui");
4523
- const dbRoot = import_node_path17.default.join(contractsRoot, "db");
4902
+ const apiRoot = import_node_path18.default.join(contractsRoot, "api");
4903
+ const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
4904
+ const dbRoot = import_node_path18.default.join(contractsRoot, "db");
4524
4905
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
4525
4906
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
4526
4907
  const specFiles = await collectSpecFiles(specsRoot);
@@ -5174,7 +5555,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
5174
5555
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
5175
5556
  }
5176
5557
  for (const file of specFiles) {
5177
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5558
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5178
5559
  const parsed = parseSpec(text, file);
5179
5560
  const specKey = parsed.specId;
5180
5561
  if (!specKey) {
@@ -5218,7 +5599,7 @@ async function collectIds(files) {
5218
5599
  THEMA: /* @__PURE__ */ new Set()
5219
5600
  };
5220
5601
  for (const file of files) {
5221
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5602
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5222
5603
  for (const prefix of ID_PREFIXES2) {
5223
5604
  const ids = extractIds(text, prefix);
5224
5605
  ids.forEach((id) => result[prefix].add(id));
@@ -5239,7 +5620,7 @@ async function collectIds(files) {
5239
5620
  async function collectUpstreamIds(files) {
5240
5621
  const ids = /* @__PURE__ */ new Set();
5241
5622
  for (const file of files) {
5242
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5623
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5243
5624
  extractAllIds(text).forEach((id) => ids.add(id));
5244
5625
  }
5245
5626
  return ids;
@@ -5260,7 +5641,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
5260
5641
  }
5261
5642
  const pattern = buildIdPattern2(Array.from(upstreamIds));
5262
5643
  for (const file of targetFiles) {
5263
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5644
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5264
5645
  if (pattern.test(text)) {
5265
5646
  return true;
5266
5647
  }
@@ -5381,7 +5762,7 @@ function normalizeScSources(root, sources) {
5381
5762
  async function countScenarios(scenarioFiles) {
5382
5763
  let total = 0;
5383
5764
  for (const file of scenarioFiles) {
5384
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5765
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5385
5766
  const { document, errors } = parseScenarioDocument(text, file);
5386
5767
  if (!document || errors.length > 0) {
5387
5768
  continue;
@@ -5412,7 +5793,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
5412
5793
  let totalScenarios = 0;
5413
5794
  let e2eCount = 0;
5414
5795
  for (const file of scenarioFiles) {
5415
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5796
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
5416
5797
  const { document, errors } = parseScenarioDocument(text, file);
5417
5798
  if (!document || errors.length > 0) {
5418
5799
  continue;