qfai 1.7.0 → 1.7.2
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/assets/init/.qfai/assistant/skills/qfai-implement/SKILL.md +7 -0
- package/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +7 -0
- package/assets/init/.qfai/evidence/README.md +30 -0
- package/assets/validators/designSlopPatterns.json +56 -0
- package/dist/cli/index.cjs +966 -122
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +956 -112
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +777 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.mjs +778 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -35,6 +35,22 @@ var import_node_path10 = __toESM(require("path"), 1);
|
|
|
35
35
|
var import_promises = require("fs/promises");
|
|
36
36
|
var import_node_path = __toESM(require("path"), 1);
|
|
37
37
|
var import_yaml = require("yaml");
|
|
38
|
+
|
|
39
|
+
// src/core/uiux/renderEvidenceTypes.ts
|
|
40
|
+
var DEFAULT_RENDER_VIEWPORTS = ["desktop", "mobile"];
|
|
41
|
+
function normalizeRenderViewports(viewports) {
|
|
42
|
+
const normalized = Array.isArray(viewports) ? viewports.map((item) => item.trim()).filter((item) => item.length > 0) : [];
|
|
43
|
+
if (normalized.length > 0) {
|
|
44
|
+
return Array.from(new Set(normalized));
|
|
45
|
+
}
|
|
46
|
+
return [...DEFAULT_RENDER_VIEWPORTS];
|
|
47
|
+
}
|
|
48
|
+
function looksLikeInlineRenderPayload(value) {
|
|
49
|
+
const trimmed = value.trim().toLowerCase();
|
|
50
|
+
return trimmed.startsWith("data:image") || trimmed.includes("<html");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/core/config.ts
|
|
38
54
|
var defaultConfig = {
|
|
39
55
|
paths: {
|
|
40
56
|
contractsDir: ".qfai/contracts",
|
|
@@ -484,6 +500,133 @@ function normalizeUiux(raw, configPath, issues) {
|
|
|
484
500
|
);
|
|
485
501
|
}
|
|
486
502
|
}
|
|
503
|
+
if (raw.renderEvidence !== void 0) {
|
|
504
|
+
const renderEvidence = normalizeRenderEvidence(raw.renderEvidence, configPath, issues);
|
|
505
|
+
if (renderEvidence) {
|
|
506
|
+
result.renderEvidence = renderEvidence;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (raw.audit !== void 0) {
|
|
510
|
+
const audit = normalizeUiuxAudit(raw.audit, configPath, issues);
|
|
511
|
+
if (audit) {
|
|
512
|
+
result.audit = audit;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
516
|
+
}
|
|
517
|
+
function normalizeUiuxAudit(raw, configPath, issues) {
|
|
518
|
+
if (!isRecord(raw)) {
|
|
519
|
+
issues.push(configIssue(configPath, "uiux.audit \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
const result = {};
|
|
523
|
+
if (raw.enabled !== void 0) {
|
|
524
|
+
if (typeof raw.enabled === "boolean") {
|
|
525
|
+
result.enabled = raw.enabled;
|
|
526
|
+
} else {
|
|
527
|
+
issues.push(configIssue(configPath, "uiux.audit.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (raw.slopDetection !== void 0) {
|
|
531
|
+
if (typeof raw.slopDetection === "boolean") {
|
|
532
|
+
result.slopDetection = raw.slopDetection;
|
|
533
|
+
} else {
|
|
534
|
+
issues.push(
|
|
535
|
+
configIssue(configPath, "uiux.audit.slopDetection \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (raw.maxPrimaryCtas !== void 0) {
|
|
540
|
+
if (typeof raw.maxPrimaryCtas === "number" && Number.isFinite(raw.maxPrimaryCtas) && raw.maxPrimaryCtas >= 0) {
|
|
541
|
+
result.maxPrimaryCtas = raw.maxPrimaryCtas;
|
|
542
|
+
} else {
|
|
543
|
+
issues.push(
|
|
544
|
+
configIssue(configPath, "uiux.audit.maxPrimaryCtas \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (raw.maxRawTokenLiteralWarnings !== void 0) {
|
|
549
|
+
if (typeof raw.maxRawTokenLiteralWarnings === "number" && Number.isFinite(raw.maxRawTokenLiteralWarnings) && raw.maxRawTokenLiteralWarnings >= 0) {
|
|
550
|
+
result.maxRawTokenLiteralWarnings = raw.maxRawTokenLiteralWarnings;
|
|
551
|
+
} else {
|
|
552
|
+
issues.push(
|
|
553
|
+
configIssue(
|
|
554
|
+
configPath,
|
|
555
|
+
"uiux.audit.maxRawTokenLiteralWarnings \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (raw.maxDuplicateFindingsPerRule !== void 0) {
|
|
561
|
+
if (typeof raw.maxDuplicateFindingsPerRule === "number" && Number.isFinite(raw.maxDuplicateFindingsPerRule) && raw.maxDuplicateFindingsPerRule >= 0) {
|
|
562
|
+
result.maxDuplicateFindingsPerRule = raw.maxDuplicateFindingsPerRule;
|
|
563
|
+
} else {
|
|
564
|
+
issues.push(
|
|
565
|
+
configIssue(
|
|
566
|
+
configPath,
|
|
567
|
+
"uiux.audit.maxDuplicateFindingsPerRule \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
573
|
+
}
|
|
574
|
+
function normalizeRenderEvidence(raw, configPath, issues) {
|
|
575
|
+
if (!isRecord(raw)) {
|
|
576
|
+
issues.push(
|
|
577
|
+
configIssue(configPath, "uiux.renderEvidence \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
578
|
+
);
|
|
579
|
+
return void 0;
|
|
580
|
+
}
|
|
581
|
+
const result = {};
|
|
582
|
+
if (raw.enabled !== void 0) {
|
|
583
|
+
if (typeof raw.enabled === "boolean") {
|
|
584
|
+
result.enabled = raw.enabled;
|
|
585
|
+
} else {
|
|
586
|
+
issues.push(
|
|
587
|
+
configIssue(configPath, "uiux.renderEvidence.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (raw.viewports !== void 0) {
|
|
592
|
+
if (Array.isArray(raw.viewports) && raw.viewports.every((item) => typeof item === "string")) {
|
|
593
|
+
result.viewports = normalizeRenderViewports(raw.viewports);
|
|
594
|
+
} else {
|
|
595
|
+
issues.push(
|
|
596
|
+
configIssue(configPath, "uiux.renderEvidence.viewports \u306F\u6587\u5B57\u5217\u914D\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (raw.out !== void 0) {
|
|
601
|
+
if (typeof raw.out === "string" && raw.out.trim().length > 0) {
|
|
602
|
+
result.out = raw.out.trim();
|
|
603
|
+
} else {
|
|
604
|
+
issues.push(
|
|
605
|
+
configIssue(configPath, "uiux.renderEvidence.out \u306F\u7A7A\u3067\u306A\u3044\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (raw.baseUrl !== void 0) {
|
|
610
|
+
if (typeof raw.baseUrl === "string" && raw.baseUrl.trim().length > 0) {
|
|
611
|
+
result.baseUrl = raw.baseUrl.trim();
|
|
612
|
+
} else {
|
|
613
|
+
issues.push(
|
|
614
|
+
configIssue(
|
|
615
|
+
configPath,
|
|
616
|
+
"uiux.renderEvidence.baseUrl \u306F\u7A7A\u3067\u306A\u3044\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
617
|
+
)
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (raw.failOpen !== void 0) {
|
|
622
|
+
if (typeof raw.failOpen === "boolean") {
|
|
623
|
+
result.failOpen = raw.failOpen;
|
|
624
|
+
} else {
|
|
625
|
+
issues.push(
|
|
626
|
+
configIssue(configPath, "uiux.renderEvidence.failOpen \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
487
630
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
488
631
|
}
|
|
489
632
|
function configIssue(file, message) {
|
|
@@ -1564,8 +1707,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1564
1707
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
1565
1708
|
var import_node_url2 = require("url");
|
|
1566
1709
|
async function resolveToolVersion() {
|
|
1567
|
-
if ("1.7.
|
|
1568
|
-
return "1.7.
|
|
1710
|
+
if ("1.7.2".length > 0) {
|
|
1711
|
+
return "1.7.2";
|
|
1569
1712
|
}
|
|
1570
1713
|
try {
|
|
1571
1714
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -3324,6 +3467,7 @@ var ID_PREFIXES = [
|
|
|
3324
3467
|
"DB",
|
|
3325
3468
|
"THEMA"
|
|
3326
3469
|
];
|
|
3470
|
+
var DIGIT_AHEAD = "(?=[A-Za-z0-9_-]*\\d)";
|
|
3327
3471
|
var STRICT_ID_PATTERNS = {
|
|
3328
3472
|
CAP: /\bCAP-\d{4}\b/g,
|
|
3329
3473
|
SPEC: /\bSPEC-\d{4}\b/g,
|
|
@@ -3339,18 +3483,18 @@ var STRICT_ID_PATTERNS = {
|
|
|
3339
3483
|
ADR: /\bADR-\d{4}\b/g
|
|
3340
3484
|
};
|
|
3341
3485
|
var LOOSE_ID_PATTERNS = {
|
|
3342
|
-
CAP:
|
|
3343
|
-
SPEC:
|
|
3344
|
-
US:
|
|
3345
|
-
BR:
|
|
3346
|
-
SC:
|
|
3347
|
-
AC:
|
|
3348
|
-
CASE:
|
|
3349
|
-
UI:
|
|
3350
|
-
API:
|
|
3351
|
-
DB:
|
|
3352
|
-
THEMA:
|
|
3353
|
-
ADR:
|
|
3486
|
+
CAP: new RegExp(`\\bCAP-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3487
|
+
SPEC: new RegExp(`\\bSPEC-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3488
|
+
US: new RegExp(`\\bUS-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3489
|
+
BR: new RegExp(`\\bBR-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3490
|
+
SC: new RegExp(`\\bSC-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3491
|
+
AC: new RegExp(`\\bAC-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3492
|
+
CASE: new RegExp(`\\bCASE-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3493
|
+
UI: new RegExp(`\\bUI-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3494
|
+
API: new RegExp(`\\bAPI-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3495
|
+
DB: new RegExp(`\\bDB-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3496
|
+
THEMA: new RegExp(`\\bTHEMA-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi"),
|
|
3497
|
+
ADR: new RegExp(`\\bADR-${DIGIT_AHEAD}[A-Za-z0-9_-]+\\b`, "gi")
|
|
3354
3498
|
};
|
|
3355
3499
|
function extractIds(text, prefix) {
|
|
3356
3500
|
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
@@ -3932,6 +4076,8 @@ function formatError4(error2) {
|
|
|
3932
4076
|
var ENV_AUTOGEN = "QFAI_PROTOTYPE_FIDELITY_AUTOGEN";
|
|
3933
4077
|
var DEFAULT_EVIDENCE_PATH = ".qfai/evidence/prototyping.json";
|
|
3934
4078
|
async function runPrototyping(options) {
|
|
4079
|
+
const { config } = await loadConfig(options.root);
|
|
4080
|
+
const renderOptions = mergeRenderOptions(options, config.uiux?.renderEvidence);
|
|
3935
4081
|
const autogenEnabled = options.autogenUiFidelity || process.env[ENV_AUTOGEN] === "1";
|
|
3936
4082
|
if (!autogenEnabled) {
|
|
3937
4083
|
if (options.autogenOnly) {
|
|
@@ -3962,16 +4108,24 @@ async function runPrototyping(options) {
|
|
|
3962
4108
|
status: "skipped",
|
|
3963
4109
|
reason: "autogen not enabled (--autogen-ui-fidelity or env not set)"
|
|
3964
4110
|
});
|
|
3965
|
-
await
|
|
4111
|
+
const renderBundle2 = await maybeWriteRenderBundle(
|
|
4112
|
+
toolVersion2,
|
|
4113
|
+
"qfai prototyping --render-evidence",
|
|
4114
|
+
renderOptions,
|
|
4115
|
+
false
|
|
4116
|
+
);
|
|
4117
|
+
await writeEvidence(
|
|
4118
|
+
evidencePath2,
|
|
4119
|
+
applyRenderEvidence(skippedEvidence, renderOptions, false, renderBundle2?.path)
|
|
4120
|
+
);
|
|
3966
4121
|
return 0;
|
|
3967
4122
|
}
|
|
3968
|
-
const baseUrl = resolveBaseUrl(
|
|
4123
|
+
const baseUrl = resolveBaseUrl(renderOptions);
|
|
3969
4124
|
if (!baseUrl) {
|
|
3970
4125
|
error(`prototyping: --base-url \u307E\u305F\u306F QFAI_PROTOTYPE_BASE_URL \u306E\u6307\u5B9A\u304C\u5FC5\u8981\u3067\u3059\u3002`);
|
|
3971
4126
|
return 1;
|
|
3972
4127
|
}
|
|
3973
4128
|
const toolVersion = await resolveToolVersion();
|
|
3974
|
-
const { config } = await loadConfig(options.root);
|
|
3975
4129
|
const evidencePath = resolveEvidencePath(options.root, options.evidenceOut);
|
|
3976
4130
|
info(`prototyping: autogen uiFidelity \u3092\u5B9F\u884C\u3057\u307E\u3059 (baseUrl=${baseUrl})`);
|
|
3977
4131
|
let existingEvidence = {};
|
|
@@ -3998,7 +4152,16 @@ async function runPrototyping(options) {
|
|
|
3998
4152
|
status: "failed",
|
|
3999
4153
|
reason
|
|
4000
4154
|
});
|
|
4001
|
-
await
|
|
4155
|
+
const renderBundle2 = await maybeWriteRenderBundle(
|
|
4156
|
+
toolVersion,
|
|
4157
|
+
"qfai prototyping --render-evidence",
|
|
4158
|
+
renderOptions,
|
|
4159
|
+
true
|
|
4160
|
+
);
|
|
4161
|
+
await writeEvidence(
|
|
4162
|
+
evidencePath,
|
|
4163
|
+
applyRenderEvidence(failedEvidence, renderOptions, true, renderBundle2?.path)
|
|
4164
|
+
);
|
|
4002
4165
|
info(`prototyping: wrote evidence with status=failed to ${evidencePath}`);
|
|
4003
4166
|
return options.autogenOnly ? 1 : 0;
|
|
4004
4167
|
}
|
|
@@ -4017,7 +4180,16 @@ async function runPrototyping(options) {
|
|
|
4017
4180
|
crawled: result.crawled,
|
|
4018
4181
|
reason
|
|
4019
4182
|
});
|
|
4020
|
-
await
|
|
4183
|
+
const renderBundle2 = await maybeWriteRenderBundle(
|
|
4184
|
+
toolVersion,
|
|
4185
|
+
"qfai prototyping --render-evidence",
|
|
4186
|
+
renderOptions,
|
|
4187
|
+
true
|
|
4188
|
+
);
|
|
4189
|
+
await writeEvidence(
|
|
4190
|
+
evidencePath,
|
|
4191
|
+
applyRenderEvidence(failedEvidence, renderOptions, true, renderBundle2?.path)
|
|
4192
|
+
);
|
|
4021
4193
|
info(`prototyping: wrote evidence with status=failed to ${evidencePath}`);
|
|
4022
4194
|
return options.autogenOnly ? 1 : 0;
|
|
4023
4195
|
}
|
|
@@ -4030,7 +4202,16 @@ async function runPrototyping(options) {
|
|
|
4030
4202
|
screens: result.screens,
|
|
4031
4203
|
crawled: result.crawled
|
|
4032
4204
|
});
|
|
4033
|
-
await
|
|
4205
|
+
const renderBundle = await maybeWriteRenderBundle(
|
|
4206
|
+
toolVersion,
|
|
4207
|
+
"qfai prototyping --render-evidence",
|
|
4208
|
+
renderOptions,
|
|
4209
|
+
true
|
|
4210
|
+
);
|
|
4211
|
+
await writeEvidence(
|
|
4212
|
+
evidencePath,
|
|
4213
|
+
applyRenderEvidence(successEvidence, renderOptions, true, renderBundle?.path)
|
|
4214
|
+
);
|
|
4034
4215
|
const routeOkCount = result.crawled.filter((r) => r.status === "ok").length;
|
|
4035
4216
|
const routeFailCount = result.crawled.filter((r) => r.status === "failed").length;
|
|
4036
4217
|
const avgCoverage = result.screens.length > 0 ? (result.screens.reduce((sum, s) => sum + s.coverage, 0) / result.screens.length).toFixed(2) : "N/A";
|
|
@@ -4060,6 +4241,75 @@ async function writeEvidence(filePath, evidence) {
|
|
|
4060
4241
|
await (0, import_promises14.mkdir)(import_node_path17.default.dirname(filePath), { recursive: true });
|
|
4061
4242
|
await (0, import_promises14.writeFile)(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
|
|
4062
4243
|
}
|
|
4244
|
+
async function writeRenderBundle(filePath, bundle) {
|
|
4245
|
+
await (0, import_promises14.mkdir)(import_node_path17.default.dirname(filePath), { recursive: true });
|
|
4246
|
+
await (0, import_promises14.writeFile)(filePath, JSON.stringify(bundle, null, 2) + "\n", "utf-8");
|
|
4247
|
+
}
|
|
4248
|
+
async function maybeWriteRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
4249
|
+
if (!options.renderEvidence) {
|
|
4250
|
+
return void 0;
|
|
4251
|
+
}
|
|
4252
|
+
const renderBundle = buildRenderBundle(toolVersion, command, options, autogenEnabled);
|
|
4253
|
+
await writeRenderBundle(renderBundle.path, renderBundle.bundle);
|
|
4254
|
+
return renderBundle;
|
|
4255
|
+
}
|
|
4256
|
+
function applyRenderEvidence(evidence, options, autogenEnabled, outputPath) {
|
|
4257
|
+
if (!options.renderEvidence) {
|
|
4258
|
+
return evidence;
|
|
4259
|
+
}
|
|
4260
|
+
const viewports = normalizeRenderViewports(options.renderViewports);
|
|
4261
|
+
return {
|
|
4262
|
+
...evidence,
|
|
4263
|
+
renderEvidence: {
|
|
4264
|
+
status: autogenEnabled ? "requested" : "skipped",
|
|
4265
|
+
requested: true,
|
|
4266
|
+
autogenEnabled,
|
|
4267
|
+
viewports,
|
|
4268
|
+
outputPath: outputPath ?? resolveRenderOutPath(options.root, options.renderOut),
|
|
4269
|
+
reason: autogenEnabled ? "render evidence capture not implemented in this slice" : "render requested without autogen-ui-fidelity"
|
|
4270
|
+
}
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
function buildRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
4274
|
+
const path64 = resolveRenderOutPath(options.root, options.renderOut);
|
|
4275
|
+
return {
|
|
4276
|
+
path: path64,
|
|
4277
|
+
bundle: {
|
|
4278
|
+
meta: {
|
|
4279
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4280
|
+
toolVersion,
|
|
4281
|
+
commands: [command]
|
|
4282
|
+
},
|
|
4283
|
+
renderEvidence: {
|
|
4284
|
+
status: autogenEnabled ? "requested" : "skipped",
|
|
4285
|
+
requested: true,
|
|
4286
|
+
autogenEnabled,
|
|
4287
|
+
viewports: normalizeRenderViewports(options.renderViewports),
|
|
4288
|
+
outputPath: path64,
|
|
4289
|
+
reason: autogenEnabled ? "render evidence capture not implemented in this slice" : "render requested without autogen-ui-fidelity"
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
function mergeRenderOptions(options, configRenderEvidence) {
|
|
4295
|
+
const cliViewportsSpecified = Array.isArray(options.renderViewports);
|
|
4296
|
+
const mergedViewports = cliViewportsSpecified ? normalizeRenderViewports(options.renderViewports) : normalizeRenderViewports(configRenderEvidence?.viewports);
|
|
4297
|
+
const renderOut = options.renderOut ?? configRenderEvidence?.out;
|
|
4298
|
+
const baseUrl = options.baseUrl ?? configRenderEvidence?.baseUrl;
|
|
4299
|
+
return {
|
|
4300
|
+
...options,
|
|
4301
|
+
renderEvidence: options.renderEvidence || configRenderEvidence?.enabled === true,
|
|
4302
|
+
renderViewports: mergedViewports,
|
|
4303
|
+
...renderOut ? { renderOut } : {},
|
|
4304
|
+
...baseUrl ? { baseUrl } : {}
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
4307
|
+
function resolveRenderOutPath(root, explicit) {
|
|
4308
|
+
if (explicit) {
|
|
4309
|
+
return import_node_path17.default.isAbsolute(explicit) ? explicit : import_node_path17.default.resolve(root, explicit);
|
|
4310
|
+
}
|
|
4311
|
+
return import_node_path17.default.resolve(root, ".qfai/evidence/render.json");
|
|
4312
|
+
}
|
|
4063
4313
|
function extractRouteHintsFromEvidence(evidence) {
|
|
4064
4314
|
const routes = /* @__PURE__ */ new Set();
|
|
4065
4315
|
const runtimeGate = evidence.runtimeGate;
|
|
@@ -4101,8 +4351,8 @@ function extractRouteHintsFromEvidence(evidence) {
|
|
|
4101
4351
|
}
|
|
4102
4352
|
|
|
4103
4353
|
// src/cli/commands/report.ts
|
|
4104
|
-
var
|
|
4105
|
-
var
|
|
4354
|
+
var import_promises58 = require("fs/promises");
|
|
4355
|
+
var import_node_path61 = __toESM(require("path"), 1);
|
|
4106
4356
|
|
|
4107
4357
|
// src/core/normalize.ts
|
|
4108
4358
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -4197,8 +4447,8 @@ async function createPhaseGuardResult(phase, blockedIssue) {
|
|
|
4197
4447
|
}
|
|
4198
4448
|
|
|
4199
4449
|
// src/core/report.ts
|
|
4200
|
-
var
|
|
4201
|
-
var
|
|
4450
|
+
var import_promises56 = require("fs/promises");
|
|
4451
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
4202
4452
|
|
|
4203
4453
|
// src/core/contractIndex.ts
|
|
4204
4454
|
var import_promises15 = require("fs/promises");
|
|
@@ -8378,11 +8628,11 @@ var import_node_path29 = __toESM(require("path"), 1);
|
|
|
8378
8628
|
// src/core/atddTraceability.ts
|
|
8379
8629
|
var import_promises26 = require("fs/promises");
|
|
8380
8630
|
var import_node_path28 = __toESM(require("path"), 1);
|
|
8381
|
-
var US_TEST_ANNOTATION_RE = /\bQFAI:SPEC-(\d{4}):US-(\d{4})\b/g;
|
|
8382
|
-
var TC_TEST_ANNOTATION_RE = /\bQFAI:SPEC-(\d{4}):TC-(\d{4})\b/g;
|
|
8631
|
+
var US_TEST_ANNOTATION_RE = /\bQFAI:SPEC-(\d{4}):US-(\d{4}(?:-\d{4})?)\b/g;
|
|
8632
|
+
var TC_TEST_ANNOTATION_RE = /\bQFAI:SPEC-(\d{4}):TC-(\d{4}(?:-\d{4})?)\b/g;
|
|
8383
8633
|
var API_TEST_ANNOTATION_RE = /\bQFAI:CON-API-(\d+)\b/g;
|
|
8384
|
-
var
|
|
8385
|
-
var
|
|
8634
|
+
var US_ID_RE2 = /^US-\d{4}(?:-\d{4})?$/;
|
|
8635
|
+
var TC_ID_RE = /^TC-\d{4}(?:-\d{4})?$/;
|
|
8386
8636
|
var API_CONTRACT_ID_RE = /^CON-API-\d+$/;
|
|
8387
8637
|
var TEST_FILE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts,feature,md,markdown}";
|
|
8388
8638
|
async function evaluateAtddCodeTraceability(root, config) {
|
|
@@ -8530,11 +8780,11 @@ async function collectApiContractIds(apiRoot) {
|
|
|
8530
8780
|
function collectShortIds(text, prefix) {
|
|
8531
8781
|
const ids = /* @__PURE__ */ new Set();
|
|
8532
8782
|
const headingIds = collectMarkdownItems(text, prefix).map((item) => item.id);
|
|
8533
|
-
const pattern = prefix === "US" ? /\bUS-\d{4}
|
|
8783
|
+
const pattern = prefix === "US" ? /\bUS-\d{4}(?:-\d{4})?\b/g : /\bTC-\d{4}(?:-\d{4})?\b/g;
|
|
8534
8784
|
const looseIds = uniqueMatches(text, pattern);
|
|
8535
8785
|
for (const id of [...headingIds, ...looseIds]) {
|
|
8536
8786
|
const normalized = id.toUpperCase();
|
|
8537
|
-
if (prefix === "US" &&
|
|
8787
|
+
if (prefix === "US" && US_ID_RE2.test(normalized) || prefix === "TC" && TC_ID_RE.test(normalized)) {
|
|
8538
8788
|
ids.add(normalized);
|
|
8539
8789
|
}
|
|
8540
8790
|
}
|
|
@@ -9543,15 +9793,15 @@ var import_node_path33 = __toESM(require("path"), 1);
|
|
|
9543
9793
|
var import_promises33 = require("fs/promises");
|
|
9544
9794
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
9545
9795
|
var ID_PATTERNS = {
|
|
9546
|
-
us: /^US-\d{4}
|
|
9547
|
-
ac: /^AC-\d{4}
|
|
9548
|
-
br: /^BR-\d{4}
|
|
9549
|
-
ex: /^EX-\d{4}
|
|
9796
|
+
us: /^US-\d{4}(?:-\d{4})?$/,
|
|
9797
|
+
ac: /^AC-\d{4}(?:-\d{4})?$/,
|
|
9798
|
+
br: /^BR-\d{4}(?:-\d{4})?$/,
|
|
9799
|
+
ex: /^EX-\d{4}(?:-\d{4})?$/
|
|
9550
9800
|
};
|
|
9551
9801
|
var V1421_REFS = {
|
|
9552
|
-
ac: /\bAC-\d{4}
|
|
9553
|
-
br: /\bBR-\d{4}
|
|
9554
|
-
ex: /\bEX-\d{4}
|
|
9802
|
+
ac: /\bAC-\d{4}(?:-\d{4})?\b/gi,
|
|
9803
|
+
br: /\bBR-\d{4}(?:-\d{4})?\b/gi,
|
|
9804
|
+
ex: /\bEX-\d{4}(?:-\d{4})?\b/gi
|
|
9555
9805
|
};
|
|
9556
9806
|
async function validateLayerCoverage(root, config) {
|
|
9557
9807
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -9824,11 +10074,11 @@ function parseAcceptanceCriteriaIds2(text) {
|
|
|
9824
10074
|
const ids = /* @__PURE__ */ new Set();
|
|
9825
10075
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
9826
10076
|
for (const line of lines) {
|
|
9827
|
-
const headingMatch = /^##\s*(AC-\d{4})\b/i.exec(line.trim());
|
|
10077
|
+
const headingMatch = /^##\s*(AC-\d{4}(?:-\d{4})?)\b/i.exec(line.trim());
|
|
9828
10078
|
if (headingMatch?.[1]) {
|
|
9829
10079
|
ids.add(headingMatch[1].toUpperCase());
|
|
9830
10080
|
}
|
|
9831
|
-
const commentMatch = /^\s*#\s*(AC-\d{4})\b/i.exec(line);
|
|
10081
|
+
const commentMatch = /^\s*#\s*(AC-\d{4}(?:-\d{4})?)\b/i.exec(line);
|
|
9832
10082
|
if (commentMatch?.[1]) {
|
|
9833
10083
|
ids.add(commentMatch[1].toUpperCase());
|
|
9834
10084
|
}
|
|
@@ -9848,8 +10098,8 @@ function parseAcceptanceCriteriaIds2(text) {
|
|
|
9848
10098
|
function parseDefinitionRefs(text, prefix, refPattern, options = {}) {
|
|
9849
10099
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
9850
10100
|
const refsById = /* @__PURE__ */ new Map();
|
|
9851
|
-
const idPattern = new RegExp(`^${prefix}-\\d{4}
|
|
9852
|
-
const headingPattern = new RegExp(`^##\\s*(${prefix}-\\d{4})\\b`, "i");
|
|
10101
|
+
const idPattern = new RegExp(`^${prefix}-\\d{4}(?:-\\d{4})?$`);
|
|
10102
|
+
const headingPattern = new RegExp(`^##\\s*(${prefix}-\\d{4}(?:-\\d{4})?)\\b`, "i");
|
|
9853
10103
|
const referenceColumns = new Set(
|
|
9854
10104
|
(options.referenceColumns ?? []).map((column) => normalizeColumnName(column))
|
|
9855
10105
|
);
|
|
@@ -10250,7 +10500,7 @@ var US_DOWNSTREAM_RE = /\b(?:AC|BR|EX|TC)-\d{4}\b/g;
|
|
|
10250
10500
|
var AC_DOWNSTREAM_RE = /\b(?:BR|EX|TC)-\d{4}\b/g;
|
|
10251
10501
|
var BR_DOWNSTREAM_RE = /\b(?:EX|TC)-\d{4}\b/g;
|
|
10252
10502
|
var CAP_ID_RE = /^CAP-\d{4}$/;
|
|
10253
|
-
var
|
|
10503
|
+
var US_ID_RE3 = /^US-\d{4}$/;
|
|
10254
10504
|
var AC_ID_RE4 = /^AC-\d{4}$/;
|
|
10255
10505
|
var BR_OR_AC_ID_RE = /^(?:BR|AC)-\d{4}$/;
|
|
10256
10506
|
var EX_ID_RE2 = /^EX-\d{4}$/;
|
|
@@ -10289,7 +10539,7 @@ async function validateLayeredTraceability(root, config) {
|
|
|
10289
10539
|
...await validateMarkdownParentFormat(entry.userStoriesPath, "US", CAP_ID_RE, "CAP")
|
|
10290
10540
|
);
|
|
10291
10541
|
issues.push(
|
|
10292
|
-
...await validateMarkdownParentFormat(entry.acceptanceCriteriaPath, "AC",
|
|
10542
|
+
...await validateMarkdownParentFormat(entry.acceptanceCriteriaPath, "AC", US_ID_RE3, "US")
|
|
10293
10543
|
);
|
|
10294
10544
|
issues.push(
|
|
10295
10545
|
...await validateMarkdownParentFormat(entry.businessRulesPath, "BR", AC_ID_RE4, "AC")
|
|
@@ -11601,6 +11851,100 @@ async function validateUiFidelity(root, config, evidenceJsonPath, evidence) {
|
|
|
11601
11851
|
)
|
|
11602
11852
|
);
|
|
11603
11853
|
}
|
|
11854
|
+
const renderIssues = await validateRenderEvidenceScreens(
|
|
11855
|
+
root,
|
|
11856
|
+
config,
|
|
11857
|
+
evidenceJsonPath,
|
|
11858
|
+
uiFidelity.screens
|
|
11859
|
+
);
|
|
11860
|
+
issues.push(...renderIssues);
|
|
11861
|
+
return issues;
|
|
11862
|
+
}
|
|
11863
|
+
async function validateRenderEvidenceScreens(root, config, evidenceJsonPath, screens) {
|
|
11864
|
+
const issues = [];
|
|
11865
|
+
const hasAnyRenderEvidence = screens.some((screen) => screen.renders.length > 0);
|
|
11866
|
+
if (!hasAnyRenderEvidence) {
|
|
11867
|
+
return issues;
|
|
11868
|
+
}
|
|
11869
|
+
const qualityProfile = config.uiux?.qualityProfile ?? "default";
|
|
11870
|
+
for (const screen of screens) {
|
|
11871
|
+
if (screen.renders.length === 0) {
|
|
11872
|
+
continue;
|
|
11873
|
+
}
|
|
11874
|
+
const viewports = new Set(screen.renders.map((render) => render.viewport));
|
|
11875
|
+
const missingDefaultViewports = DEFAULT_RENDER_VIEWPORTS.filter(
|
|
11876
|
+
(viewport) => !viewports.has(viewport)
|
|
11877
|
+
);
|
|
11878
|
+
const allSkipped = screen.renders.every((render) => render.status === "skipped");
|
|
11879
|
+
for (const render of screen.renders) {
|
|
11880
|
+
if (render.status !== "captured") {
|
|
11881
|
+
continue;
|
|
11882
|
+
}
|
|
11883
|
+
const invalidPaths = [
|
|
11884
|
+
{ label: "imagePath", value: render.imagePath },
|
|
11885
|
+
{ label: "htmlPath", value: render.htmlPath }
|
|
11886
|
+
].filter((entry) => looksLikeInlineRenderPayload(entry.value));
|
|
11887
|
+
if (invalidPaths.length > 0) {
|
|
11888
|
+
issues.push(
|
|
11889
|
+
issue(
|
|
11890
|
+
"QFAI-PROT-244",
|
|
11891
|
+
`QFAI-PROT-244: render evidence must be path-only. route=${screen.route}, viewport=${render.viewport}, invalid=${invalidPaths.map((entry) => entry.label).join("|")}`,
|
|
11892
|
+
"error",
|
|
11893
|
+
evidenceJsonPath,
|
|
11894
|
+
"prototypingEvidence.renderArtifactPresence",
|
|
11895
|
+
[
|
|
11896
|
+
`route=${screen.route}`,
|
|
11897
|
+
`viewport=${render.viewport}`,
|
|
11898
|
+
...invalidPaths.map((entry) => `artifact=${entry.label}`)
|
|
11899
|
+
],
|
|
11900
|
+
"change",
|
|
11901
|
+
"imagePath/htmlPath \u306B\u306F\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u306E\u307F\u3092\u4FDD\u5B58\u3057\u3001data URI \u3084 HTML \u672C\u6587\u3092 JSON \u306B\u57CB\u3081\u8FBC\u307E\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002"
|
|
11902
|
+
)
|
|
11903
|
+
);
|
|
11904
|
+
continue;
|
|
11905
|
+
}
|
|
11906
|
+
const missingArtifacts = await collectMissingRenderArtifacts(root, render);
|
|
11907
|
+
if (missingArtifacts.length > 0) {
|
|
11908
|
+
issues.push(
|
|
11909
|
+
issue(
|
|
11910
|
+
"QFAI-PROT-244",
|
|
11911
|
+
`QFAI-PROT-244: captured render artifact is missing. route=${screen.route}, viewport=${render.viewport}, missing=${missingArtifacts.join("|")}`,
|
|
11912
|
+
"error",
|
|
11913
|
+
evidenceJsonPath,
|
|
11914
|
+
"prototypingEvidence.renderArtifactPresence",
|
|
11915
|
+
[
|
|
11916
|
+
`route=${screen.route}`,
|
|
11917
|
+
`viewport=${render.viewport}`,
|
|
11918
|
+
...missingArtifacts.map((artifact) => `artifact=${artifact}`)
|
|
11919
|
+
],
|
|
11920
|
+
"change",
|
|
11921
|
+
"render capture \u3092\u518D\u5B9F\u884C\u3057\u3001screenshot \u3068 HTML snapshot \u306E\u4E21\u65B9\u304C\u4FDD\u5B58\u3055\u308C\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
11922
|
+
)
|
|
11923
|
+
);
|
|
11924
|
+
}
|
|
11925
|
+
}
|
|
11926
|
+
if (missingDefaultViewports.length === 0 && !allSkipped) {
|
|
11927
|
+
continue;
|
|
11928
|
+
}
|
|
11929
|
+
const severity = qualityProfile === "default" ? "warning" : "error";
|
|
11930
|
+
const reason = allSkipped ? "all renders are skipped" : `missing default viewports=${missingDefaultViewports.join("|")}`;
|
|
11931
|
+
issues.push(
|
|
11932
|
+
issue(
|
|
11933
|
+
"QFAI-PROT-245",
|
|
11934
|
+
`QFAI-PROT-245: render coverage is incomplete for ${screen.route}. ${reason}. qualityProfile=${qualityProfile}`,
|
|
11935
|
+
severity,
|
|
11936
|
+
evidenceJsonPath,
|
|
11937
|
+
"prototypingEvidence.renderCoverage",
|
|
11938
|
+
[
|
|
11939
|
+
`route=${screen.route}`,
|
|
11940
|
+
...missingDefaultViewports.map((viewport) => `viewport=${viewport}`),
|
|
11941
|
+
`qualityProfile=${qualityProfile}`
|
|
11942
|
+
],
|
|
11943
|
+
"change",
|
|
11944
|
+
allSkipped ? "\u5C11\u306A\u304F\u3068\u3082 desktop/mobile \u306E\u3044\u305A\u308C\u304B\u3067 captured \u307E\u305F\u306F failed \u306E\u660E\u793A\u7684\u306A render outcome \u3092\u6B8B\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "desktop/mobile \u306E default viewport \u3092\u63C3\u3048\u308B\u304B\u3001profile \u8A2D\u5B9A\u3068 scope \u3092\u898B\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
11945
|
+
)
|
|
11946
|
+
);
|
|
11947
|
+
}
|
|
11604
11948
|
return issues;
|
|
11605
11949
|
}
|
|
11606
11950
|
function formatUiFidelityMismatch(mismatch) {
|
|
@@ -11813,6 +12157,10 @@ function normalizeUiFidelityScreen(value) {
|
|
|
11813
12157
|
if (!mockPaths.ok) {
|
|
11814
12158
|
return mockPaths;
|
|
11815
12159
|
}
|
|
12160
|
+
const renders = normalizeRenderEntries(value.renders);
|
|
12161
|
+
if (!renders.ok) {
|
|
12162
|
+
return renders;
|
|
12163
|
+
}
|
|
11816
12164
|
return {
|
|
11817
12165
|
ok: true,
|
|
11818
12166
|
value: {
|
|
@@ -11823,8 +12171,103 @@ function normalizeUiFidelityScreen(value) {
|
|
|
11823
12171
|
...normalizeOptionalMissingBlock(value.missing),
|
|
11824
12172
|
...typeof value.coverage === "number" ? { coverage: value.coverage } : {},
|
|
11825
12173
|
observed: observed.value,
|
|
11826
|
-
mockPaths: mockPaths.value
|
|
12174
|
+
mockPaths: mockPaths.value,
|
|
12175
|
+
renders: renders.value
|
|
12176
|
+
}
|
|
12177
|
+
};
|
|
12178
|
+
}
|
|
12179
|
+
function normalizeRenderEntries(value) {
|
|
12180
|
+
if (value === void 0) {
|
|
12181
|
+
return { ok: true, value: [] };
|
|
12182
|
+
}
|
|
12183
|
+
if (!Array.isArray(value)) {
|
|
12184
|
+
return { ok: false, reason: "`uiFidelity.screens[].renders` must be an array" };
|
|
12185
|
+
}
|
|
12186
|
+
const renders = [];
|
|
12187
|
+
for (const entry of value) {
|
|
12188
|
+
const normalized = normalizeRenderEntry(entry);
|
|
12189
|
+
if (!normalized.ok) {
|
|
12190
|
+
return normalized;
|
|
12191
|
+
}
|
|
12192
|
+
renders.push(normalized.value);
|
|
12193
|
+
}
|
|
12194
|
+
return { ok: true, value: renders };
|
|
12195
|
+
}
|
|
12196
|
+
function normalizeRenderEntry(value) {
|
|
12197
|
+
if (!isRecord5(value)) {
|
|
12198
|
+
return { ok: false, reason: "`uiFidelity.screens[].renders[]` must be objects" };
|
|
12199
|
+
}
|
|
12200
|
+
if (typeof value.viewport !== "string" || value.viewport.trim().length === 0) {
|
|
12201
|
+
return { ok: false, reason: "`uiFidelity.screens[].renders[].viewport` is required" };
|
|
12202
|
+
}
|
|
12203
|
+
if (!isNonNegativeInteger(value.width) || !isNonNegativeInteger(value.height) || value.width === 0 || value.height === 0) {
|
|
12204
|
+
return {
|
|
12205
|
+
ok: false,
|
|
12206
|
+
reason: "`uiFidelity.screens[].renders[]` requires positive integers for width/height"
|
|
12207
|
+
};
|
|
12208
|
+
}
|
|
12209
|
+
const viewport = value.viewport.trim();
|
|
12210
|
+
const width = value.width;
|
|
12211
|
+
const height = value.height;
|
|
12212
|
+
const status = typeof value.status === "string" ? value.status.trim().toLowerCase() : "";
|
|
12213
|
+
if (status === "captured") {
|
|
12214
|
+
if (typeof value.imagePath !== "string" || value.imagePath.trim().length === 0 || typeof value.htmlPath !== "string" || value.htmlPath.trim().length === 0) {
|
|
12215
|
+
return {
|
|
12216
|
+
ok: false,
|
|
12217
|
+
reason: "`captured` render entries require imagePath and htmlPath"
|
|
12218
|
+
};
|
|
12219
|
+
}
|
|
12220
|
+
return {
|
|
12221
|
+
ok: true,
|
|
12222
|
+
value: {
|
|
12223
|
+
viewport,
|
|
12224
|
+
status: "captured",
|
|
12225
|
+
width,
|
|
12226
|
+
height,
|
|
12227
|
+
imagePath: value.imagePath.trim(),
|
|
12228
|
+
htmlPath: value.htmlPath.trim()
|
|
12229
|
+
}
|
|
12230
|
+
};
|
|
12231
|
+
}
|
|
12232
|
+
if (status === "skipped") {
|
|
12233
|
+
if (typeof value.skippedReason !== "string" || value.skippedReason.trim().length === 0) {
|
|
12234
|
+
return {
|
|
12235
|
+
ok: false,
|
|
12236
|
+
reason: "`skipped` render entries require skippedReason"
|
|
12237
|
+
};
|
|
12238
|
+
}
|
|
12239
|
+
return {
|
|
12240
|
+
ok: true,
|
|
12241
|
+
value: {
|
|
12242
|
+
viewport,
|
|
12243
|
+
status: "skipped",
|
|
12244
|
+
width,
|
|
12245
|
+
height,
|
|
12246
|
+
skippedReason: value.skippedReason.trim()
|
|
12247
|
+
}
|
|
12248
|
+
};
|
|
12249
|
+
}
|
|
12250
|
+
if (status === "failed") {
|
|
12251
|
+
if (typeof value.error !== "string" || value.error.trim().length === 0) {
|
|
12252
|
+
return {
|
|
12253
|
+
ok: false,
|
|
12254
|
+
reason: "`failed` render entries require error"
|
|
12255
|
+
};
|
|
11827
12256
|
}
|
|
12257
|
+
return {
|
|
12258
|
+
ok: true,
|
|
12259
|
+
value: {
|
|
12260
|
+
viewport,
|
|
12261
|
+
status: "failed",
|
|
12262
|
+
width,
|
|
12263
|
+
height,
|
|
12264
|
+
error: value.error.trim()
|
|
12265
|
+
}
|
|
12266
|
+
};
|
|
12267
|
+
}
|
|
12268
|
+
return {
|
|
12269
|
+
ok: false,
|
|
12270
|
+
reason: "`uiFidelity.screens[].renders[].status` must be captured|skipped|failed"
|
|
11828
12271
|
};
|
|
11829
12272
|
}
|
|
11830
12273
|
function normalizeUiFidelityExpected(value) {
|
|
@@ -12050,6 +12493,22 @@ function normalizeOptionalMissingBlock(value) {
|
|
|
12050
12493
|
function isRecord5(value) {
|
|
12051
12494
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12052
12495
|
}
|
|
12496
|
+
async function collectMissingRenderArtifacts(root, render) {
|
|
12497
|
+
const missing = [];
|
|
12498
|
+
const candidates = [
|
|
12499
|
+
{ label: "imagePath", target: render.imagePath },
|
|
12500
|
+
{ label: "htmlPath", target: render.htmlPath }
|
|
12501
|
+
];
|
|
12502
|
+
for (const candidate of candidates) {
|
|
12503
|
+
const resolved = import_node_path39.default.isAbsolute(candidate.target) ? candidate.target : import_node_path39.default.resolve(root, candidate.target);
|
|
12504
|
+
try {
|
|
12505
|
+
await (0, import_promises37.access)(resolved);
|
|
12506
|
+
} catch {
|
|
12507
|
+
missing.push(candidate.label);
|
|
12508
|
+
}
|
|
12509
|
+
}
|
|
12510
|
+
return missing;
|
|
12511
|
+
}
|
|
12053
12512
|
function isInteger(value) {
|
|
12054
12513
|
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
12055
12514
|
}
|
|
@@ -12398,7 +12857,7 @@ function collectLayer(layer, layerName, target, errors) {
|
|
|
12398
12857
|
}
|
|
12399
12858
|
function flattenTokens(obj, prefix, target, errors) {
|
|
12400
12859
|
for (const [key, value] of Object.entries(obj)) {
|
|
12401
|
-
const
|
|
12860
|
+
const path64 = `${prefix}.${key}`;
|
|
12402
12861
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
12403
12862
|
const record2 = value;
|
|
12404
12863
|
if ("$value" in record2) {
|
|
@@ -12414,9 +12873,9 @@ function flattenTokens(obj, prefix, target, errors) {
|
|
|
12414
12873
|
if (typeof record2.platform === "string") {
|
|
12415
12874
|
token.platform = record2.platform;
|
|
12416
12875
|
}
|
|
12417
|
-
target.set(
|
|
12876
|
+
target.set(path64, token);
|
|
12418
12877
|
} else {
|
|
12419
|
-
flattenTokens(record2,
|
|
12878
|
+
flattenTokens(record2, path64, target, errors);
|
|
12420
12879
|
}
|
|
12421
12880
|
}
|
|
12422
12881
|
}
|
|
@@ -12426,44 +12885,44 @@ function resolveAllReferences(result) {
|
|
|
12426
12885
|
for (const [key, val] of result.primitives) allTokens.set(key, val);
|
|
12427
12886
|
for (const [key, val] of result.semantics) allTokens.set(key, val);
|
|
12428
12887
|
for (const [key, val] of result.components) allTokens.set(key, val);
|
|
12429
|
-
for (const [
|
|
12430
|
-
resolveTokenRef(
|
|
12888
|
+
for (const [path64] of allTokens) {
|
|
12889
|
+
resolveTokenRef(path64, allTokens, /* @__PURE__ */ new Set(), 0, result);
|
|
12431
12890
|
}
|
|
12432
12891
|
}
|
|
12433
|
-
function resolveTokenRef(
|
|
12434
|
-
if (result.resolved.has(
|
|
12435
|
-
return result.resolved.get(
|
|
12892
|
+
function resolveTokenRef(path64, allTokens, visited, depth, result) {
|
|
12893
|
+
if (result.resolved.has(path64)) {
|
|
12894
|
+
return result.resolved.get(path64);
|
|
12436
12895
|
}
|
|
12437
12896
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
12438
12897
|
result.errors.push({
|
|
12439
|
-
message: `Max reference depth exceeded at: ${
|
|
12440
|
-
path:
|
|
12898
|
+
message: `Max reference depth exceeded at: ${path64}`,
|
|
12899
|
+
path: path64
|
|
12441
12900
|
});
|
|
12442
12901
|
return void 0;
|
|
12443
12902
|
}
|
|
12444
|
-
if (visited.has(
|
|
12903
|
+
if (visited.has(path64)) {
|
|
12445
12904
|
result.errors.push({
|
|
12446
|
-
message: `Circular reference detected: ${
|
|
12447
|
-
path:
|
|
12905
|
+
message: `Circular reference detected: ${path64}`,
|
|
12906
|
+
path: path64
|
|
12448
12907
|
});
|
|
12449
12908
|
return void 0;
|
|
12450
12909
|
}
|
|
12451
|
-
const token = allTokens.get(
|
|
12910
|
+
const token = allTokens.get(path64);
|
|
12452
12911
|
if (!token) {
|
|
12453
12912
|
return void 0;
|
|
12454
12913
|
}
|
|
12455
12914
|
if (typeof token.$value !== "string") {
|
|
12456
12915
|
const rawValue2 = stringifyTokenValue(token.$value);
|
|
12457
|
-
result.resolved.set(
|
|
12916
|
+
result.resolved.set(path64, rawValue2);
|
|
12458
12917
|
return rawValue2;
|
|
12459
12918
|
}
|
|
12460
12919
|
const rawValue = stringifyTokenValue(token.$value);
|
|
12461
12920
|
const refs = [...rawValue.matchAll(REF_PATTERN)];
|
|
12462
12921
|
if (refs.length === 0) {
|
|
12463
|
-
result.resolved.set(
|
|
12922
|
+
result.resolved.set(path64, rawValue);
|
|
12464
12923
|
return rawValue;
|
|
12465
12924
|
}
|
|
12466
|
-
visited.add(
|
|
12925
|
+
visited.add(path64);
|
|
12467
12926
|
let resolved = rawValue;
|
|
12468
12927
|
for (const ref of refs) {
|
|
12469
12928
|
const refPath = ref[1];
|
|
@@ -12471,8 +12930,8 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12471
12930
|
const refToken = allTokens.get(refPath);
|
|
12472
12931
|
if (!refToken) {
|
|
12473
12932
|
result.errors.push({
|
|
12474
|
-
message: `Unresolved token reference: {${refPath}} at ${
|
|
12475
|
-
path:
|
|
12933
|
+
message: `Unresolved token reference: {${refPath}} at ${path64}`,
|
|
12934
|
+
path: path64
|
|
12476
12935
|
});
|
|
12477
12936
|
continue;
|
|
12478
12937
|
}
|
|
@@ -12481,7 +12940,7 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12481
12940
|
resolved = resolved.split(`{${refPath}}`).join(refValue);
|
|
12482
12941
|
}
|
|
12483
12942
|
}
|
|
12484
|
-
result.resolved.set(
|
|
12943
|
+
result.resolved.set(path64, resolved);
|
|
12485
12944
|
return resolved;
|
|
12486
12945
|
}
|
|
12487
12946
|
function stringifyTokenValue(value) {
|
|
@@ -15593,6 +16052,7 @@ async function validateNavigationFlow(root, config) {
|
|
|
15593
16052
|
|
|
15594
16053
|
// src/core/validators/renderCritique.ts
|
|
15595
16054
|
var import_node_path54 = __toESM(require("path"), 1);
|
|
16055
|
+
var import_promises52 = require("fs/promises");
|
|
15596
16056
|
var import_fast_glob9 = __toESM(require("fast-glob"), 1);
|
|
15597
16057
|
var RENDERED_KEYWORDS_RE = /\b(rendered|screenshot|html\b|preview|visual\s*review)/i;
|
|
15598
16058
|
var DDP_REFERENCE_RE = /\b(ddp|design\s*direction\s*pack)\b/i;
|
|
@@ -15624,6 +16084,7 @@ async function validateRenderCritique(root, config) {
|
|
|
15624
16084
|
if (!hasDdp) return issues;
|
|
15625
16085
|
const skillsDir = import_node_path54.default.join(root, config.paths.skillsDir).replace(/\\/g, "/");
|
|
15626
16086
|
const evidenceDir = import_node_path54.default.join(root, ".qfai", "evidence").replace(/\\/g, "/");
|
|
16087
|
+
const renderEvidenceViewports = await collectRenderEvidenceViewports(root);
|
|
15627
16088
|
const skillPromptPattern = import_node_path54.default.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
|
|
15628
16089
|
const skillFiles = await (0, import_fast_glob9.default)(skillPromptPattern, { dot: true });
|
|
15629
16090
|
const evidencePattern = import_node_path54.default.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
|
|
@@ -15663,7 +16124,7 @@ async function validateRenderCritique(root, config) {
|
|
|
15663
16124
|
}
|
|
15664
16125
|
}
|
|
15665
16126
|
const allEvidenceContent = await collectContent(evidenceFiles);
|
|
15666
|
-
if (evidenceFiles.length > 0 && !DESKTOP_RE.test(allEvidenceContent)) {
|
|
16127
|
+
if (evidenceFiles.length > 0 && !DESKTOP_RE.test(allEvidenceContent) && !renderEvidenceViewports.has("desktop")) {
|
|
15667
16128
|
issues.push(
|
|
15668
16129
|
issue(
|
|
15669
16130
|
"QFAI-CRIT-003",
|
|
@@ -15677,7 +16138,7 @@ async function validateRenderCritique(root, config) {
|
|
|
15677
16138
|
)
|
|
15678
16139
|
);
|
|
15679
16140
|
}
|
|
15680
|
-
if (evidenceFiles.length > 0 && !MOBILE_RE.test(allEvidenceContent)) {
|
|
16141
|
+
if (evidenceFiles.length > 0 && !MOBILE_RE.test(allEvidenceContent) && !renderEvidenceViewports.has("mobile")) {
|
|
15681
16142
|
issues.push(
|
|
15682
16143
|
issue(
|
|
15683
16144
|
"QFAI-CRIT-004",
|
|
@@ -15838,9 +16299,49 @@ async function collectContent(files) {
|
|
|
15838
16299
|
}
|
|
15839
16300
|
return contents.join("\n---\n");
|
|
15840
16301
|
}
|
|
16302
|
+
async function collectRenderEvidenceViewports(root) {
|
|
16303
|
+
const prototypingJsonPath = import_node_path54.default.join(root, ".qfai", "evidence", "prototyping.json");
|
|
16304
|
+
try {
|
|
16305
|
+
const raw = await (0, import_promises52.readFile)(prototypingJsonPath, "utf-8");
|
|
16306
|
+
const parsed = JSON.parse(raw);
|
|
16307
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16308
|
+
return /* @__PURE__ */ new Set();
|
|
16309
|
+
}
|
|
16310
|
+
const uiFidelity = parsed.uiFidelity;
|
|
16311
|
+
if (!uiFidelity || typeof uiFidelity !== "object" || Array.isArray(uiFidelity)) {
|
|
16312
|
+
return /* @__PURE__ */ new Set();
|
|
16313
|
+
}
|
|
16314
|
+
const screens = uiFidelity.screens;
|
|
16315
|
+
if (!Array.isArray(screens)) {
|
|
16316
|
+
return /* @__PURE__ */ new Set();
|
|
16317
|
+
}
|
|
16318
|
+
const viewports = /* @__PURE__ */ new Set();
|
|
16319
|
+
for (const screen of screens) {
|
|
16320
|
+
if (!screen || typeof screen !== "object" || Array.isArray(screen)) {
|
|
16321
|
+
continue;
|
|
16322
|
+
}
|
|
16323
|
+
const renders = screen.renders;
|
|
16324
|
+
if (!Array.isArray(renders)) {
|
|
16325
|
+
continue;
|
|
16326
|
+
}
|
|
16327
|
+
for (const render of renders) {
|
|
16328
|
+
if (!render || typeof render !== "object" || Array.isArray(render)) {
|
|
16329
|
+
continue;
|
|
16330
|
+
}
|
|
16331
|
+
const viewport = render.viewport;
|
|
16332
|
+
if (typeof viewport === "string" && viewport.trim().length > 0) {
|
|
16333
|
+
viewports.add(viewport.trim().toLowerCase());
|
|
16334
|
+
}
|
|
16335
|
+
}
|
|
16336
|
+
}
|
|
16337
|
+
return viewports;
|
|
16338
|
+
} catch {
|
|
16339
|
+
return /* @__PURE__ */ new Set();
|
|
16340
|
+
}
|
|
16341
|
+
}
|
|
15841
16342
|
|
|
15842
16343
|
// src/core/validators/designFidelity.ts
|
|
15843
|
-
var
|
|
16344
|
+
var import_promises53 = require("fs/promises");
|
|
15844
16345
|
var import_node_path55 = __toESM(require("path"), 1);
|
|
15845
16346
|
var import_fast_glob10 = __toESM(require("fast-glob"), 1);
|
|
15846
16347
|
var SCORECARD_HEADING_RE = /^#{1,3}\s+Fidelity\s+Scorecard/im;
|
|
@@ -15873,7 +16374,7 @@ async function validateDesignFidelity(root, config) {
|
|
|
15873
16374
|
for (const filePath of allFiles) {
|
|
15874
16375
|
let content;
|
|
15875
16376
|
try {
|
|
15876
|
-
content = await (0,
|
|
16377
|
+
content = await (0, import_promises53.readFile)(filePath, "utf-8");
|
|
15877
16378
|
} catch {
|
|
15878
16379
|
continue;
|
|
15879
16380
|
}
|
|
@@ -16496,6 +16997,252 @@ async function validateDiscussionDesignHardening(root, config) {
|
|
|
16496
16997
|
return issues;
|
|
16497
16998
|
}
|
|
16498
16999
|
|
|
17000
|
+
// src/core/validators/designAudit.ts
|
|
17001
|
+
var import_promises54 = require("fs/promises");
|
|
17002
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
17003
|
+
var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
|
|
17004
|
+
function resolveAuditConfig(config) {
|
|
17005
|
+
const audit = config.uiux?.audit;
|
|
17006
|
+
const profile = config.uiux?.qualityProfile ?? "default";
|
|
17007
|
+
return {
|
|
17008
|
+
enabled: audit?.enabled ?? true,
|
|
17009
|
+
slopDetection: audit?.slopDetection ?? true,
|
|
17010
|
+
qualityProfile: profile,
|
|
17011
|
+
maxPrimaryCtas: audit?.maxPrimaryCtas ?? 1,
|
|
17012
|
+
maxRawTokenLiteralWarnings: audit?.maxRawTokenLiteralWarnings ?? 5,
|
|
17013
|
+
maxDuplicateFindingsPerRule: audit?.maxDuplicateFindingsPerRule ?? 5
|
|
17014
|
+
};
|
|
17015
|
+
}
|
|
17016
|
+
function mapSeverity(tier, profile, category) {
|
|
17017
|
+
if (tier === 1) return "error";
|
|
17018
|
+
if (tier === 2) return profile === "strict" ? "error" : "warning";
|
|
17019
|
+
if (profile === "default") {
|
|
17020
|
+
return category && COSMETIC_CATEGORIES.includes(category) ? "info" : "warning";
|
|
17021
|
+
}
|
|
17022
|
+
return "warning";
|
|
17023
|
+
}
|
|
17024
|
+
function findingToIssue(finding, profile, rulePrefix = "audit") {
|
|
17025
|
+
const severity = mapSeverity(finding.severityTier, profile, finding.dimension);
|
|
17026
|
+
return issue(
|
|
17027
|
+
finding.ruleId,
|
|
17028
|
+
finding.message,
|
|
17029
|
+
severity,
|
|
17030
|
+
finding.file,
|
|
17031
|
+
`${rulePrefix}.${finding.dimension}`,
|
|
17032
|
+
finding.evidence.length > 0 ? finding.evidence : void 0,
|
|
17033
|
+
"compatibility",
|
|
17034
|
+
finding.guidance
|
|
17035
|
+
);
|
|
17036
|
+
}
|
|
17037
|
+
function extractSection2(content, heading) {
|
|
17038
|
+
const idx = content.indexOf(heading);
|
|
17039
|
+
if (idx === -1) return null;
|
|
17040
|
+
const start = idx + heading.length;
|
|
17041
|
+
const headingLevel = heading.match(/^#+/)?.[0]?.length ?? 3;
|
|
17042
|
+
const rest = content.slice(start);
|
|
17043
|
+
const headingPattern = new RegExp(`^#{1,${headingLevel}} `, "m");
|
|
17044
|
+
const nextHeadingMatch = headingPattern.exec(rest);
|
|
17045
|
+
const sectionContent = nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest;
|
|
17046
|
+
return sectionContent.trim() || null;
|
|
17047
|
+
}
|
|
17048
|
+
function checkCtaHierarchy(content, auditConfig, file) {
|
|
17049
|
+
const findings = [];
|
|
17050
|
+
const ctaSection = extractSection2(content, "### CTA Hierarchy");
|
|
17051
|
+
if (!ctaSection) return findings;
|
|
17052
|
+
const primaryLines = ctaSection.match(/^-\s*Primary:/gm) || [];
|
|
17053
|
+
const primaryCount = primaryLines.length;
|
|
17054
|
+
if (primaryCount === 0) {
|
|
17055
|
+
findings.push({
|
|
17056
|
+
ruleId: "QFAI-AUD-001",
|
|
17057
|
+
dimension: "visualHierarchy",
|
|
17058
|
+
severityTier: 1,
|
|
17059
|
+
message: "No primary CTA defined in CTA Hierarchy",
|
|
17060
|
+
why: "Every UI screen needs a clear primary action to guide users",
|
|
17061
|
+
evidence: [],
|
|
17062
|
+
guidance: "Define at least one primary CTA in the CTA Hierarchy section",
|
|
17063
|
+
file
|
|
17064
|
+
});
|
|
17065
|
+
}
|
|
17066
|
+
if (primaryCount > auditConfig.maxPrimaryCtas) {
|
|
17067
|
+
findings.push({
|
|
17068
|
+
ruleId: "QFAI-AUD-020",
|
|
17069
|
+
dimension: "visualHierarchy",
|
|
17070
|
+
severityTier: 2,
|
|
17071
|
+
message: `Multiple primary CTAs detected (${primaryCount} > ${auditConfig.maxPrimaryCtas})`,
|
|
17072
|
+
why: "Multiple primary CTAs create decision paralysis and weaken visual hierarchy",
|
|
17073
|
+
evidence: primaryLines.map((l) => l.trim()),
|
|
17074
|
+
guidance: "Reduce to a single primary CTA per screen; demote others to secondary",
|
|
17075
|
+
file
|
|
17076
|
+
});
|
|
17077
|
+
}
|
|
17078
|
+
return findings;
|
|
17079
|
+
}
|
|
17080
|
+
var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)/g;
|
|
17081
|
+
async function checkTokenDrift(root, auditConfig, cfg) {
|
|
17082
|
+
const findings = [];
|
|
17083
|
+
const configuredDir = cfg.uiux?.designTokensDir;
|
|
17084
|
+
const tokensDir = configuredDir ? import_node_path57.default.resolve(root, configuredDir) : import_node_path57.default.join(root, cfg.paths.contractsDir, "design");
|
|
17085
|
+
let hasTokenFiles = false;
|
|
17086
|
+
try {
|
|
17087
|
+
const entries = await (0, import_promises54.readdir)(tokensDir);
|
|
17088
|
+
hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
|
|
17089
|
+
} catch {
|
|
17090
|
+
return findings;
|
|
17091
|
+
}
|
|
17092
|
+
if (!hasTokenFiles) return findings;
|
|
17093
|
+
const contractsUiDir = import_node_path57.default.join(root, cfg.paths.contractsDir, "ui");
|
|
17094
|
+
let htmlFiles = [];
|
|
17095
|
+
try {
|
|
17096
|
+
const entries = await (0, import_promises54.readdir)(contractsUiDir);
|
|
17097
|
+
htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
|
|
17098
|
+
} catch {
|
|
17099
|
+
return findings;
|
|
17100
|
+
}
|
|
17101
|
+
let rawCount = 0;
|
|
17102
|
+
const sampleLiterals = [];
|
|
17103
|
+
for (const htmlFile of htmlFiles) {
|
|
17104
|
+
const content = await readSafe(import_node_path57.default.join(contractsUiDir, htmlFile));
|
|
17105
|
+
if (!content) continue;
|
|
17106
|
+
const matches = content.match(RAW_COLOR_RE);
|
|
17107
|
+
if (matches) {
|
|
17108
|
+
rawCount += matches.length;
|
|
17109
|
+
for (const m of matches) {
|
|
17110
|
+
if (sampleLiterals.length < 10) {
|
|
17111
|
+
sampleLiterals.push(m.toLowerCase());
|
|
17112
|
+
}
|
|
17113
|
+
}
|
|
17114
|
+
}
|
|
17115
|
+
}
|
|
17116
|
+
if (rawCount > auditConfig.maxRawTokenLiteralWarnings) {
|
|
17117
|
+
findings.push({
|
|
17118
|
+
ruleId: "QFAI-AUD-004",
|
|
17119
|
+
dimension: "tokenDiscipline",
|
|
17120
|
+
severityTier: 1,
|
|
17121
|
+
message: `Token drift: ${rawCount} raw color literal occurrences found (threshold: ${auditConfig.maxRawTokenLiteralWarnings})`,
|
|
17122
|
+
why: "Raw color values bypass design tokens, causing visual inconsistency",
|
|
17123
|
+
evidence: sampleLiterals,
|
|
17124
|
+
guidance: "Replace raw color literals with design token references"
|
|
17125
|
+
});
|
|
17126
|
+
}
|
|
17127
|
+
return findings;
|
|
17128
|
+
}
|
|
17129
|
+
function deduplicateFindings(issues, maxPerRule) {
|
|
17130
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17131
|
+
const result = [];
|
|
17132
|
+
for (const iss of issues) {
|
|
17133
|
+
const count = counts.get(iss.code) ?? 0;
|
|
17134
|
+
if (count < maxPerRule) {
|
|
17135
|
+
result.push(iss);
|
|
17136
|
+
}
|
|
17137
|
+
counts.set(iss.code, count + 1);
|
|
17138
|
+
}
|
|
17139
|
+
for (const [code, count] of counts) {
|
|
17140
|
+
if (count > maxPerRule) {
|
|
17141
|
+
result.push({
|
|
17142
|
+
code,
|
|
17143
|
+
severity: "info",
|
|
17144
|
+
category: "compatibility",
|
|
17145
|
+
message: `${count - maxPerRule} additional "${code}" finding(s) suppressed (max ${maxPerRule} per rule)`,
|
|
17146
|
+
rule: `audit.dedup.${code}`
|
|
17147
|
+
});
|
|
17148
|
+
}
|
|
17149
|
+
}
|
|
17150
|
+
return result;
|
|
17151
|
+
}
|
|
17152
|
+
async function validateDesignAudit(root, config) {
|
|
17153
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17154
|
+
if (!auditConfig.enabled) return [];
|
|
17155
|
+
const discussionDir = import_node_path57.default.join(root, config.paths.discussionDir);
|
|
17156
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17157
|
+
if (!packRoot) return [];
|
|
17158
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17159
|
+
if (!uiBearing) return [];
|
|
17160
|
+
const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
|
|
17161
|
+
const content = await readSafe(storyPath);
|
|
17162
|
+
if (!content) return [];
|
|
17163
|
+
const findings = [];
|
|
17164
|
+
findings.push(...checkCtaHierarchy(content, auditConfig, "03_Story-Workshop.md"));
|
|
17165
|
+
findings.push(...await checkTokenDrift(root, auditConfig, config));
|
|
17166
|
+
const issues = findings.map((f) => findingToIssue(f, auditConfig.qualityProfile));
|
|
17167
|
+
return deduplicateFindings(issues, auditConfig.maxDuplicateFindingsPerRule);
|
|
17168
|
+
}
|
|
17169
|
+
|
|
17170
|
+
// src/core/validators/designSlop.ts
|
|
17171
|
+
var import_node_fs2 = require("fs");
|
|
17172
|
+
var import_promises55 = require("fs/promises");
|
|
17173
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
17174
|
+
var import_node_url4 = require("url");
|
|
17175
|
+
function isValidSlopPattern(rule) {
|
|
17176
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
17177
|
+
const r = rule;
|
|
17178
|
+
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";
|
|
17179
|
+
}
|
|
17180
|
+
async function loadSlopPatterns(jsonPath) {
|
|
17181
|
+
const raw = await (0, import_promises55.readFile)(jsonPath, "utf-8");
|
|
17182
|
+
const parsed = JSON.parse(raw);
|
|
17183
|
+
if (!Array.isArray(parsed)) return [];
|
|
17184
|
+
return parsed.filter((r) => isValidSlopPattern(r));
|
|
17185
|
+
}
|
|
17186
|
+
function defaultPatternsPath() {
|
|
17187
|
+
const base = __filename;
|
|
17188
|
+
const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
|
|
17189
|
+
const baseDir = import_node_path58.default.dirname(basePath);
|
|
17190
|
+
const candidates = [
|
|
17191
|
+
import_node_path58.default.join(baseDir, "designSlopPatterns.json"),
|
|
17192
|
+
import_node_path58.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
|
|
17193
|
+
import_node_path58.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
|
|
17194
|
+
];
|
|
17195
|
+
for (const c of candidates) {
|
|
17196
|
+
if ((0, import_node_fs2.existsSync)(c)) return c;
|
|
17197
|
+
}
|
|
17198
|
+
return candidates[0];
|
|
17199
|
+
}
|
|
17200
|
+
async function validateDesignSlop(root, config) {
|
|
17201
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17202
|
+
if (!auditConfig.enabled) return [];
|
|
17203
|
+
if (!auditConfig.slopDetection) return [];
|
|
17204
|
+
const discussionDir = import_node_path58.default.join(root, config.paths.discussionDir);
|
|
17205
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17206
|
+
if (!packRoot) return [];
|
|
17207
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17208
|
+
if (!uiBearing) return [];
|
|
17209
|
+
let patterns;
|
|
17210
|
+
try {
|
|
17211
|
+
patterns = await loadSlopPatterns(defaultPatternsPath());
|
|
17212
|
+
} catch {
|
|
17213
|
+
return [];
|
|
17214
|
+
}
|
|
17215
|
+
const findings = [];
|
|
17216
|
+
const seenRules = /* @__PURE__ */ new Set();
|
|
17217
|
+
for (const pattern of patterns) {
|
|
17218
|
+
let regex;
|
|
17219
|
+
try {
|
|
17220
|
+
regex = new RegExp(pattern.match, "gi");
|
|
17221
|
+
} catch {
|
|
17222
|
+
continue;
|
|
17223
|
+
}
|
|
17224
|
+
for (const scope of pattern.scopes) {
|
|
17225
|
+
const filePath = import_node_path58.default.join(packRoot, scope);
|
|
17226
|
+
const content = await readSafe(filePath);
|
|
17227
|
+
if (!content) continue;
|
|
17228
|
+
if (regex.test(content) && !seenRules.has(pattern.id)) {
|
|
17229
|
+
seenRules.add(pattern.id);
|
|
17230
|
+
findings.push({
|
|
17231
|
+
ruleId: pattern.id,
|
|
17232
|
+
dimension: pattern.category,
|
|
17233
|
+
severityTier: pattern.tier,
|
|
17234
|
+
message: pattern.message,
|
|
17235
|
+
why: `Slop pattern "${pattern.id}" matched in ${scope}`,
|
|
17236
|
+
evidence: [],
|
|
17237
|
+
guidance: pattern.guidance,
|
|
17238
|
+
file: scope
|
|
17239
|
+
});
|
|
17240
|
+
}
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17243
|
+
return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
|
|
17244
|
+
}
|
|
17245
|
+
|
|
16499
17246
|
// src/core/validate.ts
|
|
16500
17247
|
var UIUX_VALIDATION_BUDGET_MS = 2e3;
|
|
16501
17248
|
async function validateProject(root, configResult, options = {}) {
|
|
@@ -16513,7 +17260,9 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
16513
17260
|
() => validateBpApDb(root, config),
|
|
16514
17261
|
() => validateUiDefinitionConsistency(root, config),
|
|
16515
17262
|
() => validateResearchSummary(root, config),
|
|
16516
|
-
() => validateAgentDefinition(root, config)
|
|
17263
|
+
() => validateAgentDefinition(root, config),
|
|
17264
|
+
() => validateDesignAudit(root, config),
|
|
17265
|
+
() => validateDesignSlop(root, config)
|
|
16517
17266
|
];
|
|
16518
17267
|
const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
|
|
16519
17268
|
const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
|
|
@@ -16600,15 +17349,15 @@ var REPORT_GUARDRAILS_MAX = 20;
|
|
|
16600
17349
|
var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
|
|
16601
17350
|
var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
|
|
16602
17351
|
async function createReportData(root, validation, configResult) {
|
|
16603
|
-
const resolvedRoot =
|
|
17352
|
+
const resolvedRoot = import_node_path59.default.resolve(root);
|
|
16604
17353
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
16605
17354
|
const config = resolved.config;
|
|
16606
17355
|
const configPath = resolved.configPath;
|
|
16607
17356
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
16608
17357
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
16609
|
-
const apiRoot =
|
|
16610
|
-
const uiRoot =
|
|
16611
|
-
const dbRoot =
|
|
17358
|
+
const apiRoot = import_node_path59.default.join(contractsRoot, "api");
|
|
17359
|
+
const uiRoot = import_node_path59.default.join(contractsRoot, "ui");
|
|
17360
|
+
const dbRoot = import_node_path59.default.join(contractsRoot, "db");
|
|
16612
17361
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
16613
17362
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
16614
17363
|
const specEntries = await collectSpecEntries(specsRoot);
|
|
@@ -16936,6 +17685,8 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
16936
17685
|
lines.push("");
|
|
16937
17686
|
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
16938
17687
|
lines.push("- [Change Issues](#change-issues)");
|
|
17688
|
+
lines.push("- [Design Audit Findings](#design-audit-findings)");
|
|
17689
|
+
lines.push("- [Slop Guardrails Findings](#slop-guardrails-findings)");
|
|
16939
17690
|
lines.push("- [Change Type](#change-type)");
|
|
16940
17691
|
lines.push("- [Waivers](#waivers)");
|
|
16941
17692
|
lines.push("- [Decision Guardrails](#decision-guardrails)");
|
|
@@ -17084,6 +17835,46 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17084
17835
|
lines.push("### Issues");
|
|
17085
17836
|
lines.push("");
|
|
17086
17837
|
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
17838
|
+
const auditIssues = data.issues.filter((i) => /^QFAI-AUD-/.test(i.code));
|
|
17839
|
+
if (auditIssues.length > 0) {
|
|
17840
|
+
lines.push("## Design Audit Findings");
|
|
17841
|
+
lines.push("");
|
|
17842
|
+
const byDimension = /* @__PURE__ */ new Map();
|
|
17843
|
+
for (const iss of auditIssues) {
|
|
17844
|
+
const dim = iss.rule?.replace(/^audit\./, "").split(".")[0] ?? "unknown";
|
|
17845
|
+
const group = byDimension.get(dim) ?? [];
|
|
17846
|
+
group.push(iss);
|
|
17847
|
+
byDimension.set(dim, group);
|
|
17848
|
+
}
|
|
17849
|
+
for (const [dim, dimIssues] of byDimension) {
|
|
17850
|
+
lines.push(`### ${dim}`);
|
|
17851
|
+
lines.push("");
|
|
17852
|
+
for (const iss of dimIssues) {
|
|
17853
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17854
|
+
}
|
|
17855
|
+
lines.push("");
|
|
17856
|
+
}
|
|
17857
|
+
}
|
|
17858
|
+
const slopIssues = data.issues.filter((i) => /^SLP-/.test(i.code));
|
|
17859
|
+
if (slopIssues.length > 0) {
|
|
17860
|
+
lines.push("## Slop Guardrails Findings");
|
|
17861
|
+
lines.push("");
|
|
17862
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
17863
|
+
for (const iss of slopIssues) {
|
|
17864
|
+
const cat = iss.rule?.replace(/^slop\./, "").split(".")[0] ?? "unknown";
|
|
17865
|
+
const group = byCategory.get(cat) ?? [];
|
|
17866
|
+
group.push(iss);
|
|
17867
|
+
byCategory.set(cat, group);
|
|
17868
|
+
}
|
|
17869
|
+
for (const [cat, catIssues] of byCategory) {
|
|
17870
|
+
lines.push(`### ${cat}`);
|
|
17871
|
+
lines.push("");
|
|
17872
|
+
for (const iss of catIssues) {
|
|
17873
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17874
|
+
}
|
|
17875
|
+
lines.push("");
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17087
17878
|
lines.push("## Change Type");
|
|
17088
17879
|
lines.push("");
|
|
17089
17880
|
lines.push("### Summary");
|
|
@@ -17455,6 +18246,20 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17455
18246
|
} else {
|
|
17456
18247
|
lines.push("- issue \u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
17457
18248
|
}
|
|
18249
|
+
const renderEvidenceIssues = data.issues.filter(
|
|
18250
|
+
(item) => ["QFAI-PROT-101", "QFAI-PROT-244", "QFAI-PROT-245"].includes(item.code)
|
|
18251
|
+
);
|
|
18252
|
+
if (renderEvidenceIssues.length > 0) {
|
|
18253
|
+
lines.push(
|
|
18254
|
+
"- render evidence \u304C\u4E0D\u8DB3\u307E\u305F\u306F\u4E0D\u5B8C\u5168\u3067\u3059\u3002viewport coverage \u3068 artifact path \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
18255
|
+
);
|
|
18256
|
+
lines.push(
|
|
18257
|
+
"- recover: `qfai prototyping --autogen-ui-fidelity --render-evidence --viewports desktop,mobile` \u3092\u5B9F\u884C\u3057\u3001`.qfai/evidence/prototyping.json` \u3068 render bundle \u3092\u66F4\u65B0\u3057\u307E\u3059\u3002"
|
|
18258
|
+
);
|
|
18259
|
+
lines.push(
|
|
18260
|
+
"- why it matters: render evidence \u306F viewport coverage \u3068 missing artifact \u306E\u5207\u308A\u5206\u3051\u306B\u4F7F\u308F\u308C\u3001strict/high profile \u3067\u306F gate \u306B\u5F71\u97FF\u3057\u307E\u3059\u3002"
|
|
18261
|
+
);
|
|
18262
|
+
}
|
|
17458
18263
|
lines.push("- \u5909\u66F4\u5185\u5BB9\u30FB\u53D7\u5165\u89B3\u70B9\u306F `.qfai/specs/*/18_delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002");
|
|
17459
18264
|
lines.push("- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/assistant/instructions/constitution.md`");
|
|
17460
18265
|
return lines.join("\n");
|
|
@@ -17489,7 +18294,7 @@ async function collectChangeTypeSummary(specsRoot) {
|
|
|
17489
18294
|
};
|
|
17490
18295
|
const deltaFiles = await collectDeltaFiles(specsRoot);
|
|
17491
18296
|
for (const deltaFile of deltaFiles) {
|
|
17492
|
-
const text = await (0,
|
|
18297
|
+
const text = await (0, import_promises56.readFile)(deltaFile, "utf-8");
|
|
17493
18298
|
const parsed = parseDeltaV1(text);
|
|
17494
18299
|
for (const entry of parsed.entries) {
|
|
17495
18300
|
if (!entry.meta) {
|
|
@@ -17526,7 +18331,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
17526
18331
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
17527
18332
|
}
|
|
17528
18333
|
for (const file of specFiles) {
|
|
17529
|
-
const text = await (0,
|
|
18334
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17530
18335
|
const parsed = parseSpec(text, file);
|
|
17531
18336
|
const specKey = parsed.specId;
|
|
17532
18337
|
if (!specKey) {
|
|
@@ -17563,7 +18368,7 @@ async function collectIds(files) {
|
|
|
17563
18368
|
result[prefix] = /* @__PURE__ */ new Set();
|
|
17564
18369
|
}
|
|
17565
18370
|
for (const file of files) {
|
|
17566
|
-
const text = await (0,
|
|
18371
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17567
18372
|
for (const prefix of ID_PREFIXES) {
|
|
17568
18373
|
const ids = extractIds(text, prefix);
|
|
17569
18374
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -17578,7 +18383,7 @@ async function collectIds(files) {
|
|
|
17578
18383
|
async function collectUpstreamIds(files) {
|
|
17579
18384
|
const ids = /* @__PURE__ */ new Set();
|
|
17580
18385
|
for (const file of files) {
|
|
17581
|
-
const text = await (0,
|
|
18386
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17582
18387
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
17583
18388
|
}
|
|
17584
18389
|
return ids;
|
|
@@ -17599,7 +18404,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
17599
18404
|
}
|
|
17600
18405
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
17601
18406
|
for (const file of targetFiles) {
|
|
17602
|
-
const text = await (0,
|
|
18407
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17603
18408
|
if (pattern.test(text)) {
|
|
17604
18409
|
return true;
|
|
17605
18410
|
}
|
|
@@ -17718,7 +18523,7 @@ function normalizeScSources(root, sources) {
|
|
|
17718
18523
|
async function countScenarios(scenarioFiles) {
|
|
17719
18524
|
let total = 0;
|
|
17720
18525
|
for (const file of scenarioFiles) {
|
|
17721
|
-
const text = await (0,
|
|
18526
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17722
18527
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
17723
18528
|
if (!document || errors.length > 0) {
|
|
17724
18529
|
continue;
|
|
@@ -17749,7 +18554,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
|
|
|
17749
18554
|
let totalScenarios = 0;
|
|
17750
18555
|
let e2eCount = 0;
|
|
17751
18556
|
for (const file of scenarioFiles) {
|
|
17752
|
-
const text = await (0,
|
|
18557
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17753
18558
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
17754
18559
|
if (!document || errors.length > 0) {
|
|
17755
18560
|
continue;
|
|
@@ -17837,10 +18642,10 @@ function buildHotspots(issues) {
|
|
|
17837
18642
|
async function collectTddCoverage(entries) {
|
|
17838
18643
|
const specs = [];
|
|
17839
18644
|
for (const entry of entries) {
|
|
17840
|
-
const testCasesPath =
|
|
18645
|
+
const testCasesPath = import_node_path59.default.join(entry.dir, "06_Test-Cases.md");
|
|
17841
18646
|
let tcContent;
|
|
17842
18647
|
try {
|
|
17843
|
-
tcContent = await (0,
|
|
18648
|
+
tcContent = await (0, import_promises56.readFile)(testCasesPath, "utf-8");
|
|
17844
18649
|
} catch {
|
|
17845
18650
|
continue;
|
|
17846
18651
|
}
|
|
@@ -17872,10 +18677,10 @@ async function collectTddCoverage(entries) {
|
|
|
17872
18677
|
});
|
|
17873
18678
|
continue;
|
|
17874
18679
|
}
|
|
17875
|
-
const tddListPath =
|
|
18680
|
+
const tddListPath = import_node_path59.default.join(entry.dir, "tdd", "test-list.md");
|
|
17876
18681
|
let tddContent;
|
|
17877
18682
|
try {
|
|
17878
|
-
tddContent = await (0,
|
|
18683
|
+
tddContent = await (0, import_promises56.readFile)(tddListPath, "utf-8");
|
|
17879
18684
|
} catch {
|
|
17880
18685
|
specs.push({
|
|
17881
18686
|
specNumber: entry.specNumber,
|
|
@@ -17958,8 +18763,8 @@ async function collectTddCoverage(entries) {
|
|
|
17958
18763
|
}
|
|
17959
18764
|
|
|
17960
18765
|
// src/core/specPackReport.ts
|
|
17961
|
-
var
|
|
17962
|
-
var
|
|
18766
|
+
var import_promises57 = require("fs/promises");
|
|
18767
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
17963
18768
|
var REQUIRED_LEDGER_COLUMNS = [
|
|
17964
18769
|
"trace_id",
|
|
17965
18770
|
"obj_id",
|
|
@@ -17977,9 +18782,9 @@ async function writeSpecPackReports(root, config) {
|
|
|
17977
18782
|
const entries = await collectSpecEntries(specsRoot);
|
|
17978
18783
|
const contractIndex = await buildContractIndex(root, config);
|
|
17979
18784
|
for (const entry of entries) {
|
|
17980
|
-
const specName =
|
|
17981
|
-
const outputDir =
|
|
17982
|
-
await (0,
|
|
18785
|
+
const specName = import_node_path60.default.basename(entry.dir);
|
|
18786
|
+
const outputDir = import_node_path60.default.join(outRoot, specName);
|
|
18787
|
+
await (0, import_promises57.mkdir)(outputDir, { recursive: true });
|
|
17983
18788
|
const [acText, tcText, exText, ledgerText] = await Promise.all([
|
|
17984
18789
|
readSafe12(entry.acceptanceCriteriaPath),
|
|
17985
18790
|
readSafe12(entry.testCasesPath),
|
|
@@ -18004,14 +18809,14 @@ async function writeSpecPackReports(root, config) {
|
|
|
18004
18809
|
contractIds: contractIndex.ids
|
|
18005
18810
|
});
|
|
18006
18811
|
const graph = buildTraceabilityGraph(ledgerRows);
|
|
18007
|
-
await (0,
|
|
18008
|
-
|
|
18812
|
+
await (0, import_promises57.writeFile)(
|
|
18813
|
+
import_node_path60.default.join(outputDir, "coverage.md"),
|
|
18009
18814
|
`${formatCoverageMarkdown(specName, coverage)}
|
|
18010
18815
|
`,
|
|
18011
18816
|
"utf-8"
|
|
18012
18817
|
);
|
|
18013
|
-
await (0,
|
|
18014
|
-
|
|
18818
|
+
await (0, import_promises57.writeFile)(
|
|
18819
|
+
import_node_path60.default.join(outputDir, "traceability-graph.json"),
|
|
18015
18820
|
`${JSON.stringify(graph, null, 2)}
|
|
18016
18821
|
`,
|
|
18017
18822
|
"utf-8"
|
|
@@ -18186,7 +18991,7 @@ function getCell(row, indexByColumn, column) {
|
|
|
18186
18991
|
}
|
|
18187
18992
|
async function readSafe12(filePath) {
|
|
18188
18993
|
try {
|
|
18189
|
-
return await (0,
|
|
18994
|
+
return await (0, import_promises57.readFile)(filePath, "utf-8");
|
|
18190
18995
|
} catch {
|
|
18191
18996
|
return "";
|
|
18192
18997
|
}
|
|
@@ -18204,7 +19009,7 @@ function warnIfTruncated(scan, context) {
|
|
|
18204
19009
|
|
|
18205
19010
|
// src/cli/commands/report.ts
|
|
18206
19011
|
async function runReport(options) {
|
|
18207
|
-
const root =
|
|
19012
|
+
const root = import_node_path61.default.resolve(options.root);
|
|
18208
19013
|
const configResult = await loadConfig(root);
|
|
18209
19014
|
let validation;
|
|
18210
19015
|
let blockedByPhaseGuard = false;
|
|
@@ -18220,7 +19025,7 @@ async function runReport(options) {
|
|
|
18220
19025
|
validation = normalized;
|
|
18221
19026
|
} else {
|
|
18222
19027
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
18223
|
-
const inputPath =
|
|
19028
|
+
const inputPath = import_node_path61.default.isAbsolute(input) ? input : import_node_path61.default.resolve(root, input);
|
|
18224
19029
|
try {
|
|
18225
19030
|
validation = await readValidationResult(inputPath);
|
|
18226
19031
|
} catch (err) {
|
|
@@ -18247,11 +19052,11 @@ async function runReport(options) {
|
|
|
18247
19052
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
18248
19053
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
18249
19054
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
18250
|
-
const defaultOut = options.format === "json" ?
|
|
19055
|
+
const defaultOut = options.format === "json" ? import_node_path61.default.join(outRoot, "report.json") : import_node_path61.default.join(outRoot, "report.md");
|
|
18251
19056
|
const out = options.outPath ?? defaultOut;
|
|
18252
|
-
const outPath =
|
|
18253
|
-
await (0,
|
|
18254
|
-
await (0,
|
|
19057
|
+
const outPath = import_node_path61.default.isAbsolute(out) ? out : import_node_path61.default.resolve(root, out);
|
|
19058
|
+
await (0, import_promises58.mkdir)(import_node_path61.default.dirname(outPath), { recursive: true });
|
|
19059
|
+
await (0, import_promises58.writeFile)(outPath, `${output}
|
|
18255
19060
|
`, "utf-8");
|
|
18256
19061
|
await writeSpecPackReports(root, configResult.config);
|
|
18257
19062
|
if (blockedByPhaseGuard) {
|
|
@@ -18266,7 +19071,7 @@ async function runReport(options) {
|
|
|
18266
19071
|
info(`wrote report: ${outPath}`);
|
|
18267
19072
|
}
|
|
18268
19073
|
async function readValidationResult(inputPath) {
|
|
18269
|
-
const raw = await (0,
|
|
19074
|
+
const raw = await (0, import_promises58.readFile)(inputPath, "utf-8");
|
|
18270
19075
|
const parsed = JSON.parse(raw);
|
|
18271
19076
|
if (!isValidationResult(parsed)) {
|
|
18272
19077
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -18326,23 +19131,23 @@ function isMissingFileError2(error2) {
|
|
|
18326
19131
|
return record2.code === "ENOENT";
|
|
18327
19132
|
}
|
|
18328
19133
|
async function writeValidationResult(root, outputPath, result) {
|
|
18329
|
-
const abs =
|
|
18330
|
-
await (0,
|
|
18331
|
-
await (0,
|
|
19134
|
+
const abs = import_node_path61.default.isAbsolute(outputPath) ? outputPath : import_node_path61.default.resolve(root, outputPath);
|
|
19135
|
+
await (0, import_promises58.mkdir)(import_node_path61.default.dirname(abs), { recursive: true });
|
|
19136
|
+
await (0, import_promises58.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
18332
19137
|
`, "utf-8");
|
|
18333
19138
|
}
|
|
18334
19139
|
|
|
18335
19140
|
// src/cli/commands/validate.ts
|
|
18336
|
-
var
|
|
18337
|
-
var
|
|
19141
|
+
var import_promises60 = require("fs/promises");
|
|
19142
|
+
var import_node_path63 = __toESM(require("path"), 1);
|
|
18338
19143
|
|
|
18339
19144
|
// src/core/runLog.ts
|
|
18340
|
-
var
|
|
18341
|
-
var
|
|
19145
|
+
var import_promises59 = require("fs/promises");
|
|
19146
|
+
var import_node_path62 = __toESM(require("path"), 1);
|
|
18342
19147
|
async function writeValidateRunLog(input) {
|
|
18343
|
-
const root =
|
|
19148
|
+
const root = import_node_path62.default.resolve(input.root);
|
|
18344
19149
|
const outDir = resolvePath(root, input.config, "outDir");
|
|
18345
|
-
await (0,
|
|
19150
|
+
await (0, import_promises59.mkdir)(outDir, { recursive: true });
|
|
18346
19151
|
const { runId, reportDir } = await allocateRunReportDir(outDir, input.startedAt);
|
|
18347
19152
|
const relativeSpecsRoot = toRelativePath(root, resolvePath(root, input.config, "specsDir"));
|
|
18348
19153
|
const latestDiscussion = await findLatestPack(
|
|
@@ -18387,10 +19192,10 @@ async function writeValidateRunLog(input) {
|
|
|
18387
19192
|
errors,
|
|
18388
19193
|
warnings
|
|
18389
19194
|
});
|
|
18390
|
-
await writeJson(
|
|
18391
|
-
await writeJson(
|
|
18392
|
-
await writeJson(
|
|
18393
|
-
await (0,
|
|
19195
|
+
await writeJson(import_node_path62.default.join(reportDir, "run.json"), runJson);
|
|
19196
|
+
await writeJson(import_node_path62.default.join(reportDir, "validator.json"), validatorJson);
|
|
19197
|
+
await writeJson(import_node_path62.default.join(reportDir, "traceability.json"), traceabilityJson);
|
|
19198
|
+
await (0, import_promises59.writeFile)(import_node_path62.default.join(reportDir, "summary.md"), `${summaryMd}
|
|
18394
19199
|
`, "utf-8");
|
|
18395
19200
|
return {
|
|
18396
19201
|
runId,
|
|
@@ -18398,7 +19203,7 @@ async function writeValidateRunLog(input) {
|
|
|
18398
19203
|
};
|
|
18399
19204
|
}
|
|
18400
19205
|
async function writeJson(filePath, value) {
|
|
18401
|
-
await (0,
|
|
19206
|
+
await (0, import_promises59.writeFile)(filePath, `${JSON.stringify(value, null, 2)}
|
|
18402
19207
|
`, "utf-8");
|
|
18403
19208
|
}
|
|
18404
19209
|
function resolveStatus(result, override) {
|
|
@@ -18514,9 +19319,9 @@ async function allocateRunReportDir(outDir, startedAt) {
|
|
|
18514
19319
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
18515
19320
|
const candidateDate = new Date(startedAt.getTime() + attempt);
|
|
18516
19321
|
const runId = `run-${formatTimestamp17(candidateDate)}`;
|
|
18517
|
-
const reportDir =
|
|
19322
|
+
const reportDir = import_node_path62.default.join(outDir, runId);
|
|
18518
19323
|
try {
|
|
18519
|
-
await (0,
|
|
19324
|
+
await (0, import_promises59.mkdir)(reportDir);
|
|
18520
19325
|
return { runId, reportDir };
|
|
18521
19326
|
} catch (error2) {
|
|
18522
19327
|
if (isAlreadyExistsError(error2)) {
|
|
@@ -18545,7 +19350,7 @@ function shouldFail(result, failOn) {
|
|
|
18545
19350
|
// src/cli/commands/validate.ts
|
|
18546
19351
|
async function runValidate(options) {
|
|
18547
19352
|
const startedAt = /* @__PURE__ */ new Date();
|
|
18548
|
-
const root =
|
|
19353
|
+
const root = import_node_path63.default.resolve(options.root);
|
|
18549
19354
|
const configResult = await loadConfig(root);
|
|
18550
19355
|
const blockedIssue = buildCiRefinementIssue(options.phase);
|
|
18551
19356
|
const blockedByPhaseGuard = blockedIssue !== null;
|
|
@@ -18701,12 +19506,12 @@ function issueKey(issue2) {
|
|
|
18701
19506
|
}
|
|
18702
19507
|
async function emitJson(result, root, jsonPath) {
|
|
18703
19508
|
const abs = resolveJsonPath(root, jsonPath);
|
|
18704
|
-
await (0,
|
|
18705
|
-
await (0,
|
|
19509
|
+
await (0, import_promises60.mkdir)(import_node_path63.default.dirname(abs), { recursive: true });
|
|
19510
|
+
await (0, import_promises60.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
18706
19511
|
`, "utf-8");
|
|
18707
19512
|
}
|
|
18708
19513
|
function resolveJsonPath(root, jsonPath) {
|
|
18709
|
-
return
|
|
19514
|
+
return import_node_path63.default.isAbsolute(jsonPath) ? jsonPath : import_node_path63.default.resolve(root, jsonPath);
|
|
18710
19515
|
}
|
|
18711
19516
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
18712
19517
|
var ISSUE_EXPECTED_BY_CODE = {
|
|
@@ -18825,6 +19630,8 @@ function parseArgs(argv, cwd) {
|
|
|
18825
19630
|
guardrailsPaths: [],
|
|
18826
19631
|
prototypingAutogen: false,
|
|
18827
19632
|
prototypingAutogenOnly: false,
|
|
19633
|
+
prototypingRenderEvidence: false,
|
|
19634
|
+
prototypingRenderViewports: [],
|
|
18828
19635
|
help: false,
|
|
18829
19636
|
invalidExitCode: 1
|
|
18830
19637
|
};
|
|
@@ -19036,6 +19843,37 @@ function parseArgs(argv, cwd) {
|
|
|
19036
19843
|
i += 1;
|
|
19037
19844
|
break;
|
|
19038
19845
|
}
|
|
19846
|
+
case "--render-evidence":
|
|
19847
|
+
if (command === "prototyping") {
|
|
19848
|
+
options.prototypingRenderEvidence = true;
|
|
19849
|
+
}
|
|
19850
|
+
break;
|
|
19851
|
+
case "--viewports": {
|
|
19852
|
+
if (command !== "prototyping") {
|
|
19853
|
+
break;
|
|
19854
|
+
}
|
|
19855
|
+
const next = readOptionValue(args, i);
|
|
19856
|
+
if (next === null) {
|
|
19857
|
+
markInvalid();
|
|
19858
|
+
break;
|
|
19859
|
+
}
|
|
19860
|
+
options.prototypingRenderViewports = next.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
19861
|
+
i += 1;
|
|
19862
|
+
break;
|
|
19863
|
+
}
|
|
19864
|
+
case "--render-out": {
|
|
19865
|
+
if (command !== "prototyping") {
|
|
19866
|
+
break;
|
|
19867
|
+
}
|
|
19868
|
+
const next = readOptionValue(args, i);
|
|
19869
|
+
if (next === null) {
|
|
19870
|
+
markInvalid();
|
|
19871
|
+
break;
|
|
19872
|
+
}
|
|
19873
|
+
options.prototypingRenderOut = next;
|
|
19874
|
+
i += 1;
|
|
19875
|
+
break;
|
|
19876
|
+
}
|
|
19039
19877
|
case "--platform": {
|
|
19040
19878
|
if (command !== "validate") {
|
|
19041
19879
|
break;
|
|
@@ -19188,7 +20026,10 @@ async function run(argv, cwd) {
|
|
|
19188
20026
|
autogenUiFidelity: options.prototypingAutogen,
|
|
19189
20027
|
autogenOnly: options.prototypingAutogenOnly,
|
|
19190
20028
|
...options.prototypingBaseUrl !== void 0 ? { baseUrl: options.prototypingBaseUrl } : {},
|
|
19191
|
-
...options.prototypingEvidenceOut !== void 0 ? { evidenceOut: options.prototypingEvidenceOut } : {}
|
|
20029
|
+
...options.prototypingEvidenceOut !== void 0 ? { evidenceOut: options.prototypingEvidenceOut } : {},
|
|
20030
|
+
renderEvidence: options.prototypingRenderEvidence,
|
|
20031
|
+
renderViewports: options.prototypingRenderViewports,
|
|
20032
|
+
...options.prototypingRenderOut !== void 0 ? { renderOut: options.prototypingRenderOut } : {}
|
|
19192
20033
|
});
|
|
19193
20034
|
process.exitCode = exitCode;
|
|
19194
20035
|
}
|
|
@@ -19234,6 +20075,9 @@ Options:
|
|
|
19234
20075
|
--autogen-ui-fidelity prototyping: uiFidelity \u81EA\u52D5\u751F\u6210\u3092\u6709\u52B9\u5316
|
|
19235
20076
|
--autogen-only prototyping: \u81EA\u52D5\u751F\u6210\u306E\u307F\u5B9F\u884C\uFF08\u5931\u6557\u6642exit 1\uFF09
|
|
19236
20077
|
--evidence-out <path> prototyping: \u51FA\u529B\u5148\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8 .qfai/evidence/prototyping.json\uFF09
|
|
20078
|
+
--render-evidence prototyping: render evidence \u306E\u53CE\u96C6\u3092\u6709\u52B9\u5316
|
|
20079
|
+
--viewports <list> prototyping: render \u5BFE\u8C61 viewport \u3092\u30AB\u30F3\u30DE\u533A\u5207\u308A\u3067\u6307\u5B9A
|
|
20080
|
+
--render-out <path> prototyping: render evidence \u306E\u51FA\u529B\u5148
|
|
19237
20081
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|
|
19238
20082
|
|
|
19239
20083
|
Environment:
|