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.mjs CHANGED
@@ -475,7 +475,8 @@ var defaultConfig = {
475
475
  requireLayerTags: false,
476
476
  requireSizeTags: false,
477
477
  maxE2eScenarioRatio: null,
478
- maxE2eScenarioCount: null
478
+ maxE2eScenarioCount: null,
479
+ forbidTestTodoStubs: true
479
480
  },
480
481
  traceability: {
481
482
  brMustHaveSc: true,
@@ -496,8 +497,7 @@ var defaultConfig = {
496
497
  },
497
498
  execution: {
498
499
  targetUrl: null,
499
- browserProvider: "playwright",
500
- renderProvider: "playwright"
500
+ browserTool: "playwright-cli"
501
501
  }
502
502
  }
503
503
  };
@@ -688,6 +688,13 @@ function normalizeValidation(raw, configPath, issues) {
688
688
  "validation.testStrategy.maxE2eScenarioCount",
689
689
  configPath,
690
690
  issues
691
+ ),
692
+ forbidTestTodoStubs: readBoolean(
693
+ testStrategyRaw?.forbidTestTodoStubs,
694
+ base.testStrategy.forbidTestTodoStubs,
695
+ "validation.testStrategy.forbidTestTodoStubs",
696
+ configPath,
697
+ issues
691
698
  )
692
699
  },
693
700
  traceability: {
@@ -772,14 +779,40 @@ function normalizePrototyping(raw, configPath, issues) {
772
779
  }
773
780
  const calibration = normalizePrototypingCalibration(raw.calibration, configPath, issues);
774
781
  const execution = normalizePrototypingExecution(raw.execution, configPath, issues);
775
- if (!calibration && !execution) {
782
+ const primarySpecId = normalizePrimarySpecId(raw.primarySpecId, configPath, issues);
783
+ if (!calibration && !execution && primarySpecId === void 0) {
776
784
  return void 0;
777
785
  }
778
786
  return {
779
787
  ...calibration ? { calibration } : {},
780
- ...execution ? { execution } : {}
788
+ ...execution ? { execution } : {},
789
+ ...primarySpecId !== void 0 ? { primarySpecId } : {}
781
790
  };
782
791
  }
792
+ function normalizePrimarySpecId(raw, configPath, issues) {
793
+ if (raw === void 0 || raw === null) {
794
+ return void 0;
795
+ }
796
+ if (typeof raw !== "string") {
797
+ issues.push(
798
+ configIssue(
799
+ configPath,
800
+ 'prototyping.primarySpecId \u306F4\u6841\u306E\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044 (\u4F8B: "0012")\u3002'
801
+ )
802
+ );
803
+ return void 0;
804
+ }
805
+ if (!/^\d{4}$/.test(raw)) {
806
+ issues.push(
807
+ configIssue(
808
+ configPath,
809
+ `prototyping.primarySpecId \u306F4\u6841\u306E\u6570\u5B57\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 (got "${raw}")\u3002`
810
+ )
811
+ );
812
+ return void 0;
813
+ }
814
+ return raw;
815
+ }
783
816
  function normalizePrototypingCalibration(raw, configPath, issues) {
784
817
  const base = defaultConfig.prototyping?.calibration;
785
818
  if (raw === void 0 || raw === null) {
@@ -813,6 +846,30 @@ function normalizePrototypingExecution(raw, configPath, issues) {
813
846
  );
814
847
  return base ? { ...base } : void 0;
815
848
  }
849
+ for (const legacyKey of ["browserProvider", "renderProvider"]) {
850
+ if (raw[legacyKey] !== void 0) {
851
+ issues.push(
852
+ configIssue(
853
+ configPath,
854
+ `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`
855
+ )
856
+ );
857
+ }
858
+ }
859
+ const browserToolRaw = raw.browserTool;
860
+ let browserTool = "playwright-cli";
861
+ if (browserToolRaw !== void 0) {
862
+ if (browserToolRaw !== "playwright-cli") {
863
+ issues.push(
864
+ configIssue(
865
+ configPath,
866
+ `prototyping.execution.browserTool \u306F "playwright-cli" \u306E\u307F\u6709\u52B9\u3067\u3059 (spec-0012)\u3002 \u53D7\u3051\u53D6\u3063\u305F\u5024: ${JSON.stringify(browserToolRaw)}`
867
+ )
868
+ );
869
+ } else {
870
+ browserTool = browserToolRaw;
871
+ }
872
+ }
816
873
  return {
817
874
  targetUrl: raw.targetUrl === null ? null : readOptionalString(
818
875
  raw.targetUrl,
@@ -820,20 +877,7 @@ function normalizePrototypingExecution(raw, configPath, issues) {
820
877
  configPath,
821
878
  issues
822
879
  ) ?? null,
823
- browserProvider: readString(
824
- raw.browserProvider,
825
- base?.browserProvider ?? "playwright",
826
- "prototyping.execution.browserProvider",
827
- configPath,
828
- issues
829
- ),
830
- renderProvider: readString(
831
- raw.renderProvider,
832
- base?.renderProvider ?? "playwright",
833
- "prototyping.execution.renderProvider",
834
- configPath,
835
- issues
836
- )
880
+ browserTool
837
881
  };
838
882
  }
839
883
  function validateObsoleteCalibrationFields(raw, configPath, issues) {
@@ -3028,11 +3072,12 @@ function isValidId(value, prefix) {
3028
3072
  init_surface();
3029
3073
  var PROTOTYPING_MODES = ["low-cost", "standard", "full-harness"];
3030
3074
  var DEFAULT_PROTOTYPING_MODE = "standard";
3031
- var PROTOTYPING_MAX_ITERATIONS = {
3075
+ var PROTOTYPING_MAX_CYCLES = {
3032
3076
  "low-cost": 1,
3033
3077
  standard: 3,
3034
3078
  "full-harness": 20
3035
3079
  };
3080
+ var PROTOTYPING_MAX_ITERATIONS = PROTOTYPING_MAX_CYCLES;
3036
3081
  var PROTOTYPING_SUPPORTED_SURFACES = ["web", "mobile", "desktop", "mixed"];
3037
3082
  var VALID_MODE_SET = new Set(PROTOTYPING_MODES);
3038
3083
  var VALID_SURFACE_SET = new Set(CANONICAL_PROTOTYPING_SURFACES);
@@ -3062,14 +3107,24 @@ function resolvePrototypingMode(requested) {
3062
3107
  };
3063
3108
  }
3064
3109
  function derivePrototypingObligations(input) {
3065
- const isFullHarness = input.effectiveMode === "full-harness";
3110
+ const maxCycles = PROTOTYPING_MAX_CYCLES[input.effectiveMode];
3066
3111
  return {
3067
- requireRuntimeGate: isFullHarness,
3068
- requireUiFidelity: isFullHarness,
3069
- requireRenderBundle: isFullHarness,
3070
- requireBrowserQaBundle: isFullHarness,
3112
+ browserTool: "playwright-cli",
3113
+ requireExecutionPlan: true,
3114
+ requirePlaywrightEvidence: true,
3115
+ requireReviewBundle: true,
3116
+ requireEvaluatorReview: true,
3117
+ requireBestOfHistory: true,
3118
+ requireBreakthrough: true,
3119
+ requireIndependentReviewerGate: true,
3120
+ requirePerfect100Completion: true,
3121
+ requireRuntimeGate: true,
3122
+ requireUiFidelity: true,
3123
+ requireRenderBundle: true,
3124
+ requireBrowserQaBundle: true,
3071
3125
  requireIterations: true,
3072
- maxIterations: PROTOTYPING_MAX_ITERATIONS[input.effectiveMode],
3126
+ maxCycles,
3127
+ maxIterations: maxCycles,
3073
3128
  validCombination: true
3074
3129
  };
3075
3130
  }
@@ -3536,8 +3591,8 @@ async function readSafe4(filePath) {
3536
3591
  }
3537
3592
 
3538
3593
  // src/core/report.ts
3539
- import { readFile as readFile46 } from "fs/promises";
3540
- import path70 from "path";
3594
+ import { readFile as readFile53 } from "fs/promises";
3595
+ import path79 from "path";
3541
3596
 
3542
3597
  // src/core/contractIndex.ts
3543
3598
  import { readFile as readFile9 } from "fs/promises";
@@ -3579,6 +3634,31 @@ function record(index, id, file) {
3579
3634
  index.idToFiles.set(id, current);
3580
3635
  }
3581
3636
 
3637
+ // src/core/prototyping/round.ts
3638
+ var EXPLORATION_ROUNDS = ["r5", "r3", "r2", "r1"];
3639
+ var ABSORPTION_ROUNDS = ["r3", "r2", "r1"];
3640
+ var ROUND_SURVIVOR_COUNT = {
3641
+ r5: 5,
3642
+ r3: 3,
3643
+ r2: 2,
3644
+ r1: 1
3645
+ };
3646
+ function isExplorationRound(value) {
3647
+ return typeof value === "string" && EXPLORATION_ROUNDS.includes(value);
3648
+ }
3649
+ function prototypingRoundsDir() {
3650
+ return ".qfai/evidence/prototyping/rounds";
3651
+ }
3652
+ function roundDir(round) {
3653
+ return `${prototypingRoundsDir()}/${round}`;
3654
+ }
3655
+ function roundReviewBundlePath(round) {
3656
+ return `${roundDir(round)}/review-bundle.json`;
3657
+ }
3658
+ function roundEvaluatorReviewsDir(round) {
3659
+ return `${roundDir(round)}/evaluator-reviews`;
3660
+ }
3661
+
3582
3662
  // src/core/specPackIds.ts
3583
3663
  var STRICT_ID_PATTERNS2 = {
3584
3664
  OBJ: /\bOBJ-\d+\b/g,
@@ -4291,15 +4371,15 @@ function asTagArray(value) {
4291
4371
  }
4292
4372
 
4293
4373
  // src/core/validate.ts
4294
- import path69 from "path";
4374
+ import path78 from "path";
4295
4375
 
4296
4376
  // src/core/version.ts
4297
4377
  import { readFile as readFile10 } from "fs/promises";
4298
4378
  import path13 from "path";
4299
4379
  import { fileURLToPath } from "url";
4300
4380
  async function resolveToolVersion() {
4301
- if ("1.8.2".length > 0) {
4302
- return "1.8.2";
4381
+ if ("1.8.4".length > 0) {
4382
+ return "1.8.4";
4303
4383
  }
4304
4384
  try {
4305
4385
  const packagePath = resolvePackageJsonPath();
@@ -7230,7 +7310,7 @@ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
7230
7310
  var US_ID_RE2 = /\bUS-\d{4}-\d{4}\b/g;
7231
7311
  var AC_ID_RE3 = /\bAC-\d{4}-\d{4}\b/g;
7232
7312
  var DOWNSTREAM_ID_RE = /\b(?:US|AC|BR|SC|CASE)-\d{4}-\d{4}\b/g;
7233
- async function validateTraceability(root, config, phase) {
7313
+ async function validateTraceability(root, config, options) {
7234
7314
  const issues = [];
7235
7315
  const specsRoot = resolvePath(root, config, "specsDir");
7236
7316
  const entries = await collectSpecEntries(specsRoot);
@@ -7306,7 +7386,7 @@ async function validateTraceability(root, config, phase) {
7306
7386
  issues.push(...validateLayeredEdges(entry, edgeData));
7307
7387
  issues.push(...validateLayeredHeuristics(entry, edgeData));
7308
7388
  }
7309
- if (phase !== "refinement" && allLayeredScIds.size > 0) {
7389
+ if (options.includeCodeReferences && allLayeredScIds.size > 0) {
7310
7390
  issues.push(
7311
7391
  ...await validateLayeredScCodeReferences(root, config, allLayeredScIds, specsRoot)
7312
7392
  );
@@ -10086,8 +10166,109 @@ function normalizeOptionalFragment(fragment, originalRef) {
10086
10166
  return normalized;
10087
10167
  }
10088
10168
 
10169
+ // src/core/prototyping/candidate.ts
10170
+ var CANDIDATE_ID_PATTERN = /^c[1-9]\d*$/;
10171
+ function isCandidateId(value) {
10172
+ return typeof value === "string" && CANDIDATE_ID_PATTERN.test(value);
10173
+ }
10174
+
10089
10175
  // src/core/validators/prototypingEvidence.ts
10090
10176
  init_utils();
10177
+
10178
+ // src/core/validators/prototyping/modeInvariant.ts
10179
+ init_utils();
10180
+ function isRecord5(v) {
10181
+ return typeof v === "object" && v !== null && !Array.isArray(v);
10182
+ }
10183
+ function detectMode(raw) {
10184
+ if (!isRecord5(raw)) return null;
10185
+ if (typeof raw.mode === "string" && isValidPrototypingMode(raw.mode)) {
10186
+ return { mode: raw.mode, source: "string" };
10187
+ }
10188
+ if (isRecord5(raw.mode) && typeof raw.mode.effective === "string") {
10189
+ const effective = raw.mode.effective;
10190
+ if (isValidPrototypingMode(effective)) {
10191
+ return { mode: effective, source: "object" };
10192
+ }
10193
+ }
10194
+ return null;
10195
+ }
10196
+ function validateModeInvariant(raw, evidencePathForIssue = ".qfai/evidence/prototyping.json") {
10197
+ if (!isRecord5(raw)) {
10198
+ return [];
10199
+ }
10200
+ const detected = detectMode(raw);
10201
+ if (!detected) {
10202
+ return [];
10203
+ }
10204
+ const issues = [];
10205
+ const expectedMaxCycles = PROTOTYPING_MAX_CYCLES[detected.mode];
10206
+ if (raw.maxCycles !== void 0 && raw.maxIterations !== void 0 && raw.maxCycles !== raw.maxIterations) {
10207
+ issues.push(
10208
+ issue(
10209
+ "QFAI-PROT-MODE-001",
10210
+ `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).`,
10211
+ "error",
10212
+ evidencePathForIssue,
10213
+ "prototyping.modeInvariant.maxCyclesAlias",
10214
+ [JSON.stringify(raw.maxCycles), JSON.stringify(raw.maxIterations)],
10215
+ "canonical",
10216
+ "Remove the redundant key or align both values; prefer maxCycles (spec-0017 REQ-0001)."
10217
+ )
10218
+ );
10219
+ }
10220
+ const maxCyclesRaw = raw.maxCycles ?? raw.maxIterations;
10221
+ if (maxCyclesRaw !== void 0) {
10222
+ if (typeof maxCyclesRaw !== "number" || !Number.isInteger(maxCyclesRaw) || maxCyclesRaw !== expectedMaxCycles) {
10223
+ const maxCyclesDisplay = JSON.stringify(maxCyclesRaw);
10224
+ issues.push(
10225
+ issue(
10226
+ "QFAI-PROT-MODE-001",
10227
+ `Mode differences are limited to maxCycles only. Expected maxCycles=${expectedMaxCycles} for mode=${detected.mode}, got ${maxCyclesDisplay}.`,
10228
+ "error",
10229
+ evidencePathForIssue,
10230
+ "prototyping.modeInvariant",
10231
+ [detected.mode, String(expectedMaxCycles), maxCyclesDisplay],
10232
+ "canonical",
10233
+ `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).`
10234
+ )
10235
+ );
10236
+ }
10237
+ }
10238
+ if (raw.browserTool !== void 0) {
10239
+ if (raw.browserTool !== "playwright-cli") {
10240
+ const browserToolDisplay = JSON.stringify(raw.browserTool);
10241
+ issues.push(
10242
+ issue(
10243
+ "QFAI-PROT-MODE-001",
10244
+ `browserTool must be "playwright-cli" (spec-0017 REQ-0002). Got: ${browserToolDisplay}`,
10245
+ "error",
10246
+ evidencePathForIssue,
10247
+ "prototyping.modeInvariant.browserTool",
10248
+ [browserToolDisplay],
10249
+ "canonical",
10250
+ 'Set prototyping.json browserTool to "playwright-cli". Playwright MCP and legacy providers are not supported in the standard harness.'
10251
+ )
10252
+ );
10253
+ }
10254
+ }
10255
+ return issues;
10256
+ }
10257
+
10258
+ // src/core/validators/prototypingEvidence.ts
10259
+ var VALID_ITERATION_KIND_SET = /* @__PURE__ */ new Set(["explore", "remix", "select", "polish", "branch"]);
10260
+ var VALID_PHASE_SET = /* @__PURE__ */ new Set([
10261
+ "planning",
10262
+ "explore",
10263
+ "remix",
10264
+ "select",
10265
+ "polish",
10266
+ "breakthrough",
10267
+ "reviewer_gate",
10268
+ "completed"
10269
+ ]);
10270
+ var REQUIRED_POLISH_CHECKS = ["critique", "fix", "recapture", "rereview", "breakthrough"];
10271
+ var LEGACY_PASS_95_FIELD = "allItemsPass" + String(95);
10091
10272
  var VALID_MODE_SOURCE_SET = /* @__PURE__ */ new Set([
10092
10273
  "explicit-request",
10093
10274
  "system-default",
@@ -10156,6 +10337,24 @@ async function validatePrototypingEvidence(root, config) {
10156
10337
  return issues;
10157
10338
  }
10158
10339
  const obligations = surface && isSupportedPrototypingSurface(surface) ? derivePrototypingObligations({ surface, effectiveMode: mode.effective }) : void 0;
10340
+ issues.push(
10341
+ ...validateModeInvariant(record2, path35.relative(root, evidencePath).replace(/\\/g, "/"))
10342
+ );
10343
+ const isV2Record = record2.schemaVersion === "2.0" || record2.rounds !== void 0 || record2.polishCycles !== void 0;
10344
+ if (isV2Record) {
10345
+ issues.push(...validateV2Lifecycle(root, evidencePath, record2, mode, obligations));
10346
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10347
+ issues.push(
10348
+ makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10349
+ );
10350
+ }
10351
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10352
+ issues.push(
10353
+ makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10354
+ );
10355
+ }
10356
+ return issues;
10357
+ }
10159
10358
  const iterations = normalizeIterations(record2.iterations);
10160
10359
  if (!iterations || iterations.length === 0) {
10161
10360
  issues.push(
@@ -10186,10 +10385,14 @@ async function validatePrototypingEvidence(root, config) {
10186
10385
  )
10187
10386
  );
10188
10387
  }
10189
- let foundPass95 = false;
10388
+ let latestPerfect100 = false;
10389
+ let latestIterationKind = null;
10390
+ let completedPolishCount = 0;
10391
+ let polishWithBreakthroughCheck = false;
10392
+ let hasLegacyPass95CompletionMarker = false;
10190
10393
  for (const [index, candidate] of iterations.entries()) {
10191
10394
  const prefix = `iterations[${index}]`;
10192
- if (!isRecord5(candidate)) {
10395
+ if (!isRecord6(candidate)) {
10193
10396
  issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10194
10397
  continue;
10195
10398
  }
@@ -10200,12 +10403,43 @@ async function validatePrototypingEvidence(root, config) {
10200
10403
  makeSchemaIssue(root, evidencePath, `${prefix}.iteration must be a positive integer.`)
10201
10404
  );
10202
10405
  }
10203
- if (typeof iteration.allItemsPass95 !== "boolean") {
10406
+ if (typeof iteration.allReviewerAxesPerfect100 !== "boolean") {
10204
10407
  issues.push(
10205
- makeSchemaIssue(root, evidencePath, `${prefix}.allItemsPass95 must be a boolean.`)
10408
+ makeSchemaIssue(
10409
+ root,
10410
+ evidencePath,
10411
+ `${prefix}.allReviewerAxesPerfect100 must be a boolean.`
10412
+ )
10206
10413
  );
10207
- } else if (iteration.allItemsPass95) {
10208
- foundPass95 = true;
10414
+ } else if (iteration.allReviewerAxesPerfect100) {
10415
+ latestPerfect100 = true;
10416
+ } else {
10417
+ latestPerfect100 = false;
10418
+ }
10419
+ if (iteration[LEGACY_PASS_95_FIELD] === true) {
10420
+ hasLegacyPass95CompletionMarker = true;
10421
+ }
10422
+ if (iteration.kind !== void 0) {
10423
+ if (typeof iteration.kind !== "string" || !VALID_ITERATION_KIND_SET.has(iteration.kind)) {
10424
+ issues.push(
10425
+ issue(
10426
+ "QFAI-PROT-285",
10427
+ `${prefix}.kind must be one of explore|remix|select|polish|branch.`,
10428
+ "error",
10429
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10430
+ "prototypingEvidence.phase",
10431
+ void 0,
10432
+ "canonical",
10433
+ "iteration kind \u3092 phase state machine \u306B\u5408\u308F\u305B\u3066\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10434
+ )
10435
+ );
10436
+ } else {
10437
+ latestIterationKind = iteration.kind;
10438
+ if (iteration.kind === "polish" && hasCompletePolishChecks(iteration.checks)) {
10439
+ completedPolishCount++;
10440
+ polishWithBreakthroughCheck = true;
10441
+ }
10442
+ }
10209
10443
  }
10210
10444
  if (!Array.isArray(iteration.reviewerScores) || iteration.reviewerScores.length === 0) {
10211
10445
  issues.push(
@@ -10215,7 +10449,7 @@ async function validatePrototypingEvidence(root, config) {
10215
10449
  }
10216
10450
  for (const [reviewerIndex, reviewer] of iteration.reviewerScores.entries()) {
10217
10451
  const reviewerPath = `${prefix}.reviewerScores[${reviewerIndex}]`;
10218
- if (!isRecord5(reviewer)) {
10452
+ if (!isRecord6(reviewer)) {
10219
10453
  issues.push(makeSchemaIssue(root, evidencePath, `${reviewerPath} must be an object.`));
10220
10454
  continue;
10221
10455
  }
@@ -10232,7 +10466,7 @@ async function validatePrototypingEvidence(root, config) {
10232
10466
  }
10233
10467
  for (const [scoreIndex, score] of reviewer.scores.entries()) {
10234
10468
  const scorePath = `${reviewerPath}.scores[${scoreIndex}]`;
10235
- if (!isRecord5(score)) {
10469
+ if (!isRecord6(score)) {
10236
10470
  issues.push(makeSchemaIssue(root, evidencePath, `${scorePath} must be an object.`));
10237
10471
  continue;
10238
10472
  }
@@ -10272,27 +10506,131 @@ async function validatePrototypingEvidence(root, config) {
10272
10506
  }
10273
10507
  }
10274
10508
  }
10509
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
10510
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
10511
+ issues.push(
10512
+ issue(
10513
+ "QFAI-PROT-285",
10514
+ "phase.current must follow the prototyping phase state machine.",
10515
+ "error",
10516
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10517
+ "prototypingEvidence.phase",
10518
+ void 0,
10519
+ "canonical",
10520
+ "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"
10521
+ )
10522
+ );
10523
+ }
10524
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
10525
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
10526
+ if (hasLegacyPass95CompletionMarker && completionClaimed) {
10527
+ issues.push(
10528
+ issue(
10529
+ "QFAI-PROT-288",
10530
+ "The legacy 95-point completion field is no longer a completion border; completion requires allReviewerAxesPerfect100.",
10531
+ "error",
10532
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10533
+ "prototypingEvidence.perfect100",
10534
+ void 0,
10535
+ "canonical",
10536
+ "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"
10537
+ )
10538
+ );
10539
+ }
10540
+ if (completionClaimed) {
10541
+ if (!latestPerfect100 || !allReviewerScoresArePerfect100(iterations)) {
10542
+ issues.push(
10543
+ issue(
10544
+ "QFAI-PROT-287",
10545
+ "Completion requires every reviewer to score every evaluation axis at 100.",
10546
+ "error",
10547
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10548
+ "prototypingEvidence.perfect100",
10549
+ void 0,
10550
+ "canonical",
10551
+ "completionClaimed/completed \u3092\u8A18\u9332\u3059\u308B\u524D\u306B\u3001\u5168 reviewerScores[].scores[].score \u3092 100 \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10552
+ )
10553
+ );
10554
+ }
10555
+ if (latestIterationKind === "select") {
10556
+ issues.push(
10557
+ issue(
10558
+ "QFAI-PROT-285",
10559
+ "Selection funnel completion is not stage completion; latest iteration cannot remain select when completion is claimed.",
10560
+ "error",
10561
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10562
+ "prototypingEvidence.phase",
10563
+ void 0,
10564
+ "canonical",
10565
+ "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"
10566
+ )
10567
+ );
10568
+ }
10569
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
10570
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
10571
+ issues.push(
10572
+ issue(
10573
+ "QFAI-PROT-286",
10574
+ "Completion requires at least one completed post-selection polish iteration.",
10575
+ "error",
10576
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10577
+ "prototypingEvidence.postSelectionPolish",
10578
+ void 0,
10579
+ "canonical",
10580
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish iteration \u306B critique/fix/recapture/rereview/breakthrough checks \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10581
+ )
10582
+ );
10583
+ }
10584
+ if (!polishWithBreakthroughCheck) {
10585
+ issues.push(
10586
+ issue(
10587
+ "QFAI-PROT-286",
10588
+ "Completion requires a polish iteration with critique/fix/recapture/rereview/breakthrough checks.",
10589
+ "error",
10590
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10591
+ "prototypingEvidence.postSelectionPolish",
10592
+ void 0,
10593
+ "canonical",
10594
+ "polish iteration \u306E checks.critique/fix/recapture/rereview/breakthrough \u3092 true \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10595
+ )
10596
+ );
10597
+ }
10598
+ if (!isRecord6(record2.completionCertificate)) {
10599
+ issues.push(
10600
+ issue(
10601
+ "QFAI-PROT-289",
10602
+ "completionCertificate is required when completion is claimed.",
10603
+ "error",
10604
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10605
+ "prototypingEvidence.completionCertificate",
10606
+ void 0,
10607
+ "canonical",
10608
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10609
+ )
10610
+ );
10611
+ }
10612
+ }
10275
10613
  const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
10276
- if (!foundPass95 && iterations.length < maxIterations) {
10614
+ if (!latestPerfect100 && iterations.length < maxIterations) {
10277
10615
  issues.push(
10278
10616
  issue(
10279
10617
  "QFAI-PROT-282",
10280
- `mode=${mode.effective} has not reached all-items-pass-95 and has remaining iterations.`,
10618
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining iterations.`,
10281
10619
  "warning",
10282
10620
  path35.relative(root, evidencePath).replace(/\\/g, "/"),
10283
10621
  "prototypingEvidence.convergence",
10284
10622
  [String(iterations.length), String(maxIterations)],
10285
10623
  "canonical",
10286
- "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"
10624
+ "\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"
10287
10625
  )
10288
10626
  );
10289
10627
  }
10290
- if (obligations?.requireRuntimeGate && !isRecord5(record2.runtimeGate)) {
10628
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10291
10629
  issues.push(
10292
10630
  makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10293
10631
  );
10294
10632
  }
10295
- if (obligations?.requireUiFidelity && !isRecord5(record2.uiFidelity)) {
10633
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10296
10634
  issues.push(
10297
10635
  makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10298
10636
  );
@@ -10331,98 +10669,446 @@ function normalizeIterations(value) {
10331
10669
  }
10332
10670
  return value;
10333
10671
  }
10334
- function isRecord5(value) {
10335
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10336
- }
10337
- async function readJsonFile(filePath) {
10338
- try {
10339
- const raw = await readFile24(filePath, "utf-8");
10340
- const parsed = JSON.parse(raw);
10341
- return isRecord5(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
10342
- } catch {
10343
- try {
10344
- await readFile24(filePath, "utf-8");
10345
- return { status: "invalid" };
10346
- } catch {
10347
- return { status: "missing" };
10348
- }
10672
+ function normalizeRounds(value) {
10673
+ if (!Array.isArray(value)) {
10674
+ return null;
10349
10675
  }
10676
+ return value;
10350
10677
  }
10351
- function makeSchemaIssue(root, evidencePath, message) {
10352
- return issue(
10353
- "QFAI-PROT-299",
10354
- message,
10355
- "error",
10356
- path35.relative(root, evidencePath).replace(/\\/g, "/"),
10357
- "prototypingEvidence.schema"
10358
- );
10359
- }
10360
-
10361
- // src/core/validators/prototypingDesignSystem.ts
10362
- import { access as access9, readFile as readFile25 } from "fs/promises";
10363
- import path36 from "path";
10364
- var RULE_CODE = "PROT-DS01";
10365
- var RULE_NAME = "prototyping.designSystemCompliance";
10366
- var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
10367
- var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
10368
- function isRecord6(value) {
10369
- return typeof value === "object" && value !== null && !Array.isArray(value);
10370
- }
10371
- function detectMode(raw) {
10372
- if (!isRecord6(raw)) {
10373
- return "other";
10678
+ function normalizePolishCycles(value) {
10679
+ if (!Array.isArray(value)) {
10680
+ return null;
10374
10681
  }
10375
- const topLevel = raw.mode;
10376
- if (isRecord6(topLevel) && topLevel.effective === "full-harness") {
10377
- return "full-harness";
10682
+ return value;
10683
+ }
10684
+ function normalizePhaseCurrent(value) {
10685
+ if (!isRecord6(value)) {
10686
+ return null;
10378
10687
  }
10379
- return "other";
10688
+ return typeof value.current === "string" ? value.current : null;
10380
10689
  }
10381
- function detectScorePresence(raw) {
10382
- if (!isRecord6(raw)) {
10383
- return false;
10690
+ function normalizeFullHarnessStatus(value) {
10691
+ if (!isRecord6(value)) {
10692
+ return null;
10384
10693
  }
10385
- const trace = raw.scoringTrace;
10386
- if (!isRecord6(trace)) {
10694
+ return typeof value.status === "string" ? value.status : null;
10695
+ }
10696
+ function hasCompletePolishChecks(value) {
10697
+ if (!isRecord6(value)) {
10387
10698
  return false;
10388
10699
  }
10389
- return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
10700
+ return REQUIRED_POLISH_CHECKS.every((key) => value[key] === true);
10390
10701
  }
10391
- async function fileExists2(filePath) {
10392
- try {
10393
- await access9(filePath);
10394
- return true;
10395
- } catch (err) {
10396
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10397
- return false;
10398
- }
10702
+ function allReviewerScoresArePerfect100(iterations) {
10703
+ const candidate = iterations[iterations.length - 1];
10704
+ if (!isRecord6(candidate) || !Array.isArray(candidate.reviewerScores)) {
10399
10705
  return false;
10400
10706
  }
10401
- }
10402
- async function readFileSafe(filePath) {
10403
- try {
10404
- return await readFile25(filePath, "utf-8");
10405
- } catch (err) {
10406
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10407
- return null;
10707
+ return candidate.reviewerScores.every((reviewer) => {
10708
+ if (!isRecord6(reviewer) || !Array.isArray(reviewer.scores) || reviewer.scores.length === 0) {
10709
+ return false;
10408
10710
  }
10409
- return null;
10410
- }
10711
+ return reviewer.scores.every((score) => isRecord6(score) && score.score === 100);
10712
+ });
10411
10713
  }
10412
- async function loadPrototypingArtifact(evidenceDir) {
10413
- const jsonPath = path36.join(evidenceDir, "prototyping.json");
10414
- const jsonRaw = await readFileSafe(jsonPath);
10415
- if (jsonRaw !== null) {
10416
- let parsed = null;
10417
- try {
10418
- parsed = JSON.parse(jsonRaw);
10419
- } catch {
10714
+ function validateV2Lifecycle(root, evidencePath, record2, mode, obligations) {
10715
+ const issues = [];
10716
+ const rounds = normalizeRounds(record2.rounds);
10717
+ if (!rounds || rounds.length === 0) {
10718
+ issues.push(
10719
+ issue(
10720
+ "QFAI-PROT-280",
10721
+ "prototyping evidence requires at least one round entry.",
10722
+ "error",
10723
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10724
+ "prototypingEvidence.rounds",
10725
+ void 0,
10726
+ "canonical",
10727
+ "rounds[] \u306B\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u306E\u63A2\u7D22 round \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10728
+ )
10729
+ );
10730
+ return issues;
10731
+ }
10732
+ const polishCycles = normalizePolishCycles(record2.polishCycles) ?? [];
10733
+ if (obligations && polishCycles.length > obligations.maxIterations) {
10734
+ issues.push(
10735
+ issue(
10736
+ "QFAI-PROT-281",
10737
+ `mode=${mode.effective} exceeds max polish cycles (${obligations.maxIterations}).`,
10738
+ "error",
10739
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10740
+ "prototypingEvidence.maxIterations",
10741
+ [String(polishCycles.length), String(obligations.maxIterations)],
10742
+ "canonical",
10743
+ `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`
10744
+ )
10745
+ );
10746
+ }
10747
+ let latestPerfect100 = false;
10748
+ let completedPolishCount = 0;
10749
+ let polishWithBreakthroughCheck = false;
10750
+ for (const [index, candidate] of rounds.entries()) {
10751
+ const prefix = `rounds[${index}]`;
10752
+ if (!isRecord6(candidate)) {
10753
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10754
+ continue;
10755
+ }
10756
+ let validRound = null;
10757
+ if (!isExplorationRound(candidate.round)) {
10758
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix}.round must be r5|r3|r2|r1.`));
10759
+ } else {
10760
+ validRound = candidate.round;
10761
+ if (index >= EXPLORATION_ROUNDS.length) {
10762
+ issues.push(
10763
+ makeSchemaIssue(
10764
+ root,
10765
+ evidencePath,
10766
+ `${prefix} exceeds the funnel limit (rounds[] must contain at most ${EXPLORATION_ROUNDS.length} entries: ${EXPLORATION_ROUNDS.join("\u2192")}).`
10767
+ )
10768
+ );
10769
+ } else {
10770
+ const expectedRound = EXPLORATION_ROUNDS[index];
10771
+ if (expectedRound !== void 0 && validRound !== expectedRound) {
10772
+ issues.push(
10773
+ makeSchemaIssue(
10774
+ root,
10775
+ evidencePath,
10776
+ `${prefix}.round must be ${expectedRound} (rounds[] must follow ${EXPLORATION_ROUNDS.join("\u2192")}); got ${validRound}.`
10777
+ )
10778
+ );
10779
+ }
10780
+ }
10781
+ }
10782
+ const candidateIds = [];
10783
+ const seenCandidateIds = /* @__PURE__ */ new Set();
10784
+ if (!Array.isArray(candidate.candidates) || candidate.candidates.length === 0) {
10785
+ issues.push(
10786
+ makeSchemaIssue(root, evidencePath, `${prefix}.candidates must be a non-empty array.`)
10787
+ );
10788
+ } else {
10789
+ for (const [candidateIndex, roundCandidate] of candidate.candidates.entries()) {
10790
+ if (!isRecord6(roundCandidate)) {
10791
+ issues.push(
10792
+ makeSchemaIssue(
10793
+ root,
10794
+ evidencePath,
10795
+ `${prefix}.candidates[${candidateIndex}] must be an object.`
10796
+ )
10797
+ );
10798
+ continue;
10799
+ }
10800
+ if (!isCandidateId(roundCandidate.candidateId)) {
10801
+ issues.push(
10802
+ makeSchemaIssue(
10803
+ root,
10804
+ evidencePath,
10805
+ `${prefix}.candidates[${candidateIndex}].candidateId must be a valid candidate id.`
10806
+ )
10807
+ );
10808
+ } else if (seenCandidateIds.has(roundCandidate.candidateId)) {
10809
+ issues.push(
10810
+ makeSchemaIssue(
10811
+ root,
10812
+ evidencePath,
10813
+ `${prefix}.candidates[${candidateIndex}].candidateId must be unique within the round; ${roundCandidate.candidateId} already appears earlier.`
10814
+ )
10815
+ );
10816
+ } else {
10817
+ seenCandidateIds.add(roundCandidate.candidateId);
10818
+ candidateIds.push(roundCandidate.candidateId);
10819
+ }
10820
+ }
10821
+ if (validRound) {
10822
+ const expectedCandidateCount = ROUND_SURVIVOR_COUNT[validRound];
10823
+ if (candidate.candidates.length !== expectedCandidateCount) {
10824
+ issues.push(
10825
+ makeSchemaIssue(
10826
+ root,
10827
+ evidencePath,
10828
+ `${prefix}.candidates must contain exactly ${expectedCandidateCount} entries for round ${validRound}; got ${candidate.candidates.length}.`
10829
+ )
10830
+ );
10831
+ }
10832
+ }
10833
+ }
10834
+ const screenMap = isRecord6(candidate.screenEvidenceByCandidate) ? candidate.screenEvidenceByCandidate : null;
10835
+ if (screenMap === null) {
10836
+ issues.push(
10837
+ makeSchemaIssue(
10838
+ root,
10839
+ evidencePath,
10840
+ `${prefix}.screenEvidenceByCandidate must be an object keyed by candidateId.`
10841
+ )
10842
+ );
10843
+ }
10844
+ const reviewMap = isRecord6(candidate.evaluatorReviewRefsByCandidate) ? candidate.evaluatorReviewRefsByCandidate : null;
10845
+ if (reviewMap === null) {
10846
+ issues.push(
10847
+ makeSchemaIssue(
10848
+ root,
10849
+ evidencePath,
10850
+ `${prefix}.evaluatorReviewRefsByCandidate must be an object keyed by candidateId.`
10851
+ )
10852
+ );
10853
+ }
10854
+ for (const candidateId of candidateIds) {
10855
+ if (screenMap) {
10856
+ const screens = screenMap[candidateId];
10857
+ if (!Array.isArray(screens) || screens.length === 0) {
10858
+ issues.push(
10859
+ makeSchemaIssue(
10860
+ root,
10861
+ evidencePath,
10862
+ `${prefix}.screenEvidenceByCandidate.${candidateId} must be a non-empty array of screen artifact refs.`
10863
+ )
10864
+ );
10865
+ }
10866
+ }
10867
+ if (reviewMap) {
10868
+ const reviewRef = reviewMap[candidateId];
10869
+ if (typeof reviewRef !== "string" || reviewRef.trim().length === 0) {
10870
+ issues.push(
10871
+ makeSchemaIssue(
10872
+ root,
10873
+ evidencePath,
10874
+ `${prefix}.evaluatorReviewRefsByCandidate.${candidateId} must be a non-empty string.`
10875
+ )
10876
+ );
10877
+ }
10878
+ }
10879
+ }
10880
+ if (typeof candidate.allAxesPerfect100 !== "boolean") {
10881
+ issues.push(
10882
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
10883
+ );
10884
+ } else {
10885
+ latestPerfect100 = candidate.allAxesPerfect100;
10886
+ }
10887
+ }
10888
+ for (const [index, cycle] of polishCycles.entries()) {
10889
+ const prefix = `polishCycles[${index}]`;
10890
+ if (!isRecord6(cycle)) {
10891
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10892
+ continue;
10893
+ }
10894
+ if (typeof cycle.cycle !== "number" || !Number.isInteger(cycle.cycle) || cycle.cycle <= 0) {
10895
+ issues.push(
10896
+ makeSchemaIssue(root, evidencePath, `${prefix}.cycle must be a positive integer.`)
10897
+ );
10898
+ }
10899
+ if (typeof cycle.kind !== "string" || !["polish", "branch", "reviewer_gate", "completed"].includes(cycle.kind)) {
10900
+ issues.push(
10901
+ makeSchemaIssue(
10902
+ root,
10903
+ evidencePath,
10904
+ `${prefix}.kind must be polish|branch|reviewer_gate|completed.`
10905
+ )
10906
+ );
10907
+ }
10908
+ if (typeof cycle.evaluatorReviewRef !== "string" || cycle.evaluatorReviewRef.trim().length === 0) {
10909
+ issues.push(
10910
+ makeSchemaIssue(root, evidencePath, `${prefix}.evaluatorReviewRef must be non-empty.`)
10911
+ );
10912
+ }
10913
+ if (typeof cycle.allAxesPerfect100 !== "boolean") {
10914
+ issues.push(
10915
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
10916
+ );
10917
+ } else {
10918
+ latestPerfect100 = cycle.allAxesPerfect100;
10919
+ }
10920
+ if (cycle.kind === "polish") {
10921
+ completedPolishCount += 1;
10922
+ if (typeof cycle.breakthroughRef === "string" && cycle.breakthroughRef.trim().length > 0) {
10923
+ polishWithBreakthroughCheck = true;
10924
+ }
10925
+ }
10926
+ }
10927
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
10928
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
10929
+ issues.push(
10930
+ issue(
10931
+ "QFAI-PROT-285",
10932
+ "phase.current must follow the prototyping phase state machine.",
10933
+ "error",
10934
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10935
+ "prototypingEvidence.phase",
10936
+ void 0,
10937
+ "canonical",
10938
+ "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"
10939
+ )
10940
+ );
10941
+ }
10942
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
10943
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
10944
+ if (completionClaimed) {
10945
+ if (!latestPerfect100) {
10946
+ issues.push(
10947
+ issue(
10948
+ "QFAI-PROT-287",
10949
+ "Completion requires every reviewer to score every evaluation axis at 100.",
10950
+ "error",
10951
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10952
+ "prototypingEvidence.perfect100",
10953
+ void 0,
10954
+ "canonical",
10955
+ "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"
10956
+ )
10957
+ );
10958
+ }
10959
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
10960
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
10961
+ issues.push(
10962
+ issue(
10963
+ "QFAI-PROT-286",
10964
+ "Completion requires at least one completed post-selection polish cycle.",
10965
+ "error",
10966
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10967
+ "prototypingEvidence.postSelectionPolish",
10968
+ void 0,
10969
+ "canonical",
10970
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish cycle \u3092\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10971
+ )
10972
+ );
10973
+ }
10974
+ if (!polishWithBreakthroughCheck) {
10975
+ issues.push(
10976
+ issue(
10977
+ "QFAI-PROT-286",
10978
+ "Completion requires a polish cycle with breakthrough evidence.",
10979
+ "error",
10980
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10981
+ "prototypingEvidence.postSelectionPolish",
10982
+ void 0,
10983
+ "canonical",
10984
+ "polish cycle \u306B breakthroughRef \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10985
+ )
10986
+ );
10987
+ }
10988
+ if (!isRecord6(record2.completionCertificate)) {
10989
+ issues.push(
10990
+ issue(
10991
+ "QFAI-PROT-289",
10992
+ "completionCertificate is required when completion is claimed.",
10993
+ "error",
10994
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
10995
+ "prototypingEvidence.completionCertificate",
10996
+ void 0,
10997
+ "canonical",
10998
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10999
+ )
11000
+ );
11001
+ }
11002
+ }
11003
+ const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
11004
+ if (!latestPerfect100 && polishCycles.length < maxIterations) {
11005
+ issues.push(
11006
+ issue(
11007
+ "QFAI-PROT-282",
11008
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining polish cycles.`,
11009
+ "warning",
11010
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
11011
+ "prototypingEvidence.convergence",
11012
+ [String(polishCycles.length), String(maxIterations)],
11013
+ "canonical",
11014
+ "\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"
11015
+ )
11016
+ );
11017
+ }
11018
+ return issues;
11019
+ }
11020
+ function isRecord6(value) {
11021
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11022
+ }
11023
+ async function readJsonFile(filePath) {
11024
+ try {
11025
+ const raw = await readFile24(filePath, "utf-8");
11026
+ const parsed = JSON.parse(raw);
11027
+ return isRecord6(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
11028
+ } catch {
11029
+ try {
11030
+ await readFile24(filePath, "utf-8");
11031
+ return { status: "invalid" };
11032
+ } catch {
11033
+ return { status: "missing" };
11034
+ }
11035
+ }
11036
+ }
11037
+ function makeSchemaIssue(root, evidencePath, message) {
11038
+ return issue(
11039
+ "QFAI-PROT-299",
11040
+ message,
11041
+ "error",
11042
+ path35.relative(root, evidencePath).replace(/\\/g, "/"),
11043
+ "prototypingEvidence.schema"
11044
+ );
11045
+ }
11046
+
11047
+ // src/core/validators/prototypingDesignSystem.ts
11048
+ import { access as access9, readFile as readFile25 } from "fs/promises";
11049
+ import path36 from "path";
11050
+ var RULE_CODE = "PROT-DS01";
11051
+ var RULE_NAME = "prototyping.designSystemCompliance";
11052
+ var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
11053
+ var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
11054
+ function isRecord7(value) {
11055
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11056
+ }
11057
+ function detectMode2(raw) {
11058
+ if (!isRecord7(raw)) {
11059
+ return "other";
11060
+ }
11061
+ const topLevel = raw.mode;
11062
+ if (isRecord7(topLevel) && topLevel.effective === "full-harness") {
11063
+ return "full-harness";
11064
+ }
11065
+ return "other";
11066
+ }
11067
+ function detectScorePresence(raw) {
11068
+ if (!isRecord7(raw)) {
11069
+ return false;
11070
+ }
11071
+ const trace = raw.scoringTrace;
11072
+ if (!isRecord7(trace)) {
11073
+ return false;
11074
+ }
11075
+ return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
11076
+ }
11077
+ async function fileExists2(filePath) {
11078
+ try {
11079
+ await access9(filePath);
11080
+ return true;
11081
+ } catch (err) {
11082
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11083
+ return false;
11084
+ }
11085
+ return false;
11086
+ }
11087
+ }
11088
+ async function readFileSafe(filePath) {
11089
+ try {
11090
+ return await readFile25(filePath, "utf-8");
11091
+ } catch (err) {
11092
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11093
+ return null;
11094
+ }
11095
+ return null;
11096
+ }
11097
+ }
11098
+ async function loadPrototypingArtifact(evidenceDir) {
11099
+ const jsonPath = path36.join(evidenceDir, "prototyping.json");
11100
+ const jsonRaw = await readFileSafe(jsonPath);
11101
+ if (jsonRaw !== null) {
11102
+ let parsed = null;
11103
+ try {
11104
+ parsed = JSON.parse(jsonRaw);
11105
+ } catch {
10420
11106
  parsed = null;
10421
11107
  }
10422
11108
  return {
10423
11109
  source: "json",
10424
11110
  fileName: "prototyping.json",
10425
- mode: detectMode(parsed),
11111
+ mode: detectMode2(parsed),
10426
11112
  designSystemCompliancePresent: detectScorePresence(parsed)
10427
11113
  };
10428
11114
  }
@@ -10444,38 +11130,1060 @@ function toIssue(severity, file) {
10444
11130
  suggested_action: SUGGESTED_ACTION
10445
11131
  };
10446
11132
  }
10447
- function resolveEvidenceRoot(root, config) {
10448
- return path36.join(path36.dirname(resolvePath(root, config, "specsDir")), "evidence");
11133
+ function resolveEvidenceRoot(root, config) {
11134
+ return path36.join(path36.dirname(resolvePath(root, config, "specsDir")), "evidence");
11135
+ }
11136
+ async function validatePrototypingDesignSystem(root, config) {
11137
+ const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
11138
+ if (screens.length === 0) {
11139
+ return [];
11140
+ }
11141
+ const evidenceDir = resolveEvidenceRoot(root, config);
11142
+ const artifact = await loadPrototypingArtifact(evidenceDir);
11143
+ if (artifact.designSystemCompliancePresent) {
11144
+ return [];
11145
+ }
11146
+ const designSystemPath = path36.join(
11147
+ root,
11148
+ config.paths.contractsDir,
11149
+ "design",
11150
+ "design-system.yaml"
11151
+ );
11152
+ const designSystemPresent = await fileExists2(designSystemPath);
11153
+ const severity = designSystemPresent && artifact.mode === "full-harness" ? "error" : "warning";
11154
+ return [toIssue(severity, artifact.fileName)];
11155
+ }
11156
+
11157
+ // src/core/validators/prototyping/executionPlan.ts
11158
+ init_utils();
11159
+ var FIELD_SHAPE_LABEL = {
11160
+ number: "finite number",
11161
+ "non-empty-string": "non-empty string",
11162
+ record: "object (not array)"
11163
+ };
11164
+ var REQUIRED_FIELDS = [
11165
+ { key: "targetIterations", shape: "number" },
11166
+ { key: "evaluationAxesSource", shape: "non-empty-string" },
11167
+ { key: "delegationMap", shape: "record" },
11168
+ { key: "plannedAt", shape: "non-empty-string" }
11169
+ ];
11170
+ function describeType(value) {
11171
+ if (value === null) return "null";
11172
+ if (Array.isArray(value)) return "array";
11173
+ return typeof value;
11174
+ }
11175
+ function matchesShape(value, shape) {
11176
+ switch (shape) {
11177
+ case "number":
11178
+ return typeof value === "number" && Number.isFinite(value);
11179
+ case "non-empty-string":
11180
+ return typeof value === "string" && value.trim().length > 0;
11181
+ case "record":
11182
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11183
+ }
11184
+ }
11185
+ function isRecord8(v) {
11186
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11187
+ }
11188
+ function detectMode3(raw) {
11189
+ if (!isRecord8(raw)) return "other";
11190
+ if (typeof raw.mode === "string") return raw.mode;
11191
+ if (isRecord8(raw.mode) && typeof raw.mode.effective === "string") {
11192
+ return raw.mode.effective;
11193
+ }
11194
+ return "other";
11195
+ }
11196
+ function validateExecutionPlanIssues(prototypingJson, prototypingJsonPath) {
11197
+ if (!isRecord8(prototypingJson)) {
11198
+ return [];
11199
+ }
11200
+ if (detectMode3(prototypingJson) !== "full-harness") {
11201
+ return [];
11202
+ }
11203
+ if (!isRecord8(prototypingJson.executionPlan)) {
11204
+ return [
11205
+ buildIssue(
11206
+ "executionPlan is required in full-harness mode but is absent or not an object in prototyping.json.",
11207
+ prototypingJsonPath
11208
+ )
11209
+ ];
11210
+ }
11211
+ const issues = [];
11212
+ const executionPlan = prototypingJson.executionPlan;
11213
+ for (const { key, shape } of REQUIRED_FIELDS) {
11214
+ const value = executionPlan[key];
11215
+ if (!matchesShape(value, shape)) {
11216
+ issues.push(
11217
+ buildIssue(
11218
+ `executionPlan.${key} is required in full-harness mode and must be a ${FIELD_SHAPE_LABEL[shape]} (got: ${describeType(value)}).`,
11219
+ prototypingJsonPath
11220
+ )
11221
+ );
11222
+ }
11223
+ }
11224
+ return issues;
11225
+ }
11226
+ function buildIssue(message, prototypingJsonPath) {
11227
+ return issue(
11228
+ "QFAI-PROT-310",
11229
+ message,
11230
+ "error",
11231
+ prototypingJsonPath,
11232
+ "prototyping.executionPlan.presence",
11233
+ void 0,
11234
+ "canonical",
11235
+ "full-harness \u30E2\u30FC\u30C9\u3067\u306F `prototyping.json.executionPlan` \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044 (targetIterations / evaluationAxesSource / delegationMap / plannedAt)\u3002"
11236
+ );
11237
+ }
11238
+
11239
+ // src/core/validators/prototyping/delegationMap.ts
11240
+ init_utils();
11241
+ var DELEGATION_SCOPE = {
11242
+ UI\u5B9F\u88C5: ["frontend-engineer", "product-experience-architect"],
11243
+ \u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8: ["devops-ci-engineer"],
11244
+ \u8A55\u4FA1\u30B9\u30B3\u30A2\u30EA\u30F3\u30B0: ["product-surface-reviewer", "product-experience-architect"],
11245
+ \u30D3\u30EB\u30C9: ["devops-ci-engineer", "backend-engineer"]
11246
+ };
11247
+ var DELEGATION_CATEGORIES = Object.keys(DELEGATION_SCOPE);
11248
+ function validateDelegationMapIssues(delegationMap, prototypingJsonPath) {
11249
+ if (!delegationMap) {
11250
+ return [];
11251
+ }
11252
+ const issues = [];
11253
+ for (const [category, rawRole] of Object.entries(delegationMap)) {
11254
+ const allowedRoles = DELEGATION_SCOPE[category];
11255
+ if (allowedRoles === void 0) {
11256
+ continue;
11257
+ }
11258
+ if (typeof rawRole !== "string") {
11259
+ issues.push(
11260
+ issue(
11261
+ "QFAI-PROT-311",
11262
+ `Delegation violation: category "${category}" assigned to non-string value (got: ${describeRoleType(rawRole)}). Allowed roles: ${allowedRoles.join(", ")}.`,
11263
+ "error",
11264
+ prototypingJsonPath,
11265
+ "prototyping.executionPlan.delegationMap",
11266
+ void 0,
11267
+ "canonical",
11268
+ `category "${category}" \u306B\u306F string \u306E role \u3092\u5272\u308A\u5F53\u3066\u3066\u304F\u3060\u3055\u3044 (allowed: ${allowedRoles.join(", ")})\u3002`
11269
+ )
11270
+ );
11271
+ continue;
11272
+ }
11273
+ if (!allowedRoles.includes(rawRole)) {
11274
+ issues.push(
11275
+ issue(
11276
+ "QFAI-PROT-311",
11277
+ `Delegation violation: category "${category}" assigned to undefined/invalid role "${rawRole}". Allowed roles: ${allowedRoles.join(", ")}.`,
11278
+ "error",
11279
+ prototypingJsonPath,
11280
+ "prototyping.executionPlan.delegationMap",
11281
+ void 0,
11282
+ "canonical",
11283
+ `category "${category}" \u3092\u8A31\u53EF\u3055\u308C\u305F role \u306B\u5272\u308A\u5F53\u3066\u3066\u304F\u3060\u3055\u3044 (allowed: ${allowedRoles.join(", ")})\u3002`
11284
+ )
11285
+ );
11286
+ }
11287
+ }
11288
+ return issues;
11289
+ }
11290
+ function describeRoleType(value) {
11291
+ if (value === null) return "null";
11292
+ if (Array.isArray(value)) return "array";
11293
+ return typeof value;
11294
+ }
11295
+
11296
+ // src/core/validators/prototyping/screenshotDir.ts
11297
+ init_utils();
11298
+ function isRecord9(v) {
11299
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11300
+ }
11301
+ function validateScreenshotDirIssues(scoringTrace, mode, prototypingJsonPath) {
11302
+ if (mode !== "full-harness") {
11303
+ return [];
11304
+ }
11305
+ const issues = [];
11306
+ 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";
11307
+ for (let i = 0; i < scoringTrace.length; i++) {
11308
+ const entry = scoringTrace[i];
11309
+ if (!isRecord9(entry)) {
11310
+ issues.push(
11311
+ issue(
11312
+ "QFAI-PROT-331",
11313
+ `scoringTrace[${i}] is not an object; screenshotDir cannot be verified.`,
11314
+ "error",
11315
+ prototypingJsonPath,
11316
+ "prototyping.fullHarness.scoringTrace.screenshotDir",
11317
+ void 0,
11318
+ "canonical",
11319
+ suggestion
11320
+ )
11321
+ );
11322
+ continue;
11323
+ }
11324
+ if (typeof entry.screenshotDir !== "string" || entry.screenshotDir.trim() === "") {
11325
+ issues.push(
11326
+ issue(
11327
+ "QFAI-PROT-331",
11328
+ `scoringTrace[${i}].screenshotDir is missing or empty; full-harness requires screenshot evidence per iteration.`,
11329
+ "error",
11330
+ prototypingJsonPath,
11331
+ "prototyping.fullHarness.scoringTrace.screenshotDir",
11332
+ void 0,
11333
+ "canonical",
11334
+ suggestion
11335
+ )
11336
+ );
11337
+ }
11338
+ }
11339
+ return issues;
11340
+ }
11341
+
11342
+ // src/core/validators/prototyping/lighthouseGate.ts
11343
+ init_utils();
11344
+ function isRecord10(v) {
11345
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11346
+ }
11347
+ function detectMode4(raw) {
11348
+ if (!isRecord10(raw)) return "other";
11349
+ if (typeof raw.mode === "string") return raw.mode;
11350
+ if (isRecord10(raw.mode) && typeof raw.mode.effective === "string") {
11351
+ return raw.mode.effective;
11352
+ }
11353
+ return "other";
11354
+ }
11355
+ function detectSurface(raw) {
11356
+ if (!isRecord10(raw)) return "unknown";
11357
+ if (typeof raw.surface === "string") return raw.surface;
11358
+ return "unknown";
11359
+ }
11360
+ function hasLighthouseReport(raw) {
11361
+ if (!isRecord10(raw)) return false;
11362
+ const report = raw.lighthouseReport ?? raw.lighthouse;
11363
+ if (!report) return false;
11364
+ if (isRecord10(report)) return Object.keys(report).length > 0;
11365
+ if (typeof report === "string") return report.trim().length > 0;
11366
+ return false;
11367
+ }
11368
+ function validateLighthouseGateIssues(prototypingJson, prototypingJsonPath) {
11369
+ const mode = detectMode4(prototypingJson);
11370
+ const surface = detectSurface(prototypingJson);
11371
+ if (mode !== "full-harness" || surface !== "web") {
11372
+ return [];
11373
+ }
11374
+ if (hasLighthouseReport(prototypingJson)) {
11375
+ return [];
11376
+ }
11377
+ return [
11378
+ issue(
11379
+ "QFAI-PROT-332",
11380
+ "Lighthouse Gate is MUST for full-harness + web surface: no Lighthouse report found in prototyping evidence.",
11381
+ "error",
11382
+ prototypingJsonPath,
11383
+ "prototyping.lighthouseReport",
11384
+ void 0,
11385
+ "canonical",
11386
+ "full-harness + web surface \u3067\u306F `prototyping.json.lighthouseReport` (\u307E\u305F\u306F `lighthouse`) \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11387
+ )
11388
+ ];
11389
+ }
11390
+
11391
+ // src/core/validators/prototyping/iterationGate.ts
11392
+ init_utils();
11393
+ function validateIterationGateIssues(iterations, prototypingJsonPath) {
11394
+ if (iterations.length === 0) {
11395
+ return [];
11396
+ }
11397
+ for (const entry of iterations) {
11398
+ if (entry.iterationCount === 1 && entry.converged === true) {
11399
+ return [
11400
+ issue(
11401
+ "QFAI-PROT-333",
11402
+ "minimum 2 iterations required before convergence: iteration 1 cannot be marked converged.",
11403
+ "error",
11404
+ prototypingJsonPath,
11405
+ "prototyping.fullHarness.iterations.convergence",
11406
+ void 0,
11407
+ "canonical",
11408
+ "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"
11409
+ )
11410
+ ];
11411
+ }
11412
+ }
11413
+ return [];
11414
+ }
11415
+
11416
+ // src/core/validators/prototyping/designSystemThreshold.ts
11417
+ init_utils();
11418
+ import { access as access10 } from "fs/promises";
11419
+ import path37 from "path";
11420
+ var DS_PASS_THRESHOLD = 0.75;
11421
+ function isRecord11(v) {
11422
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11423
+ }
11424
+ async function designSystemMdPresent(packDir) {
11425
+ try {
11426
+ await access10(path37.join(packDir, "uiux", "12_design_system.md"));
11427
+ return true;
11428
+ } catch {
11429
+ return false;
11430
+ }
11431
+ }
11432
+ function extractScore(prototypingRecord) {
11433
+ if (!isRecord11(prototypingRecord)) return null;
11434
+ const trace = prototypingRecord.scoringTrace;
11435
+ if (!isRecord11(trace)) return null;
11436
+ const score = trace.designSystemCompliance;
11437
+ if (typeof score !== "number") return null;
11438
+ return score;
11439
+ }
11440
+ async function validateDesignSystemThresholdIssues(packDir, prototypingRecord, prototypingJsonPath) {
11441
+ const hasDesignSystem = await designSystemMdPresent(packDir);
11442
+ if (!hasDesignSystem) {
11443
+ return [];
11444
+ }
11445
+ const score = extractScore(prototypingRecord);
11446
+ if (score === null) {
11447
+ return [];
11448
+ }
11449
+ if (score >= DS_PASS_THRESHOLD) {
11450
+ return [];
11451
+ }
11452
+ return [
11453
+ issue(
11454
+ "QFAI-PROT-334",
11455
+ `designSystemCompliance score ${(score * 100).toFixed(0)}% is below threshold ${(DS_PASS_THRESHOLD * 100).toFixed(0)}%; immediate fix required for next iteration.`,
11456
+ "error",
11457
+ prototypingJsonPath,
11458
+ "prototyping.scoringTrace.designSystemCompliance.threshold",
11459
+ void 0,
11460
+ "canonical",
11461
+ `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`
11462
+ )
11463
+ ];
11464
+ }
11465
+
11466
+ // src/core/validators/prototyping/stateGate.ts
11467
+ import { readFile as readFile26 } from "fs/promises";
11468
+ import path38 from "path";
11469
+ function isRecord12(value) {
11470
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11471
+ }
11472
+ function extractDelegationMap(prototypingJson) {
11473
+ if (!isRecord12(prototypingJson)) return void 0;
11474
+ const executionPlan = prototypingJson.executionPlan;
11475
+ if (!isRecord12(executionPlan)) return void 0;
11476
+ const delegationMap = executionPlan.delegationMap;
11477
+ if (!isRecord12(delegationMap)) return void 0;
11478
+ return delegationMap;
11479
+ }
11480
+ function extractMode(prototypingJson) {
11481
+ if (!isRecord12(prototypingJson)) return "other";
11482
+ if (typeof prototypingJson.mode === "string") return prototypingJson.mode;
11483
+ if (isRecord12(prototypingJson.mode) && typeof prototypingJson.mode.effective === "string") {
11484
+ return prototypingJson.mode.effective;
11485
+ }
11486
+ return "other";
11487
+ }
11488
+ function extractScoringTrace(prototypingJson) {
11489
+ if (!isRecord12(prototypingJson)) return [];
11490
+ const fullHarness = prototypingJson.fullHarness;
11491
+ if (!isRecord12(fullHarness)) return [];
11492
+ const trace = fullHarness.scoringTrace;
11493
+ return Array.isArray(trace) ? trace : [];
11494
+ }
11495
+ function extractFullHarnessIterations(prototypingJson) {
11496
+ if (!isRecord12(prototypingJson)) return [];
11497
+ const fullHarness = prototypingJson.fullHarness;
11498
+ if (!isRecord12(fullHarness)) return [];
11499
+ const iterations = fullHarness.iterations;
11500
+ if (!Array.isArray(iterations)) return [];
11501
+ return iterations.filter(
11502
+ (entry) => isRecord12(entry) && typeof entry.iterationCount === "number"
11503
+ );
11504
+ }
11505
+ function resolveCalibrationPackDir(root, config) {
11506
+ const packPath = config.prototyping?.calibration?.packPath;
11507
+ if (!packPath || typeof packPath !== "string") return void 0;
11508
+ return path38.resolve(root, packPath);
11509
+ }
11510
+ async function validateStateGate(root, config) {
11511
+ const evidenceRoot = path38.join(
11512
+ path38.dirname(path38.resolve(root, config.paths.specsDir)),
11513
+ "evidence"
11514
+ );
11515
+ const prototypingJsonPath = path38.join(evidenceRoot, "prototyping.json");
11516
+ let raw;
11517
+ try {
11518
+ raw = await readFile26(prototypingJsonPath, "utf-8");
11519
+ } catch {
11520
+ return [];
11521
+ }
11522
+ let parsed;
11523
+ try {
11524
+ parsed = JSON.parse(raw);
11525
+ } catch {
11526
+ return [];
11527
+ }
11528
+ const relPath = path38.relative(root, prototypingJsonPath).replace(/\\/g, "/");
11529
+ const mode = extractMode(parsed);
11530
+ const issues = [];
11531
+ issues.push(...validateExecutionPlanIssues(parsed, relPath));
11532
+ issues.push(...validateDelegationMapIssues(extractDelegationMap(parsed), relPath));
11533
+ issues.push(...validateScreenshotDirIssues(extractScoringTrace(parsed), mode, relPath));
11534
+ issues.push(...validateIterationGateIssues(extractFullHarnessIterations(parsed), relPath));
11535
+ issues.push(...validateLighthouseGateIssues(parsed, relPath));
11536
+ const packDir = resolveCalibrationPackDir(root, config);
11537
+ if (packDir) {
11538
+ issues.push(...await validateDesignSystemThresholdIssues(packDir, parsed, relPath));
11539
+ }
11540
+ return issues;
11541
+ }
11542
+
11543
+ // src/core/validators/prototyping/completionCertificate.ts
11544
+ import { readFile as readFile28 } from "fs/promises";
11545
+ import path40 from "path";
11546
+
11547
+ // src/core/prototyping/certificate.ts
11548
+ import { createHash } from "crypto";
11549
+ import { mkdir as mkdir4, readFile as readFile27, readdir as readdir8, stat as stat5, writeFile as writeFile4 } from "fs/promises";
11550
+ import path39 from "path";
11551
+ var COMPLETION_CERTIFICATE_REL_PATH = ".qfai/evidence/prototyping/completion-certificate.json";
11552
+ async function loadCompletionCertificate(root) {
11553
+ const fullPath = path39.join(root, COMPLETION_CERTIFICATE_REL_PATH);
11554
+ try {
11555
+ const raw = await readFile27(fullPath, "utf-8");
11556
+ const parsed = JSON.parse(raw);
11557
+ if (!isMinimallyValidCertificate(parsed)) {
11558
+ return null;
11559
+ }
11560
+ return parsed;
11561
+ } catch {
11562
+ return null;
11563
+ }
11564
+ }
11565
+ function isMinimallyValidCertificate(value) {
11566
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
11567
+ const v = value;
11568
+ if (v.schemaVersion !== "1.0") return false;
11569
+ if (typeof v.runId !== "string") return false;
11570
+ if (!Array.isArray(v.evidenceDigests)) return false;
11571
+ for (const entry of v.evidenceDigests) {
11572
+ if (!entry || typeof entry !== "object") return false;
11573
+ const e = entry;
11574
+ if (typeof e.path !== "string" || typeof e.sha256 !== "string") return false;
11575
+ }
11576
+ if (!v.validateRun || typeof v.validateRun !== "object") return false;
11577
+ if (!v.verifyRun || typeof v.verifyRun !== "object") return false;
11578
+ if (!v.reviewerSignoff || typeof v.reviewerSignoff !== "object") return false;
11579
+ return true;
11580
+ }
11581
+ async function checkCompletionCertificate(root) {
11582
+ const cert = await loadCompletionCertificate(root);
11583
+ if (!cert) {
11584
+ return {
11585
+ ok: false,
11586
+ reasons: [
11587
+ `${COMPLETION_CERTIFICATE_REL_PATH} not found. Run \`qfai prototyping certify\` after all gates pass.`
11588
+ ]
11589
+ };
11590
+ }
11591
+ const reasons = [];
11592
+ const evidenceRoot = path39.join(root, ".qfai/evidence/prototyping");
11593
+ const currentDigests = await scanEvidenceDigests(evidenceRoot);
11594
+ const certMap = new Map(cert.evidenceDigests.map((entry) => [entry.path, entry.sha256]));
11595
+ const currMap = new Map(currentDigests.map((entry) => [entry.path, entry.sha256]));
11596
+ for (const [p, hash] of certMap) {
11597
+ const observed = currMap.get(p);
11598
+ if (observed === void 0) {
11599
+ reasons.push(`evidence file removed since certify: ${p}`);
11600
+ } else if (observed !== hash) {
11601
+ reasons.push(`digest mismatch (file modified since certify): ${p}`);
11602
+ }
11603
+ }
11604
+ for (const [p] of currMap) {
11605
+ if (!certMap.has(p)) {
11606
+ reasons.push(`new evidence file present but not in certificate: ${p}`);
11607
+ }
11608
+ }
11609
+ if (cert.validateRun.errorCount !== 0) {
11610
+ reasons.push("validateRun.errorCount must be 0");
11611
+ }
11612
+ if (cert.verifyRun.status !== "PASS") {
11613
+ reasons.push("verifyRun.status must be PASS");
11614
+ }
11615
+ if (!cert.reviewerSignoff.approved) {
11616
+ reasons.push("reviewerSignoff.approved must be true");
11617
+ }
11618
+ return reasons.length === 0 ? { ok: true } : { ok: false, reasons };
11619
+ }
11620
+ async function scanEvidenceDigests(evidenceRoot) {
11621
+ const out = [];
11622
+ await walk2(evidenceRoot, evidenceRoot, out);
11623
+ out.sort((a, b) => a.path.localeCompare(b.path));
11624
+ return out;
11625
+ }
11626
+ async function walk2(rootDir, dir, out) {
11627
+ let entries;
11628
+ try {
11629
+ entries = await readdir8(dir);
11630
+ } catch {
11631
+ return;
11632
+ }
11633
+ for (const name of entries) {
11634
+ if (name === "completion-certificate.json") continue;
11635
+ const full = path39.join(dir, name);
11636
+ let s;
11637
+ try {
11638
+ s = await stat5(full);
11639
+ } catch {
11640
+ continue;
11641
+ }
11642
+ if (s.isDirectory()) {
11643
+ await walk2(rootDir, full, out);
11644
+ } else if (s.isFile()) {
11645
+ const buf = await readFile27(full);
11646
+ const hash = createHash("sha256").update(buf).digest("hex");
11647
+ const rel = path39.relative(rootDir, full).replace(/\\/g, "/");
11648
+ out.push({ path: rel, sha256: hash });
11649
+ }
11650
+ }
11651
+ }
11652
+
11653
+ // src/core/validators/prototyping/completionCertificate.ts
11654
+ init_utils();
11655
+ var CERT_REL_PATH = ".qfai/evidence/prototyping/completion-certificate.json";
11656
+ function isRecord13(value) {
11657
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11658
+ }
11659
+ async function isCompletionClaimed(root, config) {
11660
+ const evidenceRoot = path40.join(
11661
+ path40.dirname(path40.resolve(root, config.paths.specsDir)),
11662
+ "evidence"
11663
+ );
11664
+ let raw;
11665
+ try {
11666
+ raw = await readFile28(path40.join(evidenceRoot, "prototyping.json"), "utf-8");
11667
+ } catch {
11668
+ return false;
11669
+ }
11670
+ let parsed;
11671
+ try {
11672
+ parsed = JSON.parse(raw);
11673
+ } catch {
11674
+ return false;
11675
+ }
11676
+ if (!isRecord13(parsed)) return false;
11677
+ if (parsed.completionClaimed === true) return true;
11678
+ if (parsed.phase === "completed") return true;
11679
+ if (isRecord13(parsed.completionCertificate)) return true;
11680
+ return false;
11681
+ }
11682
+ async function validateCompletionCertificateIssues(root, config) {
11683
+ const certificate = await loadCompletionCertificate(root);
11684
+ const claimed = await isCompletionClaimed(root, config);
11685
+ if (!certificate) {
11686
+ if (claimed) {
11687
+ return [
11688
+ issue(
11689
+ "QFAI-PROT-335",
11690
+ `${CERT_REL_PATH} is required when completion is claimed. Run \`qfai prototyping certify\` after all gates pass.`,
11691
+ "error",
11692
+ CERT_REL_PATH,
11693
+ "prototyping.completionCertificate.presence",
11694
+ void 0,
11695
+ "canonical",
11696
+ "completion \u4E3B\u5F35\u306E\u524D\u306B `qfai prototyping certify` \u3092\u6210\u529F\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002"
11697
+ )
11698
+ ];
11699
+ }
11700
+ return [];
11701
+ }
11702
+ if (!claimed) return [];
11703
+ const result = await checkCompletionCertificate(root);
11704
+ if (result.ok) return [];
11705
+ return [
11706
+ issue(
11707
+ "QFAI-PROT-336",
11708
+ `${CERT_REL_PATH} digest mismatch \u2014 evidence has been modified since certify: ${result.reasons.join("; ")}`,
11709
+ "error",
11710
+ CERT_REL_PATH,
11711
+ "prototyping.completionCertificate.digest",
11712
+ void 0,
11713
+ "canonical",
11714
+ "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"
11715
+ )
11716
+ ];
11717
+ }
11718
+
11719
+ // src/core/validators/configReferenceIntegrity.ts
11720
+ init_utils();
11721
+ import { stat as stat6 } from "fs/promises";
11722
+ import path41 from "path";
11723
+ async function isDirectory(absolutePath) {
11724
+ try {
11725
+ const s = await stat6(absolutePath);
11726
+ return s.isDirectory();
11727
+ } catch {
11728
+ return false;
11729
+ }
11730
+ }
11731
+ async function pathExists(absolutePath) {
11732
+ try {
11733
+ await stat6(absolutePath);
11734
+ return true;
11735
+ } catch {
11736
+ return false;
11737
+ }
11738
+ }
11739
+ var VERIFIED_PATH_KEYS = [
11740
+ "specsDir",
11741
+ "contractsDir",
11742
+ "discussionDir",
11743
+ "skillsDir",
11744
+ "srcDir",
11745
+ "testsDir"
11746
+ ];
11747
+ async function validateConfigReferenceIntegrity(root, config) {
11748
+ const issues = [];
11749
+ const primarySpecId = config.prototyping?.primarySpecId;
11750
+ if (primarySpecId !== void 0) {
11751
+ const specDir = path41.join(path41.resolve(root, config.paths.specsDir), `spec-${primarySpecId}`);
11752
+ if (!await isDirectory(specDir)) {
11753
+ const relSpecDir = path41.relative(root, specDir).replace(/\\/g, "/");
11754
+ issues.push(
11755
+ issue(
11756
+ "QFAI-CFG-LINK-001",
11757
+ `qfai.config.yaml: prototyping.primarySpecId="${primarySpecId}" but ${relSpecDir} does not exist.`,
11758
+ "error",
11759
+ "qfai.config.yaml",
11760
+ "config.prototyping.primarySpecId.reality",
11761
+ void 0,
11762
+ "canonical",
11763
+ "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"
11764
+ )
11765
+ );
11766
+ }
11767
+ }
11768
+ for (const key of VERIFIED_PATH_KEYS) {
11769
+ const relPath = config.paths[key];
11770
+ const absolutePath = path41.resolve(root, relPath);
11771
+ if (!await isDirectory(absolutePath)) {
11772
+ issues.push(
11773
+ issue(
11774
+ "QFAI-CFG-LINK-002",
11775
+ `qfai.config.yaml: paths.${key}="${relPath}" but the directory does not exist.`,
11776
+ "warning",
11777
+ "qfai.config.yaml",
11778
+ `config.paths.${key}.reality`,
11779
+ void 0,
11780
+ "canonical",
11781
+ `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`
11782
+ )
11783
+ );
11784
+ }
11785
+ }
11786
+ const packPath = config.prototyping?.calibration?.packPath;
11787
+ if (packPath !== void 0) {
11788
+ const absolutePackPath = path41.resolve(root, packPath);
11789
+ const exists9 = await pathExists(absolutePackPath);
11790
+ if (!exists9) {
11791
+ issues.push(
11792
+ issue(
11793
+ "QFAI-CFG-LINK-003",
11794
+ `qfai.config.yaml: prototyping.calibration.packPath="${packPath}" but the path does not exist on disk.`,
11795
+ "error",
11796
+ "qfai.config.yaml",
11797
+ "config.prototyping.calibration.packPath.reality",
11798
+ void 0,
11799
+ "canonical",
11800
+ "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"
11801
+ )
11802
+ );
11803
+ }
11804
+ }
11805
+ return issues;
11806
+ }
11807
+
11808
+ // src/core/validators/prototyping/refIntegrity.ts
11809
+ import { readFile as readFile29, stat as stat7 } from "fs/promises";
11810
+ import path42 from "path";
11811
+ init_utils();
11812
+ function isRecord14(value) {
11813
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11814
+ }
11815
+ async function fileExists3(absolutePath) {
11816
+ try {
11817
+ const s = await stat7(absolutePath);
11818
+ return s.isFile();
11819
+ } catch {
11820
+ return false;
11821
+ }
11822
+ }
11823
+ async function loadJson(absolutePath) {
11824
+ try {
11825
+ const raw = await readFile29(absolutePath, "utf-8");
11826
+ return JSON.parse(raw);
11827
+ } catch {
11828
+ return null;
11829
+ }
11830
+ }
11831
+ function collectPrototypingJsonRefs(prototypingJson, sourcePath) {
11832
+ const out = [];
11833
+ if (!isRecord14(prototypingJson)) return out;
11834
+ const push = (ref, field) => {
11835
+ if (typeof ref === "string" && ref.length > 0) {
11836
+ out.push({ ref, source: sourcePath, field });
11837
+ }
11838
+ };
11839
+ const rounds = prototypingJson.rounds;
11840
+ if (Array.isArray(rounds)) {
11841
+ for (let i = 0; i < rounds.length; i++) {
11842
+ const round = rounds[i];
11843
+ if (!isRecord14(round)) continue;
11844
+ push(round.harvestRef, `rounds[${i}].harvestRef`);
11845
+ push(round.narrowDecisionRef, `rounds[${i}].narrowDecisionRef`);
11846
+ push(round.absorptionPlanRef, `rounds[${i}].absorptionPlanRef`);
11847
+ push(round.reimplementationRef, `rounds[${i}].reimplementationRef`);
11848
+ const evalMap = round.evaluatorReviewRefsByCandidate;
11849
+ if (isRecord14(evalMap)) {
11850
+ for (const [cid, ref] of Object.entries(evalMap)) {
11851
+ push(ref, `rounds[${i}].evaluatorReviewRefsByCandidate.${cid}`);
11852
+ }
11853
+ }
11854
+ const screenMap = round.screenEvidenceByCandidate;
11855
+ if (isRecord14(screenMap)) {
11856
+ for (const [cid, candidateScreens] of Object.entries(screenMap)) {
11857
+ if (!isRecord14(candidateScreens)) continue;
11858
+ for (const [screenId, refs] of Object.entries(candidateScreens)) {
11859
+ if (!isRecord14(refs)) continue;
11860
+ push(
11861
+ refs.screenshotRef,
11862
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.screenshotRef`
11863
+ );
11864
+ push(refs.htmlRef, `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.htmlRef`);
11865
+ push(
11866
+ refs.snapshotRef,
11867
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.snapshotRef`
11868
+ );
11869
+ push(
11870
+ refs.commandLogRef,
11871
+ `rounds[${i}].screenEvidenceByCandidate.${cid}.${screenId}.commandLogRef`
11872
+ );
11873
+ }
11874
+ }
11875
+ }
11876
+ }
11877
+ }
11878
+ const polishCycles = prototypingJson.polishCycles;
11879
+ if (Array.isArray(polishCycles)) {
11880
+ for (let i = 0; i < polishCycles.length; i++) {
11881
+ const cycle = polishCycles[i];
11882
+ if (!isRecord14(cycle)) continue;
11883
+ push(cycle.breakthroughRef, `polishCycles[${i}].breakthroughRef`);
11884
+ push(cycle.reviewerGateRef, `polishCycles[${i}].reviewerGateRef`);
11885
+ push(cycle.evaluatorReviewRef, `polishCycles[${i}].evaluatorReviewRef`);
11886
+ }
11887
+ }
11888
+ const bestOfHistory = prototypingJson.bestOfHistory;
11889
+ if (isRecord14(bestOfHistory)) {
11890
+ push(bestOfHistory.evidenceRef, "bestOfHistory.evidenceRef");
11891
+ }
11892
+ const breakthrough = prototypingJson.breakthrough;
11893
+ if (isRecord14(breakthrough)) {
11894
+ push(breakthrough.evidenceRef, "breakthrough.evidenceRef");
11895
+ }
11896
+ const reviewerGate = prototypingJson.reviewerGate;
11897
+ if (isRecord14(reviewerGate)) {
11898
+ push(reviewerGate.evidenceRef, "reviewerGate.evidenceRef");
11899
+ const signoff = reviewerGate.signoff;
11900
+ if (isRecord14(signoff)) {
11901
+ push(signoff.evidenceRef, "reviewerGate.signoff.evidenceRef");
11902
+ }
11903
+ }
11904
+ const fullHarness = prototypingJson.fullHarness;
11905
+ if (isRecord14(fullHarness) && Array.isArray(fullHarness.iterations)) {
11906
+ const iterations = fullHarness.iterations;
11907
+ for (let i = 0; i < iterations.length; i++) {
11908
+ const it = iterations[i];
11909
+ if (!isRecord14(it)) continue;
11910
+ const er = it.evidenceRefs;
11911
+ if (!isRecord14(er)) continue;
11912
+ for (const [key, refs] of Object.entries(er)) {
11913
+ if (!Array.isArray(refs)) continue;
11914
+ for (let j = 0; j < refs.length; j++) {
11915
+ const refValue = refs[j];
11916
+ push(refValue, `fullHarness.iterations[${i}].evidenceRefs.${key}[${j}]`);
11917
+ }
11918
+ }
11919
+ }
11920
+ }
11921
+ return out;
11922
+ }
11923
+ function collectReviewBundleRefs(bundle, sourcePath) {
11924
+ const out = [];
11925
+ if (!isRecord14(bundle)) return out;
11926
+ const push = (ref, field) => {
11927
+ if (typeof ref === "string" && ref.length > 0) {
11928
+ out.push({ ref, source: sourcePath, field });
11929
+ }
11930
+ };
11931
+ push(bundle.axisDefsRef, "axisDefsRef");
11932
+ push(bundle.designSystemChecklistRef, "designSystemChecklistRef");
11933
+ push(bundle.commandPlanRef, "commandPlanRef");
11934
+ const candidates = bundle.candidates;
11935
+ if (Array.isArray(candidates)) {
11936
+ for (let i = 0; i < candidates.length; i++) {
11937
+ const candidate = candidates[i];
11938
+ if (!isRecord14(candidate)) continue;
11939
+ push(candidate.conceptRef, `candidates[${i}].conceptRef`);
11940
+ push(candidate.previousEvaluatorReviewRef, `candidates[${i}].previousEvaluatorReviewRef`);
11941
+ }
11942
+ }
11943
+ return out;
11944
+ }
11945
+ function collectBreakthroughRefs(breakthroughJson, sourcePath) {
11946
+ const out = [];
11947
+ if (!isRecord14(breakthroughJson)) return out;
11948
+ const branchRefs = breakthroughJson.branchRefs;
11949
+ if (Array.isArray(branchRefs)) {
11950
+ for (let i = 0; i < branchRefs.length; i++) {
11951
+ const ref = branchRefs[i];
11952
+ if (typeof ref === "string" && ref.length > 0) {
11953
+ out.push({ ref, source: sourcePath, field: `branchRefs[${i}]` });
11954
+ }
11955
+ }
11956
+ }
11957
+ return out;
11958
+ }
11959
+ async function validatePrototypingArtifactRefIntegrity(root, config) {
11960
+ const issues = [];
11961
+ const evidenceRoot = path42.join(path42.dirname(resolvePath(root, config, "specsDir")), "evidence");
11962
+ const collectedRefs = [];
11963
+ const protoPath = path42.join(evidenceRoot, "prototyping.json");
11964
+ const protoJson = await loadJson(protoPath);
11965
+ if (protoJson) {
11966
+ const protoRel = path42.relative(root, protoPath).replace(/\\/g, "/");
11967
+ collectedRefs.push(...collectPrototypingJsonRefs(protoJson, protoRel));
11968
+ }
11969
+ for (const round of EXPLORATION_ROUNDS) {
11970
+ const bundlePath = path42.join(
11971
+ evidenceRoot,
11972
+ "prototyping",
11973
+ "rounds",
11974
+ round,
11975
+ "review-bundle.json"
11976
+ );
11977
+ const bundleJson = await loadJson(bundlePath);
11978
+ if (bundleJson) {
11979
+ const bundleRel = path42.relative(root, bundlePath).replace(/\\/g, "/");
11980
+ collectedRefs.push(...collectReviewBundleRefs(bundleJson, bundleRel));
11981
+ }
11982
+ }
11983
+ const breakthroughPath = path42.join(evidenceRoot, "prototyping", "breakthrough.json");
11984
+ const breakthroughJson = await loadJson(breakthroughPath);
11985
+ if (breakthroughJson) {
11986
+ const breakthroughRel = path42.relative(root, breakthroughPath).replace(/\\/g, "/");
11987
+ collectedRefs.push(...collectBreakthroughRefs(breakthroughJson, breakthroughRel));
11988
+ }
11989
+ for (const ref of collectedRefs) {
11990
+ const candidatePath = path42.isAbsolute(ref.ref) ? ref.ref : path42.join(root, ref.ref);
11991
+ if (!await fileExists3(candidatePath)) {
11992
+ issues.push(
11993
+ issue(
11994
+ "QFAI-PROT-REF-001",
11995
+ `${ref.source}: ${ref.field}="${ref.ref}" points to a file that does not exist on disk.`,
11996
+ "warning",
11997
+ ref.source,
11998
+ "prototyping.artifactRef.reality",
11999
+ void 0,
12000
+ "canonical",
12001
+ "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"
12002
+ )
12003
+ );
12004
+ }
12005
+ }
12006
+ return issues;
12007
+ }
12008
+
12009
+ // src/core/validators/prototyping/specIdLinkage.ts
12010
+ import { readFile as readFile30, stat as stat8 } from "fs/promises";
12011
+ import path43 from "path";
12012
+ init_utils();
12013
+ function isRecord15(value) {
12014
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12015
+ }
12016
+ async function isDirectory2(absolutePath) {
12017
+ try {
12018
+ const s = await stat8(absolutePath);
12019
+ return s.isDirectory();
12020
+ } catch {
12021
+ return false;
12022
+ }
12023
+ }
12024
+ async function loadJson2(absolutePath) {
12025
+ try {
12026
+ const raw = await readFile30(absolutePath, "utf-8");
12027
+ return JSON.parse(raw);
12028
+ } catch {
12029
+ return null;
12030
+ }
10449
12031
  }
10450
- async function validatePrototypingDesignSystem(root, config) {
10451
- const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
10452
- if (screens.length === 0) {
10453
- return [];
12032
+ function normalizeSpecId(raw) {
12033
+ if (typeof raw !== "string") return void 0;
12034
+ const trimmed = raw.trim();
12035
+ const direct = /^(\d{4})$/.exec(trimmed);
12036
+ if (direct?.[1]) return direct[1];
12037
+ const prefixed = /^spec-(\d{4})$/i.exec(trimmed);
12038
+ if (prefixed?.[1]) return prefixed[1];
12039
+ return void 0;
12040
+ }
12041
+ async function validateSpecIdLinkage(root, config) {
12042
+ const issues = [];
12043
+ const specsRoot = resolvePath(root, config, "specsDir");
12044
+ const evidenceRoot = path43.join(path43.dirname(specsRoot), "evidence");
12045
+ const specEntries = await collectSpecEntries(specsRoot);
12046
+ const knownSpecIds = new Set(specEntries.map((e) => e.specNumber));
12047
+ const protoPath = path43.join(evidenceRoot, "prototyping.json");
12048
+ const protoJson = await loadJson2(protoPath);
12049
+ if (isRecord15(protoJson)) {
12050
+ const protoRel = path43.relative(root, protoPath).replace(/\\/g, "/");
12051
+ const specs = protoJson.specs;
12052
+ if (Array.isArray(specs)) {
12053
+ for (let i = 0; i < specs.length; i++) {
12054
+ const entry = specs[i];
12055
+ if (!isRecord15(entry)) continue;
12056
+ const rawSpecId = entry.specId;
12057
+ if (rawSpecId === void 0) continue;
12058
+ const id = normalizeSpecId(rawSpecId);
12059
+ if (id === void 0) {
12060
+ issues.push(
12061
+ issue(
12062
+ "QFAI-PROT-LINK-001",
12063
+ `${protoRel}: specs[${i}].specId="${JSON.stringify(rawSpecId)}" is malformed (expected 4-digit "NNNN" or "spec-NNNN").`,
12064
+ "warning",
12065
+ protoRel,
12066
+ "prototyping.specs.idReality",
12067
+ void 0,
12068
+ "canonical",
12069
+ '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'
12070
+ )
12071
+ );
12072
+ continue;
12073
+ }
12074
+ if (!knownSpecIds.has(id)) {
12075
+ issues.push(
12076
+ issue(
12077
+ "QFAI-PROT-LINK-001",
12078
+ `${protoRel}: specs[${i}].specId="${JSON.stringify(rawSpecId)}" but spec-${id} does not exist under ${path43.relative(root, specsRoot).replace(/\\/g, "/")}/.`,
12079
+ "warning",
12080
+ protoRel,
12081
+ "prototyping.specs.idReality",
12082
+ void 0,
12083
+ "canonical",
12084
+ "\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"
12085
+ )
12086
+ );
12087
+ }
12088
+ }
12089
+ }
12090
+ const rounds = protoJson.rounds;
12091
+ if (Array.isArray(rounds)) {
12092
+ for (let i = 0; i < rounds.length; i++) {
12093
+ const round = rounds[i];
12094
+ if (!isRecord15(round)) continue;
12095
+ const roundName = round.round;
12096
+ const candidates = round.candidates;
12097
+ if (typeof roundName !== "string" || !Array.isArray(candidates)) continue;
12098
+ for (let j = 0; j < candidates.length; j++) {
12099
+ const candidate = candidates[j];
12100
+ if (!isRecord15(candidate)) continue;
12101
+ const candidateId = candidate.candidateId;
12102
+ if (typeof candidateId !== "string") continue;
12103
+ const candidateDir = path43.join(
12104
+ evidenceRoot,
12105
+ "prototyping",
12106
+ "rounds",
12107
+ roundName,
12108
+ "candidates",
12109
+ candidateId
12110
+ );
12111
+ if (!await isDirectory2(candidateDir)) {
12112
+ const relCandidateDir = path43.relative(root, candidateDir).replace(/\\/g, "/");
12113
+ issues.push(
12114
+ issue(
12115
+ "QFAI-PROT-LINK-003",
12116
+ `${protoRel}: rounds[${i}].candidates[${j}].candidateId="${candidateId}" but ${relCandidateDir} does not exist.`,
12117
+ "warning",
12118
+ protoRel,
12119
+ "prototyping.candidates.dirReality",
12120
+ void 0,
12121
+ "canonical",
12122
+ "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"
12123
+ )
12124
+ );
12125
+ }
12126
+ }
12127
+ }
12128
+ }
12129
+ const polishCycles = protoJson.polishCycles;
12130
+ if (Array.isArray(polishCycles)) {
12131
+ for (let i = 0; i < polishCycles.length; i++) {
12132
+ const cycle = polishCycles[i];
12133
+ if (!isRecord15(cycle)) continue;
12134
+ const cycleNum = cycle.cycle;
12135
+ if (typeof cycleNum !== "number" || cycleNum < 1) continue;
12136
+ const iterationDir = path43.join(evidenceRoot, "prototyping", "iterations", String(cycleNum));
12137
+ if (!await isDirectory2(iterationDir)) {
12138
+ const relIterationDir = path43.relative(root, iterationDir).replace(/\\/g, "/");
12139
+ issues.push(
12140
+ issue(
12141
+ "QFAI-PROT-LINK-004",
12142
+ `${protoRel}: polishCycles[${i}].cycle=${cycleNum} but ${relIterationDir} does not exist.`,
12143
+ "warning",
12144
+ protoRel,
12145
+ "prototyping.polishCycles.iterationDirReality",
12146
+ void 0,
12147
+ "canonical",
12148
+ "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"
12149
+ )
12150
+ );
12151
+ }
12152
+ }
12153
+ }
10454
12154
  }
10455
- const evidenceDir = resolveEvidenceRoot(root, config);
10456
- const artifact = await loadPrototypingArtifact(evidenceDir);
10457
- if (artifact.designSystemCompliancePresent) {
10458
- return [];
12155
+ for (const round of EXPLORATION_ROUNDS) {
12156
+ const bundlePath = path43.join(root, ...roundReviewBundlePath(round).split("/"));
12157
+ const bundleJson = await loadJson2(bundlePath);
12158
+ if (!isRecord15(bundleJson)) continue;
12159
+ const id = normalizeSpecId(bundleJson.spec);
12160
+ if (id !== void 0 && !knownSpecIds.has(id)) {
12161
+ const bundleRel = path43.relative(root, bundlePath).replace(/\\/g, "/");
12162
+ issues.push(
12163
+ issue(
12164
+ "QFAI-PROT-LINK-002",
12165
+ `${bundleRel}: spec="${String(bundleJson.spec)}" but spec-${id} does not exist under ${path43.relative(root, specsRoot).replace(/\\/g, "/")}/.`,
12166
+ "error",
12167
+ bundleRel,
12168
+ "prototyping.reviewBundle.specReality",
12169
+ void 0,
12170
+ "canonical",
12171
+ "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"
12172
+ )
12173
+ );
12174
+ }
10459
12175
  }
10460
- const designSystemPath = path36.join(
10461
- root,
10462
- config.paths.contractsDir,
10463
- "design",
10464
- "design-system.yaml"
10465
- );
10466
- const designSystemPresent = await fileExists2(designSystemPath);
10467
- const severity = designSystemPresent && artifact.mode === "full-harness" ? "error" : "warning";
10468
- return [toIssue(severity, artifact.fileName)];
12176
+ return issues;
10469
12177
  }
10470
12178
 
10471
12179
  // src/core/validators/requireIndex.ts
10472
- import { readFile as readFile26 } from "fs/promises";
10473
- import path37 from "path";
12180
+ import { readFile as readFile31 } from "fs/promises";
12181
+ import path44 from "path";
10474
12182
  init_utils();
10475
12183
 
10476
12184
  // src/core/validators/repositoryHygiene.ts
10477
- import { readdir as readdir8, stat as stat5 } from "fs/promises";
10478
- import path38 from "path";
12185
+ import { readdir as readdir9, stat as stat9 } from "fs/promises";
12186
+ import path45 from "path";
10479
12187
  init_utils();
10480
12188
  var LEGACY_DIR_RULES = [
10481
12189
  { legacy: "discussions", canonical: "discussion" },
@@ -10487,12 +12195,12 @@ var LEGACY_DIR_RULES = [
10487
12195
  ];
10488
12196
  var SUSPICIOUS_TEMPLATE_NAME_RE = /^(?:_?templates?|_?sample(?:s)?|sample-template)$/i;
10489
12197
  async function validateRepositoryHygiene(root, config) {
10490
- const qfaiRoot = path38.join(root, ".qfai");
12198
+ const qfaiRoot = path45.join(root, ".qfai");
10491
12199
  const specsRoot = resolvePath(root, config, "specsDir");
10492
12200
  const issues = [];
10493
12201
  for (const rule of LEGACY_DIR_RULES) {
10494
- const legacyPath = path38.join(qfaiRoot, rule.legacy);
10495
- if (!await isDirectory(legacyPath)) {
12202
+ const legacyPath = path45.join(qfaiRoot, rule.legacy);
12203
+ if (!await isDirectory3(legacyPath)) {
10496
12204
  continue;
10497
12205
  }
10498
12206
  issues.push(
@@ -10526,7 +12234,7 @@ async function validateRepositoryHygiene(root, config) {
10526
12234
  return issues;
10527
12235
  }
10528
12236
  async function collectSuspiciousTemplatePaths(root) {
10529
- if (!await isDirectory(root)) {
12237
+ if (!await isDirectory3(root)) {
10530
12238
  return [];
10531
12239
  }
10532
12240
  const matches = [];
@@ -10538,7 +12246,7 @@ async function collectSuspiciousTemplatePaths(root) {
10538
12246
  }
10539
12247
  let entries = [];
10540
12248
  try {
10541
- entries = await readdir8(current, {
12249
+ entries = await readdir9(current, {
10542
12250
  withFileTypes: true,
10543
12251
  encoding: "utf8"
10544
12252
  });
@@ -10546,10 +12254,10 @@ async function collectSuspiciousTemplatePaths(root) {
10546
12254
  continue;
10547
12255
  }
10548
12256
  for (const entry of entries) {
10549
- const absolute = path38.join(current, entry.name);
12257
+ const absolute = path45.join(current, entry.name);
10550
12258
  if (entry.isDirectory()) {
10551
12259
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(entry.name)) {
10552
- matches.push(toPosix(path38.relative(root, absolute)));
12260
+ matches.push(toPosix(path45.relative(root, absolute)));
10553
12261
  }
10554
12262
  queue.push(absolute);
10555
12263
  continue;
@@ -10557,17 +12265,17 @@ async function collectSuspiciousTemplatePaths(root) {
10557
12265
  if (!entry.isFile()) {
10558
12266
  continue;
10559
12267
  }
10560
- const baseName = path38.parse(entry.name).name;
12268
+ const baseName = path45.parse(entry.name).name;
10561
12269
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(baseName)) {
10562
- matches.push(toPosix(path38.relative(root, absolute)));
12270
+ matches.push(toPosix(path45.relative(root, absolute)));
10563
12271
  }
10564
12272
  }
10565
12273
  }
10566
12274
  return matches.sort((left, right) => left.localeCompare(right));
10567
12275
  }
10568
- async function isDirectory(target) {
12276
+ async function isDirectory3(target) {
10569
12277
  try {
10570
- return (await stat5(target)).isDirectory();
12278
+ return (await stat9(target)).isDirectory();
10571
12279
  } catch {
10572
12280
  return false;
10573
12281
  }
@@ -10577,7 +12285,7 @@ function toPosix(value) {
10577
12285
  }
10578
12286
 
10579
12287
  // src/core/validators/specSplitByCapability.ts
10580
- import path39 from "path";
12288
+ import path46 from "path";
10581
12289
  init_utils();
10582
12290
  var CAP_ID_RE2 = /\bCAP-\d{4}\b/g;
10583
12291
  async function validateSpecSplitByCapability(root, config) {
@@ -10589,8 +12297,8 @@ async function validateSpecSplitByCapability(root, config) {
10589
12297
  if (layeredEntries.length === 0) {
10590
12298
  return [];
10591
12299
  }
10592
- const policiesDir = layeredEntries[0]?.sharedDir ?? path39.join(specsRoot, "_policies");
10593
- const capabilitiesPath = path39.join(policiesDir, "03_Capabilities.md");
12300
+ const policiesDir = layeredEntries[0]?.sharedDir ?? path46.join(specsRoot, "_policies");
12301
+ const capabilitiesPath = path46.join(policiesDir, "03_Capabilities.md");
10594
12302
  const capabilityText = await readSafe(capabilitiesPath);
10595
12303
  const issues = [];
10596
12304
  if (!await exists5(capabilitiesPath)) {
@@ -10630,7 +12338,7 @@ async function validateSpecSplitByCapability(root, config) {
10630
12338
  );
10631
12339
  }
10632
12340
  const actualSpecIds = new Set(
10633
- layeredEntries.map((entry) => path39.basename(entry.dir).toLowerCase())
12341
+ layeredEntries.map((entry) => path46.basename(entry.dir).toLowerCase())
10634
12342
  );
10635
12343
  const expectedSpecIds = capIds.map((_, index) => `spec-${to4(index + 1)}`);
10636
12344
  const missingSpecIds = expectedSpecIds.filter((specId) => !actualSpecIds.has(specId));
@@ -10667,11 +12375,11 @@ async function validateSpecSplitByCapability(root, config) {
10667
12375
  continue;
10668
12376
  }
10669
12377
  const specId = `spec-${to4(index + 1)}`;
10670
- const entry = layeredEntries.find((value) => path39.basename(value.dir).toLowerCase() === specId);
12378
+ const entry = layeredEntries.find((value) => path46.basename(value.dir).toLowerCase() === specId);
10671
12379
  if (!entry) {
10672
12380
  continue;
10673
12381
  }
10674
- const specFilePath = path39.join(entry.dir, "01_Spec.md");
12382
+ const specFilePath = path46.join(entry.dir, "01_Spec.md");
10675
12383
  const specText = await readSafe(specFilePath);
10676
12384
  if (specText.trim().length === 0 || !specText.includes(capId)) {
10677
12385
  issues.push(
@@ -10690,8 +12398,8 @@ async function validateSpecSplitByCapability(root, config) {
10690
12398
  }
10691
12399
 
10692
12400
  // src/core/validators/statusInSpecs.ts
10693
- import { readFile as readFile27 } from "fs/promises";
10694
- import path40 from "path";
12401
+ import { readFile as readFile32 } from "fs/promises";
12402
+ import path47 from "path";
10695
12403
  init_utils();
10696
12404
  var STRONG_PATTERNS = [
10697
12405
  { label: "release_candidate:", pattern: /\brelease_candidate\s*:/i },
@@ -10749,32 +12457,32 @@ function hasMatch(text, pattern) {
10749
12457
  }
10750
12458
  async function readSafe11(filePath) {
10751
12459
  try {
10752
- return await readFile27(filePath, "utf-8");
12460
+ return await readFile32(filePath, "utf-8");
10753
12461
  } catch {
10754
12462
  return "";
10755
12463
  }
10756
12464
  }
10757
12465
  function isOpenQuestionsFile(filePath) {
10758
- return /open-questions\.md$/i.test(path40.basename(filePath));
12466
+ return /open-questions\.md$/i.test(path47.basename(filePath));
10759
12467
  }
10760
12468
  function isDecisionFile(filePath) {
10761
- return /decisions\.md$/i.test(path40.basename(filePath));
12469
+ return /decisions\.md$/i.test(path47.basename(filePath));
10762
12470
  }
10763
12471
 
10764
12472
  // src/core/validators/breakthroughEvidence.ts
10765
- import { readFile as readFile28 } from "fs/promises";
10766
- import path41 from "path";
12473
+ import { readFile as readFile33 } from "fs/promises";
12474
+ import path48 from "path";
10767
12475
  init_utils();
10768
12476
  function toPosixRelative(root, targetPath) {
10769
- return path41.relative(root, targetPath).replace(/\\/g, "/");
12477
+ return path48.relative(root, targetPath).replace(/\\/g, "/");
10770
12478
  }
10771
12479
  async function validateBreakthroughEvidence(root, config) {
10772
12480
  const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
10773
12481
  if (screens.length === 0) {
10774
12482
  return [];
10775
12483
  }
10776
- const evidenceRoot = path41.join(path41.dirname(resolvePath(root, config, "specsDir")), "evidence");
10777
- const evidencePath = path41.join(evidenceRoot, "breakthrough.json");
12484
+ const evidenceRoot = path48.join(path48.dirname(resolvePath(root, config, "specsDir")), "evidence");
12485
+ const evidencePath = path48.join(evidenceRoot, "breakthrough.json");
10778
12486
  const evidenceRelativePath = toPosixRelative(root, evidencePath);
10779
12487
  const raw = await readJsonFile2(evidencePath);
10780
12488
  if (raw.status === "missing") {
@@ -10890,7 +12598,7 @@ function isNonEmptyString2(value) {
10890
12598
  }
10891
12599
  async function readJsonFile2(filePath) {
10892
12600
  try {
10893
- const raw = await readFile28(filePath, "utf-8");
12601
+ const raw = await readFile33(filePath, "utf-8");
10894
12602
  const parsed = JSON.parse(raw);
10895
12603
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10896
12604
  return { status: "invalid" };
@@ -10898,7 +12606,7 @@ async function readJsonFile2(filePath) {
10898
12606
  return { status: "ok", value: parsed };
10899
12607
  } catch {
10900
12608
  try {
10901
- await readFile28(filePath, "utf-8");
12609
+ await readFile33(filePath, "utf-8");
10902
12610
  return { status: "invalid" };
10903
12611
  } catch {
10904
12612
  return { status: "missing" };
@@ -10907,8 +12615,8 @@ async function readJsonFile2(filePath) {
10907
12615
  }
10908
12616
 
10909
12617
  // src/core/validators/designToken.ts
10910
- import { readFile as readFile29 } from "fs/promises";
10911
- import path42 from "path";
12618
+ import { readFile as readFile34 } from "fs/promises";
12619
+ import path49 from "path";
10912
12620
  import fg3 from "fast-glob";
10913
12621
  import { parse as parseYaml7 } from "yaml";
10914
12622
 
@@ -10955,7 +12663,7 @@ function collectLayer(layer, layerName, target, errors) {
10955
12663
  }
10956
12664
  function flattenTokens(obj, prefix, target, errors) {
10957
12665
  for (const [key, value] of Object.entries(obj)) {
10958
- const path76 = `${prefix}.${key}`;
12666
+ const path84 = `${prefix}.${key}`;
10959
12667
  if (value && typeof value === "object" && !Array.isArray(value)) {
10960
12668
  const record2 = value;
10961
12669
  if ("$value" in record2) {
@@ -10971,9 +12679,9 @@ function flattenTokens(obj, prefix, target, errors) {
10971
12679
  if (typeof record2.platform === "string") {
10972
12680
  token.platform = record2.platform;
10973
12681
  }
10974
- target.set(path76, token);
12682
+ target.set(path84, token);
10975
12683
  } else {
10976
- flattenTokens(record2, path76, target, errors);
12684
+ flattenTokens(record2, path84, target, errors);
10977
12685
  }
10978
12686
  }
10979
12687
  }
@@ -10983,44 +12691,44 @@ function resolveAllReferences(result) {
10983
12691
  for (const [key, val] of result.primitives) allTokens.set(key, val);
10984
12692
  for (const [key, val] of result.semantics) allTokens.set(key, val);
10985
12693
  for (const [key, val] of result.components) allTokens.set(key, val);
10986
- for (const [path76] of allTokens) {
10987
- resolveTokenRef(path76, allTokens, /* @__PURE__ */ new Set(), 0, result);
12694
+ for (const [path84] of allTokens) {
12695
+ resolveTokenRef(path84, allTokens, /* @__PURE__ */ new Set(), 0, result);
10988
12696
  }
10989
12697
  }
10990
- function resolveTokenRef(path76, allTokens, visited, depth, result) {
10991
- if (result.resolved.has(path76)) {
10992
- return result.resolved.get(path76);
12698
+ function resolveTokenRef(path84, allTokens, visited, depth, result) {
12699
+ if (result.resolved.has(path84)) {
12700
+ return result.resolved.get(path84);
10993
12701
  }
10994
12702
  if (depth > MAX_RESOLVE_DEPTH) {
10995
12703
  result.errors.push({
10996
- message: `Max reference depth exceeded at: ${path76}`,
10997
- path: path76
12704
+ message: `Max reference depth exceeded at: ${path84}`,
12705
+ path: path84
10998
12706
  });
10999
12707
  return void 0;
11000
12708
  }
11001
- if (visited.has(path76)) {
12709
+ if (visited.has(path84)) {
11002
12710
  result.errors.push({
11003
- message: `Circular reference detected: ${path76}`,
11004
- path: path76
12711
+ message: `Circular reference detected: ${path84}`,
12712
+ path: path84
11005
12713
  });
11006
12714
  return void 0;
11007
12715
  }
11008
- const token = allTokens.get(path76);
12716
+ const token = allTokens.get(path84);
11009
12717
  if (!token) {
11010
12718
  return void 0;
11011
12719
  }
11012
12720
  if (typeof token.$value !== "string") {
11013
12721
  const rawValue2 = stringifyTokenValue(token.$value);
11014
- result.resolved.set(path76, rawValue2);
12722
+ result.resolved.set(path84, rawValue2);
11015
12723
  return rawValue2;
11016
12724
  }
11017
12725
  const rawValue = stringifyTokenValue(token.$value);
11018
12726
  const refs = [...rawValue.matchAll(REF_PATTERN)];
11019
12727
  if (refs.length === 0) {
11020
- result.resolved.set(path76, rawValue);
12728
+ result.resolved.set(path84, rawValue);
11021
12729
  return rawValue;
11022
12730
  }
11023
- visited.add(path76);
12731
+ visited.add(path84);
11024
12732
  let resolved = rawValue;
11025
12733
  for (const ref of refs) {
11026
12734
  const refPath = ref[1];
@@ -11028,8 +12736,8 @@ function resolveTokenRef(path76, allTokens, visited, depth, result) {
11028
12736
  const refToken = allTokens.get(refPath);
11029
12737
  if (!refToken) {
11030
12738
  result.errors.push({
11031
- message: `Unresolved token reference: {${refPath}} at ${path76}`,
11032
- path: path76
12739
+ message: `Unresolved token reference: {${refPath}} at ${path84}`,
12740
+ path: path84
11033
12741
  });
11034
12742
  continue;
11035
12743
  }
@@ -11038,7 +12746,7 @@ function resolveTokenRef(path76, allTokens, visited, depth, result) {
11038
12746
  resolved = resolved.split(`{${refPath}}`).join(refValue);
11039
12747
  }
11040
12748
  }
11041
- result.resolved.set(path76, resolved);
12749
+ result.resolved.set(path84, resolved);
11042
12750
  return resolved;
11043
12751
  }
11044
12752
  function stringifyTokenValue(value) {
@@ -11094,8 +12802,8 @@ var VALID_TYPES = [
11094
12802
  var VALID_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
11095
12803
  async function validateDesignToken(root, config) {
11096
12804
  const configuredDir = config.uiux?.designTokensDir;
11097
- const designDir = configuredDir ? path42.resolve(root, configuredDir) : path42.join(root, config.paths.contractsDir, "design");
11098
- const pattern = path42.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12805
+ const designDir = configuredDir ? path49.resolve(root, configuredDir) : path49.join(root, config.paths.contractsDir, "design");
12806
+ const pattern = path49.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
11099
12807
  const files = await fg3(pattern, {
11100
12808
  absolute: true,
11101
12809
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -11105,11 +12813,11 @@ async function validateDesignToken(root, config) {
11105
12813
  }
11106
12814
  const issues = [];
11107
12815
  for (const filePath of files) {
11108
- const rel = path42.relative(root, filePath).replace(/\\/g, "/");
12816
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
11109
12817
  let hasRootObjectError = false;
11110
12818
  let content;
11111
12819
  try {
11112
- content = await readFile29(filePath, "utf-8");
12820
+ content = await readFile34(filePath, "utf-8");
11113
12821
  } catch {
11114
12822
  issues.push(
11115
12823
  issue(
@@ -11272,8 +12980,8 @@ function normalizePlatform(value) {
11272
12980
  }
11273
12981
 
11274
12982
  // src/core/validators/htmlMock.ts
11275
- import { readFile as readFile30 } from "fs/promises";
11276
- import path43 from "path";
12983
+ import { readFile as readFile35 } from "fs/promises";
12984
+ import path50 from "path";
11277
12985
  import fg4 from "fast-glob";
11278
12986
 
11279
12987
  // src/core/uiux/contrastRatio.ts
@@ -11654,19 +13362,19 @@ async function validateHtmlMock(root, platform, config) {
11654
13362
  const startTime = performance.now();
11655
13363
  const budget = config.uiux?.htmlMockTimeout ?? 2e3;
11656
13364
  const patterns = [
11657
- path43.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
11658
- path43.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
13365
+ path50.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
13366
+ path50.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
11659
13367
  ];
11660
13368
  const files = await fg4(patterns, { absolute: true });
11661
13369
  const mockBlocks = [];
11662
13370
  for (const filePath of files) {
11663
13371
  let content;
11664
13372
  try {
11665
- content = await readFile30(filePath, "utf-8");
13373
+ content = await readFile35(filePath, "utf-8");
11666
13374
  } catch {
11667
13375
  continue;
11668
13376
  }
11669
- const rel = path43.relative(root, filePath).replace(/\\/g, "/");
13377
+ const rel = path50.relative(root, filePath).replace(/\\/g, "/");
11670
13378
  for (const block of collectHtmlMockBlocks(content)) {
11671
13379
  mockBlocks.push({ file: rel, html: block.html, rawBlock: block.rawBlock });
11672
13380
  }
@@ -11832,8 +13540,8 @@ async function validateHtmlMock(root, platform, config) {
11832
13540
  }
11833
13541
 
11834
13542
  // src/core/validators/mermaidScreenFlow.ts
11835
- import { readFile as readFile31 } from "fs/promises";
11836
- import path44 from "path";
13543
+ import { readFile as readFile36 } from "fs/promises";
13544
+ import path51 from "path";
11837
13545
  import fg5 from "fast-glob";
11838
13546
 
11839
13547
  // src/core/validators/mermaidUtils.ts
@@ -11887,19 +13595,19 @@ async function validateMermaidScreenFlow(root, config) {
11887
13595
  const specsRoot = resolvePath(root, config, "specsDir");
11888
13596
  const discussionRoot = resolvePath(root, config, "discussionDir");
11889
13597
  const latestDiscussionPackDir = await findLatestDiscussionPackDir(discussionRoot);
11890
- const patterns = [path44.posix.join(specsRoot.replace(/\\/g, "/"), "**/*.md")];
13598
+ const patterns = [path51.posix.join(specsRoot.replace(/\\/g, "/"), "**/*.md")];
11891
13599
  if (latestDiscussionPackDir) {
11892
- patterns.push(path44.posix.join(latestDiscussionPackDir.replace(/\\/g, "/"), "**/*.md"));
13600
+ patterns.push(path51.posix.join(latestDiscussionPackDir.replace(/\\/g, "/"), "**/*.md"));
11893
13601
  }
11894
13602
  const files = await fg5(patterns, { absolute: true });
11895
13603
  for (const filePath of files) {
11896
13604
  let content;
11897
13605
  try {
11898
- content = await readFile31(filePath, "utf-8");
13606
+ content = await readFile36(filePath, "utf-8");
11899
13607
  } catch {
11900
13608
  continue;
11901
13609
  }
11902
- const rel = path44.relative(root, filePath).replace(/\\/g, "/");
13610
+ const rel = path51.relative(root, filePath).replace(/\\/g, "/");
11903
13611
  const blocks = extractFencedCodeBlocks(content);
11904
13612
  for (const block of blocks) {
11905
13613
  if (block.language !== "mermaid") continue;
@@ -11998,8 +13706,8 @@ function parseTransitions(content) {
11998
13706
 
11999
13707
  // src/core/validators/bpApDb.ts
12000
13708
  init_utils();
12001
- import { readFile as readFile32 } from "fs/promises";
12002
- import path45 from "path";
13709
+ import { readFile as readFile37 } from "fs/promises";
13710
+ import path52 from "path";
12003
13711
  import fg6 from "fast-glob";
12004
13712
  import { parse as parseYaml8 } from "yaml";
12005
13713
  var BP_ID_RE = /^BP-\d{4}$/;
@@ -12036,9 +13744,9 @@ var AP_REQUIRED_FIELDS = [
12036
13744
  ];
12037
13745
  async function validateBpApDb(root, config) {
12038
13746
  const issues = [];
12039
- const designDir = path45.join(root, config.paths.contractsDir, "design");
12040
- const bpPattern = path45.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
12041
- const apPattern = path45.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
13747
+ const designDir = path52.join(root, config.paths.contractsDir, "design");
13748
+ const bpPattern = path52.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
13749
+ const apPattern = path52.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
12042
13750
  const globOptions = {
12043
13751
  absolute: true,
12044
13752
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12051,14 +13759,14 @@ async function validateBpApDb(root, config) {
12051
13759
  const seenBpIds = /* @__PURE__ */ new Set();
12052
13760
  const seenApIds = /* @__PURE__ */ new Set();
12053
13761
  for (const filePath of bpFiles) {
12054
- const rel = path45.relative(root, filePath).replace(/\\/g, "/");
13762
+ const rel = path52.relative(root, filePath).replace(/\\/g, "/");
12055
13763
  const entries = await parseRuleFile(filePath, rel, issues);
12056
13764
  for (const entry of entries) {
12057
13765
  validateBpEntry(entry, rel, seenBpIds, issues);
12058
13766
  }
12059
13767
  }
12060
13768
  for (const filePath of apFiles) {
12061
- const rel = path45.relative(root, filePath).replace(/\\/g, "/");
13769
+ const rel = path52.relative(root, filePath).replace(/\\/g, "/");
12062
13770
  const entries = await parseRuleFile(filePath, rel, issues);
12063
13771
  for (const entry of entries) {
12064
13772
  validateApEntry(entry, rel, seenApIds, issues);
@@ -12069,7 +13777,7 @@ async function validateBpApDb(root, config) {
12069
13777
  async function parseRuleFile(filePath, rel, issues) {
12070
13778
  let content;
12071
13779
  try {
12072
- content = await readFile32(filePath, "utf-8");
13780
+ content = await readFile37(filePath, "utf-8");
12073
13781
  } catch {
12074
13782
  issues.push(
12075
13783
  issue("QFAI-BPAP-001", `BP/AP file unreadable: ${rel}`, "error", rel, "bpApDb.readFile")
@@ -12234,8 +13942,8 @@ function toSafeString(value) {
12234
13942
 
12235
13943
  // src/core/validators/platformDetection.ts
12236
13944
  init_utils();
12237
- import { readFile as readFile33 } from "fs/promises";
12238
- import path46 from "path";
13945
+ import { readFile as readFile38 } from "fs/promises";
13946
+ import path53 from "path";
12239
13947
  var KNOWN_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
12240
13948
  async function detectPlatform(root, config, cliPlatform) {
12241
13949
  const issues = [];
@@ -12278,13 +13986,13 @@ async function detectPlatform(root, config, cliPlatform) {
12278
13986
  return { platform: "web", source: "fallback", issues };
12279
13987
  }
12280
13988
  async function inferPlatform(root, issues) {
12281
- if (await exists5(path46.join(root, "pubspec.yaml"))) {
12282
- const hasAndroid = await exists5(path46.join(root, "android"));
12283
- const hasIos = await exists5(path46.join(root, "ios"));
12284
- const hasWeb = await exists5(path46.join(root, "web"));
12285
- const hasWindows = await exists5(path46.join(root, "windows"));
12286
- const hasMacos = await exists5(path46.join(root, "macos"));
12287
- const hasLinux = await exists5(path46.join(root, "linux"));
13989
+ if (await exists5(path53.join(root, "pubspec.yaml"))) {
13990
+ const hasAndroid = await exists5(path53.join(root, "android"));
13991
+ const hasIos = await exists5(path53.join(root, "ios"));
13992
+ const hasWeb = await exists5(path53.join(root, "web"));
13993
+ const hasWindows = await exists5(path53.join(root, "windows"));
13994
+ const hasMacos = await exists5(path53.join(root, "macos"));
13995
+ const hasLinux = await exists5(path53.join(root, "linux"));
12288
13996
  const mobileTargets = [hasAndroid, hasIos].filter(Boolean).length;
12289
13997
  const desktopTargets = [hasWeb, hasWindows, hasMacos, hasLinux].filter(Boolean).length;
12290
13998
  if (mobileTargets + desktopTargets > 1) {
@@ -12304,10 +14012,10 @@ async function inferPlatform(root, issues) {
12304
14012
  }
12305
14013
  return null;
12306
14014
  }
12307
- const pkgJsonPath = path46.join(root, "package.json");
14015
+ const pkgJsonPath = path53.join(root, "package.json");
12308
14016
  if (await exists5(pkgJsonPath)) {
12309
14017
  try {
12310
- const raw = await readFile33(pkgJsonPath, "utf-8");
14018
+ const raw = await readFile38(pkgJsonPath, "utf-8");
12311
14019
  const pkg = JSON.parse(raw);
12312
14020
  const deps = {
12313
14021
  ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
@@ -12326,8 +14034,8 @@ async function inferPlatform(root, issues) {
12326
14034
  return "cross-platform";
12327
14035
  }
12328
14036
  if ("react-native" in deps) {
12329
- const hasAndroid = await exists5(path46.join(root, "android"));
12330
- const hasIos = await exists5(path46.join(root, "ios"));
14037
+ const hasAndroid = await exists5(path53.join(root, "android"));
14038
+ const hasIos = await exists5(path53.join(root, "ios"));
12331
14039
  if (hasAndroid && hasIos) {
12332
14040
  return "cross-platform";
12333
14041
  }
@@ -12349,16 +14057,16 @@ function normalizePlatformInput(platform) {
12349
14057
  }
12350
14058
 
12351
14059
  // src/core/validators/uiDefinitionConsistency.ts
12352
- import { readFile as readFile34 } from "fs/promises";
12353
- import path47 from "path";
14060
+ import { readFile as readFile39 } from "fs/promises";
14061
+ import path54 from "path";
12354
14062
  import fg7 from "fast-glob";
12355
14063
  import { parse as parseYaml9 } from "yaml";
12356
14064
  init_utils();
12357
14065
  async function validateUiDefinitionConsistency(root, config) {
12358
14066
  const issues = [];
12359
14067
  const configuredDir = config.uiux?.designTokensDir;
12360
- const designDir = configuredDir ? path47.resolve(root, configuredDir) : path47.join(root, config.paths.contractsDir, "design");
12361
- const tokenPattern = path47.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
14068
+ const designDir = configuredDir ? path54.resolve(root, configuredDir) : path54.join(root, config.paths.contractsDir, "design");
14069
+ const tokenPattern = path54.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12362
14070
  const tokenFiles = await fg7(tokenPattern, {
12363
14071
  absolute: true,
12364
14072
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12366,7 +14074,7 @@ async function validateUiDefinitionConsistency(root, config) {
12366
14074
  const resolvedTokens = /* @__PURE__ */ new Map();
12367
14075
  for (const tokenFile of tokenFiles) {
12368
14076
  try {
12369
- const content = await readFile34(tokenFile, "utf-8");
14077
+ const content = await readFile39(tokenFile, "utf-8");
12370
14078
  const result = parseDesignToken(content);
12371
14079
  for (const [key, val] of result.resolved) {
12372
14080
  resolvedTokens.set(key, val);
@@ -12374,13 +14082,13 @@ async function validateUiDefinitionConsistency(root, config) {
12374
14082
  } catch {
12375
14083
  }
12376
14084
  }
12377
- const uiContractDir = path47.join(root, config.paths.contractsDir, "ui");
12378
- const uiPattern = path47.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
14085
+ const uiContractDir = path54.join(root, config.paths.contractsDir, "ui");
14086
+ const uiPattern = path54.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
12379
14087
  const uiFiles = await fg7(uiPattern, { absolute: true });
12380
14088
  const contractScreenIds = /* @__PURE__ */ new Set();
12381
14089
  for (const uiFile of uiFiles) {
12382
14090
  try {
12383
- const content = await readFile34(uiFile, "utf-8");
14091
+ const content = await readFile39(uiFile, "utf-8");
12384
14092
  const parsed = parseYaml9(content);
12385
14093
  if (parsed && typeof parsed === "object") {
12386
14094
  const screens = parsed.screens;
@@ -12396,15 +14104,15 @@ async function validateUiDefinitionConsistency(root, config) {
12396
14104
  }
12397
14105
  }
12398
14106
  const mdPatterns = [
12399
- path47.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12400
- path47.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
14107
+ path54.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
14108
+ path54.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12401
14109
  ];
12402
14110
  const mdFiles = await fg7(mdPatterns, { absolute: true });
12403
14111
  const mockScreenIds = /* @__PURE__ */ new Set();
12404
14112
  for (const mdFile of mdFiles) {
12405
14113
  try {
12406
- const content = await readFile34(mdFile, "utf-8");
12407
- const rel = path47.relative(root, mdFile).replace(/\\/g, "/");
14114
+ const content = await readFile39(mdFile, "utf-8");
14115
+ const rel = path54.relative(root, mdFile).replace(/\\/g, "/");
12408
14116
  const htmlBlocks = collectHtmlMockBlocks(content);
12409
14117
  if (resolvedTokens.size > 0) {
12410
14118
  for (const htmlBlock of htmlBlocks) {
@@ -12464,8 +14172,8 @@ async function validateUiDefinitionConsistency(root, config) {
12464
14172
 
12465
14173
  // src/core/validators/researchSummary.ts
12466
14174
  init_utils();
12467
- import { readFile as readFile35 } from "fs/promises";
12468
- import path48 from "path";
14175
+ import { readFile as readFile40 } from "fs/promises";
14176
+ import path55 from "path";
12469
14177
  import fg8 from "fast-glob";
12470
14178
  var RESEARCH_SUMMARY_HEADING_RE = /^#{1,3}\s+Research\s+Summary/im;
12471
14179
  var SOURCE_ENTRY_RE = /^\s*-\s*id:\s*(\S+)/gm;
@@ -12473,17 +14181,17 @@ var REFLECTION_APPLY_RE = /action:\s*apply/i;
12473
14181
  var FULL_DATE_RE = /^\s+published:\s*["']?(\d{4}-\d{2}-\d{2})["']?/m;
12474
14182
  async function validateResearchSummary(root, config) {
12475
14183
  const issues = [];
12476
- const pattern = path48.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
14184
+ const pattern = path55.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
12477
14185
  const files = await fg8(pattern, { absolute: true });
12478
14186
  for (const filePath of files) {
12479
14187
  let content;
12480
14188
  try {
12481
- content = await readFile35(filePath, "utf-8");
14189
+ content = await readFile40(filePath, "utf-8");
12482
14190
  } catch {
12483
14191
  continue;
12484
14192
  }
12485
14193
  if (!RESEARCH_SUMMARY_HEADING_RE.test(content)) continue;
12486
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
14194
+ const rel = path55.relative(root, filePath).replace(/\\/g, "/");
12487
14195
  const section = extractResearchSummarySection(content);
12488
14196
  if (!section) continue;
12489
14197
  const sourceEntries = extractSourceEntries(section);
@@ -12715,8 +14423,8 @@ function resolveFreshnessReferenceNow() {
12715
14423
 
12716
14424
  // src/core/validators/agentDefinition.ts
12717
14425
  init_utils();
12718
- import { readFile as readFile36 } from "fs/promises";
12719
- import path49 from "path";
14426
+ import { readFile as readFile41 } from "fs/promises";
14427
+ import path56 from "path";
12720
14428
  import { parse as parseYaml10 } from "yaml";
12721
14429
  var REQUIRED_AGENT_SECTIONS = [
12722
14430
  "## Mission",
@@ -12728,11 +14436,11 @@ var REQUIRED_AGENT_SECTIONS = [
12728
14436
  ];
12729
14437
  async function validateAgentDefinition(root, _config) {
12730
14438
  const issues = [];
12731
- const steeringDir = path49.join(root, ".qfai", "assistant", "steering");
12732
- const agentsDir = path49.join(root, ".qfai", "assistant", "agents");
12733
- const catalogPath = path49.join(steeringDir, "agent-catalog.yml");
12734
- const routingPath = path49.join(steeringDir, "agent-routing.yml");
12735
- const profilesPath = path49.join(steeringDir, "review-profiles.yml");
14439
+ const steeringDir = path56.join(root, ".qfai", "assistant", "steering");
14440
+ const agentsDir = path56.join(root, ".qfai", "assistant", "agents");
14441
+ const catalogPath = path56.join(steeringDir, "agent-catalog.yml");
14442
+ const routingPath = path56.join(steeringDir, "agent-routing.yml");
14443
+ const profilesPath = path56.join(steeringDir, "review-profiles.yml");
12736
14444
  if (!await exists5(agentsDir) && !await exists5(catalogPath)) {
12737
14445
  return [];
12738
14446
  }
@@ -12741,7 +14449,7 @@ async function validateAgentDefinition(root, _config) {
12741
14449
  ["agent-routing.yml", "QFAI-AGENT-002"],
12742
14450
  ["review-profiles.yml", "QFAI-AGENT-003"]
12743
14451
  ]) {
12744
- const filePath = path49.join(steeringDir, fileName);
14452
+ const filePath = path56.join(steeringDir, fileName);
12745
14453
  if (!await exists5(filePath)) {
12746
14454
  issues.push(
12747
14455
  issue(
@@ -12766,7 +14474,7 @@ async function validateAgentDefinition(root, _config) {
12766
14474
  catalog.filter((agent) => agent.kind === "reviewer").map((agent) => agent.id)
12767
14475
  );
12768
14476
  for (const agent of catalog) {
12769
- const filePath = path49.join(agentsDir, `${agent.id}.md`);
14477
+ const filePath = path56.join(agentsDir, `${agent.id}.md`);
12770
14478
  const rel = `.qfai/assistant/agents/${agent.id}.md`;
12771
14479
  if (!await exists5(filePath)) {
12772
14480
  issues.push(
@@ -12780,7 +14488,7 @@ async function validateAgentDefinition(root, _config) {
12780
14488
  );
12781
14489
  continue;
12782
14490
  }
12783
- const content = await readFile36(filePath, "utf-8");
14491
+ const content = await readFile41(filePath, "utf-8");
12784
14492
  for (const heading of REQUIRED_AGENT_SECTIONS) {
12785
14493
  if (!content.includes(heading)) {
12786
14494
  issues.push(
@@ -12801,7 +14509,7 @@ async function validateAgentDefinition(root, _config) {
12801
14509
  }
12802
14510
  async function readCatalog(catalogPath, issues) {
12803
14511
  try {
12804
- const parsed = parseYaml10(await readFile36(catalogPath, "utf-8"));
14512
+ const parsed = parseYaml10(await readFile41(catalogPath, "utf-8"));
12805
14513
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12806
14514
  issues.push(
12807
14515
  issue(
@@ -12875,7 +14583,7 @@ async function readCatalog(catalogPath, issues) {
12875
14583
  }
12876
14584
  async function validateRouting(routingPath, catalogIds, issues) {
12877
14585
  try {
12878
- const parsed = parseYaml10(await readFile36(routingPath, "utf-8"));
14586
+ const parsed = parseYaml10(await readFile41(routingPath, "utf-8"));
12879
14587
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12880
14588
  issues.push(
12881
14589
  issue(
@@ -12991,7 +14699,7 @@ function validateAgentRefs(value, catalogIds, issues, skill, routeIndex, phaseIn
12991
14699
  }
12992
14700
  async function validateProfiles(profilesPath, reviewerIds, issues) {
12993
14701
  try {
12994
- const parsed = parseYaml10(await readFile36(profilesPath, "utf-8"));
14702
+ const parsed = parseYaml10(await readFile41(profilesPath, "utf-8"));
12995
14703
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12996
14704
  issues.push(
12997
14705
  issue(
@@ -13070,8 +14778,8 @@ function validateReviewerRefs(value, reviewerIds, issues, profileName, field) {
13070
14778
  }
13071
14779
 
13072
14780
  // src/core/validators/tddList.ts
13073
- import { readFile as readFile37, stat as stat6 } from "fs/promises";
13074
- import path50 from "path";
14781
+ import { readFile as readFile42, stat as stat10 } from "fs/promises";
14782
+ import path57 from "path";
13075
14783
  init_utils();
13076
14784
  var REQUIRED_COLUMNS = [
13077
14785
  "TDD-ID",
@@ -13086,7 +14794,7 @@ var REQUIRED_COLUMNS = [
13086
14794
  var VALID_STATUSES = /* @__PURE__ */ new Set(["todo", "red", "green", "refactor", "done", "exception"]);
13087
14795
  var TEST_FILE_CHECK_STATUSES = /* @__PURE__ */ new Set(["green", "refactor", "done"]);
13088
14796
  var TDD_ID_FORMAT = /^TDD-\d{4}$/;
13089
- var TDD_LIST_REL_PATH = path50.join("tdd", "test-list.md");
14797
+ var TDD_LIST_REL_PATH = path57.join("tdd", "test-list.md");
13090
14798
  async function validateTddList(root, config) {
13091
14799
  const specsRoot = resolvePath(root, config, "specsDir");
13092
14800
  const entries = await collectSpecEntries(specsRoot);
@@ -13098,8 +14806,8 @@ async function validateTddList(root, config) {
13098
14806
  return issues;
13099
14807
  }
13100
14808
  async function validateSpecTddList(root, specDir, specNumber) {
13101
- const filePath = path50.join(specDir, TDD_LIST_REL_PATH);
13102
- const relPath = path50.relative(root, filePath).replace(/\\/g, "/");
14809
+ const filePath = path57.join(specDir, TDD_LIST_REL_PATH);
14810
+ const relPath = path57.relative(root, filePath).replace(/\\/g, "/");
13103
14811
  const issues = [];
13104
14812
  if (!await exists5(filePath)) {
13105
14813
  issues.push(
@@ -13286,9 +14994,9 @@ async function validateSpecTddList(root, specDir, specNumber) {
13286
14994
  continue;
13287
14995
  }
13288
14996
  const normalized = testFile.replace(/\\/g, "/");
13289
- const resolved = path50.resolve(root, normalized);
13290
- const relative = path50.relative(root, resolved);
13291
- if (path50.isAbsolute(normalized) || path50.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + path50.sep)) {
14997
+ const resolved = path57.resolve(root, normalized);
14998
+ const relative = path57.relative(root, resolved);
14999
+ if (path57.isAbsolute(normalized) || path57.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + path57.sep)) {
13292
15000
  issues.push(
13293
15001
  issue(
13294
15002
  "TDDLIST_TEST_FILE_MISSING",
@@ -13302,7 +15010,7 @@ async function validateSpecTddList(root, specDir, specNumber) {
13302
15010
  }
13303
15011
  let isFile2 = false;
13304
15012
  try {
13305
- isFile2 = (await stat6(resolved)).isFile();
15013
+ isFile2 = (await stat10(resolved)).isFile();
13306
15014
  } catch {
13307
15015
  }
13308
15016
  if (!isFile2) {
@@ -13351,11 +15059,11 @@ async function validateSpecTddList(root, specDir, specNumber) {
13351
15059
  }
13352
15060
  async function collectTestCaseIds(specDir) {
13353
15061
  const empty = { knownTcIds: /* @__PURE__ */ new Set(), unitComponentTcIds: /* @__PURE__ */ new Set() };
13354
- const testCasesPath = path50.join(specDir, "06_Test-Cases.md");
15062
+ const testCasesPath = path57.join(specDir, "06_Test-Cases.md");
13355
15063
  if (!await exists5(testCasesPath)) return empty;
13356
15064
  let content;
13357
15065
  try {
13358
- content = await readFile37(testCasesPath, "utf-8");
15066
+ content = await readFile42(testCasesPath, "utf-8");
13359
15067
  } catch {
13360
15068
  return empty;
13361
15069
  }
@@ -13381,7 +15089,7 @@ async function collectTestCaseIds(specDir) {
13381
15089
  }
13382
15090
 
13383
15091
  // src/core/validators/navigationFlow.ts
13384
- import { readFile as readFile38 } from "fs/promises";
15092
+ import { readFile as readFile43 } from "fs/promises";
13385
15093
  init_utils();
13386
15094
  var NODE_DEF_RE = /([A-Za-z_][\w-]*)\s*(?:\[.*?\]|\(.*?\)|\{.*?\})/g;
13387
15095
  var EDGE_RE = /([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?\s*(?:--+>|==+>|-.->|~~>)\s*(?:\|"?([^"|]*)"?\|)?\s*([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?/g;
@@ -13661,7 +15369,7 @@ async function validateNavigationFlow(root, config) {
13661
15369
  const specFiles = allFiles.filter((f) => /[\\/]spec-\d{4}[\\/]/.test(f));
13662
15370
  const issues = [];
13663
15371
  for (const file of specFiles) {
13664
- const content = await readFile38(file, "utf-8");
15372
+ const content = await readFile43(file, "utf-8");
13665
15373
  const blocks = extractFencedCodeBlocks(content);
13666
15374
  const mermaidBlocks = blocks.filter((b) => b.language === "mermaid");
13667
15375
  const _flowchartBlocks = mermaidBlocks.filter((b) => hasFlowchartDeclaration(b.content));
@@ -13684,8 +15392,8 @@ async function validateNavigationFlow(root, config) {
13684
15392
 
13685
15393
  // src/core/validators/renderCritique.ts
13686
15394
  init_utils();
13687
- import path51 from "path";
13688
- import { readFile as readFile39 } from "fs/promises";
15395
+ import path58 from "path";
15396
+ import { readFile as readFile44 } from "fs/promises";
13689
15397
  import fg9 from "fast-glob";
13690
15398
  var RENDERED_KEYWORDS_RE = /\b(rendered|screenshot|html\b|preview|visual\s*review)/i;
13691
15399
  var SPEC_RE = /\b(01_spec|03_acceptance-criteria|spec-|\bspec\b)\b/i;
@@ -13709,11 +15417,11 @@ var FOUR_STATE_CHECK_RE = /\bfour_state_check\s*:/i;
13709
15417
  var MAX_PRIMARY_STEPS_RE = /\bmax_primary_steps\s*:\s*(\d+)/i;
13710
15418
  async function validateRenderCritique(root, config) {
13711
15419
  const issues = [];
13712
- const skillsDir = path51.join(root, config.paths.skillsDir).replace(/\\/g, "/");
13713
- const evidenceDir = path51.join(root, ".qfai", "evidence").replace(/\\/g, "/");
13714
- const skillPromptPattern = path51.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
15420
+ const skillsDir = path58.join(root, config.paths.skillsDir).replace(/\\/g, "/");
15421
+ const evidenceDir = path58.join(root, ".qfai", "evidence").replace(/\\/g, "/");
15422
+ const skillPromptPattern = path58.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
13715
15423
  const skillFiles = await fg9(skillPromptPattern, { dot: true });
13716
- const evidencePattern = path51.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
15424
+ const evidencePattern = path58.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
13717
15425
  const evidenceFiles = await fg9(evidencePattern, { dot: true });
13718
15426
  if (skillFiles.length === 0 && evidenceFiles.length === 0) return issues;
13719
15427
  const renderEvidenceViewports = await collectRenderEvidenceViewports(root);
@@ -13723,7 +15431,7 @@ async function validateRenderCritique(root, config) {
13723
15431
  issues.push(
13724
15432
  issue(
13725
15433
  "QFAI-CRIT-001",
13726
- `Skill prompt does not mention rendered/screenshot/HTML review: ${path51.relative(root, sf)}`,
15434
+ `Skill prompt does not mention rendered/screenshot/HTML review: ${path58.relative(root, sf)}`,
13727
15435
  "error",
13728
15436
  sf,
13729
15437
  "renderCritique.codeOnly",
@@ -13740,7 +15448,7 @@ async function validateRenderCritique(root, config) {
13740
15448
  issues.push(
13741
15449
  issue(
13742
15450
  "QFAI-CRIT-002",
13743
- `Downstream skill prompt missing canonical spec/contract references: ${path51.relative(root, sf)}`,
15451
+ `Downstream skill prompt missing canonical spec/contract references: ${path58.relative(root, sf)}`,
13744
15452
  "error",
13745
15453
  sf,
13746
15454
  "renderCritique.contractMissing",
@@ -13802,7 +15510,7 @@ async function validateRenderCritique(root, config) {
13802
15510
  issues.push(
13803
15511
  issue(
13804
15512
  "QFAI-CRIT-005",
13805
- `Read order missing required tokens (${missing.join(", ")}): ${path51.relative(root, sf)}`,
15513
+ `Read order missing required tokens (${missing.join(", ")}): ${path58.relative(root, sf)}`,
13806
15514
  "error",
13807
15515
  sf,
13808
15516
  "renderCritique.readOrder",
@@ -13830,7 +15538,7 @@ async function validateRenderCritique(root, config) {
13830
15538
  issues.push(
13831
15539
  issue(
13832
15540
  "QFAI-CRIT-006",
13833
- `Critique evidence incomplete (missing: ${missing.join(", ")}): ${path51.relative(root, ef)}`,
15541
+ `Critique evidence incomplete (missing: ${missing.join(", ")}): ${path58.relative(root, ef)}`,
13834
15542
  "error",
13835
15543
  ef,
13836
15544
  "renderCritique.incompleteEvidence",
@@ -13945,9 +15653,9 @@ async function collectContent(files) {
13945
15653
  return contents.join("\n---\n");
13946
15654
  }
13947
15655
  async function collectRenderEvidenceViewports(root) {
13948
- const prototypingJsonPath = path51.join(root, ".qfai", "evidence", "prototyping.json");
15656
+ const prototypingJsonPath = path58.join(root, ".qfai", "evidence", "prototyping.json");
13949
15657
  try {
13950
- const raw = await readFile39(prototypingJsonPath, "utf-8");
15658
+ const raw = await readFile44(prototypingJsonPath, "utf-8");
13951
15659
  const parsed = JSON.parse(raw);
13952
15660
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
13953
15661
  return /* @__PURE__ */ new Set();
@@ -13987,8 +15695,8 @@ async function collectRenderEvidenceViewports(root) {
13987
15695
 
13988
15696
  // src/core/validators/designFidelity.ts
13989
15697
  init_utils();
13990
- import { readFile as readFile40 } from "fs/promises";
13991
- import path52 from "path";
15698
+ import { readFile as readFile45 } from "fs/promises";
15699
+ import path59 from "path";
13992
15700
  import fg10 from "fast-glob";
13993
15701
  var SCORECARD_HEADING_RE = /^#{1,3}\s+Fidelity\s+Scorecard/im;
13994
15702
  var BASE_DIMENSIONS = ["hierarchy", "clarity", "accessibility", "responsive"];
@@ -14013,19 +15721,19 @@ async function validateDesignFidelity(root, config) {
14013
15721
  const evidenceDirs = [".qfai/evidence", ".qfai/review"];
14014
15722
  const allFiles = [];
14015
15723
  for (const dir of evidenceDirs) {
14016
- const pattern = path52.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
15724
+ const pattern = path59.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
14017
15725
  const files = await fg10(pattern, { absolute: true });
14018
15726
  allFiles.push(...files);
14019
15727
  }
14020
15728
  for (const filePath of allFiles) {
14021
15729
  let content;
14022
15730
  try {
14023
- content = await readFile40(filePath, "utf-8");
15731
+ content = await readFile45(filePath, "utf-8");
14024
15732
  } catch {
14025
15733
  continue;
14026
15734
  }
14027
15735
  if (!SCORECARD_HEADING_RE.test(content)) continue;
14028
- const rel = path52.relative(root, filePath).replace(/\\/g, "/");
15736
+ const rel = path59.relative(root, filePath).replace(/\\/g, "/");
14029
15737
  const section = extractScorecardSection(content);
14030
15738
  if (!section) continue;
14031
15739
  const dimensions = parseDimensions(section);
@@ -14318,7 +16026,7 @@ function hasAntiPatternMention(section, code) {
14318
16026
 
14319
16027
  // src/core/validators/discussionDesignHardening.ts
14320
16028
  init_surfaceType();
14321
- import path53 from "path";
16029
+ import path60 from "path";
14322
16030
  init_utils();
14323
16031
  var REQUIRED_SIDECARS = [
14324
16032
  "uiux/30_exploration_brief.md",
@@ -14333,14 +16041,14 @@ function canonicalIssue(code, message, severity, file, rule) {
14333
16041
  return issue(code, message, severity, file, rule, void 0, "canonical");
14334
16042
  }
14335
16043
  async function validateDiscussionDesignHardening(root, config) {
14336
- const discussionDir = path53.join(root, config.paths.discussionDir);
16044
+ const discussionDir = path60.join(root, config.paths.discussionDir);
14337
16045
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14338
16046
  if (!packRoot) return [];
14339
16047
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
14340
16048
  if (!uiBearing) return [];
14341
16049
  const issues = [];
14342
16050
  for (const relPath of REQUIRED_SIDECARS) {
14343
- const content = await readSafe(path53.join(packRoot, relPath));
16051
+ const content = await readSafe(path60.join(packRoot, relPath));
14344
16052
  if (!content) {
14345
16053
  issues.push(
14346
16054
  canonicalIssue(
@@ -14382,7 +16090,7 @@ async function validateDiscussionDesignHardening(root, config) {
14382
16090
  "UIX-VAL-EVAL-CALIBRATION"
14383
16091
  )
14384
16092
  );
14385
- const antiGoals = await readSafe(path53.join(packRoot, "uiux/32_design_anti_goals.md"));
16093
+ const antiGoals = await readSafe(path60.join(packRoot, "uiux/32_design_anti_goals.md"));
14386
16094
  if (antiGoals && !/^- /m.test(antiGoals)) {
14387
16095
  issues.push(
14388
16096
  canonicalIssue(
@@ -14394,7 +16102,7 @@ async function validateDiscussionDesignHardening(root, config) {
14394
16102
  )
14395
16103
  );
14396
16104
  }
14397
- const reviewBundle = await readSafe(path53.join(packRoot, "uiux/50_review_input_bundle.md"));
16105
+ const reviewBundle = await readSafe(path60.join(packRoot, "uiux/50_review_input_bundle.md"));
14398
16106
  if (reviewBundle && !/best-of-history/i.test(reviewBundle)) {
14399
16107
  issues.push(
14400
16108
  canonicalIssue(
@@ -14409,7 +16117,7 @@ async function validateDiscussionDesignHardening(root, config) {
14409
16117
  return issues;
14410
16118
  }
14411
16119
  async function validateSection(packRoot, relPath, headings, code) {
14412
- const content = await readSafe(path53.join(packRoot, relPath));
16120
+ const content = await readSafe(path60.join(packRoot, relPath));
14413
16121
  if (!content) {
14414
16122
  return [];
14415
16123
  }
@@ -14432,12 +16140,12 @@ async function validateSection(packRoot, relPath, headings, code) {
14432
16140
 
14433
16141
  // src/core/validators/designAudit.ts
14434
16142
  init_surfaceType();
14435
- import { readdir as readdir9 } from "fs/promises";
14436
- import path54 from "path";
16143
+ import { readdir as readdir10 } from "fs/promises";
16144
+ import path61 from "path";
14437
16145
  init_utils();
14438
16146
  var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
14439
16147
  function toPosixRelative2(root, targetPath) {
14440
- return path54.relative(root, targetPath).replace(/\\/g, "/");
16148
+ return path61.relative(root, targetPath).replace(/\\/g, "/");
14441
16149
  }
14442
16150
  function resolveAuditConfig(config) {
14443
16151
  const audit = config.uiux?.audit;
@@ -14559,19 +16267,19 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
14559
16267
  async function checkTokenDrift(root, auditConfig, cfg) {
14560
16268
  const findings = [];
14561
16269
  const configuredDir = cfg.uiux?.designTokensDir;
14562
- const tokensDir = configuredDir ? path54.resolve(root, configuredDir) : path54.join(root, cfg.paths.contractsDir, "design");
16270
+ const tokensDir = configuredDir ? path61.resolve(root, configuredDir) : path61.join(root, cfg.paths.contractsDir, "design");
14563
16271
  let hasTokenFiles = false;
14564
16272
  try {
14565
- const entries = await readdir9(tokensDir);
16273
+ const entries = await readdir10(tokensDir);
14566
16274
  hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
14567
16275
  } catch {
14568
16276
  return findings;
14569
16277
  }
14570
16278
  if (!hasTokenFiles) return findings;
14571
- const contractsUiDir = path54.join(root, cfg.paths.contractsDir, "ui");
16279
+ const contractsUiDir = path61.join(root, cfg.paths.contractsDir, "ui");
14572
16280
  let htmlFiles = [];
14573
16281
  try {
14574
- const entries = await readdir9(contractsUiDir);
16282
+ const entries = await readdir10(contractsUiDir);
14575
16283
  htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
14576
16284
  } catch {
14577
16285
  return findings;
@@ -14579,7 +16287,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
14579
16287
  let rawCount = 0;
14580
16288
  const sampleLiterals = [];
14581
16289
  for (const htmlFile of htmlFiles) {
14582
- const content = await readSafe(path54.join(contractsUiDir, htmlFile));
16290
+ const content = await readSafe(path61.join(contractsUiDir, htmlFile));
14583
16291
  if (!content) continue;
14584
16292
  const matches = content.match(RAW_COLOR_RE);
14585
16293
  if (matches) {
@@ -14630,14 +16338,14 @@ function deduplicateFindings(issues, maxPerRule) {
14630
16338
  async function validateDesignAudit(root, config) {
14631
16339
  const auditConfig = resolveAuditConfig(config);
14632
16340
  if (!auditConfig.enabled) return [];
14633
- const discussionDir = path54.join(root, config.paths.discussionDir);
16341
+ const discussionDir = path61.join(root, config.paths.discussionDir);
14634
16342
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14635
16343
  if (!packRoot) return [];
14636
16344
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
14637
16345
  if (!uiBearing) return [];
14638
- const contractsPath = path54.join(packRoot, "uiux", "40_screen_contracts.md");
16346
+ const contractsPath = path61.join(packRoot, "uiux", "40_screen_contracts.md");
14639
16347
  const contractsContent = await readSafe(contractsPath);
14640
- const selectedDirectionPath = path54.join(
16348
+ const selectedDirectionPath = path61.join(
14641
16349
  root,
14642
16350
  config.paths.contractsDir,
14643
16351
  "design",
@@ -14665,8 +16373,8 @@ async function validateDesignAudit(root, config) {
14665
16373
  // src/core/validators/designSlop.ts
14666
16374
  init_surfaceType();
14667
16375
  import { existsSync as existsSync2 } from "fs";
14668
- import { readFile as readFile41 } from "fs/promises";
14669
- import path55 from "path";
16376
+ import { readFile as readFile46 } from "fs/promises";
16377
+ import path62 from "path";
14670
16378
  import { fileURLToPath as fileURLToPath3 } from "url";
14671
16379
  init_utils();
14672
16380
  function isValidSlopPattern(rule) {
@@ -14675,7 +16383,7 @@ function isValidSlopPattern(rule) {
14675
16383
  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";
14676
16384
  }
14677
16385
  async function loadSlopPatterns(jsonPath) {
14678
- const raw = await readFile41(jsonPath, "utf-8");
16386
+ const raw = await readFile46(jsonPath, "utf-8");
14679
16387
  const parsed = JSON.parse(raw);
14680
16388
  if (!Array.isArray(parsed)) return [];
14681
16389
  return parsed.filter((r) => isValidSlopPattern(r));
@@ -14683,11 +16391,11 @@ async function loadSlopPatterns(jsonPath) {
14683
16391
  function defaultPatternsPath() {
14684
16392
  const base = import.meta.url;
14685
16393
  const basePath = base.startsWith("file:") ? fileURLToPath3(base) : base;
14686
- const baseDir = path55.dirname(basePath);
16394
+ const baseDir = path62.dirname(basePath);
14687
16395
  const candidates = [
14688
- path55.join(baseDir, "designSlopPatterns.json"),
14689
- path55.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
14690
- path55.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
16396
+ path62.join(baseDir, "designSlopPatterns.json"),
16397
+ path62.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
16398
+ path62.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
14691
16399
  ];
14692
16400
  for (const c of candidates) {
14693
16401
  if (existsSync2(c)) return c;
@@ -14698,7 +16406,7 @@ async function validateDesignSlop(root, config) {
14698
16406
  const auditConfig = resolveAuditConfig(config);
14699
16407
  if (!auditConfig.enabled) return [];
14700
16408
  if (!auditConfig.slopDetection) return [];
14701
- const discussionDir = path55.join(root, config.paths.discussionDir);
16409
+ const discussionDir = path62.join(root, config.paths.discussionDir);
14702
16410
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
14703
16411
  if (!packRoot) return [];
14704
16412
  const uiBearing = await isDiscussionUiBearingPack(packRoot);
@@ -14719,7 +16427,7 @@ async function validateDesignSlop(root, config) {
14719
16427
  continue;
14720
16428
  }
14721
16429
  for (const scope of pattern.scopes) {
14722
- const filePath = path55.join(packRoot, scope);
16430
+ const filePath = path62.join(packRoot, scope);
14723
16431
  const content = await readSafe(filePath);
14724
16432
  if (!content) continue;
14725
16433
  if (regex.test(content) && !seenRules.has(pattern.id)) {
@@ -14742,8 +16450,8 @@ async function validateDesignSlop(root, config) {
14742
16450
 
14743
16451
  // src/core/validators/designContractReadiness.ts
14744
16452
  init_utils();
14745
- import path56 from "path";
14746
- import { readFile as readFile42 } from "fs/promises";
16453
+ import path63 from "path";
16454
+ import { readFile as readFile47 } from "fs/promises";
14747
16455
  import fg11 from "fast-glob";
14748
16456
  import { parse as parseYaml11 } from "yaml";
14749
16457
  var REQUIRED_DESIGN_FILES = [
@@ -14754,24 +16462,24 @@ var REQUIRED_DESIGN_FILES = [
14754
16462
  "design-system.yaml"
14755
16463
  ];
14756
16464
  function toPosixRelative3(root, targetPath) {
14757
- return path56.relative(root, targetPath).replace(/\\/g, "/");
16465
+ return path63.relative(root, targetPath).replace(/\\/g, "/");
14758
16466
  }
14759
16467
  async function validateDesignContractReadiness(root, config) {
14760
- const uiPattern = path56.posix.join(
14761
- path56.join(root, config.paths.contractsDir, "ui").replace(/\\/g, "/"),
16468
+ const uiPattern = path63.posix.join(
16469
+ path63.join(root, config.paths.contractsDir, "ui").replace(/\\/g, "/"),
14762
16470
  "**/*.yaml"
14763
16471
  );
14764
16472
  const uiContracts = await fg11(uiPattern, { absolute: true });
14765
16473
  if (uiContracts.length === 0) {
14766
16474
  return [];
14767
16475
  }
14768
- const designDir = path56.join(root, config.paths.contractsDir, "design");
16476
+ const designDir = path63.join(root, config.paths.contractsDir, "design");
14769
16477
  const designDirRelative = toPosixRelative3(root, designDir);
14770
16478
  const issues = [];
14771
16479
  for (const fileName of REQUIRED_DESIGN_FILES) {
14772
- const filePath = path56.join(designDir, fileName);
16480
+ const filePath = path63.join(designDir, fileName);
14773
16481
  try {
14774
- await readFile42(filePath, "utf-8");
16482
+ await readFile47(filePath, "utf-8");
14775
16483
  } catch {
14776
16484
  issues.push(
14777
16485
  issue(
@@ -14795,7 +16503,7 @@ async function validateDesignContractReadiness(root, config) {
14795
16503
  return issues;
14796
16504
  }
14797
16505
  async function validateExplorationBrief(root, config) {
14798
- const filePath = path56.join(root, config.paths.contractsDir, "design", "exploration-brief.yaml");
16506
+ const filePath = path63.join(root, config.paths.contractsDir, "design", "exploration-brief.yaml");
14799
16507
  const parsed = await readYaml(filePath);
14800
16508
  if (parsed.kind !== "ok") {
14801
16509
  return parsed.kind === "invalid" ? [
@@ -14826,7 +16534,7 @@ async function validateExplorationBrief(root, config) {
14826
16534
  );
14827
16535
  }
14828
16536
  async function validateEvaluationRubric(root, config) {
14829
- const filePath = path56.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
16537
+ const filePath = path63.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
14830
16538
  const parsed = await readYaml(filePath);
14831
16539
  if (parsed.kind !== "ok") {
14832
16540
  return parsed.kind === "invalid" ? [
@@ -14857,7 +16565,7 @@ async function validateEvaluationRubric(root, config) {
14857
16565
  return issues;
14858
16566
  }
14859
16567
  async function validateEvaluatorCalibration(root, config) {
14860
- const filePath = path56.join(
16568
+ const filePath = path63.join(
14861
16569
  root,
14862
16570
  config.paths.contractsDir,
14863
16571
  "design",
@@ -14891,7 +16599,7 @@ async function validateEvaluatorCalibration(root, config) {
14891
16599
  );
14892
16600
  }
14893
16601
  async function validateSelectedDirection(root, config) {
14894
- const filePath = path56.join(root, config.paths.contractsDir, "design", "selected-direction.yaml");
16602
+ const filePath = path63.join(root, config.paths.contractsDir, "design", "selected-direction.yaml");
14895
16603
  const parsed = await readYaml(filePath);
14896
16604
  if (parsed.kind !== "ok") {
14897
16605
  return parsed.kind === "invalid" ? [
@@ -14929,7 +16637,7 @@ async function validateSelectedDirection(root, config) {
14929
16637
  return issues;
14930
16638
  }
14931
16639
  async function validateDesignSystem(root, config) {
14932
- const filePath = path56.join(root, config.paths.contractsDir, "design", "design-system.yaml");
16640
+ const filePath = path63.join(root, config.paths.contractsDir, "design", "design-system.yaml");
14933
16641
  const parsed = await readYaml(filePath);
14934
16642
  if (parsed.kind !== "ok") {
14935
16643
  return parsed.kind === "invalid" ? [
@@ -14987,14 +16695,14 @@ function isNonEmptyStringValue(value) {
14987
16695
  }
14988
16696
  async function readYaml(filePath) {
14989
16697
  try {
14990
- const parsed = parseYaml11(await readFile42(filePath, "utf-8"));
16698
+ const parsed = parseYaml11(await readFile47(filePath, "utf-8"));
14991
16699
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14992
16700
  return { kind: "invalid" };
14993
16701
  }
14994
16702
  return { kind: "ok", value: parsed };
14995
16703
  } catch {
14996
16704
  try {
14997
- await readFile42(filePath, "utf-8");
16705
+ await readFile47(filePath, "utf-8");
14998
16706
  return { kind: "invalid" };
14999
16707
  } catch {
15000
16708
  return { kind: "missing" };
@@ -15002,14 +16710,184 @@ async function readYaml(filePath) {
15002
16710
  }
15003
16711
  }
15004
16712
 
16713
+ // src/core/validators/evaluatorReviewHardFloor.ts
16714
+ import { readFile as readFile48, readdir as readdir11 } from "fs/promises";
16715
+ import path64 from "path";
16716
+ import { parse as parseYaml12 } from "yaml";
16717
+ init_utils();
16718
+ var ISSUE_CODE = "QFAI-PROT-AXIS-FLOOR-001";
16719
+ var RULE = "evaluatorReviewHardFloor.perAxisScore";
16720
+ function isRecord16(value) {
16721
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16722
+ }
16723
+ function toPosixRelative4(root, target) {
16724
+ return path64.relative(root, target).replace(/\\/g, "/");
16725
+ }
16726
+ function isEnoentError(error) {
16727
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
16728
+ }
16729
+ async function readYamlObject(filePath) {
16730
+ let raw;
16731
+ try {
16732
+ raw = await readFile48(filePath, "utf-8");
16733
+ } catch (error) {
16734
+ if (isEnoentError(error)) {
16735
+ return { kind: "missing" };
16736
+ }
16737
+ return { kind: "invalid" };
16738
+ }
16739
+ try {
16740
+ const parsed = parseYaml12(raw);
16741
+ return isRecord16(parsed) ? { kind: "ok", value: parsed } : { kind: "invalid" };
16742
+ } catch {
16743
+ return { kind: "invalid" };
16744
+ }
16745
+ }
16746
+ async function readJsonObject(filePath) {
16747
+ let raw;
16748
+ try {
16749
+ raw = await readFile48(filePath, "utf-8");
16750
+ } catch (error) {
16751
+ if (isEnoentError(error)) {
16752
+ return { kind: "missing" };
16753
+ }
16754
+ return { kind: "invalid" };
16755
+ }
16756
+ try {
16757
+ const parsed = JSON.parse(raw);
16758
+ return isRecord16(parsed) ? { kind: "ok", value: parsed } : { kind: "invalid" };
16759
+ } catch {
16760
+ return { kind: "invalid" };
16761
+ }
16762
+ }
16763
+ function loadHardFloors(rubric) {
16764
+ const map = /* @__PURE__ */ new Map();
16765
+ const entries = rubric.hard_floors;
16766
+ if (!Array.isArray(entries)) {
16767
+ return map;
16768
+ }
16769
+ for (const entry of entries) {
16770
+ if (!isRecord16(entry)) {
16771
+ continue;
16772
+ }
16773
+ const id = entry.id;
16774
+ const minScore = entry.min_score;
16775
+ if (typeof id !== "string" || id.trim().length === 0 || typeof minScore !== "number" || !Number.isFinite(minScore)) {
16776
+ continue;
16777
+ }
16778
+ map.set(id, minScore);
16779
+ }
16780
+ return map;
16781
+ }
16782
+ async function listCandidateReviewFiles(reviewsDirAbsolute) {
16783
+ try {
16784
+ const names = await readdir11(reviewsDirAbsolute);
16785
+ return names.filter((name) => name.endsWith(".json"));
16786
+ } catch (error) {
16787
+ if (isEnoentError(error)) {
16788
+ return null;
16789
+ }
16790
+ throw error;
16791
+ }
16792
+ }
16793
+ function extractCandidateId(fileName) {
16794
+ return fileName.replace(/\.json$/i, "");
16795
+ }
16796
+ function evaluatePerAxis(review, hardFloors) {
16797
+ const perAxis = review.perAxis;
16798
+ if (!Array.isArray(perAxis)) {
16799
+ return [];
16800
+ }
16801
+ const violations = [];
16802
+ for (const axis of perAxis) {
16803
+ if (!isRecord16(axis)) {
16804
+ continue;
16805
+ }
16806
+ const axisId = axis.axisId;
16807
+ const score = axis.score;
16808
+ if (typeof axisId !== "string" || axisId.trim().length === 0 || typeof score !== "number" || !Number.isFinite(score)) {
16809
+ continue;
16810
+ }
16811
+ const minScore = hardFloors.get(axisId);
16812
+ if (minScore === void 0) {
16813
+ continue;
16814
+ }
16815
+ if (score < minScore) {
16816
+ violations.push({ axisId, score, minScore });
16817
+ }
16818
+ }
16819
+ return violations;
16820
+ }
16821
+ async function checkRound(root, round, hardFloors) {
16822
+ const reviewsDirRelative = roundEvaluatorReviewsDir(round);
16823
+ const reviewsDirAbsolute = path64.join(root, reviewsDirRelative);
16824
+ const files = await listCandidateReviewFiles(reviewsDirAbsolute);
16825
+ if (files === null || files.length === 0) {
16826
+ return [];
16827
+ }
16828
+ const issues = [];
16829
+ for (const fileName of files) {
16830
+ const candidateId = extractCandidateId(fileName);
16831
+ const reviewPathAbsolute = path64.join(reviewsDirAbsolute, fileName);
16832
+ const result = await readJsonObject(reviewPathAbsolute);
16833
+ if (result.kind !== "ok") {
16834
+ continue;
16835
+ }
16836
+ const violations = evaluatePerAxis(result.value, hardFloors);
16837
+ const reviewPathRelative = toPosixRelative4(root, reviewPathAbsolute);
16838
+ for (const violation of violations) {
16839
+ issues.push(
16840
+ issue(
16841
+ ISSUE_CODE,
16842
+ `Candidate ${candidateId} axis ${violation.axisId} score ${violation.score} is below hard_floors min_score ${violation.minScore} in round ${round}.`,
16843
+ "error",
16844
+ reviewPathRelative,
16845
+ RULE,
16846
+ [
16847
+ round,
16848
+ candidateId,
16849
+ violation.axisId,
16850
+ String(violation.score),
16851
+ String(violation.minScore)
16852
+ ],
16853
+ "canonical",
16854
+ `${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`
16855
+ )
16856
+ );
16857
+ }
16858
+ }
16859
+ return issues;
16860
+ }
16861
+ async function validateEvaluatorReviewHardFloor(root, config) {
16862
+ const screens = await readUiContractScreenContracts(root, config.paths.contractsDir);
16863
+ if (screens.length === 0) {
16864
+ return [];
16865
+ }
16866
+ const rubricPath = path64.join(root, config.paths.contractsDir, "design", "evaluation-rubric.yaml");
16867
+ const rubric = await readYamlObject(rubricPath);
16868
+ if (rubric.kind !== "ok") {
16869
+ return [];
16870
+ }
16871
+ const hardFloors = loadHardFloors(rubric.value);
16872
+ if (hardFloors.size === 0) {
16873
+ return [];
16874
+ }
16875
+ const issues = [];
16876
+ for (const round of ABSORPTION_ROUNDS) {
16877
+ const roundIssues = await checkRound(root, round, hardFloors);
16878
+ issues.push(...roundIssues);
16879
+ }
16880
+ return issues;
16881
+ }
16882
+
15005
16883
  // src/core/validators/uiEvidenceArtifacts.ts
15006
- import path57 from "path";
16884
+ import path65 from "path";
15007
16885
  init_utils();
15008
16886
  function resolveEvidenceRoot2(root, config) {
15009
- return path57.join(path57.dirname(resolvePath(root, config, "specsDir")), "evidence");
16887
+ return path65.join(path65.dirname(resolvePath(root, config, "specsDir")), "evidence");
15010
16888
  }
15011
- function toPosixRelative4(root, targetPath) {
15012
- return path57.relative(root, targetPath).replace(/\\/g, "/");
16889
+ function toPosixRelative5(root, targetPath) {
16890
+ return path65.relative(root, targetPath).replace(/\\/g, "/");
15013
16891
  }
15014
16892
  var SAFE_SCREEN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
15015
16893
  async function validateUiEvidenceArtifacts(root, config) {
@@ -15019,8 +16897,8 @@ async function validateUiEvidenceArtifacts(root, config) {
15019
16897
  return issues;
15020
16898
  }
15021
16899
  const evidenceRoot = resolveEvidenceRoot2(root, config);
15022
- const screenshotRoot = path57.join(evidenceRoot, "prototyping", "screenshots");
15023
- const htmlRoot = path57.join(evidenceRoot, "prototyping", "html");
16900
+ const screenshotRoot = path65.join(evidenceRoot, "prototyping", "screenshots");
16901
+ const htmlRoot = path65.join(evidenceRoot, "prototyping", "html");
15024
16902
  for (const screen of screens) {
15025
16903
  if (!SAFE_SCREEN_ID_PATTERN.test(screen.screenId)) {
15026
16904
  issues.push(
@@ -15037,11 +16915,11 @@ async function validateUiEvidenceArtifacts(root, config) {
15037
16915
  );
15038
16916
  continue;
15039
16917
  }
15040
- const screenshotPath = path57.join(screenshotRoot, `${screen.screenId}.png`);
15041
- const htmlPath = path57.join(htmlRoot, `${screen.screenId}.html`);
16918
+ const screenshotPath = path65.join(screenshotRoot, `${screen.screenId}.png`);
16919
+ const htmlPath = path65.join(htmlRoot, `${screen.screenId}.html`);
15042
16920
  if (!await exists5(screenshotPath)) {
15043
- const screenshotPattern = path57.posix.join(
15044
- toPosixRelative4(root, screenshotRoot),
16921
+ const screenshotPattern = path65.posix.join(
16922
+ toPosixRelative5(root, screenshotRoot),
15045
16923
  "<screen-id>.png"
15046
16924
  );
15047
16925
  issues.push(
@@ -15049,7 +16927,7 @@ async function validateUiEvidenceArtifacts(root, config) {
15049
16927
  "QFAI-UIE-001",
15050
16928
  `Missing screenshot evidence for declared screen "${screen.screenId}".`,
15051
16929
  "error",
15052
- toPosixRelative4(root, screenshotPath),
16930
+ toPosixRelative5(root, screenshotPath),
15053
16931
  "uiEvidenceArtifacts.screenshotRequired",
15054
16932
  [screen.sourceRef],
15055
16933
  "canonical",
@@ -15058,13 +16936,13 @@ async function validateUiEvidenceArtifacts(root, config) {
15058
16936
  );
15059
16937
  }
15060
16938
  if (!await exists5(htmlPath)) {
15061
- const htmlPattern = path57.posix.join(toPosixRelative4(root, htmlRoot), "<screen-id>.html");
16939
+ const htmlPattern = path65.posix.join(toPosixRelative5(root, htmlRoot), "<screen-id>.html");
15062
16940
  issues.push(
15063
16941
  issue(
15064
16942
  "QFAI-UIE-002",
15065
16943
  `Missing HTML snapshot evidence for declared screen "${screen.screenId}".`,
15066
16944
  "error",
15067
- toPosixRelative4(root, htmlPath),
16945
+ toPosixRelative5(root, htmlPath),
15068
16946
  "uiEvidenceArtifacts.htmlRequired",
15069
16947
  [screen.sourceRef],
15070
16948
  "canonical",
@@ -15083,8 +16961,8 @@ async function isUiBearingSpec(root) {
15083
16961
  }
15084
16962
 
15085
16963
  // src/core/validators/uix/threeLayer.ts
15086
- import { readdir as readdir10 } from "fs/promises";
15087
- import path58 from "path";
16964
+ import { readdir as readdir12 } from "fs/promises";
16965
+ import path66 from "path";
15088
16966
  init_utils();
15089
16967
  var EXPLORATION_SECTIONS = [
15090
16968
  "product_intent",
@@ -15139,7 +17017,7 @@ async function validateThreeLayerModel(root, _config) {
15139
17017
  if (!await isUiBearingSpec(root)) return [];
15140
17018
  const issues = [];
15141
17019
  for (const sidecar of CANONICAL_REQUIRED_SIDECAR_FILES) {
15142
- const content = await readSafe(path58.join(root, "uiux", sidecar));
17020
+ const content = await readSafe(path66.join(root, "uiux", sidecar));
15143
17021
  if (!content) {
15144
17022
  continue;
15145
17023
  }
@@ -15181,7 +17059,7 @@ async function validateForbiddenLegacyFiles(root, _config) {
15181
17059
  if (!await isUiBearingSpec(root)) return [];
15182
17060
  let entries = [];
15183
17061
  try {
15184
- entries = await readdir10(path58.join(root, "uiux"));
17062
+ entries = await readdir12(path66.join(root, "uiux"));
15185
17063
  } catch {
15186
17064
  return [];
15187
17065
  }
@@ -15197,11 +17075,11 @@ async function validateForbiddenLegacyFiles(root, _config) {
15197
17075
  }
15198
17076
  async function validateThreeLayerFamilyCompleteness(root, _config) {
15199
17077
  if (!await isUiBearingSpec(root)) return [];
15200
- const indexContent = await readSafe(path58.join(root, "uiux", "00_index.md"));
17078
+ const indexContent = await readSafe(path66.join(root, "uiux", "00_index.md"));
15201
17079
  if (!indexContent) return [];
15202
17080
  const issues = [];
15203
17081
  for (const required of CANONICAL_REQUIRED_SIDECAR_FILES) {
15204
- const content = await readSafe(path58.join(root, "uiux", required));
17082
+ const content = await readSafe(path66.join(root, "uiux", required));
15205
17083
  if (!content) {
15206
17084
  issues.push(
15207
17085
  threeLayerIssue(
@@ -15218,7 +17096,7 @@ async function validateThreeLayerFamilyCompleteness(root, _config) {
15218
17096
  }
15219
17097
 
15220
17098
  // src/core/validators/uix/taste.ts
15221
- import path59 from "path";
17099
+ import path67 from "path";
15222
17100
  init_utils();
15223
17101
  var REQUIRED_SECTIONS = [
15224
17102
  "visual_character",
@@ -15235,12 +17113,12 @@ var REQUIRED_SECTIONS = [
15235
17113
  var REQUIRED_SECTION_COUNT = REQUIRED_SECTIONS.length;
15236
17114
 
15237
17115
  // src/core/validators/uix/trendScan.ts
15238
- import path60 from "path";
17116
+ import path68 from "path";
15239
17117
  init_utils();
15240
17118
 
15241
17119
  // src/core/validators/uix/strategy.ts
15242
17120
  init_surface();
15243
- import path61 from "path";
17121
+ import path69 from "path";
15244
17122
 
15245
17123
  // src/core/domain/strategyDecision.ts
15246
17124
  var CANONICAL_STRATEGY_DECISIONS = [
@@ -15257,9 +17135,9 @@ var CANONICAL_STRATEGY_DECISION_SET = new Set(CANONICAL_STRATEGY_DECISIONS);
15257
17135
  init_utils();
15258
17136
 
15259
17137
  // src/core/validators/uix/screenContract.ts
15260
- import path62 from "path";
17138
+ import path70 from "path";
15261
17139
  init_utils();
15262
- var REQUIRED_FIELDS = [
17140
+ var REQUIRED_FIELDS2 = [
15263
17141
  "screen_id",
15264
17142
  "route",
15265
17143
  "purpose",
@@ -15388,7 +17266,7 @@ function assignNestedChild(block, key, value) {
15388
17266
  }
15389
17267
  async function validateScreenContractSchema(root, _config) {
15390
17268
  if (!await isUiBearingSpec(root)) return [];
15391
- const contractsPath = path62.join(root, "uiux", "40_screen_contracts.md");
17269
+ const contractsPath = path70.join(root, "uiux", "40_screen_contracts.md");
15392
17270
  const content = await readSafe(contractsPath);
15393
17271
  if (!content) return [];
15394
17272
  const screens = parseScreenBlocks2(content);
@@ -15424,7 +17302,7 @@ async function validateScreenContractSchema(root, _config) {
15424
17302
  );
15425
17303
  continue;
15426
17304
  }
15427
- const missing = REQUIRED_FIELDS.filter((f) => {
17305
+ const missing = REQUIRED_FIELDS2.filter((f) => {
15428
17306
  if (f === "primary_tasks") return screen.primaryTasks.length === 0;
15429
17307
  if (f === "secondary_tasks") return screen.secondaryTasks.length === 0;
15430
17308
  if (f === "required_states") return Object.keys(screen.requiredStates).length === 0;
@@ -15462,12 +17340,12 @@ async function validateScreenContractSchema(root, _config) {
15462
17340
 
15463
17341
  // src/core/validators/uix/canonical.ts
15464
17342
  init_utils();
15465
- import path67 from "path";
17343
+ import path75 from "path";
15466
17344
 
15467
17345
  // src/core/validators/uix/classification.ts
15468
17346
  init_surfaceType();
15469
17347
  init_utils();
15470
- import path63 from "path";
17348
+ import path71 from "path";
15471
17349
  var VALID_PRIMARY_SURFACES = [
15472
17350
  "web",
15473
17351
  "mobile",
@@ -15488,7 +17366,7 @@ function classificationIssue(code, message, severity, file, suggestedAction) {
15488
17366
  };
15489
17367
  }
15490
17368
  async function validateClassification(root, _config) {
15491
- const contextPath = path63.join(root, "01_Context.md");
17369
+ const contextPath = path71.join(root, "01_Context.md");
15492
17370
  const content = await readSafe(contextPath);
15493
17371
  if (!content) {
15494
17372
  return [];
@@ -15666,8 +17544,8 @@ async function validateClassification(root, _config) {
15666
17544
  }
15667
17545
 
15668
17546
  // src/core/validators/uix/foundation.ts
15669
- import { readdir as readdir11 } from "fs/promises";
15670
- import path64 from "path";
17547
+ import { readdir as readdir13 } from "fs/promises";
17548
+ import path72 from "path";
15671
17549
  function canonicalIssue2(code, message, severity, file, suggestedAction) {
15672
17550
  return {
15673
17551
  code,
@@ -15681,7 +17559,7 @@ function canonicalIssue2(code, message, severity, file, suggestedAction) {
15681
17559
  async function validateSidecarMissing(root, _config) {
15682
17560
  if (!await isUiBearingSpec(root)) return [];
15683
17561
  try {
15684
- await readdir11(path64.join(root, "uiux"));
17562
+ await readdir13(path72.join(root, "uiux"));
15685
17563
  return [];
15686
17564
  } catch (err) {
15687
17565
  if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
@@ -15700,7 +17578,7 @@ async function validateSidecarMissing(root, _config) {
15700
17578
  }
15701
17579
 
15702
17580
  // src/core/validators/uix/comparisonValidator.ts
15703
- import path65 from "path";
17581
+ import path73 from "path";
15704
17582
  init_utils();
15705
17583
  function canonicalIssue3(code, message, severity, file, suggestedAction) {
15706
17584
  return {
@@ -15715,10 +17593,10 @@ function canonicalIssue3(code, message, severity, file, suggestedAction) {
15715
17593
  async function validateExplorationArtifacts(root, _config) {
15716
17594
  if (!await isUiBearingSpec(root)) return [];
15717
17595
  const issues = [];
15718
- const briefPath = path65.join(root, "uiux", "30_exploration_brief.md");
15719
- const rubricPath = path65.join(root, "uiux", "33_exploration_rubric.md");
15720
- const calibrationPath = path65.join(root, "uiux", "34_evaluator_calibration.md");
15721
- const reviewBundlePath = path65.join(root, "uiux", "50_review_input_bundle.md");
17596
+ const briefPath = path73.join(root, "uiux", "30_exploration_brief.md");
17597
+ const rubricPath = path73.join(root, "uiux", "33_exploration_rubric.md");
17598
+ const calibrationPath = path73.join(root, "uiux", "34_evaluator_calibration.md");
17599
+ const reviewBundlePath = path73.join(root, "uiux", "50_review_input_bundle.md");
15722
17600
  const briefContent = await readSafe(briefPath);
15723
17601
  if (!briefContent) {
15724
17602
  issues.push(
@@ -15818,7 +17696,7 @@ async function validateExplorationArtifacts(root, _config) {
15818
17696
  }
15819
17697
 
15820
17698
  // src/core/validators/uix/oqClosure.ts
15821
- import path66 from "path";
17699
+ import path74 from "path";
15822
17700
  init_utils();
15823
17701
  function canonicalIssue4(code, message, severity, file, suggestedAction) {
15824
17702
  return {
@@ -15832,8 +17710,8 @@ function canonicalIssue4(code, message, severity, file, suggestedAction) {
15832
17710
  }
15833
17711
  async function validateOqClosure(root, _config) {
15834
17712
  if (!await isUiBearingSpec(root)) return [];
15835
- const rootOqPath = path66.join(root, "11_OQ-Register.md");
15836
- const uiuxOqPath = path66.join(root, "uiux", "11_OQ-Register.md");
17713
+ const rootOqPath = path74.join(root, "11_OQ-Register.md");
17714
+ const uiuxOqPath = path74.join(root, "uiux", "11_OQ-Register.md");
15837
17715
  const rootContent = await readSafe(rootOqPath);
15838
17716
  const content = rootContent || await readSafe(uiuxOqPath);
15839
17717
  if (!content) return [];
@@ -15885,7 +17763,7 @@ async function validateOqClosure(root, _config) {
15885
17763
 
15886
17764
  // src/core/validators/uix/canonical.ts
15887
17765
  async function runCanonicalUixValidators(root, config) {
15888
- const directSpec = await readSafe(path67.join(root, "01_Spec.md"));
17766
+ const directSpec = await readSafe(path75.join(root, "01_Spec.md"));
15889
17767
  if (!directSpec) {
15890
17768
  return [];
15891
17769
  }
@@ -15912,8 +17790,8 @@ async function runCanonicalUixValidators(root, config) {
15912
17790
 
15913
17791
  // src/core/validators/traceabilityIntegrity.ts
15914
17792
  import { execFileSync } from "child_process";
15915
- import { readFile as readFile43 } from "fs/promises";
15916
- import path68 from "path";
17793
+ import { readFile as readFile49 } from "fs/promises";
17794
+ import path76 from "path";
15917
17795
  init_utils();
15918
17796
  var BR_AC_FILES = /* @__PURE__ */ new Set(["04_Business-Rules.md", "03_Acceptance-Criteria.md"]);
15919
17797
  function normalizePath2(p) {
@@ -15997,10 +17875,10 @@ async function validateTraceabilityIntegrity(root, config) {
15997
17875
  const specsRelDir = config.paths.specsDir;
15998
17876
  const changedSpecIds = findChangedSpecDirs(changedFiles, specsRelDir);
15999
17877
  for (const specId of changedSpecIds) {
16000
- const ledgerPath = path68.join(specsDir, specId, "16_Traceability-ledger.md");
17878
+ const ledgerPath = path76.join(specsDir, specId, "16_Traceability-ledger.md");
16001
17879
  let ledgerContent;
16002
17880
  try {
16003
- ledgerContent = await readFile43(ledgerPath, "utf-8");
17881
+ ledgerContent = await readFile49(ledgerPath, "utf-8");
16004
17882
  } catch {
16005
17883
  issues.push(
16006
17884
  issue(
@@ -16256,16 +18134,187 @@ function validatePrototypingSkillContent(content) {
16256
18134
  };
16257
18135
  }
16258
18136
 
18137
+ // src/core/validators/testTodoStubs.ts
18138
+ import { readFile as readFile50 } from "fs/promises";
18139
+ import path77 from "path";
18140
+ init_utils();
18141
+ var TEST_TODO_PATTERN = /\b(it|test|describe)\.todo\s*\(/g;
18142
+ async function validateTestTodoStubs(root, config) {
18143
+ if (!config.validation.testStrategy.forbidTestTodoStubs) {
18144
+ return [];
18145
+ }
18146
+ const globs = config.validation.traceability.testFileGlobs;
18147
+ if (globs.length === 0) {
18148
+ return [];
18149
+ }
18150
+ const excludeGlobs = Array.from(
18151
+ /* @__PURE__ */ new Set([
18152
+ ...DEFAULT_TEST_FILE_EXCLUDE_GLOBS,
18153
+ ...config.validation.traceability.testFileExcludeGlobs
18154
+ ])
18155
+ );
18156
+ const { files } = await collectFilesByGlobs(root, {
18157
+ globs,
18158
+ ignore: excludeGlobs,
18159
+ limit: DEFAULT_GLOB_FILE_LIMIT
18160
+ });
18161
+ const issues = [];
18162
+ for (const absFile of files) {
18163
+ const relFile = path77.relative(root, absFile).replace(/\\/g, "/");
18164
+ let content;
18165
+ try {
18166
+ content = await readFile50(absFile, "utf-8");
18167
+ } catch {
18168
+ continue;
18169
+ }
18170
+ const lines = content.split(/\r?\n/);
18171
+ for (let i = 0; i < lines.length; i += 1) {
18172
+ const line = lines[i] ?? "";
18173
+ const lineNumber = i + 1;
18174
+ for (const match of line.matchAll(TEST_TODO_PATTERN)) {
18175
+ const matchedKind = match[1];
18176
+ const stubIssue = issue(
18177
+ "QFAI-TEST-001",
18178
+ `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).`,
18179
+ "error",
18180
+ relFile,
18181
+ "validation.testStrategy.forbidTestTodoStubs",
18182
+ [`${matchedKind}.todo`],
18183
+ "canonical",
18184
+ "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."
18185
+ );
18186
+ stubIssue.loc = { line: lineNumber };
18187
+ issues.push(stubIssue);
18188
+ }
18189
+ }
18190
+ }
18191
+ return issues;
18192
+ }
18193
+
16259
18194
  // src/core/validate.ts
16260
18195
  init_utils();
16261
18196
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
16262
18197
  async function validateProject(root, configResult, options = {}) {
16263
18198
  const resolved = configResult ?? await loadConfig(root);
16264
18199
  const { config, issues: configIssues } = resolved;
16265
- const phase = options.phase ?? "full";
16266
- const atddCodeTraceabilityIssues = phase === "refinement" ? [] : await validateAtddCodeTraceability(root, config);
18200
+ const profile = options.profile ?? "full";
18201
+ const findings = [
18202
+ ...configIssues,
18203
+ ...await runProfileValidators(root, config, profile, options.platform)
18204
+ ];
18205
+ const { issues, waivers } = await applyWaivers(root, findings);
18206
+ const specsRoot = resolvePath(root, config, "specsDir");
18207
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
18208
+ const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
18209
+ const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
18210
+ root,
18211
+ config.validation.traceability.testFileGlobs,
18212
+ config.validation.traceability.testFileExcludeGlobs
18213
+ );
18214
+ const scCoverage = buildScCoverage(scIds, scTestRefs);
18215
+ const toolVersion = await resolveToolVersion();
18216
+ return {
18217
+ toolVersion,
18218
+ profile,
18219
+ issues,
18220
+ counts: countIssues(issues),
18221
+ traceability: {
18222
+ sc: scCoverage,
18223
+ testFiles
18224
+ },
18225
+ waivers
18226
+ };
18227
+ }
18228
+ async function runProfileValidators(root, config, profile, platformOption) {
18229
+ switch (profile) {
18230
+ case "discussion":
18231
+ return runDiscussionValidators(root, config);
18232
+ case "sdd":
18233
+ return runSddValidators(root, config);
18234
+ case "prototyping":
18235
+ return runPrototypingValidators(root, config, platformOption);
18236
+ case "atdd":
18237
+ return runAtddValidators(root, config);
18238
+ case "tdd":
18239
+ return runTddValidators(root, config);
18240
+ case "verify":
18241
+ case "full":
18242
+ return runFullValidators(root, config, platformOption);
18243
+ }
18244
+ }
18245
+ async function runDiscussionValidators(root, config) {
18246
+ return [
18247
+ ...await validateDiscussionMermaid(root),
18248
+ ...await validateDiscussionPackReadiness(root, config),
18249
+ ...await validateDiscussionVisuals(root),
18250
+ ...await validateDiscussionDesignHardening(root, config),
18251
+ ...await validateResearchSummary(root, config),
18252
+ ...await runCanonicalUixValidators(root, config)
18253
+ ];
18254
+ }
18255
+ async function runSddValidators(root, config, includeCodeReferences = false) {
18256
+ return [
18257
+ ...await validateMermaidEnforcement(root),
18258
+ ...await validateSpecPacks(root, config),
18259
+ ...await validateStatusInSpecs(root, config),
18260
+ ...await validateDensityHints(root, config),
18261
+ ...await validateSpecSplitByCapability(root, config),
18262
+ ...await validateLayeredTraceability(root, config),
18263
+ ...await validateOrphanProhibition(root, config),
18264
+ ...await validateLayerCoverage(root, config),
18265
+ ...await validateContractReferences(root, config),
18266
+ ...await validateTraceability(root, config, { includeCodeReferences }),
18267
+ ...await validateDefinedIds(root, config),
18268
+ ...await validateContracts(root, config),
18269
+ ...await validateNavigationFlow(root, config)
18270
+ ];
18271
+ }
18272
+ async function runPrototypingValidators(root, config, platformOption) {
18273
+ return [
18274
+ ...await runUiuxValidators(root, config, platformOption),
18275
+ ...await validatePrototypingEvidence(root, config),
18276
+ ...await validateBreakthroughEvidence(root, config),
18277
+ ...await validatePrototypingDesignSystem(root, config),
18278
+ ...await validateUiEvidenceArtifacts(root, config),
18279
+ ...await validateRenderCritique(root, config),
18280
+ ...await validateDesignFidelity(root, config),
18281
+ ...await validateDesignContractReadiness(root, config),
18282
+ ...await validateEvaluatorReviewHardFloor(root, config),
18283
+ ...await validateStateGate(root, config),
18284
+ ...await validateCompletionCertificateIssues(root, config),
18285
+ ...await validateConfigReferenceIntegrity(root, config),
18286
+ ...await validatePrototypingArtifactRefIntegrity(root, config),
18287
+ ...await validateSpecIdLinkage(root, config)
18288
+ ];
18289
+ }
18290
+ async function runAtddValidators(root, config) {
18291
+ return [...await validateAtddCodeTraceability(root, config)];
18292
+ }
18293
+ async function runTddValidators(root, config, includeTraceability = true) {
18294
+ return [
18295
+ ...await validateTddList(root, config),
18296
+ ...await validateTestTodoStubs(root, config),
18297
+ ...includeTraceability ? await validateTraceability(root, config, { includeCodeReferences: true }) : [],
18298
+ ...await validateTraceabilityIntegrity(root, config)
18299
+ ];
18300
+ }
18301
+ async function runFullValidators(root, config, platformOption) {
18302
+ return [
18303
+ ...await validateRepositoryHygiene(root, config),
18304
+ ...await validateSkillsIntegrity(root, config),
18305
+ ...await validateAssistantAssets(root, config),
18306
+ ...await runDiscussionValidators(root, config),
18307
+ ...await runSddValidators(root, config, true),
18308
+ ...await validateReviewArtifacts(root),
18309
+ ...await runPrototypingValidators(root, config, platformOption),
18310
+ ...await runAtddValidators(root, config),
18311
+ ...await runTddValidators(root, config, false),
18312
+ ...await validatePrototypingSkill(root, config)
18313
+ ];
18314
+ }
18315
+ async function runUiuxValidators(root, config, platformOption) {
16267
18316
  const uiuxStart = performance.now();
16268
- const platformResult = await detectPlatform(root, config, options.platform);
18317
+ const platformResult = await detectPlatform(root, config, platformOption);
16269
18318
  const platform = platformResult.platform;
16270
18319
  const uiuxValidators = [
16271
18320
  () => validateDesignToken(root, config),
@@ -16291,68 +18340,13 @@ async function validateProject(root, configResult, options = {}) {
16291
18340
  rule: "uiux.performanceBudget"
16292
18341
  });
16293
18342
  }
18343
+ return uiuxIssues;
18344
+ }
18345
+ async function validatePrototypingSkill(root, config) {
16294
18346
  const skillsDir = resolvePath(root, config, "skillsDir");
16295
- const prototypingSkillPath = path69.join(skillsDir, "qfai-prototyping", "SKILL.md");
18347
+ const prototypingSkillPath = path78.join(skillsDir, "qfai-prototyping", "SKILL.md");
16296
18348
  const prototypingSkillContent = await readSafe(prototypingSkillPath);
16297
- const prototypingSkillResult = prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent) : { issues: [] };
16298
- const findings = [
16299
- ...configIssues,
16300
- ...await validateRepositoryHygiene(root, config),
16301
- ...await validateSkillsIntegrity(root, config),
16302
- ...await validateAssistantAssets(root, config),
16303
- ...await validateDiscussionMermaid(root),
16304
- ...await validateMermaidEnforcement(root),
16305
- ...await validateSpecPacks(root, config),
16306
- ...await validateDiscussionPackReadiness(root, config),
16307
- ...await validateDiscussionVisuals(root),
16308
- ...await validateStatusInSpecs(root, config),
16309
- ...await validateDensityHints(root, config),
16310
- ...await validateReviewArtifacts(root),
16311
- ...await validatePrototypingEvidence(root, config),
16312
- ...await validateBreakthroughEvidence(root, config),
16313
- ...await validatePrototypingDesignSystem(root, config),
16314
- ...await validateUiEvidenceArtifacts(root, config),
16315
- ...await validateSpecSplitByCapability(root, config),
16316
- ...await validateLayeredTraceability(root, config),
16317
- ...await validateOrphanProhibition(root, config),
16318
- ...await validateLayerCoverage(root, config),
16319
- ...atddCodeTraceabilityIssues,
16320
- ...await validateContractReferences(root, config),
16321
- ...await validateTraceability(root, config, phase),
16322
- ...await validateDefinedIds(root, config),
16323
- ...await validateContracts(root, config),
16324
- ...await validateTddList(root, config),
16325
- ...await validateDiscussionDesignHardening(root, config),
16326
- ...await validateNavigationFlow(root, config),
16327
- ...await validateRenderCritique(root, config),
16328
- ...await validateDesignFidelity(root, config),
16329
- ...await validateDesignContractReadiness(root, config),
16330
- ...await validateTraceabilityIntegrity(root, config),
16331
- ...prototypingSkillResult.issues,
16332
- ...uiuxIssues
16333
- ];
16334
- const { issues, waivers } = await applyWaivers(root, findings);
16335
- const specsRoot = resolvePath(root, config, "specsDir");
16336
- const scenarioFiles = await collectScenarioFiles(specsRoot);
16337
- const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
16338
- const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
16339
- root,
16340
- config.validation.traceability.testFileGlobs,
16341
- config.validation.traceability.testFileExcludeGlobs
16342
- );
16343
- const scCoverage = buildScCoverage(scIds, scTestRefs);
16344
- const toolVersion = await resolveToolVersion();
16345
- return {
16346
- toolVersion,
16347
- phase,
16348
- issues,
16349
- counts: countIssues(issues),
16350
- traceability: {
16351
- sc: scCoverage,
16352
- testFiles
16353
- },
16354
- waivers
16355
- };
18349
+ return prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent).issues : [];
16356
18350
  }
16357
18351
  function countIssues(issues) {
16358
18352
  return issues.reduce(
@@ -16368,11 +18362,11 @@ function countIssues(issues) {
16368
18362
  }
16369
18363
 
16370
18364
  // src/core/uiux/renderEvidence.ts
16371
- import { readFile as readFile44 } from "fs/promises";
18365
+ import { readFile as readFile51 } from "fs/promises";
16372
18366
  async function readRenderEvidenceBundle(filePath) {
16373
18367
  let raw;
16374
18368
  try {
16375
- raw = await readFile44(filePath, "utf-8");
18369
+ raw = await readFile51(filePath, "utf-8");
16376
18370
  } catch {
16377
18371
  return null;
16378
18372
  }
@@ -16382,9 +18376,9 @@ async function readRenderEvidenceBundle(filePath) {
16382
18376
  } catch {
16383
18377
  return null;
16384
18378
  }
16385
- return isRecord7(parsed) ? parsed : null;
18379
+ return isRecord17(parsed) ? parsed : null;
16386
18380
  }
16387
- function isRecord7(value) {
18381
+ function isRecord17(value) {
16388
18382
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16389
18383
  }
16390
18384
  function summarizeRenderEvidence(bundle) {
@@ -16392,7 +18386,7 @@ function summarizeRenderEvidence(bundle) {
16392
18386
  let inlinePayloadViolation = false;
16393
18387
  if (bundle?.screens) {
16394
18388
  for (const screen of bundle.screens) {
16395
- if (isRecord7(screen)) {
18389
+ if (isRecord17(screen)) {
16396
18390
  const s = screen;
16397
18391
  if (s.status === "captured" || s.status === "skipped" || s.status === "failed") {
16398
18392
  counts[s.status] += 1;
@@ -16417,7 +18411,7 @@ function summarizeRenderEvidence(bundle) {
16417
18411
  }
16418
18412
 
16419
18413
  // src/core/browserQa/index.ts
16420
- import { readFile as readFile45 } from "fs/promises";
18414
+ import { readFile as readFile52 } from "fs/promises";
16421
18415
 
16422
18416
  // src/core/browserQa/types.ts
16423
18417
  var BROWSER_QA_PHASES = [
@@ -16980,7 +18974,7 @@ function summarizeBrowserQaResult(result) {
16980
18974
  async function readBrowserQaBundle(filePath) {
16981
18975
  let raw;
16982
18976
  try {
16983
- raw = await readFile45(filePath, "utf-8");
18977
+ raw = await readFile52(filePath, "utf-8");
16984
18978
  } catch {
16985
18979
  return null;
16986
18980
  }
@@ -16990,9 +18984,9 @@ async function readBrowserQaBundle(filePath) {
16990
18984
  } catch {
16991
18985
  return null;
16992
18986
  }
16993
- return isRecord8(parsed) ? parsed : null;
18987
+ return isRecord18(parsed) ? parsed : null;
16994
18988
  }
16995
- function isRecord8(value) {
18989
+ function isRecord18(value) {
16996
18990
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16997
18991
  }
16998
18992
 
@@ -17001,15 +18995,15 @@ var REPORT_GUARDRAILS_MAX = 20;
17001
18995
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
17002
18996
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
17003
18997
  async function createReportData(root, validation, configResult) {
17004
- const resolvedRoot = path70.resolve(root);
18998
+ const resolvedRoot = path79.resolve(root);
17005
18999
  const resolved = configResult ?? await loadConfig(resolvedRoot);
17006
19000
  const config = resolved.config;
17007
19001
  const configPath = resolved.configPath;
17008
19002
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
17009
19003
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
17010
- const apiRoot = path70.join(contractsRoot, "api");
17011
- const uiRoot = path70.join(contractsRoot, "ui");
17012
- const dbRoot = path70.join(contractsRoot, "db");
19004
+ const apiRoot = path79.join(contractsRoot, "api");
19005
+ const uiRoot = path79.join(contractsRoot, "ui");
19006
+ const dbRoot = path79.join(contractsRoot, "db");
17013
19007
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
17014
19008
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
17015
19009
  const specEntries = await collectSpecEntries(specsRoot);
@@ -17806,6 +19800,28 @@ function formatReportMarkdown(data, options = {}) {
17806
19800
  );
17807
19801
  lines.push("");
17808
19802
  }
19803
+ if (data.prototyping.roundLifecycle) {
19804
+ const lifecycle = data.prototyping.roundLifecycle;
19805
+ lines.push("### prototyping.roundLifecycle");
19806
+ lines.push("");
19807
+ lines.push(`- schemaVersion: ${lifecycle.schemaVersion}`);
19808
+ lines.push(`- rounds: ${lifecycle.rounds}`);
19809
+ lines.push(`- round ids: ${lifecycle.roundIds.join(", ") || "(none)"}`);
19810
+ lines.push(`- candidates observed: ${lifecycle.candidatesObserved}`);
19811
+ lines.push(`- harvest artifacts: ${lifecycle.harvestArtifacts}`);
19812
+ lines.push(`- narrow decisions: ${lifecycle.narrowDecisions}`);
19813
+ lines.push(`- absorption plans: ${lifecycle.absorptionPlans}`);
19814
+ lines.push(`- reimplementations: ${lifecycle.reimplementations}`);
19815
+ lines.push(`- perfect rounds: ${lifecycle.perfectRounds}`);
19816
+ lines.push(`- polish cycles: ${lifecycle.polishCycles}`);
19817
+ lines.push(`- completed polish cycles: ${lifecycle.completedPolishCycles}`);
19818
+ lines.push(`- perfect polish cycles: ${lifecycle.perfectPolishCycles}`);
19819
+ if (Object.keys(lifecycle.polishKinds).length > 0) {
19820
+ const kinds = Object.entries(lifecycle.polishKinds).map(([kind, count]) => `${kind}=${count}`).join(", ");
19821
+ lines.push(`- polish kinds: ${kinds}`);
19822
+ }
19823
+ lines.push("");
19824
+ }
17809
19825
  lines.push("### prototyping.mode");
17810
19826
  lines.push("");
17811
19827
  lines.push(`- requested: ${data.prototyping.mode.requested ?? "(none)"}`);
@@ -18162,7 +20178,7 @@ async function collectChangeTypeSummary(specsRoot) {
18162
20178
  };
18163
20179
  const deltaFiles = await collectDeltaFiles(specsRoot);
18164
20180
  for (const deltaFile of deltaFiles) {
18165
- const text = await readFile46(deltaFile, "utf-8");
20181
+ const text = await readFile53(deltaFile, "utf-8");
18166
20182
  const parsed = parseDeltaV1(text);
18167
20183
  for (const entry of parsed.entries) {
18168
20184
  if (!entry.meta) {
@@ -18191,13 +20207,46 @@ async function collectChangeTypeSummary(specsRoot) {
18191
20207
  }
18192
20208
  return summary;
18193
20209
  }
20210
+ var PROTOTYPING_ROUND_DIR_NAMES = new Set(EXPLORATION_ROUNDS);
20211
+ async function scanPrototypingRoundsFilesystem(evidenceRoot) {
20212
+ const result = {
20213
+ observedRoundIds: [],
20214
+ harvestArtifacts: 0,
20215
+ narrowDecisions: 0,
20216
+ absorptionPlans: 0,
20217
+ reimplementations: 0
20218
+ };
20219
+ const roundsDir = path79.join(evidenceRoot, "prototyping", "rounds");
20220
+ const { readdir: readdir14 } = await import("fs/promises");
20221
+ let entries;
20222
+ try {
20223
+ entries = await readdir14(roundsDir);
20224
+ } catch {
20225
+ return result;
20226
+ }
20227
+ for (const name of entries.sort()) {
20228
+ if (!PROTOTYPING_ROUND_DIR_NAMES.has(name)) continue;
20229
+ let files;
20230
+ try {
20231
+ files = await readdir14(path79.join(roundsDir, name));
20232
+ } catch {
20233
+ continue;
20234
+ }
20235
+ result.observedRoundIds.push(name);
20236
+ if (files.includes("harvest.json")) result.harvestArtifacts += 1;
20237
+ if (files.includes("narrow-decision.json")) result.narrowDecisions += 1;
20238
+ if (files.includes("absorption-plan.json")) result.absorptionPlans += 1;
20239
+ if (files.includes("reimplementation.json")) result.reimplementations += 1;
20240
+ }
20241
+ return result;
20242
+ }
18194
20243
  async function collectPrototypingSummary(root, config) {
18195
20244
  const specsRoot = resolvePath(root, config, "specsDir");
18196
- const evidenceRoot = path70.join(path70.dirname(specsRoot), "evidence");
18197
- const evidencePath = path70.join(evidenceRoot, "prototyping.json");
20245
+ const evidenceRoot = path79.join(path79.dirname(specsRoot), "evidence");
20246
+ const evidencePath = path79.join(evidenceRoot, "prototyping.json");
18198
20247
  let raw;
18199
20248
  try {
18200
- raw = await readFile46(evidencePath, "utf-8");
20249
+ raw = await readFile53(evidencePath, "utf-8");
18201
20250
  } catch {
18202
20251
  return void 0;
18203
20252
  }
@@ -18224,10 +20273,12 @@ async function collectPrototypingSummary(root, config) {
18224
20273
  const warnings = [];
18225
20274
  const obligations = derivePrototypingObligations({ surface: effectiveSurface, effectiveMode });
18226
20275
  const fullHarness = asRecord2(record2.fullHarness);
20276
+ const rounds = Array.isArray(record2.rounds) ? record2.rounds : [];
20277
+ const polishCycles = Array.isArray(record2.polishCycles) ? record2.polishCycles : [];
18227
20278
  const runtimeGate = asRecord2(record2.runtimeGate);
18228
20279
  const uiFidelity = asRecord2(record2.uiFidelity);
18229
- const renderBundle = await readRenderEvidenceBundle(path70.join(evidenceRoot, "render.json"));
18230
- const browserQaBundle = await readBrowserQaBundle(path70.join(evidenceRoot, "browser-qa.json"));
20280
+ const renderBundle = await readRenderEvidenceBundle(path79.join(evidenceRoot, "render.json"));
20281
+ const browserQaBundle = await readBrowserQaBundle(path79.join(evidenceRoot, "browser-qa.json"));
18231
20282
  const specs = Array.isArray(record2.specs) ? record2.specs : [];
18232
20283
  const specEntries = await collectSpecEntries(specsRoot);
18233
20284
  const expectedSpecIds = specEntries.map((entry) => `spec-${entry.specNumber}`.toLowerCase());
@@ -18284,8 +20335,81 @@ async function collectPrototypingSummary(root, config) {
18284
20335
  ...classificationBlock?.secondary_surfaces?.length ? { secondarySurfaces: classificationBlock.secondary_surfaces } : {},
18285
20336
  ...classificationBlock?.classification_rationale ? { classificationRationale: classificationBlock.classification_rationale } : {}
18286
20337
  };
20338
+ const fsRoundScan = await scanPrototypingRoundsFilesystem(evidenceRoot);
20339
+ const roundLifecycle = record2.schemaVersion === "2.0" || rounds.length > 0 || polishCycles.length > 0 || fsRoundScan.observedRoundIds.length > 0 ? (() => {
20340
+ const polishKinds = {};
20341
+ let candidatesObserved = 0;
20342
+ let perfectRounds = 0;
20343
+ let completedPolishCycles = 0;
20344
+ let perfectPolishCycles = 0;
20345
+ for (const item of rounds) {
20346
+ const round = asRecord2(item);
20347
+ if (!round) {
20348
+ continue;
20349
+ }
20350
+ const candidates = Array.isArray(round.candidates) ? round.candidates : [];
20351
+ candidatesObserved += candidates.length;
20352
+ if (round.allAxesPerfect100 === true) {
20353
+ perfectRounds += 1;
20354
+ }
20355
+ }
20356
+ for (const item of polishCycles) {
20357
+ const cycle = asRecord2(item);
20358
+ if (!cycle) {
20359
+ continue;
20360
+ }
20361
+ if (typeof cycle.kind === "string" && cycle.kind.length > 0) {
20362
+ polishKinds[cycle.kind] = (polishKinds[cycle.kind] ?? 0) + 1;
20363
+ if (cycle.kind === "completed") {
20364
+ completedPolishCycles += 1;
20365
+ }
20366
+ }
20367
+ if (cycle.allAxesPerfect100 === true) {
20368
+ perfectPolishCycles += 1;
20369
+ }
20370
+ }
20371
+ const indexAbsorptionRefs = rounds.map((item) => asRecord2(item)).filter(
20372
+ (round) => round && typeof round.absorptionPlanRef === "string" && round.absorptionPlanRef.length > 0
20373
+ ).length;
20374
+ if (indexAbsorptionRefs !== fsRoundScan.absorptionPlans) {
20375
+ warnings.push(
20376
+ `prototyping.json index references ${indexAbsorptionRefs} absorption-plan(s) but filesystem has ${fsRoundScan.absorptionPlans}; counts use filesystem (canonical SoT).`
20377
+ );
20378
+ }
20379
+ const indexReimplRefs = rounds.map((item) => asRecord2(item)).filter(
20380
+ (round) => round && typeof round.reimplementationRef === "string" && round.reimplementationRef.length > 0
20381
+ ).length;
20382
+ if (indexReimplRefs !== fsRoundScan.reimplementations) {
20383
+ warnings.push(
20384
+ `prototyping.json index references ${indexReimplRefs} reimplementation record(s) but filesystem has ${fsRoundScan.reimplementations}; counts use filesystem (canonical SoT).`
20385
+ );
20386
+ }
20387
+ const indexRoundIds = rounds.map((item) => asRecord2(item)).flatMap(
20388
+ (round) => round && typeof round.round === "string" && round.round.length > 0 ? [round.round] : []
20389
+ );
20390
+ const roundIdSet = /* @__PURE__ */ new Set([...indexRoundIds, ...fsRoundScan.observedRoundIds]);
20391
+ return {
20392
+ schemaVersion: "2.0",
20393
+ // Use the union size — Math.max underestimates when index and
20394
+ // filesystem describe disjoint round IDs (Copilot MAJOR review
20395
+ // on PR #201).
20396
+ rounds: roundIdSet.size,
20397
+ roundIds: Array.from(roundIdSet),
20398
+ candidatesObserved,
20399
+ perfectRounds,
20400
+ harvestArtifacts: fsRoundScan.harvestArtifacts,
20401
+ narrowDecisions: fsRoundScan.narrowDecisions,
20402
+ absorptionPlans: fsRoundScan.absorptionPlans,
20403
+ reimplementations: fsRoundScan.reimplementations,
20404
+ polishCycles: polishCycles.length,
20405
+ completedPolishCycles,
20406
+ perfectPolishCycles,
20407
+ polishKinds
20408
+ };
20409
+ })() : void 0;
18287
20410
  return {
18288
20411
  surfaceClassification,
20412
+ ...roundLifecycle ? { roundLifecycle } : {},
18289
20413
  mode: {
18290
20414
  ...resolvedMode.requested ? { requested: resolvedMode.requested } : {},
18291
20415
  effective: effectiveMode,
@@ -18378,7 +20502,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
18378
20502
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
18379
20503
  }
18380
20504
  for (const file of specFiles) {
18381
- const text = await readFile46(file, "utf-8");
20505
+ const text = await readFile53(file, "utf-8");
18382
20506
  const parsed = parseSpec(text, file);
18383
20507
  const specKey = parsed.specId;
18384
20508
  if (!specKey) {
@@ -18415,7 +20539,7 @@ async function collectIds(files) {
18415
20539
  result[prefix] = /* @__PURE__ */ new Set();
18416
20540
  }
18417
20541
  for (const file of files) {
18418
- const text = await readFile46(file, "utf-8");
20542
+ const text = await readFile53(file, "utf-8");
18419
20543
  for (const prefix of ID_PREFIXES) {
18420
20544
  const ids = extractIds(text, prefix);
18421
20545
  ids.forEach((id) => result[prefix].add(id));
@@ -18430,7 +20554,7 @@ async function collectIds(files) {
18430
20554
  async function collectUpstreamIds(files) {
18431
20555
  const ids = /* @__PURE__ */ new Set();
18432
20556
  for (const file of files) {
18433
- const text = await readFile46(file, "utf-8");
20557
+ const text = await readFile53(file, "utf-8");
18434
20558
  extractAllIds(text).forEach((id) => ids.add(id));
18435
20559
  }
18436
20560
  return ids;
@@ -18451,7 +20575,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
18451
20575
  }
18452
20576
  const pattern = buildIdPattern(Array.from(upstreamIds));
18453
20577
  for (const file of targetFiles) {
18454
- const text = await readFile46(file, "utf-8");
20578
+ const text = await readFile53(file, "utf-8");
18455
20579
  if (pattern.test(text)) {
18456
20580
  return true;
18457
20581
  }
@@ -18573,7 +20697,7 @@ function normalizeScSources(root, sources) {
18573
20697
  async function countScenarios(scenarioFiles) {
18574
20698
  let total = 0;
18575
20699
  for (const file of scenarioFiles) {
18576
- const text = await readFile46(file, "utf-8");
20700
+ const text = await readFile53(file, "utf-8");
18577
20701
  const { document, errors } = parseScenarioDocument(text, file);
18578
20702
  if (!document || errors.length > 0) {
18579
20703
  continue;
@@ -18604,7 +20728,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
18604
20728
  let totalScenarios = 0;
18605
20729
  let e2eCount = 0;
18606
20730
  for (const file of scenarioFiles) {
18607
- const text = await readFile46(file, "utf-8");
20731
+ const text = await readFile53(file, "utf-8");
18608
20732
  const { document, errors } = parseScenarioDocument(text, file);
18609
20733
  if (!document || errors.length > 0) {
18610
20734
  continue;
@@ -18692,10 +20816,10 @@ function buildHotspots(issues) {
18692
20816
  async function collectTddCoverage(entries) {
18693
20817
  const specs = [];
18694
20818
  for (const entry of entries) {
18695
- const testCasesPath = path70.join(entry.dir, "06_Test-Cases.md");
20819
+ const testCasesPath = path79.join(entry.dir, "06_Test-Cases.md");
18696
20820
  let tcContent;
18697
20821
  try {
18698
- tcContent = await readFile46(testCasesPath, "utf-8");
20822
+ tcContent = await readFile53(testCasesPath, "utf-8");
18699
20823
  } catch {
18700
20824
  continue;
18701
20825
  }
@@ -18727,10 +20851,10 @@ async function collectTddCoverage(entries) {
18727
20851
  });
18728
20852
  continue;
18729
20853
  }
18730
- const tddListPath = path70.join(entry.dir, "tdd", "test-list.md");
20854
+ const tddListPath = path79.join(entry.dir, "tdd", "test-list.md");
18731
20855
  let tddContent;
18732
20856
  try {
18733
- tddContent = await readFile46(tddListPath, "utf-8");
20857
+ tddContent = await readFile53(tddListPath, "utf-8");
18734
20858
  } catch {
18735
20859
  specs.push({
18736
20860
  specNumber: entry.specNumber,
@@ -18881,16 +21005,16 @@ async function runRenderCapture(targets, outputDir, adapter, options) {
18881
21005
  }
18882
21006
 
18883
21007
  // src/core/evidence/bundleWriter.ts
18884
- import { mkdir as mkdir5 } from "fs/promises";
18885
- import path72 from "path";
21008
+ import { mkdir as mkdir6 } from "fs/promises";
21009
+ import path81 from "path";
18886
21010
 
18887
21011
  // src/core/evidence/fsEvidenceWriter.ts
18888
- import { writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
18889
- import path71 from "path";
21012
+ import { writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
21013
+ import path80 from "path";
18890
21014
  async function writeEvidenceFile(filePath, content) {
18891
21015
  try {
18892
- await mkdir4(path71.dirname(filePath), { recursive: true });
18893
- await writeFile4(filePath, content);
21016
+ await mkdir5(path80.dirname(filePath), { recursive: true });
21017
+ await writeFile5(filePath, content);
18894
21018
  return { path: filePath, written: true };
18895
21019
  } catch (error) {
18896
21020
  return {
@@ -18902,20 +21026,20 @@ async function writeEvidenceFile(filePath, content) {
18902
21026
  }
18903
21027
 
18904
21028
  // src/core/evidence/bundleWriter.ts
18905
- function toPosixRelative5(root, targetPath) {
18906
- return path72.relative(root, targetPath).replace(/\\/g, "/");
21029
+ function toPosixRelative6(root, targetPath) {
21030
+ return path81.relative(root, targetPath).replace(/\\/g, "/");
18907
21031
  }
18908
21032
  async function writeEvidenceBundles(input) {
18909
- const evidenceRoot = path72.join(input.root, ".qfai", "evidence");
18910
- const renderAssetRoot = path72.join(evidenceRoot, "render");
18911
- await mkdir5(renderAssetRoot, { recursive: true });
18912
- const prototypingPath = path72.join(evidenceRoot, "prototyping.json");
18913
- const renderPath = path72.join(evidenceRoot, "render.json");
18914
- const browserQaPath = path72.join(evidenceRoot, "browser-qa.json");
18915
- const browserQaSummaryPath = path72.join(evidenceRoot, "browserQa.summary.json");
18916
- const browserQaFindingsPath = path72.join(evidenceRoot, "browserQa.findings.json");
18917
- const browserQaRepairsPath = path72.join(evidenceRoot, "browserQa.repairs.json");
18918
- const breakthroughPath = path72.join(evidenceRoot, "breakthrough.json");
21033
+ const evidenceRoot = path81.join(input.root, ".qfai", "evidence");
21034
+ const renderAssetRoot = path81.join(evidenceRoot, "render");
21035
+ await mkdir6(renderAssetRoot, { recursive: true });
21036
+ const prototypingPath = path81.join(evidenceRoot, "prototyping.json");
21037
+ const renderPath = path81.join(evidenceRoot, "render.json");
21038
+ const browserQaPath = path81.join(evidenceRoot, "browser-qa.json");
21039
+ const browserQaSummaryPath = path81.join(evidenceRoot, "browserQa.summary.json");
21040
+ const browserQaFindingsPath = path81.join(evidenceRoot, "browserQa.findings.json");
21041
+ const browserQaRepairsPath = path81.join(evidenceRoot, "browserQa.repairs.json");
21042
+ const breakthroughPath = path81.join(evidenceRoot, "breakthrough.json");
18919
21043
  const renderBundle = input.render === void 0 ? {
18920
21044
  renderEvidence: {
18921
21045
  status: "skipped",
@@ -18937,7 +21061,7 @@ async function writeEvidenceBundles(input) {
18937
21061
  await Promise.all([
18938
21062
  writeEvidenceFile(prototypingPath, JSON.stringify(input.prototyping, null, 2)),
18939
21063
  writeEvidenceFile(
18940
- path72.join(evidenceRoot, "prototyping.md"),
21064
+ path81.join(evidenceRoot, "prototyping.md"),
18941
21065
  buildPrototypingMarkdown(input.prototyping)
18942
21066
  ),
18943
21067
  writeEvidenceFile(renderPath, JSON.stringify(renderBundle, null, 2)),
@@ -18954,15 +21078,15 @@ async function writeEvidenceBundles(input) {
18954
21078
  writeEvidenceFile(breakthroughPath, JSON.stringify(input.prototyping.breakthrough, null, 2)),
18955
21079
  ...input.fullHarnessArtifacts ? [
18956
21080
  writeEvidenceFile(
18957
- path72.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
21081
+ path81.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
18958
21082
  JSON.stringify(input.fullHarnessArtifacts.fakeUiDetection, null, 2)
18959
21083
  ),
18960
21084
  writeEvidenceFile(
18961
- path72.join(evidenceRoot, "fullHarness.handoff.json"),
21085
+ path81.join(evidenceRoot, "fullHarness.handoff.json"),
18962
21086
  JSON.stringify(input.fullHarnessArtifacts.handoff, null, 2)
18963
21087
  ),
18964
21088
  writeEvidenceFile(
18965
- path72.join(evidenceRoot, "fullHarness.exit.json"),
21089
+ path81.join(evidenceRoot, "fullHarness.exit.json"),
18966
21090
  JSON.stringify({ exit_reason: input.fullHarnessArtifacts.exitReason }, null, 2)
18967
21091
  )
18968
21092
  ] : []
@@ -18976,8 +21100,8 @@ function buildRenderBundle(root, input) {
18976
21100
  status: entry.status,
18977
21101
  width: 1440,
18978
21102
  height: 900,
18979
- ...entry.screenshot_path ? { imagePath: toPosixRelative5(root, entry.screenshot_path) } : {},
18980
- ...entry.html_path ? { htmlPath: toPosixRelative5(root, entry.html_path) } : {},
21103
+ ...entry.screenshot_path ? { imagePath: toPosixRelative6(root, entry.screenshot_path) } : {},
21104
+ ...entry.html_path ? { htmlPath: toPosixRelative6(root, entry.html_path) } : {},
18981
21105
  ...entry.reason ? entry.status === "failed" ? { error: entry.reason } : { skippedReason: entry.reason } : {}
18982
21106
  }));
18983
21107
  const topStatus = screens.some((screen) => screen.status === "captured") ? "captured" : screens.some((screen) => screen.status === "failed") ? "failed" : "skipped";
@@ -19062,15 +21186,15 @@ function buildPrototypingMarkdown(bundle) {
19062
21186
  }
19063
21187
 
19064
21188
  // src/core/harness/history.ts
19065
- import { readFile as readFile47 } from "fs/promises";
19066
- import path73 from "path";
21189
+ import { readFile as readFile54 } from "fs/promises";
21190
+ import path82 from "path";
19067
21191
  var EVIDENCE_PATH = ".qfai/evidence/prototyping.json";
19068
21192
  async function loadHistory(root) {
19069
21193
  try {
19070
- const filePath = path73.join(root, EVIDENCE_PATH);
19071
- const content = await readFile47(filePath, "utf-8");
21194
+ const filePath = path82.join(root, EVIDENCE_PATH);
21195
+ const content = await readFile54(filePath, "utf-8");
19072
21196
  const parsed = JSON.parse(content);
19073
- if (isRecord9(parsed) && isRecord9(parsed.fullHarness)) {
21197
+ if (isRecord19(parsed) && isRecord19(parsed.fullHarness)) {
19074
21198
  const fh = parsed.fullHarness;
19075
21199
  if (Array.isArray(fh.iterations)) {
19076
21200
  return {
@@ -19119,7 +21243,7 @@ function computeTerminationReason(history, iterationPolicy) {
19119
21243
  return void 0;
19120
21244
  }
19121
21245
  const latestIteration = history.iterations[count - 1];
19122
- if (latestIteration?.allItemsPass95) {
21246
+ if (latestIteration && hasAllReviewerAxesPerfect100(latestIteration.reviewerScores)) {
19123
21247
  return "converged";
19124
21248
  }
19125
21249
  if (count >= iterationPolicy.maxIterations) {
@@ -19131,6 +21255,7 @@ function buildScoreSnapshot(iteration) {
19131
21255
  const axisScores = iteration.reviewerScores.flatMap(
19132
21256
  (reviewer) => reviewer.scores.map((score) => score.score)
19133
21257
  );
21258
+ const allReviewerAxesPerfect100 = hasAllReviewerAxesPerfect100(iteration.reviewerScores);
19134
21259
  if (axisScores.length === 0) {
19135
21260
  return {
19136
21261
  iteration: iteration.iteration,
@@ -19138,7 +21263,7 @@ function buildScoreSnapshot(iteration) {
19138
21263
  axisCount: 0,
19139
21264
  minScore: null,
19140
21265
  averageScore: null,
19141
- allItemsPass95: iteration.allItemsPass95,
21266
+ allReviewerAxesPerfect100: false,
19142
21267
  commitSha: iteration.commitSha
19143
21268
  };
19144
21269
  }
@@ -19149,13 +21274,13 @@ function buildScoreSnapshot(iteration) {
19149
21274
  axisCount: axisScores.length,
19150
21275
  minScore: Math.min(...axisScores),
19151
21276
  averageScore: total / axisScores.length,
19152
- allItemsPass95: iteration.allItemsPass95,
21277
+ allReviewerAxesPerfect100,
19153
21278
  commitSha: iteration.commitSha
19154
21279
  };
19155
21280
  }
19156
21281
  function compareSnapshots(left, right) {
19157
- if (left.allItemsPass95 !== right.allItemsPass95) {
19158
- return left.allItemsPass95 ? 1 : -1;
21282
+ if (left.allReviewerAxesPerfect100 !== right.allReviewerAxesPerfect100) {
21283
+ return left.allReviewerAxesPerfect100 ? 1 : -1;
19159
21284
  }
19160
21285
  const leftMin = left.minScore ?? -1;
19161
21286
  const rightMin = right.minScore ?? -1;
@@ -19169,7 +21294,15 @@ function compareSnapshots(left, right) {
19169
21294
  }
19170
21295
  return left.iteration - right.iteration;
19171
21296
  }
19172
- function isRecord9(value) {
21297
+ function hasAllReviewerAxesPerfect100(reviewerScores) {
21298
+ if (reviewerScores.length === 0) {
21299
+ return false;
21300
+ }
21301
+ return reviewerScores.every(
21302
+ (reviewer) => reviewer.scores.length > 0 && reviewer.scores.every((score) => score.score === 100)
21303
+ );
21304
+ }
21305
+ function isRecord19(value) {
19173
21306
  return typeof value === "object" && value !== null && !Array.isArray(value);
19174
21307
  }
19175
21308
 
@@ -19204,16 +21337,16 @@ function validateReviewer(reviewer) {
19204
21337
  }
19205
21338
 
19206
21339
  // src/core/harness/gitRevision.ts
19207
- import { readFile as readFile48, stat as stat7 } from "fs/promises";
19208
- import path74 from "path";
21340
+ import { readFile as readFile55, stat as stat11 } from "fs/promises";
21341
+ import path83 from "path";
19209
21342
  async function resolveGitDirs(root) {
19210
- const dotGit = path74.join(root, ".git");
19211
- const info = await stat7(dotGit);
21343
+ const dotGit = path83.join(root, ".git");
21344
+ const info = await stat11(dotGit);
19212
21345
  const gitDir = info.isDirectory() ? dotGit : await resolveWorktreeGitDir(root, dotGit);
19213
21346
  try {
19214
- const commondirText = (await readFile48(path74.join(gitDir, "commondir"), "utf-8")).trim();
21347
+ const commondirText = (await readFile55(path83.join(gitDir, "commondir"), "utf-8")).trim();
19215
21348
  if (commondirText.length > 0) {
19216
- const commonDir = path74.isAbsolute(commondirText) ? commondirText : path74.resolve(gitDir, commondirText);
21349
+ const commonDir = path83.isAbsolute(commondirText) ? commondirText : path83.resolve(gitDir, commondirText);
19217
21350
  return { gitDir, commonDir };
19218
21351
  }
19219
21352
  } catch {
@@ -19221,18 +21354,18 @@ async function resolveGitDirs(root) {
19221
21354
  return { gitDir, commonDir: gitDir };
19222
21355
  }
19223
21356
  async function resolveWorktreeGitDir(root, dotGitFile) {
19224
- const text = (await readFile48(dotGitFile, "utf-8")).trim();
21357
+ const text = (await readFile55(dotGitFile, "utf-8")).trim();
19225
21358
  const match = /^gitdir:\s*(.+)$/m.exec(text);
19226
21359
  if (!match?.[1]) {
19227
21360
  throw new Error(`Unrecognized .git file format at ${dotGitFile}`);
19228
21361
  }
19229
21362
  const gitdirValue = match[1].trim();
19230
- return path74.isAbsolute(gitdirValue) ? gitdirValue : path74.resolve(root, gitdirValue);
21363
+ return path83.isAbsolute(gitdirValue) ? gitdirValue : path83.resolve(root, gitdirValue);
19231
21364
  }
19232
21365
  async function lookupPackedRef(commonDir, refName) {
19233
21366
  try {
19234
- const packedPath = path74.join(commonDir, "packed-refs");
19235
- const content = await readFile48(packedPath, "utf-8");
21367
+ const packedPath = path83.join(commonDir, "packed-refs");
21368
+ const content = await readFile55(packedPath, "utf-8");
19236
21369
  for (const rawLine of content.split(/\r?\n/)) {
19237
21370
  const line = rawLine.trim();
19238
21371
  if (line.length === 0 || line.startsWith("#") || line.startsWith("^")) continue;
@@ -19248,7 +21381,7 @@ async function lookupPackedRef(commonDir, refName) {
19248
21381
  }
19249
21382
  async function readRefLoose(dir, refName) {
19250
21383
  try {
19251
- const sha = (await readFile48(path74.join(dir, refName), "utf-8")).trim();
21384
+ const sha = (await readFile55(path83.join(dir, refName), "utf-8")).trim();
19252
21385
  return sha.length > 0 ? sha : null;
19253
21386
  } catch {
19254
21387
  return null;
@@ -19257,8 +21390,8 @@ async function readRefLoose(dir, refName) {
19257
21390
  async function resolveCommitSha(root) {
19258
21391
  try {
19259
21392
  const { gitDir, commonDir } = await resolveGitDirs(root);
19260
- const headPath = path74.join(gitDir, "HEAD");
19261
- const headContent = (await readFile48(headPath, "utf-8")).trim();
21393
+ const headPath = path83.join(gitDir, "HEAD");
21394
+ const headContent = (await readFile55(headPath, "utf-8")).trim();
19262
21395
  if (headContent.startsWith("ref: ")) {
19263
21396
  const refName = headContent.slice(5).trim();
19264
21397
  const looseLocal = await readRefLoose(gitDir, refName);
@@ -19279,168 +21412,13 @@ async function resolveCommitSha(root) {
19279
21412
  );
19280
21413
  }
19281
21414
  }
19282
-
19283
- // src/core/evidence/playwrightRenderAdapter.ts
19284
- import { mkdir as mkdir6, writeFile as writeFile5 } from "fs/promises";
19285
- import path75 from "path";
19286
- function sanitizeName(value) {
19287
- return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
19288
- }
19289
- async function loadPlaywright() {
19290
- return import("playwright");
19291
- }
19292
- function createPlaywrightRenderAdapter(input) {
19293
- async function _renderTarget(target) {
19294
- if (!input.targetUrl) {
19295
- throw new Error("target URL is required for Playwright render capture");
19296
- }
19297
- const playwright = await loadPlaywright();
19298
- const browser = await playwright.chromium.launch();
19299
- try {
19300
- const page = await browser.newPage({
19301
- viewport: {
19302
- width: target.width ?? 1440,
19303
- height: target.height ?? 900
19304
- }
19305
- });
19306
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19307
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19308
- const slug = sanitizeName(`${target.targetId}.${target.viewport}`);
19309
- const screenshotPath = path75.join(process.cwd(), ".", slug);
19310
- return {
19311
- screenshotPath,
19312
- htmlPath: await page.content()
19313
- };
19314
- } finally {
19315
- await browser.close();
19316
- }
19317
- }
19318
- return {
19319
- async captureScreenshot(target, outputDir) {
19320
- if (!input.targetUrl) {
19321
- throw new Error("target URL is required for Playwright render capture");
19322
- }
19323
- const playwright = await loadPlaywright();
19324
- const browser = await playwright.chromium.launch();
19325
- try {
19326
- await mkdir6(outputDir, { recursive: true });
19327
- const page = await browser.newPage({
19328
- viewport: {
19329
- width: target.width ?? 1440,
19330
- height: target.height ?? 900
19331
- }
19332
- });
19333
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19334
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19335
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19336
- const screenshotPath = path75.join(outputDir, `${targetName}.png`);
19337
- await page.screenshot({ path: screenshotPath, fullPage: true });
19338
- return screenshotPath;
19339
- } finally {
19340
- await browser.close();
19341
- }
19342
- },
19343
- async captureHtml(target, outputDir) {
19344
- if (!input.targetUrl) {
19345
- throw new Error("target URL is required for Playwright render capture");
19346
- }
19347
- const playwright = await loadPlaywright();
19348
- const browser = await playwright.chromium.launch();
19349
- try {
19350
- await mkdir6(outputDir, { recursive: true });
19351
- const page = await browser.newPage({
19352
- viewport: {
19353
- width: target.width ?? 1440,
19354
- height: target.height ?? 900
19355
- }
19356
- });
19357
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19358
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19359
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19360
- const htmlPath = path75.join(outputDir, `${targetName}.html`);
19361
- await writeFile5(htmlPath, await page.content(), "utf-8");
19362
- return htmlPath;
19363
- } finally {
19364
- await browser.close();
19365
- }
19366
- }
19367
- };
19368
- }
19369
-
19370
- // src/core/providers/playwrightBrowserQaProvider.ts
19371
- init_surfaceType();
19372
- async function loadPlaywright2() {
19373
- try {
19374
- return await import("playwright");
19375
- } catch {
19376
- throw new Error("Playwright is not installed. Install it with: npm install playwright");
19377
- }
19378
- }
19379
- async function withPage(input, task) {
19380
- const playwright = await loadPlaywright2();
19381
- const browser = await playwright.chromium.launch();
19382
- try {
19383
- const page = await browser.newPage();
19384
- if (input.targetUrl) {
19385
- await page.goto(input.targetUrl, { waitUntil: "networkidle" });
19386
- } else if (input.htmlContent) {
19387
- await page.setContent(input.htmlContent, { waitUntil: "load" });
19388
- } else {
19389
- throw new Error("Browser QA requires targetUrl or htmlContent");
19390
- }
19391
- return await task(page);
19392
- } finally {
19393
- await browser.close();
19394
- }
19395
- }
19396
- function completed(phase) {
19397
- return {
19398
- phase,
19399
- status: "executed",
19400
- findings: [],
19401
- repair_suggestions: [],
19402
- evidence_refs: [`provider:playwright:${phase}`],
19403
- checks_performed: [`playwright executed ${phase} checks`]
19404
- };
19405
- }
19406
- function createPlaywrightBrowserQaProvider() {
19407
- return {
19408
- providerId: "playwright",
19409
- canRun(surface) {
19410
- return requiresVisualBrowserEvidence(surface);
19411
- },
19412
- async runSmoke(input) {
19413
- return withPage(input, async (page) => {
19414
- await page.title();
19415
- return completed("smoke");
19416
- });
19417
- },
19418
- async runInteraction(input) {
19419
- return withPage(input, async (page) => {
19420
- await page.locator("button, a, input, form").count();
19421
- return completed("interaction");
19422
- });
19423
- },
19424
- async runVisual(input) {
19425
- return withPage(input, async (page) => {
19426
- await page.viewportSize();
19427
- return completed("visual");
19428
- });
19429
- },
19430
- async runAccessibility(input) {
19431
- return withPage(input, async (page) => {
19432
- await page.locator("html").getAttribute("lang");
19433
- return completed("accessibility");
19434
- });
19435
- }
19436
- };
19437
- }
19438
21415
  export {
19439
21416
  BROWSER_QA_PHASES,
19440
21417
  DEFAULT_PROTOTYPING_MODE,
19441
21418
  DISCUSSION_NON_UI_SURFACES,
19442
21419
  DISCUSSION_UI_BEARING_SURFACES,
19443
21420
  ID_PREFIXES,
21421
+ PROTOTYPING_MAX_CYCLES,
19444
21422
  PROTOTYPING_MAX_ITERATIONS,
19445
21423
  PROTOTYPING_MODES,
19446
21424
  PROTOTYPING_SUPPORTED_SURFACES,
@@ -19449,8 +21427,6 @@ export {
19449
21427
  appendIteration,
19450
21428
  checkDecisionGuardrails,
19451
21429
  computeTerminationReason,
19452
- createPlaywrightBrowserQaProvider,
19453
- createPlaywrightRenderAdapter,
19454
21430
  createReportData,
19455
21431
  defaultConfig,
19456
21432
  derivePrototypingObligations,