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