qfai 1.8.2 → 1.8.3

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 +121 -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 +20 -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 +2 -0
  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 +2539 -927
  29. package/dist/cli/index.cjs.map +1 -1
  30. package/dist/cli/index.mjs +2624 -1012
  31. package/dist/cli/index.mjs.map +1 -1
  32. package/dist/index.cjs +1120 -421
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +95 -23
  35. package/dist/index.d.ts +95 -23
  36. package/dist/index.mjs +1114 -414
  37. package/dist/index.mjs.map +1 -1
  38. package/package.json +3 -2
  39. package/assets/scripts/capture-screenshots.js +0 -128
package/dist/index.cjs CHANGED
@@ -453,6 +453,7 @@ __export(src_exports, {
453
453
  DISCUSSION_NON_UI_SURFACES: () => DISCUSSION_NON_UI_SURFACES,
454
454
  DISCUSSION_UI_BEARING_SURFACES: () => DISCUSSION_UI_BEARING_SURFACES,
455
455
  ID_PREFIXES: () => ID_PREFIXES,
456
+ PROTOTYPING_MAX_CYCLES: () => PROTOTYPING_MAX_CYCLES,
456
457
  PROTOTYPING_MAX_ITERATIONS: () => PROTOTYPING_MAX_ITERATIONS,
457
458
  PROTOTYPING_MODES: () => PROTOTYPING_MODES,
458
459
  PROTOTYPING_SUPPORTED_SURFACES: () => PROTOTYPING_SUPPORTED_SURFACES,
@@ -461,8 +462,6 @@ __export(src_exports, {
461
462
  appendIteration: () => appendIteration,
462
463
  checkDecisionGuardrails: () => checkDecisionGuardrails,
463
464
  computeTerminationReason: () => computeTerminationReason,
464
- createPlaywrightBrowserQaProvider: () => createPlaywrightBrowserQaProvider,
465
- createPlaywrightRenderAdapter: () => createPlaywrightRenderAdapter,
466
465
  createReportData: () => createReportData,
467
466
  defaultConfig: () => defaultConfig,
468
467
  derivePrototypingObligations: () => derivePrototypingObligations,
@@ -570,7 +569,8 @@ var defaultConfig = {
570
569
  requireLayerTags: false,
571
570
  requireSizeTags: false,
572
571
  maxE2eScenarioRatio: null,
573
- maxE2eScenarioCount: null
572
+ maxE2eScenarioCount: null,
573
+ forbidTestTodoStubs: true
574
574
  },
575
575
  traceability: {
576
576
  brMustHaveSc: true,
@@ -591,8 +591,7 @@ var defaultConfig = {
591
591
  },
592
592
  execution: {
593
593
  targetUrl: null,
594
- browserProvider: "playwright",
595
- renderProvider: "playwright"
594
+ browserTool: "playwright-cli"
596
595
  }
597
596
  }
598
597
  };
@@ -783,6 +782,13 @@ function normalizeValidation(raw, configPath, issues) {
783
782
  "validation.testStrategy.maxE2eScenarioCount",
784
783
  configPath,
785
784
  issues
785
+ ),
786
+ forbidTestTodoStubs: readBoolean(
787
+ testStrategyRaw?.forbidTestTodoStubs,
788
+ base.testStrategy.forbidTestTodoStubs,
789
+ "validation.testStrategy.forbidTestTodoStubs",
790
+ configPath,
791
+ issues
786
792
  )
787
793
  },
788
794
  traceability: {
@@ -908,6 +914,30 @@ function normalizePrototypingExecution(raw, configPath, issues) {
908
914
  );
909
915
  return base ? { ...base } : void 0;
910
916
  }
917
+ for (const legacyKey of ["browserProvider", "renderProvider"]) {
918
+ if (raw[legacyKey] !== void 0) {
919
+ issues.push(
920
+ configIssue(
921
+ configPath,
922
+ `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`
923
+ )
924
+ );
925
+ }
926
+ }
927
+ const browserToolRaw = raw.browserTool;
928
+ let browserTool = "playwright-cli";
929
+ if (browserToolRaw !== void 0) {
930
+ if (browserToolRaw !== "playwright-cli") {
931
+ issues.push(
932
+ configIssue(
933
+ configPath,
934
+ `prototyping.execution.browserTool \u306F "playwright-cli" \u306E\u307F\u6709\u52B9\u3067\u3059 (spec-0012)\u3002 \u53D7\u3051\u53D6\u3063\u305F\u5024: ${JSON.stringify(browserToolRaw)}`
935
+ )
936
+ );
937
+ } else {
938
+ browserTool = browserToolRaw;
939
+ }
940
+ }
911
941
  return {
912
942
  targetUrl: raw.targetUrl === null ? null : readOptionalString(
913
943
  raw.targetUrl,
@@ -915,20 +945,7 @@ function normalizePrototypingExecution(raw, configPath, issues) {
915
945
  configPath,
916
946
  issues
917
947
  ) ?? null,
918
- browserProvider: readString(
919
- raw.browserProvider,
920
- base?.browserProvider ?? "playwright",
921
- "prototyping.execution.browserProvider",
922
- configPath,
923
- issues
924
- ),
925
- renderProvider: readString(
926
- raw.renderProvider,
927
- base?.renderProvider ?? "playwright",
928
- "prototyping.execution.renderProvider",
929
- configPath,
930
- issues
931
- )
948
+ browserTool
932
949
  };
933
950
  }
934
951
  function validateObsoleteCalibrationFields(raw, configPath, issues) {
@@ -3123,11 +3140,12 @@ function isValidId(value, prefix) {
3123
3140
  init_surface();
3124
3141
  var PROTOTYPING_MODES = ["low-cost", "standard", "full-harness"];
3125
3142
  var DEFAULT_PROTOTYPING_MODE = "standard";
3126
- var PROTOTYPING_MAX_ITERATIONS = {
3143
+ var PROTOTYPING_MAX_CYCLES = {
3127
3144
  "low-cost": 1,
3128
3145
  standard: 3,
3129
3146
  "full-harness": 20
3130
3147
  };
3148
+ var PROTOTYPING_MAX_ITERATIONS = PROTOTYPING_MAX_CYCLES;
3131
3149
  var PROTOTYPING_SUPPORTED_SURFACES = ["web", "mobile", "desktop", "mixed"];
3132
3150
  var VALID_MODE_SET = new Set(PROTOTYPING_MODES);
3133
3151
  var VALID_SURFACE_SET = new Set(CANONICAL_PROTOTYPING_SURFACES);
@@ -3157,14 +3175,24 @@ function resolvePrototypingMode(requested) {
3157
3175
  };
3158
3176
  }
3159
3177
  function derivePrototypingObligations(input) {
3160
- const isFullHarness = input.effectiveMode === "full-harness";
3178
+ const maxCycles = PROTOTYPING_MAX_CYCLES[input.effectiveMode];
3161
3179
  return {
3162
- requireRuntimeGate: isFullHarness,
3163
- requireUiFidelity: isFullHarness,
3164
- requireRenderBundle: isFullHarness,
3165
- requireBrowserQaBundle: isFullHarness,
3180
+ browserTool: "playwright-cli",
3181
+ requireExecutionPlan: true,
3182
+ requirePlaywrightEvidence: true,
3183
+ requireReviewBundle: true,
3184
+ requireEvaluatorReview: true,
3185
+ requireBestOfHistory: true,
3186
+ requireBreakthrough: true,
3187
+ requireIndependentReviewerGate: true,
3188
+ requirePerfect100Completion: true,
3189
+ requireRuntimeGate: true,
3190
+ requireUiFidelity: true,
3191
+ requireRenderBundle: true,
3192
+ requireBrowserQaBundle: true,
3166
3193
  requireIterations: true,
3167
- maxIterations: PROTOTYPING_MAX_ITERATIONS[input.effectiveMode],
3194
+ maxCycles,
3195
+ maxIterations: maxCycles,
3168
3196
  validCombination: true
3169
3197
  };
3170
3198
  }
@@ -3631,8 +3659,8 @@ async function readSafe4(filePath) {
3631
3659
  }
3632
3660
 
3633
3661
  // src/core/report.ts
3634
- var import_promises58 = require("fs/promises");
3635
- var import_node_path70 = __toESM(require("path"), 1);
3662
+ var import_promises59 = require("fs/promises");
3663
+ var import_node_path71 = __toESM(require("path"), 1);
3636
3664
 
3637
3665
  // src/core/contractIndex.ts
3638
3666
  var import_promises13 = require("fs/promises");
@@ -4386,15 +4414,15 @@ function asTagArray(value) {
4386
4414
  }
4387
4415
 
4388
4416
  // src/core/validate.ts
4389
- var import_node_path69 = __toESM(require("path"), 1);
4417
+ var import_node_path70 = __toESM(require("path"), 1);
4390
4418
 
4391
4419
  // src/core/version.ts
4392
4420
  var import_promises14 = require("fs/promises");
4393
4421
  var import_node_path13 = __toESM(require("path"), 1);
4394
4422
  var import_node_url = require("url");
4395
4423
  async function resolveToolVersion() {
4396
- if ("1.8.2".length > 0) {
4397
- return "1.8.2";
4424
+ if ("1.8.3".length > 0) {
4425
+ return "1.8.3";
4398
4426
  }
4399
4427
  try {
4400
4428
  const packagePath = resolvePackageJsonPath();
@@ -7325,7 +7353,7 @@ var SPEC_TAG_RE = /^SPEC-\d{4}$/;
7325
7353
  var US_ID_RE2 = /\bUS-\d{4}-\d{4}\b/g;
7326
7354
  var AC_ID_RE3 = /\bAC-\d{4}-\d{4}\b/g;
7327
7355
  var DOWNSTREAM_ID_RE = /\b(?:US|AC|BR|SC|CASE)-\d{4}-\d{4}\b/g;
7328
- async function validateTraceability(root, config, phase) {
7356
+ async function validateTraceability(root, config, options) {
7329
7357
  const issues = [];
7330
7358
  const specsRoot = resolvePath(root, config, "specsDir");
7331
7359
  const entries = await collectSpecEntries(specsRoot);
@@ -7401,7 +7429,7 @@ async function validateTraceability(root, config, phase) {
7401
7429
  issues.push(...validateLayeredEdges(entry, edgeData));
7402
7430
  issues.push(...validateLayeredHeuristics(entry, edgeData));
7403
7431
  }
7404
- if (phase !== "refinement" && allLayeredScIds.size > 0) {
7432
+ if (options.includeCodeReferences && allLayeredScIds.size > 0) {
7405
7433
  issues.push(
7406
7434
  ...await validateLayeredScCodeReferences(root, config, allLayeredScIds, specsRoot)
7407
7435
  );
@@ -10181,8 +10209,121 @@ function normalizeOptionalFragment(fragment, originalRef) {
10181
10209
  return normalized;
10182
10210
  }
10183
10211
 
10212
+ // src/core/prototyping/candidate.ts
10213
+ var CANDIDATE_ID_PATTERN = /^c[1-9]\d*$/;
10214
+ function isCandidateId(value) {
10215
+ return typeof value === "string" && CANDIDATE_ID_PATTERN.test(value);
10216
+ }
10217
+
10218
+ // src/core/prototyping/round.ts
10219
+ var EXPLORATION_ROUNDS = ["r5", "r3", "r2", "r1"];
10220
+ var ROUND_SURVIVOR_COUNT = {
10221
+ r5: 5,
10222
+ r3: 3,
10223
+ r2: 2,
10224
+ r1: 1
10225
+ };
10226
+ function isExplorationRound(value) {
10227
+ return typeof value === "string" && EXPLORATION_ROUNDS.includes(value);
10228
+ }
10229
+
10184
10230
  // src/core/validators/prototypingEvidence.ts
10185
10231
  init_utils();
10232
+
10233
+ // src/core/validators/prototyping/modeInvariant.ts
10234
+ init_utils();
10235
+ function isRecord5(v) {
10236
+ return typeof v === "object" && v !== null && !Array.isArray(v);
10237
+ }
10238
+ function detectMode(raw) {
10239
+ if (!isRecord5(raw)) return null;
10240
+ if (typeof raw.mode === "string" && isValidPrototypingMode(raw.mode)) {
10241
+ return { mode: raw.mode, source: "string" };
10242
+ }
10243
+ if (isRecord5(raw.mode) && typeof raw.mode.effective === "string") {
10244
+ const effective = raw.mode.effective;
10245
+ if (isValidPrototypingMode(effective)) {
10246
+ return { mode: effective, source: "object" };
10247
+ }
10248
+ }
10249
+ return null;
10250
+ }
10251
+ function validateModeInvariant(raw, evidencePathForIssue = ".qfai/evidence/prototyping.json") {
10252
+ if (!isRecord5(raw)) {
10253
+ return [];
10254
+ }
10255
+ const detected = detectMode(raw);
10256
+ if (!detected) {
10257
+ return [];
10258
+ }
10259
+ const issues = [];
10260
+ const expectedMaxCycles = PROTOTYPING_MAX_CYCLES[detected.mode];
10261
+ if (raw.maxCycles !== void 0 && raw.maxIterations !== void 0 && raw.maxCycles !== raw.maxIterations) {
10262
+ issues.push(
10263
+ issue(
10264
+ "QFAI-PROT-MODE-001",
10265
+ `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).`,
10266
+ "error",
10267
+ evidencePathForIssue,
10268
+ "prototyping.modeInvariant.maxCyclesAlias",
10269
+ [JSON.stringify(raw.maxCycles), JSON.stringify(raw.maxIterations)],
10270
+ "canonical",
10271
+ "Remove the redundant key or align both values; prefer maxCycles (spec-0017 REQ-0001)."
10272
+ )
10273
+ );
10274
+ }
10275
+ const maxCyclesRaw = raw.maxCycles ?? raw.maxIterations;
10276
+ if (maxCyclesRaw !== void 0) {
10277
+ if (typeof maxCyclesRaw !== "number" || !Number.isInteger(maxCyclesRaw) || maxCyclesRaw !== expectedMaxCycles) {
10278
+ const maxCyclesDisplay = JSON.stringify(maxCyclesRaw);
10279
+ issues.push(
10280
+ issue(
10281
+ "QFAI-PROT-MODE-001",
10282
+ `Mode differences are limited to maxCycles only. Expected maxCycles=${expectedMaxCycles} for mode=${detected.mode}, got ${maxCyclesDisplay}.`,
10283
+ "error",
10284
+ evidencePathForIssue,
10285
+ "prototyping.modeInvariant",
10286
+ [detected.mode, String(expectedMaxCycles), maxCyclesDisplay],
10287
+ "canonical",
10288
+ `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).`
10289
+ )
10290
+ );
10291
+ }
10292
+ }
10293
+ if (raw.browserTool !== void 0) {
10294
+ if (raw.browserTool !== "playwright-cli") {
10295
+ const browserToolDisplay = JSON.stringify(raw.browserTool);
10296
+ issues.push(
10297
+ issue(
10298
+ "QFAI-PROT-MODE-001",
10299
+ `browserTool must be "playwright-cli" (spec-0017 REQ-0002). Got: ${browserToolDisplay}`,
10300
+ "error",
10301
+ evidencePathForIssue,
10302
+ "prototyping.modeInvariant.browserTool",
10303
+ [browserToolDisplay],
10304
+ "canonical",
10305
+ 'Set prototyping.json browserTool to "playwright-cli". Playwright MCP and legacy providers are not supported in the standard harness.'
10306
+ )
10307
+ );
10308
+ }
10309
+ }
10310
+ return issues;
10311
+ }
10312
+
10313
+ // src/core/validators/prototypingEvidence.ts
10314
+ var VALID_ITERATION_KIND_SET = /* @__PURE__ */ new Set(["explore", "remix", "select", "polish", "branch"]);
10315
+ var VALID_PHASE_SET = /* @__PURE__ */ new Set([
10316
+ "planning",
10317
+ "explore",
10318
+ "remix",
10319
+ "select",
10320
+ "polish",
10321
+ "breakthrough",
10322
+ "reviewer_gate",
10323
+ "completed"
10324
+ ]);
10325
+ var REQUIRED_POLISH_CHECKS = ["critique", "fix", "recapture", "rereview", "breakthrough"];
10326
+ var LEGACY_PASS_95_FIELD = "allItemsPass" + String(95);
10186
10327
  var VALID_MODE_SOURCE_SET = /* @__PURE__ */ new Set([
10187
10328
  "explicit-request",
10188
10329
  "system-default",
@@ -10251,6 +10392,24 @@ async function validatePrototypingEvidence(root, config) {
10251
10392
  return issues;
10252
10393
  }
10253
10394
  const obligations = surface && isSupportedPrototypingSurface(surface) ? derivePrototypingObligations({ surface, effectiveMode: mode.effective }) : void 0;
10395
+ issues.push(
10396
+ ...validateModeInvariant(record2, import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"))
10397
+ );
10398
+ const isV2Record = record2.schemaVersion === "2.0" || record2.rounds !== void 0 || record2.polishCycles !== void 0;
10399
+ if (isV2Record) {
10400
+ issues.push(...validateV2Lifecycle(root, evidencePath, record2, mode, obligations));
10401
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10402
+ issues.push(
10403
+ makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10404
+ );
10405
+ }
10406
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10407
+ issues.push(
10408
+ makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10409
+ );
10410
+ }
10411
+ return issues;
10412
+ }
10254
10413
  const iterations = normalizeIterations(record2.iterations);
10255
10414
  if (!iterations || iterations.length === 0) {
10256
10415
  issues.push(
@@ -10281,10 +10440,14 @@ async function validatePrototypingEvidence(root, config) {
10281
10440
  )
10282
10441
  );
10283
10442
  }
10284
- let foundPass95 = false;
10443
+ let latestPerfect100 = false;
10444
+ let latestIterationKind = null;
10445
+ let completedPolishCount = 0;
10446
+ let polishWithBreakthroughCheck = false;
10447
+ let hasLegacyPass95CompletionMarker = false;
10285
10448
  for (const [index, candidate] of iterations.entries()) {
10286
10449
  const prefix = `iterations[${index}]`;
10287
- if (!isRecord5(candidate)) {
10450
+ if (!isRecord6(candidate)) {
10288
10451
  issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10289
10452
  continue;
10290
10453
  }
@@ -10295,12 +10458,43 @@ async function validatePrototypingEvidence(root, config) {
10295
10458
  makeSchemaIssue(root, evidencePath, `${prefix}.iteration must be a positive integer.`)
10296
10459
  );
10297
10460
  }
10298
- if (typeof iteration.allItemsPass95 !== "boolean") {
10461
+ if (typeof iteration.allReviewerAxesPerfect100 !== "boolean") {
10299
10462
  issues.push(
10300
- makeSchemaIssue(root, evidencePath, `${prefix}.allItemsPass95 must be a boolean.`)
10463
+ makeSchemaIssue(
10464
+ root,
10465
+ evidencePath,
10466
+ `${prefix}.allReviewerAxesPerfect100 must be a boolean.`
10467
+ )
10301
10468
  );
10302
- } else if (iteration.allItemsPass95) {
10303
- foundPass95 = true;
10469
+ } else if (iteration.allReviewerAxesPerfect100) {
10470
+ latestPerfect100 = true;
10471
+ } else {
10472
+ latestPerfect100 = false;
10473
+ }
10474
+ if (iteration[LEGACY_PASS_95_FIELD] === true) {
10475
+ hasLegacyPass95CompletionMarker = true;
10476
+ }
10477
+ if (iteration.kind !== void 0) {
10478
+ if (typeof iteration.kind !== "string" || !VALID_ITERATION_KIND_SET.has(iteration.kind)) {
10479
+ issues.push(
10480
+ issue(
10481
+ "QFAI-PROT-285",
10482
+ `${prefix}.kind must be one of explore|remix|select|polish|branch.`,
10483
+ "error",
10484
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10485
+ "prototypingEvidence.phase",
10486
+ void 0,
10487
+ "canonical",
10488
+ "iteration kind \u3092 phase state machine \u306B\u5408\u308F\u305B\u3066\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10489
+ )
10490
+ );
10491
+ } else {
10492
+ latestIterationKind = iteration.kind;
10493
+ if (iteration.kind === "polish" && hasCompletePolishChecks(iteration.checks)) {
10494
+ completedPolishCount++;
10495
+ polishWithBreakthroughCheck = true;
10496
+ }
10497
+ }
10304
10498
  }
10305
10499
  if (!Array.isArray(iteration.reviewerScores) || iteration.reviewerScores.length === 0) {
10306
10500
  issues.push(
@@ -10310,7 +10504,7 @@ async function validatePrototypingEvidence(root, config) {
10310
10504
  }
10311
10505
  for (const [reviewerIndex, reviewer] of iteration.reviewerScores.entries()) {
10312
10506
  const reviewerPath = `${prefix}.reviewerScores[${reviewerIndex}]`;
10313
- if (!isRecord5(reviewer)) {
10507
+ if (!isRecord6(reviewer)) {
10314
10508
  issues.push(makeSchemaIssue(root, evidencePath, `${reviewerPath} must be an object.`));
10315
10509
  continue;
10316
10510
  }
@@ -10327,7 +10521,7 @@ async function validatePrototypingEvidence(root, config) {
10327
10521
  }
10328
10522
  for (const [scoreIndex, score] of reviewer.scores.entries()) {
10329
10523
  const scorePath = `${reviewerPath}.scores[${scoreIndex}]`;
10330
- if (!isRecord5(score)) {
10524
+ if (!isRecord6(score)) {
10331
10525
  issues.push(makeSchemaIssue(root, evidencePath, `${scorePath} must be an object.`));
10332
10526
  continue;
10333
10527
  }
@@ -10367,27 +10561,131 @@ async function validatePrototypingEvidence(root, config) {
10367
10561
  }
10368
10562
  }
10369
10563
  }
10564
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
10565
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
10566
+ issues.push(
10567
+ issue(
10568
+ "QFAI-PROT-285",
10569
+ "phase.current must follow the prototyping phase state machine.",
10570
+ "error",
10571
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10572
+ "prototypingEvidence.phase",
10573
+ void 0,
10574
+ "canonical",
10575
+ "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"
10576
+ )
10577
+ );
10578
+ }
10579
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
10580
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
10581
+ if (hasLegacyPass95CompletionMarker && completionClaimed) {
10582
+ issues.push(
10583
+ issue(
10584
+ "QFAI-PROT-288",
10585
+ "The legacy 95-point completion field is no longer a completion border; completion requires allReviewerAxesPerfect100.",
10586
+ "error",
10587
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10588
+ "prototypingEvidence.perfect100",
10589
+ void 0,
10590
+ "canonical",
10591
+ "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"
10592
+ )
10593
+ );
10594
+ }
10595
+ if (completionClaimed) {
10596
+ if (!latestPerfect100 || !allReviewerScoresArePerfect100(iterations)) {
10597
+ issues.push(
10598
+ issue(
10599
+ "QFAI-PROT-287",
10600
+ "Completion requires every reviewer to score every evaluation axis at 100.",
10601
+ "error",
10602
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10603
+ "prototypingEvidence.perfect100",
10604
+ void 0,
10605
+ "canonical",
10606
+ "completionClaimed/completed \u3092\u8A18\u9332\u3059\u308B\u524D\u306B\u3001\u5168 reviewerScores[].scores[].score \u3092 100 \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10607
+ )
10608
+ );
10609
+ }
10610
+ if (latestIterationKind === "select") {
10611
+ issues.push(
10612
+ issue(
10613
+ "QFAI-PROT-285",
10614
+ "Selection funnel completion is not stage completion; latest iteration cannot remain select when completion is claimed.",
10615
+ "error",
10616
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10617
+ "prototypingEvidence.phase",
10618
+ void 0,
10619
+ "canonical",
10620
+ "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"
10621
+ )
10622
+ );
10623
+ }
10624
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
10625
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
10626
+ issues.push(
10627
+ issue(
10628
+ "QFAI-PROT-286",
10629
+ "Completion requires at least one completed post-selection polish iteration.",
10630
+ "error",
10631
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10632
+ "prototypingEvidence.postSelectionPolish",
10633
+ void 0,
10634
+ "canonical",
10635
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish iteration \u306B critique/fix/recapture/rereview/breakthrough checks \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10636
+ )
10637
+ );
10638
+ }
10639
+ if (!polishWithBreakthroughCheck) {
10640
+ issues.push(
10641
+ issue(
10642
+ "QFAI-PROT-286",
10643
+ "Completion requires a polish iteration with critique/fix/recapture/rereview/breakthrough checks.",
10644
+ "error",
10645
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10646
+ "prototypingEvidence.postSelectionPolish",
10647
+ void 0,
10648
+ "canonical",
10649
+ "polish iteration \u306E checks.critique/fix/recapture/rereview/breakthrough \u3092 true \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10650
+ )
10651
+ );
10652
+ }
10653
+ if (!isRecord6(record2.completionCertificate)) {
10654
+ issues.push(
10655
+ issue(
10656
+ "QFAI-PROT-289",
10657
+ "completionCertificate is required when completion is claimed.",
10658
+ "error",
10659
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10660
+ "prototypingEvidence.completionCertificate",
10661
+ void 0,
10662
+ "canonical",
10663
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10664
+ )
10665
+ );
10666
+ }
10667
+ }
10370
10668
  const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
10371
- if (!foundPass95 && iterations.length < maxIterations) {
10669
+ if (!latestPerfect100 && iterations.length < maxIterations) {
10372
10670
  issues.push(
10373
10671
  issue(
10374
10672
  "QFAI-PROT-282",
10375
- `mode=${mode.effective} has not reached all-items-pass-95 and has remaining iterations.`,
10673
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining iterations.`,
10376
10674
  "warning",
10377
10675
  import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10378
10676
  "prototypingEvidence.convergence",
10379
10677
  [String(iterations.length), String(maxIterations)],
10380
10678
  "canonical",
10381
- "95 \u70B9\u672A\u6E80\u306E\u9805\u76EE\u304C\u6B8B\u3063\u3066\u3044\u308B\u305F\u3081\u3001mode \u4E0A\u9650\u306B\u9054\u3059\u308B\u307E\u3067\u53CD\u5FA9\u3092\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10679
+ "\u5168 reviewer / \u5168 axis \u304C 100 \u70B9\u306B\u9054\u3057\u3066\u3044\u306A\u3044\u305F\u3081\u3001mode \u4E0A\u9650\u306B\u9054\u3059\u308B\u307E\u3067\u53CD\u5FA9\u3092\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10382
10680
  )
10383
10681
  );
10384
10682
  }
10385
- if (obligations?.requireRuntimeGate && !isRecord5(record2.runtimeGate)) {
10683
+ if (obligations?.requireRuntimeGate && !isRecord6(record2.runtimeGate)) {
10386
10684
  issues.push(
10387
10685
  makeSchemaIssue(root, evidencePath, "runtimeGate is required in full-harness mode.")
10388
10686
  );
10389
10687
  }
10390
- if (obligations?.requireUiFidelity && !isRecord5(record2.uiFidelity)) {
10688
+ if (obligations?.requireUiFidelity && !isRecord6(record2.uiFidelity)) {
10391
10689
  issues.push(
10392
10690
  makeSchemaIssue(root, evidencePath, "uiFidelity is required in full-harness mode.")
10393
10691
  );
@@ -10426,100 +10724,448 @@ function normalizeIterations(value) {
10426
10724
  }
10427
10725
  return value;
10428
10726
  }
10429
- function isRecord5(value) {
10430
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10431
- }
10432
- async function readJsonFile(filePath) {
10433
- try {
10434
- const raw = await (0, import_promises32.readFile)(filePath, "utf-8");
10435
- const parsed = JSON.parse(raw);
10436
- return isRecord5(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
10437
- } catch {
10438
- try {
10439
- await (0, import_promises32.readFile)(filePath, "utf-8");
10440
- return { status: "invalid" };
10441
- } catch {
10442
- return { status: "missing" };
10443
- }
10727
+ function normalizeRounds(value) {
10728
+ if (!Array.isArray(value)) {
10729
+ return null;
10444
10730
  }
10731
+ return value;
10445
10732
  }
10446
- function makeSchemaIssue(root, evidencePath, message) {
10447
- return issue(
10448
- "QFAI-PROT-299",
10449
- message,
10450
- "error",
10451
- import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10452
- "prototypingEvidence.schema"
10453
- );
10454
- }
10455
-
10456
- // src/core/validators/prototypingDesignSystem.ts
10457
- var import_promises33 = require("fs/promises");
10458
- var import_node_path36 = __toESM(require("path"), 1);
10459
- var RULE_CODE = "PROT-DS01";
10460
- var RULE_NAME = "prototyping.designSystemCompliance";
10461
- var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
10462
- var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
10463
- function isRecord6(value) {
10464
- return typeof value === "object" && value !== null && !Array.isArray(value);
10733
+ function normalizePolishCycles(value) {
10734
+ if (!Array.isArray(value)) {
10735
+ return null;
10736
+ }
10737
+ return value;
10465
10738
  }
10466
- function detectMode(raw) {
10467
- if (!isRecord6(raw)) {
10468
- return "other";
10739
+ function normalizePhaseCurrent(value) {
10740
+ if (!isRecord6(value)) {
10741
+ return null;
10469
10742
  }
10470
- const topLevel = raw.mode;
10471
- if (isRecord6(topLevel) && topLevel.effective === "full-harness") {
10472
- return "full-harness";
10743
+ return typeof value.current === "string" ? value.current : null;
10744
+ }
10745
+ function normalizeFullHarnessStatus(value) {
10746
+ if (!isRecord6(value)) {
10747
+ return null;
10473
10748
  }
10474
- return "other";
10749
+ return typeof value.status === "string" ? value.status : null;
10475
10750
  }
10476
- function detectScorePresence(raw) {
10477
- if (!isRecord6(raw)) {
10751
+ function hasCompletePolishChecks(value) {
10752
+ if (!isRecord6(value)) {
10478
10753
  return false;
10479
10754
  }
10480
- const trace = raw.scoringTrace;
10481
- if (!isRecord6(trace)) {
10755
+ return REQUIRED_POLISH_CHECKS.every((key) => value[key] === true);
10756
+ }
10757
+ function allReviewerScoresArePerfect100(iterations) {
10758
+ const candidate = iterations[iterations.length - 1];
10759
+ if (!isRecord6(candidate) || !Array.isArray(candidate.reviewerScores)) {
10482
10760
  return false;
10483
10761
  }
10484
- return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
10485
- }
10486
- async function fileExists2(filePath) {
10487
- try {
10488
- await (0, import_promises33.access)(filePath);
10489
- return true;
10490
- } catch (err) {
10491
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10762
+ return candidate.reviewerScores.every((reviewer) => {
10763
+ if (!isRecord6(reviewer) || !Array.isArray(reviewer.scores) || reviewer.scores.length === 0) {
10492
10764
  return false;
10493
10765
  }
10494
- return false;
10495
- }
10766
+ return reviewer.scores.every((score) => isRecord6(score) && score.score === 100);
10767
+ });
10496
10768
  }
10497
- async function readFileSafe(filePath) {
10498
- try {
10499
- return await (0, import_promises33.readFile)(filePath, "utf-8");
10500
- } catch (err) {
10501
- if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
10502
- return null;
10503
- }
10504
- return null;
10769
+ function validateV2Lifecycle(root, evidencePath, record2, mode, obligations) {
10770
+ const issues = [];
10771
+ const rounds = normalizeRounds(record2.rounds);
10772
+ if (!rounds || rounds.length === 0) {
10773
+ issues.push(
10774
+ issue(
10775
+ "QFAI-PROT-280",
10776
+ "prototyping evidence requires at least one round entry.",
10777
+ "error",
10778
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10779
+ "prototypingEvidence.rounds",
10780
+ void 0,
10781
+ "canonical",
10782
+ "rounds[] \u306B\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u306E\u63A2\u7D22 round \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
10783
+ )
10784
+ );
10785
+ return issues;
10505
10786
  }
10506
- }
10507
- async function loadPrototypingArtifact(evidenceDir) {
10508
- const jsonPath = import_node_path36.default.join(evidenceDir, "prototyping.json");
10509
- const jsonRaw = await readFileSafe(jsonPath);
10510
- if (jsonRaw !== null) {
10511
- let parsed = null;
10512
- try {
10513
- parsed = JSON.parse(jsonRaw);
10514
- } catch {
10515
- parsed = null;
10516
- }
10517
- return {
10518
- source: "json",
10519
- fileName: "prototyping.json",
10520
- mode: detectMode(parsed),
10521
- designSystemCompliancePresent: detectScorePresence(parsed)
10522
- };
10787
+ const polishCycles = normalizePolishCycles(record2.polishCycles) ?? [];
10788
+ if (obligations && polishCycles.length > obligations.maxIterations) {
10789
+ issues.push(
10790
+ issue(
10791
+ "QFAI-PROT-281",
10792
+ `mode=${mode.effective} exceeds max polish cycles (${obligations.maxIterations}).`,
10793
+ "error",
10794
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10795
+ "prototypingEvidence.maxIterations",
10796
+ [String(polishCycles.length), String(obligations.maxIterations)],
10797
+ "canonical",
10798
+ `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`
10799
+ )
10800
+ );
10801
+ }
10802
+ let latestPerfect100 = false;
10803
+ let completedPolishCount = 0;
10804
+ let polishWithBreakthroughCheck = false;
10805
+ for (const [index, candidate] of rounds.entries()) {
10806
+ const prefix = `rounds[${index}]`;
10807
+ if (!isRecord6(candidate)) {
10808
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10809
+ continue;
10810
+ }
10811
+ let validRound = null;
10812
+ if (!isExplorationRound(candidate.round)) {
10813
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix}.round must be r5|r3|r2|r1.`));
10814
+ } else {
10815
+ validRound = candidate.round;
10816
+ if (index >= EXPLORATION_ROUNDS.length) {
10817
+ issues.push(
10818
+ makeSchemaIssue(
10819
+ root,
10820
+ evidencePath,
10821
+ `${prefix} exceeds the funnel limit (rounds[] must contain at most ${EXPLORATION_ROUNDS.length} entries: ${EXPLORATION_ROUNDS.join("\u2192")}).`
10822
+ )
10823
+ );
10824
+ } else {
10825
+ const expectedRound = EXPLORATION_ROUNDS[index];
10826
+ if (expectedRound !== void 0 && validRound !== expectedRound) {
10827
+ issues.push(
10828
+ makeSchemaIssue(
10829
+ root,
10830
+ evidencePath,
10831
+ `${prefix}.round must be ${expectedRound} (rounds[] must follow ${EXPLORATION_ROUNDS.join("\u2192")}); got ${validRound}.`
10832
+ )
10833
+ );
10834
+ }
10835
+ }
10836
+ }
10837
+ const candidateIds = [];
10838
+ const seenCandidateIds = /* @__PURE__ */ new Set();
10839
+ if (!Array.isArray(candidate.candidates) || candidate.candidates.length === 0) {
10840
+ issues.push(
10841
+ makeSchemaIssue(root, evidencePath, `${prefix}.candidates must be a non-empty array.`)
10842
+ );
10843
+ } else {
10844
+ for (const [candidateIndex, roundCandidate] of candidate.candidates.entries()) {
10845
+ if (!isRecord6(roundCandidate)) {
10846
+ issues.push(
10847
+ makeSchemaIssue(
10848
+ root,
10849
+ evidencePath,
10850
+ `${prefix}.candidates[${candidateIndex}] must be an object.`
10851
+ )
10852
+ );
10853
+ continue;
10854
+ }
10855
+ if (!isCandidateId(roundCandidate.candidateId)) {
10856
+ issues.push(
10857
+ makeSchemaIssue(
10858
+ root,
10859
+ evidencePath,
10860
+ `${prefix}.candidates[${candidateIndex}].candidateId must be a valid candidate id.`
10861
+ )
10862
+ );
10863
+ } else if (seenCandidateIds.has(roundCandidate.candidateId)) {
10864
+ issues.push(
10865
+ makeSchemaIssue(
10866
+ root,
10867
+ evidencePath,
10868
+ `${prefix}.candidates[${candidateIndex}].candidateId must be unique within the round; ${roundCandidate.candidateId} already appears earlier.`
10869
+ )
10870
+ );
10871
+ } else {
10872
+ seenCandidateIds.add(roundCandidate.candidateId);
10873
+ candidateIds.push(roundCandidate.candidateId);
10874
+ }
10875
+ }
10876
+ if (validRound) {
10877
+ const expectedCandidateCount = ROUND_SURVIVOR_COUNT[validRound];
10878
+ if (candidate.candidates.length !== expectedCandidateCount) {
10879
+ issues.push(
10880
+ makeSchemaIssue(
10881
+ root,
10882
+ evidencePath,
10883
+ `${prefix}.candidates must contain exactly ${expectedCandidateCount} entries for round ${validRound}; got ${candidate.candidates.length}.`
10884
+ )
10885
+ );
10886
+ }
10887
+ }
10888
+ }
10889
+ const screenMap = isRecord6(candidate.screenEvidenceByCandidate) ? candidate.screenEvidenceByCandidate : null;
10890
+ if (screenMap === null) {
10891
+ issues.push(
10892
+ makeSchemaIssue(
10893
+ root,
10894
+ evidencePath,
10895
+ `${prefix}.screenEvidenceByCandidate must be an object keyed by candidateId.`
10896
+ )
10897
+ );
10898
+ }
10899
+ const reviewMap = isRecord6(candidate.evaluatorReviewRefsByCandidate) ? candidate.evaluatorReviewRefsByCandidate : null;
10900
+ if (reviewMap === null) {
10901
+ issues.push(
10902
+ makeSchemaIssue(
10903
+ root,
10904
+ evidencePath,
10905
+ `${prefix}.evaluatorReviewRefsByCandidate must be an object keyed by candidateId.`
10906
+ )
10907
+ );
10908
+ }
10909
+ for (const candidateId of candidateIds) {
10910
+ if (screenMap) {
10911
+ const screens = screenMap[candidateId];
10912
+ if (!Array.isArray(screens) || screens.length === 0) {
10913
+ issues.push(
10914
+ makeSchemaIssue(
10915
+ root,
10916
+ evidencePath,
10917
+ `${prefix}.screenEvidenceByCandidate.${candidateId} must be a non-empty array of screen artifact refs.`
10918
+ )
10919
+ );
10920
+ }
10921
+ }
10922
+ if (reviewMap) {
10923
+ const reviewRef = reviewMap[candidateId];
10924
+ if (typeof reviewRef !== "string" || reviewRef.trim().length === 0) {
10925
+ issues.push(
10926
+ makeSchemaIssue(
10927
+ root,
10928
+ evidencePath,
10929
+ `${prefix}.evaluatorReviewRefsByCandidate.${candidateId} must be a non-empty string.`
10930
+ )
10931
+ );
10932
+ }
10933
+ }
10934
+ }
10935
+ if (typeof candidate.allAxesPerfect100 !== "boolean") {
10936
+ issues.push(
10937
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
10938
+ );
10939
+ } else {
10940
+ latestPerfect100 = candidate.allAxesPerfect100;
10941
+ }
10942
+ }
10943
+ for (const [index, cycle] of polishCycles.entries()) {
10944
+ const prefix = `polishCycles[${index}]`;
10945
+ if (!isRecord6(cycle)) {
10946
+ issues.push(makeSchemaIssue(root, evidencePath, `${prefix} must be an object.`));
10947
+ continue;
10948
+ }
10949
+ if (typeof cycle.cycle !== "number" || !Number.isInteger(cycle.cycle) || cycle.cycle <= 0) {
10950
+ issues.push(
10951
+ makeSchemaIssue(root, evidencePath, `${prefix}.cycle must be a positive integer.`)
10952
+ );
10953
+ }
10954
+ if (typeof cycle.kind !== "string" || !["polish", "branch", "reviewer_gate", "completed"].includes(cycle.kind)) {
10955
+ issues.push(
10956
+ makeSchemaIssue(
10957
+ root,
10958
+ evidencePath,
10959
+ `${prefix}.kind must be polish|branch|reviewer_gate|completed.`
10960
+ )
10961
+ );
10962
+ }
10963
+ if (typeof cycle.evaluatorReviewRef !== "string" || cycle.evaluatorReviewRef.trim().length === 0) {
10964
+ issues.push(
10965
+ makeSchemaIssue(root, evidencePath, `${prefix}.evaluatorReviewRef must be non-empty.`)
10966
+ );
10967
+ }
10968
+ if (typeof cycle.allAxesPerfect100 !== "boolean") {
10969
+ issues.push(
10970
+ makeSchemaIssue(root, evidencePath, `${prefix}.allAxesPerfect100 must be a boolean.`)
10971
+ );
10972
+ } else {
10973
+ latestPerfect100 = cycle.allAxesPerfect100;
10974
+ }
10975
+ if (cycle.kind === "polish") {
10976
+ completedPolishCount += 1;
10977
+ if (typeof cycle.breakthroughRef === "string" && cycle.breakthroughRef.trim().length > 0) {
10978
+ polishWithBreakthroughCheck = true;
10979
+ }
10980
+ }
10981
+ }
10982
+ const phaseCurrent = normalizePhaseCurrent(record2.phase);
10983
+ if (phaseCurrent !== null && !VALID_PHASE_SET.has(phaseCurrent)) {
10984
+ issues.push(
10985
+ issue(
10986
+ "QFAI-PROT-285",
10987
+ "phase.current must follow the prototyping phase state machine.",
10988
+ "error",
10989
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
10990
+ "prototypingEvidence.phase",
10991
+ void 0,
10992
+ "canonical",
10993
+ "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"
10994
+ )
10995
+ );
10996
+ }
10997
+ const fullHarnessStatus = normalizeFullHarnessStatus(record2.fullHarness);
10998
+ const completionClaimed = record2.completionClaimed === true || record2.completionEligible === true || phaseCurrent === "completed" || fullHarnessStatus === "completed";
10999
+ if (completionClaimed) {
11000
+ if (!latestPerfect100) {
11001
+ issues.push(
11002
+ issue(
11003
+ "QFAI-PROT-287",
11004
+ "Completion requires every reviewer to score every evaluation axis at 100.",
11005
+ "error",
11006
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11007
+ "prototypingEvidence.perfect100",
11008
+ void 0,
11009
+ "canonical",
11010
+ "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"
11011
+ )
11012
+ );
11013
+ }
11014
+ const postSelectionPolishCount = typeof record2.postSelectionPolishCount === "number" ? record2.postSelectionPolishCount : completedPolishCount;
11015
+ if (!Number.isInteger(postSelectionPolishCount) || postSelectionPolishCount < 1) {
11016
+ issues.push(
11017
+ issue(
11018
+ "QFAI-PROT-286",
11019
+ "Completion requires at least one completed post-selection polish cycle.",
11020
+ "error",
11021
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11022
+ "prototypingEvidence.postSelectionPolish",
11023
+ void 0,
11024
+ "canonical",
11025
+ "postSelectionPolishCount >= 1 \u3068\u3057\u3001polish cycle \u3092\u5C11\u306A\u304F\u3068\u3082 1 \u4EF6\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11026
+ )
11027
+ );
11028
+ }
11029
+ if (!polishWithBreakthroughCheck) {
11030
+ issues.push(
11031
+ issue(
11032
+ "QFAI-PROT-286",
11033
+ "Completion requires a polish cycle with breakthrough evidence.",
11034
+ "error",
11035
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11036
+ "prototypingEvidence.postSelectionPolish",
11037
+ void 0,
11038
+ "canonical",
11039
+ "polish cycle \u306B breakthroughRef \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11040
+ )
11041
+ );
11042
+ }
11043
+ if (!isRecord6(record2.completionCertificate)) {
11044
+ issues.push(
11045
+ issue(
11046
+ "QFAI-PROT-289",
11047
+ "completionCertificate is required when completion is claimed.",
11048
+ "error",
11049
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11050
+ "prototypingEvidence.completionCertificate",
11051
+ void 0,
11052
+ "canonical",
11053
+ "reviewerGateResult/validateCommand/bestOfHistoryRef/breakthroughRef \u3092\u542B\u3080 completionCertificate \u3092\u8A18\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11054
+ )
11055
+ );
11056
+ }
11057
+ }
11058
+ const maxIterations = PROTOTYPING_MAX_ITERATIONS[mode.effective];
11059
+ if (!latestPerfect100 && polishCycles.length < maxIterations) {
11060
+ issues.push(
11061
+ issue(
11062
+ "QFAI-PROT-282",
11063
+ `mode=${mode.effective} has not reached all-reviewer-axes-perfect-100 and has remaining polish cycles.`,
11064
+ "warning",
11065
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11066
+ "prototypingEvidence.convergence",
11067
+ [String(polishCycles.length), String(maxIterations)],
11068
+ "canonical",
11069
+ "\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"
11070
+ )
11071
+ );
11072
+ }
11073
+ return issues;
11074
+ }
11075
+ function isRecord6(value) {
11076
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11077
+ }
11078
+ async function readJsonFile(filePath) {
11079
+ try {
11080
+ const raw = await (0, import_promises32.readFile)(filePath, "utf-8");
11081
+ const parsed = JSON.parse(raw);
11082
+ return isRecord6(parsed) ? { status: "ok", value: parsed } : { status: "invalid" };
11083
+ } catch {
11084
+ try {
11085
+ await (0, import_promises32.readFile)(filePath, "utf-8");
11086
+ return { status: "invalid" };
11087
+ } catch {
11088
+ return { status: "missing" };
11089
+ }
11090
+ }
11091
+ }
11092
+ function makeSchemaIssue(root, evidencePath, message) {
11093
+ return issue(
11094
+ "QFAI-PROT-299",
11095
+ message,
11096
+ "error",
11097
+ import_node_path35.default.relative(root, evidencePath).replace(/\\/g, "/"),
11098
+ "prototypingEvidence.schema"
11099
+ );
11100
+ }
11101
+
11102
+ // src/core/validators/prototypingDesignSystem.ts
11103
+ var import_promises33 = require("fs/promises");
11104
+ var import_node_path36 = __toESM(require("path"), 1);
11105
+ var RULE_CODE = "PROT-DS01";
11106
+ var RULE_NAME = "prototyping.designSystemCompliance";
11107
+ var MESSAGE = "prototyping.json scoringTrace is missing required `designSystemCompliance` score.";
11108
+ var SUGGESTED_ACTION = "Add `scoringTrace.designSystemCompliance` (numeric score 0..100 or null with rationale) to prototyping.json.";
11109
+ function isRecord7(value) {
11110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11111
+ }
11112
+ function detectMode2(raw) {
11113
+ if (!isRecord7(raw)) {
11114
+ return "other";
11115
+ }
11116
+ const topLevel = raw.mode;
11117
+ if (isRecord7(topLevel) && topLevel.effective === "full-harness") {
11118
+ return "full-harness";
11119
+ }
11120
+ return "other";
11121
+ }
11122
+ function detectScorePresence(raw) {
11123
+ if (!isRecord7(raw)) {
11124
+ return false;
11125
+ }
11126
+ const trace = raw.scoringTrace;
11127
+ if (!isRecord7(trace)) {
11128
+ return false;
11129
+ }
11130
+ return Object.prototype.hasOwnProperty.call(trace, "designSystemCompliance");
11131
+ }
11132
+ async function fileExists2(filePath) {
11133
+ try {
11134
+ await (0, import_promises33.access)(filePath);
11135
+ return true;
11136
+ } catch (err) {
11137
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11138
+ return false;
11139
+ }
11140
+ return false;
11141
+ }
11142
+ }
11143
+ async function readFileSafe(filePath) {
11144
+ try {
11145
+ return await (0, import_promises33.readFile)(filePath, "utf-8");
11146
+ } catch (err) {
11147
+ if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
11148
+ return null;
11149
+ }
11150
+ return null;
11151
+ }
11152
+ }
11153
+ async function loadPrototypingArtifact(evidenceDir) {
11154
+ const jsonPath = import_node_path36.default.join(evidenceDir, "prototyping.json");
11155
+ const jsonRaw = await readFileSafe(jsonPath);
11156
+ if (jsonRaw !== null) {
11157
+ let parsed = null;
11158
+ try {
11159
+ parsed = JSON.parse(jsonRaw);
11160
+ } catch {
11161
+ parsed = null;
11162
+ }
11163
+ return {
11164
+ source: "json",
11165
+ fileName: "prototyping.json",
11166
+ mode: detectMode2(parsed),
11167
+ designSystemCompliancePresent: detectScorePresence(parsed)
11168
+ };
10523
11169
  }
10524
11170
  return {
10525
11171
  source: "none",
@@ -16351,16 +16997,181 @@ function validatePrototypingSkillContent(content) {
16351
16997
  };
16352
16998
  }
16353
16999
 
17000
+ // src/core/validators/testTodoStubs.ts
17001
+ var import_promises56 = require("fs/promises");
17002
+ var import_node_path69 = __toESM(require("path"), 1);
17003
+ init_utils();
17004
+ var TEST_TODO_PATTERN = /\b(it|test|describe)\.todo\s*\(/g;
17005
+ async function validateTestTodoStubs(root, config) {
17006
+ if (!config.validation.testStrategy.forbidTestTodoStubs) {
17007
+ return [];
17008
+ }
17009
+ const globs = config.validation.traceability.testFileGlobs;
17010
+ if (globs.length === 0) {
17011
+ return [];
17012
+ }
17013
+ const excludeGlobs = Array.from(
17014
+ /* @__PURE__ */ new Set([
17015
+ ...DEFAULT_TEST_FILE_EXCLUDE_GLOBS,
17016
+ ...config.validation.traceability.testFileExcludeGlobs
17017
+ ])
17018
+ );
17019
+ const { files } = await collectFilesByGlobs(root, {
17020
+ globs,
17021
+ ignore: excludeGlobs,
17022
+ limit: DEFAULT_GLOB_FILE_LIMIT
17023
+ });
17024
+ const issues = [];
17025
+ for (const absFile of files) {
17026
+ const relFile = import_node_path69.default.relative(root, absFile).replace(/\\/g, "/");
17027
+ let content;
17028
+ try {
17029
+ content = await (0, import_promises56.readFile)(absFile, "utf-8");
17030
+ } catch {
17031
+ continue;
17032
+ }
17033
+ const lines = content.split(/\r?\n/);
17034
+ for (let i = 0; i < lines.length; i += 1) {
17035
+ const line = lines[i] ?? "";
17036
+ const lineNumber = i + 1;
17037
+ for (const match of line.matchAll(TEST_TODO_PATTERN)) {
17038
+ const matchedKind = match[1];
17039
+ const stubIssue = issue(
17040
+ "QFAI-TEST-001",
17041
+ `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).`,
17042
+ "error",
17043
+ relFile,
17044
+ "validation.testStrategy.forbidTestTodoStubs",
17045
+ [`${matchedKind}.todo`],
17046
+ "canonical",
17047
+ "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."
17048
+ );
17049
+ stubIssue.loc = { line: lineNumber };
17050
+ issues.push(stubIssue);
17051
+ }
17052
+ }
17053
+ }
17054
+ return issues;
17055
+ }
17056
+
16354
17057
  // src/core/validate.ts
16355
17058
  init_utils();
16356
17059
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
16357
17060
  async function validateProject(root, configResult, options = {}) {
16358
17061
  const resolved = configResult ?? await loadConfig(root);
16359
17062
  const { config, issues: configIssues } = resolved;
16360
- const phase = options.phase ?? "full";
16361
- const atddCodeTraceabilityIssues = phase === "refinement" ? [] : await validateAtddCodeTraceability(root, config);
17063
+ const profile = options.profile ?? "full";
17064
+ const findings = [
17065
+ ...configIssues,
17066
+ ...await runProfileValidators(root, config, profile, options.platform)
17067
+ ];
17068
+ const { issues, waivers } = await applyWaivers(root, findings);
17069
+ const specsRoot = resolvePath(root, config, "specsDir");
17070
+ const scenarioFiles = await collectScenarioFiles(specsRoot);
17071
+ const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
17072
+ const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
17073
+ root,
17074
+ config.validation.traceability.testFileGlobs,
17075
+ config.validation.traceability.testFileExcludeGlobs
17076
+ );
17077
+ const scCoverage = buildScCoverage(scIds, scTestRefs);
17078
+ const toolVersion = await resolveToolVersion();
17079
+ return {
17080
+ toolVersion,
17081
+ profile,
17082
+ issues,
17083
+ counts: countIssues(issues),
17084
+ traceability: {
17085
+ sc: scCoverage,
17086
+ testFiles
17087
+ },
17088
+ waivers
17089
+ };
17090
+ }
17091
+ async function runProfileValidators(root, config, profile, platformOption) {
17092
+ switch (profile) {
17093
+ case "discussion":
17094
+ return runDiscussionValidators(root, config);
17095
+ case "sdd":
17096
+ return runSddValidators(root, config);
17097
+ case "prototyping":
17098
+ return runPrototypingValidators(root, config, platformOption);
17099
+ case "atdd":
17100
+ return runAtddValidators(root, config);
17101
+ case "tdd":
17102
+ return runTddValidators(root, config);
17103
+ case "verify":
17104
+ case "full":
17105
+ return runFullValidators(root, config, platformOption);
17106
+ }
17107
+ }
17108
+ async function runDiscussionValidators(root, config) {
17109
+ return [
17110
+ ...await validateDiscussionMermaid(root),
17111
+ ...await validateDiscussionPackReadiness(root, config),
17112
+ ...await validateDiscussionVisuals(root),
17113
+ ...await validateDiscussionDesignHardening(root, config),
17114
+ ...await validateResearchSummary(root, config),
17115
+ ...await runCanonicalUixValidators(root, config)
17116
+ ];
17117
+ }
17118
+ async function runSddValidators(root, config, includeCodeReferences = false) {
17119
+ return [
17120
+ ...await validateMermaidEnforcement(root),
17121
+ ...await validateSpecPacks(root, config),
17122
+ ...await validateStatusInSpecs(root, config),
17123
+ ...await validateDensityHints(root, config),
17124
+ ...await validateSpecSplitByCapability(root, config),
17125
+ ...await validateLayeredTraceability(root, config),
17126
+ ...await validateOrphanProhibition(root, config),
17127
+ ...await validateLayerCoverage(root, config),
17128
+ ...await validateContractReferences(root, config),
17129
+ ...await validateTraceability(root, config, { includeCodeReferences }),
17130
+ ...await validateDefinedIds(root, config),
17131
+ ...await validateContracts(root, config),
17132
+ ...await validateNavigationFlow(root, config)
17133
+ ];
17134
+ }
17135
+ async function runPrototypingValidators(root, config, platformOption) {
17136
+ return [
17137
+ ...await runUiuxValidators(root, config, platformOption),
17138
+ ...await validatePrototypingEvidence(root, config),
17139
+ ...await validateBreakthroughEvidence(root, config),
17140
+ ...await validatePrototypingDesignSystem(root, config),
17141
+ ...await validateUiEvidenceArtifacts(root, config),
17142
+ ...await validateRenderCritique(root, config),
17143
+ ...await validateDesignFidelity(root, config),
17144
+ ...await validateDesignContractReadiness(root, config)
17145
+ ];
17146
+ }
17147
+ async function runAtddValidators(root, config) {
17148
+ return [...await validateAtddCodeTraceability(root, config)];
17149
+ }
17150
+ async function runTddValidators(root, config, includeTraceability = true) {
17151
+ return [
17152
+ ...await validateTddList(root, config),
17153
+ ...await validateTestTodoStubs(root, config),
17154
+ ...includeTraceability ? await validateTraceability(root, config, { includeCodeReferences: true }) : [],
17155
+ ...await validateTraceabilityIntegrity(root, config)
17156
+ ];
17157
+ }
17158
+ async function runFullValidators(root, config, platformOption) {
17159
+ return [
17160
+ ...await validateRepositoryHygiene(root, config),
17161
+ ...await validateSkillsIntegrity(root, config),
17162
+ ...await validateAssistantAssets(root, config),
17163
+ ...await runDiscussionValidators(root, config),
17164
+ ...await runSddValidators(root, config, true),
17165
+ ...await validateReviewArtifacts(root),
17166
+ ...await runPrototypingValidators(root, config, platformOption),
17167
+ ...await runAtddValidators(root, config),
17168
+ ...await runTddValidators(root, config, false),
17169
+ ...await validatePrototypingSkill(root, config)
17170
+ ];
17171
+ }
17172
+ async function runUiuxValidators(root, config, platformOption) {
16362
17173
  const uiuxStart = performance.now();
16363
- const platformResult = await detectPlatform(root, config, options.platform);
17174
+ const platformResult = await detectPlatform(root, config, platformOption);
16364
17175
  const platform = platformResult.platform;
16365
17176
  const uiuxValidators = [
16366
17177
  () => validateDesignToken(root, config),
@@ -16386,68 +17197,13 @@ async function validateProject(root, configResult, options = {}) {
16386
17197
  rule: "uiux.performanceBudget"
16387
17198
  });
16388
17199
  }
17200
+ return uiuxIssues;
17201
+ }
17202
+ async function validatePrototypingSkill(root, config) {
16389
17203
  const skillsDir = resolvePath(root, config, "skillsDir");
16390
- const prototypingSkillPath = import_node_path69.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
17204
+ const prototypingSkillPath = import_node_path70.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
16391
17205
  const prototypingSkillContent = await readSafe(prototypingSkillPath);
16392
- const prototypingSkillResult = prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent) : { issues: [] };
16393
- const findings = [
16394
- ...configIssues,
16395
- ...await validateRepositoryHygiene(root, config),
16396
- ...await validateSkillsIntegrity(root, config),
16397
- ...await validateAssistantAssets(root, config),
16398
- ...await validateDiscussionMermaid(root),
16399
- ...await validateMermaidEnforcement(root),
16400
- ...await validateSpecPacks(root, config),
16401
- ...await validateDiscussionPackReadiness(root, config),
16402
- ...await validateDiscussionVisuals(root),
16403
- ...await validateStatusInSpecs(root, config),
16404
- ...await validateDensityHints(root, config),
16405
- ...await validateReviewArtifacts(root),
16406
- ...await validatePrototypingEvidence(root, config),
16407
- ...await validateBreakthroughEvidence(root, config),
16408
- ...await validatePrototypingDesignSystem(root, config),
16409
- ...await validateUiEvidenceArtifacts(root, config),
16410
- ...await validateSpecSplitByCapability(root, config),
16411
- ...await validateLayeredTraceability(root, config),
16412
- ...await validateOrphanProhibition(root, config),
16413
- ...await validateLayerCoverage(root, config),
16414
- ...atddCodeTraceabilityIssues,
16415
- ...await validateContractReferences(root, config),
16416
- ...await validateTraceability(root, config, phase),
16417
- ...await validateDefinedIds(root, config),
16418
- ...await validateContracts(root, config),
16419
- ...await validateTddList(root, config),
16420
- ...await validateDiscussionDesignHardening(root, config),
16421
- ...await validateNavigationFlow(root, config),
16422
- ...await validateRenderCritique(root, config),
16423
- ...await validateDesignFidelity(root, config),
16424
- ...await validateDesignContractReadiness(root, config),
16425
- ...await validateTraceabilityIntegrity(root, config),
16426
- ...prototypingSkillResult.issues,
16427
- ...uiuxIssues
16428
- ];
16429
- const { issues, waivers } = await applyWaivers(root, findings);
16430
- const specsRoot = resolvePath(root, config, "specsDir");
16431
- const scenarioFiles = await collectScenarioFiles(specsRoot);
16432
- const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
16433
- const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
16434
- root,
16435
- config.validation.traceability.testFileGlobs,
16436
- config.validation.traceability.testFileExcludeGlobs
16437
- );
16438
- const scCoverage = buildScCoverage(scIds, scTestRefs);
16439
- const toolVersion = await resolveToolVersion();
16440
- return {
16441
- toolVersion,
16442
- phase,
16443
- issues,
16444
- counts: countIssues(issues),
16445
- traceability: {
16446
- sc: scCoverage,
16447
- testFiles
16448
- },
16449
- waivers
16450
- };
17206
+ return prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent).issues : [];
16451
17207
  }
16452
17208
  function countIssues(issues) {
16453
17209
  return issues.reduce(
@@ -16463,11 +17219,11 @@ function countIssues(issues) {
16463
17219
  }
16464
17220
 
16465
17221
  // src/core/uiux/renderEvidence.ts
16466
- var import_promises56 = require("fs/promises");
17222
+ var import_promises57 = require("fs/promises");
16467
17223
  async function readRenderEvidenceBundle(filePath) {
16468
17224
  let raw;
16469
17225
  try {
16470
- raw = await (0, import_promises56.readFile)(filePath, "utf-8");
17226
+ raw = await (0, import_promises57.readFile)(filePath, "utf-8");
16471
17227
  } catch {
16472
17228
  return null;
16473
17229
  }
@@ -16477,9 +17233,9 @@ async function readRenderEvidenceBundle(filePath) {
16477
17233
  } catch {
16478
17234
  return null;
16479
17235
  }
16480
- return isRecord7(parsed) ? parsed : null;
17236
+ return isRecord8(parsed) ? parsed : null;
16481
17237
  }
16482
- function isRecord7(value) {
17238
+ function isRecord8(value) {
16483
17239
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16484
17240
  }
16485
17241
  function summarizeRenderEvidence(bundle) {
@@ -16487,7 +17243,7 @@ function summarizeRenderEvidence(bundle) {
16487
17243
  let inlinePayloadViolation = false;
16488
17244
  if (bundle?.screens) {
16489
17245
  for (const screen of bundle.screens) {
16490
- if (isRecord7(screen)) {
17246
+ if (isRecord8(screen)) {
16491
17247
  const s = screen;
16492
17248
  if (s.status === "captured" || s.status === "skipped" || s.status === "failed") {
16493
17249
  counts[s.status] += 1;
@@ -16512,7 +17268,7 @@ function summarizeRenderEvidence(bundle) {
16512
17268
  }
16513
17269
 
16514
17270
  // src/core/browserQa/index.ts
16515
- var import_promises57 = require("fs/promises");
17271
+ var import_promises58 = require("fs/promises");
16516
17272
 
16517
17273
  // src/core/browserQa/types.ts
16518
17274
  var BROWSER_QA_PHASES = [
@@ -17075,7 +17831,7 @@ function summarizeBrowserQaResult(result) {
17075
17831
  async function readBrowserQaBundle(filePath) {
17076
17832
  let raw;
17077
17833
  try {
17078
- raw = await (0, import_promises57.readFile)(filePath, "utf-8");
17834
+ raw = await (0, import_promises58.readFile)(filePath, "utf-8");
17079
17835
  } catch {
17080
17836
  return null;
17081
17837
  }
@@ -17085,9 +17841,9 @@ async function readBrowserQaBundle(filePath) {
17085
17841
  } catch {
17086
17842
  return null;
17087
17843
  }
17088
- return isRecord8(parsed) ? parsed : null;
17844
+ return isRecord9(parsed) ? parsed : null;
17089
17845
  }
17090
- function isRecord8(value) {
17846
+ function isRecord9(value) {
17091
17847
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17092
17848
  }
17093
17849
 
@@ -17096,15 +17852,15 @@ var REPORT_GUARDRAILS_MAX = 20;
17096
17852
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
17097
17853
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
17098
17854
  async function createReportData(root, validation, configResult) {
17099
- const resolvedRoot = import_node_path70.default.resolve(root);
17855
+ const resolvedRoot = import_node_path71.default.resolve(root);
17100
17856
  const resolved = configResult ?? await loadConfig(resolvedRoot);
17101
17857
  const config = resolved.config;
17102
17858
  const configPath = resolved.configPath;
17103
17859
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
17104
17860
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
17105
- const apiRoot = import_node_path70.default.join(contractsRoot, "api");
17106
- const uiRoot = import_node_path70.default.join(contractsRoot, "ui");
17107
- const dbRoot = import_node_path70.default.join(contractsRoot, "db");
17861
+ const apiRoot = import_node_path71.default.join(contractsRoot, "api");
17862
+ const uiRoot = import_node_path71.default.join(contractsRoot, "ui");
17863
+ const dbRoot = import_node_path71.default.join(contractsRoot, "db");
17108
17864
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
17109
17865
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
17110
17866
  const specEntries = await collectSpecEntries(specsRoot);
@@ -17901,6 +18657,28 @@ function formatReportMarkdown(data, options = {}) {
17901
18657
  );
17902
18658
  lines.push("");
17903
18659
  }
18660
+ if (data.prototyping.roundLifecycle) {
18661
+ const lifecycle = data.prototyping.roundLifecycle;
18662
+ lines.push("### prototyping.roundLifecycle");
18663
+ lines.push("");
18664
+ lines.push(`- schemaVersion: ${lifecycle.schemaVersion}`);
18665
+ lines.push(`- rounds: ${lifecycle.rounds}`);
18666
+ lines.push(`- round ids: ${lifecycle.roundIds.join(", ") || "(none)"}`);
18667
+ lines.push(`- candidates observed: ${lifecycle.candidatesObserved}`);
18668
+ lines.push(`- harvest artifacts: ${lifecycle.harvestArtifacts}`);
18669
+ lines.push(`- narrow decisions: ${lifecycle.narrowDecisions}`);
18670
+ lines.push(`- absorption plans: ${lifecycle.absorptionPlans}`);
18671
+ lines.push(`- reimplementations: ${lifecycle.reimplementations}`);
18672
+ lines.push(`- perfect rounds: ${lifecycle.perfectRounds}`);
18673
+ lines.push(`- polish cycles: ${lifecycle.polishCycles}`);
18674
+ lines.push(`- completed polish cycles: ${lifecycle.completedPolishCycles}`);
18675
+ lines.push(`- perfect polish cycles: ${lifecycle.perfectPolishCycles}`);
18676
+ if (Object.keys(lifecycle.polishKinds).length > 0) {
18677
+ const kinds = Object.entries(lifecycle.polishKinds).map(([kind, count]) => `${kind}=${count}`).join(", ");
18678
+ lines.push(`- polish kinds: ${kinds}`);
18679
+ }
18680
+ lines.push("");
18681
+ }
17904
18682
  lines.push("### prototyping.mode");
17905
18683
  lines.push("");
17906
18684
  lines.push(`- requested: ${data.prototyping.mode.requested ?? "(none)"}`);
@@ -18257,7 +19035,7 @@ async function collectChangeTypeSummary(specsRoot) {
18257
19035
  };
18258
19036
  const deltaFiles = await collectDeltaFiles(specsRoot);
18259
19037
  for (const deltaFile of deltaFiles) {
18260
- const text = await (0, import_promises58.readFile)(deltaFile, "utf-8");
19038
+ const text = await (0, import_promises59.readFile)(deltaFile, "utf-8");
18261
19039
  const parsed = parseDeltaV1(text);
18262
19040
  for (const entry of parsed.entries) {
18263
19041
  if (!entry.meta) {
@@ -18288,11 +19066,11 @@ async function collectChangeTypeSummary(specsRoot) {
18288
19066
  }
18289
19067
  async function collectPrototypingSummary(root, config) {
18290
19068
  const specsRoot = resolvePath(root, config, "specsDir");
18291
- const evidenceRoot = import_node_path70.default.join(import_node_path70.default.dirname(specsRoot), "evidence");
18292
- const evidencePath = import_node_path70.default.join(evidenceRoot, "prototyping.json");
19069
+ const evidenceRoot = import_node_path71.default.join(import_node_path71.default.dirname(specsRoot), "evidence");
19070
+ const evidencePath = import_node_path71.default.join(evidenceRoot, "prototyping.json");
18293
19071
  let raw;
18294
19072
  try {
18295
- raw = await (0, import_promises58.readFile)(evidencePath, "utf-8");
19073
+ raw = await (0, import_promises59.readFile)(evidencePath, "utf-8");
18296
19074
  } catch {
18297
19075
  return void 0;
18298
19076
  }
@@ -18319,10 +19097,12 @@ async function collectPrototypingSummary(root, config) {
18319
19097
  const warnings = [];
18320
19098
  const obligations = derivePrototypingObligations({ surface: effectiveSurface, effectiveMode });
18321
19099
  const fullHarness = asRecord2(record2.fullHarness);
19100
+ const rounds = Array.isArray(record2.rounds) ? record2.rounds : [];
19101
+ const polishCycles = Array.isArray(record2.polishCycles) ? record2.polishCycles : [];
18322
19102
  const runtimeGate = asRecord2(record2.runtimeGate);
18323
19103
  const uiFidelity = asRecord2(record2.uiFidelity);
18324
- const renderBundle = await readRenderEvidenceBundle(import_node_path70.default.join(evidenceRoot, "render.json"));
18325
- const browserQaBundle = await readBrowserQaBundle(import_node_path70.default.join(evidenceRoot, "browser-qa.json"));
19104
+ const renderBundle = await readRenderEvidenceBundle(import_node_path71.default.join(evidenceRoot, "render.json"));
19105
+ const browserQaBundle = await readBrowserQaBundle(import_node_path71.default.join(evidenceRoot, "browser-qa.json"));
18326
19106
  const specs = Array.isArray(record2.specs) ? record2.specs : [];
18327
19107
  const specEntries = await collectSpecEntries(specsRoot);
18328
19108
  const expectedSpecIds = specEntries.map((entry) => `spec-${entry.specNumber}`.toLowerCase());
@@ -18379,8 +19159,75 @@ async function collectPrototypingSummary(root, config) {
18379
19159
  ...classificationBlock?.secondary_surfaces?.length ? { secondarySurfaces: classificationBlock.secondary_surfaces } : {},
18380
19160
  ...classificationBlock?.classification_rationale ? { classificationRationale: classificationBlock.classification_rationale } : {}
18381
19161
  };
19162
+ const roundLifecycle = record2.schemaVersion === "2.0" || rounds.length > 0 || polishCycles.length > 0 ? (() => {
19163
+ const polishKinds = {};
19164
+ let candidatesObserved = 0;
19165
+ let perfectRounds = 0;
19166
+ let harvestArtifacts = 0;
19167
+ let narrowDecisions = 0;
19168
+ let absorptionPlans = 0;
19169
+ let reimplementations = 0;
19170
+ let completedPolishCycles = 0;
19171
+ let perfectPolishCycles = 0;
19172
+ for (const item of rounds) {
19173
+ const round = asRecord2(item);
19174
+ if (!round) {
19175
+ continue;
19176
+ }
19177
+ const candidates = Array.isArray(round.candidates) ? round.candidates : [];
19178
+ candidatesObserved += candidates.length;
19179
+ if (round.allAxesPerfect100 === true) {
19180
+ perfectRounds += 1;
19181
+ }
19182
+ if (typeof round.harvestRef === "string" && round.harvestRef.length > 0) {
19183
+ harvestArtifacts += 1;
19184
+ }
19185
+ if (typeof round.narrowDecisionRef === "string" && round.narrowDecisionRef.length > 0) {
19186
+ narrowDecisions += 1;
19187
+ }
19188
+ if (typeof round.absorptionPlanRef === "string" && round.absorptionPlanRef.length > 0) {
19189
+ absorptionPlans += 1;
19190
+ }
19191
+ if (typeof round.reimplementationRef === "string" && round.reimplementationRef.length > 0) {
19192
+ reimplementations += 1;
19193
+ }
19194
+ }
19195
+ for (const item of polishCycles) {
19196
+ const cycle = asRecord2(item);
19197
+ if (!cycle) {
19198
+ continue;
19199
+ }
19200
+ if (typeof cycle.kind === "string" && cycle.kind.length > 0) {
19201
+ polishKinds[cycle.kind] = (polishKinds[cycle.kind] ?? 0) + 1;
19202
+ if (cycle.kind === "completed") {
19203
+ completedPolishCycles += 1;
19204
+ }
19205
+ }
19206
+ if (cycle.allAxesPerfect100 === true) {
19207
+ perfectPolishCycles += 1;
19208
+ }
19209
+ }
19210
+ return {
19211
+ schemaVersion: "2.0",
19212
+ rounds: rounds.length,
19213
+ roundIds: rounds.map((item) => asRecord2(item)).flatMap(
19214
+ (round) => round && typeof round.round === "string" && round.round.length > 0 ? [round.round] : []
19215
+ ),
19216
+ candidatesObserved,
19217
+ perfectRounds,
19218
+ harvestArtifacts,
19219
+ narrowDecisions,
19220
+ absorptionPlans,
19221
+ reimplementations,
19222
+ polishCycles: polishCycles.length,
19223
+ completedPolishCycles,
19224
+ perfectPolishCycles,
19225
+ polishKinds
19226
+ };
19227
+ })() : void 0;
18382
19228
  return {
18383
19229
  surfaceClassification,
19230
+ ...roundLifecycle ? { roundLifecycle } : {},
18384
19231
  mode: {
18385
19232
  ...resolvedMode.requested ? { requested: resolvedMode.requested } : {},
18386
19233
  effective: effectiveMode,
@@ -18473,7 +19320,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
18473
19320
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
18474
19321
  }
18475
19322
  for (const file of specFiles) {
18476
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19323
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18477
19324
  const parsed = parseSpec(text, file);
18478
19325
  const specKey = parsed.specId;
18479
19326
  if (!specKey) {
@@ -18510,7 +19357,7 @@ async function collectIds(files) {
18510
19357
  result[prefix] = /* @__PURE__ */ new Set();
18511
19358
  }
18512
19359
  for (const file of files) {
18513
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19360
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18514
19361
  for (const prefix of ID_PREFIXES) {
18515
19362
  const ids = extractIds(text, prefix);
18516
19363
  ids.forEach((id) => result[prefix].add(id));
@@ -18525,7 +19372,7 @@ async function collectIds(files) {
18525
19372
  async function collectUpstreamIds(files) {
18526
19373
  const ids = /* @__PURE__ */ new Set();
18527
19374
  for (const file of files) {
18528
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19375
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18529
19376
  extractAllIds(text).forEach((id) => ids.add(id));
18530
19377
  }
18531
19378
  return ids;
@@ -18546,7 +19393,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
18546
19393
  }
18547
19394
  const pattern = buildIdPattern(Array.from(upstreamIds));
18548
19395
  for (const file of targetFiles) {
18549
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19396
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18550
19397
  if (pattern.test(text)) {
18551
19398
  return true;
18552
19399
  }
@@ -18668,7 +19515,7 @@ function normalizeScSources(root, sources) {
18668
19515
  async function countScenarios(scenarioFiles) {
18669
19516
  let total = 0;
18670
19517
  for (const file of scenarioFiles) {
18671
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19518
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18672
19519
  const { document, errors } = parseScenarioDocument(text, file);
18673
19520
  if (!document || errors.length > 0) {
18674
19521
  continue;
@@ -18699,7 +19546,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
18699
19546
  let totalScenarios = 0;
18700
19547
  let e2eCount = 0;
18701
19548
  for (const file of scenarioFiles) {
18702
- const text = await (0, import_promises58.readFile)(file, "utf-8");
19549
+ const text = await (0, import_promises59.readFile)(file, "utf-8");
18703
19550
  const { document, errors } = parseScenarioDocument(text, file);
18704
19551
  if (!document || errors.length > 0) {
18705
19552
  continue;
@@ -18787,10 +19634,10 @@ function buildHotspots(issues) {
18787
19634
  async function collectTddCoverage(entries) {
18788
19635
  const specs = [];
18789
19636
  for (const entry of entries) {
18790
- const testCasesPath = import_node_path70.default.join(entry.dir, "06_Test-Cases.md");
19637
+ const testCasesPath = import_node_path71.default.join(entry.dir, "06_Test-Cases.md");
18791
19638
  let tcContent;
18792
19639
  try {
18793
- tcContent = await (0, import_promises58.readFile)(testCasesPath, "utf-8");
19640
+ tcContent = await (0, import_promises59.readFile)(testCasesPath, "utf-8");
18794
19641
  } catch {
18795
19642
  continue;
18796
19643
  }
@@ -18822,10 +19669,10 @@ async function collectTddCoverage(entries) {
18822
19669
  });
18823
19670
  continue;
18824
19671
  }
18825
- const tddListPath = import_node_path70.default.join(entry.dir, "tdd", "test-list.md");
19672
+ const tddListPath = import_node_path71.default.join(entry.dir, "tdd", "test-list.md");
18826
19673
  let tddContent;
18827
19674
  try {
18828
- tddContent = await (0, import_promises58.readFile)(tddListPath, "utf-8");
19675
+ tddContent = await (0, import_promises59.readFile)(tddListPath, "utf-8");
18829
19676
  } catch {
18830
19677
  specs.push({
18831
19678
  specNumber: entry.specNumber,
@@ -18976,16 +19823,16 @@ async function runRenderCapture(targets, outputDir, adapter, options) {
18976
19823
  }
18977
19824
 
18978
19825
  // src/core/evidence/bundleWriter.ts
18979
- var import_promises60 = require("fs/promises");
18980
- var import_node_path72 = __toESM(require("path"), 1);
19826
+ var import_promises61 = require("fs/promises");
19827
+ var import_node_path73 = __toESM(require("path"), 1);
18981
19828
 
18982
19829
  // src/core/evidence/fsEvidenceWriter.ts
18983
- var import_promises59 = require("fs/promises");
18984
- var import_node_path71 = __toESM(require("path"), 1);
19830
+ var import_promises60 = require("fs/promises");
19831
+ var import_node_path72 = __toESM(require("path"), 1);
18985
19832
  async function writeEvidenceFile(filePath, content) {
18986
19833
  try {
18987
- await (0, import_promises59.mkdir)(import_node_path71.default.dirname(filePath), { recursive: true });
18988
- await (0, import_promises59.writeFile)(filePath, content);
19834
+ await (0, import_promises60.mkdir)(import_node_path72.default.dirname(filePath), { recursive: true });
19835
+ await (0, import_promises60.writeFile)(filePath, content);
18989
19836
  return { path: filePath, written: true };
18990
19837
  } catch (error) {
18991
19838
  return {
@@ -18998,19 +19845,19 @@ async function writeEvidenceFile(filePath, content) {
18998
19845
 
18999
19846
  // src/core/evidence/bundleWriter.ts
19000
19847
  function toPosixRelative5(root, targetPath) {
19001
- return import_node_path72.default.relative(root, targetPath).replace(/\\/g, "/");
19848
+ return import_node_path73.default.relative(root, targetPath).replace(/\\/g, "/");
19002
19849
  }
19003
19850
  async function writeEvidenceBundles(input) {
19004
- const evidenceRoot = import_node_path72.default.join(input.root, ".qfai", "evidence");
19005
- const renderAssetRoot = import_node_path72.default.join(evidenceRoot, "render");
19006
- await (0, import_promises60.mkdir)(renderAssetRoot, { recursive: true });
19007
- const prototypingPath = import_node_path72.default.join(evidenceRoot, "prototyping.json");
19008
- const renderPath = import_node_path72.default.join(evidenceRoot, "render.json");
19009
- const browserQaPath = import_node_path72.default.join(evidenceRoot, "browser-qa.json");
19010
- const browserQaSummaryPath = import_node_path72.default.join(evidenceRoot, "browserQa.summary.json");
19011
- const browserQaFindingsPath = import_node_path72.default.join(evidenceRoot, "browserQa.findings.json");
19012
- const browserQaRepairsPath = import_node_path72.default.join(evidenceRoot, "browserQa.repairs.json");
19013
- const breakthroughPath = import_node_path72.default.join(evidenceRoot, "breakthrough.json");
19851
+ const evidenceRoot = import_node_path73.default.join(input.root, ".qfai", "evidence");
19852
+ const renderAssetRoot = import_node_path73.default.join(evidenceRoot, "render");
19853
+ await (0, import_promises61.mkdir)(renderAssetRoot, { recursive: true });
19854
+ const prototypingPath = import_node_path73.default.join(evidenceRoot, "prototyping.json");
19855
+ const renderPath = import_node_path73.default.join(evidenceRoot, "render.json");
19856
+ const browserQaPath = import_node_path73.default.join(evidenceRoot, "browser-qa.json");
19857
+ const browserQaSummaryPath = import_node_path73.default.join(evidenceRoot, "browserQa.summary.json");
19858
+ const browserQaFindingsPath = import_node_path73.default.join(evidenceRoot, "browserQa.findings.json");
19859
+ const browserQaRepairsPath = import_node_path73.default.join(evidenceRoot, "browserQa.repairs.json");
19860
+ const breakthroughPath = import_node_path73.default.join(evidenceRoot, "breakthrough.json");
19014
19861
  const renderBundle = input.render === void 0 ? {
19015
19862
  renderEvidence: {
19016
19863
  status: "skipped",
@@ -19032,7 +19879,7 @@ async function writeEvidenceBundles(input) {
19032
19879
  await Promise.all([
19033
19880
  writeEvidenceFile(prototypingPath, JSON.stringify(input.prototyping, null, 2)),
19034
19881
  writeEvidenceFile(
19035
- import_node_path72.default.join(evidenceRoot, "prototyping.md"),
19882
+ import_node_path73.default.join(evidenceRoot, "prototyping.md"),
19036
19883
  buildPrototypingMarkdown(input.prototyping)
19037
19884
  ),
19038
19885
  writeEvidenceFile(renderPath, JSON.stringify(renderBundle, null, 2)),
@@ -19049,15 +19896,15 @@ async function writeEvidenceBundles(input) {
19049
19896
  writeEvidenceFile(breakthroughPath, JSON.stringify(input.prototyping.breakthrough, null, 2)),
19050
19897
  ...input.fullHarnessArtifacts ? [
19051
19898
  writeEvidenceFile(
19052
- import_node_path72.default.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
19899
+ import_node_path73.default.join(evidenceRoot, "fullHarness.fakeUiDetection.json"),
19053
19900
  JSON.stringify(input.fullHarnessArtifacts.fakeUiDetection, null, 2)
19054
19901
  ),
19055
19902
  writeEvidenceFile(
19056
- import_node_path72.default.join(evidenceRoot, "fullHarness.handoff.json"),
19903
+ import_node_path73.default.join(evidenceRoot, "fullHarness.handoff.json"),
19057
19904
  JSON.stringify(input.fullHarnessArtifacts.handoff, null, 2)
19058
19905
  ),
19059
19906
  writeEvidenceFile(
19060
- import_node_path72.default.join(evidenceRoot, "fullHarness.exit.json"),
19907
+ import_node_path73.default.join(evidenceRoot, "fullHarness.exit.json"),
19061
19908
  JSON.stringify({ exit_reason: input.fullHarnessArtifacts.exitReason }, null, 2)
19062
19909
  )
19063
19910
  ] : []
@@ -19157,15 +20004,15 @@ function buildPrototypingMarkdown(bundle) {
19157
20004
  }
19158
20005
 
19159
20006
  // src/core/harness/history.ts
19160
- var import_promises61 = require("fs/promises");
19161
- var import_node_path73 = __toESM(require("path"), 1);
20007
+ var import_promises62 = require("fs/promises");
20008
+ var import_node_path74 = __toESM(require("path"), 1);
19162
20009
  var EVIDENCE_PATH = ".qfai/evidence/prototyping.json";
19163
20010
  async function loadHistory(root) {
19164
20011
  try {
19165
- const filePath = import_node_path73.default.join(root, EVIDENCE_PATH);
19166
- const content = await (0, import_promises61.readFile)(filePath, "utf-8");
20012
+ const filePath = import_node_path74.default.join(root, EVIDENCE_PATH);
20013
+ const content = await (0, import_promises62.readFile)(filePath, "utf-8");
19167
20014
  const parsed = JSON.parse(content);
19168
- if (isRecord9(parsed) && isRecord9(parsed.fullHarness)) {
20015
+ if (isRecord10(parsed) && isRecord10(parsed.fullHarness)) {
19169
20016
  const fh = parsed.fullHarness;
19170
20017
  if (Array.isArray(fh.iterations)) {
19171
20018
  return {
@@ -19214,7 +20061,7 @@ function computeTerminationReason(history, iterationPolicy) {
19214
20061
  return void 0;
19215
20062
  }
19216
20063
  const latestIteration = history.iterations[count - 1];
19217
- if (latestIteration?.allItemsPass95) {
20064
+ if (latestIteration && hasAllReviewerAxesPerfect100(latestIteration.reviewerScores)) {
19218
20065
  return "converged";
19219
20066
  }
19220
20067
  if (count >= iterationPolicy.maxIterations) {
@@ -19226,6 +20073,7 @@ function buildScoreSnapshot(iteration) {
19226
20073
  const axisScores = iteration.reviewerScores.flatMap(
19227
20074
  (reviewer) => reviewer.scores.map((score) => score.score)
19228
20075
  );
20076
+ const allReviewerAxesPerfect100 = hasAllReviewerAxesPerfect100(iteration.reviewerScores);
19229
20077
  if (axisScores.length === 0) {
19230
20078
  return {
19231
20079
  iteration: iteration.iteration,
@@ -19233,7 +20081,7 @@ function buildScoreSnapshot(iteration) {
19233
20081
  axisCount: 0,
19234
20082
  minScore: null,
19235
20083
  averageScore: null,
19236
- allItemsPass95: iteration.allItemsPass95,
20084
+ allReviewerAxesPerfect100: false,
19237
20085
  commitSha: iteration.commitSha
19238
20086
  };
19239
20087
  }
@@ -19244,13 +20092,13 @@ function buildScoreSnapshot(iteration) {
19244
20092
  axisCount: axisScores.length,
19245
20093
  minScore: Math.min(...axisScores),
19246
20094
  averageScore: total / axisScores.length,
19247
- allItemsPass95: iteration.allItemsPass95,
20095
+ allReviewerAxesPerfect100,
19248
20096
  commitSha: iteration.commitSha
19249
20097
  };
19250
20098
  }
19251
20099
  function compareSnapshots(left, right) {
19252
- if (left.allItemsPass95 !== right.allItemsPass95) {
19253
- return left.allItemsPass95 ? 1 : -1;
20100
+ if (left.allReviewerAxesPerfect100 !== right.allReviewerAxesPerfect100) {
20101
+ return left.allReviewerAxesPerfect100 ? 1 : -1;
19254
20102
  }
19255
20103
  const leftMin = left.minScore ?? -1;
19256
20104
  const rightMin = right.minScore ?? -1;
@@ -19264,7 +20112,15 @@ function compareSnapshots(left, right) {
19264
20112
  }
19265
20113
  return left.iteration - right.iteration;
19266
20114
  }
19267
- function isRecord9(value) {
20115
+ function hasAllReviewerAxesPerfect100(reviewerScores) {
20116
+ if (reviewerScores.length === 0) {
20117
+ return false;
20118
+ }
20119
+ return reviewerScores.every(
20120
+ (reviewer) => reviewer.scores.length > 0 && reviewer.scores.every((score) => score.score === 100)
20121
+ );
20122
+ }
20123
+ function isRecord10(value) {
19268
20124
  return typeof value === "object" && value !== null && !Array.isArray(value);
19269
20125
  }
19270
20126
 
@@ -19299,16 +20155,16 @@ function validateReviewer(reviewer) {
19299
20155
  }
19300
20156
 
19301
20157
  // src/core/harness/gitRevision.ts
19302
- var import_promises62 = require("fs/promises");
19303
- var import_node_path74 = __toESM(require("path"), 1);
20158
+ var import_promises63 = require("fs/promises");
20159
+ var import_node_path75 = __toESM(require("path"), 1);
19304
20160
  async function resolveGitDirs(root) {
19305
- const dotGit = import_node_path74.default.join(root, ".git");
19306
- const info = await (0, import_promises62.stat)(dotGit);
20161
+ const dotGit = import_node_path75.default.join(root, ".git");
20162
+ const info = await (0, import_promises63.stat)(dotGit);
19307
20163
  const gitDir = info.isDirectory() ? dotGit : await resolveWorktreeGitDir(root, dotGit);
19308
20164
  try {
19309
- const commondirText = (await (0, import_promises62.readFile)(import_node_path74.default.join(gitDir, "commondir"), "utf-8")).trim();
20165
+ const commondirText = (await (0, import_promises63.readFile)(import_node_path75.default.join(gitDir, "commondir"), "utf-8")).trim();
19310
20166
  if (commondirText.length > 0) {
19311
- const commonDir = import_node_path74.default.isAbsolute(commondirText) ? commondirText : import_node_path74.default.resolve(gitDir, commondirText);
20167
+ const commonDir = import_node_path75.default.isAbsolute(commondirText) ? commondirText : import_node_path75.default.resolve(gitDir, commondirText);
19312
20168
  return { gitDir, commonDir };
19313
20169
  }
19314
20170
  } catch {
@@ -19316,18 +20172,18 @@ async function resolveGitDirs(root) {
19316
20172
  return { gitDir, commonDir: gitDir };
19317
20173
  }
19318
20174
  async function resolveWorktreeGitDir(root, dotGitFile) {
19319
- const text = (await (0, import_promises62.readFile)(dotGitFile, "utf-8")).trim();
20175
+ const text = (await (0, import_promises63.readFile)(dotGitFile, "utf-8")).trim();
19320
20176
  const match = /^gitdir:\s*(.+)$/m.exec(text);
19321
20177
  if (!match?.[1]) {
19322
20178
  throw new Error(`Unrecognized .git file format at ${dotGitFile}`);
19323
20179
  }
19324
20180
  const gitdirValue = match[1].trim();
19325
- return import_node_path74.default.isAbsolute(gitdirValue) ? gitdirValue : import_node_path74.default.resolve(root, gitdirValue);
20181
+ return import_node_path75.default.isAbsolute(gitdirValue) ? gitdirValue : import_node_path75.default.resolve(root, gitdirValue);
19326
20182
  }
19327
20183
  async function lookupPackedRef(commonDir, refName) {
19328
20184
  try {
19329
- const packedPath = import_node_path74.default.join(commonDir, "packed-refs");
19330
- const content = await (0, import_promises62.readFile)(packedPath, "utf-8");
20185
+ const packedPath = import_node_path75.default.join(commonDir, "packed-refs");
20186
+ const content = await (0, import_promises63.readFile)(packedPath, "utf-8");
19331
20187
  for (const rawLine of content.split(/\r?\n/)) {
19332
20188
  const line = rawLine.trim();
19333
20189
  if (line.length === 0 || line.startsWith("#") || line.startsWith("^")) continue;
@@ -19343,7 +20199,7 @@ async function lookupPackedRef(commonDir, refName) {
19343
20199
  }
19344
20200
  async function readRefLoose(dir, refName) {
19345
20201
  try {
19346
- const sha = (await (0, import_promises62.readFile)(import_node_path74.default.join(dir, refName), "utf-8")).trim();
20202
+ const sha = (await (0, import_promises63.readFile)(import_node_path75.default.join(dir, refName), "utf-8")).trim();
19347
20203
  return sha.length > 0 ? sha : null;
19348
20204
  } catch {
19349
20205
  return null;
@@ -19352,8 +20208,8 @@ async function readRefLoose(dir, refName) {
19352
20208
  async function resolveCommitSha(root) {
19353
20209
  try {
19354
20210
  const { gitDir, commonDir } = await resolveGitDirs(root);
19355
- const headPath = import_node_path74.default.join(gitDir, "HEAD");
19356
- const headContent = (await (0, import_promises62.readFile)(headPath, "utf-8")).trim();
20211
+ const headPath = import_node_path75.default.join(gitDir, "HEAD");
20212
+ const headContent = (await (0, import_promises63.readFile)(headPath, "utf-8")).trim();
19357
20213
  if (headContent.startsWith("ref: ")) {
19358
20214
  const refName = headContent.slice(5).trim();
19359
20215
  const looseLocal = await readRefLoose(gitDir, refName);
@@ -19374,162 +20230,6 @@ async function resolveCommitSha(root) {
19374
20230
  );
19375
20231
  }
19376
20232
  }
19377
-
19378
- // src/core/evidence/playwrightRenderAdapter.ts
19379
- var import_promises63 = require("fs/promises");
19380
- var import_node_path75 = __toESM(require("path"), 1);
19381
- function sanitizeName(value) {
19382
- return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
19383
- }
19384
- async function loadPlaywright() {
19385
- return import("playwright");
19386
- }
19387
- function createPlaywrightRenderAdapter(input) {
19388
- async function _renderTarget(target) {
19389
- if (!input.targetUrl) {
19390
- throw new Error("target URL is required for Playwright render capture");
19391
- }
19392
- const playwright = await loadPlaywright();
19393
- const browser = await playwright.chromium.launch();
19394
- try {
19395
- const page = await browser.newPage({
19396
- viewport: {
19397
- width: target.width ?? 1440,
19398
- height: target.height ?? 900
19399
- }
19400
- });
19401
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19402
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19403
- const slug = sanitizeName(`${target.targetId}.${target.viewport}`);
19404
- const screenshotPath = import_node_path75.default.join(process.cwd(), ".", slug);
19405
- return {
19406
- screenshotPath,
19407
- htmlPath: await page.content()
19408
- };
19409
- } finally {
19410
- await browser.close();
19411
- }
19412
- }
19413
- return {
19414
- async captureScreenshot(target, outputDir) {
19415
- if (!input.targetUrl) {
19416
- throw new Error("target URL is required for Playwright render capture");
19417
- }
19418
- const playwright = await loadPlaywright();
19419
- const browser = await playwright.chromium.launch();
19420
- try {
19421
- await (0, import_promises63.mkdir)(outputDir, { recursive: true });
19422
- const page = await browser.newPage({
19423
- viewport: {
19424
- width: target.width ?? 1440,
19425
- height: target.height ?? 900
19426
- }
19427
- });
19428
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19429
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19430
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19431
- const screenshotPath = import_node_path75.default.join(outputDir, `${targetName}.png`);
19432
- await page.screenshot({ path: screenshotPath, fullPage: true });
19433
- return screenshotPath;
19434
- } finally {
19435
- await browser.close();
19436
- }
19437
- },
19438
- async captureHtml(target, outputDir) {
19439
- if (!input.targetUrl) {
19440
- throw new Error("target URL is required for Playwright render capture");
19441
- }
19442
- const playwright = await loadPlaywright();
19443
- const browser = await playwright.chromium.launch();
19444
- try {
19445
- await (0, import_promises63.mkdir)(outputDir, { recursive: true });
19446
- const page = await browser.newPage({
19447
- viewport: {
19448
- width: target.width ?? 1440,
19449
- height: target.height ?? 900
19450
- }
19451
- });
19452
- const resolvedUrl = new URL(target.route ?? "/", input.targetUrl).toString();
19453
- await page.goto(resolvedUrl, { waitUntil: "networkidle" });
19454
- const targetName = sanitizeName(`${target.targetId}.${target.viewport}`);
19455
- const htmlPath = import_node_path75.default.join(outputDir, `${targetName}.html`);
19456
- await (0, import_promises63.writeFile)(htmlPath, await page.content(), "utf-8");
19457
- return htmlPath;
19458
- } finally {
19459
- await browser.close();
19460
- }
19461
- }
19462
- };
19463
- }
19464
-
19465
- // src/core/providers/playwrightBrowserQaProvider.ts
19466
- init_surfaceType();
19467
- async function loadPlaywright2() {
19468
- try {
19469
- return await import("playwright");
19470
- } catch {
19471
- throw new Error("Playwright is not installed. Install it with: npm install playwright");
19472
- }
19473
- }
19474
- async function withPage(input, task) {
19475
- const playwright = await loadPlaywright2();
19476
- const browser = await playwright.chromium.launch();
19477
- try {
19478
- const page = await browser.newPage();
19479
- if (input.targetUrl) {
19480
- await page.goto(input.targetUrl, { waitUntil: "networkidle" });
19481
- } else if (input.htmlContent) {
19482
- await page.setContent(input.htmlContent, { waitUntil: "load" });
19483
- } else {
19484
- throw new Error("Browser QA requires targetUrl or htmlContent");
19485
- }
19486
- return await task(page);
19487
- } finally {
19488
- await browser.close();
19489
- }
19490
- }
19491
- function completed(phase) {
19492
- return {
19493
- phase,
19494
- status: "executed",
19495
- findings: [],
19496
- repair_suggestions: [],
19497
- evidence_refs: [`provider:playwright:${phase}`],
19498
- checks_performed: [`playwright executed ${phase} checks`]
19499
- };
19500
- }
19501
- function createPlaywrightBrowserQaProvider() {
19502
- return {
19503
- providerId: "playwright",
19504
- canRun(surface) {
19505
- return requiresVisualBrowserEvidence(surface);
19506
- },
19507
- async runSmoke(input) {
19508
- return withPage(input, async (page) => {
19509
- await page.title();
19510
- return completed("smoke");
19511
- });
19512
- },
19513
- async runInteraction(input) {
19514
- return withPage(input, async (page) => {
19515
- await page.locator("button, a, input, form").count();
19516
- return completed("interaction");
19517
- });
19518
- },
19519
- async runVisual(input) {
19520
- return withPage(input, async (page) => {
19521
- await page.viewportSize();
19522
- return completed("visual");
19523
- });
19524
- },
19525
- async runAccessibility(input) {
19526
- return withPage(input, async (page) => {
19527
- await page.locator("html").getAttribute("lang");
19528
- return completed("accessibility");
19529
- });
19530
- }
19531
- };
19532
- }
19533
20233
  // Annotate the CommonJS export names for ESM import in node:
19534
20234
  0 && (module.exports = {
19535
20235
  BROWSER_QA_PHASES,
@@ -19537,6 +20237,7 @@ function createPlaywrightBrowserQaProvider() {
19537
20237
  DISCUSSION_NON_UI_SURFACES,
19538
20238
  DISCUSSION_UI_BEARING_SURFACES,
19539
20239
  ID_PREFIXES,
20240
+ PROTOTYPING_MAX_CYCLES,
19540
20241
  PROTOTYPING_MAX_ITERATIONS,
19541
20242
  PROTOTYPING_MODES,
19542
20243
  PROTOTYPING_SUPPORTED_SURFACES,
@@ -19545,8 +20246,6 @@ function createPlaywrightBrowserQaProvider() {
19545
20246
  appendIteration,
19546
20247
  checkDecisionGuardrails,
19547
20248
  computeTerminationReason,
19548
- createPlaywrightBrowserQaProvider,
19549
- createPlaywrightRenderAdapter,
19550
20249
  createReportData,
19551
20250
  defaultConfig,
19552
20251
  derivePrototypingObligations,