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.
- package/README.md +9 -4
- package/assets/init/.qfai/assistant/agents/product-experience-architect.md +2 -1
- package/assets/init/.qfai/assistant/skills/qfai-atdd/SKILL.md +4 -4
- package/assets/init/.qfai/assistant/skills/qfai-configure/SKILL.md +1 -1
- package/assets/init/.qfai/assistant/skills/qfai-discussion/SKILL.md +1 -0
- package/assets/init/.qfai/assistant/skills/qfai-discussion/references/rcp_footer.md +1 -1
- package/assets/init/.qfai/assistant/skills/qfai-implement/SKILL.md +3 -1
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +121 -62
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/evidence-requirements.md +43 -12
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/iteration-cycle.md +46 -14
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/l1-review-guide.md +13 -12
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/l2-review-guide.md +16 -10
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/reviewer-gate.md +25 -4
- package/assets/init/.qfai/assistant/skills/qfai-sdd/SKILL.md +3 -3
- package/assets/init/.qfai/assistant/skills/qfai-sdd/references/rcp_footer.md +1 -1
- package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-quality-gate.md +1 -1
- package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/absorption-policy.sample.yaml +7 -0
- package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/evaluation-rubric.sample.yaml +20 -3
- package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/evaluator-calibration.sample.yaml +6 -0
- package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/exploration-brief.sample.yaml +9 -0
- package/assets/init/.qfai/assistant/skills/qfai-verify/SKILL.md +6 -6
- package/assets/init/.qfai/contracts/design/README.md +6 -1
- package/assets/init/.qfai/contracts/ui/README.md +2 -0
- package/assets/init/.qfai/discussion/README.md +14 -9
- package/assets/init/.qfai/evidence/README.md +66 -46
- package/assets/init/root/.github/workflows/qfai-validate.yml +39 -0
- package/assets/init/root/qfai.config.yaml +1 -2
- package/dist/cli/index.cjs +2539 -927
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +2624 -1012
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +1120 -421
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -23
- package/dist/index.d.ts +95 -23
- package/dist/index.mjs +1114 -414
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3178
|
+
const maxCycles = PROTOTYPING_MAX_CYCLES[input.effectiveMode];
|
|
3161
3179
|
return {
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
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
|
-
|
|
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
|
|
3635
|
-
var
|
|
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
|
|
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.
|
|
4397
|
-
return "1.8.
|
|
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,
|
|
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 (
|
|
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
|
|
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 (!
|
|
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.
|
|
10461
|
+
if (typeof iteration.allReviewerAxesPerfect100 !== "boolean") {
|
|
10299
10462
|
issues.push(
|
|
10300
|
-
makeSchemaIssue(
|
|
10463
|
+
makeSchemaIssue(
|
|
10464
|
+
root,
|
|
10465
|
+
evidencePath,
|
|
10466
|
+
`${prefix}.allReviewerAxesPerfect100 must be a boolean.`
|
|
10467
|
+
)
|
|
10301
10468
|
);
|
|
10302
|
-
} else if (iteration.
|
|
10303
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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-
|
|
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
|
-
"
|
|
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 && !
|
|
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 && !
|
|
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
|
|
10430
|
-
|
|
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
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
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
|
|
10467
|
-
if (!isRecord6(
|
|
10468
|
-
return
|
|
10739
|
+
function normalizePhaseCurrent(value) {
|
|
10740
|
+
if (!isRecord6(value)) {
|
|
10741
|
+
return null;
|
|
10469
10742
|
}
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
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 "
|
|
10749
|
+
return typeof value.status === "string" ? value.status : null;
|
|
10475
10750
|
}
|
|
10476
|
-
function
|
|
10477
|
-
if (!isRecord6(
|
|
10751
|
+
function hasCompletePolishChecks(value) {
|
|
10752
|
+
if (!isRecord6(value)) {
|
|
10478
10753
|
return false;
|
|
10479
10754
|
}
|
|
10480
|
-
|
|
10481
|
-
|
|
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
|
|
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
|
|
10495
|
-
}
|
|
10766
|
+
return reviewer.scores.every((score) => isRecord6(score) && score.score === 100);
|
|
10767
|
+
});
|
|
10496
10768
|
}
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
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
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
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
|
|
16361
|
-
const
|
|
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,
|
|
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 =
|
|
17204
|
+
const prototypingSkillPath = import_node_path70.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
|
|
16391
17205
|
const prototypingSkillContent = await readSafe(prototypingSkillPath);
|
|
16392
|
-
|
|
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
|
|
17222
|
+
var import_promises57 = require("fs/promises");
|
|
16467
17223
|
async function readRenderEvidenceBundle(filePath) {
|
|
16468
17224
|
let raw;
|
|
16469
17225
|
try {
|
|
16470
|
-
raw = await (0,
|
|
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
|
|
17236
|
+
return isRecord8(parsed) ? parsed : null;
|
|
16481
17237
|
}
|
|
16482
|
-
function
|
|
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 (
|
|
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
|
|
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,
|
|
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
|
|
17844
|
+
return isRecord9(parsed) ? parsed : null;
|
|
17089
17845
|
}
|
|
17090
|
-
function
|
|
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 =
|
|
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 =
|
|
17106
|
-
const uiRoot =
|
|
17107
|
-
const dbRoot =
|
|
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,
|
|
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 =
|
|
18292
|
-
const evidencePath =
|
|
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,
|
|
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(
|
|
18325
|
-
const browserQaBundle = await readBrowserQaBundle(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
|
18980
|
-
var
|
|
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
|
|
18984
|
-
var
|
|
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,
|
|
18988
|
-
await (0,
|
|
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
|
|
19848
|
+
return import_node_path73.default.relative(root, targetPath).replace(/\\/g, "/");
|
|
19002
19849
|
}
|
|
19003
19850
|
async function writeEvidenceBundles(input) {
|
|
19004
|
-
const evidenceRoot =
|
|
19005
|
-
const renderAssetRoot =
|
|
19006
|
-
await (0,
|
|
19007
|
-
const prototypingPath =
|
|
19008
|
-
const renderPath =
|
|
19009
|
-
const browserQaPath =
|
|
19010
|
-
const browserQaSummaryPath =
|
|
19011
|
-
const browserQaFindingsPath =
|
|
19012
|
-
const browserQaRepairsPath =
|
|
19013
|
-
const breakthroughPath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
19161
|
-
var
|
|
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 =
|
|
19166
|
-
const content = await (0,
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
20095
|
+
allReviewerAxesPerfect100,
|
|
19248
20096
|
commitSha: iteration.commitSha
|
|
19249
20097
|
};
|
|
19250
20098
|
}
|
|
19251
20099
|
function compareSnapshots(left, right) {
|
|
19252
|
-
if (left.
|
|
19253
|
-
return left.
|
|
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
|
|
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
|
|
19303
|
-
var
|
|
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 =
|
|
19306
|
-
const info = await (0,
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
|
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 =
|
|
19330
|
-
const content = await (0,
|
|
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,
|
|
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 =
|
|
19356
|
-
const headContent = (await (0,
|
|
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,
|