qfai 1.8.2 → 1.8.4

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 (39) hide show
  1. package/README.md +9 -4
  2. package/assets/init/.qfai/assistant/agents/product-experience-architect.md +2 -1
  3. package/assets/init/.qfai/assistant/skills/qfai-atdd/SKILL.md +4 -4
  4. package/assets/init/.qfai/assistant/skills/qfai-configure/SKILL.md +1 -1
  5. package/assets/init/.qfai/assistant/skills/qfai-discussion/SKILL.md +1 -0
  6. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/rcp_footer.md +1 -1
  7. package/assets/init/.qfai/assistant/skills/qfai-implement/SKILL.md +3 -1
  8. package/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +126 -62
  9. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/evidence-requirements.md +43 -12
  10. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/iteration-cycle.md +46 -14
  11. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/l1-review-guide.md +13 -12
  12. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/l2-review-guide.md +16 -10
  13. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/reviewer-gate.md +25 -4
  14. package/assets/init/.qfai/assistant/skills/qfai-sdd/SKILL.md +3 -3
  15. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/rcp_footer.md +1 -1
  16. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-quality-gate.md +1 -1
  17. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/absorption-policy.sample.yaml +7 -0
  18. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/evaluation-rubric.sample.yaml +22 -3
  19. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/evaluator-calibration.sample.yaml +6 -0
  20. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/exploration-brief.sample.yaml +9 -0
  21. package/assets/init/.qfai/assistant/skills/qfai-verify/SKILL.md +6 -6
  22. package/assets/init/.qfai/contracts/design/README.md +6 -1
  23. package/assets/init/.qfai/contracts/ui/README.md +3 -3
  24. package/assets/init/.qfai/discussion/README.md +14 -9
  25. package/assets/init/.qfai/evidence/README.md +66 -46
  26. package/assets/init/root/.github/workflows/qfai-validate.yml +39 -0
  27. package/assets/init/root/qfai.config.yaml +1 -2
  28. package/dist/cli/index.cjs +8787 -5641
  29. package/dist/cli/index.cjs.map +1 -1
  30. package/dist/cli/index.mjs +5993 -2847
  31. package/dist/cli/index.mjs.map +1 -1
  32. package/dist/index.cjs +2677 -702
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +102 -23
  35. package/dist/index.d.ts +102 -23
  36. package/dist/index.mjs +2651 -675
  37. package/dist/index.mjs.map +1 -1
  38. package/package.json +4 -2
  39. package/assets/scripts/capture-screenshots.js +0 -128
package/dist/index.cjs CHANGED
@@ -453,6 +453,7 @@ __export(src_exports, {
453
453
  DISCUSSION_NON_UI_SURFACES: () => DISCUSSION_NON_UI_SURFACES,
454
454
  DISCUSSION_UI_BEARING_SURFACES: () => DISCUSSION_UI_BEARING_SURFACES,
455
455
  ID_PREFIXES: () => ID_PREFIXES,
456
+ PROTOTYPING_MAX_CYCLES: () => PROTOTYPING_MAX_CYCLES,
456
457
  PROTOTYPING_MAX_ITERATIONS: () => PROTOTYPING_MAX_ITERATIONS,
457
458
  PROTOTYPING_MODES: () => PROTOTYPING_MODES,
458
459
  PROTOTYPING_SUPPORTED_SURFACES: () => PROTOTYPING_SUPPORTED_SURFACES,
@@ -461,8 +462,6 @@ __export(src_exports, {
461
462
  appendIteration: () => appendIteration,
462
463
  checkDecisionGuardrails: () => checkDecisionGuardrails,
463
464
  computeTerminationReason: () => computeTerminationReason,
464
- createPlaywrightBrowserQaProvider: () => createPlaywrightBrowserQaProvider,
465
- createPlaywrightRenderAdapter: () => createPlaywrightRenderAdapter,
466
465
  createReportData: () => createReportData,
467
466
  defaultConfig: () => defaultConfig,
468
467
  derivePrototypingObligations: () => derivePrototypingObligations,
@@ -570,7 +569,8 @@ var defaultConfig = {
570
569
  requireLayerTags: false,
571
570
  requireSizeTags: false,
572
571
  maxE2eScenarioRatio: null,
573
- maxE2eScenarioCount: null
572
+ maxE2eScenarioCount: null,
573
+ forbidTestTodoStubs: true
574
574
  },
575
575
  traceability: {
576
576
  brMustHaveSc: true,
@@ -591,8 +591,7 @@ var defaultConfig = {
591
591
  },
592
592
  execution: {
593
593
  targetUrl: null,
594
- browserProvider: "playwright",
595
- renderProvider: "playwright"
594
+ browserTool: "playwright-cli"
596
595
  }
597
596
  }
598
597
  };
@@ -783,6 +782,13 @@ function normalizeValidation(raw, configPath, issues) {
783
782
  "validation.testStrategy.maxE2eScenarioCount",
784
783
  configPath,
785
784
  issues
785
+ ),
786
+ forbidTestTodoStubs: readBoolean(
787
+ testStrategyRaw?.forbidTestTodoStubs,
788
+ base.testStrategy.forbidTestTodoStubs,
789
+ "validation.testStrategy.forbidTestTodoStubs",
790
+ configPath,
791
+ issues
786
792
  )
787
793
  },
788
794
  traceability: {
@@ -867,14 +873,40 @@ function normalizePrototyping(raw, configPath, issues) {
867
873
  }
868
874
  const calibration = normalizePrototypingCalibration(raw.calibration, configPath, issues);
869
875
  const execution = normalizePrototypingExecution(raw.execution, configPath, issues);
870
- if (!calibration && !execution) {
876
+ const primarySpecId = normalizePrimarySpecId(raw.primarySpecId, configPath, issues);
877
+ if (!calibration && !execution && primarySpecId === void 0) {
871
878
  return void 0;
872
879
  }
873
880
  return {
874
881
  ...calibration ? { calibration } : {},
875
- ...execution ? { execution } : {}
882
+ ...execution ? { execution } : {},
883
+ ...primarySpecId !== void 0 ? { primarySpecId } : {}
876
884
  };
877
885
  }
886
+ function normalizePrimarySpecId(raw, configPath, issues) {
887
+ if (raw === void 0 || raw === null) {
888
+ return void 0;
889
+ }
890
+ if (typeof raw !== "string") {
891
+ issues.push(
892
+ configIssue(
893
+ configPath,
894
+ 'prototyping.primarySpecId \u306F4\u6841\u306E\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044 (\u4F8B: "0012")\u3002'
895
+ )
896
+ );
897
+ return void 0;
898
+ }
899
+ if (!/^\d{4}$/.test(raw)) {
900
+ issues.push(
901
+ configIssue(
902
+ configPath,
903
+ `prototyping.primarySpecId \u306F4\u6841\u306E\u6570\u5B57\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 (got "${raw}")\u3002`
904
+ )
905
+ );
906
+ return void 0;
907
+ }
908
+ return raw;
909
+ }
878
910
  function normalizePrototypingCalibration(raw, configPath, issues) {
879
911
  const base = defaultConfig.prototyping?.calibration;
880
912
  if (raw === void 0 || raw === null) {
@@ -908,6 +940,30 @@ function normalizePrototypingExecution(raw, configPath, issues) {
908
940
  );
909
941
  return base ? { ...base } : void 0;
910
942
  }
943
+ for (const legacyKey of ["browserProvider", "renderProvider"]) {
944
+ if (raw[legacyKey] !== void 0) {
945
+ issues.push(
946
+ configIssue(
947
+ configPath,
948
+ `prototyping.execution.${legacyKey} \u306F\u5EC3\u6B62\u3055\u308C\u307E\u3057\u305F (spec-0012)\u3002 prototyping.execution.browserTool: playwright-cli \u306B\u7F6E\u304D\u63DB\u3048\u3066\u304F\u3060\u3055\u3044\u3002`
949
+ )
950
+ );
951
+ }
952
+ }
953
+ const browserToolRaw = raw.browserTool;
954
+ let browserTool = "playwright-cli";
955
+ if (browserToolRaw !== void 0) {
956
+ if (browserToolRaw !== "playwright-cli") {
957
+ issues.push(
958
+ configIssue(
959
+ configPath,
960
+ `prototyping.execution.browserTool \u306F "playwright-cli" \u306E\u307F\u6709\u52B9\u3067\u3059 (spec-0012)\u3002 \u53D7\u3051\u53D6\u3063\u305F\u5024: ${JSON.stringify(browserToolRaw)}`
961
+ )
962
+ );
963
+ } else {
964
+ browserTool = browserToolRaw;
965
+ }
966
+ }
911
967
  return {
912
968
  targetUrl: raw.targetUrl === null ? null : readOptionalString(
913
969
  raw.targetUrl,
@@ -915,20 +971,7 @@ function normalizePrototypingExecution(raw, configPath, issues) {
915
971
  configPath,
916
972
  issues
917
973
  ) ?? null,
918
- browserProvider: readString(
919
- raw.browserProvider,
920
- base?.browserProvider ?? "playwright",
921
- "prototyping.execution.browserProvider",
922
- configPath,
923
- issues
924
- ),
925
- renderProvider: readString(
926
- raw.renderProvider,
927
- base?.renderProvider ?? "playwright",
928
- "prototyping.execution.renderProvider",
929
- configPath,
930
- issues
931
- )
974
+ browserTool
932
975
  };
933
976
  }
934
977
  function validateObsoleteCalibrationFields(raw, configPath, issues) {
@@ -3123,11 +3166,12 @@ function isValidId(value, prefix) {
3123
3166
  init_surface();
3124
3167
  var PROTOTYPING_MODES = ["low-cost", "standard", "full-harness"];
3125
3168
  var DEFAULT_PROTOTYPING_MODE = "standard";
3126
- var PROTOTYPING_MAX_ITERATIONS = {
3169
+ var PROTOTYPING_MAX_CYCLES = {
3127
3170
  "low-cost": 1,
3128
3171
  standard: 3,
3129
3172
  "full-harness": 20
3130
3173
  };
3174
+ var PROTOTYPING_MAX_ITERATIONS = PROTOTYPING_MAX_CYCLES;
3131
3175
  var PROTOTYPING_SUPPORTED_SURFACES = ["web", "mobile", "desktop", "mixed"];
3132
3176
  var VALID_MODE_SET = new Set(PROTOTYPING_MODES);
3133
3177
  var VALID_SURFACE_SET = new Set(CANONICAL_PROTOTYPING_SURFACES);
@@ -3157,14 +3201,24 @@ function resolvePrototypingMode(requested) {
3157
3201
  };
3158
3202
  }
3159
3203
  function derivePrototypingObligations(input) {
3160
- const isFullHarness = input.effectiveMode === "full-harness";
3204
+ const maxCycles = PROTOTYPING_MAX_CYCLES[input.effectiveMode];
3161
3205
  return {
3162
- requireRuntimeGate: isFullHarness,
3163
- requireUiFidelity: isFullHarness,
3164
- requireRenderBundle: isFullHarness,
3165
- requireBrowserQaBundle: isFullHarness,
3206
+ browserTool: "playwright-cli",
3207
+ requireExecutionPlan: true,
3208
+ requirePlaywrightEvidence: true,
3209
+ requireReviewBundle: true,
3210
+ requireEvaluatorReview: true,
3211
+ requireBestOfHistory: true,
3212
+ requireBreakthrough: true,
3213
+ requireIndependentReviewerGate: true,
3214
+ requirePerfect100Completion: true,
3215
+ requireRuntimeGate: true,
3216
+ requireUiFidelity: true,
3217
+ requireRenderBundle: true,
3218
+ requireBrowserQaBundle: true,
3166
3219
  requireIterations: true,
3167
- maxIterations: PROTOTYPING_MAX_ITERATIONS[input.effectiveMode],
3220
+ maxCycles,
3221
+ maxIterations: maxCycles,
3168
3222
  validCombination: true
3169
3223
  };
3170
3224
  }
@@ -3631,8 +3685,8 @@ async function readSafe4(filePath) {
3631
3685
  }
3632
3686
 
3633
3687
  // src/core/report.ts
3634
- var import_promises58 = require("fs/promises");
3635
- var import_node_path70 = __toESM(require("path"), 1);
3688
+ var import_promises67 = require("fs/promises");
3689
+ var import_node_path79 = __toESM(require("path"), 1);
3636
3690
 
3637
3691
  // src/core/contractIndex.ts
3638
3692
  var import_promises13 = require("fs/promises");
@@ -3674,6 +3728,31 @@ function record(index, id, file) {
3674
3728
  index.idToFiles.set(id, current);
3675
3729
  }
3676
3730
 
3731
+ // src/core/prototyping/round.ts
3732
+ var EXPLORATION_ROUNDS = ["r5", "r3", "r2", "r1"];
3733
+ var ABSORPTION_ROUNDS = ["r3", "r2", "r1"];
3734
+ var ROUND_SURVIVOR_COUNT = {
3735
+ r5: 5,
3736
+ r3: 3,
3737
+ r2: 2,
3738
+ r1: 1
3739
+ };
3740
+ function isExplorationRound(value) {
3741
+ return typeof value === "string" && EXPLORATION_ROUNDS.includes(value);
3742
+ }
3743
+ function prototypingRoundsDir() {
3744
+ return ".qfai/evidence/prototyping/rounds";
3745
+ }
3746
+ function roundDir(round) {
3747
+ return `${prototypingRoundsDir()}/${round}`;
3748
+ }
3749
+ function roundReviewBundlePath(round) {
3750
+ return `${roundDir(round)}/review-bundle.json`;
3751
+ }
3752
+ function roundEvaluatorReviewsDir(round) {
3753
+ return `${roundDir(round)}/evaluator-reviews`;
3754
+ }
3755
+
3677
3756
  // src/core/specPackIds.ts
3678
3757
  var STRICT_ID_PATTERNS2 = {
3679
3758
  OBJ: /\bOBJ-\d+\b/g,
@@ -4386,15 +4465,15 @@ function asTagArray(value) {
4386
4465
  }
4387
4466
 
4388
4467
  // src/core/validate.ts
4389
- var import_node_path69 = __toESM(require("path"), 1);
4468
+ var import_node_path78 = __toESM(require("path"), 1);
4390
4469
 
4391
4470
  // src/core/version.ts
4392
4471
  var import_promises14 = require("fs/promises");
4393
4472
  var import_node_path13 = __toESM(require("path"), 1);
4394
4473
  var import_node_url = require("url");
4395
4474
  async function resolveToolVersion() {
4396
- if ("1.8.2".length > 0) {
4397
- return "1.8.2";
4475
+ if ("1.8.4".length > 0) {
4476
+ return "1.8.4";
4398
4477
  }
4399
4478
  try {
4400
4479
  const packagePath = resolvePackageJsonPath();
@@ -7325,7 +7404,7 @@ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
7325
7404
  var US_ID_RE2 = /\bUS-\d{4}-\d{4}\b/g;
7326
7405
  var AC_ID_RE3 = /\bAC-\d{4}-\d{4}\b/g;
7327
7406
  var DOWNSTREAM_ID_RE = /\b(?:US|AC|BR|SC|CASE)-\d{4}-\d{4}\b/g;
7328
- async function validateTraceability(root, config, phase) {
7407
+ async function validateTraceability(root, config, options) {
7329
7408
  const issues = [];
7330
7409
  const specsRoot = resolvePath(root, config, "specsDir");
7331
7410
  const entries = await collectSpecEntries(specsRoot);
@@ -7401,7 +7480,7 @@ async function validateTraceability(root, config, phase) {
7401
7480
  issues.push(...validateLayeredEdges(entry, edgeData));
7402
7481
  issues.push(...validateLayeredHeuristics(entry, edgeData));
7403
7482
  }
7404
- if (phase !== "refinement" && allLayeredScIds.size > 0) {
7483
+ if (options.includeCodeReferences && allLayeredScIds.size > 0) {
7405
7484
  issues.push(
7406
7485
  ...await validateLayeredScCodeReferences(root, config, allLayeredScIds, specsRoot)
7407
7486
  );
@@ -10181,8 +10260,109 @@ function normalizeOptionalFragment(fragment, originalRef) {
10181
10260
  return normalized;
10182
10261
  }
10183
10262
 
10263
+ // src/core/prototyping/candidate.ts
10264
+ var CANDIDATE_ID_PATTERN = /^c[1-9]\d*$/;
10265
+ function isCandidateId(value) {
10266
+ return typeof value === "string" && CANDIDATE_ID_PATTERN.test(value);
10267
+ }
10268
+
10184
10269
  // src/core/validators/prototypingEvidence.ts
10185
10270
  init_utils();
10271
+
10272
+ // src/core/validators/prototyping/modeInvariant.ts
10273
+ init_utils();
10274
+ function isRecord5(v) {
10275
+ return typeof v === "object" && v !== null && !Array.isArray(v);
10276
+ }
10277
+ function detectMode(raw) {
10278
+ if (!isRecord5(raw)) return null;
10279
+ if (typeof raw.mode === "string" && isValidPrototypingMode(raw.mode)) {
10280
+ return { mode: raw.mode, source: "string" };
10281
+ }
10282
+ if (isRecord5(raw.mode) && typeof raw.mode.effective === "string") {
10283
+ const effective = raw.mode.effective;
10284
+ if (isValidPrototypingMode(effective)) {
10285
+ return { mode: effective, source: "object" };
10286
+ }
10287
+ }
10288
+ return null;
10289
+ }
10290
+ function validateModeInvariant(raw, evidencePathForIssue = ".qfai/evidence/prototyping.json") {
10291
+ if (!isRecord5(raw)) {
10292
+ return [];
10293
+ }
10294
+ const detected = detectMode(raw);
10295
+ if (!detected) {
10296
+ return [];
10297
+ }
10298
+ const issues = [];
10299
+ const expectedMaxCycles = PROTOTYPING_MAX_CYCLES[detected.mode];
10300
+ if (raw.maxCycles !== void 0 && raw.maxIterations !== void 0 && raw.maxCycles !== raw.maxIterations) {
10301
+ issues.push(
10302
+ issue(
10303
+ "QFAI-PROT-MODE-001",
10304
+ `prototyping.json declares both maxCycles and maxIterations with conflicting values (maxCycles=${JSON.stringify(raw.maxCycles)}, maxIterations=${JSON.stringify(raw.maxIterations)}). They must agree (maxIterations is a deprecated alias).`,
10305
+ "error",
10306
+ evidencePathForIssue,
10307
+ "prototyping.modeInvariant.maxCyclesAlias",
10308
+ [JSON.stringify(raw.maxCycles), JSON.stringify(raw.maxIterations)],
10309
+ "canonical",
10310
+ "Remove the redundant key or align both values; prefer maxCycles (spec-0017 REQ-0001)."
10311
+ )
10312
+ );
10313
+ }
10314
+ const maxCyclesRaw = raw.maxCycles ?? raw.maxIterations;
10315
+ if (maxCyclesRaw !== void 0) {
10316
+ if (typeof maxCyclesRaw !== "number" || !Number.isInteger(maxCyclesRaw) || maxCyclesRaw !== expectedMaxCycles) {
10317
+ const maxCyclesDisplay = JSON.stringify(maxCyclesRaw);
10318
+ issues.push(
10319
+ issue(
10320
+ "QFAI-PROT-MODE-001",
10321
+ `Mode differences are limited to maxCycles only. Expected maxCycles=${expectedMaxCycles} for mode=${detected.mode}, got ${maxCyclesDisplay}.`,
10322
+ "error",
10323
+ evidencePathForIssue,
10324
+ "prototyping.modeInvariant",
10325
+ [detected.mode, String(expectedMaxCycles), maxCyclesDisplay],
10326
+ "canonical",
10327
+ `Set prototyping.json maxCycles to ${expectedMaxCycles} to match PROTOTYPING_MAX_CYCLES[${detected.mode}], or switch the mode if a different budget is desired (spec-0017 REQ-0001).`
10328
+ )
10329
+ );
10330
+ }
10331
+ }
10332
+ if (raw.browserTool !== void 0) {
10333
+ if (raw.browserTool !== "playwright-cli") {
10334
+ const browserToolDisplay = JSON.stringify(raw.browserTool);
10335
+ issues.push(
10336
+ issue(
10337
+ "QFAI-PROT-MODE-001",
10338
+ `browserTool must be "playwright-cli" (spec-0017 REQ-0002). Got: ${browserToolDisplay}`,
10339
+ "error",
10340
+ evidencePathForIssue,
10341
+ "prototyping.modeInvariant.browserTool",
10342
+ [browserToolDisplay],
10343
+ "canonical",
10344
+ 'Set prototyping.json browserTool to "playwright-cli". Playwright MCP and legacy providers are not supported in the standard harness.'
10345
+ )
10346
+ );
10347
+ }
10348
+ }
10349
+ return issues;
10350
+ }
10351
+
10352
+ // src/core/validators/prototypingEvidence.ts
10353
+ var VALID_ITERATION_KIND_SET = /* @__PURE__ */ new Set(["explore", "remix", "select", "polish", "branch"]);
10354
+ var VALID_PHASE_SET = /* @__PURE__ */ new Set([
10355
+ "planning",
10356
+ "explore",
10357
+ "remix",
10358
+ "select",
10359
+ "polish",
10360
+ "breakthrough",
10361
+ "reviewer_gate",
10362
+ "completed"
10363
+ ]);
10364
+ var REQUIRED_POLISH_CHECKS = ["critique", "fix", "recapture", "rereview", "breakthrough"];
10365
+ var LEGACY_PASS_95_FIELD = "allItemsPass" + String(95);
10186
10366
  var VALID_MODE_SOURCE_SET = /* @__PURE__ */ new Set([
10187
10367
  "explicit-request",
10188
10368
  "system-default",
@@ -10251,6 +10431,24 @@ async function validatePrototypingEvidence(root, config) {
10251
10431
  return issues;
10252
10432
  }
10253
10433
  const obligations = surface && isSupportedPrototypingSurface(surface) ? derivePrototypingObligations({ surface, effectiveMode: mode.effective }) : void 0;
10434
+ issues.push(
10435
+ ...validateModeInvariant(record2, import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"))
10436
+ );
10437
+ const isV2Record = record2.schemaVersion === "2.0" || record2.rounds !== void 0 || record2.polishCycles !== void 0;
10438
+ if (isV2Record) {
10439
+ issues.push(...validateV2Lifecycle(root, evidencePath, record2, mode, obligations));
10440
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10441
+ issues.push(
10442
+ makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10443
+ );
10444
+ }
10445
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10446
+ issues.push(
10447
+ makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10448
+ );
10449
+ }
10450
+ return issues;
10451
+ }
10254
10452
  const iterations = normalizeIterations(record2.iterations);
10255
10453
  if (!iterations || iterations.length === 0) {
10256
10454
  issues.push(
@@ -10281,10 +10479,14 @@ async function validatePrototypingEvidence(root, config) {
10281
10479
  )
10282
10480
  );
10283
10481
  }
10284
- let foundPass95 = false;
10482
+ let latestPerfect100 = false;
10483
+ let latestIterationKind = null;
10484
+ let completedPolishCount = 0;
10485
+ let polishWithBreakthroughCheck = false;
10486
+ let hasLegacyPass95CompletionMarker = false;
10285
10487
  for (const [index, candidate] of iterations.entries()) {
10286
10488
  const prefix = `iterations[${index}]`;
10287
- if (!isRecord5(candidate)) {
10489
+ if (!isRecord6(candidate)) {
10288
10490
  issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10289
10491
  continue;
10290
10492
  }
@@ -10295,12 +10497,43 @@ async function validatePrototypingEvidence(root, config) {
10295
10497
  makeSchemaIssue(root, evidencePath, `${prefix}.iteration must be a positive integer.`)
10296
10498
  );
10297
10499
  }
10298
- if (typeof iteration.allItemsPass95 !== "boolean") {
10500
+ if (typeof iteration.allReviewerAxesPerfect100 !== "boolean") {
10299
10501
  issues.push(
10300
- makeSchemaIssue(root, evidencePath, `${prefix}.allItemsPass95 must be a boolean.`)
10502
+ makeSchemaIssue(
10503
+ root,
10504
+ evidencePath,
10505
+ `${prefix}.allReviewerAxesPerfect100 must be a boolean.`
10506
+ )
10301
10507
  );
10302
- } else if (iteration.allItemsPass95) {
10303
- foundPass95 = true;
10508
+ } else if (iteration.allReviewerAxesPerfect100) {
10509
+ latestPerfect100 = true;
10510
+ } else {
10511
+ latestPerfect100 = false;
10512
+ }
10513
+ if (iteration[LEGACY_PASS_95_FIELD] === true) {
10514
+ hasLegacyPass95CompletionMarker = true;
10515
+ }
10516
+ if (iteration.kind !== void 0) {
10517
+ if (typeof iteration.kind !== "string" || !VALID_ITERATION_KIND_SET.has(iteration.kind)) {
10518
+ issues.push(
10519
+ issue(
10520
+ "QFAI-PROT-285",
10521
+ `${prefix}.kind must be one of explore|remix|select|polish|branch.`,
10522
+ "error",
10523
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10524
+ "prototypingEvidence.phase",
10525
+ void 0,
10526
+ "canonical",
10527
+ "iteration kind \u3092 phase state machine \u306B\u5408\u308F\u305B\u3066\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10528
+ )
10529
+ );
10530
+ } else {
10531
+ latestIterationKind = iteration.kind;
10532
+ if (iteration.kind === "polish" && hasCompletePolishChecks(iteration.checks)) {
10533
+ completedPolishCount++;
10534
+ polishWithBreakthroughCheck = true;
10535
+ }
10536
+ }
10304
10537
  }
10305
10538
  if (!Array.isArray(iteration.reviewerScores) || iteration.reviewerScores.length === 0) {
10306
10539
  issues.push(
@@ -10310,7 +10543,7 @@ async function validatePrototypingEvidence(root, config) {
10310
10543
  }
10311
10544
  for (const [reviewerIndex, reviewer] of iteration.reviewerScores.entries()) {
10312
10545
  const reviewerPath = `${prefix}.reviewerScores[${reviewerIndex}]`;
10313
- if (!isRecord5(reviewer)) {
10546
+ if (!isRecord6(reviewer)) {
10314
10547
  issues.push(makeSchemaIssue(root, evidencePath, `${reviewerPath} must be an object.`));
10315
10548
  continue;
10316
10549
  }
@@ -10327,7 +10560,7 @@ async function validatePrototypingEvidence(root, config) {
10327
10560
  }
10328
10561
  for (const [scoreIndex, score] of reviewer.scores.entries()) {
10329
10562
  const scorePath = `${reviewerPath}.scores[${scoreIndex}]`;
10330
- if (!isRecord5(score)) {
10563
+ if (!isRecord6(score)) {
10331
10564
  issues.push(makeSchemaIssue(root, evidencePath, `${scorePath} must be an object.`));
10332
10565
  continue;
10333
10566
  }
@@ -10367,27 +10600,131 @@ async function validatePrototypingEvidence(root, config) {
10367
10600
  }
10368
10601
  }
10369
10602
  }
10603
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
10604
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
10605
+ issues.push(
10606
+ issue(
10607
+ "QFAI-PROT-285",
10608
+ "phase.current must follow the prototyping phase state machine.",
10609
+ "error",
10610
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10611
+ "prototypingEvidence.phase",
10612
+ void 0,
10613
+ "canonical",
10614
+ "phase.current \u306F planning|explore|remix|select|polish|breakthrough|reviewer_gate|completed \u306E\u3044\u305A\u308C\u304B\u3067\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10615
+ )
10616
+ );
10617
+ }
10618
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
10619
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
10620
+ if (hasLegacyPass95CompletionMarker && completionClaimed) {
10621
+ issues.push(
10622
+ issue(
10623
+ "QFAI-PROT-288",
10624
+ "The legacy 95-point completion field is no longer a completion border; completion requires allReviewerAxesPerfect100.",
10625
+ "error",
10626
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10627
+ "prototypingEvidence.perfect100",
10628
+ void 0,
10629
+ "canonical",
10630
+ "95 \u70B9\u5230\u9054\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u5B8C\u4E86\u6761\u4EF6\u3068\u3057\u3066\u4F7F\u308F\u305A\u3001\u5168 reviewer / \u5168 axis 100 \u70B9\u3092 allReviewerAxesPerfect100 \u3067\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10631
+ )
10632
+ );
10633
+ }
10634
+ if (completionClaimed) {
10635
+ if (!latestPerfect100 || !allReviewerScoresArePerfect100(iterations)) {
10636
+ issues.push(
10637
+ issue(
10638
+ "QFAI-PROT-287",
10639
+ "Completion requires every reviewer to score every evaluation axis at 100.",
10640
+ "error",
10641
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10642
+ "prototypingEvidence.perfect100",
10643
+ void 0,
10644
+ "canonical",
10645
+ "completionClaimed/completed \u3092\u8A18\u9332\u3059\u308B\u524D\u306B\u3001\u5168 reviewerScores[].scores[].score \u3092 100 \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10646
+ )
10647
+ );
10648
+ }
10649
+ if (latestIterationKind === "select") {
10650
+ issues.push(
10651
+ issue(
10652
+ "QFAI-PROT-285",
10653
+ "Selection funnel completion is not stage completion; latest iteration cannot remain select when completion is claimed.",
10654
+ "error",
10655
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10656
+ "prototypingEvidence.phase",
10657
+ void 0,
10658
+ "canonical",
10659
+ "winner \u9078\u5B9A\u5F8C\u306B post-selection polish iteration \u3092\u5B8C\u4E86\u3057\u3066\u304B\u3089 completion \u3092 claim \u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10660
+ )
10661
+ );
10662
+ }
10663
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
10664
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
10665
+ issues.push(
10666
+ issue(
10667
+ "QFAI-PROT-286",
10668
+ "Completion requires at least one completed post-selection polish iteration.",
10669
+ "error",
10670
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10671
+ "prototypingEvidence.postSelectionPolish",
10672
+ void 0,
10673
+ "canonical",
10674
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish iteration \u306B critique/fix/recapture/rereview/breakthrough checks \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10675
+ )
10676
+ );
10677
+ }
10678
+ if (!polishWithBreakthroughCheck) {
10679
+ issues.push(
10680
+ issue(
10681
+ "QFAI-PROT-286",
10682
+ "Completion requires a polish iteration with critique/fix/recapture/rereview/breakthrough checks.",
10683
+ "error",
10684
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10685
+ "prototypingEvidence.postSelectionPolish",
10686
+ void 0,
10687
+ "canonical",
10688
+ "polish iteration \u306E checks.critique/fix/recapture/rereview/breakthrough \u3092 true \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10689
+ )
10690
+ );
10691
+ }
10692
+ if (!isRecord6(record2.completionCertificate)) {
10693
+ issues.push(
10694
+ issue(
10695
+ "QFAI-PROT-289",
10696
+ "completionCertificate is required when completion is claimed.",
10697
+ "error",
10698
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10699
+ "prototypingEvidence.completionCertificate",
10700
+ void 0,
10701
+ "canonical",
10702
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10703
+ )
10704
+ );
10705
+ }
10706
+ }
10370
10707
  const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
10371
- if (!foundPass95 && iterations.length < maxIterations) {
10708
+ if (!latestPerfect100 && iterations.length < maxIterations) {
10372
10709
  issues.push(
10373
10710
  issue(
10374
10711
  "QFAI-PROT-282",
10375
- `mode=${mode.effective} has not reached all-items-pass-95 and has remaining iterations.`,
10712
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining iterations.`,
10376
10713
  "warning",
10377
10714
  import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10378
10715
  "prototypingEvidence.convergence",
10379
10716
  [String(iterations.length), String(maxIterations)],
10380
10717
  "canonical",
10381
- "95 \u70B9\u672A\u6E80\u306E\u9805\u76EE\u304C\u6B8B\u3063\u3066\u3044\u308B\u305F\u3081\u3001mode \u4E0A\u9650\u306B\u9054\u3059\u308B\u307E\u3067\u53CD\u5FA9\u3092\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10718
+ "\u5168 reviewer / \u5168 axis \u304C 100 \u70B9\u306B\u9054\u3057\u3066\u3044\u306A\u3044\u305F\u3081\u3001mode \u4E0A\u9650\u306B\u9054\u3059\u308B\u307E\u3067\u53CD\u5FA9\u3092\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10382
10719
  )
10383
10720
  );
10384
10721
  }
10385
- if (obligations?.requireRuntimeGate && !isRecord5(record2.runtimeGate)) {
10722
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10386
10723
  issues.push(
10387
10724
  makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10388
10725
  );
10389
10726
  }
10390
- if (obligations?.requireUiFidelity && !isRecord5(record2.uiFidelity)) {
10727
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10391
10728
  issues.push(
10392
10729
  makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10393
10730
  );
@@ -10426,151 +10763,1521 @@ function normalizeIterations(value) {
10426
10763
  }
10427
10764
  return value;
10428
10765
  }
10429
- function isRecord5(value) {
10430
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10431
- }
10432
- async function readJsonFile(filePath) {
10433
- try {
10434
- const raw = await (0, import_promises32.readFile)(filePath, "utf-8");
10435
- const parsed = JSON.parse(raw);
10436
- return isRecord5(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
10437
- } catch {
10438
- try {
10439
- await (0, import_promises32.readFile)(filePath, "utf-8");
10440
- return { status: "invalid" };
10441
- } catch {
10442
- return { status: "missing" };
10443
- }
10766
+ function normalizeRounds(value) {
10767
+ if (!Array.isArray(value)) {
10768
+ return null;
10444
10769
  }
10770
+ return value;
10445
10771
  }
10446
- function makeSchemaIssue(root, evidencePath, message) {
10447
- return issue(
10448
- "QFAI-PROT-299",
10449
- message,
10450
- "error",
10451
- import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10452
- "prototypingEvidence.schema"
10453
- );
10454
- }
10455
-
10456
- // src/core/validators/prototypingDesignSystem.ts
10457
- var import_promises33 = require("fs/promises");
10458
- var import_node_path36 = __toESM(require("path"), 1);
10459
- var RULE_CODE = "PROT-DS01";
10460
- var RULE_NAME = "prototyping.designSystemCompliance";
10461
- var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
10462
- var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
10463
- function isRecord6(value) {
10464
- return typeof value === "object" && value !== null && !Array.isArray(value);
10465
- }
10466
- function detectMode(raw) {
10467
- if (!isRecord6(raw)) {
10468
- return "other";
10772
+ function normalizePolishCycles(value) {
10773
+ if (!Array.isArray(value)) {
10774
+ return null;
10469
10775
  }
10470
- const topLevel = raw.mode;
10471
- if (isRecord6(topLevel) && topLevel.effective === "full-harness") {
10472
- return "full-harness";
10776
+ return value;
10777
+ }
10778
+ function normalizePhaseCurrent(value) {
10779
+ if (!isRecord6(value)) {
10780
+ return null;
10473
10781
  }
10474
- return "other";
10782
+ return typeof value.current === "string" ? value.current : null;
10475
10783
  }
10476
- function detectScorePresence(raw) {
10477
- if (!isRecord6(raw)) {
10478
- return false;
10784
+ function normalizeFullHarnessStatus(value) {
10785
+ if (!isRecord6(value)) {
10786
+ return null;
10479
10787
  }
10480
- const trace = raw.scoringTrace;
10481
- if (!isRecord6(trace)) {
10788
+ return typeof value.status === "string" ? value.status : null;
10789
+ }
10790
+ function hasCompletePolishChecks(value) {
10791
+ if (!isRecord6(value)) {
10482
10792
  return false;
10483
10793
  }
10484
- return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
10794
+ return REQUIRED_POLISH_CHECKS.every((key) => value[key] === true);
10485
10795
  }
10486
- async function fileExists2(filePath) {
10487
- try {
10488
- await (0, import_promises33.access)(filePath);
10489
- return true;
10490
- } catch (err) {
10491
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10492
- return false;
10493
- }
10796
+ function allReviewerScoresArePerfect100(iterations) {
10797
+ const candidate = iterations[iterations.length - 1];
10798
+ if (!isRecord6(candidate) || !Array.isArray(candidate.reviewerScores)) {
10494
10799
  return false;
10495
10800
  }
10496
- }
10497
- async function readFileSafe(filePath) {
10498
- try {
10499
- return await (0, import_promises33.readFile)(filePath, "utf-8");
10500
- } catch (err) {
10501
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10502
- return null;
10801
+ return candidate.reviewerScores.every((reviewer) => {
10802
+ if (!isRecord6(reviewer) || !Array.isArray(reviewer.scores) || reviewer.scores.length === 0) {
10803
+ return false;
10503
10804
  }
10504
- return null;
10505
- }
10805
+ return reviewer.scores.every((score) => isRecord6(score) && score.score === 100);
10806
+ });
10506
10807
  }
10507
- async function loadPrototypingArtifact(evidenceDir) {
10508
- const jsonPath = import_node_path36.default.join(evidenceDir, "prototyping.json");
10509
- const jsonRaw = await readFileSafe(jsonPath);
10510
- if (jsonRaw !== null) {
10511
- let parsed = null;
10512
- try {
10513
- parsed = JSON.parse(jsonRaw);
10514
- } catch {
10808
+ function validateV2Lifecycle(root, evidencePath, record2, mode, obligations) {
10809
+ const issues = [];
10810
+ const rounds = normalizeRounds(record2.rounds);
10811
+ if (!rounds || rounds.length === 0) {
10812
+ issues.push(
10813
+ issue(
10814
+ "QFAI-PROT-280",
10815
+ "prototyping evidence requires at least one round entry.",
10816
+ "error",
10817
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10818
+ "prototypingEvidence.rounds",
10819
+ void 0,
10820
+ "canonical",
10821
+ "rounds[] \u306B\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u306E\u63A2\u7D22 round \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10822
+ )
10823
+ );
10824
+ return issues;
10825
+ }
10826
+ const polishCycles = normalizePolishCycles(record2.polishCycles) ?? [];
10827
+ if (obligations && polishCycles.length > obligations.maxIterations) {
10828
+ issues.push(
10829
+ issue(
10830
+ "QFAI-PROT-281",
10831
+ `mode=${mode.effective} exceeds max polish cycles (${obligations.maxIterations}).`,
10832
+ "error",
10833
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10834
+ "prototypingEvidence.maxIterations",
10835
+ [String(polishCycles.length), String(obligations.maxIterations)],
10836
+ "canonical",
10837
+ `mode=${mode.effective} \u306E polish cycle \u4E0A\u9650 ${obligations.maxIterations} \u3092\u8D85\u3048\u306A\u3044\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
10838
+ )
10839
+ );
10840
+ }
10841
+ let latestPerfect100 = false;
10842
+ let completedPolishCount = 0;
10843
+ let polishWithBreakthroughCheck = false;
10844
+ for (const [index, candidate] of rounds.entries()) {
10845
+ const prefix = `rounds[${index}]`;
10846
+ if (!isRecord6(candidate)) {
10847
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10848
+ continue;
10849
+ }
10850
+ let validRound = null;
10851
+ if (!isExplorationRound(candidate.round)) {
10852
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix}.round must be r5|r3|r2|r1.`));
10853
+ } else {
10854
+ validRound = candidate.round;
10855
+ if (index >= EXPLORATION_ROUNDS.length) {
10856
+ issues.push(
10857
+ makeSchemaIssue(
10858
+ root,
10859
+ evidencePath,
10860
+ `${prefix} exceeds the funnel limit (rounds[] must contain at most ${EXPLORATION_ROUNDS.length} entries: ${EXPLORATION_ROUNDS.join("\u2192")}).`
10861
+ )
10862
+ );
10863
+ } else {
10864
+ const expectedRound = EXPLORATION_ROUNDS[index];
10865
+ if (expectedRound !== void 0 && validRound !== expectedRound) {
10866
+ issues.push(
10867
+ makeSchemaIssue(
10868
+ root,
10869
+ evidencePath,
10870
+ `${prefix}.round must be ${expectedRound} (rounds[] must follow ${EXPLORATION_ROUNDS.join("\u2192")}); got ${validRound}.`
10871
+ )
10872
+ );
10873
+ }
10874
+ }
10875
+ }
10876
+ const candidateIds = [];
10877
+ const seenCandidateIds = /* @__PURE__ */ new Set();
10878
+ if (!Array.isArray(candidate.candidates) || candidate.candidates.length === 0) {
10879
+ issues.push(
10880
+ makeSchemaIssue(root, evidencePath, `${prefix}.candidates must be a non-empty array.`)
10881
+ );
10882
+ } else {
10883
+ for (const [candidateIndex, roundCandidate] of candidate.candidates.entries()) {
10884
+ if (!isRecord6(roundCandidate)) {
10885
+ issues.push(
10886
+ makeSchemaIssue(
10887
+ root,
10888
+ evidencePath,
10889
+ `${prefix}.candidates[${candidateIndex}] must be an object.`
10890
+ )
10891
+ );
10892
+ continue;
10893
+ }
10894
+ if (!isCandidateId(roundCandidate.candidateId)) {
10895
+ issues.push(
10896
+ makeSchemaIssue(
10897
+ root,
10898
+ evidencePath,
10899
+ `${prefix}.candidates[${candidateIndex}].candidateId must be a valid candidate id.`
10900
+ )
10901
+ );
10902
+ } else if (seenCandidateIds.has(roundCandidate.candidateId)) {
10903
+ issues.push(
10904
+ makeSchemaIssue(
10905
+ root,
10906
+ evidencePath,
10907
+ `${prefix}.candidates[${candidateIndex}].candidateId must be unique within the round; ${roundCandidate.candidateId} already appears earlier.`
10908
+ )
10909
+ );
10910
+ } else {
10911
+ seenCandidateIds.add(roundCandidate.candidateId);
10912
+ candidateIds.push(roundCandidate.candidateId);
10913
+ }
10914
+ }
10915
+ if (validRound) {
10916
+ const expectedCandidateCount = ROUND_SURVIVOR_COUNT[validRound];
10917
+ if (candidate.candidates.length !== expectedCandidateCount) {
10918
+ issues.push(
10919
+ makeSchemaIssue(
10920
+ root,
10921
+ evidencePath,
10922
+ `${prefix}.candidates must contain exactly ${expectedCandidateCount} entries for round ${validRound}; got ${candidate.candidates.length}.`
10923
+ )
10924
+ );
10925
+ }
10926
+ }
10927
+ }
10928
+ const screenMap = isRecord6(candidate.screenEvidenceByCandidate) ? candidate.screenEvidenceByCandidate : null;
10929
+ if (screenMap === null) {
10930
+ issues.push(
10931
+ makeSchemaIssue(
10932
+ root,
10933
+ evidencePath,
10934
+ `${prefix}.screenEvidenceByCandidate must be an object keyed by candidateId.`
10935
+ )
10936
+ );
10937
+ }
10938
+ const reviewMap = isRecord6(candidate.evaluatorReviewRefsByCandidate) ? candidate.evaluatorReviewRefsByCandidate : null;
10939
+ if (reviewMap === null) {
10940
+ issues.push(
10941
+ makeSchemaIssue(
10942
+ root,
10943
+ evidencePath,
10944
+ `${prefix}.evaluatorReviewRefsByCandidate must be an object keyed by candidateId.`
10945
+ )
10946
+ );
10947
+ }
10948
+ for (const candidateId of candidateIds) {
10949
+ if (screenMap) {
10950
+ const screens = screenMap[candidateId];
10951
+ if (!Array.isArray(screens) || screens.length === 0) {
10952
+ issues.push(
10953
+ makeSchemaIssue(
10954
+ root,
10955
+ evidencePath,
10956
+ `${prefix}.screenEvidenceByCandidate.${candidateId} must be a non-empty array of screen artifact refs.`
10957
+ )
10958
+ );
10959
+ }
10960
+ }
10961
+ if (reviewMap) {
10962
+ const reviewRef = reviewMap[candidateId];
10963
+ if (typeof reviewRef !== "string" || reviewRef.trim().length === 0) {
10964
+ issues.push(
10965
+ makeSchemaIssue(
10966
+ root,
10967
+ evidencePath,
10968
+ `${prefix}.evaluatorReviewRefsByCandidate.${candidateId} must be a non-empty string.`
10969
+ )
10970
+ );
10971
+ }
10972
+ }
10973
+ }
10974
+ if (typeof candidate.allAxesPerfect100 !== "boolean") {
10975
+ issues.push(
10976
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
10977
+ );
10978
+ } else {
10979
+ latestPerfect100 = candidate.allAxesPerfect100;
10980
+ }
10981
+ }
10982
+ for (const [index, cycle] of polishCycles.entries()) {
10983
+ const prefix = `polishCycles[${index}]`;
10984
+ if (!isRecord6(cycle)) {
10985
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10986
+ continue;
10987
+ }
10988
+ if (typeof cycle.cycle !== "number" || !Number.isInteger(cycle.cycle) || cycle.cycle <= 0) {
10989
+ issues.push(
10990
+ makeSchemaIssue(root, evidencePath, `${prefix}.cycle must be a positive integer.`)
10991
+ );
10992
+ }
10993
+ if (typeof cycle.kind !== "string" || !["polish", "branch", "reviewer_gate", "completed"].includes(cycle.kind)) {
10994
+ issues.push(
10995
+ makeSchemaIssue(
10996
+ root,
10997
+ evidencePath,
10998
+ `${prefix}.kind must be polish|branch|reviewer_gate|completed.`
10999
+ )
11000
+ );
11001
+ }
11002
+ if (typeof cycle.evaluatorReviewRef !== "string" || cycle.evaluatorReviewRef.trim().length === 0) {
11003
+ issues.push(
11004
+ makeSchemaIssue(root, evidencePath, `${prefix}.evaluatorReviewRef must be non-empty.`)
11005
+ );
11006
+ }
11007
+ if (typeof cycle.allAxesPerfect100 !== "boolean") {
11008
+ issues.push(
11009
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
11010
+ );
11011
+ } else {
11012
+ latestPerfect100 = cycle.allAxesPerfect100;
11013
+ }
11014
+ if (cycle.kind === "polish") {
11015
+ completedPolishCount += 1;
11016
+ if (typeof cycle.breakthroughRef === "string" && cycle.breakthroughRef.trim().length > 0) {
11017
+ polishWithBreakthroughCheck = true;
11018
+ }
11019
+ }
11020
+ }
11021
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
11022
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
11023
+ issues.push(
11024
+ issue(
11025
+ "QFAI-PROT-285",
11026
+ "phase.current must follow the prototyping phase state machine.",
11027
+ "error",
11028
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11029
+ "prototypingEvidence.phase",
11030
+ void 0,
11031
+ "canonical",
11032
+ "phase.current \u306F planning|explore|remix|select|polish|breakthrough|reviewer_gate|completed \u306E\u3044\u305A\u308C\u304B\u3067\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11033
+ )
11034
+ );
11035
+ }
11036
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
11037
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
11038
+ if (completionClaimed) {
11039
+ if (!latestPerfect100) {
11040
+ issues.push(
11041
+ issue(
11042
+ "QFAI-PROT-287",
11043
+ "Completion requires every reviewer to score every evaluation axis at 100.",
11044
+ "error",
11045
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11046
+ "prototypingEvidence.perfect100",
11047
+ void 0,
11048
+ "canonical",
11049
+ "completionClaimed/completed \u3092\u8A18\u9332\u3059\u308B\u524D\u306B\u3001\u6700\u65B0 round \u307E\u305F\u306F polish cycle \u306E allAxesPerfect100 \u3092 true \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11050
+ )
11051
+ );
11052
+ }
11053
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
11054
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
11055
+ issues.push(
11056
+ issue(
11057
+ "QFAI-PROT-286",
11058
+ "Completion requires at least one completed post-selection polish cycle.",
11059
+ "error",
11060
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11061
+ "prototypingEvidence.postSelectionPolish",
11062
+ void 0,
11063
+ "canonical",
11064
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish cycle \u3092\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11065
+ )
11066
+ );
11067
+ }
11068
+ if (!polishWithBreakthroughCheck) {
11069
+ issues.push(
11070
+ issue(
11071
+ "QFAI-PROT-286",
11072
+ "Completion requires a polish cycle with breakthrough evidence.",
11073
+ "error",
11074
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11075
+ "prototypingEvidence.postSelectionPolish",
11076
+ void 0,
11077
+ "canonical",
11078
+ "polish cycle \u306B breakthroughRef \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11079
+ )
11080
+ );
11081
+ }
11082
+ if (!isRecord6(record2.completionCertificate)) {
11083
+ issues.push(
11084
+ issue(
11085
+ "QFAI-PROT-289",
11086
+ "completionCertificate is required when completion is claimed.",
11087
+ "error",
11088
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11089
+ "prototypingEvidence.completionCertificate",
11090
+ void 0,
11091
+ "canonical",
11092
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11093
+ )
11094
+ );
11095
+ }
11096
+ }
11097
+ const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
11098
+ if (!latestPerfect100 && polishCycles.length < maxIterations) {
11099
+ issues.push(
11100
+ issue(
11101
+ "QFAI-PROT-282",
11102
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining polish cycles.`,
11103
+ "warning",
11104
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11105
+ "prototypingEvidence.convergence",
11106
+ [String(polishCycles.length), String(maxIterations)],
11107
+ "canonical",
11108
+ "\u5168 reviewer / \u5168 axis \u304C 100 \u70B9\u306B\u9054\u3057\u3066\u3044\u306A\u3044\u305F\u3081\u3001mode \u4E0A\u9650\u306B\u9054\u3059\u308B\u307E\u3067 polish cycle \u3092\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11109
+ )
11110
+ );
11111
+ }
11112
+ return issues;
11113
+ }
11114
+ function isRecord6(value) {
11115
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11116
+ }
11117
+ async function readJsonFile(filePath) {
11118
+ try {
11119
+ const raw = await (0, import_promises32.readFile)(filePath, "utf-8");
11120
+ const parsed = JSON.parse(raw);
11121
+ return isRecord6(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
11122
+ } catch {
11123
+ try {
11124
+ await (0, import_promises32.readFile)(filePath, "utf-8");
11125
+ return { status: "invalid" };
11126
+ } catch {
11127
+ return { status: "missing" };
11128
+ }
11129
+ }
11130
+ }
11131
+ function makeSchemaIssue(root, evidencePath, message) {
11132
+ return issue(
11133
+ "QFAI-PROT-299",
11134
+ message,
11135
+ "error",
11136
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11137
+ "prototypingEvidence.schema"
11138
+ );
11139
+ }
11140
+
11141
+ // src/core/validators/prototypingDesignSystem.ts
11142
+ var import_promises33 = require("fs/promises");
11143
+ var import_node_path36 = __toESM(require("path"), 1);
11144
+ var RULE_CODE = "PROT-DS01";
11145
+ var RULE_NAME = "prototyping.designSystemCompliance";
11146
+ var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
11147
+ var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
11148
+ function isRecord7(value) {
11149
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11150
+ }
11151
+ function detectMode2(raw) {
11152
+ if (!isRecord7(raw)) {
11153
+ return "other";
11154
+ }
11155
+ const topLevel = raw.mode;
11156
+ if (isRecord7(topLevel) && topLevel.effective === "full-harness") {
11157
+ return "full-harness";
11158
+ }
11159
+ return "other";
11160
+ }
11161
+ function detectScorePresence(raw) {
11162
+ if (!isRecord7(raw)) {
11163
+ return false;
11164
+ }
11165
+ const trace = raw.scoringTrace;
11166
+ if (!isRecord7(trace)) {
11167
+ return false;
11168
+ }
11169
+ return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
11170
+ }
11171
+ async function fileExists2(filePath) {
11172
+ try {
11173
+ await (0, import_promises33.access)(filePath);
11174
+ return true;
11175
+ } catch (err) {
11176
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11177
+ return false;
11178
+ }
11179
+ return false;
11180
+ }
11181
+ }
11182
+ async function readFileSafe(filePath) {
11183
+ try {
11184
+ return await (0, import_promises33.readFile)(filePath, "utf-8");
11185
+ } catch (err) {
11186
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11187
+ return null;
11188
+ }
11189
+ return null;
11190
+ }
11191
+ }
11192
+ async function loadPrototypingArtifact(evidenceDir) {
11193
+ const jsonPath = import_node_path36.default.join(evidenceDir, "prototyping.json");
11194
+ const jsonRaw = await readFileSafe(jsonPath);
11195
+ if (jsonRaw !== null) {
11196
+ let parsed = null;
11197
+ try {
11198
+ parsed = JSON.parse(jsonRaw);
11199
+ } catch {
10515
11200
  parsed = null;
10516
11201
  }
10517
- return {
10518
- source: "json",
10519
- fileName: "prototyping.json",
10520
- mode: detectMode(parsed),
10521
- designSystemCompliancePresent: detectScorePresence(parsed)
10522
- };
10523
- }
10524
- return {
10525
- source: "none",
10526
- fileName: "prototyping.json",
10527
- mode: "other",
10528
- designSystemCompliancePresent: false
10529
- };
10530
- }
10531
- function toIssue(severity, file) {
10532
- return {
10533
- code: RULE_CODE,
10534
- severity,
10535
- category: "canonical",
10536
- message: MESSAGE,
10537
- file,
10538
- rule: RULE_NAME,
10539
- suggested_action: SUGGESTED_ACTION
10540
- };
10541
- }
10542
- function resolveEvidenceRoot(root, config) {
10543
- return import_node_path36.default.join(import_node_path36.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
10544
- }
10545
- async function validatePrototypingDesignSystem(root, config) {
10546
- const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
10547
- if (screens.length === 0) {
10548
- return [];
11202
+ return {
11203
+ source: "json",
11204
+ fileName: "prototyping.json",
11205
+ mode: detectMode2(parsed),
11206
+ designSystemCompliancePresent: detectScorePresence(parsed)
11207
+ };
11208
+ }
11209
+ return {
11210
+ source: "none",
11211
+ fileName: "prototyping.json",
11212
+ mode: "other",
11213
+ designSystemCompliancePresent: false
11214
+ };
11215
+ }
11216
+ function toIssue(severity, file) {
11217
+ return {
11218
+ code: RULE_CODE,
11219
+ severity,
11220
+ category: "canonical",
11221
+ message: MESSAGE,
11222
+ file,
11223
+ rule: RULE_NAME,
11224
+ suggested_action: SUGGESTED_ACTION
11225
+ };
11226
+ }
11227
+ function resolveEvidenceRoot(root, config) {
11228
+ return import_node_path36.default.join(import_node_path36.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
11229
+ }
11230
+ async function validatePrototypingDesignSystem(root, config) {
11231
+ const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
11232
+ if (screens.length === 0) {
11233
+ return [];
11234
+ }
11235
+ const evidenceDir = resolveEvidenceRoot(root, config);
11236
+ const artifact = await loadPrototypingArtifact(evidenceDir);
11237
+ if (artifact.designSystemCompliancePresent) {
11238
+ return [];
11239
+ }
11240
+ const designSystemPath = import_node_path36.default.join(
11241
+ root,
11242
+ config.paths.contractsDir,
11243
+ "design",
11244
+ "design-system.yaml"
11245
+ );
11246
+ const designSystemPresent = await fileExists2(designSystemPath);
11247
+ const severity = designSystemPresent && artifact.mode === "full-harness" ? "error" : "warning";
11248
+ return [toIssue(severity, artifact.fileName)];
11249
+ }
11250
+
11251
+ // src/core/validators/prototyping/executionPlan.ts
11252
+ init_utils();
11253
+ var FIELD_SHAPE_LABEL = {
11254
+ number: "finite number",
11255
+ "non-empty-string": "non-empty string",
11256
+ record: "object (not array)"
11257
+ };
11258
+ var REQUIRED_FIELDS = [
11259
+ { key: "targetIterations", shape: "number" },
11260
+ { key: "evaluationAxesSource", shape: "non-empty-string" },
11261
+ { key: "delegationMap", shape: "record" },
11262
+ { key: "plannedAt", shape: "non-empty-string" }
11263
+ ];
11264
+ function describeType(value) {
11265
+ if (value === null) return "null";
11266
+ if (Array.isArray(value)) return "array";
11267
+ return typeof value;
11268
+ }
11269
+ function matchesShape(value, shape) {
11270
+ switch (shape) {
11271
+ case "number":
11272
+ return typeof value === "number" && Number.isFinite(value);
11273
+ case "non-empty-string":
11274
+ return typeof value === "string" && value.trim().length > 0;
11275
+ case "record":
11276
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11277
+ }
11278
+ }
11279
+ function isRecord8(v) {
11280
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11281
+ }
11282
+ function detectMode3(raw) {
11283
+ if (!isRecord8(raw)) return "other";
11284
+ if (typeof raw.mode === "string") return raw.mode;
11285
+ if (isRecord8(raw.mode) && typeof raw.mode.effective === "string") {
11286
+ return raw.mode.effective;
11287
+ }
11288
+ return "other";
11289
+ }
11290
+ function validateExecutionPlanIssues(prototypingJson, prototypingJsonPath) {
11291
+ if (!isRecord8(prototypingJson)) {
11292
+ return [];
11293
+ }
11294
+ if (detectMode3(prototypingJson) !== "full-harness") {
11295
+ return [];
11296
+ }
11297
+ if (!isRecord8(prototypingJson.executionPlan)) {
11298
+ return [
11299
+ buildIssue(
11300
+ "executionPlan is required in full-harness mode but is absent or not an object in prototyping.json.",
11301
+ prototypingJsonPath
11302
+ )
11303
+ ];
11304
+ }
11305
+ const issues = [];
11306
+ const executionPlan = prototypingJson.executionPlan;
11307
+ for (const { key, shape } of REQUIRED_FIELDS) {
11308
+ const value = executionPlan[key];
11309
+ if (!matchesShape(value, shape)) {
11310
+ issues.push(
11311
+ buildIssue(
11312
+ `executionPlan.${key} is required in full-harness mode and must be a ${FIELD_SHAPE_LABEL[shape]} (got: ${describeType(value)}).`,
11313
+ prototypingJsonPath
11314
+ )
11315
+ );
11316
+ }
11317
+ }
11318
+ return issues;
11319
+ }
11320
+ function buildIssue(message, prototypingJsonPath) {
11321
+ return issue(
11322
+ "QFAI-PROT-310",
11323
+ message,
11324
+ "error",
11325
+ prototypingJsonPath,
11326
+ "prototyping.executionPlan.presence",
11327
+ void 0,
11328
+ "canonical",
11329
+ "full-harness \u30E2\u30FC\u30C9\u3067\u306F `prototyping.json.executionPlan` \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044 (targetIterations / evaluationAxesSource / delegationMap / plannedAt)\u3002"
11330
+ );
11331
+ }
11332
+
11333
+ // src/core/validators/prototyping/delegationMap.ts
11334
+ init_utils();
11335
+ var DELEGATION_SCOPE = {
11336
+ UI\u5B9F\u88C5: ["frontend-engineer", "product-experience-architect"],
11337
+ \u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8: ["devops-ci-engineer"],
11338
+ \u8A55\u4FA1\u30B9\u30B3\u30A2\u30EA\u30F3\u30B0: ["product-surface-reviewer", "product-experience-architect"],
11339
+ \u30D3\u30EB\u30C9: ["devops-ci-engineer", "backend-engineer"]
11340
+ };
11341
+ var DELEGATION_CATEGORIES = Object.keys(DELEGATION_SCOPE);
11342
+ function validateDelegationMapIssues(delegationMap, prototypingJsonPath) {
11343
+ if (!delegationMap) {
11344
+ return [];
11345
+ }
11346
+ const issues = [];
11347
+ for (const [category, rawRole] of Object.entries(delegationMap)) {
11348
+ const allowedRoles = DELEGATION_SCOPE[category];
11349
+ if (allowedRoles === void 0) {
11350
+ continue;
11351
+ }
11352
+ if (typeof rawRole !== "string") {
11353
+ issues.push(
11354
+ issue(
11355
+ "QFAI-PROT-311",
11356
+ `Delegation violation: category "${category}" assigned to non-string value (got: ${describeRoleType(rawRole)}). Allowed roles: ${allowedRoles.join(", ")}.`,
11357
+ "error",
11358
+ prototypingJsonPath,
11359
+ "prototyping.executionPlan.delegationMap",
11360
+ void 0,
11361
+ "canonical",
11362
+ `category "${category}" \u306B\u306F string \u306E role \u3092\u5272\u308A\u5F53\u3066\u3066\u304F\u3060\u3055\u3044 (allowed: ${allowedRoles.join(", ")})\u3002`
11363
+ )
11364
+ );
11365
+ continue;
11366
+ }
11367
+ if (!allowedRoles.includes(rawRole)) {
11368
+ issues.push(
11369
+ issue(
11370
+ "QFAI-PROT-311",
11371
+ `Delegation violation: category "${category}" assigned to undefined/invalid role "${rawRole}". Allowed roles: ${allowedRoles.join(", ")}.`,
11372
+ "error",
11373
+ prototypingJsonPath,
11374
+ "prototyping.executionPlan.delegationMap",
11375
+ void 0,
11376
+ "canonical",
11377
+ `category "${category}" \u3092\u8A31\u53EF\u3055\u308C\u305F role \u306B\u5272\u308A\u5F53\u3066\u3066\u304F\u3060\u3055\u3044 (allowed: ${allowedRoles.join(", ")})\u3002`
11378
+ )
11379
+ );
11380
+ }
11381
+ }
11382
+ return issues;
11383
+ }
11384
+ function describeRoleType(value) {
11385
+ if (value === null) return "null";
11386
+ if (Array.isArray(value)) return "array";
11387
+ return typeof value;
11388
+ }
11389
+
11390
+ // src/core/validators/prototyping/screenshotDir.ts
11391
+ init_utils();
11392
+ function isRecord9(v) {
11393
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11394
+ }
11395
+ function validateScreenshotDirIssues(scoringTrace, mode, prototypingJsonPath) {
11396
+ if (mode !== "full-harness") {
11397
+ return [];
11398
+ }
11399
+ const issues = [];
11400
+ const suggestion = "full-harness \u30E2\u30FC\u30C9\u3067\u306F scoringTrace \u306E\u5404 entry \u306B screenshotDir \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
11401
+ for (let i = 0; i < scoringTrace.length; i++) {
11402
+ const entry = scoringTrace[i];
11403
+ if (!isRecord9(entry)) {
11404
+ issues.push(
11405
+ issue(
11406
+ "QFAI-PROT-331",
11407
+ `scoringTrace[${i}] is not an object; screenshotDir cannot be verified.`,
11408
+ "error",
11409
+ prototypingJsonPath,
11410
+ "prototyping.fullHarness.scoringTrace.screenshotDir",
11411
+ void 0,
11412
+ "canonical",
11413
+ suggestion
11414
+ )
11415
+ );
11416
+ continue;
11417
+ }
11418
+ if (typeof entry.screenshotDir !== "string" || entry.screenshotDir.trim() === "") {
11419
+ issues.push(
11420
+ issue(
11421
+ "QFAI-PROT-331",
11422
+ `scoringTrace[${i}].screenshotDir is missing or empty; full-harness requires screenshot evidence per iteration.`,
11423
+ "error",
11424
+ prototypingJsonPath,
11425
+ "prototyping.fullHarness.scoringTrace.screenshotDir",
11426
+ void 0,
11427
+ "canonical",
11428
+ suggestion
11429
+ )
11430
+ );
11431
+ }
11432
+ }
11433
+ return issues;
11434
+ }
11435
+
11436
+ // src/core/validators/prototyping/lighthouseGate.ts
11437
+ init_utils();
11438
+ function isRecord10(v) {
11439
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11440
+ }
11441
+ function detectMode4(raw) {
11442
+ if (!isRecord10(raw)) return "other";
11443
+ if (typeof raw.mode === "string") return raw.mode;
11444
+ if (isRecord10(raw.mode) && typeof raw.mode.effective === "string") {
11445
+ return raw.mode.effective;
11446
+ }
11447
+ return "other";
11448
+ }
11449
+ function detectSurface(raw) {
11450
+ if (!isRecord10(raw)) return "unknown";
11451
+ if (typeof raw.surface === "string") return raw.surface;
11452
+ return "unknown";
11453
+ }
11454
+ function hasLighthouseReport(raw) {
11455
+ if (!isRecord10(raw)) return false;
11456
+ const report = raw.lighthouseReport ?? raw.lighthouse;
11457
+ if (!report) return false;
11458
+ if (isRecord10(report)) return Object.keys(report).length > 0;
11459
+ if (typeof report === "string") return report.trim().length > 0;
11460
+ return false;
11461
+ }
11462
+ function validateLighthouseGateIssues(prototypingJson, prototypingJsonPath) {
11463
+ const mode = detectMode4(prototypingJson);
11464
+ const surface = detectSurface(prototypingJson);
11465
+ if (mode !== "full-harness" || surface !== "web") {
11466
+ return [];
11467
+ }
11468
+ if (hasLighthouseReport(prototypingJson)) {
11469
+ return [];
11470
+ }
11471
+ return [
11472
+ issue(
11473
+ "QFAI-PROT-332",
11474
+ "Lighthouse Gate is MUST for full-harness + web surface: no Lighthouse report found in prototyping evidence.",
11475
+ "error",
11476
+ prototypingJsonPath,
11477
+ "prototyping.lighthouseReport",
11478
+ void 0,
11479
+ "canonical",
11480
+ "full-harness + web surface \u3067\u306F `prototyping.json.lighthouseReport` (\u307E\u305F\u306F `lighthouse`) \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11481
+ )
11482
+ ];
11483
+ }
11484
+
11485
+ // src/core/validators/prototyping/iterationGate.ts
11486
+ init_utils();
11487
+ function validateIterationGateIssues(iterations, prototypingJsonPath) {
11488
+ if (iterations.length === 0) {
11489
+ return [];
11490
+ }
11491
+ for (const entry of iterations) {
11492
+ if (entry.iterationCount === 1 && entry.converged === true) {
11493
+ return [
11494
+ issue(
11495
+ "QFAI-PROT-333",
11496
+ "minimum 2 iterations required before convergence: iteration 1 cannot be marked converged.",
11497
+ "error",
11498
+ prototypingJsonPath,
11499
+ "prototyping.fullHarness.iterations.convergence",
11500
+ void 0,
11501
+ "canonical",
11502
+ "iteration 1 \u3067 converged=true \u306F\u8A31\u5BB9\u3055\u308C\u307E\u305B\u3093\u3002\u5C11\u306A\u304F\u3068\u3082 2 iteration \u4EE5\u4E0A \u8D70\u3089\u305B\u3066\u304F\u3060\u3055\u3044\u3002"
11503
+ )
11504
+ ];
11505
+ }
11506
+ }
11507
+ return [];
11508
+ }
11509
+
11510
+ // src/core/validators/prototyping/designSystemThreshold.ts
11511
+ var import_promises34 = require("fs/promises");
11512
+ var import_node_path37 = __toESM(require("path"), 1);
11513
+ init_utils();
11514
+ var DS_PASS_THRESHOLD = 0.75;
11515
+ function isRecord11(v) {
11516
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11517
+ }
11518
+ async function designSystemMdPresent(packDir) {
11519
+ try {
11520
+ await (0, import_promises34.access)(import_node_path37.default.join(packDir, "uiux", "12_design_system.md"));
11521
+ return true;
11522
+ } catch {
11523
+ return false;
11524
+ }
11525
+ }
11526
+ function extractScore(prototypingRecord) {
11527
+ if (!isRecord11(prototypingRecord)) return null;
11528
+ const trace = prototypingRecord.scoringTrace;
11529
+ if (!isRecord11(trace)) return null;
11530
+ const score = trace.designSystemCompliance;
11531
+ if (typeof score !== "number") return null;
11532
+ return score;
11533
+ }
11534
+ async function validateDesignSystemThresholdIssues(packDir, prototypingRecord, prototypingJsonPath) {
11535
+ const hasDesignSystem = await designSystemMdPresent(packDir);
11536
+ if (!hasDesignSystem) {
11537
+ return [];
11538
+ }
11539
+ const score = extractScore(prototypingRecord);
11540
+ if (score === null) {
11541
+ return [];
11542
+ }
11543
+ if (score >= DS_PASS_THRESHOLD) {
11544
+ return [];
11545
+ }
11546
+ return [
11547
+ issue(
11548
+ "QFAI-PROT-334",
11549
+ `designSystemCompliance score ${(score * 100).toFixed(0)}% is below threshold ${(DS_PASS_THRESHOLD * 100).toFixed(0)}%; immediate fix required for next iteration.`,
11550
+ "error",
11551
+ prototypingJsonPath,
11552
+ "prototyping.scoringTrace.designSystemCompliance.threshold",
11553
+ void 0,
11554
+ "canonical",
11555
+ `scoringTrace.designSystemCompliance \u3092\u95BE\u5024 ${(DS_PASS_THRESHOLD * 100).toFixed(0)}% \u4EE5\u4E0A\u306B\u4E0A\u3052\u308B\u4FEE\u6B63\u3092\u6B21\u30A4\u30C6\u30EC\u30FC\u30B7\u30E7\u30F3\u306B\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002`
11556
+ )
11557
+ ];
11558
+ }
11559
+
11560
+ // src/core/validators/prototyping/stateGate.ts
11561
+ var import_promises35 = require("fs/promises");
11562
+ var import_node_path38 = __toESM(require("path"), 1);
11563
+ function isRecord12(value) {
11564
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11565
+ }
11566
+ function extractDelegationMap(prototypingJson) {
11567
+ if (!isRecord12(prototypingJson)) return void 0;
11568
+ const executionPlan = prototypingJson.executionPlan;
11569
+ if (!isRecord12(executionPlan)) return void 0;
11570
+ const delegationMap = executionPlan.delegationMap;
11571
+ if (!isRecord12(delegationMap)) return void 0;
11572
+ return delegationMap;
11573
+ }
11574
+ function extractMode(prototypingJson) {
11575
+ if (!isRecord12(prototypingJson)) return "other";
11576
+ if (typeof prototypingJson.mode === "string") return prototypingJson.mode;
11577
+ if (isRecord12(prototypingJson.mode) && typeof prototypingJson.mode.effective === "string") {
11578
+ return prototypingJson.mode.effective;
11579
+ }
11580
+ return "other";
11581
+ }
11582
+ function extractScoringTrace(prototypingJson) {
11583
+ if (!isRecord12(prototypingJson)) return [];
11584
+ const fullHarness = prototypingJson.fullHarness;
11585
+ if (!isRecord12(fullHarness)) return [];
11586
+ const trace = fullHarness.scoringTrace;
11587
+ return Array.isArray(trace) ? trace : [];
11588
+ }
11589
+ function extractFullHarnessIterations(prototypingJson) {
11590
+ if (!isRecord12(prototypingJson)) return [];
11591
+ const fullHarness = prototypingJson.fullHarness;
11592
+ if (!isRecord12(fullHarness)) return [];
11593
+ const iterations = fullHarness.iterations;
11594
+ if (!Array.isArray(iterations)) return [];
11595
+ return iterations.filter(
11596
+ (entry) => isRecord12(entry) && typeof entry.iterationCount === "number"
11597
+ );
11598
+ }
11599
+ function resolveCalibrationPackDir(root, config) {
11600
+ const packPath = config.prototyping?.calibration?.packPath;
11601
+ if (!packPath || typeof packPath !== "string") return void 0;
11602
+ return import_node_path38.default.resolve(root, packPath);
11603
+ }
11604
+ async function validateStateGate(root, config) {
11605
+ const evidenceRoot = import_node_path38.default.join(
11606
+ import_node_path38.default.dirname(import_node_path38.default.resolve(root, config.paths.specsDir)),
11607
+ "evidence"
11608
+ );
11609
+ const prototypingJsonPath = import_node_path38.default.join(evidenceRoot, "prototyping.json");
11610
+ let raw;
11611
+ try {
11612
+ raw = await (0, import_promises35.readFile)(prototypingJsonPath, "utf-8");
11613
+ } catch {
11614
+ return [];
11615
+ }
11616
+ let parsed;
11617
+ try {
11618
+ parsed = JSON.parse(raw);
11619
+ } catch {
11620
+ return [];
11621
+ }
11622
+ const relPath = import_node_path38.default.relative(root, prototypingJsonPath).replace(/\\/g, "/");
11623
+ const mode = extractMode(parsed);
11624
+ const issues = [];
11625
+ issues.push(...validateExecutionPlanIssues(parsed, relPath));
11626
+ issues.push(...validateDelegationMapIssues(extractDelegationMap(parsed), relPath));
11627
+ issues.push(...validateScreenshotDirIssues(extractScoringTrace(parsed), mode, relPath));
11628
+ issues.push(...validateIterationGateIssues(extractFullHarnessIterations(parsed), relPath));
11629
+ issues.push(...validateLighthouseGateIssues(parsed, relPath));
11630
+ const packDir = resolveCalibrationPackDir(root, config);
11631
+ if (packDir) {
11632
+ issues.push(...await validateDesignSystemThresholdIssues(packDir, parsed, relPath));
11633
+ }
11634
+ return issues;
11635
+ }
11636
+
11637
+ // src/core/validators/prototyping/completionCertificate.ts
11638
+ var import_promises37 = require("fs/promises");
11639
+ var import_node_path40 = __toESM(require("path"), 1);
11640
+
11641
+ // src/core/prototyping/certificate.ts
11642
+ var import_node_crypto2 = require("crypto");
11643
+ var import_promises36 = require("fs/promises");
11644
+ var import_node_path39 = __toESM(require("path"), 1);
11645
+ var COMPLETION_CERTIFICATE_REL_PATH = ".qfai/evidence/prototyping/completion-certificate.json";
11646
+ async function loadCompletionCertificate(root) {
11647
+ const fullPath = import_node_path39.default.join(root, COMPLETION_CERTIFICATE_REL_PATH);
11648
+ try {
11649
+ const raw = await (0, import_promises36.readFile)(fullPath, "utf-8");
11650
+ const parsed = JSON.parse(raw);
11651
+ if (!isMinimallyValidCertificate(parsed)) {
11652
+ return null;
11653
+ }
11654
+ return parsed;
11655
+ } catch {
11656
+ return null;
11657
+ }
11658
+ }
11659
+ function isMinimallyValidCertificate(value) {
11660
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
11661
+ const v = value;
11662
+ if (v.schemaVersion !== "1.0") return false;
11663
+ if (typeof v.runId !== "string") return false;
11664
+ if (!Array.isArray(v.evidenceDigests)) return false;
11665
+ for (const entry of v.evidenceDigests) {
11666
+ if (!entry || typeof entry !== "object") return false;
11667
+ const e = entry;
11668
+ if (typeof e.path !== "string" || typeof e.sha256 !== "string") return false;
11669
+ }
11670
+ if (!v.validateRun || typeof v.validateRun !== "object") return false;
11671
+ if (!v.verifyRun || typeof v.verifyRun !== "object") return false;
11672
+ if (!v.reviewerSignoff || typeof v.reviewerSignoff !== "object") return false;
11673
+ return true;
11674
+ }
11675
+ async function checkCompletionCertificate(root) {
11676
+ const cert = await loadCompletionCertificate(root);
11677
+ if (!cert) {
11678
+ return {
11679
+ ok: false,
11680
+ reasons: [
11681
+ `${COMPLETION_CERTIFICATE_REL_PATH} not found. Run \`qfai prototyping certify\` after all gates pass.`
11682
+ ]
11683
+ };
11684
+ }
11685
+ const reasons = [];
11686
+ const evidenceRoot = import_node_path39.default.join(root, ".qfai/evidence/prototyping");
11687
+ const currentDigests = await scanEvidenceDigests(evidenceRoot);
11688
+ const certMap = new Map(cert.evidenceDigests.map((entry) => [entry.path, entry.sha256]));
11689
+ const currMap = new Map(currentDigests.map((entry) => [entry.path, entry.sha256]));
11690
+ for (const [p, hash] of certMap) {
11691
+ const observed = currMap.get(p);
11692
+ if (observed === void 0) {
11693
+ reasons.push(`evidence file removed since certify: ${p}`);
11694
+ } else if (observed !== hash) {
11695
+ reasons.push(`digest mismatch (file modified since certify): ${p}`);
11696
+ }
11697
+ }
11698
+ for (const [p] of currMap) {
11699
+ if (!certMap.has(p)) {
11700
+ reasons.push(`new evidence file present but not in certificate: ${p}`);
11701
+ }
11702
+ }
11703
+ if (cert.validateRun.errorCount !== 0) {
11704
+ reasons.push("validateRun.errorCount must be 0");
11705
+ }
11706
+ if (cert.verifyRun.status !== "PASS") {
11707
+ reasons.push("verifyRun.status must be PASS");
11708
+ }
11709
+ if (!cert.reviewerSignoff.approved) {
11710
+ reasons.push("reviewerSignoff.approved must be true");
11711
+ }
11712
+ return reasons.length === 0 ? { ok: true } : { ok: false, reasons };
11713
+ }
11714
+ async function scanEvidenceDigests(evidenceRoot) {
11715
+ const out = [];
11716
+ await walk2(evidenceRoot, evidenceRoot, out);
11717
+ out.sort((a, b) => a.path.localeCompare(b.path));
11718
+ return out;
11719
+ }
11720
+ async function walk2(rootDir, dir, out) {
11721
+ let entries;
11722
+ try {
11723
+ entries = await (0, import_promises36.readdir)(dir);
11724
+ } catch {
11725
+ return;
11726
+ }
11727
+ for (const name of entries) {
11728
+ if (name === "completion-certificate.json") continue;
11729
+ const full = import_node_path39.default.join(dir, name);
11730
+ let s;
11731
+ try {
11732
+ s = await (0, import_promises36.stat)(full);
11733
+ } catch {
11734
+ continue;
11735
+ }
11736
+ if (s.isDirectory()) {
11737
+ await walk2(rootDir, full, out);
11738
+ } else if (s.isFile()) {
11739
+ const buf = await (0, import_promises36.readFile)(full);
11740
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
11741
+ const rel = import_node_path39.default.relative(rootDir, full).replace(/\\/g, "/");
11742
+ out.push({ path: rel, sha256: hash });
11743
+ }
11744
+ }
11745
+ }
11746
+
11747
+ // src/core/validators/prototyping/completionCertificate.ts
11748
+ init_utils();
11749
+ var CERT_REL_PATH = ".qfai/evidence/prototyping/completion-certificate.json";
11750
+ function isRecord13(value) {
11751
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11752
+ }
11753
+ async function isCompletionClaimed(root, config) {
11754
+ const evidenceRoot = import_node_path40.default.join(
11755
+ import_node_path40.default.dirname(import_node_path40.default.resolve(root, config.paths.specsDir)),
11756
+ "evidence"
11757
+ );
11758
+ let raw;
11759
+ try {
11760
+ raw = await (0, import_promises37.readFile)(import_node_path40.default.join(evidenceRoot, "prototyping.json"), "utf-8");
11761
+ } catch {
11762
+ return false;
11763
+ }
11764
+ let parsed;
11765
+ try {
11766
+ parsed = JSON.parse(raw);
11767
+ } catch {
11768
+ return false;
11769
+ }
11770
+ if (!isRecord13(parsed)) return false;
11771
+ if (parsed.completionClaimed === true) return true;
11772
+ if (parsed.phase === "completed") return true;
11773
+ if (isRecord13(parsed.completionCertificate)) return true;
11774
+ return false;
11775
+ }
11776
+ async function validateCompletionCertificateIssues(root, config) {
11777
+ const certificate = await loadCompletionCertificate(root);
11778
+ const claimed = await isCompletionClaimed(root, config);
11779
+ if (!certificate) {
11780
+ if (claimed) {
11781
+ return [
11782
+ issue(
11783
+ "QFAI-PROT-335",
11784
+ `${CERT_REL_PATH} is required when completion is claimed. Run \`qfai prototyping certify\` after all gates pass.`,
11785
+ "error",
11786
+ CERT_REL_PATH,
11787
+ "prototyping.completionCertificate.presence",
11788
+ void 0,
11789
+ "canonical",
11790
+ "completion \u4E3B\u5F35\u306E\u524D\u306B `qfai prototyping certify` \u3092\u6210\u529F\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002"
11791
+ )
11792
+ ];
11793
+ }
11794
+ return [];
11795
+ }
11796
+ if (!claimed) return [];
11797
+ const result = await checkCompletionCertificate(root);
11798
+ if (result.ok) return [];
11799
+ return [
11800
+ issue(
11801
+ "QFAI-PROT-336",
11802
+ `${CERT_REL_PATH} digest mismatch \u2014 evidence has been modified since certify: ${result.reasons.join("; ")}`,
11803
+ "error",
11804
+ CERT_REL_PATH,
11805
+ "prototyping.completionCertificate.digest",
11806
+ void 0,
11807
+ "canonical",
11808
+ "evidence \u3092\u5909\u66F4\u3057\u305F\u5834\u5408\u306F `qfai prototyping certify` \u3092\u518D\u5B9F\u884C\u3057\u3066 certificate \u3092\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11809
+ )
11810
+ ];
11811
+ }
11812
+
11813
+ // src/core/validators/configReferenceIntegrity.ts
11814
+ var import_promises38 = require("fs/promises");
11815
+ var import_node_path41 = __toESM(require("path"), 1);
11816
+ init_utils();
11817
+ async function isDirectory(absolutePath) {
11818
+ try {
11819
+ const s = await (0, import_promises38.stat)(absolutePath);
11820
+ return s.isDirectory();
11821
+ } catch {
11822
+ return false;
11823
+ }
11824
+ }
11825
+ async function pathExists(absolutePath) {
11826
+ try {
11827
+ await (0, import_promises38.stat)(absolutePath);
11828
+ return true;
11829
+ } catch {
11830
+ return false;
11831
+ }
11832
+ }
11833
+ var VERIFIED_PATH_KEYS = [
11834
+ "specsDir",
11835
+ "contractsDir",
11836
+ "discussionDir",
11837
+ "skillsDir",
11838
+ "srcDir",
11839
+ "testsDir"
11840
+ ];
11841
+ async function validateConfigReferenceIntegrity(root, config) {
11842
+ const issues = [];
11843
+ const primarySpecId = config.prototyping?.primarySpecId;
11844
+ if (primarySpecId !== void 0) {
11845
+ const specDir = import_node_path41.default.join(import_node_path41.default.resolve(root, config.paths.specsDir), `spec-${primarySpecId}`);
11846
+ if (!await isDirectory(specDir)) {
11847
+ const relSpecDir = import_node_path41.default.relative(root, specDir).replace(/\\/g, "/");
11848
+ issues.push(
11849
+ issue(
11850
+ "QFAI-CFG-LINK-001",
11851
+ `qfai.config.yaml: prototyping.primarySpecId="${primarySpecId}" but ${relSpecDir} does not exist.`,
11852
+ "error",
11853
+ "qfai.config.yaml",
11854
+ "config.prototyping.primarySpecId.reality",
11855
+ void 0,
11856
+ "canonical",
11857
+ "prototyping.primarySpecId \u3092\u5B9F\u5728\u3059\u308B spec-NNNN \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u5408\u308F\u305B\u308B\u304B\u3001\u5BFE\u5FDC\u3059\u308B spec \u3092 `.qfai/specs/spec-NNNN/` \u306B\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11858
+ )
11859
+ );
11860
+ }
11861
+ }
11862
+ for (const key of VERIFIED_PATH_KEYS) {
11863
+ const relPath = config.paths[key];
11864
+ const absolutePath = import_node_path41.default.resolve(root, relPath);
11865
+ if (!await isDirectory(absolutePath)) {
11866
+ issues.push(
11867
+ issue(
11868
+ "QFAI-CFG-LINK-002",
11869
+ `qfai.config.yaml: paths.${key}="${relPath}" but the directory does not exist.`,
11870
+ "warning",
11871
+ "qfai.config.yaml",
11872
+ `config.paths.${key}.reality`,
11873
+ void 0,
11874
+ "canonical",
11875
+ `paths.${key} \u3092\u5B9F\u5728\u3059\u308B\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u5408\u308F\u305B\u308B\u304B\u3001\u5BFE\u5FDC\u3059\u308B\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
11876
+ )
11877
+ );
11878
+ }
11879
+ }
11880
+ const packPath = config.prototyping?.calibration?.packPath;
11881
+ if (packPath !== void 0) {
11882
+ const absolutePackPath = import_node_path41.default.resolve(root, packPath);
11883
+ const exists9 = await pathExists(absolutePackPath);
11884
+ if (!exists9) {
11885
+ issues.push(
11886
+ issue(
11887
+ "QFAI-CFG-LINK-003",
11888
+ `qfai.config.yaml: prototyping.calibration.packPath="${packPath}" but the path does not exist on disk.`,
11889
+ "error",
11890
+ "qfai.config.yaml",
11891
+ "config.prototyping.calibration.packPath.reality",
11892
+ void 0,
11893
+ "canonical",
11894
+ "prototyping.calibration.packPath \u3092\u5B9F\u5728\u3059\u308B calibration pack (YAML \u30D5\u30A1\u30A4\u30EB \u307E\u305F\u306F \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA) \u306B\u5408\u308F\u305B\u3066\u304F\u3060\u3055\u3044\u3002"
11895
+ )
11896
+ );
11897
+ }
11898
+ }
11899
+ return issues;
11900
+ }
11901
+
11902
+ // src/core/validators/prototyping/refIntegrity.ts
11903
+ var import_promises39 = require("fs/promises");
11904
+ var import_node_path42 = __toESM(require("path"), 1);
11905
+ init_utils();
11906
+ function isRecord14(value) {
11907
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11908
+ }
11909
+ async function fileExists3(absolutePath) {
11910
+ try {
11911
+ const s = await (0, import_promises39.stat)(absolutePath);
11912
+ return s.isFile();
11913
+ } catch {
11914
+ return false;
11915
+ }
11916
+ }
11917
+ async function loadJson(absolutePath) {
11918
+ try {
11919
+ const raw = await (0, import_promises39.readFile)(absolutePath, "utf-8");
11920
+ return JSON.parse(raw);
11921
+ } catch {
11922
+ return null;
11923
+ }
11924
+ }
11925
+ function collectPrototypingJsonRefs(prototypingJson, sourcePath) {
11926
+ const out = [];
11927
+ if (!isRecord14(prototypingJson)) return out;
11928
+ const push = (ref, field) => {
11929
+ if (typeof ref === "string" && ref.length > 0) {
11930
+ out.push({ ref, source: sourcePath, field });
11931
+ }
11932
+ };
11933
+ const rounds = prototypingJson.rounds;
11934
+ if (Array.isArray(rounds)) {
11935
+ for (let i = 0; i < rounds.length; i++) {
11936
+ const round = rounds[i];
11937
+ if (!isRecord14(round)) continue;
11938
+ push(round.harvestRef, `rounds[${i}].harvestRef`);
11939
+ push(round.narrowDecisionRef, `rounds[${i}].narrowDecisionRef`);
11940
+ push(round.absorptionPlanRef, `rounds[${i}].absorptionPlanRef`);
11941
+ push(round.reimplementationRef, `rounds[${i}].reimplementationRef`);
11942
+ const evalMap = round.evaluatorReviewRefsByCandidate;
11943
+ if (isRecord14(evalMap)) {
11944
+ for (const [cid, ref] of Object.entries(evalMap)) {
11945
+ push(ref, `rounds[${i}].evaluatorReviewRefsByCandidate.${cid}`);
11946
+ }
11947
+ }
11948
+ const screenMap = round.screenEvidenceByCandidate;
11949
+ if (isRecord14(screenMap)) {
11950
+ for (const [cid, candidateScreens] of Object.entries(screenMap)) {
11951
+ if (!isRecord14(candidateScreens)) continue;
11952
+ for (const [screenId, refs] of Object.entries(candidateScreens)) {
11953
+ if (!isRecord14(refs)) continue;
11954
+ push(
11955
+ refs.screenshotRef,
11956
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.screenshotRef`
11957
+ );
11958
+ push(refs.htmlRef, `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.htmlRef`);
11959
+ push(
11960
+ refs.snapshotRef,
11961
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.snapshotRef`
11962
+ );
11963
+ push(
11964
+ refs.commandLogRef,
11965
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.commandLogRef`
11966
+ );
11967
+ }
11968
+ }
11969
+ }
11970
+ }
11971
+ }
11972
+ const polishCycles = prototypingJson.polishCycles;
11973
+ if (Array.isArray(polishCycles)) {
11974
+ for (let i = 0; i < polishCycles.length; i++) {
11975
+ const cycle = polishCycles[i];
11976
+ if (!isRecord14(cycle)) continue;
11977
+ push(cycle.breakthroughRef, `polishCycles[${i}].breakthroughRef`);
11978
+ push(cycle.reviewerGateRef, `polishCycles[${i}].reviewerGateRef`);
11979
+ push(cycle.evaluatorReviewRef, `polishCycles[${i}].evaluatorReviewRef`);
11980
+ }
11981
+ }
11982
+ const bestOfHistory = prototypingJson.bestOfHistory;
11983
+ if (isRecord14(bestOfHistory)) {
11984
+ push(bestOfHistory.evidenceRef, "bestOfHistory.evidenceRef");
11985
+ }
11986
+ const breakthrough = prototypingJson.breakthrough;
11987
+ if (isRecord14(breakthrough)) {
11988
+ push(breakthrough.evidenceRef, "breakthrough.evidenceRef");
11989
+ }
11990
+ const reviewerGate = prototypingJson.reviewerGate;
11991
+ if (isRecord14(reviewerGate)) {
11992
+ push(reviewerGate.evidenceRef, "reviewerGate.evidenceRef");
11993
+ const signoff = reviewerGate.signoff;
11994
+ if (isRecord14(signoff)) {
11995
+ push(signoff.evidenceRef, "reviewerGate.signoff.evidenceRef");
11996
+ }
11997
+ }
11998
+ const fullHarness = prototypingJson.fullHarness;
11999
+ if (isRecord14(fullHarness) && Array.isArray(fullHarness.iterations)) {
12000
+ const iterations = fullHarness.iterations;
12001
+ for (let i = 0; i < iterations.length; i++) {
12002
+ const it = iterations[i];
12003
+ if (!isRecord14(it)) continue;
12004
+ const er = it.evidenceRefs;
12005
+ if (!isRecord14(er)) continue;
12006
+ for (const [key, refs] of Object.entries(er)) {
12007
+ if (!Array.isArray(refs)) continue;
12008
+ for (let j = 0; j < refs.length; j++) {
12009
+ const refValue = refs[j];
12010
+ push(refValue, `fullHarness.iterations[${i}].evidenceRefs.${key}[${j}]`);
12011
+ }
12012
+ }
12013
+ }
12014
+ }
12015
+ return out;
12016
+ }
12017
+ function collectReviewBundleRefs(bundle, sourcePath) {
12018
+ const out = [];
12019
+ if (!isRecord14(bundle)) return out;
12020
+ const push = (ref, field) => {
12021
+ if (typeof ref === "string" && ref.length > 0) {
12022
+ out.push({ ref, source: sourcePath, field });
12023
+ }
12024
+ };
12025
+ push(bundle.axisDefsRef, "axisDefsRef");
12026
+ push(bundle.designSystemChecklistRef, "designSystemChecklistRef");
12027
+ push(bundle.commandPlanRef, "commandPlanRef");
12028
+ const candidates = bundle.candidates;
12029
+ if (Array.isArray(candidates)) {
12030
+ for (let i = 0; i < candidates.length; i++) {
12031
+ const candidate = candidates[i];
12032
+ if (!isRecord14(candidate)) continue;
12033
+ push(candidate.conceptRef, `candidates[${i}].conceptRef`);
12034
+ push(candidate.previousEvaluatorReviewRef, `candidates[${i}].previousEvaluatorReviewRef`);
12035
+ }
12036
+ }
12037
+ return out;
12038
+ }
12039
+ function collectBreakthroughRefs(breakthroughJson, sourcePath) {
12040
+ const out = [];
12041
+ if (!isRecord14(breakthroughJson)) return out;
12042
+ const branchRefs = breakthroughJson.branchRefs;
12043
+ if (Array.isArray(branchRefs)) {
12044
+ for (let i = 0; i < branchRefs.length; i++) {
12045
+ const ref = branchRefs[i];
12046
+ if (typeof ref === "string" && ref.length > 0) {
12047
+ out.push({ ref, source: sourcePath, field: `branchRefs[${i}]` });
12048
+ }
12049
+ }
12050
+ }
12051
+ return out;
12052
+ }
12053
+ async function validatePrototypingArtifactRefIntegrity(root, config) {
12054
+ const issues = [];
12055
+ const evidenceRoot = import_node_path42.default.join(import_node_path42.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
12056
+ const collectedRefs = [];
12057
+ const protoPath = import_node_path42.default.join(evidenceRoot, "prototyping.json");
12058
+ const protoJson = await loadJson(protoPath);
12059
+ if (protoJson) {
12060
+ const protoRel = import_node_path42.default.relative(root, protoPath).replace(/\\/g, "/");
12061
+ collectedRefs.push(...collectPrototypingJsonRefs(protoJson, protoRel));
12062
+ }
12063
+ for (const round of EXPLORATION_ROUNDS) {
12064
+ const bundlePath = import_node_path42.default.join(
12065
+ evidenceRoot,
12066
+ "prototyping",
12067
+ "rounds",
12068
+ round,
12069
+ "review-bundle.json"
12070
+ );
12071
+ const bundleJson = await loadJson(bundlePath);
12072
+ if (bundleJson) {
12073
+ const bundleRel = import_node_path42.default.relative(root, bundlePath).replace(/\\/g, "/");
12074
+ collectedRefs.push(...collectReviewBundleRefs(bundleJson, bundleRel));
12075
+ }
12076
+ }
12077
+ const breakthroughPath = import_node_path42.default.join(evidenceRoot, "prototyping", "breakthrough.json");
12078
+ const breakthroughJson = await loadJson(breakthroughPath);
12079
+ if (breakthroughJson) {
12080
+ const breakthroughRel = import_node_path42.default.relative(root, breakthroughPath).replace(/\\/g, "/");
12081
+ collectedRefs.push(...collectBreakthroughRefs(breakthroughJson, breakthroughRel));
12082
+ }
12083
+ for (const ref of collectedRefs) {
12084
+ const candidatePath = import_node_path42.default.isAbsolute(ref.ref) ? ref.ref : import_node_path42.default.join(root, ref.ref);
12085
+ if (!await fileExists3(candidatePath)) {
12086
+ issues.push(
12087
+ issue(
12088
+ "QFAI-PROT-REF-001",
12089
+ `${ref.source}: ${ref.field}="${ref.ref}" points to a file that does not exist on disk.`,
12090
+ "warning",
12091
+ ref.source,
12092
+ "prototyping.artifactRef.reality",
12093
+ void 0,
12094
+ "canonical",
12095
+ "ref \u3092\u5B9F\u5728\u3059\u308B\u30D5\u30A1\u30A4\u30EB\u306B\u5408\u308F\u305B\u308B\u304B\u3001\u6B20\u843D artifact \u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044 (round-* / verify CLI \u3092\u518D\u5B9F\u884C)\u3002"
12096
+ )
12097
+ );
12098
+ }
12099
+ }
12100
+ return issues;
12101
+ }
12102
+
12103
+ // src/core/validators/prototyping/specIdLinkage.ts
12104
+ var import_promises40 = require("fs/promises");
12105
+ var import_node_path43 = __toESM(require("path"), 1);
12106
+ init_utils();
12107
+ function isRecord15(value) {
12108
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12109
+ }
12110
+ async function isDirectory2(absolutePath) {
12111
+ try {
12112
+ const s = await (0, import_promises40.stat)(absolutePath);
12113
+ return s.isDirectory();
12114
+ } catch {
12115
+ return false;
12116
+ }
12117
+ }
12118
+ async function loadJson2(absolutePath) {
12119
+ try {
12120
+ const raw = await (0, import_promises40.readFile)(absolutePath, "utf-8");
12121
+ return JSON.parse(raw);
12122
+ } catch {
12123
+ return null;
12124
+ }
12125
+ }
12126
+ function normalizeSpecId(raw) {
12127
+ if (typeof raw !== "string") return void 0;
12128
+ const trimmed = raw.trim();
12129
+ const direct = /^(\d{4})$/.exec(trimmed);
12130
+ if (direct?.[1]) return direct[1];
12131
+ const prefixed = /^spec-(\d{4})$/i.exec(trimmed);
12132
+ if (prefixed?.[1]) return prefixed[1];
12133
+ return void 0;
12134
+ }
12135
+ async function validateSpecIdLinkage(root, config) {
12136
+ const issues = [];
12137
+ const specsRoot = resolvePath(root, config, "specsDir");
12138
+ const evidenceRoot = import_node_path43.default.join(import_node_path43.default.dirname(specsRoot), "evidence");
12139
+ const specEntries = await collectSpecEntries(specsRoot);
12140
+ const knownSpecIds = new Set(specEntries.map((e) => e.specNumber));
12141
+ const protoPath = import_node_path43.default.join(evidenceRoot, "prototyping.json");
12142
+ const protoJson = await loadJson2(protoPath);
12143
+ if (isRecord15(protoJson)) {
12144
+ const protoRel = import_node_path43.default.relative(root, protoPath).replace(/\\/g, "/");
12145
+ const specs = protoJson.specs;
12146
+ if (Array.isArray(specs)) {
12147
+ for (let i = 0; i < specs.length; i++) {
12148
+ const entry = specs[i];
12149
+ if (!isRecord15(entry)) continue;
12150
+ const rawSpecId = entry.specId;
12151
+ if (rawSpecId === void 0) continue;
12152
+ const id = normalizeSpecId(rawSpecId);
12153
+ if (id === void 0) {
12154
+ issues.push(
12155
+ issue(
12156
+ "QFAI-PROT-LINK-001",
12157
+ `${protoRel}: specs[${i}].specId="${JSON.stringify(rawSpecId)}" is malformed (expected 4-digit "NNNN" or "spec-NNNN").`,
12158
+ "warning",
12159
+ protoRel,
12160
+ "prototyping.specs.idReality",
12161
+ void 0,
12162
+ "canonical",
12163
+ 'specs[].specId \u306F 4 \u6841\u306E "NNNN" \u5F62\u5F0F\u307E\u305F\u306F "spec-NNNN" \u5F62\u5F0F\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002'
12164
+ )
12165
+ );
12166
+ continue;
12167
+ }
12168
+ if (!knownSpecIds.has(id)) {
12169
+ issues.push(
12170
+ issue(
12171
+ "QFAI-PROT-LINK-001",
12172
+ `${protoRel}: specs[${i}].specId="${JSON.stringify(rawSpecId)}" but spec-${id} does not exist under ${import_node_path43.default.relative(root, specsRoot).replace(/\\/g, "/")}/.`,
12173
+ "warning",
12174
+ protoRel,
12175
+ "prototyping.specs.idReality",
12176
+ void 0,
12177
+ "canonical",
12178
+ "\u5BFE\u5FDC\u3059\u308B spec-NNNN \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u4F5C\u6210\u3059\u308B\u304B\u3001specs[].specId \u5024\u3092\u5B9F\u5728\u306E spec \u306B\u5408\u308F\u305B\u3066\u304F\u3060\u3055\u3044\u3002"
12179
+ )
12180
+ );
12181
+ }
12182
+ }
12183
+ }
12184
+ const rounds = protoJson.rounds;
12185
+ if (Array.isArray(rounds)) {
12186
+ for (let i = 0; i < rounds.length; i++) {
12187
+ const round = rounds[i];
12188
+ if (!isRecord15(round)) continue;
12189
+ const roundName = round.round;
12190
+ const candidates = round.candidates;
12191
+ if (typeof roundName !== "string" || !Array.isArray(candidates)) continue;
12192
+ for (let j = 0; j < candidates.length; j++) {
12193
+ const candidate = candidates[j];
12194
+ if (!isRecord15(candidate)) continue;
12195
+ const candidateId = candidate.candidateId;
12196
+ if (typeof candidateId !== "string") continue;
12197
+ const candidateDir = import_node_path43.default.join(
12198
+ evidenceRoot,
12199
+ "prototyping",
12200
+ "rounds",
12201
+ roundName,
12202
+ "candidates",
12203
+ candidateId
12204
+ );
12205
+ if (!await isDirectory2(candidateDir)) {
12206
+ const relCandidateDir = import_node_path43.default.relative(root, candidateDir).replace(/\\/g, "/");
12207
+ issues.push(
12208
+ issue(
12209
+ "QFAI-PROT-LINK-003",
12210
+ `${protoRel}: rounds[${i}].candidates[${j}].candidateId="${candidateId}" but ${relCandidateDir} does not exist.`,
12211
+ "warning",
12212
+ protoRel,
12213
+ "prototyping.candidates.dirReality",
12214
+ void 0,
12215
+ "canonical",
12216
+ "candidateId \u306B\u5BFE\u5FDC\u3059\u308B artifact \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092 `qfai prototyping round-start` \u3067\u751F\u6210\u3059\u308B\u304B\u3001\u7121\u52B9\u306A candidate \u3092 index \u304B\u3089\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
12217
+ )
12218
+ );
12219
+ }
12220
+ }
12221
+ }
12222
+ }
12223
+ const polishCycles = protoJson.polishCycles;
12224
+ if (Array.isArray(polishCycles)) {
12225
+ for (let i = 0; i < polishCycles.length; i++) {
12226
+ const cycle = polishCycles[i];
12227
+ if (!isRecord15(cycle)) continue;
12228
+ const cycleNum = cycle.cycle;
12229
+ if (typeof cycleNum !== "number" || cycleNum < 1) continue;
12230
+ const iterationDir = import_node_path43.default.join(evidenceRoot, "prototyping", "iterations", String(cycleNum));
12231
+ if (!await isDirectory2(iterationDir)) {
12232
+ const relIterationDir = import_node_path43.default.relative(root, iterationDir).replace(/\\/g, "/");
12233
+ issues.push(
12234
+ issue(
12235
+ "QFAI-PROT-LINK-004",
12236
+ `${protoRel}: polishCycles[${i}].cycle=${cycleNum} but ${relIterationDir} does not exist.`,
12237
+ "warning",
12238
+ protoRel,
12239
+ "prototyping.polishCycles.iterationDirReality",
12240
+ void 0,
12241
+ "canonical",
12242
+ "polish cycle \u306B\u5BFE\u5FDC\u3059\u308B iteration \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u518D\u751F\u6210\u3059\u308B\u304B\u3001\u7121\u52B9\u306A polish cycle \u3092 index \u304B\u3089\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
12243
+ )
12244
+ );
12245
+ }
12246
+ }
12247
+ }
10549
12248
  }
10550
- const evidenceDir = resolveEvidenceRoot(root, config);
10551
- const artifact = await loadPrototypingArtifact(evidenceDir);
10552
- if (artifact.designSystemCompliancePresent) {
10553
- return [];
12249
+ for (const round of EXPLORATION_ROUNDS) {
12250
+ const bundlePath = import_node_path43.default.join(root, ...roundReviewBundlePath(round).split("/"));
12251
+ const bundleJson = await loadJson2(bundlePath);
12252
+ if (!isRecord15(bundleJson)) continue;
12253
+ const id = normalizeSpecId(bundleJson.spec);
12254
+ if (id !== void 0 && !knownSpecIds.has(id)) {
12255
+ const bundleRel = import_node_path43.default.relative(root, bundlePath).replace(/\\/g, "/");
12256
+ issues.push(
12257
+ issue(
12258
+ "QFAI-PROT-LINK-002",
12259
+ `${bundleRel}: spec="${String(bundleJson.spec)}" but spec-${id} does not exist under ${import_node_path43.default.relative(root, specsRoot).replace(/\\/g, "/")}/.`,
12260
+ "error",
12261
+ bundleRel,
12262
+ "prototyping.reviewBundle.specReality",
12263
+ void 0,
12264
+ "canonical",
12265
+ "review-bundle.json \u306E spec \u3092 `qfai prototyping show-spec` \u306E\u7D50\u679C\u306B\u5408\u308F\u305B\u3066\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
12266
+ )
12267
+ );
12268
+ }
10554
12269
  }
10555
- const designSystemPath = import_node_path36.default.join(
10556
- root,
10557
- config.paths.contractsDir,
10558
- "design",
10559
- "design-system.yaml"
10560
- );
10561
- const designSystemPresent = await fileExists2(designSystemPath);
10562
- const severity = designSystemPresent && artifact.mode === "full-harness" ? "error" : "warning";
10563
- return [toIssue(severity, artifact.fileName)];
12270
+ return issues;
10564
12271
  }
10565
12272
 
10566
12273
  // src/core/validators/requireIndex.ts
10567
- var import_promises34 = require("fs/promises");
10568
- var import_node_path37 = __toESM(require("path"), 1);
12274
+ var import_promises41 = require("fs/promises");
12275
+ var import_node_path44 = __toESM(require("path"), 1);
10569
12276
  init_utils();
10570
12277
 
10571
12278
  // src/core/validators/repositoryHygiene.ts
10572
- var import_promises35 = require("fs/promises");
10573
- var import_node_path38 = __toESM(require("path"), 1);
12279
+ var import_promises42 = require("fs/promises");
12280
+ var import_node_path45 = __toESM(require("path"), 1);
10574
12281
  init_utils();
10575
12282
  var LEGACY_DIR_RULES = [
10576
12283
  { legacy: "discussions", canonical: "discussion" },
@@ -10582,12 +12289,12 @@ var LEGACY_DIR_RULES = [
10582
12289
  ];
10583
12290
  var SUSPICIOUS_TEMPLATE_NAME_RE = /^(?:_?templates?|_?sample(?:s)?|sample-template)$/i;
10584
12291
  async function validateRepositoryHygiene(root, config) {
10585
- const qfaiRoot = import_node_path38.default.join(root, ".qfai");
12292
+ const qfaiRoot = import_node_path45.default.join(root, ".qfai");
10586
12293
  const specsRoot = resolvePath(root, config, "specsDir");
10587
12294
  const issues = [];
10588
12295
  for (const rule of LEGACY_DIR_RULES) {
10589
- const legacyPath = import_node_path38.default.join(qfaiRoot, rule.legacy);
10590
- if (!await isDirectory(legacyPath)) {
12296
+ const legacyPath = import_node_path45.default.join(qfaiRoot, rule.legacy);
12297
+ if (!await isDirectory3(legacyPath)) {
10591
12298
  continue;
10592
12299
  }
10593
12300
  issues.push(
@@ -10621,7 +12328,7 @@ async function validateRepositoryHygiene(root, config) {
10621
12328
  return issues;
10622
12329
  }
10623
12330
  async function collectSuspiciousTemplatePaths(root) {
10624
- if (!await isDirectory(root)) {
12331
+ if (!await isDirectory3(root)) {
10625
12332
  return [];
10626
12333
  }
10627
12334
  const matches = [];
@@ -10633,7 +12340,7 @@ async function collectSuspiciousTemplatePaths(root) {
10633
12340
  }
10634
12341
  let entries = [];
10635
12342
  try {
10636
- entries = await (0, import_promises35.readdir)(current, {
12343
+ entries = await (0, import_promises42.readdir)(current, {
10637
12344
  withFileTypes: true,
10638
12345
  encoding: "utf8"
10639
12346
  });
@@ -10641,10 +12348,10 @@ async function collectSuspiciousTemplatePaths(root) {
10641
12348
  continue;
10642
12349
  }
10643
12350
  for (const entry of entries) {
10644
- const absolute = import_node_path38.default.join(current, entry.name);
12351
+ const absolute = import_node_path45.default.join(current, entry.name);
10645
12352
  if (entry.isDirectory()) {
10646
12353
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(entry.name)) {
10647
- matches.push(toPosix(import_node_path38.default.relative(root, absolute)));
12354
+ matches.push(toPosix(import_node_path45.default.relative(root, absolute)));
10648
12355
  }
10649
12356
  queue.push(absolute);
10650
12357
  continue;
@@ -10652,17 +12359,17 @@ async function collectSuspiciousTemplatePaths(root) {
10652
12359
  if (!entry.isFile()) {
10653
12360
  continue;
10654
12361
  }
10655
- const baseName = import_node_path38.default.parse(entry.name).name;
12362
+ const baseName = import_node_path45.default.parse(entry.name).name;
10656
12363
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(baseName)) {
10657
- matches.push(toPosix(import_node_path38.default.relative(root, absolute)));
12364
+ matches.push(toPosix(import_node_path45.default.relative(root, absolute)));
10658
12365
  }
10659
12366
  }
10660
12367
  }
10661
12368
  return matches.sort((left, right) => left.localeCompare(right));
10662
12369
  }
10663
- async function isDirectory(target) {
12370
+ async function isDirectory3(target) {
10664
12371
  try {
10665
- return (await (0, import_promises35.stat)(target)).isDirectory();
12372
+ return (await (0, import_promises42.stat)(target)).isDirectory();
10666
12373
  } catch {
10667
12374
  return false;
10668
12375
  }
@@ -10672,7 +12379,7 @@ function toPosix(value) {
10672
12379
  }
10673
12380
 
10674
12381
  // src/core/validators/specSplitByCapability.ts
10675
- var import_node_path39 = __toESM(require("path"), 1);
12382
+ var import_node_path46 = __toESM(require("path"), 1);
10676
12383
  init_utils();
10677
12384
  var CAP_ID_RE2 = /\bCAP-\d{4}\b/g;
10678
12385
  async function validateSpecSplitByCapability(root, config) {
@@ -10684,8 +12391,8 @@ async function validateSpecSplitByCapability(root, config) {
10684
12391
  if (layeredEntries.length === 0) {
10685
12392
  return [];
10686
12393
  }
10687
- const policiesDir = layeredEntries[0]?.sharedDir ?? import_node_path39.default.join(specsRoot, "_policies");
10688
- const capabilitiesPath = import_node_path39.default.join(policiesDir, "03_Capabilities.md");
12394
+ const policiesDir = layeredEntries[0]?.sharedDir ?? import_node_path46.default.join(specsRoot, "_policies");
12395
+ const capabilitiesPath = import_node_path46.default.join(policiesDir, "03_Capabilities.md");
10689
12396
  const capabilityText = await readSafe(capabilitiesPath);
10690
12397
  const issues = [];
10691
12398
  if (!await exists5(capabilitiesPath)) {
@@ -10725,7 +12432,7 @@ async function validateSpecSplitByCapability(root, config) {
10725
12432
  );
10726
12433
  }
10727
12434
  const actualSpecIds = new Set(
10728
- layeredEntries.map((entry) => import_node_path39.default.basename(entry.dir).toLowerCase())
12435
+ layeredEntries.map((entry) => import_node_path46.default.basename(entry.dir).toLowerCase())
10729
12436
  );
10730
12437
  const expectedSpecIds = capIds.map((_, index) => `spec-${to4(index + 1)}`);
10731
12438
  const missingSpecIds = expectedSpecIds.filter((specId) => !actualSpecIds.has(specId));
@@ -10762,11 +12469,11 @@ async function validateSpecSplitByCapability(root, config) {
10762
12469
  continue;
10763
12470
  }
10764
12471
  const specId = `spec-${to4(index + 1)}`;
10765
- const entry = layeredEntries.find((value) => import_node_path39.default.basename(value.dir).toLowerCase() === specId);
12472
+ const entry = layeredEntries.find((value) => import_node_path46.default.basename(value.dir).toLowerCase() === specId);
10766
12473
  if (!entry) {
10767
12474
  continue;
10768
12475
  }
10769
- const specFilePath = import_node_path39.default.join(entry.dir, "01_Spec.md");
12476
+ const specFilePath = import_node_path46.default.join(entry.dir, "01_Spec.md");
10770
12477
  const specText = await readSafe(specFilePath);
10771
12478
  if (specText.trim().length === 0 || !specText.includes(capId)) {
10772
12479
  issues.push(
@@ -10785,8 +12492,8 @@ async function validateSpecSplitByCapability(root, config) {
10785
12492
  }
10786
12493
 
10787
12494
  // src/core/validators/statusInSpecs.ts
10788
- var import_promises36 = require("fs/promises");
10789
- var import_node_path40 = __toESM(require("path"), 1);
12495
+ var import_promises43 = require("fs/promises");
12496
+ var import_node_path47 = __toESM(require("path"), 1);
10790
12497
  init_utils();
10791
12498
  var STRONG_PATTERNS = [
10792
12499
  { label: "release_candidate:", pattern: /\brelease_candidate\s*:/i },
@@ -10844,32 +12551,32 @@ function hasMatch(text, pattern) {
10844
12551
  }
10845
12552
  async function readSafe11(filePath) {
10846
12553
  try {
10847
- return await (0, import_promises36.readFile)(filePath, "utf-8");
12554
+ return await (0, import_promises43.readFile)(filePath, "utf-8");
10848
12555
  } catch {
10849
12556
  return "";
10850
12557
  }
10851
12558
  }
10852
12559
  function isOpenQuestionsFile(filePath) {
10853
- return /open-questions\.md$/i.test(import_node_path40.default.basename(filePath));
12560
+ return /open-questions\.md$/i.test(import_node_path47.default.basename(filePath));
10854
12561
  }
10855
12562
  function isDecisionFile(filePath) {
10856
- return /decisions\.md$/i.test(import_node_path40.default.basename(filePath));
12563
+ return /decisions\.md$/i.test(import_node_path47.default.basename(filePath));
10857
12564
  }
10858
12565
 
10859
12566
  // src/core/validators/breakthroughEvidence.ts
10860
- var import_promises37 = require("fs/promises");
10861
- var import_node_path41 = __toESM(require("path"), 1);
12567
+ var import_promises44 = require("fs/promises");
12568
+ var import_node_path48 = __toESM(require("path"), 1);
10862
12569
  init_utils();
10863
12570
  function toPosixRelative(root, targetPath) {
10864
- return import_node_path41.default.relative(root, targetPath).replace(/\\/g, "/");
12571
+ return import_node_path48.default.relative(root, targetPath).replace(/\\/g, "/");
10865
12572
  }
10866
12573
  async function validateBreakthroughEvidence(root, config) {
10867
12574
  const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
10868
12575
  if (screens.length === 0) {
10869
12576
  return [];
10870
12577
  }
10871
- const evidenceRoot = import_node_path41.default.join(import_node_path41.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
10872
- const evidencePath = import_node_path41.default.join(evidenceRoot, "breakthrough.json");
12578
+ const evidenceRoot = import_node_path48.default.join(import_node_path48.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
12579
+ const evidencePath = import_node_path48.default.join(evidenceRoot, "breakthrough.json");
10873
12580
  const evidenceRelativePath = toPosixRelative(root, evidencePath);
10874
12581
  const raw = await readJsonFile2(evidencePath);
10875
12582
  if (raw.status === "missing") {
@@ -10985,7 +12692,7 @@ function isNonEmptyString2(value) {
10985
12692
  }
10986
12693
  async function readJsonFile2(filePath) {
10987
12694
  try {
10988
- const raw = await (0, import_promises37.readFile)(filePath, "utf-8");
12695
+ const raw = await (0, import_promises44.readFile)(filePath, "utf-8");
10989
12696
  const parsed = JSON.parse(raw);
10990
12697
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10991
12698
  return { status: "invalid" };
@@ -10993,7 +12700,7 @@ async function readJsonFile2(filePath) {
10993
12700
  return { status: "ok", value: parsed };
10994
12701
  } catch {
10995
12702
  try {
10996
- await (0, import_promises37.readFile)(filePath, "utf-8");
12703
+ await (0, import_promises44.readFile)(filePath, "utf-8");
10997
12704
  return { status: "invalid" };
10998
12705
  } catch {
10999
12706
  return { status: "missing" };
@@ -11002,8 +12709,8 @@ async function readJsonFile2(filePath) {
11002
12709
  }
11003
12710
 
11004
12711
  // src/core/validators/designToken.ts
11005
- var import_promises38 = require("fs/promises");
11006
- var import_node_path42 = __toESM(require("path"), 1);
12712
+ var import_promises45 = require("fs/promises");
12713
+ var import_node_path49 = __toESM(require("path"), 1);
11007
12714
  var import_fast_glob3 = __toESM(require("fast-glob"), 1);
11008
12715
  var import_yaml7 = require("yaml");
11009
12716
 
@@ -11050,7 +12757,7 @@ function collectLayer(layer, layerName, target, errors) {
11050
12757
  }
11051
12758
  function flattenTokens(obj, prefix, target, errors) {
11052
12759
  for (const [key, value] of Object.entries(obj)) {
11053
- const path76 = `${prefix}.${key}`;
12760
+ const path84 = `${prefix}.${key}`;
11054
12761
  if (value && typeof value === "object" && !Array.isArray(value)) {
11055
12762
  const record2 = value;
11056
12763
  if ("$value" in record2) {
@@ -11066,9 +12773,9 @@ function flattenTokens(obj, prefix, target, errors) {
11066
12773
  if (typeof record2.platform === "string") {
11067
12774
  token.platform = record2.platform;
11068
12775
  }
11069
- target.set(path76, token);
12776
+ target.set(path84, token);
11070
12777
  } else {
11071
- flattenTokens(record2, path76, target, errors);
12778
+ flattenTokens(record2, path84, target, errors);
11072
12779
  }
11073
12780
  }
11074
12781
  }
@@ -11078,44 +12785,44 @@ function resolveAllReferences(result) {
11078
12785
  for (const [key, val] of result.primitives) allTokens.set(key, val);
11079
12786
  for (const [key, val] of result.semantics) allTokens.set(key, val);
11080
12787
  for (const [key, val] of result.components) allTokens.set(key, val);
11081
- for (const [path76] of allTokens) {
11082
- resolveTokenRef(path76, allTokens, /* @__PURE__ */ new Set(), 0, result);
12788
+ for (const [path84] of allTokens) {
12789
+ resolveTokenRef(path84, allTokens, /* @__PURE__ */ new Set(), 0, result);
11083
12790
  }
11084
12791
  }
11085
- function resolveTokenRef(path76, allTokens, visited, depth, result) {
11086
- if (result.resolved.has(path76)) {
11087
- return result.resolved.get(path76);
12792
+ function resolveTokenRef(path84, allTokens, visited, depth, result) {
12793
+ if (result.resolved.has(path84)) {
12794
+ return result.resolved.get(path84);
11088
12795
  }
11089
12796
  if (depth > MAX_RESOLVE_DEPTH) {
11090
12797
  result.errors.push({
11091
- message: `Max reference depth exceeded at: ${path76}`,
11092
- path: path76
12798
+ message: `Max reference depth exceeded at: ${path84}`,
12799
+ path: path84
11093
12800
  });
11094
12801
  return void 0;
11095
12802
  }
11096
- if (visited.has(path76)) {
12803
+ if (visited.has(path84)) {
11097
12804
  result.errors.push({
11098
- message: `Circular reference detected: ${path76}`,
11099
- path: path76
12805
+ message: `Circular reference detected: ${path84}`,
12806
+ path: path84
11100
12807
  });
11101
12808
  return void 0;
11102
12809
  }
11103
- const token = allTokens.get(path76);
12810
+ const token = allTokens.get(path84);
11104
12811
  if (!token) {
11105
12812
  return void 0;
11106
12813
  }
11107
12814
  if (typeof token.$value !== "string") {
11108
12815
  const rawValue2 = stringifyTokenValue(token.$value);
11109
- result.resolved.set(path76, rawValue2);
12816
+ result.resolved.set(path84, rawValue2);
11110
12817
  return rawValue2;
11111
12818
  }
11112
12819
  const rawValue = stringifyTokenValue(token.$value);
11113
12820
  const refs = [...rawValue.matchAll(REF_PATTERN)];
11114
12821
  if (refs.length === 0) {
11115
- result.resolved.set(path76, rawValue);
12822
+ result.resolved.set(path84, rawValue);
11116
12823
  return rawValue;
11117
12824
  }
11118
- visited.add(path76);
12825
+ visited.add(path84);
11119
12826
  let resolved = rawValue;
11120
12827
  for (const ref of refs) {
11121
12828
  const refPath = ref[1];
@@ -11123,8 +12830,8 @@ function resolveTokenRef(path76, allTokens, visited, depth, result) {
11123
12830
  const refToken = allTokens.get(refPath);
11124
12831
  if (!refToken) {
11125
12832
  result.errors.push({
11126
- message: `Unresolved token reference: {${refPath}} at ${path76}`,
11127
- path: path76
12833
+ message: `Unresolved token reference: {${refPath}} at ${path84}`,
12834
+ path: path84
11128
12835
  });
11129
12836
  continue;
11130
12837
  }
@@ -11133,7 +12840,7 @@ function resolveTokenRef(path76, allTokens, visited, depth, result) {
11133
12840
  resolved = resolved.split(`{${refPath}}`).join(refValue);
11134
12841
  }
11135
12842
  }
11136
- result.resolved.set(path76, resolved);
12843
+ result.resolved.set(path84, resolved);
11137
12844
  return resolved;
11138
12845
  }
11139
12846
  function stringifyTokenValue(value) {
@@ -11189,8 +12896,8 @@ var VALID_TYPES = [
11189
12896
  var VALID_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
11190
12897
  async function validateDesignToken(root, config) {
11191
12898
  const configuredDir = config.uiux?.designTokensDir;
11192
- const designDir = configuredDir ? import_node_path42.default.resolve(root, configuredDir) : import_node_path42.default.join(root, config.paths.contractsDir, "design");
11193
- const pattern = import_node_path42.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12899
+ const designDir = configuredDir ? import_node_path49.default.resolve(root, configuredDir) : import_node_path49.default.join(root, config.paths.contractsDir, "design");
12900
+ const pattern = import_node_path49.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
11194
12901
  const files = await (0, import_fast_glob3.default)(pattern, {
11195
12902
  absolute: true,
11196
12903
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -11200,11 +12907,11 @@ async function validateDesignToken(root, config) {
11200
12907
  }
11201
12908
  const issues = [];
11202
12909
  for (const filePath of files) {
11203
- const rel = import_node_path42.default.relative(root, filePath).replace(/\\/g, "/");
12910
+ const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
11204
12911
  let hasRootObjectError = false;
11205
12912
  let content;
11206
12913
  try {
11207
- content = await (0, import_promises38.readFile)(filePath, "utf-8");
12914
+ content = await (0, import_promises45.readFile)(filePath, "utf-8");
11208
12915
  } catch {
11209
12916
  issues.push(
11210
12917
  issue(
@@ -11367,8 +13074,8 @@ function normalizePlatform(value) {
11367
13074
  }
11368
13075
 
11369
13076
  // src/core/validators/htmlMock.ts
11370
- var import_promises39 = require("fs/promises");
11371
- var import_node_path43 = __toESM(require("path"), 1);
13077
+ var import_promises46 = require("fs/promises");
13078
+ var import_node_path50 = __toESM(require("path"), 1);
11372
13079
  var import_fast_glob4 = __toESM(require("fast-glob"), 1);
11373
13080
 
11374
13081
  // src/core/uiux/contrastRatio.ts
@@ -11749,19 +13456,19 @@ async function validateHtmlMock(root, platform, config) {
11749
13456
  const startTime = performance.now();
11750
13457
  const budget = config.uiux?.htmlMockTimeout ?? 2e3;
11751
13458
  const patterns = [
11752
- import_node_path43.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
11753
- import_node_path43.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
13459
+ import_node_path50.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
13460
+ import_node_path50.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
11754
13461
  ];
11755
13462
  const files = await (0, import_fast_glob4.default)(patterns, { absolute: true });
11756
13463
  const mockBlocks = [];
11757
13464
  for (const filePath of files) {
11758
13465
  let content;
11759
13466
  try {
11760
- content = await (0, import_promises39.readFile)(filePath, "utf-8");
13467
+ content = await (0, import_promises46.readFile)(filePath, "utf-8");
11761
13468
  } catch {
11762
13469
  continue;
11763
13470
  }
11764
- const rel = import_node_path43.default.relative(root, filePath).replace(/\\/g, "/");
13471
+ const rel = import_node_path50.default.relative(root, filePath).replace(/\\/g, "/");
11765
13472
  for (const block of collectHtmlMockBlocks(content)) {
11766
13473
  mockBlocks.push({ file: rel, html: block.html, rawBlock: block.rawBlock });
11767
13474
  }
@@ -11927,8 +13634,8 @@ async function validateHtmlMock(root, platform, config) {
11927
13634
  }
11928
13635
 
11929
13636
  // src/core/validators/mermaidScreenFlow.ts
11930
- var import_promises40 = require("fs/promises");
11931
- var import_node_path44 = __toESM(require("path"), 1);
13637
+ var import_promises47 = require("fs/promises");
13638
+ var import_node_path51 = __toESM(require("path"), 1);
11932
13639
  var import_fast_glob5 = __toESM(require("fast-glob"), 1);
11933
13640
 
11934
13641
  // src/core/validators/mermaidUtils.ts
@@ -11982,19 +13689,19 @@ async function validateMermaidScreenFlow(root, config) {
11982
13689
  const specsRoot = resolvePath(root, config, "specsDir");
11983
13690
  const discussionRoot = resolvePath(root, config, "discussionDir");
11984
13691
  const latestDiscussionPackDir = await findLatestDiscussionPackDir(discussionRoot);
11985
- const patterns = [import_node_path44.default.posix.join(specsRoot.replace(/\\/g, "/"), "**/*.md")];
13692
+ const patterns = [import_node_path51.default.posix.join(specsRoot.replace(/\\/g, "/"), "**/*.md")];
11986
13693
  if (latestDiscussionPackDir) {
11987
- patterns.push(import_node_path44.default.posix.join(latestDiscussionPackDir.replace(/\\/g, "/"), "**/*.md"));
13694
+ patterns.push(import_node_path51.default.posix.join(latestDiscussionPackDir.replace(/\\/g, "/"), "**/*.md"));
11988
13695
  }
11989
13696
  const files = await (0, import_fast_glob5.default)(patterns, { absolute: true });
11990
13697
  for (const filePath of files) {
11991
13698
  let content;
11992
13699
  try {
11993
- content = await (0, import_promises40.readFile)(filePath, "utf-8");
13700
+ content = await (0, import_promises47.readFile)(filePath, "utf-8");
11994
13701
  } catch {
11995
13702
  continue;
11996
13703
  }
11997
- const rel = import_node_path44.default.relative(root, filePath).replace(/\\/g, "/");
13704
+ const rel = import_node_path51.default.relative(root, filePath).replace(/\\/g, "/");
11998
13705
  const blocks = extractFencedCodeBlocks(content);
11999
13706
  for (const block of blocks) {
12000
13707
  if (block.language !== "mermaid") continue;
@@ -12092,8 +13799,8 @@ function parseTransitions(content) {
12092
13799
  }
12093
13800
 
12094
13801
  // src/core/validators/bpApDb.ts
12095
- var import_promises41 = require("fs/promises");
12096
- var import_node_path45 = __toESM(require("path"), 1);
13802
+ var import_promises48 = require("fs/promises");
13803
+ var import_node_path52 = __toESM(require("path"), 1);
12097
13804
  var import_fast_glob6 = __toESM(require("fast-glob"), 1);
12098
13805
  var import_yaml8 = require("yaml");
12099
13806
  init_utils();
@@ -12131,9 +13838,9 @@ var AP_REQUIRED_FIELDS = [
12131
13838
  ];
12132
13839
  async function validateBpApDb(root, config) {
12133
13840
  const issues = [];
12134
- const designDir = import_node_path45.default.join(root, config.paths.contractsDir, "design");
12135
- const bpPattern = import_node_path45.default.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
12136
- const apPattern = import_node_path45.default.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
13841
+ const designDir = import_node_path52.default.join(root, config.paths.contractsDir, "design");
13842
+ const bpPattern = import_node_path52.default.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
13843
+ const apPattern = import_node_path52.default.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
12137
13844
  const globOptions = {
12138
13845
  absolute: true,
12139
13846
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12146,14 +13853,14 @@ async function validateBpApDb(root, config) {
12146
13853
  const seenBpIds = /* @__PURE__ */ new Set();
12147
13854
  const seenApIds = /* @__PURE__ */ new Set();
12148
13855
  for (const filePath of bpFiles) {
12149
- const rel = import_node_path45.default.relative(root, filePath).replace(/\\/g, "/");
13856
+ const rel = import_node_path52.default.relative(root, filePath).replace(/\\/g, "/");
12150
13857
  const entries = await parseRuleFile(filePath, rel, issues);
12151
13858
  for (const entry of entries) {
12152
13859
  validateBpEntry(entry, rel, seenBpIds, issues);
12153
13860
  }
12154
13861
  }
12155
13862
  for (const filePath of apFiles) {
12156
- const rel = import_node_path45.default.relative(root, filePath).replace(/\\/g, "/");
13863
+ const rel = import_node_path52.default.relative(root, filePath).replace(/\\/g, "/");
12157
13864
  const entries = await parseRuleFile(filePath, rel, issues);
12158
13865
  for (const entry of entries) {
12159
13866
  validateApEntry(entry, rel, seenApIds, issues);
@@ -12164,7 +13871,7 @@ async function validateBpApDb(root, config) {
12164
13871
  async function parseRuleFile(filePath, rel, issues) {
12165
13872
  let content;
12166
13873
  try {
12167
- content = await (0, import_promises41.readFile)(filePath, "utf-8");
13874
+ content = await (0, import_promises48.readFile)(filePath, "utf-8");
12168
13875
  } catch {
12169
13876
  issues.push(
12170
13877
  issue("QFAI-BPAP-001", `BP/AP file unreadable: ${rel}`, "error", rel, "bpApDb.readFile")
@@ -12328,8 +14035,8 @@ function toSafeString(value) {
12328
14035
  }
12329
14036
 
12330
14037
  // src/core/validators/platformDetection.ts
12331
- var import_promises42 = require("fs/promises");
12332
- var import_node_path46 = __toESM(require("path"), 1);
14038
+ var import_promises49 = require("fs/promises");
14039
+ var import_node_path53 = __toESM(require("path"), 1);
12333
14040
  init_utils();
12334
14041
  var KNOWN_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
12335
14042
  async function detectPlatform(root, config, cliPlatform) {
@@ -12373,13 +14080,13 @@ async function detectPlatform(root, config, cliPlatform) {
12373
14080
  return { platform: "web", source: "fallback", issues };
12374
14081
  }
12375
14082
  async function inferPlatform(root, issues) {
12376
- if (await exists5(import_node_path46.default.join(root, "pubspec.yaml"))) {
12377
- const hasAndroid = await exists5(import_node_path46.default.join(root, "android"));
12378
- const hasIos = await exists5(import_node_path46.default.join(root, "ios"));
12379
- const hasWeb = await exists5(import_node_path46.default.join(root, "web"));
12380
- const hasWindows = await exists5(import_node_path46.default.join(root, "windows"));
12381
- const hasMacos = await exists5(import_node_path46.default.join(root, "macos"));
12382
- const hasLinux = await exists5(import_node_path46.default.join(root, "linux"));
14083
+ if (await exists5(import_node_path53.default.join(root, "pubspec.yaml"))) {
14084
+ const hasAndroid = await exists5(import_node_path53.default.join(root, "android"));
14085
+ const hasIos = await exists5(import_node_path53.default.join(root, "ios"));
14086
+ const hasWeb = await exists5(import_node_path53.default.join(root, "web"));
14087
+ const hasWindows = await exists5(import_node_path53.default.join(root, "windows"));
14088
+ const hasMacos = await exists5(import_node_path53.default.join(root, "macos"));
14089
+ const hasLinux = await exists5(import_node_path53.default.join(root, "linux"));
12383
14090
  const mobileTargets = [hasAndroid, hasIos].filter(Boolean).length;
12384
14091
  const desktopTargets = [hasWeb, hasWindows, hasMacos, hasLinux].filter(Boolean).length;
12385
14092
  if (mobileTargets + desktopTargets > 1) {
@@ -12399,10 +14106,10 @@ async function inferPlatform(root, issues) {
12399
14106
  }
12400
14107
  return null;
12401
14108
  }
12402
- const pkgJsonPath = import_node_path46.default.join(root, "package.json");
14109
+ const pkgJsonPath = import_node_path53.default.join(root, "package.json");
12403
14110
  if (await exists5(pkgJsonPath)) {
12404
14111
  try {
12405
- const raw = await (0, import_promises42.readFile)(pkgJsonPath, "utf-8");
14112
+ const raw = await (0, import_promises49.readFile)(pkgJsonPath, "utf-8");
12406
14113
  const pkg = JSON.parse(raw);
12407
14114
  const deps = {
12408
14115
  ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
@@ -12421,8 +14128,8 @@ async function inferPlatform(root, issues) {
12421
14128
  return "cross-platform";
12422
14129
  }
12423
14130
  if ("react-native" in deps) {
12424
- const hasAndroid = await exists5(import_node_path46.default.join(root, "android"));
12425
- const hasIos = await exists5(import_node_path46.default.join(root, "ios"));
14131
+ const hasAndroid = await exists5(import_node_path53.default.join(root, "android"));
14132
+ const hasIos = await exists5(import_node_path53.default.join(root, "ios"));
12426
14133
  if (hasAndroid && hasIos) {
12427
14134
  return "cross-platform";
12428
14135
  }
@@ -12444,16 +14151,16 @@ function normalizePlatformInput(platform) {
12444
14151
  }
12445
14152
 
12446
14153
  // src/core/validators/uiDefinitionConsistency.ts
12447
- var import_promises43 = require("fs/promises");
12448
- var import_node_path47 = __toESM(require("path"), 1);
14154
+ var import_promises50 = require("fs/promises");
14155
+ var import_node_path54 = __toESM(require("path"), 1);
12449
14156
  var import_fast_glob7 = __toESM(require("fast-glob"), 1);
12450
14157
  var import_yaml9 = require("yaml");
12451
14158
  init_utils();
12452
14159
  async function validateUiDefinitionConsistency(root, config) {
12453
14160
  const issues = [];
12454
14161
  const configuredDir = config.uiux?.designTokensDir;
12455
- const designDir = configuredDir ? import_node_path47.default.resolve(root, configuredDir) : import_node_path47.default.join(root, config.paths.contractsDir, "design");
12456
- const tokenPattern = import_node_path47.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
14162
+ const designDir = configuredDir ? import_node_path54.default.resolve(root, configuredDir) : import_node_path54.default.join(root, config.paths.contractsDir, "design");
14163
+ const tokenPattern = import_node_path54.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12457
14164
  const tokenFiles = await (0, import_fast_glob7.default)(tokenPattern, {
12458
14165
  absolute: true,
12459
14166
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12461,7 +14168,7 @@ async function validateUiDefinitionConsistency(root, config) {
12461
14168
  const resolvedTokens = /* @__PURE__ */ new Map();
12462
14169
  for (const tokenFile of tokenFiles) {
12463
14170
  try {
12464
- const content = await (0, import_promises43.readFile)(tokenFile, "utf-8");
14171
+ const content = await (0, import_promises50.readFile)(tokenFile, "utf-8");
12465
14172
  const result = parseDesignToken(content);
12466
14173
  for (const [key, val] of result.resolved) {
12467
14174
  resolvedTokens.set(key, val);
@@ -12469,13 +14176,13 @@ async function validateUiDefinitionConsistency(root, config) {
12469
14176
  } catch {
12470
14177
  }
12471
14178
  }
12472
- const uiContractDir = import_node_path47.default.join(root, config.paths.contractsDir, "ui");
12473
- const uiPattern = import_node_path47.default.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
14179
+ const uiContractDir = import_node_path54.default.join(root, config.paths.contractsDir, "ui");
14180
+ const uiPattern = import_node_path54.default.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
12474
14181
  const uiFiles = await (0, import_fast_glob7.default)(uiPattern, { absolute: true });
12475
14182
  const contractScreenIds = /* @__PURE__ */ new Set();
12476
14183
  for (const uiFile of uiFiles) {
12477
14184
  try {
12478
- const content = await (0, import_promises43.readFile)(uiFile, "utf-8");
14185
+ const content = await (0, import_promises50.readFile)(uiFile, "utf-8");
12479
14186
  const parsed = (0, import_yaml9.parse)(content);
12480
14187
  if (parsed && typeof parsed === "object") {
12481
14188
  const screens = parsed.screens;
@@ -12491,15 +14198,15 @@ async function validateUiDefinitionConsistency(root, config) {
12491
14198
  }
12492
14199
  }
12493
14200
  const mdPatterns = [
12494
- import_node_path47.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12495
- import_node_path47.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
14201
+ import_node_path54.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
14202
+ import_node_path54.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12496
14203
  ];
12497
14204
  const mdFiles = await (0, import_fast_glob7.default)(mdPatterns, { absolute: true });
12498
14205
  const mockScreenIds = /* @__PURE__ */ new Set();
12499
14206
  for (const mdFile of mdFiles) {
12500
14207
  try {
12501
- const content = await (0, import_promises43.readFile)(mdFile, "utf-8");
12502
- const rel = import_node_path47.default.relative(root, mdFile).replace(/\\/g, "/");
14208
+ const content = await (0, import_promises50.readFile)(mdFile, "utf-8");
14209
+ const rel = import_node_path54.default.relative(root, mdFile).replace(/\\/g, "/");
12503
14210
  const htmlBlocks = collectHtmlMockBlocks(content);
12504
14211
  if (resolvedTokens.size > 0) {
12505
14212
  for (const htmlBlock of htmlBlocks) {
@@ -12558,8 +14265,8 @@ async function validateUiDefinitionConsistency(root, config) {
12558
14265
  }
12559
14266
 
12560
14267
  // src/core/validators/researchSummary.ts
12561
- var import_promises44 = require("fs/promises");
12562
- var import_node_path48 = __toESM(require("path"), 1);
14268
+ var import_promises51 = require("fs/promises");
14269
+ var import_node_path55 = __toESM(require("path"), 1);
12563
14270
  var import_fast_glob8 = __toESM(require("fast-glob"), 1);
12564
14271
  init_utils();
12565
14272
  var RESEARCH_SUMMARY_HEADING_RE = /^#{1,3}\s+Research\s+Summary/im;
@@ -12568,17 +14275,17 @@ var REFLECTION_APPLY_RE = /action:\s*apply/i;
12568
14275
  var FULL_DATE_RE = /^\s+published:\s*["']?(\d{4}-\d{2}-\d{2})["']?/m;
12569
14276
  async function validateResearchSummary(root, config) {
12570
14277
  const issues = [];
12571
- const pattern = import_node_path48.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
14278
+ const pattern = import_node_path55.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
12572
14279
  const files = await (0, import_fast_glob8.default)(pattern, { absolute: true });
12573
14280
  for (const filePath of files) {
12574
14281
  let content;
12575
14282
  try {
12576
- content = await (0, import_promises44.readFile)(filePath, "utf-8");
14283
+ content = await (0, import_promises51.readFile)(filePath, "utf-8");
12577
14284
  } catch {
12578
14285
  continue;
12579
14286
  }
12580
14287
  if (!RESEARCH_SUMMARY_HEADING_RE.test(content)) continue;
12581
- const rel = import_node_path48.default.relative(root, filePath).replace(/\\/g, "/");
14288
+ const rel = import_node_path55.default.relative(root, filePath).replace(/\\/g, "/");
12582
14289
  const section = extractResearchSummarySection(content);
12583
14290
  if (!section) continue;
12584
14291
  const sourceEntries = extractSourceEntries(section);
@@ -12809,8 +14516,8 @@ function resolveFreshnessReferenceNow() {
12809
14516
  }
12810
14517
 
12811
14518
  // src/core/validators/agentDefinition.ts
12812
- var import_promises45 = require("fs/promises");
12813
- var import_node_path49 = __toESM(require("path"), 1);
14519
+ var import_promises52 = require("fs/promises");
14520
+ var import_node_path56 = __toESM(require("path"), 1);
12814
14521
  var import_yaml10 = require("yaml");
12815
14522
  init_utils();
12816
14523
  var REQUIRED_AGENT_SECTIONS = [
@@ -12823,11 +14530,11 @@ var REQUIRED_AGENT_SECTIONS = [
12823
14530
  ];
12824
14531
  async function validateAgentDefinition(root, _config) {
12825
14532
  const issues = [];
12826
- const steeringDir = import_node_path49.default.join(root, ".qfai", "assistant", "steering");
12827
- const agentsDir = import_node_path49.default.join(root, ".qfai", "assistant", "agents");
12828
- const catalogPath = import_node_path49.default.join(steeringDir, "agent-catalog.yml");
12829
- const routingPath = import_node_path49.default.join(steeringDir, "agent-routing.yml");
12830
- const profilesPath = import_node_path49.default.join(steeringDir, "review-profiles.yml");
14533
+ const steeringDir = import_node_path56.default.join(root, ".qfai", "assistant", "steering");
14534
+ const agentsDir = import_node_path56.default.join(root, ".qfai", "assistant", "agents");
14535
+ const catalogPath = import_node_path56.default.join(steeringDir, "agent-catalog.yml");
14536
+ const routingPath = import_node_path56.default.join(steeringDir, "agent-routing.yml");
14537
+ const profilesPath = import_node_path56.default.join(steeringDir, "review-profiles.yml");
12831
14538
  if (!await exists5(agentsDir) && !await exists5(catalogPath)) {
12832
14539
  return [];
12833
14540
  }
@@ -12836,7 +14543,7 @@ async function validateAgentDefinition(root, _config) {
12836
14543
  ["agent-routing.yml", "QFAI-AGENT-002"],
12837
14544
  ["review-profiles.yml", "QFAI-AGENT-003"]
12838
14545
  ]) {
12839
- const filePath = import_node_path49.default.join(steeringDir, fileName);
14546
+ const filePath = import_node_path56.default.join(steeringDir, fileName);
12840
14547
  if (!await exists5(filePath)) {
12841
14548
  issues.push(
12842
14549
  issue(
@@ -12861,7 +14568,7 @@ async function validateAgentDefinition(root, _config) {
12861
14568
  catalog.filter((agent) => agent.kind === "reviewer").map((agent) => agent.id)
12862
14569
  );
12863
14570
  for (const agent of catalog) {
12864
- const filePath = import_node_path49.default.join(agentsDir, `${agent.id}.md`);
14571
+ const filePath = import_node_path56.default.join(agentsDir, `${agent.id}.md`);
12865
14572
  const rel = `.qfai/assistant/agents/${agent.id}.md`;
12866
14573
  if (!await exists5(filePath)) {
12867
14574
  issues.push(
@@ -12875,7 +14582,7 @@ async function validateAgentDefinition(root, _config) {
12875
14582
  );
12876
14583
  continue;
12877
14584
  }
12878
- const content = await (0, import_promises45.readFile)(filePath, "utf-8");
14585
+ const content = await (0, import_promises52.readFile)(filePath, "utf-8");
12879
14586
  for (const heading of REQUIRED_AGENT_SECTIONS) {
12880
14587
  if (!content.includes(heading)) {
12881
14588
  issues.push(
@@ -12896,7 +14603,7 @@ async function validateAgentDefinition(root, _config) {
12896
14603
  }
12897
14604
  async function readCatalog(catalogPath, issues) {
12898
14605
  try {
12899
- const parsed = (0, import_yaml10.parse)(await (0, import_promises45.readFile)(catalogPath, "utf-8"));
14606
+ const parsed = (0, import_yaml10.parse)(await (0, import_promises52.readFile)(catalogPath, "utf-8"));
12900
14607
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12901
14608
  issues.push(
12902
14609
  issue(
@@ -12970,7 +14677,7 @@ async function readCatalog(catalogPath, issues) {
12970
14677
  }
12971
14678
  async function validateRouting(routingPath, catalogIds, issues) {
12972
14679
  try {
12973
- const parsed = (0, import_yaml10.parse)(await (0, import_promises45.readFile)(routingPath, "utf-8"));
14680
+ const parsed = (0, import_yaml10.parse)(await (0, import_promises52.readFile)(routingPath, "utf-8"));
12974
14681
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12975
14682
  issues.push(
12976
14683
  issue(
@@ -13086,7 +14793,7 @@ function validateAgentRefs(value, catalogIds, issues, skill, routeIndex, phaseIn
13086
14793
  }
13087
14794
  async function validateProfiles(profilesPath, reviewerIds, issues) {
13088
14795
  try {
13089
- const parsed = (0, import_yaml10.parse)(await (0, import_promises45.readFile)(profilesPath, "utf-8"));
14796
+ const parsed = (0, import_yaml10.parse)(await (0, import_promises52.readFile)(profilesPath, "utf-8"));
13090
14797
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
13091
14798
  issues.push(
13092
14799
  issue(
@@ -13165,8 +14872,8 @@ function validateReviewerRefs(value, reviewerIds, issues, profileName, field) {
13165
14872
  }
13166
14873
 
13167
14874
  // src/core/validators/tddList.ts
13168
- var import_promises46 = require("fs/promises");
13169
- var import_node_path50 = __toESM(require("path"), 1);
14875
+ var import_promises53 = require("fs/promises");
14876
+ var import_node_path57 = __toESM(require("path"), 1);
13170
14877
  init_utils();
13171
14878
  var REQUIRED_COLUMNS = [
13172
14879
  "TDD-ID",
@@ -13181,7 +14888,7 @@ var REQUIRED_COLUMNS = [
13181
14888
  var VALID_STATUSES = /* @__PURE__ */ new Set(["todo", "red", "green", "refactor", "done", "exception"]);
13182
14889
  var TEST_FILE_CHECK_STATUSES = /* @__PURE__ */ new Set(["green", "refactor", "done"]);
13183
14890
  var TDD_ID_FORMAT = /^TDD-\d{4}$/;
13184
- var TDD_LIST_REL_PATH = import_node_path50.default.join("tdd", "test-list.md");
14891
+ var TDD_LIST_REL_PATH = import_node_path57.default.join("tdd", "test-list.md");
13185
14892
  async function validateTddList(root, config) {
13186
14893
  const specsRoot = resolvePath(root, config, "specsDir");
13187
14894
  const entries = await collectSpecEntries(specsRoot);
@@ -13193,8 +14900,8 @@ async function validateTddList(root, config) {
13193
14900
  return issues;
13194
14901
  }
13195
14902
  async function validateSpecTddList(root, specDir, specNumber) {
13196
- const filePath = import_node_path50.default.join(specDir, TDD_LIST_REL_PATH);
13197
- const relPath = import_node_path50.default.relative(root, filePath).replace(/\\/g, "/");
14903
+ const filePath = import_node_path57.default.join(specDir, TDD_LIST_REL_PATH);
14904
+ const relPath = import_node_path57.default.relative(root, filePath).replace(/\\/g, "/");
13198
14905
  const issues = [];
13199
14906
  if (!await exists5(filePath)) {
13200
14907
  issues.push(
@@ -13381,9 +15088,9 @@ async function validateSpecTddList(root, specDir, specNumber) {
13381
15088
  continue;
13382
15089
  }
13383
15090
  const normalized = testFile.replace(/\\/g, "/");
13384
- const resolved = import_node_path50.default.resolve(root, normalized);
13385
- const relative = import_node_path50.default.relative(root, resolved);
13386
- if (import_node_path50.default.isAbsolute(normalized) || import_node_path50.default.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + import_node_path50.default.sep)) {
15091
+ const resolved = import_node_path57.default.resolve(root, normalized);
15092
+ const relative = import_node_path57.default.relative(root, resolved);
15093
+ if (import_node_path57.default.isAbsolute(normalized) || import_node_path57.default.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + import_node_path57.default.sep)) {
13387
15094
  issues.push(
13388
15095
  issue(
13389
15096
  "TDDLIST_TEST_FILE_MISSING",
@@ -13397,7 +15104,7 @@ async function validateSpecTddList(root, specDir, specNumber) {
13397
15104
  }
13398
15105
  let isFile2 = false;
13399
15106
  try {
13400
- isFile2 = (await (0, import_promises46.stat)(resolved)).isFile();
15107
+ isFile2 = (await (0, import_promises53.stat)(resolved)).isFile();
13401
15108
  } catch {
13402
15109
  }
13403
15110
  if (!isFile2) {
@@ -13446,11 +15153,11 @@ async function validateSpecTddList(root, specDir, specNumber) {
13446
15153
  }
13447
15154
  async function collectTestCaseIds(specDir) {
13448
15155
  const empty = { knownTcIds: /* @__PURE__ */ new Set(), unitComponentTcIds: /* @__PURE__ */ new Set() };
13449
- const testCasesPath = import_node_path50.default.join(specDir, "06_Test-Cases.md");
15156
+ const testCasesPath = import_node_path57.default.join(specDir, "06_Test-Cases.md");
13450
15157
  if (!await exists5(testCasesPath)) return empty;
13451
15158
  let content;
13452
15159
  try {
13453
- content = await (0, import_promises46.readFile)(testCasesPath, "utf-8");
15160
+ content = await (0, import_promises53.readFile)(testCasesPath, "utf-8");
13454
15161
  } catch {
13455
15162
  return empty;
13456
15163
  }
@@ -13476,7 +15183,7 @@ async function collectTestCaseIds(specDir) {
13476
15183
  }
13477
15184
 
13478
15185
  // src/core/validators/navigationFlow.ts
13479
- var import_promises47 = require("fs/promises");
15186
+ var import_promises54 = require("fs/promises");
13480
15187
  init_utils();
13481
15188
  var NODE_DEF_RE = /([A-Za-z_][\w-]*)\s*(?:\[.*?\]|\(.*?\)|\{.*?\})/g;
13482
15189
  var EDGE_RE = /([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?\s*(?:--+>|==+>|-.->|~~>)\s*(?:\|"?([^"|]*)"?\|)?\s*([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?/g;
@@ -13756,7 +15463,7 @@ async function validateNavigationFlow(root, config) {
13756
15463
  const specFiles = allFiles.filter((f) => /[\\/]spec-\d{4}[\\/]/.test(f));
13757
15464
  const issues = [];
13758
15465
  for (const file of specFiles) {
13759
- const content = await (0, import_promises47.readFile)(file, "utf-8");
15466
+ const content = await (0, import_promises54.readFile)(file, "utf-8");
13760
15467
  const blocks = extractFencedCodeBlocks(content);
13761
15468
  const mermaidBlocks = blocks.filter((b) => b.language === "mermaid");
13762
15469
  const _flowchartBlocks = mermaidBlocks.filter((b) => hasFlowchartDeclaration(b.content));
@@ -13778,8 +15485,8 @@ async function validateNavigationFlow(root, config) {
13778
15485
  }
13779
15486
 
13780
15487
  // src/core/validators/renderCritique.ts
13781
- var import_node_path51 = __toESM(require("path"), 1);
13782
- var import_promises48 = require("fs/promises");
15488
+ var import_node_path58 = __toESM(require("path"), 1);
15489
+ var import_promises55 = require("fs/promises");
13783
15490
  var import_fast_glob9 = __toESM(require("fast-glob"), 1);
13784
15491
  init_utils();
13785
15492
  var RENDERED_KEYWORDS_RE = /\b(rendered|screenshot|html\b|preview|visual\s*review)/i;
@@ -13804,11 +15511,11 @@ var FOUR_STATE_CHECK_RE = /\bfour_state_check\s*:/i;
13804
15511
  var MAX_PRIMARY_STEPS_RE = /\bmax_primary_steps\s*:\s*(\d+)/i;
13805
15512
  async function validateRenderCritique(root, config) {
13806
15513
  const issues = [];
13807
- const skillsDir = import_node_path51.default.join(root, config.paths.skillsDir).replace(/\\/g, "/");
13808
- const evidenceDir = import_node_path51.default.join(root, ".qfai", "evidence").replace(/\\/g, "/");
13809
- const skillPromptPattern = import_node_path51.default.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
15514
+ const skillsDir = import_node_path58.default.join(root, config.paths.skillsDir).replace(/\\/g, "/");
15515
+ const evidenceDir = import_node_path58.default.join(root, ".qfai", "evidence").replace(/\\/g, "/");
15516
+ const skillPromptPattern = import_node_path58.default.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
13810
15517
  const skillFiles = await (0, import_fast_glob9.default)(skillPromptPattern, { dot: true });
13811
- const evidencePattern = import_node_path51.default.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
15518
+ const evidencePattern = import_node_path58.default.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
13812
15519
  const evidenceFiles = await (0, import_fast_glob9.default)(evidencePattern, { dot: true });
13813
15520
  if (skillFiles.length === 0 && evidenceFiles.length === 0) return issues;
13814
15521
  const renderEvidenceViewports = await collectRenderEvidenceViewports(root);
@@ -13818,7 +15525,7 @@ async function validateRenderCritique(root, config) {
13818
15525
  issues.push(
13819
15526
  issue(
13820
15527
  "QFAI-CRIT-001",
13821
- `Skill prompt does not mention rendered/screenshot/HTML review: ${import_node_path51.default.relative(root, sf)}`,
15528
+ `Skill prompt does not mention rendered/screenshot/HTML review: ${import_node_path58.default.relative(root, sf)}`,
13822
15529
  "error",
13823
15530
  sf,
13824
15531
  "renderCritique.codeOnly",
@@ -13835,7 +15542,7 @@ async function validateRenderCritique(root, config) {
13835
15542
  issues.push(
13836
15543
  issue(
13837
15544
  "QFAI-CRIT-002",
13838
- `Downstream skill prompt missing canonical spec/contract references: ${import_node_path51.default.relative(root, sf)}`,
15545
+ `Downstream skill prompt missing canonical spec/contract references: ${import_node_path58.default.relative(root, sf)}`,
13839
15546
  "error",
13840
15547
  sf,
13841
15548
  "renderCritique.contractMissing",
@@ -13897,7 +15604,7 @@ async function validateRenderCritique(root, config) {
13897
15604
  issues.push(
13898
15605
  issue(
13899
15606
  "QFAI-CRIT-005",
13900
- `Read order missing required tokens (${missing.join(", ")}): ${import_node_path51.default.relative(root, sf)}`,
15607
+ `Read order missing required tokens (${missing.join(", ")}): ${import_node_path58.default.relative(root, sf)}`,
13901
15608
  "error",
13902
15609
  sf,
13903
15610
  "renderCritique.readOrder",
@@ -13925,7 +15632,7 @@ async function validateRenderCritique(root, config) {
13925
15632
  issues.push(
13926
15633
  issue(
13927
15634
  "QFAI-CRIT-006",
13928
- `Critique evidence incomplete (missing: ${missing.join(", ")}): ${import_node_path51.default.relative(root, ef)}`,
15635
+ `Critique evidence incomplete (missing: ${missing.join(", ")}): ${import_node_path58.default.relative(root, ef)}`,
13929
15636
  "error",
13930
15637
  ef,
13931
15638
  "renderCritique.incompleteEvidence",
@@ -14040,9 +15747,9 @@ async function collectContent(files) {
14040
15747
  return contents.join("\n---\n");
14041
15748
  }
14042
15749
  async function collectRenderEvidenceViewports(root) {
14043
- const prototypingJsonPath = import_node_path51.default.join(root, ".qfai", "evidence", "prototyping.json");
15750
+ const prototypingJsonPath = import_node_path58.default.join(root, ".qfai", "evidence", "prototyping.json");
14044
15751
  try {
14045
- const raw = await (0, import_promises48.readFile)(prototypingJsonPath, "utf-8");
15752
+ const raw = await (0, import_promises55.readFile)(prototypingJsonPath, "utf-8");
14046
15753
  const parsed = JSON.parse(raw);
14047
15754
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14048
15755
  return /* @__PURE__ */ new Set();
@@ -14081,8 +15788,8 @@ async function collectRenderEvidenceViewports(root) {
14081
15788
  }
14082
15789
 
14083
15790
  // src/core/validators/designFidelity.ts
14084
- var import_promises49 = require("fs/promises");
14085
- var import_node_path52 = __toESM(require("path"), 1);
15791
+ var import_promises56 = require("fs/promises");
15792
+ var import_node_path59 = __toESM(require("path"), 1);
14086
15793
  var import_fast_glob10 = __toESM(require("fast-glob"), 1);
14087
15794
  init_utils();
14088
15795
  var SCORECARD_HEADING_RE = /^#{1,3}\s+Fidelity\s+Scorecard/im;
@@ -14108,19 +15815,19 @@ async function validateDesignFidelity(root, config) {
14108
15815
  const evidenceDirs = [".qfai/evidence", ".qfai/review"];
14109
15816
  const allFiles = [];
14110
15817
  for (const dir of evidenceDirs) {
14111
- const pattern = import_node_path52.default.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
15818
+ const pattern = import_node_path59.default.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
14112
15819
  const files = await (0, import_fast_glob10.default)(pattern, { absolute: true });
14113
15820
  allFiles.push(...files);
14114
15821
  }
14115
15822
  for (const filePath of allFiles) {
14116
15823
  let content;
14117
15824
  try {
14118
- content = await (0, import_promises49.readFile)(filePath, "utf-8");
15825
+ content = await (0, import_promises56.readFile)(filePath, "utf-8");
14119
15826
  } catch {
14120
15827
  continue;
14121
15828
  }
14122
15829
  if (!SCORECARD_HEADING_RE.test(content)) continue;
14123
- const rel = import_node_path52.default.relative(root, filePath).replace(/\\/g, "/");
15830
+ const rel = import_node_path59.default.relative(root, filePath).replace(/\\/g, "/");
14124
15831
  const section = extractScorecardSection(content);
14125
15832
  if (!section) continue;
14126
15833
  const dimensions = parseDimensions(section);
@@ -14412,7 +16119,7 @@ function hasAntiPatternMention(section, code) {
14412
16119
  }
14413
16120
 
14414
16121
  // src/core/validators/discussionDesignHardening.ts
14415
- var import_node_path53 = __toESM(require("path"), 1);
16122
+ var import_node_path60 = __toESM(require("path"), 1);
14416
16123
  init_surfaceType();
14417
16124
  init_utils();
14418
16125
  var REQUIRED_SIDECARS = [
@@ -14428,14 +16135,14 @@ function canonicalIssue(code, message, severity, file, rule) {
14428
16135
  return issue(code, message, severity, file, rule, void 0, "canonical");
14429
16136
  }
14430
16137
  async function validateDiscussionDesignHardening(root, config) {
14431
- const discussionDir = import_node_path53.default.join(root, config.paths.discussionDir);
16138
+ const discussionDir = import_node_path60.default.join(root, config.paths.discussionDir);
14432
16139
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14433
16140
  if (!packRoot) return [];
14434
16141
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
14435
16142
  if (!uiBearing) return [];
14436
16143
  const issues = [];
14437
16144
  for (const relPath of REQUIRED_SIDECARS) {
14438
- const content = await readSafe(import_node_path53.default.join(packRoot, relPath));
16145
+ const content = await readSafe(import_node_path60.default.join(packRoot, relPath));
14439
16146
  if (!content) {
14440
16147
  issues.push(
14441
16148
  canonicalIssue(
@@ -14477,7 +16184,7 @@ async function validateDiscussionDesignHardening(root, config) {
14477
16184
  "UIX-VAL-EVAL-CALIBRATION"
14478
16185
  )
14479
16186
  );
14480
- const antiGoals = await readSafe(import_node_path53.default.join(packRoot, "uiux/32_design_anti_goals.md"));
16187
+ const antiGoals = await readSafe(import_node_path60.default.join(packRoot, "uiux/32_design_anti_goals.md"));
14481
16188
  if (antiGoals && !/^- /m.test(antiGoals)) {
14482
16189
  issues.push(
14483
16190
  canonicalIssue(
@@ -14489,7 +16196,7 @@ async function validateDiscussionDesignHardening(root, config) {
14489
16196
  )
14490
16197
  );
14491
16198
  }
14492
- const reviewBundle = await readSafe(import_node_path53.default.join(packRoot, "uiux/50_review_input_bundle.md"));
16199
+ const reviewBundle = await readSafe(import_node_path60.default.join(packRoot, "uiux/50_review_input_bundle.md"));
14493
16200
  if (reviewBundle && !/best-of-history/i.test(reviewBundle)) {
14494
16201
  issues.push(
14495
16202
  canonicalIssue(
@@ -14504,7 +16211,7 @@ async function validateDiscussionDesignHardening(root, config) {
14504
16211
  return issues;
14505
16212
  }
14506
16213
  async function validateSection(packRoot, relPath, headings, code) {
14507
- const content = await readSafe(import_node_path53.default.join(packRoot, relPath));
16214
+ const content = await readSafe(import_node_path60.default.join(packRoot, relPath));
14508
16215
  if (!content) {
14509
16216
  return [];
14510
16217
  }
@@ -14526,13 +16233,13 @@ async function validateSection(packRoot, relPath, headings, code) {
14526
16233
  }
14527
16234
 
14528
16235
  // src/core/validators/designAudit.ts
14529
- var import_promises50 = require("fs/promises");
14530
- var import_node_path54 = __toESM(require("path"), 1);
16236
+ var import_promises57 = require("fs/promises");
16237
+ var import_node_path61 = __toESM(require("path"), 1);
14531
16238
  init_surfaceType();
14532
16239
  init_utils();
14533
16240
  var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
14534
16241
  function toPosixRelative2(root, targetPath) {
14535
- return import_node_path54.default.relative(root, targetPath).replace(/\\/g, "/");
16242
+ return import_node_path61.default.relative(root, targetPath).replace(/\\/g, "/");
14536
16243
  }
14537
16244
  function resolveAuditConfig(config) {
14538
16245
  const audit = config.uiux?.audit;
@@ -14654,19 +16361,19 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
14654
16361
  async function checkTokenDrift(root, auditConfig, cfg) {
14655
16362
  const findings = [];
14656
16363
  const configuredDir = cfg.uiux?.designTokensDir;
14657
- const tokensDir = configuredDir ? import_node_path54.default.resolve(root, configuredDir) : import_node_path54.default.join(root, cfg.paths.contractsDir, "design");
16364
+ const tokensDir = configuredDir ? import_node_path61.default.resolve(root, configuredDir) : import_node_path61.default.join(root, cfg.paths.contractsDir, "design");
14658
16365
  let hasTokenFiles = false;
14659
16366
  try {
14660
- const entries = await (0, import_promises50.readdir)(tokensDir);
16367
+ const entries = await (0, import_promises57.readdir)(tokensDir);
14661
16368
  hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
14662
16369
  } catch {
14663
16370
  return findings;
14664
16371
  }
14665
16372
  if (!hasTokenFiles) return findings;
14666
- const contractsUiDir = import_node_path54.default.join(root, cfg.paths.contractsDir, "ui");
16373
+ const contractsUiDir = import_node_path61.default.join(root, cfg.paths.contractsDir, "ui");
14667
16374
  let htmlFiles = [];
14668
16375
  try {
14669
- const entries = await (0, import_promises50.readdir)(contractsUiDir);
16376
+ const entries = await (0, import_promises57.readdir)(contractsUiDir);
14670
16377
  htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
14671
16378
  } catch {
14672
16379
  return findings;
@@ -14674,7 +16381,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
14674
16381
  let rawCount = 0;
14675
16382
  const sampleLiterals = [];
14676
16383
  for (const htmlFile of htmlFiles) {
14677
- const content = await readSafe(import_node_path54.default.join(contractsUiDir, htmlFile));
16384
+ const content = await readSafe(import_node_path61.default.join(contractsUiDir, htmlFile));
14678
16385
  if (!content) continue;
14679
16386
  const matches = content.match(RAW_COLOR_RE);
14680
16387
  if (matches) {
@@ -14725,14 +16432,14 @@ function deduplicateFindings(issues, maxPerRule) {
14725
16432
  async function validateDesignAudit(root, config) {
14726
16433
  const auditConfig = resolveAuditConfig(config);
14727
16434
  if (!auditConfig.enabled) return [];
14728
- const discussionDir = import_node_path54.default.join(root, config.paths.discussionDir);
16435
+ const discussionDir = import_node_path61.default.join(root, config.paths.discussionDir);
14729
16436
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14730
16437
  if (!packRoot) return [];
14731
16438
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
14732
16439
  if (!uiBearing) return [];
14733
- const contractsPath = import_node_path54.default.join(packRoot, "uiux", "40_screen_contracts.md");
16440
+ const contractsPath = import_node_path61.default.join(packRoot, "uiux", "40_screen_contracts.md");
14734
16441
  const contractsContent = await readSafe(contractsPath);
14735
- const selectedDirectionPath = import_node_path54.default.join(
16442
+ const selectedDirectionPath = import_node_path61.default.join(
14736
16443
  root,
14737
16444
  config.paths.contractsDir,
14738
16445
  "design",
@@ -14759,8 +16466,8 @@ async function validateDesignAudit(root, config) {
14759
16466
 
14760
16467
  // src/core/validators/designSlop.ts
14761
16468
  var import_node_fs2 = require("fs");
14762
- var import_promises51 = require("fs/promises");
14763
- var import_node_path55 = __toESM(require("path"), 1);
16469
+ var import_promises58 = require("fs/promises");
16470
+ var import_node_path62 = __toESM(require("path"), 1);
14764
16471
  var import_node_url3 = require("url");
14765
16472
  init_surfaceType();
14766
16473
  init_utils();
@@ -14770,7 +16477,7 @@ function isValidSlopPattern(rule) {
14770
16477
  return typeof r.id === "string" && typeof r.category === "string" && typeof r.tier === "number" && Array.isArray(r.scopes) && typeof r.match === "string" && typeof r.message === "string" && typeof r.guidance === "string";
14771
16478
  }
14772
16479
  async function loadSlopPatterns(jsonPath) {
14773
- const raw = await (0, import_promises51.readFile)(jsonPath, "utf-8");
16480
+ const raw = await (0, import_promises58.readFile)(jsonPath, "utf-8");
14774
16481
  const parsed = JSON.parse(raw);
14775
16482
  if (!Array.isArray(parsed)) return [];
14776
16483
  return parsed.filter((r) => isValidSlopPattern(r));
@@ -14778,11 +16485,11 @@ async function loadSlopPatterns(jsonPath) {
14778
16485
  function defaultPatternsPath() {
14779
16486
  const base = __filename;
14780
16487
  const basePath = base.startsWith("file:") ? (0, import_node_url3.fileURLToPath)(base) : base;
14781
- const baseDir = import_node_path55.default.dirname(basePath);
16488
+ const baseDir = import_node_path62.default.dirname(basePath);
14782
16489
  const candidates = [
14783
- import_node_path55.default.join(baseDir, "designSlopPatterns.json"),
14784
- import_node_path55.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
14785
- import_node_path55.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
16490
+ import_node_path62.default.join(baseDir, "designSlopPatterns.json"),
16491
+ import_node_path62.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
16492
+ import_node_path62.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
14786
16493
  ];
14787
16494
  for (const c of candidates) {
14788
16495
  if ((0, import_node_fs2.existsSync)(c)) return c;
@@ -14793,7 +16500,7 @@ async function validateDesignSlop(root, config) {
14793
16500
  const auditConfig = resolveAuditConfig(config);
14794
16501
  if (!auditConfig.enabled) return [];
14795
16502
  if (!auditConfig.slopDetection) return [];
14796
- const discussionDir = import_node_path55.default.join(root, config.paths.discussionDir);
16503
+ const discussionDir = import_node_path62.default.join(root, config.paths.discussionDir);
14797
16504
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14798
16505
  if (!packRoot) return [];
14799
16506
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
@@ -14814,7 +16521,7 @@ async function validateDesignSlop(root, config) {
14814
16521
  continue;
14815
16522
  }
14816
16523
  for (const scope of pattern.scopes) {
14817
- const filePath = import_node_path55.default.join(packRoot, scope);
16524
+ const filePath = import_node_path62.default.join(packRoot, scope);
14818
16525
  const content = await readSafe(filePath);
14819
16526
  if (!content) continue;
14820
16527
  if (regex.test(content) && !seenRules.has(pattern.id)) {
@@ -14836,8 +16543,8 @@ async function validateDesignSlop(root, config) {
14836
16543
  }
14837
16544
 
14838
16545
  // src/core/validators/designContractReadiness.ts
14839
- var import_node_path56 = __toESM(require("path"), 1);
14840
- var import_promises52 = require("fs/promises");
16546
+ var import_node_path63 = __toESM(require("path"), 1);
16547
+ var import_promises59 = require("fs/promises");
14841
16548
  var import_fast_glob11 = __toESM(require("fast-glob"), 1);
14842
16549
  var import_yaml11 = require("yaml");
14843
16550
  init_utils();
@@ -14849,24 +16556,24 @@ var REQUIRED_DESIGN_FILES = [
14849
16556
  "design-system.yaml"
14850
16557
  ];
14851
16558
  function toPosixRelative3(root, targetPath) {
14852
- return import_node_path56.default.relative(root, targetPath).replace(/\\/g, "/");
16559
+ return import_node_path63.default.relative(root, targetPath).replace(/\\/g, "/");
14853
16560
  }
14854
16561
  async function validateDesignContractReadiness(root, config) {
14855
- const uiPattern = import_node_path56.default.posix.join(
14856
- import_node_path56.default.join(root, config.paths.contractsDir, "ui").replace(/\\/g, "/"),
16562
+ const uiPattern = import_node_path63.default.posix.join(
16563
+ import_node_path63.default.join(root, config.paths.contractsDir, "ui").replace(/\\/g, "/"),
14857
16564
  "**/*.yaml"
14858
16565
  );
14859
16566
  const uiContracts = await (0, import_fast_glob11.default)(uiPattern, { absolute: true });
14860
16567
  if (uiContracts.length === 0) {
14861
16568
  return [];
14862
16569
  }
14863
- const designDir = import_node_path56.default.join(root, config.paths.contractsDir, "design");
16570
+ const designDir = import_node_path63.default.join(root, config.paths.contractsDir, "design");
14864
16571
  const designDirRelative = toPosixRelative3(root, designDir);
14865
16572
  const issues = [];
14866
16573
  for (const fileName of REQUIRED_DESIGN_FILES) {
14867
- const filePath = import_node_path56.default.join(designDir, fileName);
16574
+ const filePath = import_node_path63.default.join(designDir, fileName);
14868
16575
  try {
14869
- await (0, import_promises52.readFile)(filePath, "utf-8");
16576
+ await (0, import_promises59.readFile)(filePath, "utf-8");
14870
16577
  } catch {
14871
16578
  issues.push(
14872
16579
  issue(
@@ -14890,7 +16597,7 @@ async function validateDesignContractReadiness(root, config) {
14890
16597
  return issues;
14891
16598
  }
14892
16599
  async function validateExplorationBrief(root, config) {
14893
- const filePath = import_node_path56.default.join(root, config.paths.contractsDir, "design", "exploration-brief.yaml");
16600
+ const filePath = import_node_path63.default.join(root, config.paths.contractsDir, "design", "exploration-brief.yaml");
14894
16601
  const parsed = await readYaml(filePath);
14895
16602
  if (parsed.kind !== "ok") {
14896
16603
  return parsed.kind === "invalid" ? [
@@ -14921,7 +16628,7 @@ async function validateExplorationBrief(root, config) {
14921
16628
  );
14922
16629
  }
14923
16630
  async function validateEvaluationRubric(root, config) {
14924
- const filePath = import_node_path56.default.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
16631
+ const filePath = import_node_path63.default.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
14925
16632
  const parsed = await readYaml(filePath);
14926
16633
  if (parsed.kind !== "ok") {
14927
16634
  return parsed.kind === "invalid" ? [
@@ -14952,7 +16659,7 @@ async function validateEvaluationRubric(root, config) {
14952
16659
  return issues;
14953
16660
  }
14954
16661
  async function validateEvaluatorCalibration(root, config) {
14955
- const filePath = import_node_path56.default.join(
16662
+ const filePath = import_node_path63.default.join(
14956
16663
  root,
14957
16664
  config.paths.contractsDir,
14958
16665
  "design",
@@ -14986,7 +16693,7 @@ async function validateEvaluatorCalibration(root, config) {
14986
16693
  );
14987
16694
  }
14988
16695
  async function validateSelectedDirection(root, config) {
14989
- const filePath = import_node_path56.default.join(root, config.paths.contractsDir, "design", "selected-direction.yaml");
16696
+ const filePath = import_node_path63.default.join(root, config.paths.contractsDir, "design", "selected-direction.yaml");
14990
16697
  const parsed = await readYaml(filePath);
14991
16698
  if (parsed.kind !== "ok") {
14992
16699
  return parsed.kind === "invalid" ? [
@@ -15024,7 +16731,7 @@ async function validateSelectedDirection(root, config) {
15024
16731
  return issues;
15025
16732
  }
15026
16733
  async function validateDesignSystem(root, config) {
15027
- const filePath = import_node_path56.default.join(root, config.paths.contractsDir, "design", "design-system.yaml");
16734
+ const filePath = import_node_path63.default.join(root, config.paths.contractsDir, "design", "design-system.yaml");
15028
16735
  const parsed = await readYaml(filePath);
15029
16736
  if (parsed.kind !== "ok") {
15030
16737
  return parsed.kind === "invalid" ? [
@@ -15082,14 +16789,14 @@ function isNonEmptyStringValue(value) {
15082
16789
  }
15083
16790
  async function readYaml(filePath) {
15084
16791
  try {
15085
- const parsed = (0, import_yaml11.parse)(await (0, import_promises52.readFile)(filePath, "utf-8"));
16792
+ const parsed = (0, import_yaml11.parse)(await (0, import_promises59.readFile)(filePath, "utf-8"));
15086
16793
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15087
16794
  return { kind: "invalid" };
15088
16795
  }
15089
16796
  return { kind: "ok", value: parsed };
15090
16797
  } catch {
15091
16798
  try {
15092
- await (0, import_promises52.readFile)(filePath, "utf-8");
16799
+ await (0, import_promises59.readFile)(filePath, "utf-8");
15093
16800
  return { kind: "invalid" };
15094
16801
  } catch {
15095
16802
  return { kind: "missing" };
@@ -15097,14 +16804,184 @@ async function readYaml(filePath) {
15097
16804
  }
15098
16805
  }
15099
16806
 
16807
+ // src/core/validators/evaluatorReviewHardFloor.ts
16808
+ var import_promises60 = require("fs/promises");
16809
+ var import_node_path64 = __toESM(require("path"), 1);
16810
+ var import_yaml12 = require("yaml");
16811
+ init_utils();
16812
+ var ISSUE_CODE = "QFAI-PROT-AXIS-FLOOR-001";
16813
+ var RULE = "evaluatorReviewHardFloor.perAxisScore";
16814
+ function isRecord16(value) {
16815
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16816
+ }
16817
+ function toPosixRelative4(root, target) {
16818
+ return import_node_path64.default.relative(root, target).replace(/\\/g, "/");
16819
+ }
16820
+ function isEnoentError(error) {
16821
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
16822
+ }
16823
+ async function readYamlObject(filePath) {
16824
+ let raw;
16825
+ try {
16826
+ raw = await (0, import_promises60.readFile)(filePath, "utf-8");
16827
+ } catch (error) {
16828
+ if (isEnoentError(error)) {
16829
+ return { kind: "missing" };
16830
+ }
16831
+ return { kind: "invalid" };
16832
+ }
16833
+ try {
16834
+ const parsed = (0, import_yaml12.parse)(raw);
16835
+ return isRecord16(parsed) ? { kind: "ok", value: parsed } : { kind: "invalid" };
16836
+ } catch {
16837
+ return { kind: "invalid" };
16838
+ }
16839
+ }
16840
+ async function readJsonObject(filePath) {
16841
+ let raw;
16842
+ try {
16843
+ raw = await (0, import_promises60.readFile)(filePath, "utf-8");
16844
+ } catch (error) {
16845
+ if (isEnoentError(error)) {
16846
+ return { kind: "missing" };
16847
+ }
16848
+ return { kind: "invalid" };
16849
+ }
16850
+ try {
16851
+ const parsed = JSON.parse(raw);
16852
+ return isRecord16(parsed) ? { kind: "ok", value: parsed } : { kind: "invalid" };
16853
+ } catch {
16854
+ return { kind: "invalid" };
16855
+ }
16856
+ }
16857
+ function loadHardFloors(rubric) {
16858
+ const map = /* @__PURE__ */ new Map();
16859
+ const entries = rubric.hard_floors;
16860
+ if (!Array.isArray(entries)) {
16861
+ return map;
16862
+ }
16863
+ for (const entry of entries) {
16864
+ if (!isRecord16(entry)) {
16865
+ continue;
16866
+ }
16867
+ const id = entry.id;
16868
+ const minScore = entry.min_score;
16869
+ if (typeof id !== "string" || id.trim().length === 0 || typeof minScore !== "number" || !Number.isFinite(minScore)) {
16870
+ continue;
16871
+ }
16872
+ map.set(id, minScore);
16873
+ }
16874
+ return map;
16875
+ }
16876
+ async function listCandidateReviewFiles(reviewsDirAbsolute) {
16877
+ try {
16878
+ const names = await (0, import_promises60.readdir)(reviewsDirAbsolute);
16879
+ return names.filter((name) => name.endsWith(".json"));
16880
+ } catch (error) {
16881
+ if (isEnoentError(error)) {
16882
+ return null;
16883
+ }
16884
+ throw error;
16885
+ }
16886
+ }
16887
+ function extractCandidateId(fileName) {
16888
+ return fileName.replace(/\.json$/i, "");
16889
+ }
16890
+ function evaluatePerAxis(review, hardFloors) {
16891
+ const perAxis = review.perAxis;
16892
+ if (!Array.isArray(perAxis)) {
16893
+ return [];
16894
+ }
16895
+ const violations = [];
16896
+ for (const axis of perAxis) {
16897
+ if (!isRecord16(axis)) {
16898
+ continue;
16899
+ }
16900
+ const axisId = axis.axisId;
16901
+ const score = axis.score;
16902
+ if (typeof axisId !== "string" || axisId.trim().length === 0 || typeof score !== "number" || !Number.isFinite(score)) {
16903
+ continue;
16904
+ }
16905
+ const minScore = hardFloors.get(axisId);
16906
+ if (minScore === void 0) {
16907
+ continue;
16908
+ }
16909
+ if (score < minScore) {
16910
+ violations.push({ axisId, score, minScore });
16911
+ }
16912
+ }
16913
+ return violations;
16914
+ }
16915
+ async function checkRound(root, round, hardFloors) {
16916
+ const reviewsDirRelative = roundEvaluatorReviewsDir(round);
16917
+ const reviewsDirAbsolute = import_node_path64.default.join(root, reviewsDirRelative);
16918
+ const files = await listCandidateReviewFiles(reviewsDirAbsolute);
16919
+ if (files === null || files.length === 0) {
16920
+ return [];
16921
+ }
16922
+ const issues = [];
16923
+ for (const fileName of files) {
16924
+ const candidateId = extractCandidateId(fileName);
16925
+ const reviewPathAbsolute = import_node_path64.default.join(reviewsDirAbsolute, fileName);
16926
+ const result = await readJsonObject(reviewPathAbsolute);
16927
+ if (result.kind !== "ok") {
16928
+ continue;
16929
+ }
16930
+ const violations = evaluatePerAxis(result.value, hardFloors);
16931
+ const reviewPathRelative = toPosixRelative4(root, reviewPathAbsolute);
16932
+ for (const violation of violations) {
16933
+ issues.push(
16934
+ issue(
16935
+ ISSUE_CODE,
16936
+ `Candidate ${candidateId} axis ${violation.axisId} score ${violation.score} is below hard_floors min_score ${violation.minScore} in round ${round}.`,
16937
+ "error",
16938
+ reviewPathRelative,
16939
+ RULE,
16940
+ [
16941
+ round,
16942
+ candidateId,
16943
+ violation.axisId,
16944
+ String(violation.score),
16945
+ String(violation.minScore)
16946
+ ],
16947
+ "canonical",
16948
+ `${reviewPathRelative} \u306E ${violation.axisId} \u30B9\u30B3\u30A2\u3092\u518D\u8A55\u4FA1\u3057\u3001hard_floors[].min_score (${violation.minScore}) \u4EE5\u4E0A\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
16949
+ )
16950
+ );
16951
+ }
16952
+ }
16953
+ return issues;
16954
+ }
16955
+ async function validateEvaluatorReviewHardFloor(root, config) {
16956
+ const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
16957
+ if (screens.length === 0) {
16958
+ return [];
16959
+ }
16960
+ const rubricPath = import_node_path64.default.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
16961
+ const rubric = await readYamlObject(rubricPath);
16962
+ if (rubric.kind !== "ok") {
16963
+ return [];
16964
+ }
16965
+ const hardFloors = loadHardFloors(rubric.value);
16966
+ if (hardFloors.size === 0) {
16967
+ return [];
16968
+ }
16969
+ const issues = [];
16970
+ for (const round of ABSORPTION_ROUNDS) {
16971
+ const roundIssues = await checkRound(root, round, hardFloors);
16972
+ issues.push(...roundIssues);
16973
+ }
16974
+ return issues;
16975
+ }
16976
+
15100
16977
  // src/core/validators/uiEvidenceArtifacts.ts
15101
- var import_node_path57 = __toESM(require("path"), 1);
16978
+ var import_node_path65 = __toESM(require("path"), 1);
15102
16979
  init_utils();
15103
16980
  function resolveEvidenceRoot2(root, config) {
15104
- return import_node_path57.default.join(import_node_path57.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
16981
+ return import_node_path65.default.join(import_node_path65.default.dirname(resolvePath(root, config, "specsDir")), "evidence");
15105
16982
  }
15106
- function toPosixRelative4(root, targetPath) {
15107
- return import_node_path57.default.relative(root, targetPath).replace(/\\/g, "/");
16983
+ function toPosixRelative5(root, targetPath) {
16984
+ return import_node_path65.default.relative(root, targetPath).replace(/\\/g, "/");
15108
16985
  }
15109
16986
  var SAFE_SCREEN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
15110
16987
  async function validateUiEvidenceArtifacts(root, config) {
@@ -15114,8 +16991,8 @@ async function validateUiEvidenceArtifacts(root, config) {
15114
16991
  return issues;
15115
16992
  }
15116
16993
  const evidenceRoot = resolveEvidenceRoot2(root, config);
15117
- const screenshotRoot = import_node_path57.default.join(evidenceRoot, "prototyping", "screenshots");
15118
- const htmlRoot = import_node_path57.default.join(evidenceRoot, "prototyping", "html");
16994
+ const screenshotRoot = import_node_path65.default.join(evidenceRoot, "prototyping", "screenshots");
16995
+ const htmlRoot = import_node_path65.default.join(evidenceRoot, "prototyping", "html");
15119
16996
  for (const screen of screens) {
15120
16997
  if (!SAFE_SCREEN_ID_PATTERN.test(screen.screenId)) {
15121
16998
  issues.push(
@@ -15132,11 +17009,11 @@ async function validateUiEvidenceArtifacts(root, config) {
15132
17009
  );
15133
17010
  continue;
15134
17011
  }
15135
- const screenshotPath = import_node_path57.default.join(screenshotRoot, `${screen.screenId}.png`);
15136
- const htmlPath = import_node_path57.default.join(htmlRoot, `${screen.screenId}.html`);
17012
+ const screenshotPath = import_node_path65.default.join(screenshotRoot, `${screen.screenId}.png`);
17013
+ const htmlPath = import_node_path65.default.join(htmlRoot, `${screen.screenId}.html`);
15137
17014
  if (!await exists5(screenshotPath)) {
15138
- const screenshotPattern = import_node_path57.default.posix.join(
15139
- toPosixRelative4(root, screenshotRoot),
17015
+ const screenshotPattern = import_node_path65.default.posix.join(
17016
+ toPosixRelative5(root, screenshotRoot),
15140
17017
  "<screen-id>.png"
15141
17018
  );
15142
17019
  issues.push(
@@ -15144,7 +17021,7 @@ async function validateUiEvidenceArtifacts(root, config) {
15144
17021
  "QFAI-UIE-001",
15145
17022
  `Missing screenshot evidence for declared screen "${screen.screenId}".`,
15146
17023
  "error",
15147
- toPosixRelative4(root, screenshotPath),
17024
+ toPosixRelative5(root, screenshotPath),
15148
17025
  "uiEvidenceArtifacts.screenshotRequired",
15149
17026
  [screen.sourceRef],
15150
17027
  "canonical",
@@ -15153,13 +17030,13 @@ async function validateUiEvidenceArtifacts(root, config) {
15153
17030
  );
15154
17031
  }
15155
17032
  if (!await exists5(htmlPath)) {
15156
- const htmlPattern = import_node_path57.default.posix.join(toPosixRelative4(root, htmlRoot), "<screen-id>.html");
17033
+ const htmlPattern = import_node_path65.default.posix.join(toPosixRelative5(root, htmlRoot), "<screen-id>.html");
15157
17034
  issues.push(
15158
17035
  issue(
15159
17036
  "QFAI-UIE-002",
15160
17037
  `Missing HTML snapshot evidence for declared screen "${screen.screenId}".`,
15161
17038
  "error",
15162
- toPosixRelative4(root, htmlPath),
17039
+ toPosixRelative5(root, htmlPath),
15163
17040
  "uiEvidenceArtifacts.htmlRequired",
15164
17041
  [screen.sourceRef],
15165
17042
  "canonical",
@@ -15178,8 +17055,8 @@ async function isUiBearingSpec(root) {
15178
17055
  }
15179
17056
 
15180
17057
  // src/core/validators/uix/threeLayer.ts
15181
- var import_promises53 = require("fs/promises");
15182
- var import_node_path58 = __toESM(require("path"), 1);
17058
+ var import_promises61 = require("fs/promises");
17059
+ var import_node_path66 = __toESM(require("path"), 1);
15183
17060
  init_utils();
15184
17061
  var EXPLORATION_SECTIONS = [
15185
17062
  "product_intent",
@@ -15234,7 +17111,7 @@ async function validateThreeLayerModel(root, _config) {
15234
17111
  if (!await isUiBearingSpec(root)) return [];
15235
17112
  const issues = [];
15236
17113
  for (const sidecar of CANONICAL_REQUIRED_SIDECAR_FILES) {
15237
- const content = await readSafe(import_node_path58.default.join(root, "uiux", sidecar));
17114
+ const content = await readSafe(import_node_path66.default.join(root, "uiux", sidecar));
15238
17115
  if (!content) {
15239
17116
  continue;
15240
17117
  }
@@ -15276,7 +17153,7 @@ async function validateForbiddenLegacyFiles(root, _config) {
15276
17153
  if (!await isUiBearingSpec(root)) return [];
15277
17154
  let entries = [];
15278
17155
  try {
15279
- entries = await (0, import_promises53.readdir)(import_node_path58.default.join(root, "uiux"));
17156
+ entries = await (0, import_promises61.readdir)(import_node_path66.default.join(root, "uiux"));
15280
17157
  } catch {
15281
17158
  return [];
15282
17159
  }
@@ -15292,11 +17169,11 @@ async function validateForbiddenLegacyFiles(root, _config) {
15292
17169
  }
15293
17170
  async function validateThreeLayerFamilyCompleteness(root, _config) {
15294
17171
  if (!await isUiBearingSpec(root)) return [];
15295
- const indexContent = await readSafe(import_node_path58.default.join(root, "uiux", "00_index.md"));
17172
+ const indexContent = await readSafe(import_node_path66.default.join(root, "uiux", "00_index.md"));
15296
17173
  if (!indexContent) return [];
15297
17174
  const issues = [];
15298
17175
  for (const required of CANONICAL_REQUIRED_SIDECAR_FILES) {
15299
- const content = await readSafe(import_node_path58.default.join(root, "uiux", required));
17176
+ const content = await readSafe(import_node_path66.default.join(root, "uiux", required));
15300
17177
  if (!content) {
15301
17178
  issues.push(
15302
17179
  threeLayerIssue(
@@ -15313,7 +17190,7 @@ async function validateThreeLayerFamilyCompleteness(root, _config) {
15313
17190
  }
15314
17191
 
15315
17192
  // src/core/validators/uix/taste.ts
15316
- var import_node_path59 = __toESM(require("path"), 1);
17193
+ var import_node_path67 = __toESM(require("path"), 1);
15317
17194
  init_utils();
15318
17195
  var REQUIRED_SECTIONS = [
15319
17196
  "visual_character",
@@ -15330,11 +17207,11 @@ var REQUIRED_SECTIONS = [
15330
17207
  var REQUIRED_SECTION_COUNT = REQUIRED_SECTIONS.length;
15331
17208
 
15332
17209
  // src/core/validators/uix/trendScan.ts
15333
- var import_node_path60 = __toESM(require("path"), 1);
17210
+ var import_node_path68 = __toESM(require("path"), 1);
15334
17211
  init_utils();
15335
17212
 
15336
17213
  // src/core/validators/uix/strategy.ts
15337
- var import_node_path61 = __toESM(require("path"), 1);
17214
+ var import_node_path69 = __toESM(require("path"), 1);
15338
17215
  init_surface();
15339
17216
 
15340
17217
  // src/core/domain/strategyDecision.ts
@@ -15352,9 +17229,9 @@ var CANONICAL_STRATEGY_DECISION_SET = new Set(CANONICAL_STRATEGY_DECISIONS);
15352
17229
  init_utils();
15353
17230
 
15354
17231
  // src/core/validators/uix/screenContract.ts
15355
- var import_node_path62 = __toESM(require("path"), 1);
17232
+ var import_node_path70 = __toESM(require("path"), 1);
15356
17233
  init_utils();
15357
- var REQUIRED_FIELDS = [
17234
+ var REQUIRED_FIELDS2 = [
15358
17235
  "screen_id",
15359
17236
  "route",
15360
17237
  "purpose",
@@ -15483,7 +17360,7 @@ function assignNestedChild(block, key, value) {
15483
17360
  }
15484
17361
  async function validateScreenContractSchema(root, _config) {
15485
17362
  if (!await isUiBearingSpec(root)) return [];
15486
- const contractsPath = import_node_path62.default.join(root, "uiux", "40_screen_contracts.md");
17363
+ const contractsPath = import_node_path70.default.join(root, "uiux", "40_screen_contracts.md");
15487
17364
  const content = await readSafe(contractsPath);
15488
17365
  if (!content) return [];
15489
17366
  const screens = parseScreenBlocks2(content);
@@ -15519,7 +17396,7 @@ async function validateScreenContractSchema(root, _config) {
15519
17396
  );
15520
17397
  continue;
15521
17398
  }
15522
- const missing = REQUIRED_FIELDS.filter((f) => {
17399
+ const missing = REQUIRED_FIELDS2.filter((f) => {
15523
17400
  if (f === "primary_tasks") return screen.primaryTasks.length === 0;
15524
17401
  if (f === "secondary_tasks") return screen.secondaryTasks.length === 0;
15525
17402
  if (f === "required_states") return Object.keys(screen.requiredStates).length === 0;
@@ -15556,11 +17433,11 @@ async function validateScreenContractSchema(root, _config) {
15556
17433
  }
15557
17434
 
15558
17435
  // src/core/validators/uix/canonical.ts
15559
- var import_node_path67 = __toESM(require("path"), 1);
17436
+ var import_node_path75 = __toESM(require("path"), 1);
15560
17437
  init_utils();
15561
17438
 
15562
17439
  // src/core/validators/uix/classification.ts
15563
- var import_node_path63 = __toESM(require("path"), 1);
17440
+ var import_node_path71 = __toESM(require("path"), 1);
15564
17441
  init_surfaceType();
15565
17442
  init_utils();
15566
17443
  var VALID_PRIMARY_SURFACES = [
@@ -15583,7 +17460,7 @@ function classificationIssue(code, message, severity, file, suggestedAction) {
15583
17460
  };
15584
17461
  }
15585
17462
  async function validateClassification(root, _config) {
15586
- const contextPath = import_node_path63.default.join(root, "01_Context.md");
17463
+ const contextPath = import_node_path71.default.join(root, "01_Context.md");
15587
17464
  const content = await readSafe(contextPath);
15588
17465
  if (!content) {
15589
17466
  return [];
@@ -15761,8 +17638,8 @@ async function validateClassification(root, _config) {
15761
17638
  }
15762
17639
 
15763
17640
  // src/core/validators/uix/foundation.ts
15764
- var import_promises54 = require("fs/promises");
15765
- var import_node_path64 = __toESM(require("path"), 1);
17641
+ var import_promises62 = require("fs/promises");
17642
+ var import_node_path72 = __toESM(require("path"), 1);
15766
17643
  function canonicalIssue2(code, message, severity, file, suggestedAction) {
15767
17644
  return {
15768
17645
  code,
@@ -15776,7 +17653,7 @@ function canonicalIssue2(code, message, severity, file, suggestedAction) {
15776
17653
  async function validateSidecarMissing(root, _config) {
15777
17654
  if (!await isUiBearingSpec(root)) return [];
15778
17655
  try {
15779
- await (0, import_promises54.readdir)(import_node_path64.default.join(root, "uiux"));
17656
+ await (0, import_promises62.readdir)(import_node_path72.default.join(root, "uiux"));
15780
17657
  return [];
15781
17658
  } catch (err) {
15782
17659
  if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
@@ -15795,7 +17672,7 @@ async function validateSidecarMissing(root, _config) {
15795
17672
  }
15796
17673
 
15797
17674
  // src/core/validators/uix/comparisonValidator.ts
15798
- var import_node_path65 = __toESM(require("path"), 1);
17675
+ var import_node_path73 = __toESM(require("path"), 1);
15799
17676
  init_utils();
15800
17677
  function canonicalIssue3(code, message, severity, file, suggestedAction) {
15801
17678
  return {
@@ -15810,10 +17687,10 @@ function canonicalIssue3(code, message, severity, file, suggestedAction) {
15810
17687
  async function validateExplorationArtifacts(root, _config) {
15811
17688
  if (!await isUiBearingSpec(root)) return [];
15812
17689
  const issues = [];
15813
- const briefPath = import_node_path65.default.join(root, "uiux", "30_exploration_brief.md");
15814
- const rubricPath = import_node_path65.default.join(root, "uiux", "33_exploration_rubric.md");
15815
- const calibrationPath = import_node_path65.default.join(root, "uiux", "34_evaluator_calibration.md");
15816
- const reviewBundlePath = import_node_path65.default.join(root, "uiux", "50_review_input_bundle.md");
17690
+ const briefPath = import_node_path73.default.join(root, "uiux", "30_exploration_brief.md");
17691
+ const rubricPath = import_node_path73.default.join(root, "uiux", "33_exploration_rubric.md");
17692
+ const calibrationPath = import_node_path73.default.join(root, "uiux", "34_evaluator_calibration.md");
17693
+ const reviewBundlePath = import_node_path73.default.join(root, "uiux", "50_review_input_bundle.md");
15817
17694
  const briefContent = await readSafe(briefPath);
15818
17695
  if (!briefContent) {
15819
17696
  issues.push(
@@ -15913,7 +17790,7 @@ async function validateExplorationArtifacts(root, _config) {
15913
17790
  }
15914
17791
 
15915
17792
  // src/core/validators/uix/oqClosure.ts
15916
- var import_node_path66 = __toESM(require("path"), 1);
17793
+ var import_node_path74 = __toESM(require("path"), 1);
15917
17794
  init_utils();
15918
17795
  function canonicalIssue4(code, message, severity, file, suggestedAction) {
15919
17796
  return {
@@ -15927,8 +17804,8 @@ function canonicalIssue4(code, message, severity, file, suggestedAction) {
15927
17804
  }
15928
17805
  async function validateOqClosure(root, _config) {
15929
17806
  if (!await isUiBearingSpec(root)) return [];
15930
- const rootOqPath = import_node_path66.default.join(root, "11_OQ-Register.md");
15931
- const uiuxOqPath = import_node_path66.default.join(root, "uiux", "11_OQ-Register.md");
17807
+ const rootOqPath = import_node_path74.default.join(root, "11_OQ-Register.md");
17808
+ const uiuxOqPath = import_node_path74.default.join(root, "uiux", "11_OQ-Register.md");
15932
17809
  const rootContent = await readSafe(rootOqPath);
15933
17810
  const content = rootContent || await readSafe(uiuxOqPath);
15934
17811
  if (!content) return [];
@@ -15980,7 +17857,7 @@ async function validateOqClosure(root, _config) {
15980
17857
 
15981
17858
  // src/core/validators/uix/canonical.ts
15982
17859
  async function runCanonicalUixValidators(root, config) {
15983
- const directSpec = await readSafe(import_node_path67.default.join(root, "01_Spec.md"));
17860
+ const directSpec = await readSafe(import_node_path75.default.join(root, "01_Spec.md"));
15984
17861
  if (!directSpec) {
15985
17862
  return [];
15986
17863
  }
@@ -16007,8 +17884,8 @@ async function runCanonicalUixValidators(root, config) {
16007
17884
 
16008
17885
  // src/core/validators/traceabilityIntegrity.ts
16009
17886
  var import_node_child_process = require("child_process");
16010
- var import_promises55 = require("fs/promises");
16011
- var import_node_path68 = __toESM(require("path"), 1);
17887
+ var import_promises63 = require("fs/promises");
17888
+ var import_node_path76 = __toESM(require("path"), 1);
16012
17889
  init_utils();
16013
17890
  var BR_AC_FILES = /* @__PURE__ */ new Set(["04_Business-Rules.md", "03_Acceptance-Criteria.md"]);
16014
17891
  function normalizePath2(p) {
@@ -16092,10 +17969,10 @@ async function validateTraceabilityIntegrity(root, config) {
16092
17969
  const specsRelDir = config.paths.specsDir;
16093
17970
  const changedSpecIds = findChangedSpecDirs(changedFiles, specsRelDir);
16094
17971
  for (const specId of changedSpecIds) {
16095
- const ledgerPath = import_node_path68.default.join(specsDir, specId, "16_Traceability-ledger.md");
17972
+ const ledgerPath = import_node_path76.default.join(specsDir, specId, "16_Traceability-ledger.md");
16096
17973
  let ledgerContent;
16097
17974
  try {
16098
- ledgerContent = await (0, import_promises55.readFile)(ledgerPath, "utf-8");
17975
+ ledgerContent = await (0, import_promises63.readFile)(ledgerPath, "utf-8");
16099
17976
  } catch {
16100
17977
  issues.push(
16101
17978
  issue(
@@ -16351,16 +18228,187 @@ function validatePrototypingSkillContent(content) {
16351
18228
  };
16352
18229
  }
16353
18230
 
18231
+ // src/core/validators/testTodoStubs.ts
18232
+ var import_promises64 = require("fs/promises");
18233
+ var import_node_path77 = __toESM(require("path"), 1);
18234
+ init_utils();
18235
+ var TEST_TODO_PATTERN = /\b(it|test|describe)\.todo\s*\(/g;
18236
+ async function validateTestTodoStubs(root, config) {
18237
+ if (!config.validation.testStrategy.forbidTestTodoStubs) {
18238
+ return [];
18239
+ }
18240
+ const globs = config.validation.traceability.testFileGlobs;
18241
+ if (globs.length === 0) {
18242
+ return [];
18243
+ }
18244
+ const excludeGlobs = Array.from(
18245
+ /* @__PURE__ */ new Set([
18246
+ ...DEFAULT_TEST_FILE_EXCLUDE_GLOBS,
18247
+ ...config.validation.traceability.testFileExcludeGlobs
18248
+ ])
18249
+ );
18250
+ const { files } = await collectFilesByGlobs(root, {
18251
+ globs,
18252
+ ignore: excludeGlobs,
18253
+ limit: DEFAULT_GLOB_FILE_LIMIT
18254
+ });
18255
+ const issues = [];
18256
+ for (const absFile of files) {
18257
+ const relFile = import_node_path77.default.relative(root, absFile).replace(/\\/g, "/");
18258
+ let content;
18259
+ try {
18260
+ content = await (0, import_promises64.readFile)(absFile, "utf-8");
18261
+ } catch {
18262
+ continue;
18263
+ }
18264
+ const lines = content.split(/\r?\n/);
18265
+ for (let i = 0; i < lines.length; i += 1) {
18266
+ const line = lines[i] ?? "";
18267
+ const lineNumber = i + 1;
18268
+ for (const match of line.matchAll(TEST_TODO_PATTERN)) {
18269
+ const matchedKind = match[1];
18270
+ const stubIssue = issue(
18271
+ "QFAI-TEST-001",
18272
+ `Test todo stub found: ${matchedKind}.todo at ${relFile}:${lineNumber}. Stubs are silent in vitest/jest and rot as missed work. Implement the body or delete the stub (spec-0017 REQ-0009).`,
18273
+ "error",
18274
+ relFile,
18275
+ "validation.testStrategy.forbidTestTodoStubs",
18276
+ [`${matchedKind}.todo`],
18277
+ "canonical",
18278
+ "Implement the test body, or delete the stub entirely. If you need to temporarily opt out of this check, set `validation.testStrategy.forbidTestTodoStubs: false` in qfai.config.yaml."
18279
+ );
18280
+ stubIssue.loc = { line: lineNumber };
18281
+ issues.push(stubIssue);
18282
+ }
18283
+ }
18284
+ }
18285
+ return issues;
18286
+ }
18287
+
16354
18288
  // src/core/validate.ts
16355
18289
  init_utils();
16356
18290
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
16357
18291
  async function validateProject(root, configResult, options = {}) {
16358
18292
  const resolved = configResult ?? await loadConfig(root);
16359
18293
  const { config, issues: configIssues } = resolved;
16360
- const phase = options.phase ?? "full";
16361
- const atddCodeTraceabilityIssues = phase === "refinement" ? [] : await validateAtddCodeTraceability(root, config);
18294
+ const profile = options.profile ?? "full";
18295
+ const findings = [
18296
+ ...configIssues,
18297
+ ...await runProfileValidators(root, config, profile, options.platform)
18298
+ ];
18299
+ const { issues, waivers } = await applyWaivers(root, findings);
18300
+ const specsRoot = resolvePath(root, config, "specsDir");
18301
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
18302
+ const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
18303
+ const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
18304
+ root,
18305
+ config.validation.traceability.testFileGlobs,
18306
+ config.validation.traceability.testFileExcludeGlobs
18307
+ );
18308
+ const scCoverage = buildScCoverage(scIds, scTestRefs);
18309
+ const toolVersion = await resolveToolVersion();
18310
+ return {
18311
+ toolVersion,
18312
+ profile,
18313
+ issues,
18314
+ counts: countIssues(issues),
18315
+ traceability: {
18316
+ sc: scCoverage,
18317
+ testFiles
18318
+ },
18319
+ waivers
18320
+ };
18321
+ }
18322
+ async function runProfileValidators(root, config, profile, platformOption) {
18323
+ switch (profile) {
18324
+ case "discussion":
18325
+ return runDiscussionValidators(root, config);
18326
+ case "sdd":
18327
+ return runSddValidators(root, config);
18328
+ case "prototyping":
18329
+ return runPrototypingValidators(root, config, platformOption);
18330
+ case "atdd":
18331
+ return runAtddValidators(root, config);
18332
+ case "tdd":
18333
+ return runTddValidators(root, config);
18334
+ case "verify":
18335
+ case "full":
18336
+ return runFullValidators(root, config, platformOption);
18337
+ }
18338
+ }
18339
+ async function runDiscussionValidators(root, config) {
18340
+ return [
18341
+ ...await validateDiscussionMermaid(root),
18342
+ ...await validateDiscussionPackReadiness(root, config),
18343
+ ...await validateDiscussionVisuals(root),
18344
+ ...await validateDiscussionDesignHardening(root, config),
18345
+ ...await validateResearchSummary(root, config),
18346
+ ...await runCanonicalUixValidators(root, config)
18347
+ ];
18348
+ }
18349
+ async function runSddValidators(root, config, includeCodeReferences = false) {
18350
+ return [
18351
+ ...await validateMermaidEnforcement(root),
18352
+ ...await validateSpecPacks(root, config),
18353
+ ...await validateStatusInSpecs(root, config),
18354
+ ...await validateDensityHints(root, config),
18355
+ ...await validateSpecSplitByCapability(root, config),
18356
+ ...await validateLayeredTraceability(root, config),
18357
+ ...await validateOrphanProhibition(root, config),
18358
+ ...await validateLayerCoverage(root, config),
18359
+ ...await validateContractReferences(root, config),
18360
+ ...await validateTraceability(root, config, { includeCodeReferences }),
18361
+ ...await validateDefinedIds(root, config),
18362
+ ...await validateContracts(root, config),
18363
+ ...await validateNavigationFlow(root, config)
18364
+ ];
18365
+ }
18366
+ async function runPrototypingValidators(root, config, platformOption) {
18367
+ return [
18368
+ ...await runUiuxValidators(root, config, platformOption),
18369
+ ...await validatePrototypingEvidence(root, config),
18370
+ ...await validateBreakthroughEvidence(root, config),
18371
+ ...await validatePrototypingDesignSystem(root, config),
18372
+ ...await validateUiEvidenceArtifacts(root, config),
18373
+ ...await validateRenderCritique(root, config),
18374
+ ...await validateDesignFidelity(root, config),
18375
+ ...await validateDesignContractReadiness(root, config),
18376
+ ...await validateEvaluatorReviewHardFloor(root, config),
18377
+ ...await validateStateGate(root, config),
18378
+ ...await validateCompletionCertificateIssues(root, config),
18379
+ ...await validateConfigReferenceIntegrity(root, config),
18380
+ ...await validatePrototypingArtifactRefIntegrity(root, config),
18381
+ ...await validateSpecIdLinkage(root, config)
18382
+ ];
18383
+ }
18384
+ async function runAtddValidators(root, config) {
18385
+ return [...await validateAtddCodeTraceability(root, config)];
18386
+ }
18387
+ async function runTddValidators(root, config, includeTraceability = true) {
18388
+ return [
18389
+ ...await validateTddList(root, config),
18390
+ ...await validateTestTodoStubs(root, config),
18391
+ ...includeTraceability ? await validateTraceability(root, config, { includeCodeReferences: true }) : [],
18392
+ ...await validateTraceabilityIntegrity(root, config)
18393
+ ];
18394
+ }
18395
+ async function runFullValidators(root, config, platformOption) {
18396
+ return [
18397
+ ...await validateRepositoryHygiene(root, config),
18398
+ ...await validateSkillsIntegrity(root, config),
18399
+ ...await validateAssistantAssets(root, config),
18400
+ ...await runDiscussionValidators(root, config),
18401
+ ...await runSddValidators(root, config, true),
18402
+ ...await validateReviewArtifacts(root),
18403
+ ...await runPrototypingValidators(root, config, platformOption),
18404
+ ...await runAtddValidators(root, config),
18405
+ ...await runTddValidators(root, config, false),
18406
+ ...await validatePrototypingSkill(root, config)
18407
+ ];
18408
+ }
18409
+ async function runUiuxValidators(root, config, platformOption) {
16362
18410
  const uiuxStart = performance.now();
16363
- const platformResult = await detectPlatform(root, config, options.platform);
18411
+ const platformResult = await detectPlatform(root, config, platformOption);
16364
18412
  const platform = platformResult.platform;
16365
18413
  const uiuxValidators = [
16366
18414
  () => validateDesignToken(root, config),
@@ -16386,68 +18434,13 @@ async function validateProject(root, configResult, options = {}) {
16386
18434
  rule: "uiux.performanceBudget"
16387
18435
  });
16388
18436
  }
18437
+ return uiuxIssues;
18438
+ }
18439
+ async function validatePrototypingSkill(root, config) {
16389
18440
  const skillsDir = resolvePath(root, config, "skillsDir");
16390
- const prototypingSkillPath = import_node_path69.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
18441
+ const prototypingSkillPath = import_node_path78.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
16391
18442
  const prototypingSkillContent = await readSafe(prototypingSkillPath);
16392
- const prototypingSkillResult = prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent) : { issues: [] };
16393
- const findings = [
16394
- ...configIssues,
16395
- ...await validateRepositoryHygiene(root, config),
16396
- ...await validateSkillsIntegrity(root, config),
16397
- ...await validateAssistantAssets(root, config),
16398
- ...await validateDiscussionMermaid(root),
16399
- ...await validateMermaidEnforcement(root),
16400
- ...await validateSpecPacks(root, config),
16401
- ...await validateDiscussionPackReadiness(root, config),
16402
- ...await validateDiscussionVisuals(root),
16403
- ...await validateStatusInSpecs(root, config),
16404
- ...await validateDensityHints(root, config),
16405
- ...await validateReviewArtifacts(root),
16406
- ...await validatePrototypingEvidence(root, config),
16407
- ...await validateBreakthroughEvidence(root, config),
16408
- ...await validatePrototypingDesignSystem(root, config),
16409
- ...await validateUiEvidenceArtifacts(root, config),
16410
- ...await validateSpecSplitByCapability(root, config),
16411
- ...await validateLayeredTraceability(root, config),
16412
- ...await validateOrphanProhibition(root, config),
16413
- ...await validateLayerCoverage(root, config),
16414
- ...atddCodeTraceabilityIssues,
16415
- ...await validateContractReferences(root, config),
16416
- ...await validateTraceability(root, config, phase),
16417
- ...await validateDefinedIds(root, config),
16418
- ...await validateContracts(root, config),
16419
- ...await validateTddList(root, config),
16420
- ...await validateDiscussionDesignHardening(root, config),
16421
- ...await validateNavigationFlow(root, config),
16422
- ...await validateRenderCritique(root, config),
16423
- ...await validateDesignFidelity(root, config),
16424
- ...await validateDesignContractReadiness(root, config),
16425
- ...await validateTraceabilityIntegrity(root, config),
16426
- ...prototypingSkillResult.issues,
16427
- ...uiuxIssues
16428
- ];
16429
- const { issues, waivers } = await applyWaivers(root, findings);
16430
- const specsRoot = resolvePath(root, config, "specsDir");
16431
- const scenarioFiles = await collectScenarioFiles(specsRoot);
16432
- const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
16433
- const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
16434
- root,
16435
- config.validation.traceability.testFileGlobs,
16436
- config.validation.traceability.testFileExcludeGlobs
16437
- );
16438
- const scCoverage = buildScCoverage(scIds, scTestRefs);
16439
- const toolVersion = await resolveToolVersion();
16440
- return {
16441
- toolVersion,
16442
- phase,
16443
- issues,
16444
- counts: countIssues(issues),
16445
- traceability: {
16446
- sc: scCoverage,
16447
- testFiles
16448
- },
16449
- waivers
16450
- };
18443
+ return prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent).issues : [];
16451
18444
  }
16452
18445
  function countIssues(issues) {
16453
18446
  return issues.reduce(
@@ -16463,11 +18456,11 @@ function countIssues(issues) {
16463
18456
  }
16464
18457
 
16465
18458
  // src/core/uiux/renderEvidence.ts
16466
- var import_promises56 = require("fs/promises");
18459
+ var import_promises65 = require("fs/promises");
16467
18460
  async function readRenderEvidenceBundle(filePath) {
16468
18461
  let raw;
16469
18462
  try {
16470
- raw = await (0, import_promises56.readFile)(filePath, "utf-8");
18463
+ raw = await (0, import_promises65.readFile)(filePath, "utf-8");
16471
18464
  } catch {
16472
18465
  return null;
16473
18466
  }
@@ -16477,9 +18470,9 @@ async function readRenderEvidenceBundle(filePath) {
16477
18470
  } catch {
16478
18471
  return null;
16479
18472
  }
16480
- return isRecord7(parsed) ? parsed : null;
18473
+ return isRecord17(parsed) ? parsed : null;
16481
18474
  }
16482
- function isRecord7(value) {
18475
+ function isRecord17(value) {
16483
18476
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16484
18477
  }
16485
18478
  function summarizeRenderEvidence(bundle) {
@@ -16487,7 +18480,7 @@ function summarizeRenderEvidence(bundle) {
16487
18480
  let inlinePayloadViolation = false;
16488
18481
  if (bundle?.screens) {
16489
18482
  for (const screen of bundle.screens) {
16490
- if (isRecord7(screen)) {
18483
+ if (isRecord17(screen)) {
16491
18484
  const s = screen;
16492
18485
  if (s.status === "captured" || s.status === "skipped" || s.status === "failed") {
16493
18486
  counts[s.status] += 1;
@@ -16512,7 +18505,7 @@ function summarizeRenderEvidence(bundle) {
16512
18505
  }
16513
18506
 
16514
18507
  // src/core/browserQa/index.ts
16515
- var import_promises57 = require("fs/promises");
18508
+ var import_promises66 = require("fs/promises");
16516
18509
 
16517
18510
  // src/core/browserQa/types.ts
16518
18511
  var BROWSER_QA_PHASES = [
@@ -17075,7 +19068,7 @@ function summarizeBrowserQaResult(result) {
17075
19068
  async function readBrowserQaBundle(filePath) {
17076
19069
  let raw;
17077
19070
  try {
17078
- raw = await (0, import_promises57.readFile)(filePath, "utf-8");
19071
+ raw = await (0, import_promises66.readFile)(filePath, "utf-8");
17079
19072
  } catch {
17080
19073
  return null;
17081
19074
  }
@@ -17085,9 +19078,9 @@ async function readBrowserQaBundle(filePath) {
17085
19078
  } catch {
17086
19079
  return null;
17087
19080
  }
17088
- return isRecord8(parsed) ? parsed : null;
19081
+ return isRecord18(parsed) ? parsed : null;
17089
19082
  }
17090
- function isRecord8(value) {
19083
+ function isRecord18(value) {
17091
19084
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17092
19085
  }
17093
19086
 
@@ -17096,15 +19089,15 @@ var REPORT_GUARDRAILS_MAX = 20;
17096
19089
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
17097
19090
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
17098
19091
  async function createReportData(root, validation, configResult) {
17099
- const resolvedRoot = import_node_path70.default.resolve(root);
19092
+ const resolvedRoot = import_node_path79.default.resolve(root);
17100
19093
  const resolved = configResult ?? await loadConfig(resolvedRoot);
17101
19094
  const config = resolved.config;
17102
19095
  const configPath = resolved.configPath;
17103
19096
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
17104
19097
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
17105
- const apiRoot = import_node_path70.default.join(contractsRoot, "api");
17106
- const uiRoot = import_node_path70.default.join(contractsRoot, "ui");
17107
- const dbRoot = import_node_path70.default.join(contractsRoot, "db");
19098
+ const apiRoot = import_node_path79.default.join(contractsRoot, "api");
19099
+ const uiRoot = import_node_path79.default.join(contractsRoot, "ui");
19100
+ const dbRoot = import_node_path79.default.join(contractsRoot, "db");
17108
19101
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
17109
19102
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
17110
19103
  const specEntries = await collectSpecEntries(specsRoot);
@@ -17901,6 +19894,28 @@ function formatReportMarkdown(data, options = {}) {
17901
19894
  );
17902
19895
  lines.push("");
17903
19896
  }
19897
+ if (data.prototyping.roundLifecycle) {
19898
+ const lifecycle = data.prototyping.roundLifecycle;
19899
+ lines.push("### prototyping.roundLifecycle");
19900
+ lines.push("");
19901
+ lines.push(`- schemaVersion: ${lifecycle.schemaVersion}`);
19902
+ lines.push(`- rounds: ${lifecycle.rounds}`);
19903
+ lines.push(`- round ids: ${lifecycle.roundIds.join(", ") || "(none)"}`);
19904
+ lines.push(`- candidates observed: ${lifecycle.candidatesObserved}`);
19905
+ lines.push(`- harvest artifacts: ${lifecycle.harvestArtifacts}`);
19906
+ lines.push(`- narrow decisions: ${lifecycle.narrowDecisions}`);
19907
+ lines.push(`- absorption plans: ${lifecycle.absorptionPlans}`);
19908
+ lines.push(`- reimplementations: ${lifecycle.reimplementations}`);
19909
+ lines.push(`- perfect rounds: ${lifecycle.perfectRounds}`);
19910
+ lines.push(`- polish cycles: ${lifecycle.polishCycles}`);
19911
+ lines.push(`- completed polish cycles: ${lifecycle.completedPolishCycles}`);
19912
+ lines.push(`- perfect polish cycles: ${lifecycle.perfectPolishCycles}`);
19913
+ if (Object.keys(lifecycle.polishKinds).length > 0) {
19914
+ const kinds = Object.entries(lifecycle.polishKinds).map(([kind, count]) => `${kind}=${count}`).join(", ");
19915
+ lines.push(`- polish kinds: ${kinds}`);
19916
+ }
19917
+ lines.push("");
19918
+ }
17904
19919
  lines.push("### prototyping.mode");
17905
19920
  lines.push("");
17906
19921
  lines.push(`- requested: ${data.prototyping.mode.requested ?? "(none)"}`);
@@ -18257,7 +20272,7 @@ async function collectChangeTypeSummary(specsRoot) {
18257
20272
  };
18258
20273
  const deltaFiles = await collectDeltaFiles(specsRoot);
18259
20274
  for (const deltaFile of deltaFiles) {
18260
- const text = await (0, import_promises58.readFile)(deltaFile, "utf-8");
20275
+ const text = await (0, import_promises67.readFile)(deltaFile, "utf-8");
18261
20276
  const parsed = parseDeltaV1(text);
18262
20277
  for (const entry of parsed.entries) {
18263
20278
  if (!entry.meta) {
@@ -18286,13 +20301,46 @@ async function collectChangeTypeSummary(specsRoot) {
18286
20301
  }
18287
20302
  return summary;
18288
20303
  }
20304
+ var PROTOTYPING_ROUND_DIR_NAMES = new Set(EXPLORATION_ROUNDS);
20305
+ async function scanPrototypingRoundsFilesystem(evidenceRoot) {
20306
+ const result = {
20307
+ observedRoundIds: [],
20308
+ harvestArtifacts: 0,
20309
+ narrowDecisions: 0,
20310
+ absorptionPlans: 0,
20311
+ reimplementations: 0
20312
+ };
20313
+ const roundsDir = import_node_path79.default.join(evidenceRoot, "prototyping", "rounds");
20314
+ const { readdir: readdir14 } = await import("fs/promises");
20315
+ let entries;
20316
+ try {
20317
+ entries = await readdir14(roundsDir);
20318
+ } catch {
20319
+ return result;
20320
+ }
20321
+ for (const name of entries.sort()) {
20322
+ if (!PROTOTYPING_ROUND_DIR_NAMES.has(name)) continue;
20323
+ let files;
20324
+ try {
20325
+ files = await readdir14(import_node_path79.default.join(roundsDir, name));
20326
+ } catch {
20327
+ continue;
20328
+ }
20329
+ result.observedRoundIds.push(name);
20330
+ if (files.includes("harvest.json")) result.harvestArtifacts += 1;
20331
+ if (files.includes("narrow-decision.json")) result.narrowDecisions += 1;
20332
+ if (files.includes("absorption-plan.json")) result.absorptionPlans += 1;
20333
+ if (files.includes("reimplementation.json")) result.reimplementations += 1;
20334
+ }
20335
+ return result;
20336
+ }
18289
20337
  async function collectPrototypingSummary(root, config) {
18290
20338
  const specsRoot = resolvePath(root, config, "specsDir");
18291
- const evidenceRoot = import_node_path70.default.join(import_node_path70.default.dirname(specsRoot), "evidence");
18292
- const evidencePath = import_node_path70.default.join(evidenceRoot, "prototyping.json");
20339
+ const evidenceRoot = import_node_path79.default.join(import_node_path79.default.dirname(specsRoot), "evidence");
20340
+ const evidencePath = import_node_path79.default.join(evidenceRoot, "prototyping.json");
18293
20341
  let raw;
18294
20342
  try {
18295
- raw = await (0, import_promises58.readFile)(evidencePath, "utf-8");
20343
+ raw = await (0, import_promises67.readFile)(evidencePath, "utf-8");
18296
20344
  } catch {
18297
20345
  return void 0;
18298
20346
  }
@@ -18319,10 +20367,12 @@ async function collectPrototypingSummary(root, config) {
18319
20367
  const warnings = [];
18320
20368
  const obligations = derivePrototypingObligations({ surface: effectiveSurface, effectiveMode });
18321
20369
  const fullHarness = asRecord2(record2.fullHarness);
20370
+ const rounds = Array.isArray(record2.rounds) ? record2.rounds : [];
20371
+ const polishCycles = Array.isArray(record2.polishCycles) ? record2.polishCycles : [];
18322
20372
  const runtimeGate = asRecord2(record2.runtimeGate);
18323
20373
  const uiFidelity = asRecord2(record2.uiFidelity);
18324
- const renderBundle = await readRenderEvidenceBundle(import_node_path70.default.join(evidenceRoot, "render.json"));
18325
- const browserQaBundle = await readBrowserQaBundle(import_node_path70.default.join(evidenceRoot, "browser-qa.json"));
20374
+ const renderBundle = await readRenderEvidenceBundle(import_node_path79.default.join(evidenceRoot, "render.json"));
20375
+ const browserQaBundle = await readBrowserQaBundle(import_node_path79.default.join(evidenceRoot, "browser-qa.json"));
18326
20376
  const specs = Array.isArray(record2.specs) ? record2.specs : [];
18327
20377
  const specEntries = await collectSpecEntries(specsRoot);
18328
20378
  const expectedSpecIds = specEntries.map((entry) => `spec-${entry.specNumber}`.toLowerCase());
@@ -18379,8 +20429,81 @@ async function collectPrototypingSummary(root, config) {
18379
20429
  ...classificationBlock?.secondary_surfaces?.length ? { secondarySurfaces: classificationBlock.secondary_surfaces } : {},
18380
20430
  ...classificationBlock?.classification_rationale ? { classificationRationale: classificationBlock.classification_rationale } : {}
18381
20431
  };
20432
+ const fsRoundScan = await scanPrototypingRoundsFilesystem(evidenceRoot);
20433
+ const roundLifecycle = record2.schemaVersion === "2.0" || rounds.length > 0 || polishCycles.length > 0 || fsRoundScan.observedRoundIds.length > 0 ? (() => {
20434
+ const polishKinds = {};
20435
+ let candidatesObserved = 0;
20436
+ let perfectRounds = 0;
20437
+ let completedPolishCycles = 0;
20438
+ let perfectPolishCycles = 0;
20439
+ for (const item of rounds) {
20440
+ const round = asRecord2(item);
20441
+ if (!round) {
20442
+ continue;
20443
+ }
20444
+ const candidates = Array.isArray(round.candidates) ? round.candidates : [];
20445
+ candidatesObserved += candidates.length;
20446
+ if (round.allAxesPerfect100 === true) {
20447
+ perfectRounds += 1;
20448
+ }
20449
+ }
20450
+ for (const item of polishCycles) {
20451
+ const cycle = asRecord2(item);
20452
+ if (!cycle) {
20453
+ continue;
20454
+ }
20455
+ if (typeof cycle.kind === "string" && cycle.kind.length > 0) {
20456
+ polishKinds[cycle.kind] = (polishKinds[cycle.kind] ?? 0) + 1;
20457
+ if (cycle.kind === "completed") {
20458
+ completedPolishCycles += 1;
20459
+ }
20460
+ }
20461
+ if (cycle.allAxesPerfect100 === true) {
20462
+ perfectPolishCycles += 1;
20463
+ }
20464
+ }
20465
+ const indexAbsorptionRefs = rounds.map((item) => asRecord2(item)).filter(
20466
+ (round) => round && typeof round.absorptionPlanRef === "string" && round.absorptionPlanRef.length > 0
20467
+ ).length;
20468
+ if (indexAbsorptionRefs !== fsRoundScan.absorptionPlans) {
20469
+ warnings.push(
20470
+ `prototyping.json index references ${indexAbsorptionRefs} absorption-plan(s) but filesystem has ${fsRoundScan.absorptionPlans}; counts use filesystem (canonical SoT).`
20471
+ );
20472
+ }
20473
+ const indexReimplRefs = rounds.map((item) => asRecord2(item)).filter(
20474
+ (round) => round && typeof round.reimplementationRef === "string" && round.reimplementationRef.length > 0
20475
+ ).length;
20476
+ if (indexReimplRefs !== fsRoundScan.reimplementations) {
20477
+ warnings.push(
20478
+ `prototyping.json index references ${indexReimplRefs} reimplementation record(s) but filesystem has ${fsRoundScan.reimplementations}; counts use filesystem (canonical SoT).`
20479
+ );
20480
+ }
20481
+ const indexRoundIds = rounds.map((item) => asRecord2(item)).flatMap(
20482
+ (round) => round && typeof round.round === "string" && round.round.length > 0 ? [round.round] : []
20483
+ );
20484
+ const roundIdSet = /* @__PURE__ */ new Set([...indexRoundIds, ...fsRoundScan.observedRoundIds]);
20485
+ return {
20486
+ schemaVersion: "2.0",
20487
+ // Use the union size — Math.max underestimates when index and
20488
+ // filesystem describe disjoint round IDs (Copilot MAJOR review
20489
+ // on PR #201).
20490
+ rounds: roundIdSet.size,
20491
+ roundIds: Array.from(roundIdSet),
20492
+ candidatesObserved,
20493
+ perfectRounds,
20494
+ harvestArtifacts: fsRoundScan.harvestArtifacts,
20495
+ narrowDecisions: fsRoundScan.narrowDecisions,
20496
+ absorptionPlans: fsRoundScan.absorptionPlans,
20497
+ reimplementations: fsRoundScan.reimplementations,
20498
+ polishCycles: polishCycles.length,
20499
+ completedPolishCycles,
20500
+ perfectPolishCycles,
20501
+ polishKinds
20502
+ };
20503
+ })() : void 0;
18382
20504
  return {
18383
20505
  surfaceClassification,
20506
+ ...roundLifecycle ? { roundLifecycle } : {},
18384
20507
  mode: {
18385
20508
  ...resolvedMode.requested ? { requested: resolvedMode.requested } : {},
18386
20509
  effective: effectiveMode,
@@ -18473,7 +20596,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
18473
20596
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
18474
20597
  }
18475
20598
  for (const file of specFiles) {
18476
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20599
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18477
20600
  const parsed = parseSpec(text, file);
18478
20601
  const specKey = parsed.specId;
18479
20602
  if (!specKey) {
@@ -18510,7 +20633,7 @@ async function collectIds(files) {
18510
20633
  result[prefix] = /* @__PURE__ */ new Set();
18511
20634
  }
18512
20635
  for (const file of files) {
18513
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20636
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18514
20637
  for (const prefix of ID_PREFIXES) {
18515
20638
  const ids = extractIds(text, prefix);
18516
20639
  ids.forEach((id) => result[prefix].add(id));
@@ -18525,7 +20648,7 @@ async function collectIds(files) {
18525
20648
  async function collectUpstreamIds(files) {
18526
20649
  const ids = /* @__PURE__ */ new Set();
18527
20650
  for (const file of files) {
18528
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20651
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18529
20652
  extractAllIds(text).forEach((id) => ids.add(id));
18530
20653
  }
18531
20654
  return ids;
@@ -18546,7 +20669,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
18546
20669
  }
18547
20670
  const pattern = buildIdPattern(Array.from(upstreamIds));
18548
20671
  for (const file of targetFiles) {
18549
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20672
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18550
20673
  if (pattern.test(text)) {
18551
20674
  return true;
18552
20675
  }
@@ -18668,7 +20791,7 @@ function normalizeScSources(root, sources) {
18668
20791
  async function countScenarios(scenarioFiles) {
18669
20792
  let total = 0;
18670
20793
  for (const file of scenarioFiles) {
18671
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20794
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18672
20795
  const { document, errors } = parseScenarioDocument(text, file);
18673
20796
  if (!document || errors.length > 0) {
18674
20797
  continue;
@@ -18699,7 +20822,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
18699
20822
  let totalScenarios = 0;
18700
20823
  let e2eCount = 0;
18701
20824
  for (const file of scenarioFiles) {
18702
- const text = await (0, import_promises58.readFile)(file, "utf-8");
20825
+ const text = await (0, import_promises67.readFile)(file, "utf-8");
18703
20826
  const { document, errors } = parseScenarioDocument(text, file);
18704
20827
  if (!document || errors.length > 0) {
18705
20828
  continue;
@@ -18787,10 +20910,10 @@ function buildHotspots(issues) {
18787
20910
  async function collectTddCoverage(entries) {
18788
20911
  const specs = [];
18789
20912
  for (const entry of entries) {
18790
- const testCasesPath = import_node_path70.default.join(entry.dir, "06_Test-Cases.md");
20913
+ const testCasesPath = import_node_path79.default.join(entry.dir, "06_Test-Cases.md");
18791
20914
  let tcContent;
18792
20915
  try {
18793
- tcContent = await (0, import_promises58.readFile)(testCasesPath, "utf-8");
20916
+ tcContent = await (0, import_promises67.readFile)(testCasesPath, "utf-8");
18794
20917
  } catch {
18795
20918
  continue;
18796
20919
  }
@@ -18822,10 +20945,10 @@ async function collectTddCoverage(entries) {
18822
20945
  });
18823
20946
  continue;
18824
20947
  }
18825
- const tddListPath = import_node_path70.default.join(entry.dir, "tdd", "test-list.md");
20948
+ const tddListPath = import_node_path79.default.join(entry.dir, "tdd", "test-list.md");
18826
20949
  let tddContent;
18827
20950
  try {
18828
- tddContent = await (0, import_promises58.readFile)(tddListPath, "utf-8");
20951
+ tddContent = await (0, import_promises67.readFile)(tddListPath, "utf-8");
18829
20952
  } catch {
18830
20953
  specs.push({
18831
20954
  specNumber: entry.specNumber,
@@ -18976,16 +21099,16 @@ async function runRenderCapture(targets, outputDir, adapter, options) {
18976
21099
  }
18977
21100
 
18978
21101
  // src/core/evidence/bundleWriter.ts
18979
- var import_promises60 = require("fs/promises");
18980
- var import_node_path72 = __toESM(require("path"), 1);
21102
+ var import_promises69 = require("fs/promises");
21103
+ var import_node_path81 = __toESM(require("path"), 1);
18981
21104
 
18982
21105
  // src/core/evidence/fsEvidenceWriter.ts
18983
- var import_promises59 = require("fs/promises");
18984
- var import_node_path71 = __toESM(require("path"), 1);
21106
+ var import_promises68 = require("fs/promises");
21107
+ var import_node_path80 = __toESM(require("path"), 1);
18985
21108
  async function writeEvidenceFile(filePath, content) {
18986
21109
  try {
18987
- await (0, import_promises59.mkdir)(import_node_path71.default.dirname(filePath), { recursive: true });
18988
- await (0, import_promises59.writeFile)(filePath, content);
21110
+ await (0, import_promises68.mkdir)(import_node_path80.default.dirname(filePath), { recursive: true });
21111
+ await (0, import_promises68.writeFile)(filePath, content);
18989
21112
  return { path: filePath, written: true };
18990
21113
  } catch (error) {
18991
21114
  return {
@@ -18997,20 +21120,20 @@ async function writeEvidenceFile(filePath, content) {
18997
21120
  }
18998
21121
 
18999
21122
  // src/core/evidence/bundleWriter.ts
19000
- function toPosixRelative5(root, targetPath) {
19001
- return import_node_path72.default.relative(root, targetPath).replace(/\\/g, "/");
21123
+ function toPosixRelative6(root, targetPath) {
21124
+ return import_node_path81.default.relative(root, targetPath).replace(/\\/g, "/");
19002
21125
  }
19003
21126
  async function writeEvidenceBundles(input) {
19004
- const evidenceRoot = import_node_path72.default.join(input.root, ".qfai", "evidence");
19005
- const renderAssetRoot = import_node_path72.default.join(evidenceRoot, "render");
19006
- await (0, import_promises60.mkdir)(renderAssetRoot, { recursive: true });
19007
- const prototypingPath = import_node_path72.default.join(evidenceRoot, "prototyping.json");
19008
- const renderPath = import_node_path72.default.join(evidenceRoot, "render.json");
19009
- const browserQaPath = import_node_path72.default.join(evidenceRoot, "browser-qa.json");
19010
- const browserQaSummaryPath = import_node_path72.default.join(evidenceRoot, "browserQa.summary.json");
19011
- const browserQaFindingsPath = import_node_path72.default.join(evidenceRoot, "browserQa.findings.json");
19012
- const browserQaRepairsPath = import_node_path72.default.join(evidenceRoot, "browserQa.repairs.json");
19013
- const breakthroughPath = import_node_path72.default.join(evidenceRoot, "breakthrough.json");
21127
+ const evidenceRoot = import_node_path81.default.join(input.root, ".qfai", "evidence");
21128
+ const renderAssetRoot = import_node_path81.default.join(evidenceRoot, "render");
21129
+ await (0, import_promises69.mkdir)(renderAssetRoot, { recursive: true });
21130
+ const prototypingPath = import_node_path81.default.join(evidenceRoot, "prototyping.json");
21131
+ const renderPath = import_node_path81.default.join(evidenceRoot, "render.json");
21132
+ const browserQaPath = import_node_path81.default.join(evidenceRoot, "browser-qa.json");
21133
+ const browserQaSummaryPath = import_node_path81.default.join(evidenceRoot, "browserQa.summary.json");
21134
+ const browserQaFindingsPath = import_node_path81.default.join(evidenceRoot, "browserQa.findings.json");
21135
+ const browserQaRepairsPath = import_node_path81.default.join(evidenceRoot, "browserQa.repairs.json");
21136
+ const breakthroughPath = import_node_path81.default.join(evidenceRoot, "breakthrough.json");
19014
21137
  const renderBundle = input.render === void 0 ? {
19015
21138
  renderEvidence: {
19016
21139
  status: "skipped",
@@ -19032,7 +21155,7 @@ async function writeEvidenceBundles(input) {
19032
21155
  await Promise.all([
19033
21156
  writeEvidenceFile(prototypingPath, JSON.stringify(input.prototyping, null, 2)),
19034
21157
  writeEvidenceFile(
19035
- import_node_path72.default.join(evidenceRoot, "prototyping.md"),
21158
+ import_node_path81.default.join(evidenceRoot, "prototyping.md"),
19036
21159
  buildPrototypingMarkdown(input.prototyping)
19037
21160
  ),
19038
21161
  writeEvidenceFile(renderPath, JSON.stringify(renderBundle, null, 2)),
@@ -19049,15 +21172,15 @@ async function writeEvidenceBundles(input) {
19049
21172
  writeEvidenceFile(breakthroughPath, JSON.stringify(input.prototyping.breakthrough, null, 2)),
19050
21173
  ...input.fullHarnessArtifacts ? [
19051
21174
  writeEvidenceFile(
19052
- import_node_path72.default.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
21175
+ import_node_path81.default.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
19053
21176
  JSON.stringify(input.fullHarnessArtifacts.fakeUiDetection, null, 2)
19054
21177
  ),
19055
21178
  writeEvidenceFile(
19056
- import_node_path72.default.join(evidenceRoot, "fullHarness.handoff.json"),
21179
+ import_node_path81.default.join(evidenceRoot, "fullHarness.handoff.json"),
19057
21180
  JSON.stringify(input.fullHarnessArtifacts.handoff, null, 2)
19058
21181
  ),
19059
21182
  writeEvidenceFile(
19060
- import_node_path72.default.join(evidenceRoot, "fullHarness.exit.json"),
21183
+ import_node_path81.default.join(evidenceRoot, "fullHarness.exit.json"),
19061
21184
  JSON.stringify({ exit_reason: input.fullHarnessArtifacts.exitReason }, null, 2)
19062
21185
  )
19063
21186
  ] : []
@@ -19071,8 +21194,8 @@ function buildRenderBundle(root, input) {
19071
21194
  status: entry.status,
19072
21195
  width: 1440,
19073
21196
  height: 900,
19074
- ...entry.screenshot_path ? { imagePath: toPosixRelative5(root, entry.screenshot_path) } : {},
19075
- ...entry.html_path ? { htmlPath: toPosixRelative5(root, entry.html_path) } : {},
21197
+ ...entry.screenshot_path ? { imagePath: toPosixRelative6(root, entry.screenshot_path) } : {},
21198
+ ...entry.html_path ? { htmlPath: toPosixRelative6(root, entry.html_path) } : {},
19076
21199
  ...entry.reason ? entry.status === "failed" ? { error: entry.reason } : { skippedReason: entry.reason } : {}
19077
21200
  }));
19078
21201
  const topStatus = screens.some((screen) => screen.status === "captured") ? "captured" : screens.some((screen) => screen.status === "failed") ? "failed" : "skipped";
@@ -19157,15 +21280,15 @@ function buildPrototypingMarkdown(bundle) {
19157
21280
  }
19158
21281
 
19159
21282
  // src/core/harness/history.ts
19160
- var import_promises61 = require("fs/promises");
19161
- var import_node_path73 = __toESM(require("path"), 1);
21283
+ var import_promises70 = require("fs/promises");
21284
+ var import_node_path82 = __toESM(require("path"), 1);
19162
21285
  var EVIDENCE_PATH = ".qfai/evidence/prototyping.json";
19163
21286
  async function loadHistory(root) {
19164
21287
  try {
19165
- const filePath = import_node_path73.default.join(root, EVIDENCE_PATH);
19166
- const content = await (0, import_promises61.readFile)(filePath, "utf-8");
21288
+ const filePath = import_node_path82.default.join(root, EVIDENCE_PATH);
21289
+ const content = await (0, import_promises70.readFile)(filePath, "utf-8");
19167
21290
  const parsed = JSON.parse(content);
19168
- if (isRecord9(parsed) && isRecord9(parsed.fullHarness)) {
21291
+ if (isRecord19(parsed) && isRecord19(parsed.fullHarness)) {
19169
21292
  const fh = parsed.fullHarness;
19170
21293
  if (Array.isArray(fh.iterations)) {
19171
21294
  return {
@@ -19214,7 +21337,7 @@ function computeTerminationReason(history, iterationPolicy) {
19214
21337
  return void 0;
19215
21338
  }
19216
21339
  const latestIteration = history.iterations[count - 1];
19217
- if (latestIteration?.allItemsPass95) {
21340
+ if (latestIteration && hasAllReviewerAxesPerfect100(latestIteration.reviewerScores)) {
19218
21341
  return "converged";
19219
21342
  }
19220
21343
  if (count >= iterationPolicy.maxIterations) {
@@ -19226,6 +21349,7 @@ function buildScoreSnapshot(iteration) {
19226
21349
  const axisScores = iteration.reviewerScores.flatMap(
19227
21350
  (reviewer) => reviewer.scores.map((score) => score.score)
19228
21351
  );
21352
+ const allReviewerAxesPerfect100 = hasAllReviewerAxesPerfect100(iteration.reviewerScores);
19229
21353
  if (axisScores.length === 0) {
19230
21354
  return {
19231
21355
  iteration: iteration.iteration,
@@ -19233,7 +21357,7 @@ function buildScoreSnapshot(iteration) {
19233
21357
  axisCount: 0,
19234
21358
  minScore: null,
19235
21359
  averageScore: null,
19236
- allItemsPass95: iteration.allItemsPass95,
21360
+ allReviewerAxesPerfect100: false,
19237
21361
  commitSha: iteration.commitSha
19238
21362
  };
19239
21363
  }
@@ -19244,13 +21368,13 @@ function buildScoreSnapshot(iteration) {
19244
21368
  axisCount: axisScores.length,
19245
21369
  minScore: Math.min(...axisScores),
19246
21370
  averageScore: total / axisScores.length,
19247
- allItemsPass95: iteration.allItemsPass95,
21371
+ allReviewerAxesPerfect100,
19248
21372
  commitSha: iteration.commitSha
19249
21373
  };
19250
21374
  }
19251
21375
  function compareSnapshots(left, right) {
19252
- if (left.allItemsPass95 !== right.allItemsPass95) {
19253
- return left.allItemsPass95 ? 1 : -1;
21376
+ if (left.allReviewerAxesPerfect100 !== right.allReviewerAxesPerfect100) {
21377
+ return left.allReviewerAxesPerfect100 ? 1 : -1;
19254
21378
  }
19255
21379
  const leftMin = left.minScore ?? -1;
19256
21380
  const rightMin = right.minScore ?? -1;
@@ -19264,7 +21388,15 @@ function compareSnapshots(left, right) {
19264
21388
  }
19265
21389
  return left.iteration - right.iteration;
19266
21390
  }
19267
- function isRecord9(value) {
21391
+ function hasAllReviewerAxesPerfect100(reviewerScores) {
21392
+ if (reviewerScores.length === 0) {
21393
+ return false;
21394
+ }
21395
+ return reviewerScores.every(
21396
+ (reviewer) => reviewer.scores.length > 0 && reviewer.scores.every((score) => score.score === 100)
21397
+ );
21398
+ }
21399
+ function isRecord19(value) {
19268
21400
  return typeof value === "object" && value !== null && !Array.isArray(value);
19269
21401
  }
19270
21402
 
@@ -19299,16 +21431,16 @@ function validateReviewer(reviewer) {
19299
21431
  }
19300
21432
 
19301
21433
  // src/core/harness/gitRevision.ts
19302
- var import_promises62 = require("fs/promises");
19303
- var import_node_path74 = __toESM(require("path"), 1);
21434
+ var import_promises71 = require("fs/promises");
21435
+ var import_node_path83 = __toESM(require("path"), 1);
19304
21436
  async function resolveGitDirs(root) {
19305
- const dotGit = import_node_path74.default.join(root, ".git");
19306
- const info = await (0, import_promises62.stat)(dotGit);
21437
+ const dotGit = import_node_path83.default.join(root, ".git");
21438
+ const info = await (0, import_promises71.stat)(dotGit);
19307
21439
  const gitDir = info.isDirectory() ? dotGit : await resolveWorktreeGitDir(root, dotGit);
19308
21440
  try {
19309
- const commondirText = (await (0, import_promises62.readFile)(import_node_path74.default.join(gitDir, "commondir"), "utf-8")).trim();
21441
+ const commondirText = (await (0, import_promises71.readFile)(import_node_path83.default.join(gitDir, "commondir"), "utf-8")).trim();
19310
21442
  if (commondirText.length > 0) {
19311
- const commonDir = import_node_path74.default.isAbsolute(commondirText) ? commondirText : import_node_path74.default.resolve(gitDir, commondirText);
21443
+ const commonDir = import_node_path83.default.isAbsolute(commondirText) ? commondirText : import_node_path83.default.resolve(gitDir, commondirText);
19312
21444
  return { gitDir, commonDir };
19313
21445
  }
19314
21446
  } catch {
@@ -19316,18 +21448,18 @@ async function resolveGitDirs(root) {
19316
21448
  return { gitDir, commonDir: gitDir };
19317
21449
  }
19318
21450
  async function resolveWorktreeGitDir(root, dotGitFile) {
19319
- const text = (await (0, import_promises62.readFile)(dotGitFile, "utf-8")).trim();
21451
+ const text = (await (0, import_promises71.readFile)(dotGitFile, "utf-8")).trim();
19320
21452
  const match = /^gitdir:\s*(.+)$/m.exec(text);
19321
21453
  if (!match?.[1]) {
19322
21454
  throw new Error(`Unrecognized .git file format at ${dotGitFile}`);
19323
21455
  }
19324
21456
  const gitdirValue = match[1].trim();
19325
- return import_node_path74.default.isAbsolute(gitdirValue) ? gitdirValue : import_node_path74.default.resolve(root, gitdirValue);
21457
+ return import_node_path83.default.isAbsolute(gitdirValue) ? gitdirValue : import_node_path83.default.resolve(root, gitdirValue);
19326
21458
  }
19327
21459
  async function lookupPackedRef(commonDir, refName) {
19328
21460
  try {
19329
- const packedPath = import_node_path74.default.join(commonDir, "packed-refs");
19330
- const content = await (0, import_promises62.readFile)(packedPath, "utf-8");
21461
+ const packedPath = import_node_path83.default.join(commonDir, "packed-refs");
21462
+ const content = await (0, import_promises71.readFile)(packedPath, "utf-8");
19331
21463
  for (const rawLine of content.split(/\r?\n/)) {
19332
21464
  const line = rawLine.trim();
19333
21465
  if (line.length === 0 || line.startsWith("#") || line.startsWith("^")) continue;
@@ -19343,7 +21475,7 @@ async function lookupPackedRef(commonDir, refName) {
19343
21475
  }
19344
21476
  async function readRefLoose(dir, refName) {
19345
21477
  try {
19346
- const sha = (await (0, import_promises62.readFile)(import_node_path74.default.join(dir, refName), "utf-8")).trim();
21478
+ const sha = (await (0, import_promises71.readFile)(import_node_path83.default.join(dir, refName), "utf-8")).trim();
19347
21479
  return sha.length > 0 ? sha : null;
19348
21480
  } catch {
19349
21481
  return null;
@@ -19352,8 +21484,8 @@ async function readRefLoose(dir, refName) {
19352
21484
  async function resolveCommitSha(root) {
19353
21485
  try {
19354
21486
  const { gitDir, commonDir } = await resolveGitDirs(root);
19355
- const headPath = import_node_path74.default.join(gitDir, "HEAD");
19356
- const headContent = (await (0, import_promises62.readFile)(headPath, "utf-8")).trim();
21487
+ const headPath = import_node_path83.default.join(gitDir, "HEAD");
21488
+ const headContent = (await (0, import_promises71.readFile)(headPath, "utf-8")).trim();
19357
21489
  if (headContent.startsWith("ref: ")) {
19358
21490
  const refName = headContent.slice(5).trim();
19359
21491
  const looseLocal = await readRefLoose(gitDir, refName);
@@ -19374,162 +21506,6 @@ async function resolveCommitSha(root) {
19374
21506
  );
19375
21507
  }
19376
21508
  }
19377
-
19378
- // src/core/evidence/playwrightRenderAdapter.ts
19379
- var import_promises63 = require("fs/promises");
19380
- var import_node_path75 = __toESM(require("path"), 1);
19381
- function sanitizeName(value) {
19382
- return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
19383
- }
19384
- async function loadPlaywright() {
19385
- return import("playwright");
19386
- }
19387
- function createPlaywrightRenderAdapter(input) {
19388
- async function _renderTarget(target) {
19389
- if (!input.targetUrl) {
19390
- throw new Error("target URL is required for Playwright render capture");
19391
- }
19392
- const playwright = await loadPlaywright();
19393
- const browser = await playwright.chromium.launch();
19394
- try {
19395
- const page = await browser.newPage({
19396
- viewport: {
19397
- width: target.width ?? 1440,
19398
- height: target.height ?? 900
19399
- }
19400
- });
19401
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19402
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19403
- const slug = sanitizeName(`${target.targetId}.${target.viewport}`);
19404
- const screenshotPath = import_node_path75.default.join(process.cwd(), ".", slug);
19405
- return {
19406
- screenshotPath,
19407
- htmlPath: await page.content()
19408
- };
19409
- } finally {
19410
- await browser.close();
19411
- }
19412
- }
19413
- return {
19414
- async captureScreenshot(target, outputDir) {
19415
- if (!input.targetUrl) {
19416
- throw new Error("target URL is required for Playwright render capture");
19417
- }
19418
- const playwright = await loadPlaywright();
19419
- const browser = await playwright.chromium.launch();
19420
- try {
19421
- await (0, import_promises63.mkdir)(outputDir, { recursive: true });
19422
- const page = await browser.newPage({
19423
- viewport: {
19424
- width: target.width ?? 1440,
19425
- height: target.height ?? 900
19426
- }
19427
- });
19428
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19429
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19430
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19431
- const screenshotPath = import_node_path75.default.join(outputDir, `${targetName}.png`);
19432
- await page.screenshot({ path: screenshotPath, fullPage: true });
19433
- return screenshotPath;
19434
- } finally {
19435
- await browser.close();
19436
- }
19437
- },
19438
- async captureHtml(target, outputDir) {
19439
- if (!input.targetUrl) {
19440
- throw new Error("target URL is required for Playwright render capture");
19441
- }
19442
- const playwright = await loadPlaywright();
19443
- const browser = await playwright.chromium.launch();
19444
- try {
19445
- await (0, import_promises63.mkdir)(outputDir, { recursive: true });
19446
- const page = await browser.newPage({
19447
- viewport: {
19448
- width: target.width ?? 1440,
19449
- height: target.height ?? 900
19450
- }
19451
- });
19452
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19453
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19454
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19455
- const htmlPath = import_node_path75.default.join(outputDir, `${targetName}.html`);
19456
- await (0, import_promises63.writeFile)(htmlPath, await page.content(), "utf-8");
19457
- return htmlPath;
19458
- } finally {
19459
- await browser.close();
19460
- }
19461
- }
19462
- };
19463
- }
19464
-
19465
- // src/core/providers/playwrightBrowserQaProvider.ts
19466
- init_surfaceType();
19467
- async function loadPlaywright2() {
19468
- try {
19469
- return await import("playwright");
19470
- } catch {
19471
- throw new Error("Playwright is not installed. Install it with: npm install playwright");
19472
- }
19473
- }
19474
- async function withPage(input, task) {
19475
- const playwright = await loadPlaywright2();
19476
- const browser = await playwright.chromium.launch();
19477
- try {
19478
- const page = await browser.newPage();
19479
- if (input.targetUrl) {
19480
- await page.goto(input.targetUrl, { waitUntil: "networkidle" });
19481
- } else if (input.htmlContent) {
19482
- await page.setContent(input.htmlContent, { waitUntil: "load" });
19483
- } else {
19484
- throw new Error("Browser QA requires targetUrl or htmlContent");
19485
- }
19486
- return await task(page);
19487
- } finally {
19488
- await browser.close();
19489
- }
19490
- }
19491
- function completed(phase) {
19492
- return {
19493
- phase,
19494
- status: "executed",
19495
- findings: [],
19496
- repair_suggestions: [],
19497
- evidence_refs: [`provider:playwright:${phase}`],
19498
- checks_performed: [`playwright executed ${phase} checks`]
19499
- };
19500
- }
19501
- function createPlaywrightBrowserQaProvider() {
19502
- return {
19503
- providerId: "playwright",
19504
- canRun(surface) {
19505
- return requiresVisualBrowserEvidence(surface);
19506
- },
19507
- async runSmoke(input) {
19508
- return withPage(input, async (page) => {
19509
- await page.title();
19510
- return completed("smoke");
19511
- });
19512
- },
19513
- async runInteraction(input) {
19514
- return withPage(input, async (page) => {
19515
- await page.locator("button, a, input, form").count();
19516
- return completed("interaction");
19517
- });
19518
- },
19519
- async runVisual(input) {
19520
- return withPage(input, async (page) => {
19521
- await page.viewportSize();
19522
- return completed("visual");
19523
- });
19524
- },
19525
- async runAccessibility(input) {
19526
- return withPage(input, async (page) => {
19527
- await page.locator("html").getAttribute("lang");
19528
- return completed("accessibility");
19529
- });
19530
- }
19531
- };
19532
- }
19533
21509
  // Annotate the CommonJS export names for ESM import in node:
19534
21510
  0 && (module.exports = {
19535
21511
  BROWSER_QA_PHASES,
@@ -19537,6 +21513,7 @@ function createPlaywrightBrowserQaProvider() {
19537
21513
  DISCUSSION_NON_UI_SURFACES,
19538
21514
  DISCUSSION_UI_BEARING_SURFACES,
19539
21515
  ID_PREFIXES,
21516
+ PROTOTYPING_MAX_CYCLES,
19540
21517
  PROTOTYPING_MAX_ITERATIONS,
19541
21518
  PROTOTYPING_MODES,
19542
21519
  PROTOTYPING_SUPPORTED_SURFACES,
@@ -19545,8 +21522,6 @@ function createPlaywrightBrowserQaProvider() {
19545
21522
  appendIteration,
19546
21523
  checkDecisionGuardrails,
19547
21524
  computeTerminationReason,
19548
- createPlaywrightBrowserQaProvider,
19549
- createPlaywrightRenderAdapter,
19550
21525
  createReportData,
19551
21526
  defaultConfig,
19552
21527
  derivePrototypingObligations,