qfai 1.7.6 → 1.7.7
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 +10 -4
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/20_eval_axis_usability.md +5 -0
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/21_eval_axis_consistency.md +5 -0
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/22_eval_axis_accessibility.md +5 -0
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/23_eval_axis_delight.md +23 -1
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/30_comparison.md +20 -5
- package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/40_contracts.md +36 -15
- package/assets/uix-rev/comparison-review.md +13 -0
- package/assets/uix-rev/contracts-review.md +18 -0
- package/assets/uix-rev/migration-review.md +17 -0
- package/assets/uix-rev/oq-review.md +13 -0
- package/assets/uix-rev/scoring-review.md +14 -0
- package/assets/uix-rev/strategy-review.md +22 -0
- package/dist/cli/index.cjs +1809 -1146
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +1789 -1126
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +995 -384
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -1
- package/dist/index.d.ts +107 -1
- package/dist/index.mjs +963 -357
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,8 @@ __export(src_exports, {
|
|
|
33
33
|
ID_PREFIXES: () => ID_PREFIXES,
|
|
34
34
|
RUNTIME_HEAVY_CHECKS: () => RUNTIME_HEAVY_CHECKS,
|
|
35
35
|
STATIC_OBLIGATIONS: () => STATIC_OBLIGATIONS,
|
|
36
|
+
VALID_MODE_SOURCES: () => VALID_MODE_SOURCES,
|
|
37
|
+
adaptSurfaceEvidence: () => adaptSurfaceEvidence,
|
|
36
38
|
autogenerateUiFidelity: () => autogenerateUiFidelity,
|
|
37
39
|
buildUiFidelityScreens: () => buildUiFidelityScreens,
|
|
38
40
|
checkDecisionGuardrails: () => checkDecisionGuardrails,
|
|
@@ -58,6 +60,7 @@ __export(src_exports, {
|
|
|
58
60
|
filterDecisionGuardrailsByKeyword: () => filterDecisionGuardrailsByKeyword,
|
|
59
61
|
findConfigRoot: () => findConfigRoot,
|
|
60
62
|
formatGuardrailsForLlm: () => formatGuardrailsForLlm,
|
|
63
|
+
formatModeLog: () => formatModeLog,
|
|
61
64
|
formatReportJson: () => formatReportJson,
|
|
62
65
|
formatReportMarkdown: () => formatReportMarkdown,
|
|
63
66
|
getConfigPath: () => getConfigPath,
|
|
@@ -65,9 +68,11 @@ __export(src_exports, {
|
|
|
65
68
|
loadConfig: () => loadConfig,
|
|
66
69
|
loadDecisionGuardrails: () => loadDecisionGuardrails,
|
|
67
70
|
normalizeDecisionGuardrails: () => normalizeDecisionGuardrails,
|
|
71
|
+
readDiscussionRecommendation: () => readDiscussionRecommendation,
|
|
68
72
|
resolveObligations: () => resolveObligations,
|
|
69
73
|
resolveObligationsWithOptIn: () => resolveObligationsWithOptIn,
|
|
70
74
|
resolvePath: () => resolvePath,
|
|
75
|
+
resolvePrecedence: () => resolvePrecedence,
|
|
71
76
|
resolveToolVersion: () => resolveToolVersion,
|
|
72
77
|
runMockPaths: () => runMockPaths,
|
|
73
78
|
runSddPreflight: () => runSddPreflight,
|
|
@@ -540,6 +545,15 @@ function normalizeUiux(raw, configPath, issues) {
|
|
|
540
545
|
);
|
|
541
546
|
}
|
|
542
547
|
}
|
|
548
|
+
if (raw.phase1ReleaseDate !== void 0) {
|
|
549
|
+
if (typeof raw.phase1ReleaseDate === "string" && !isNaN(new Date(raw.phase1ReleaseDate).getTime())) {
|
|
550
|
+
result.phase1ReleaseDate = raw.phase1ReleaseDate;
|
|
551
|
+
} else {
|
|
552
|
+
issues.push(
|
|
553
|
+
configIssue(configPath, "uiux.phase1ReleaseDate \u306F\u6709\u52B9\u306A\u65E5\u4ED8\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
543
557
|
if (raw.warning_as_error_override !== void 0) {
|
|
544
558
|
if (Array.isArray(raw.warning_as_error_override) && raw.warning_as_error_override.every((v) => typeof v === "string")) {
|
|
545
559
|
result.warning_as_error_override = raw.warning_as_error_override;
|
|
@@ -564,6 +578,12 @@ function normalizeUiux(raw, configPath, issues) {
|
|
|
564
578
|
result.audit = audit;
|
|
565
579
|
}
|
|
566
580
|
}
|
|
581
|
+
if (raw.migration !== void 0) {
|
|
582
|
+
const migration = normalizeUiuxMigration(raw.migration, configPath, issues);
|
|
583
|
+
if (migration) {
|
|
584
|
+
result.migration = migration;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
567
587
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
568
588
|
}
|
|
569
589
|
function normalizeUiuxAudit(raw, configPath, issues) {
|
|
@@ -623,6 +643,23 @@ function normalizeUiuxAudit(raw, configPath, issues) {
|
|
|
623
643
|
}
|
|
624
644
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
625
645
|
}
|
|
646
|
+
function normalizeUiuxMigration(raw, configPath, issues) {
|
|
647
|
+
if (!isRecord(raw)) {
|
|
648
|
+
issues.push(configIssue(configPath, "uiux.migration \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
651
|
+
const result = {};
|
|
652
|
+
if (raw.strict !== void 0) {
|
|
653
|
+
if (typeof raw.strict === "boolean") {
|
|
654
|
+
result.strict = raw.strict;
|
|
655
|
+
} else {
|
|
656
|
+
issues.push(
|
|
657
|
+
configIssue(configPath, "uiux.migration.strict \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
662
|
+
}
|
|
626
663
|
function normalizeRenderEvidence(raw, configPath, issues) {
|
|
627
664
|
if (!isRecord(raw)) {
|
|
628
665
|
issues.push(
|
|
@@ -3680,18 +3717,125 @@ function resolveObligationsWithOptIn(mode, optIn) {
|
|
|
3680
3717
|
return [.../* @__PURE__ */ new Set([...base, ...validOptIns])];
|
|
3681
3718
|
}
|
|
3682
3719
|
|
|
3720
|
+
// src/core/prototyping/discussionReader.ts
|
|
3721
|
+
var import_promises13 = require("fs/promises");
|
|
3722
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3723
|
+
var import_yaml3 = require("yaml");
|
|
3724
|
+
var VALID_MODES = /* @__PURE__ */ new Set(["low-cost", "standard", "full-harness"]);
|
|
3725
|
+
var SIDECAR_FILENAME = "prototyping.yaml";
|
|
3726
|
+
async function readDiscussionRecommendation(discussionPackDir) {
|
|
3727
|
+
const filePath = import_node_path12.default.join(discussionPackDir, SIDECAR_FILENAME);
|
|
3728
|
+
let raw;
|
|
3729
|
+
try {
|
|
3730
|
+
raw = await (0, import_promises13.readFile)(filePath, "utf-8");
|
|
3731
|
+
} catch {
|
|
3732
|
+
return { recommended_mode: null, rationale: null };
|
|
3733
|
+
}
|
|
3734
|
+
let doc;
|
|
3735
|
+
try {
|
|
3736
|
+
doc = (0, import_yaml3.parse)(raw);
|
|
3737
|
+
} catch {
|
|
3738
|
+
return { recommended_mode: null, rationale: null };
|
|
3739
|
+
}
|
|
3740
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
3741
|
+
return { recommended_mode: null, rationale: null };
|
|
3742
|
+
}
|
|
3743
|
+
const record2 = doc;
|
|
3744
|
+
const prototyping = record2.prototyping;
|
|
3745
|
+
if (!prototyping || typeof prototyping !== "object" || Array.isArray(prototyping)) {
|
|
3746
|
+
return { recommended_mode: null, rationale: null };
|
|
3747
|
+
}
|
|
3748
|
+
const section = prototyping;
|
|
3749
|
+
const rawMode = typeof section.recommended_mode === "string" ? section.recommended_mode.trim() : null;
|
|
3750
|
+
const rationale = typeof section.rationale === "string" ? section.rationale.trim() : null;
|
|
3751
|
+
const recommended_mode = rawMode && VALID_MODES.has(rawMode) ? rawMode : null;
|
|
3752
|
+
return { recommended_mode, rationale };
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
// src/core/prototyping/precedenceResolver.ts
|
|
3756
|
+
var VALID_MODES2 = /* @__PURE__ */ new Set([
|
|
3757
|
+
"default",
|
|
3758
|
+
"standard",
|
|
3759
|
+
"low-cost",
|
|
3760
|
+
"full-harness"
|
|
3761
|
+
]);
|
|
3762
|
+
function isPrototypingMode(value) {
|
|
3763
|
+
return VALID_MODES2.has(value);
|
|
3764
|
+
}
|
|
3765
|
+
var SYSTEM_DEFAULT_MODE = "standard";
|
|
3766
|
+
function resolvePrecedence(input) {
|
|
3767
|
+
const { cliMode, discussion } = input;
|
|
3768
|
+
const rawRecommended = discussion?.recommended_mode ?? null;
|
|
3769
|
+
const recommended_mode = rawRecommended && isPrototypingMode(rawRecommended) ? rawRecommended : null;
|
|
3770
|
+
if (cliMode) {
|
|
3771
|
+
return {
|
|
3772
|
+
effective_mode: cliMode,
|
|
3773
|
+
mode_source: "cli-override",
|
|
3774
|
+
recommended_mode,
|
|
3775
|
+
rationale: "CLI override"
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
if (recommended_mode) {
|
|
3779
|
+
return {
|
|
3780
|
+
effective_mode: recommended_mode,
|
|
3781
|
+
mode_source: "discussion-recommendation",
|
|
3782
|
+
recommended_mode,
|
|
3783
|
+
rationale: discussion?.rationale ?? "discussion recommendation"
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
return {
|
|
3787
|
+
effective_mode: SYSTEM_DEFAULT_MODE,
|
|
3788
|
+
mode_source: "default",
|
|
3789
|
+
recommended_mode: null,
|
|
3790
|
+
rationale: "system default"
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
// src/core/prototyping/modeLogger.ts
|
|
3795
|
+
var VALID_MODE_SOURCES = /* @__PURE__ */ new Set([
|
|
3796
|
+
"cli-override",
|
|
3797
|
+
"discussion-recommendation",
|
|
3798
|
+
"default"
|
|
3799
|
+
]);
|
|
3800
|
+
var EVIDENCE_EXPECTATIONS = {
|
|
3801
|
+
"low-cost": "L1/L2",
|
|
3802
|
+
standard: "L2/L3",
|
|
3803
|
+
"full-harness": "L3/L4/L5"
|
|
3804
|
+
};
|
|
3805
|
+
function formatModeLog(resolution) {
|
|
3806
|
+
return {
|
|
3807
|
+
mode_source: resolution.mode_source,
|
|
3808
|
+
recommended_mode: resolution.recommended_mode,
|
|
3809
|
+
effective_mode: resolution.effective_mode,
|
|
3810
|
+
rationale: resolution.rationale,
|
|
3811
|
+
evidence_expectations: EVIDENCE_EXPECTATIONS[resolution.effective_mode] ?? "L2/L3"
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
// src/core/prototyping/surfaceAdapter.ts
|
|
3816
|
+
var VISUAL_SURFACES = /* @__PURE__ */ new Set(["web", "mobile", "desktop"]);
|
|
3817
|
+
function adaptSurfaceEvidence(input) {
|
|
3818
|
+
const { surfaceType, effectiveMode } = input;
|
|
3819
|
+
const isVisual = VISUAL_SURFACES.has(surfaceType);
|
|
3820
|
+
const isLowCost = effectiveMode === "low-cost";
|
|
3821
|
+
const static_evidence = "collected";
|
|
3822
|
+
const runtime_evidence = isLowCost ? "n/a" : "collected";
|
|
3823
|
+
const visual_review = isVisual && !isLowCost ? "collected" : "n/a";
|
|
3824
|
+
return { visual_review, static_evidence, runtime_evidence };
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3683
3827
|
// src/core/report.ts
|
|
3684
|
-
var
|
|
3685
|
-
var
|
|
3828
|
+
var import_promises55 = require("fs/promises");
|
|
3829
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
3686
3830
|
|
|
3687
3831
|
// src/core/contractIndex.ts
|
|
3688
|
-
var
|
|
3689
|
-
var
|
|
3832
|
+
var import_promises14 = require("fs/promises");
|
|
3833
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3690
3834
|
async function buildContractIndex(root, config) {
|
|
3691
3835
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
3692
|
-
const uiRoot =
|
|
3693
|
-
const apiRoot =
|
|
3694
|
-
const dbRoot =
|
|
3836
|
+
const uiRoot = import_node_path13.default.join(contractsRoot, "ui");
|
|
3837
|
+
const apiRoot = import_node_path13.default.join(contractsRoot, "api");
|
|
3838
|
+
const dbRoot = import_node_path13.default.join(contractsRoot, "db");
|
|
3695
3839
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
3696
3840
|
collectUiContractFiles(uiRoot),
|
|
3697
3841
|
collectThemaContractFiles(),
|
|
@@ -3711,7 +3855,7 @@ async function buildContractIndex(root, config) {
|
|
|
3711
3855
|
}
|
|
3712
3856
|
async function indexContractFiles(files, index) {
|
|
3713
3857
|
for (const file of files) {
|
|
3714
|
-
const text = await (0,
|
|
3858
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
3715
3859
|
extractDeclaredContractIds(text).forEach((id) => {
|
|
3716
3860
|
record(index, id, file);
|
|
3717
3861
|
});
|
|
@@ -3981,15 +4125,15 @@ function resolveParentTcId(tcRef) {
|
|
|
3981
4125
|
}
|
|
3982
4126
|
|
|
3983
4127
|
// src/core/paths.ts
|
|
3984
|
-
var
|
|
4128
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3985
4129
|
function toRelativePath(root, target) {
|
|
3986
4130
|
if (!target) {
|
|
3987
4131
|
return target;
|
|
3988
4132
|
}
|
|
3989
|
-
if (!
|
|
4133
|
+
if (!import_node_path14.default.isAbsolute(target)) {
|
|
3990
4134
|
return toPosixPath2(target);
|
|
3991
4135
|
}
|
|
3992
|
-
const relative =
|
|
4136
|
+
const relative = import_node_path14.default.relative(root, target);
|
|
3993
4137
|
if (!relative) {
|
|
3994
4138
|
return ".";
|
|
3995
4139
|
}
|
|
@@ -4160,7 +4304,7 @@ function parseSpec(md, file) {
|
|
|
4160
4304
|
}
|
|
4161
4305
|
|
|
4162
4306
|
// src/core/deltaV1.ts
|
|
4163
|
-
var
|
|
4307
|
+
var import_yaml4 = require("yaml");
|
|
4164
4308
|
var REQUIRED_DELTA_META_KEYS = [
|
|
4165
4309
|
"id",
|
|
4166
4310
|
"date",
|
|
@@ -4315,7 +4459,7 @@ function parseYamlMeta(block) {
|
|
|
4315
4459
|
return { value: null, error: null };
|
|
4316
4460
|
}
|
|
4317
4461
|
try {
|
|
4318
|
-
const parsed = (0,
|
|
4462
|
+
const parsed = (0, import_yaml4.parse)(sanitizeMetaYaml(block));
|
|
4319
4463
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4320
4464
|
return {
|
|
4321
4465
|
value: null,
|
|
@@ -4349,7 +4493,7 @@ function parseVerificationPlan(body) {
|
|
|
4349
4493
|
return { planHeadingLine: planHeading.line, parseError: null, items: [] };
|
|
4350
4494
|
}
|
|
4351
4495
|
try {
|
|
4352
|
-
const parsed = (0,
|
|
4496
|
+
const parsed = (0, import_yaml4.parse)(yamlSource);
|
|
4353
4497
|
if (!Array.isArray(parsed)) {
|
|
4354
4498
|
return {
|
|
4355
4499
|
planHeadingLine: planHeading.line,
|
|
@@ -4436,16 +4580,16 @@ function asTagArray(value) {
|
|
|
4436
4580
|
}
|
|
4437
4581
|
|
|
4438
4582
|
// src/core/version.ts
|
|
4439
|
-
var
|
|
4440
|
-
var
|
|
4583
|
+
var import_promises15 = require("fs/promises");
|
|
4584
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
4441
4585
|
var import_node_url = require("url");
|
|
4442
4586
|
async function resolveToolVersion() {
|
|
4443
|
-
if ("1.7.
|
|
4444
|
-
return "1.7.
|
|
4587
|
+
if ("1.7.7".length > 0) {
|
|
4588
|
+
return "1.7.7";
|
|
4445
4589
|
}
|
|
4446
4590
|
try {
|
|
4447
4591
|
const packagePath = resolvePackageJsonPath();
|
|
4448
|
-
const raw = await (0,
|
|
4592
|
+
const raw = await (0, import_promises15.readFile)(packagePath, "utf-8");
|
|
4449
4593
|
const parsed = JSON.parse(raw);
|
|
4450
4594
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
4451
4595
|
return version.length > 0 ? version : "unknown";
|
|
@@ -4456,13 +4600,13 @@ async function resolveToolVersion() {
|
|
|
4456
4600
|
function resolvePackageJsonPath() {
|
|
4457
4601
|
const base = __filename;
|
|
4458
4602
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
4459
|
-
return
|
|
4603
|
+
return import_node_path15.default.resolve(import_node_path15.default.dirname(basePath), "../../package.json");
|
|
4460
4604
|
}
|
|
4461
4605
|
|
|
4462
4606
|
// src/core/waivers.ts
|
|
4463
|
-
var
|
|
4464
|
-
var
|
|
4465
|
-
var
|
|
4607
|
+
var import_promises16 = require("fs/promises");
|
|
4608
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
4609
|
+
var import_yaml5 = require("yaml");
|
|
4466
4610
|
|
|
4467
4611
|
// src/core/regex.ts
|
|
4468
4612
|
function escapeRegExp(value) {
|
|
@@ -4470,12 +4614,12 @@ function escapeRegExp(value) {
|
|
|
4470
4614
|
}
|
|
4471
4615
|
|
|
4472
4616
|
// src/core/waivers.ts
|
|
4473
|
-
var WAIVER_FILE =
|
|
4474
|
-
var UNSUPPORTED_WAIVER_FILE =
|
|
4617
|
+
var WAIVER_FILE = import_node_path16.default.join(".qfai", "waivers.yml");
|
|
4618
|
+
var UNSUPPORTED_WAIVER_FILE = import_node_path16.default.join(".qfai", "waivers.yaml");
|
|
4475
4619
|
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4476
4620
|
var RULE_ID_RE = /^[A-Z]+-\d{3}$/;
|
|
4477
4621
|
async function applyWaivers(root, findings) {
|
|
4478
|
-
const resolvedRoot =
|
|
4622
|
+
const resolvedRoot = import_node_path16.default.resolve(root);
|
|
4479
4623
|
const ruleSeverityIndex = buildRuleSeverityIndex(findings);
|
|
4480
4624
|
const loaded = await loadWaivers(resolvedRoot, ruleSeverityIndex);
|
|
4481
4625
|
const applied = applyWaiversToFindings(resolvedRoot, findings, loaded.applicableWaivers);
|
|
@@ -4488,8 +4632,8 @@ async function applyWaivers(root, findings) {
|
|
|
4488
4632
|
};
|
|
4489
4633
|
}
|
|
4490
4634
|
async function loadWaivers(root, ruleSeverityIndex) {
|
|
4491
|
-
const waiverPath =
|
|
4492
|
-
const unsupportedPath =
|
|
4635
|
+
const waiverPath = import_node_path16.default.join(root, WAIVER_FILE);
|
|
4636
|
+
const unsupportedPath = import_node_path16.default.join(root, UNSUPPORTED_WAIVER_FILE);
|
|
4493
4637
|
const validationIssues = [];
|
|
4494
4638
|
if (await exists6(unsupportedPath)) {
|
|
4495
4639
|
validationIssues.push(
|
|
@@ -4513,7 +4657,7 @@ async function loadWaivers(root, ruleSeverityIndex) {
|
|
|
4513
4657
|
}
|
|
4514
4658
|
let rawText;
|
|
4515
4659
|
try {
|
|
4516
|
-
rawText = await (0,
|
|
4660
|
+
rawText = await (0, import_promises16.readFile)(waiverPath, "utf-8");
|
|
4517
4661
|
} catch (error) {
|
|
4518
4662
|
validationIssues.push(
|
|
4519
4663
|
issue(
|
|
@@ -4534,7 +4678,7 @@ async function loadWaivers(root, ruleSeverityIndex) {
|
|
|
4534
4678
|
}
|
|
4535
4679
|
let parsed;
|
|
4536
4680
|
try {
|
|
4537
|
-
parsed = (0,
|
|
4681
|
+
parsed = (0, import_yaml5.parse)(rawText);
|
|
4538
4682
|
} catch (error) {
|
|
4539
4683
|
validationIssues.push(
|
|
4540
4684
|
issue(
|
|
@@ -5172,7 +5316,7 @@ function toErrorMessage(error) {
|
|
|
5172
5316
|
}
|
|
5173
5317
|
async function exists6(targetPath) {
|
|
5174
5318
|
try {
|
|
5175
|
-
await (0,
|
|
5319
|
+
await (0, import_promises16.access)(targetPath);
|
|
5176
5320
|
return true;
|
|
5177
5321
|
} catch {
|
|
5178
5322
|
return false;
|
|
@@ -5206,8 +5350,8 @@ var STATIC_RULE_SEVERITY = {
|
|
|
5206
5350
|
};
|
|
5207
5351
|
|
|
5208
5352
|
// src/core/validators/contracts.ts
|
|
5209
|
-
var
|
|
5210
|
-
var
|
|
5353
|
+
var import_promises17 = require("fs/promises");
|
|
5354
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
5211
5355
|
var SQL_DANGEROUS_PATTERNS = [
|
|
5212
5356
|
{ pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
|
|
5213
5357
|
{ pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
|
|
@@ -5220,9 +5364,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
5220
5364
|
async function validateContracts(root, config) {
|
|
5221
5365
|
const issues = [];
|
|
5222
5366
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
5223
|
-
const uiRoot =
|
|
5224
|
-
const apiRoot =
|
|
5225
|
-
const dbRoot =
|
|
5367
|
+
const uiRoot = import_node_path17.default.join(contractsRoot, "ui");
|
|
5368
|
+
const apiRoot = import_node_path17.default.join(contractsRoot, "api");
|
|
5369
|
+
const dbRoot = import_node_path17.default.join(contractsRoot, "db");
|
|
5226
5370
|
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
5227
5371
|
collectUiContractFiles(uiRoot),
|
|
5228
5372
|
collectApiContractFiles(apiRoot),
|
|
@@ -5276,7 +5420,7 @@ async function validateContracts(root, config) {
|
|
|
5276
5420
|
}
|
|
5277
5421
|
async function validateContractFile(file, kind) {
|
|
5278
5422
|
const issues = [];
|
|
5279
|
-
const text = await (0,
|
|
5423
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
5280
5424
|
const declaredIds = extractDeclaredContractIds(text);
|
|
5281
5425
|
issues.push(...validateDeclaredContractIds(declaredIds, file, kind));
|
|
5282
5426
|
if (kind === "DB") {
|
|
@@ -5397,23 +5541,23 @@ function formatError5(error) {
|
|
|
5397
5541
|
}
|
|
5398
5542
|
|
|
5399
5543
|
// src/core/validators/discussMermaid.ts
|
|
5400
|
-
var
|
|
5401
|
-
var
|
|
5544
|
+
var import_promises18 = require("fs/promises");
|
|
5545
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
5402
5546
|
var DISCUSSION_PACK_FLOW_FILE = "03_Story-Workshop.md";
|
|
5403
5547
|
var MERMAID_START_RE = /^\s*(`{3,}|~{3,})\s*mermaid\b/i;
|
|
5404
5548
|
var FLOW_OR_SEQUENCE_RE = /\b(?:sequenceDiagram|flowchart)\b/;
|
|
5405
5549
|
async function validateDiscussionMermaid(root) {
|
|
5406
|
-
const discussionRootDir =
|
|
5550
|
+
const discussionRootDir = import_node_path18.default.join(root, ".qfai", "discussion");
|
|
5407
5551
|
const discussionPackMarkdownFiles = await collectFiles(discussionRootDir, {
|
|
5408
5552
|
extensions: [".md"]
|
|
5409
5553
|
});
|
|
5410
5554
|
const discussionPackFiles = [];
|
|
5411
5555
|
for (const file of discussionPackMarkdownFiles) {
|
|
5412
|
-
const fileName =
|
|
5556
|
+
const fileName = import_node_path18.default.basename(file);
|
|
5413
5557
|
if (fileName !== DISCUSSION_PACK_FLOW_FILE) {
|
|
5414
5558
|
continue;
|
|
5415
5559
|
}
|
|
5416
|
-
const discussionDirName =
|
|
5560
|
+
const discussionDirName = import_node_path18.default.basename(import_node_path18.default.dirname(file));
|
|
5417
5561
|
const nameValidation = validatePackName("discussion", discussionDirName);
|
|
5418
5562
|
if (nameValidation.isCanonical) {
|
|
5419
5563
|
discussionPackFiles.push({ file, kind: "current" });
|
|
@@ -5443,7 +5587,7 @@ async function validateDiscussionMermaid(root) {
|
|
|
5443
5587
|
);
|
|
5444
5588
|
}
|
|
5445
5589
|
for (const { file } of discussionPackFiles) {
|
|
5446
|
-
const text = await (0,
|
|
5590
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
5447
5591
|
if (containsMermaidFlowDiagram(text)) {
|
|
5448
5592
|
continue;
|
|
5449
5593
|
}
|
|
@@ -5493,17 +5637,17 @@ function containsMermaidFlowDiagram(text) {
|
|
|
5493
5637
|
}
|
|
5494
5638
|
|
|
5495
5639
|
// src/core/validators/assistantAssets.ts
|
|
5496
|
-
var
|
|
5497
|
-
var
|
|
5640
|
+
var import_promises19 = require("fs/promises");
|
|
5641
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
5498
5642
|
var DRIFT_PROTOCOL_MARKER = "[DRIFT-PROTOCOL:MANDATORY]";
|
|
5499
5643
|
var REVIEWER_GATE_HEADING_PATTERN = /^###\s+Reviewer Gate\b.*$/im;
|
|
5500
5644
|
var ANY_MARKDOWN_HEADING_PATTERN = /^\s*#{1,6}\s+/m;
|
|
5501
5645
|
async function validateAssistantAssets(root, config) {
|
|
5502
5646
|
const skillsDir = resolvePath(root, config, "skillsDir");
|
|
5503
|
-
const assistantDir =
|
|
5504
|
-
const skillsLocalDir =
|
|
5505
|
-
const driftProtocolPath =
|
|
5506
|
-
const testLayersPath =
|
|
5647
|
+
const assistantDir = import_node_path19.default.dirname(skillsDir);
|
|
5648
|
+
const skillsLocalDir = import_node_path19.default.join(import_node_path19.default.dirname(skillsDir), `${import_node_path19.default.basename(skillsDir)}.local`);
|
|
5649
|
+
const driftProtocolPath = import_node_path19.default.join(assistantDir, "instructions", "drift-protocol.md");
|
|
5650
|
+
const testLayersPath = import_node_path19.default.join(assistantDir, "steering", "test-layers.md");
|
|
5507
5651
|
const issues = [];
|
|
5508
5652
|
if (!await exists7(driftProtocolPath)) {
|
|
5509
5653
|
issues.push(
|
|
@@ -5529,7 +5673,7 @@ async function validateAssistantAssets(root, config) {
|
|
|
5529
5673
|
}
|
|
5530
5674
|
const skillFiles = await collectSkillFiles([skillsDir, skillsLocalDir]);
|
|
5531
5675
|
for (const skillFile of skillFiles) {
|
|
5532
|
-
const content = await (0,
|
|
5676
|
+
const content = await (0, import_promises19.readFile)(skillFile, "utf-8");
|
|
5533
5677
|
if (!content.includes(DRIFT_PROTOCOL_MARKER)) {
|
|
5534
5678
|
issues.push(
|
|
5535
5679
|
issue(
|
|
@@ -5571,7 +5715,7 @@ async function validateAssistantAssets(root, config) {
|
|
|
5571
5715
|
}
|
|
5572
5716
|
async function collectSkillFiles(dirs) {
|
|
5573
5717
|
const files = await Promise.all(dirs.map((dir) => collectFiles(dir)));
|
|
5574
|
-
return files.flat().filter((filePath) =>
|
|
5718
|
+
return files.flat().filter((filePath) => import_node_path19.default.basename(filePath) === "SKILL.md").sort((a, b) => a.localeCompare(b));
|
|
5575
5719
|
}
|
|
5576
5720
|
function extractReviewerGateSection(content) {
|
|
5577
5721
|
const headingMatch = REVIEWER_GATE_HEADING_PATTERN.exec(content);
|
|
@@ -5604,7 +5748,7 @@ function collectMissingReviewerGateTerms(section) {
|
|
|
5604
5748
|
}
|
|
5605
5749
|
async function exists7(target) {
|
|
5606
5750
|
try {
|
|
5607
|
-
await (0,
|
|
5751
|
+
await (0, import_promises19.access)(target);
|
|
5608
5752
|
return true;
|
|
5609
5753
|
} catch {
|
|
5610
5754
|
return false;
|
|
@@ -5612,20 +5756,20 @@ async function exists7(target) {
|
|
|
5612
5756
|
}
|
|
5613
5757
|
|
|
5614
5758
|
// src/core/skillsIntegrity.ts
|
|
5615
|
-
var
|
|
5616
|
-
var
|
|
5759
|
+
var import_promises20 = require("fs/promises");
|
|
5760
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
5617
5761
|
|
|
5618
5762
|
// src/shared/assets.ts
|
|
5619
5763
|
var import_node_fs = require("fs");
|
|
5620
|
-
var
|
|
5764
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
5621
5765
|
var import_node_url2 = require("url");
|
|
5622
5766
|
function getInitAssetsDir() {
|
|
5623
5767
|
const base = __filename;
|
|
5624
5768
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
5625
|
-
const baseDir =
|
|
5769
|
+
const baseDir = import_node_path20.default.dirname(basePath);
|
|
5626
5770
|
const candidates = [
|
|
5627
|
-
|
|
5628
|
-
|
|
5771
|
+
import_node_path20.default.resolve(baseDir, "../../../assets/init"),
|
|
5772
|
+
import_node_path20.default.resolve(baseDir, "../../assets/init")
|
|
5629
5773
|
];
|
|
5630
5774
|
for (const candidate of candidates) {
|
|
5631
5775
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
@@ -5644,10 +5788,10 @@ function getInitAssetsDir() {
|
|
|
5644
5788
|
// src/core/skillsIntegrity.ts
|
|
5645
5789
|
async function diffProjectSkillsAgainstInitAssets(root, config) {
|
|
5646
5790
|
const skillsDirConfig = config.paths.skillsDir;
|
|
5647
|
-
const skillsDir =
|
|
5791
|
+
const skillsDir = import_node_path21.default.isAbsolute(skillsDirConfig) ? skillsDirConfig : import_node_path21.default.resolve(root, skillsDirConfig);
|
|
5648
5792
|
let templateDir;
|
|
5649
5793
|
try {
|
|
5650
|
-
const rel =
|
|
5794
|
+
const rel = import_node_path21.default.isAbsolute(skillsDirConfig) ? import_node_path21.default.relative(root, skillsDirConfig) : skillsDirConfig;
|
|
5651
5795
|
const normalized = rel.replace(/^[\\/]+/, "");
|
|
5652
5796
|
if (normalized.length === 0 || normalized.startsWith("..")) {
|
|
5653
5797
|
return {
|
|
@@ -5659,7 +5803,7 @@ async function diffProjectSkillsAgainstInitAssets(root, config) {
|
|
|
5659
5803
|
changed: []
|
|
5660
5804
|
};
|
|
5661
5805
|
}
|
|
5662
|
-
templateDir =
|
|
5806
|
+
templateDir = import_node_path21.default.join(getInitAssetsDir(), normalized);
|
|
5663
5807
|
} catch {
|
|
5664
5808
|
return {
|
|
5665
5809
|
status: "skipped_missing_assets",
|
|
@@ -5712,8 +5856,8 @@ async function diffProjectSkillsAgainstInitAssets(root, config) {
|
|
|
5712
5856
|
}
|
|
5713
5857
|
try {
|
|
5714
5858
|
const [a, b] = await Promise.all([
|
|
5715
|
-
(0,
|
|
5716
|
-
(0,
|
|
5859
|
+
(0, import_promises20.readFile)(templateAbs, "utf-8"),
|
|
5860
|
+
(0, import_promises20.readFile)(projectAbs, "utf-8")
|
|
5717
5861
|
]);
|
|
5718
5862
|
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
5719
5863
|
changed.push(rel);
|
|
@@ -5736,7 +5880,7 @@ function normalizeNewlines(text) {
|
|
|
5736
5880
|
return text.replace(/\r\n/g, "\n");
|
|
5737
5881
|
}
|
|
5738
5882
|
function toRel(base, abs) {
|
|
5739
|
-
const rel =
|
|
5883
|
+
const rel = import_node_path21.default.relative(base, abs);
|
|
5740
5884
|
return rel.replace(/[\\/]+/g, "/");
|
|
5741
5885
|
}
|
|
5742
5886
|
function intersectKeys(a, b) {
|
|
@@ -5781,8 +5925,8 @@ async function validateSkillsIntegrity(root, config) {
|
|
|
5781
5925
|
}
|
|
5782
5926
|
|
|
5783
5927
|
// src/core/validators/ids.ts
|
|
5784
|
-
var
|
|
5785
|
-
var
|
|
5928
|
+
var import_promises21 = require("fs/promises");
|
|
5929
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
5786
5930
|
var SC_TAG_RE2 = /^SC-\d{4}-\d{4}$/;
|
|
5787
5931
|
var AC_ID_RE2 = /\bAC-[A-Za-z0-9_-]+\b/g;
|
|
5788
5932
|
var CASE_ID_RE = /\b(?:CASE|TC)-[A-Za-z0-9_-]+\b/g;
|
|
@@ -5898,7 +6042,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
5898
6042
|
}
|
|
5899
6043
|
async function readSafe5(file) {
|
|
5900
6044
|
try {
|
|
5901
|
-
return await (0,
|
|
6045
|
+
return await (0, import_promises21.readFile)(file, "utf-8");
|
|
5902
6046
|
} catch {
|
|
5903
6047
|
return "";
|
|
5904
6048
|
}
|
|
@@ -5929,23 +6073,23 @@ function recordId(out, id, file) {
|
|
|
5929
6073
|
}
|
|
5930
6074
|
function formatFileList(files, root) {
|
|
5931
6075
|
return files.map((file) => {
|
|
5932
|
-
const relative =
|
|
6076
|
+
const relative = import_node_path22.default.relative(root, file);
|
|
5933
6077
|
return relative.length > 0 ? relative : file;
|
|
5934
6078
|
}).join(", ");
|
|
5935
6079
|
}
|
|
5936
6080
|
|
|
5937
6081
|
// src/core/validators/reviewArtifacts.ts
|
|
5938
|
-
var
|
|
5939
|
-
var
|
|
6082
|
+
var import_promises22 = require("fs/promises");
|
|
6083
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5940
6084
|
var REVIEW_PACK_DIR_RE = /^review-(\d{17})$/i;
|
|
5941
6085
|
var REVIEWER_FILE_RE = /^R\d+_.+\.md$/i;
|
|
5942
6086
|
var ALLOWED_TARGET_KINDS = /* @__PURE__ */ new Set(["spec", "discussion"]);
|
|
5943
6087
|
var ALLOWED_ROSTER_STATUS = /* @__PURE__ */ new Set(["PASS", "FAIL", "NA"]);
|
|
5944
6088
|
var ALLOWED_OVERALL_STATUS = /* @__PURE__ */ new Set(["PASS", "FAIL"]);
|
|
5945
6089
|
async function validateReviewArtifacts(root) {
|
|
5946
|
-
const reviewRoot =
|
|
6090
|
+
const reviewRoot = import_node_path23.default.join(root, ".qfai", "review");
|
|
5947
6091
|
const issues = [];
|
|
5948
|
-
const gitignorePath =
|
|
6092
|
+
const gitignorePath = import_node_path23.default.join(reviewRoot, ".gitignore");
|
|
5949
6093
|
if (!await isFile(gitignorePath)) {
|
|
5950
6094
|
issues.push(
|
|
5951
6095
|
issue(
|
|
@@ -5983,8 +6127,8 @@ async function validateReviewArtifacts(root) {
|
|
|
5983
6127
|
}
|
|
5984
6128
|
async function validateReviewPack(reviewPackDir) {
|
|
5985
6129
|
const issues = [];
|
|
5986
|
-
const reviewRequestPath =
|
|
5987
|
-
const summaryPath =
|
|
6130
|
+
const reviewRequestPath = import_node_path23.default.join(reviewPackDir, "review_request.md");
|
|
6131
|
+
const summaryPath = import_node_path23.default.join(reviewPackDir, "summary.json");
|
|
5988
6132
|
if (!await isFile(reviewRequestPath)) {
|
|
5989
6133
|
issues.push(
|
|
5990
6134
|
issue(
|
|
@@ -6027,7 +6171,7 @@ async function validateReviewPack(reviewPackDir) {
|
|
|
6027
6171
|
async function validateSummarySchema(summaryPath) {
|
|
6028
6172
|
let parsed;
|
|
6029
6173
|
try {
|
|
6030
|
-
parsed = JSON.parse(await (0,
|
|
6174
|
+
parsed = JSON.parse(await (0, import_promises22.readFile)(summaryPath, "utf-8"));
|
|
6031
6175
|
} catch (error) {
|
|
6032
6176
|
return [
|
|
6033
6177
|
issue(
|
|
@@ -6111,16 +6255,16 @@ async function validateSummarySchema(summaryPath) {
|
|
|
6111
6255
|
async function listReviewPackDirs(reviewRoot) {
|
|
6112
6256
|
let entries = [];
|
|
6113
6257
|
try {
|
|
6114
|
-
const dirEntries = await (0,
|
|
6258
|
+
const dirEntries = await (0, import_promises22.readdir)(reviewRoot, { withFileTypes: true });
|
|
6115
6259
|
entries = dirEntries.filter((entry) => entry.isDirectory() && REVIEW_PACK_DIR_RE.test(entry.name)).map((entry) => entry.name).sort((left, right) => right.localeCompare(left));
|
|
6116
6260
|
} catch {
|
|
6117
6261
|
return [];
|
|
6118
6262
|
}
|
|
6119
|
-
return entries.map((name) =>
|
|
6263
|
+
return entries.map((name) => import_node_path23.default.join(reviewRoot, name));
|
|
6120
6264
|
}
|
|
6121
6265
|
async function listReviewerFiles(reviewPackDir) {
|
|
6122
6266
|
try {
|
|
6123
|
-
const entries = await (0,
|
|
6267
|
+
const entries = await (0, import_promises22.readdir)(reviewPackDir, { withFileTypes: true });
|
|
6124
6268
|
return entries.filter((entry) => entry.isFile() && REVIEWER_FILE_RE.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
6125
6269
|
} catch {
|
|
6126
6270
|
return [];
|
|
@@ -6128,7 +6272,7 @@ async function listReviewerFiles(reviewPackDir) {
|
|
|
6128
6272
|
}
|
|
6129
6273
|
async function isFile(target) {
|
|
6130
6274
|
try {
|
|
6131
|
-
const stats = await (0,
|
|
6275
|
+
const stats = await (0, import_promises22.stat)(target);
|
|
6132
6276
|
return stats.isFile();
|
|
6133
6277
|
} catch {
|
|
6134
6278
|
return false;
|
|
@@ -6164,8 +6308,8 @@ function formatError6(error) {
|
|
|
6164
6308
|
}
|
|
6165
6309
|
|
|
6166
6310
|
// src/core/validators/specPack.ts
|
|
6167
|
-
var
|
|
6168
|
-
var
|
|
6311
|
+
var import_promises23 = require("fs/promises");
|
|
6312
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
6169
6313
|
var LEDGER_REQUIRED_COLUMNS = [
|
|
6170
6314
|
"trace_id",
|
|
6171
6315
|
"obj_id",
|
|
@@ -6583,7 +6727,7 @@ function validateLayeredNamespace(entry, filePath, ids, prefix) {
|
|
|
6583
6727
|
"specPack.layered.namespace",
|
|
6584
6728
|
mismatched,
|
|
6585
6729
|
"compatibility",
|
|
6586
|
-
`${
|
|
6730
|
+
`${import_node_path24.default.basename(filePath)} \u306E ${prefix} ID \u3092 spec-${entry.specNumber} \u306B\u5408\u308F\u305B\u3066\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
6587
6731
|
)
|
|
6588
6732
|
];
|
|
6589
6733
|
}
|
|
@@ -6591,7 +6735,7 @@ async function collectExistingLayeredDeltaFiles(entry) {
|
|
|
6591
6735
|
const candidates = Array.from(new Set(entry.deltaCandidates));
|
|
6592
6736
|
const existing = [];
|
|
6593
6737
|
for (const candidate of candidates) {
|
|
6594
|
-
if (!/(?:^|_)delta\.md$/i.test(
|
|
6738
|
+
if (!/(?:^|_)delta\.md$/i.test(import_node_path24.default.basename(candidate))) {
|
|
6595
6739
|
continue;
|
|
6596
6740
|
}
|
|
6597
6741
|
if (await fileExists(candidate)) {
|
|
@@ -6602,7 +6746,7 @@ async function collectExistingLayeredDeltaFiles(entry) {
|
|
|
6602
6746
|
}
|
|
6603
6747
|
async function fileExists(target) {
|
|
6604
6748
|
try {
|
|
6605
|
-
await (0,
|
|
6749
|
+
await (0, import_promises23.readFile)(target, "utf-8");
|
|
6606
6750
|
return true;
|
|
6607
6751
|
} catch {
|
|
6608
6752
|
return false;
|
|
@@ -6763,7 +6907,7 @@ async function validateTraceabilityLedger(entry, contractIds) {
|
|
|
6763
6907
|
const issues = [];
|
|
6764
6908
|
let ledgerText;
|
|
6765
6909
|
try {
|
|
6766
|
-
ledgerText = await (0,
|
|
6910
|
+
ledgerText = await (0, import_promises23.readFile)(entry.traceabilityLedgerPath, "utf-8");
|
|
6767
6911
|
} catch {
|
|
6768
6912
|
return issues;
|
|
6769
6913
|
}
|
|
@@ -7161,10 +7305,10 @@ async function loadExistingRequiredTexts(entry, missingFiles) {
|
|
|
7161
7305
|
}
|
|
7162
7306
|
async function loadLayerPolicy(root, config) {
|
|
7163
7307
|
const skillsDir = resolvePath(root, config, "skillsDir");
|
|
7164
|
-
const assistantRoot =
|
|
7165
|
-
const policyPath =
|
|
7308
|
+
const assistantRoot = import_node_path24.default.dirname(skillsDir);
|
|
7309
|
+
const policyPath = import_node_path24.default.join(assistantRoot, "steering", "test-layers.md");
|
|
7166
7310
|
try {
|
|
7167
|
-
const policyText = await (0,
|
|
7311
|
+
const policyText = await (0, import_promises23.readFile)(policyPath, "utf-8");
|
|
7168
7312
|
return {
|
|
7169
7313
|
tags: resolveAllowedLayerTagsFromPolicy(policyText),
|
|
7170
7314
|
issues: []
|
|
@@ -7272,15 +7416,15 @@ function extractMarkdownSection(text, heading) {
|
|
|
7272
7416
|
}
|
|
7273
7417
|
async function readSafe6(filePath) {
|
|
7274
7418
|
try {
|
|
7275
|
-
return await (0,
|
|
7419
|
+
return await (0, import_promises23.readFile)(filePath, "utf-8");
|
|
7276
7420
|
} catch {
|
|
7277
7421
|
return "";
|
|
7278
7422
|
}
|
|
7279
7423
|
}
|
|
7280
7424
|
|
|
7281
7425
|
// src/core/validators/traceability.ts
|
|
7282
|
-
var
|
|
7283
|
-
var
|
|
7426
|
+
var import_promises24 = require("fs/promises");
|
|
7427
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
7284
7428
|
var SC_TAG_RE3 = /^SC-\d{4}-\d{4}$/;
|
|
7285
7429
|
var SC_TAG_RE_GLOBAL = /\bSC-\d{4}-\d{4}\b/g;
|
|
7286
7430
|
var SPEC_TAG_RE = /^SPEC-\d{4}$/;
|
|
@@ -7297,8 +7441,8 @@ async function validateTraceability(root, config, phase) {
|
|
|
7297
7441
|
if (layeredEntries.length === 0) {
|
|
7298
7442
|
return issues;
|
|
7299
7443
|
}
|
|
7300
|
-
const policiesDir = layeredEntries[0]?.sharedDir ??
|
|
7301
|
-
const capabilitiesPath =
|
|
7444
|
+
const policiesDir = layeredEntries[0]?.sharedDir ?? import_node_path25.default.join(specsRoot, "_policies");
|
|
7445
|
+
const capabilitiesPath = import_node_path25.default.join(policiesDir, "03_Capabilities.md");
|
|
7302
7446
|
const capabilitiesExists = await exists8(capabilitiesPath);
|
|
7303
7447
|
const capabilitiesText = await readSafe7(capabilitiesPath);
|
|
7304
7448
|
const capIds = new Set(extractIds(capabilitiesText, "CAP"));
|
|
@@ -7379,7 +7523,7 @@ async function validatePoliciesLayerDownRef(policiesDir) {
|
|
|
7379
7523
|
"04_Business-flow.md"
|
|
7380
7524
|
];
|
|
7381
7525
|
for (const fileName of targets) {
|
|
7382
|
-
const targetPath =
|
|
7526
|
+
const targetPath = import_node_path25.default.join(policiesDir, fileName);
|
|
7383
7527
|
const text = await readSafe7(targetPath);
|
|
7384
7528
|
if (text.length === 0) {
|
|
7385
7529
|
continue;
|
|
@@ -7838,14 +7982,14 @@ function cloneGlobal3(re) {
|
|
|
7838
7982
|
}
|
|
7839
7983
|
async function readSafe7(target) {
|
|
7840
7984
|
try {
|
|
7841
|
-
return await (0,
|
|
7985
|
+
return await (0, import_promises24.readFile)(target, "utf-8");
|
|
7842
7986
|
} catch {
|
|
7843
7987
|
return "";
|
|
7844
7988
|
}
|
|
7845
7989
|
}
|
|
7846
7990
|
async function exists8(target) {
|
|
7847
7991
|
try {
|
|
7848
|
-
await (0,
|
|
7992
|
+
await (0, import_promises24.access)(target);
|
|
7849
7993
|
return true;
|
|
7850
7994
|
} catch {
|
|
7851
7995
|
return false;
|
|
@@ -7853,8 +7997,8 @@ async function exists8(target) {
|
|
|
7853
7997
|
}
|
|
7854
7998
|
|
|
7855
7999
|
// src/core/validators/atddCodeTraceability.ts
|
|
7856
|
-
var
|
|
7857
|
-
var
|
|
8000
|
+
var import_promises25 = require("fs/promises");
|
|
8001
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
7858
8002
|
async function validateAtddCodeTraceability(root, config) {
|
|
7859
8003
|
const result = await evaluateAtddCodeTraceability(root, config);
|
|
7860
8004
|
const issues = [];
|
|
@@ -7937,7 +8081,7 @@ async function validateAtddCodeTraceability(root, config) {
|
|
|
7937
8081
|
"QFAI-ATDD-901",
|
|
7938
8082
|
`atdd-traceability report \u306E\u51FA\u529B\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError7(error)}`,
|
|
7939
8083
|
"warning",
|
|
7940
|
-
|
|
8084
|
+
import_node_path26.default.join(resolvePath(root, config, "outDir"), "atdd-traceability"),
|
|
7941
8085
|
"atddCodeTraceability.report"
|
|
7942
8086
|
)
|
|
7943
8087
|
);
|
|
@@ -8003,8 +8147,8 @@ function buildUnknownIssues(unknown) {
|
|
|
8003
8147
|
});
|
|
8004
8148
|
}
|
|
8005
8149
|
async function writeAtddTraceabilityReport(root, config, result) {
|
|
8006
|
-
const outputDir =
|
|
8007
|
-
await (0,
|
|
8150
|
+
const outputDir = import_node_path26.default.join(resolvePath(root, config, "outDir"), "atdd-traceability");
|
|
8151
|
+
await (0, import_promises25.mkdir)(outputDir, { recursive: true });
|
|
8008
8152
|
const summary = {
|
|
8009
8153
|
missing: {
|
|
8010
8154
|
us: result.missing.us,
|
|
@@ -8026,13 +8170,13 @@ async function writeAtddTraceabilityReport(root, config, result) {
|
|
|
8026
8170
|
globs: result.scan.globs
|
|
8027
8171
|
}
|
|
8028
8172
|
};
|
|
8029
|
-
await (0,
|
|
8030
|
-
|
|
8173
|
+
await (0, import_promises25.writeFile)(
|
|
8174
|
+
import_node_path26.default.join(outputDir, "summary.json"),
|
|
8031
8175
|
`${JSON.stringify(summary, null, 2)}
|
|
8032
8176
|
`,
|
|
8033
8177
|
"utf-8"
|
|
8034
8178
|
);
|
|
8035
|
-
await (0,
|
|
8179
|
+
await (0, import_promises25.writeFile)(import_node_path26.default.join(outputDir, "summary.md"), buildSummaryMarkdown(summary), "utf-8");
|
|
8036
8180
|
}
|
|
8037
8181
|
function buildSummaryMarkdown(summary) {
|
|
8038
8182
|
const lines = [];
|
|
@@ -8097,8 +8241,8 @@ function formatError7(error) {
|
|
|
8097
8241
|
}
|
|
8098
8242
|
|
|
8099
8243
|
// src/core/validators/discussionPack.ts
|
|
8100
|
-
var
|
|
8101
|
-
var
|
|
8244
|
+
var import_promises26 = require("fs/promises");
|
|
8245
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
8102
8246
|
async function validateDiscussionPackReadiness(root, config) {
|
|
8103
8247
|
const discussionRoot = resolvePath(root, config, "discussionDir");
|
|
8104
8248
|
const readiness = await inspectLatestDiscussionPack(discussionRoot);
|
|
@@ -8190,7 +8334,7 @@ async function validateDiscussionPackReadiness(root, config) {
|
|
|
8190
8334
|
);
|
|
8191
8335
|
}
|
|
8192
8336
|
if (readiness.blockingOqIds.length > 0) {
|
|
8193
|
-
const oqPath =
|
|
8337
|
+
const oqPath = import_node_path27.default.join(readiness.latestPackDir, "11_OQ-Register.md");
|
|
8194
8338
|
issues.push(
|
|
8195
8339
|
issue(
|
|
8196
8340
|
"QFAI-DPACK-004",
|
|
@@ -8205,7 +8349,7 @@ async function validateDiscussionPackReadiness(root, config) {
|
|
|
8205
8349
|
);
|
|
8206
8350
|
}
|
|
8207
8351
|
if (readiness.deferredWithoutDetails.length > 0) {
|
|
8208
|
-
const deferredPath =
|
|
8352
|
+
const deferredPath = import_node_path27.default.join(readiness.latestPackDir, "13_Deferred.md");
|
|
8209
8353
|
issues.push(
|
|
8210
8354
|
issue(
|
|
8211
8355
|
"QFAI-DPACK-007",
|
|
@@ -8219,7 +8363,7 @@ async function validateDiscussionPackReadiness(root, config) {
|
|
|
8219
8363
|
)
|
|
8220
8364
|
);
|
|
8221
8365
|
}
|
|
8222
|
-
const storyWorkshopPath =
|
|
8366
|
+
const storyWorkshopPath = import_node_path27.default.join(readiness.latestPackDir, "03_Story-Workshop.md");
|
|
8223
8367
|
const storyWorkshopText = await readSafe8(storyWorkshopPath);
|
|
8224
8368
|
if (storyWorkshopText !== null && !containsMermaidBlock(storyWorkshopText)) {
|
|
8225
8369
|
issues.push(
|
|
@@ -8242,32 +8386,32 @@ function containsMermaidBlock(text) {
|
|
|
8242
8386
|
}
|
|
8243
8387
|
async function readSafe8(filePath) {
|
|
8244
8388
|
try {
|
|
8245
|
-
const stats = await (0,
|
|
8389
|
+
const stats = await (0, import_promises26.stat)(filePath);
|
|
8246
8390
|
if (!stats.isFile()) {
|
|
8247
8391
|
return null;
|
|
8248
8392
|
}
|
|
8249
|
-
return await (0,
|
|
8393
|
+
return await (0, import_promises26.readFile)(filePath, "utf-8");
|
|
8250
8394
|
} catch {
|
|
8251
8395
|
return null;
|
|
8252
8396
|
}
|
|
8253
8397
|
}
|
|
8254
8398
|
|
|
8255
8399
|
// src/core/validators/discussionVisuals.ts
|
|
8256
|
-
var
|
|
8257
|
-
var
|
|
8400
|
+
var import_promises27 = require("fs/promises");
|
|
8401
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
8258
8402
|
var MERMAID_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*mermaid\b/im;
|
|
8259
8403
|
var SCREEN_MOCK_HEADING_RE = /^\s*#{1,6}\s*screen mock(?:\s*[-\u2014]+\s*fallback)?\s*\(html\+css\)\s*$/im;
|
|
8260
8404
|
var HTML_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*html\b/im;
|
|
8261
8405
|
var CSS_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*css\b/im;
|
|
8262
8406
|
var UI_HINT_RE = /\b(?:ui|ux|screen|screens|page|pages|layout|wireframe|mock|form|button|frontend|navigation)\b|画面|モック|遷移|フォーム|ボタン/i;
|
|
8263
8407
|
async function validateDiscussionVisuals(root) {
|
|
8264
|
-
const discussionRoot =
|
|
8408
|
+
const discussionRoot = import_node_path28.default.join(root, ".qfai", "discussion");
|
|
8265
8409
|
const latestPackDir = await findLatestDiscussionPackDir(discussionRoot);
|
|
8266
8410
|
if (!latestPackDir) {
|
|
8267
8411
|
return [];
|
|
8268
8412
|
}
|
|
8269
8413
|
const issues = [];
|
|
8270
|
-
const inceptionPath =
|
|
8414
|
+
const inceptionPath = import_node_path28.default.join(latestPackDir, "02_Inception-Deck.md");
|
|
8271
8415
|
const inceptionText = await readSafe9(inceptionPath);
|
|
8272
8416
|
if (inceptionText !== null && !MERMAID_FENCE_RE.test(inceptionText)) {
|
|
8273
8417
|
issues.push(
|
|
@@ -8283,7 +8427,7 @@ async function validateDiscussionVisuals(root) {
|
|
|
8283
8427
|
)
|
|
8284
8428
|
);
|
|
8285
8429
|
}
|
|
8286
|
-
const storyWorkshopPath =
|
|
8430
|
+
const storyWorkshopPath = import_node_path28.default.join(latestPackDir, "03_Story-Workshop.md");
|
|
8287
8431
|
const storyWorkshopText = await readSafe9(storyWorkshopPath);
|
|
8288
8432
|
const storyWorkshopPlainText = storyWorkshopText === null ? null : stripFencedCodeBlocks(storyWorkshopText);
|
|
8289
8433
|
if (storyWorkshopText === null || storyWorkshopPlainText === null) {
|
|
@@ -8347,14 +8491,14 @@ function escapeRegExp2(value) {
|
|
|
8347
8491
|
}
|
|
8348
8492
|
async function readSafe9(filePath) {
|
|
8349
8493
|
try {
|
|
8350
|
-
return await (0,
|
|
8494
|
+
return await (0, import_promises27.readFile)(filePath, "utf-8");
|
|
8351
8495
|
} catch {
|
|
8352
8496
|
return null;
|
|
8353
8497
|
}
|
|
8354
8498
|
}
|
|
8355
8499
|
|
|
8356
8500
|
// src/core/validators/densityHints.ts
|
|
8357
|
-
var
|
|
8501
|
+
var import_promises28 = require("fs/promises");
|
|
8358
8502
|
var BR_ID_RE = /\bBR-[A-Za-z0-9-]+\b/g;
|
|
8359
8503
|
var SCENARIO_RE = /^\s*Scenario(?:\s+Outline)?\s*:/gim;
|
|
8360
8504
|
var TC_OR_CASE_RE = /\b(?:TC|CASE)-[A-Za-z0-9-]+\b/g;
|
|
@@ -8453,19 +8597,19 @@ function isSeparatorRow(line) {
|
|
|
8453
8597
|
}
|
|
8454
8598
|
async function readSafe10(filePath) {
|
|
8455
8599
|
try {
|
|
8456
|
-
return await (0,
|
|
8600
|
+
return await (0, import_promises28.readFile)(filePath, "utf-8");
|
|
8457
8601
|
} catch {
|
|
8458
8602
|
return "";
|
|
8459
8603
|
}
|
|
8460
8604
|
}
|
|
8461
8605
|
|
|
8462
8606
|
// src/core/validators/importLite.ts
|
|
8463
|
-
var import_promises28 = require("fs/promises");
|
|
8464
|
-
var import_node_path28 = __toESM(require("path"), 1);
|
|
8465
|
-
|
|
8466
|
-
// src/core/validators/layerCoverage.ts
|
|
8467
8607
|
var import_promises29 = require("fs/promises");
|
|
8468
8608
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
8609
|
+
|
|
8610
|
+
// src/core/validators/layerCoverage.ts
|
|
8611
|
+
var import_promises30 = require("fs/promises");
|
|
8612
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
8469
8613
|
var ID_PATTERNS = {
|
|
8470
8614
|
us: /^US-\d{4}(?:-\d{4})?$/,
|
|
8471
8615
|
ac: /^AC-\d{4}(?:-\d{4})?$/,
|
|
@@ -8486,7 +8630,7 @@ async function validateLayerCoverage(root, config) {
|
|
|
8486
8630
|
if (layeredEntries.length === 0) {
|
|
8487
8631
|
return issues;
|
|
8488
8632
|
}
|
|
8489
|
-
const coverageRoot =
|
|
8633
|
+
const coverageRoot = import_node_path30.default.join(resolvePath(root, config, "outDir"), "specs-coverage");
|
|
8490
8634
|
for (const entry of layeredEntries) {
|
|
8491
8635
|
if (isV1421LayeredEntry(entry)) {
|
|
8492
8636
|
const coverageResult = await validateV1421Coverage(entry);
|
|
@@ -8521,7 +8665,7 @@ async function validateLayerCoverage(root, config) {
|
|
|
8521
8665
|
}
|
|
8522
8666
|
async function validatePlanArtifacts(specsRoot, entries) {
|
|
8523
8667
|
const issues = [];
|
|
8524
|
-
const deprecatedPlanPath =
|
|
8668
|
+
const deprecatedPlanPath = import_node_path30.default.join(specsRoot, "plan.md");
|
|
8525
8669
|
if (await exists5(deprecatedPlanPath)) {
|
|
8526
8670
|
issues.push(
|
|
8527
8671
|
issue(
|
|
@@ -8537,7 +8681,7 @@ async function validatePlanArtifacts(specsRoot, entries) {
|
|
|
8537
8681
|
);
|
|
8538
8682
|
}
|
|
8539
8683
|
for (const entry of entries) {
|
|
8540
|
-
const planFileName =
|
|
8684
|
+
const planFileName = import_node_path30.default.basename(entry.planPath).toLowerCase();
|
|
8541
8685
|
if (planFileName !== "10_plan.md") {
|
|
8542
8686
|
continue;
|
|
8543
8687
|
}
|
|
@@ -8918,8 +9062,8 @@ function buildSignalRows(prefix, counts, target) {
|
|
|
8918
9062
|
return Array.from(counts.entries()).filter(([, count]) => count <= 1).sort((left, right) => left[0].localeCompare(right[0])).map(([id, count]) => `${prefix} signal: ${id} -> ${count} ${target}`);
|
|
8919
9063
|
}
|
|
8920
9064
|
async function writeCoverageReport(coverageRoot, specNumber, snapshot) {
|
|
8921
|
-
await (0,
|
|
8922
|
-
const reportPath =
|
|
9065
|
+
await (0, import_promises30.mkdir)(coverageRoot, { recursive: true });
|
|
9066
|
+
const reportPath = import_node_path30.default.join(coverageRoot, `spec-${specNumber}.md`);
|
|
8923
9067
|
const lines = [];
|
|
8924
9068
|
lines.push(`# Spec Coverage (spec-${specNumber})`);
|
|
8925
9069
|
lines.push("");
|
|
@@ -8957,14 +9101,14 @@ async function writeCoverageReport(coverageRoot, specNumber, snapshot) {
|
|
|
8957
9101
|
}
|
|
8958
9102
|
}
|
|
8959
9103
|
lines.push("");
|
|
8960
|
-
await (0,
|
|
9104
|
+
await (0, import_promises30.writeFile)(reportPath, `${lines.join("\n")}
|
|
8961
9105
|
`, "utf-8");
|
|
8962
9106
|
}
|
|
8963
9107
|
function isV1421LayeredEntry(entry) {
|
|
8964
9108
|
if (entry.layeredStyle === "v1421") {
|
|
8965
9109
|
return true;
|
|
8966
9110
|
}
|
|
8967
|
-
return
|
|
9111
|
+
return import_node_path30.default.extname(entry.examplesPath).toLowerCase() === ".md";
|
|
8968
9112
|
}
|
|
8969
9113
|
function extractHeadings(text) {
|
|
8970
9114
|
const headings = [];
|
|
@@ -9155,8 +9299,8 @@ async function validateExToTcCoverage(examplesPath, testCasesPath) {
|
|
|
9155
9299
|
}
|
|
9156
9300
|
|
|
9157
9301
|
// src/core/validators/layeredTraceability.ts
|
|
9158
|
-
var
|
|
9159
|
-
var
|
|
9302
|
+
var import_promises31 = require("fs/promises");
|
|
9303
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
9160
9304
|
var POLICIES_FILES = [
|
|
9161
9305
|
"01_Objective.md",
|
|
9162
9306
|
"02_Initiative.md",
|
|
@@ -9205,7 +9349,7 @@ async function validateLayeredTraceability(root, config) {
|
|
|
9205
9349
|
}
|
|
9206
9350
|
const issues = [];
|
|
9207
9351
|
if (layeredV1417Entries.length > 0) {
|
|
9208
|
-
const policiesDir = layeredV1417Entries[0]?.sharedDir ??
|
|
9352
|
+
const policiesDir = layeredV1417Entries[0]?.sharedDir ?? import_node_path31.default.join(specsRoot, "_policies");
|
|
9209
9353
|
issues.push(...await validatePoliciesDownstreamReferences(policiesDir));
|
|
9210
9354
|
for (const entry of layeredV1417Entries) {
|
|
9211
9355
|
issues.push(...await validateSpecRootParent(entry));
|
|
@@ -9246,7 +9390,7 @@ async function validateLayeredTraceability(root, config) {
|
|
|
9246
9390
|
}
|
|
9247
9391
|
}
|
|
9248
9392
|
if (layeredV1421Entries.length > 0) {
|
|
9249
|
-
const policiesDir = layeredV1421Entries[0]?.sharedDir ??
|
|
9393
|
+
const policiesDir = layeredV1421Entries[0]?.sharedDir ?? import_node_path31.default.join(specsRoot, "_policies");
|
|
9250
9394
|
issues.push(...await validatePoliciesScopeForV1421(policiesDir));
|
|
9251
9395
|
for (const entry of layeredV1421Entries) {
|
|
9252
9396
|
issues.push(...await validateDownstreamRefsForV1421(entry));
|
|
@@ -9257,7 +9401,7 @@ async function validateLayeredTraceability(root, config) {
|
|
|
9257
9401
|
async function validatePoliciesDownstreamReferences(policiesDir) {
|
|
9258
9402
|
const issues = [];
|
|
9259
9403
|
for (const fileName of POLICIES_FILES) {
|
|
9260
|
-
const filePath =
|
|
9404
|
+
const filePath = import_node_path31.default.join(policiesDir, fileName);
|
|
9261
9405
|
const text = await readSafe(filePath);
|
|
9262
9406
|
if (text.trim().length === 0) {
|
|
9263
9407
|
continue;
|
|
@@ -9330,7 +9474,7 @@ async function validateDownstreamRefsForV1421(entry) {
|
|
|
9330
9474
|
issues.push(
|
|
9331
9475
|
issue(
|
|
9332
9476
|
"TRACE_DOWNSTREAM_REF",
|
|
9333
|
-
`${
|
|
9477
|
+
`${import_node_path31.default.basename(check.filePath)} \u3067\u4E0B\u4F4D\u30EC\u30A4\u30E4\u30FC\u53C2\u7167\u306F\u7981\u6B62\u3067\u3059: ${refs.join(", ")}`,
|
|
9334
9478
|
"error",
|
|
9335
9479
|
check.filePath,
|
|
9336
9480
|
"layeredTraceability.downstream",
|
|
@@ -9359,14 +9503,14 @@ function resolveLayerFromId(id) {
|
|
|
9359
9503
|
}
|
|
9360
9504
|
async function collectMarkdownFiles(policiesDir) {
|
|
9361
9505
|
try {
|
|
9362
|
-
const entries = await (0,
|
|
9363
|
-
return entries.filter((entry) => entry.isFile() &&
|
|
9506
|
+
const entries = await (0, import_promises31.readdir)(policiesDir, { withFileTypes: true });
|
|
9507
|
+
return entries.filter((entry) => entry.isFile() && import_node_path31.default.extname(entry.name).toLowerCase() === ".md").map((entry) => import_node_path31.default.join(policiesDir, entry.name)).sort((left, right) => left.localeCompare(right));
|
|
9364
9508
|
} catch {
|
|
9365
9509
|
return [];
|
|
9366
9510
|
}
|
|
9367
9511
|
}
|
|
9368
9512
|
async function validateSpecRootParent(entry) {
|
|
9369
|
-
const specPath =
|
|
9513
|
+
const specPath = import_node_path31.default.join(entry.dir, "01_Spec.md");
|
|
9370
9514
|
const text = await readSafe(specPath);
|
|
9371
9515
|
if (text.trim().length > 0 && /\bCAP-\d{4}\b/.test(text)) {
|
|
9372
9516
|
return [];
|
|
@@ -9466,11 +9610,11 @@ async function validateForbiddenRefs(filePath, pattern, messagePrefix) {
|
|
|
9466
9610
|
}
|
|
9467
9611
|
|
|
9468
9612
|
// src/core/validators/legacyStatusDir.ts
|
|
9469
|
-
var
|
|
9470
|
-
var
|
|
9613
|
+
var import_promises32 = require("fs/promises");
|
|
9614
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
9471
9615
|
var BASELINE_STATUS_ARTIFACTS = /* @__PURE__ */ new Set(["README.md", ".gitignore"]);
|
|
9472
9616
|
async function validateLegacyStatusDir(root) {
|
|
9473
|
-
const statusDir =
|
|
9617
|
+
const statusDir = import_node_path32.default.join(root, ".qfai", "status");
|
|
9474
9618
|
if (!await isDirectory(statusDir)) {
|
|
9475
9619
|
return [];
|
|
9476
9620
|
}
|
|
@@ -9507,7 +9651,7 @@ async function validateLegacyStatusDir(root) {
|
|
|
9507
9651
|
}
|
|
9508
9652
|
async function listStatusArtifacts(statusDir) {
|
|
9509
9653
|
try {
|
|
9510
|
-
const entries = await (0,
|
|
9654
|
+
const entries = await (0, import_promises32.readdir)(statusDir, { withFileTypes: true });
|
|
9511
9655
|
return entries.map((entry) => entry.isDirectory() ? `${entry.name}/` : entry.name).sort((left, right) => left.localeCompare(right));
|
|
9512
9656
|
} catch {
|
|
9513
9657
|
return [];
|
|
@@ -9515,22 +9659,22 @@ async function listStatusArtifacts(statusDir) {
|
|
|
9515
9659
|
}
|
|
9516
9660
|
async function isDirectory(target) {
|
|
9517
9661
|
try {
|
|
9518
|
-
return (await (0,
|
|
9662
|
+
return (await (0, import_promises32.stat)(target)).isDirectory();
|
|
9519
9663
|
} catch {
|
|
9520
9664
|
return false;
|
|
9521
9665
|
}
|
|
9522
9666
|
}
|
|
9523
9667
|
|
|
9524
9668
|
// src/core/validators/mermaidEnforcement.ts
|
|
9525
|
-
var
|
|
9526
|
-
var
|
|
9669
|
+
var import_promises33 = require("fs/promises");
|
|
9670
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
9527
9671
|
var TARGETS = [
|
|
9528
9672
|
{ segments: [".qfai", "specs"], extensions: [".md", ".feature"] },
|
|
9529
9673
|
{ segments: [".qfai", "discussion"], extensions: [".md"] }
|
|
9530
9674
|
];
|
|
9531
9675
|
var BUSINESS_FLOW_RELATIVE_CANDIDATES = [
|
|
9532
|
-
|
|
9533
|
-
|
|
9676
|
+
import_node_path33.default.join(".qfai", "specs", "_policies", "04_Business-Flow.md"),
|
|
9677
|
+
import_node_path33.default.join(".qfai", "specs", "_policies", "04_Business-flow.md")
|
|
9534
9678
|
];
|
|
9535
9679
|
var MERMAID_DIRECTIVE_RE = /^\s*(?:sequenceDiagram|flowchart|erDiagram|classDiagram|stateDiagram(?:-v2)?|journey|gantt|graph\s+(?:TB|BT|RL|LR|TD))\b/i;
|
|
9536
9680
|
var FLOW_OR_SEQUENCE_RE2 = /\b(?:sequenceDiagram|flowchart)\b/i;
|
|
@@ -9540,13 +9684,13 @@ async function validateMermaidEnforcement(root) {
|
|
|
9540
9684
|
const issues = [];
|
|
9541
9685
|
const scanResults = /* @__PURE__ */ new Map();
|
|
9542
9686
|
for (const filePath of targetFiles) {
|
|
9543
|
-
const text = await (0,
|
|
9687
|
+
const text = await (0, import_promises33.readFile)(filePath, "utf-8");
|
|
9544
9688
|
const result = scanMermaidUsage(filePath, text);
|
|
9545
9689
|
scanResults.set(filePath, result);
|
|
9546
9690
|
issues.push(...result.issues);
|
|
9547
9691
|
}
|
|
9548
9692
|
if (businessFlowPath) {
|
|
9549
|
-
const businessFlowScan = scanResults.get(businessFlowPath) ?? scanMermaidUsage(businessFlowPath, await (0,
|
|
9693
|
+
const businessFlowScan = scanResults.get(businessFlowPath) ?? scanMermaidUsage(businessFlowPath, await (0, import_promises33.readFile)(businessFlowPath, "utf-8"));
|
|
9550
9694
|
if (businessFlowScan.mermaidFenceCount === 0) {
|
|
9551
9695
|
issues.push(
|
|
9552
9696
|
issue(
|
|
@@ -9574,7 +9718,7 @@ async function validateMermaidEnforcement(root) {
|
|
|
9574
9718
|
)
|
|
9575
9719
|
);
|
|
9576
9720
|
}
|
|
9577
|
-
if (
|
|
9721
|
+
if (import_node_path33.default.basename(businessFlowPath) === "04_Business-flow.md") {
|
|
9578
9722
|
issues.push(
|
|
9579
9723
|
issue(
|
|
9580
9724
|
"QFAI-MMD-005",
|
|
@@ -9595,14 +9739,14 @@ async function validateMermaidEnforcement(root) {
|
|
|
9595
9739
|
async function collectTargetFiles(root) {
|
|
9596
9740
|
const files = [];
|
|
9597
9741
|
for (const target of TARGETS) {
|
|
9598
|
-
const targetDir =
|
|
9742
|
+
const targetDir = import_node_path33.default.join(root, ...target.segments);
|
|
9599
9743
|
files.push(
|
|
9600
9744
|
...await collectFiles(targetDir, {
|
|
9601
9745
|
extensions: [...target.extensions]
|
|
9602
9746
|
})
|
|
9603
9747
|
);
|
|
9604
9748
|
}
|
|
9605
|
-
return Array.from(new Set(files)).filter((filePath) =>
|
|
9749
|
+
return Array.from(new Set(files)).filter((filePath) => import_node_path33.default.basename(filePath).toLowerCase() !== "readme.md").sort((a, b) => a.localeCompare(b));
|
|
9606
9750
|
}
|
|
9607
9751
|
function scanMermaidUsage(filePath, text) {
|
|
9608
9752
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
@@ -9692,13 +9836,13 @@ function parseFenceStart(line) {
|
|
|
9692
9836
|
};
|
|
9693
9837
|
}
|
|
9694
9838
|
async function collectDeprecatedBusinessFlowFeatureWarnings(root) {
|
|
9695
|
-
const policiesDir =
|
|
9839
|
+
const policiesDir = import_node_path33.default.join(root, ".qfai", "specs", "_policies");
|
|
9696
9840
|
const featureFiles = await collectFiles(policiesDir, {
|
|
9697
9841
|
extensions: [".feature"]
|
|
9698
9842
|
});
|
|
9699
9843
|
const issues = [];
|
|
9700
9844
|
for (const filePath of featureFiles) {
|
|
9701
|
-
if (!/business-flow/i.test(
|
|
9845
|
+
if (!/business-flow/i.test(import_node_path33.default.basename(filePath))) {
|
|
9702
9846
|
continue;
|
|
9703
9847
|
}
|
|
9704
9848
|
issues.push(
|
|
@@ -9718,7 +9862,7 @@ async function collectDeprecatedBusinessFlowFeatureWarnings(root) {
|
|
|
9718
9862
|
}
|
|
9719
9863
|
async function resolveBusinessFlowPath(root) {
|
|
9720
9864
|
for (const relativePath of BUSINESS_FLOW_RELATIVE_CANDIDATES) {
|
|
9721
|
-
const target =
|
|
9865
|
+
const target = import_node_path33.default.join(root, relativePath);
|
|
9722
9866
|
if (await exists5(target)) {
|
|
9723
9867
|
return target;
|
|
9724
9868
|
}
|
|
@@ -9856,7 +10000,7 @@ function normalizeHeaderKey(column) {
|
|
|
9856
10000
|
}
|
|
9857
10001
|
|
|
9858
10002
|
// src/core/validators/orphanProhibition.ts
|
|
9859
|
-
var
|
|
10003
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
9860
10004
|
async function validateOrphanProhibition(root, config) {
|
|
9861
10005
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
9862
10006
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -9867,9 +10011,9 @@ async function validateOrphanProhibition(root, config) {
|
|
|
9867
10011
|
return [];
|
|
9868
10012
|
}
|
|
9869
10013
|
const issues = [];
|
|
9870
|
-
const policiesDir = layeredEntries[0]?.sharedDir ??
|
|
10014
|
+
const policiesDir = layeredEntries[0]?.sharedDir ?? import_node_path34.default.join(specsRoot, "_policies");
|
|
9871
10015
|
const capIds = new Set(
|
|
9872
|
-
uniqueMatches(await readSafe(
|
|
10016
|
+
uniqueMatches(await readSafe(import_node_path34.default.join(policiesDir, "03_Capabilities.md")), /\bCAP-\d{4}\b/g)
|
|
9873
10017
|
);
|
|
9874
10018
|
for (const entry of layeredEntries) {
|
|
9875
10019
|
const usItems = collectMarkdownItems(await readSafe(entry.userStoriesPath), "US");
|
|
@@ -10019,8 +10163,8 @@ function validateExParentExists(filePath, exItems, acIds, brIds) {
|
|
|
10019
10163
|
}
|
|
10020
10164
|
|
|
10021
10165
|
// src/core/validators/prototypingEvidence.ts
|
|
10022
|
-
var
|
|
10023
|
-
var
|
|
10166
|
+
var import_promises34 = require("fs/promises");
|
|
10167
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
10024
10168
|
var EVIDENCE_MARKDOWN_FILE = "prototyping.md";
|
|
10025
10169
|
var EVIDENCE_JSON_FILE = "prototyping.json";
|
|
10026
10170
|
async function validatePrototypingEvidence(root, config) {
|
|
@@ -10029,10 +10173,10 @@ async function validatePrototypingEvidence(root, config) {
|
|
|
10029
10173
|
if (specEntries.length === 0) {
|
|
10030
10174
|
return [];
|
|
10031
10175
|
}
|
|
10032
|
-
const qfaiRoot =
|
|
10033
|
-
const evidenceRoot =
|
|
10034
|
-
const evidenceMarkdownPath =
|
|
10035
|
-
const evidenceJsonPath =
|
|
10176
|
+
const qfaiRoot = import_node_path35.default.dirname(specsRoot);
|
|
10177
|
+
const evidenceRoot = import_node_path35.default.join(qfaiRoot, "evidence");
|
|
10178
|
+
const evidenceMarkdownPath = import_node_path35.default.join(evidenceRoot, EVIDENCE_MARKDOWN_FILE);
|
|
10179
|
+
const evidenceJsonPath = import_node_path35.default.join(evidenceRoot, EVIDENCE_JSON_FILE);
|
|
10036
10180
|
const [markdownRaw, jsonRaw] = await Promise.all([
|
|
10037
10181
|
readSafe11(evidenceMarkdownPath),
|
|
10038
10182
|
readSafe11(evidenceJsonPath)
|
|
@@ -10209,7 +10353,7 @@ function extractSpecRefs(rows) {
|
|
|
10209
10353
|
}
|
|
10210
10354
|
async function readSafe11(filePath) {
|
|
10211
10355
|
try {
|
|
10212
|
-
return await (0,
|
|
10356
|
+
return await (0, import_promises34.readFile)(filePath, "utf-8");
|
|
10213
10357
|
} catch {
|
|
10214
10358
|
return null;
|
|
10215
10359
|
}
|
|
@@ -10365,7 +10509,7 @@ async function validateUiFidelity(root, config, evidenceJsonPath, evidence) {
|
|
|
10365
10509
|
for (const screen of uiFidelity.screens) {
|
|
10366
10510
|
const contractFiles = contractIndex.idToFiles.get(screen.uiContractId);
|
|
10367
10511
|
const contractFile = contractFiles ? Array.from(contractFiles).sort((left, right) => left.localeCompare(right))[0] : void 0;
|
|
10368
|
-
const contractRefFile = contractFile ? toPosixPath3(
|
|
10512
|
+
const contractRefFile = contractFile ? toPosixPath3(import_node_path35.default.relative(root, contractFile)) : void 0;
|
|
10369
10513
|
if (!contractFiles || contractFiles.size === 0) {
|
|
10370
10514
|
mismatches.push({
|
|
10371
10515
|
contractId: screen.uiContractId,
|
|
@@ -11174,9 +11318,9 @@ async function collectMissingRenderArtifacts(root, render) {
|
|
|
11174
11318
|
{ label: "htmlPath", target: render.htmlPath }
|
|
11175
11319
|
];
|
|
11176
11320
|
for (const candidate of candidates) {
|
|
11177
|
-
const resolved =
|
|
11321
|
+
const resolved = import_node_path35.default.isAbsolute(candidate.target) ? candidate.target : import_node_path35.default.resolve(root, candidate.target);
|
|
11178
11322
|
try {
|
|
11179
|
-
await (0,
|
|
11323
|
+
await (0, import_promises34.access)(resolved);
|
|
11180
11324
|
} catch {
|
|
11181
11325
|
missing.push(candidate.label);
|
|
11182
11326
|
}
|
|
@@ -11197,12 +11341,12 @@ function formatError9(error) {
|
|
|
11197
11341
|
}
|
|
11198
11342
|
|
|
11199
11343
|
// src/core/validators/requireIndex.ts
|
|
11200
|
-
var import_promises34 = require("fs/promises");
|
|
11201
|
-
var import_node_path35 = __toESM(require("path"), 1);
|
|
11202
|
-
|
|
11203
|
-
// src/core/validators/repositoryHygiene.ts
|
|
11204
11344
|
var import_promises35 = require("fs/promises");
|
|
11205
11345
|
var import_node_path36 = __toESM(require("path"), 1);
|
|
11346
|
+
|
|
11347
|
+
// src/core/validators/repositoryHygiene.ts
|
|
11348
|
+
var import_promises36 = require("fs/promises");
|
|
11349
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
11206
11350
|
var LEGACY_DIR_RULES = [
|
|
11207
11351
|
{ legacy: "discussions", canonical: "discussion" },
|
|
11208
11352
|
{ legacy: "discuss", canonical: "discussion" },
|
|
@@ -11213,11 +11357,11 @@ var LEGACY_DIR_RULES = [
|
|
|
11213
11357
|
];
|
|
11214
11358
|
var SUSPICIOUS_TEMPLATE_NAME_RE = /^(?:_?templates?|_?sample(?:s)?|sample-template)$/i;
|
|
11215
11359
|
async function validateRepositoryHygiene(root, config) {
|
|
11216
|
-
const qfaiRoot =
|
|
11360
|
+
const qfaiRoot = import_node_path37.default.join(root, ".qfai");
|
|
11217
11361
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
11218
11362
|
const issues = [];
|
|
11219
11363
|
for (const rule of LEGACY_DIR_RULES) {
|
|
11220
|
-
const legacyPath =
|
|
11364
|
+
const legacyPath = import_node_path37.default.join(qfaiRoot, rule.legacy);
|
|
11221
11365
|
if (!await isDirectory2(legacyPath)) {
|
|
11222
11366
|
continue;
|
|
11223
11367
|
}
|
|
@@ -11264,7 +11408,7 @@ async function collectSuspiciousTemplatePaths(root) {
|
|
|
11264
11408
|
}
|
|
11265
11409
|
let entries = [];
|
|
11266
11410
|
try {
|
|
11267
|
-
entries = await (0,
|
|
11411
|
+
entries = await (0, import_promises36.readdir)(current, {
|
|
11268
11412
|
withFileTypes: true,
|
|
11269
11413
|
encoding: "utf8"
|
|
11270
11414
|
});
|
|
@@ -11272,10 +11416,10 @@ async function collectSuspiciousTemplatePaths(root) {
|
|
|
11272
11416
|
continue;
|
|
11273
11417
|
}
|
|
11274
11418
|
for (const entry of entries) {
|
|
11275
|
-
const absolute =
|
|
11419
|
+
const absolute = import_node_path37.default.join(current, entry.name);
|
|
11276
11420
|
if (entry.isDirectory()) {
|
|
11277
11421
|
if (SUSPICIOUS_TEMPLATE_NAME_RE.test(entry.name)) {
|
|
11278
|
-
matches.push(toPosix(
|
|
11422
|
+
matches.push(toPosix(import_node_path37.default.relative(root, absolute)));
|
|
11279
11423
|
}
|
|
11280
11424
|
queue.push(absolute);
|
|
11281
11425
|
continue;
|
|
@@ -11283,9 +11427,9 @@ async function collectSuspiciousTemplatePaths(root) {
|
|
|
11283
11427
|
if (!entry.isFile()) {
|
|
11284
11428
|
continue;
|
|
11285
11429
|
}
|
|
11286
|
-
const baseName =
|
|
11430
|
+
const baseName = import_node_path37.default.parse(entry.name).name;
|
|
11287
11431
|
if (SUSPICIOUS_TEMPLATE_NAME_RE.test(baseName)) {
|
|
11288
|
-
matches.push(toPosix(
|
|
11432
|
+
matches.push(toPosix(import_node_path37.default.relative(root, absolute)));
|
|
11289
11433
|
}
|
|
11290
11434
|
}
|
|
11291
11435
|
}
|
|
@@ -11293,7 +11437,7 @@ async function collectSuspiciousTemplatePaths(root) {
|
|
|
11293
11437
|
}
|
|
11294
11438
|
async function isDirectory2(target) {
|
|
11295
11439
|
try {
|
|
11296
|
-
return (await (0,
|
|
11440
|
+
return (await (0, import_promises36.stat)(target)).isDirectory();
|
|
11297
11441
|
} catch {
|
|
11298
11442
|
return false;
|
|
11299
11443
|
}
|
|
@@ -11303,7 +11447,7 @@ function toPosix(value) {
|
|
|
11303
11447
|
}
|
|
11304
11448
|
|
|
11305
11449
|
// src/core/validators/specSplitByCapability.ts
|
|
11306
|
-
var
|
|
11450
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
11307
11451
|
var CAP_ID_RE2 = /\bCAP-\d{4}\b/g;
|
|
11308
11452
|
async function validateSpecSplitByCapability(root, config) {
|
|
11309
11453
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -11314,8 +11458,8 @@ async function validateSpecSplitByCapability(root, config) {
|
|
|
11314
11458
|
if (layeredEntries.length === 0) {
|
|
11315
11459
|
return [];
|
|
11316
11460
|
}
|
|
11317
|
-
const policiesDir = layeredEntries[0]?.sharedDir ??
|
|
11318
|
-
const capabilitiesPath =
|
|
11461
|
+
const policiesDir = layeredEntries[0]?.sharedDir ?? import_node_path38.default.join(specsRoot, "_policies");
|
|
11462
|
+
const capabilitiesPath = import_node_path38.default.join(policiesDir, "03_Capabilities.md");
|
|
11319
11463
|
const capabilityText = await readSafe(capabilitiesPath);
|
|
11320
11464
|
const issues = [];
|
|
11321
11465
|
if (!await exists5(capabilitiesPath)) {
|
|
@@ -11355,7 +11499,7 @@ async function validateSpecSplitByCapability(root, config) {
|
|
|
11355
11499
|
);
|
|
11356
11500
|
}
|
|
11357
11501
|
const actualSpecIds = new Set(
|
|
11358
|
-
layeredEntries.map((entry) =>
|
|
11502
|
+
layeredEntries.map((entry) => import_node_path38.default.basename(entry.dir).toLowerCase())
|
|
11359
11503
|
);
|
|
11360
11504
|
const expectedSpecIds = capIds.map((_, index) => `spec-${to4(index + 1)}`);
|
|
11361
11505
|
const missingSpecIds = expectedSpecIds.filter((specId) => !actualSpecIds.has(specId));
|
|
@@ -11392,11 +11536,11 @@ async function validateSpecSplitByCapability(root, config) {
|
|
|
11392
11536
|
continue;
|
|
11393
11537
|
}
|
|
11394
11538
|
const specId = `spec-${to4(index + 1)}`;
|
|
11395
|
-
const entry = layeredEntries.find((value) =>
|
|
11539
|
+
const entry = layeredEntries.find((value) => import_node_path38.default.basename(value.dir).toLowerCase() === specId);
|
|
11396
11540
|
if (!entry) {
|
|
11397
11541
|
continue;
|
|
11398
11542
|
}
|
|
11399
|
-
const specFilePath =
|
|
11543
|
+
const specFilePath = import_node_path38.default.join(entry.dir, "01_Spec.md");
|
|
11400
11544
|
const specText = await readSafe(specFilePath);
|
|
11401
11545
|
if (specText.trim().length === 0 || !specText.includes(capId)) {
|
|
11402
11546
|
issues.push(
|
|
@@ -11415,8 +11559,8 @@ async function validateSpecSplitByCapability(root, config) {
|
|
|
11415
11559
|
}
|
|
11416
11560
|
|
|
11417
11561
|
// src/core/validators/statusInSpecs.ts
|
|
11418
|
-
var
|
|
11419
|
-
var
|
|
11562
|
+
var import_promises37 = require("fs/promises");
|
|
11563
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
11420
11564
|
var STRONG_PATTERNS = [
|
|
11421
11565
|
{ label: "release_candidate:", pattern: /\brelease_candidate\s*:/i },
|
|
11422
11566
|
{ label: "status:", pattern: /^\s*(?:-\s*)?status\s*:/im },
|
|
@@ -11473,23 +11617,23 @@ function hasMatch(text, pattern) {
|
|
|
11473
11617
|
}
|
|
11474
11618
|
async function readSafe12(filePath) {
|
|
11475
11619
|
try {
|
|
11476
|
-
return await (0,
|
|
11620
|
+
return await (0, import_promises37.readFile)(filePath, "utf-8");
|
|
11477
11621
|
} catch {
|
|
11478
11622
|
return "";
|
|
11479
11623
|
}
|
|
11480
11624
|
}
|
|
11481
11625
|
function isOpenQuestionsFile(filePath) {
|
|
11482
|
-
return /open-questions\.md$/i.test(
|
|
11626
|
+
return /open-questions\.md$/i.test(import_node_path39.default.basename(filePath));
|
|
11483
11627
|
}
|
|
11484
11628
|
|
|
11485
11629
|
// src/core/validators/designToken.ts
|
|
11486
|
-
var
|
|
11487
|
-
var
|
|
11630
|
+
var import_promises38 = require("fs/promises");
|
|
11631
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
11488
11632
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
11489
|
-
var
|
|
11633
|
+
var import_yaml7 = require("yaml");
|
|
11490
11634
|
|
|
11491
11635
|
// src/core/parse/designToken.ts
|
|
11492
|
-
var
|
|
11636
|
+
var import_yaml6 = require("yaml");
|
|
11493
11637
|
var REF_PATTERN = /\{([^}]+)\}/g;
|
|
11494
11638
|
var MAX_RESOLVE_DEPTH = 10;
|
|
11495
11639
|
function parseDesignToken(yamlContent) {
|
|
@@ -11502,7 +11646,7 @@ function parseDesignToken(yamlContent) {
|
|
|
11502
11646
|
};
|
|
11503
11647
|
let parsed;
|
|
11504
11648
|
try {
|
|
11505
|
-
parsed = (0,
|
|
11649
|
+
parsed = (0, import_yaml6.parse)(yamlContent);
|
|
11506
11650
|
} catch (error) {
|
|
11507
11651
|
const msg = error instanceof Error ? error.message : String(error);
|
|
11508
11652
|
result.errors.push({ message: `YAML parse error: ${msg}` });
|
|
@@ -11531,7 +11675,7 @@ function collectLayer(layer, layerName, target, errors) {
|
|
|
11531
11675
|
}
|
|
11532
11676
|
function flattenTokens(obj, prefix, target, errors) {
|
|
11533
11677
|
for (const [key, value] of Object.entries(obj)) {
|
|
11534
|
-
const
|
|
11678
|
+
const path58 = `${prefix}.${key}`;
|
|
11535
11679
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
11536
11680
|
const record2 = value;
|
|
11537
11681
|
if ("$value" in record2) {
|
|
@@ -11547,9 +11691,9 @@ function flattenTokens(obj, prefix, target, errors) {
|
|
|
11547
11691
|
if (typeof record2.platform === "string") {
|
|
11548
11692
|
token.platform = record2.platform;
|
|
11549
11693
|
}
|
|
11550
|
-
target.set(
|
|
11694
|
+
target.set(path58, token);
|
|
11551
11695
|
} else {
|
|
11552
|
-
flattenTokens(record2,
|
|
11696
|
+
flattenTokens(record2, path58, target, errors);
|
|
11553
11697
|
}
|
|
11554
11698
|
}
|
|
11555
11699
|
}
|
|
@@ -11559,44 +11703,44 @@ function resolveAllReferences(result) {
|
|
|
11559
11703
|
for (const [key, val] of result.primitives) allTokens.set(key, val);
|
|
11560
11704
|
for (const [key, val] of result.semantics) allTokens.set(key, val);
|
|
11561
11705
|
for (const [key, val] of result.components) allTokens.set(key, val);
|
|
11562
|
-
for (const [
|
|
11563
|
-
resolveTokenRef(
|
|
11706
|
+
for (const [path58] of allTokens) {
|
|
11707
|
+
resolveTokenRef(path58, allTokens, /* @__PURE__ */ new Set(), 0, result);
|
|
11564
11708
|
}
|
|
11565
11709
|
}
|
|
11566
|
-
function resolveTokenRef(
|
|
11567
|
-
if (result.resolved.has(
|
|
11568
|
-
return result.resolved.get(
|
|
11710
|
+
function resolveTokenRef(path58, allTokens, visited, depth, result) {
|
|
11711
|
+
if (result.resolved.has(path58)) {
|
|
11712
|
+
return result.resolved.get(path58);
|
|
11569
11713
|
}
|
|
11570
11714
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
11571
11715
|
result.errors.push({
|
|
11572
|
-
message: `Max reference depth exceeded at: ${
|
|
11573
|
-
path:
|
|
11716
|
+
message: `Max reference depth exceeded at: ${path58}`,
|
|
11717
|
+
path: path58
|
|
11574
11718
|
});
|
|
11575
11719
|
return void 0;
|
|
11576
11720
|
}
|
|
11577
|
-
if (visited.has(
|
|
11721
|
+
if (visited.has(path58)) {
|
|
11578
11722
|
result.errors.push({
|
|
11579
|
-
message: `Circular reference detected: ${
|
|
11580
|
-
path:
|
|
11723
|
+
message: `Circular reference detected: ${path58}`,
|
|
11724
|
+
path: path58
|
|
11581
11725
|
});
|
|
11582
11726
|
return void 0;
|
|
11583
11727
|
}
|
|
11584
|
-
const token = allTokens.get(
|
|
11728
|
+
const token = allTokens.get(path58);
|
|
11585
11729
|
if (!token) {
|
|
11586
11730
|
return void 0;
|
|
11587
11731
|
}
|
|
11588
11732
|
if (typeof token.$value !== "string") {
|
|
11589
11733
|
const rawValue2 = stringifyTokenValue(token.$value);
|
|
11590
|
-
result.resolved.set(
|
|
11734
|
+
result.resolved.set(path58, rawValue2);
|
|
11591
11735
|
return rawValue2;
|
|
11592
11736
|
}
|
|
11593
11737
|
const rawValue = stringifyTokenValue(token.$value);
|
|
11594
11738
|
const refs = [...rawValue.matchAll(REF_PATTERN)];
|
|
11595
11739
|
if (refs.length === 0) {
|
|
11596
|
-
result.resolved.set(
|
|
11740
|
+
result.resolved.set(path58, rawValue);
|
|
11597
11741
|
return rawValue;
|
|
11598
11742
|
}
|
|
11599
|
-
visited.add(
|
|
11743
|
+
visited.add(path58);
|
|
11600
11744
|
let resolved = rawValue;
|
|
11601
11745
|
for (const ref of refs) {
|
|
11602
11746
|
const refPath = ref[1];
|
|
@@ -11604,8 +11748,8 @@ function resolveTokenRef(path55, allTokens, visited, depth, result) {
|
|
|
11604
11748
|
const refToken = allTokens.get(refPath);
|
|
11605
11749
|
if (!refToken) {
|
|
11606
11750
|
result.errors.push({
|
|
11607
|
-
message: `Unresolved token reference: {${refPath}} at ${
|
|
11608
|
-
path:
|
|
11751
|
+
message: `Unresolved token reference: {${refPath}} at ${path58}`,
|
|
11752
|
+
path: path58
|
|
11609
11753
|
});
|
|
11610
11754
|
continue;
|
|
11611
11755
|
}
|
|
@@ -11614,7 +11758,7 @@ function resolveTokenRef(path55, allTokens, visited, depth, result) {
|
|
|
11614
11758
|
resolved = resolved.split(`{${refPath}}`).join(refValue);
|
|
11615
11759
|
}
|
|
11616
11760
|
}
|
|
11617
|
-
result.resolved.set(
|
|
11761
|
+
result.resolved.set(path58, resolved);
|
|
11618
11762
|
return resolved;
|
|
11619
11763
|
}
|
|
11620
11764
|
function stringifyTokenValue(value) {
|
|
@@ -11669,8 +11813,8 @@ var VALID_TYPES = [
|
|
|
11669
11813
|
var VALID_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
|
|
11670
11814
|
async function validateDesignToken(root, config) {
|
|
11671
11815
|
const configuredDir = config.uiux?.designTokensDir;
|
|
11672
|
-
const designDir = configuredDir ?
|
|
11673
|
-
const pattern =
|
|
11816
|
+
const designDir = configuredDir ? import_node_path40.default.resolve(root, configuredDir) : import_node_path40.default.join(root, config.paths.contractsDir, "design");
|
|
11817
|
+
const pattern = import_node_path40.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
|
|
11674
11818
|
const files = await (0, import_fast_glob2.default)(pattern, {
|
|
11675
11819
|
absolute: true,
|
|
11676
11820
|
ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
|
|
@@ -11680,11 +11824,11 @@ async function validateDesignToken(root, config) {
|
|
|
11680
11824
|
}
|
|
11681
11825
|
const issues = [];
|
|
11682
11826
|
for (const filePath of files) {
|
|
11683
|
-
const rel =
|
|
11827
|
+
const rel = import_node_path40.default.relative(root, filePath).replace(/\\/g, "/");
|
|
11684
11828
|
let hasRootObjectError = false;
|
|
11685
11829
|
let content;
|
|
11686
11830
|
try {
|
|
11687
|
-
content = await (0,
|
|
11831
|
+
content = await (0, import_promises38.readFile)(filePath, "utf-8");
|
|
11688
11832
|
} catch {
|
|
11689
11833
|
issues.push(
|
|
11690
11834
|
issue(
|
|
@@ -11698,7 +11842,7 @@ async function validateDesignToken(root, config) {
|
|
|
11698
11842
|
continue;
|
|
11699
11843
|
}
|
|
11700
11844
|
try {
|
|
11701
|
-
const parsed = (0,
|
|
11845
|
+
const parsed = (0, import_yaml7.parse)(content);
|
|
11702
11846
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11703
11847
|
hasRootObjectError = true;
|
|
11704
11848
|
issues.push(
|
|
@@ -11847,8 +11991,8 @@ function normalizePlatform(value) {
|
|
|
11847
11991
|
}
|
|
11848
11992
|
|
|
11849
11993
|
// src/core/validators/htmlMock.ts
|
|
11850
|
-
var
|
|
11851
|
-
var
|
|
11994
|
+
var import_promises39 = require("fs/promises");
|
|
11995
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
11852
11996
|
var import_fast_glob3 = __toESM(require("fast-glob"), 1);
|
|
11853
11997
|
|
|
11854
11998
|
// src/core/uiux/contrastRatio.ts
|
|
@@ -12228,19 +12372,19 @@ async function validateHtmlMock(root, platform, config) {
|
|
|
12228
12372
|
const startTime = performance.now();
|
|
12229
12373
|
const budget = config.uiux?.htmlMockTimeout ?? 2e3;
|
|
12230
12374
|
const patterns = [
|
|
12231
|
-
|
|
12232
|
-
|
|
12375
|
+
import_node_path41.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
|
|
12376
|
+
import_node_path41.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
|
|
12233
12377
|
];
|
|
12234
12378
|
const files = await (0, import_fast_glob3.default)(patterns, { absolute: true });
|
|
12235
12379
|
const mockBlocks = [];
|
|
12236
12380
|
for (const filePath of files) {
|
|
12237
12381
|
let content;
|
|
12238
12382
|
try {
|
|
12239
|
-
content = await (0,
|
|
12383
|
+
content = await (0, import_promises39.readFile)(filePath, "utf-8");
|
|
12240
12384
|
} catch {
|
|
12241
12385
|
continue;
|
|
12242
12386
|
}
|
|
12243
|
-
const rel =
|
|
12387
|
+
const rel = import_node_path41.default.relative(root, filePath).replace(/\\/g, "/");
|
|
12244
12388
|
for (const block of collectHtmlMockBlocks(content)) {
|
|
12245
12389
|
mockBlocks.push({ file: rel, html: block.html, rawBlock: block.rawBlock });
|
|
12246
12390
|
}
|
|
@@ -12406,8 +12550,8 @@ async function validateHtmlMock(root, platform, config) {
|
|
|
12406
12550
|
}
|
|
12407
12551
|
|
|
12408
12552
|
// src/core/validators/mermaidScreenFlow.ts
|
|
12409
|
-
var
|
|
12410
|
-
var
|
|
12553
|
+
var import_promises40 = require("fs/promises");
|
|
12554
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
12411
12555
|
var import_fast_glob4 = __toESM(require("fast-glob"), 1);
|
|
12412
12556
|
|
|
12413
12557
|
// src/core/validators/mermaidUtils.ts
|
|
@@ -12458,18 +12602,18 @@ var FLOWCHART_RE = /^\s*flowchart\s+(TD|LR|TB|RL|BT)\b/;
|
|
|
12458
12602
|
async function validateMermaidScreenFlow(root, config) {
|
|
12459
12603
|
const issues = [];
|
|
12460
12604
|
const patterns = [
|
|
12461
|
-
|
|
12462
|
-
|
|
12605
|
+
import_node_path42.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
|
|
12606
|
+
import_node_path42.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
|
|
12463
12607
|
];
|
|
12464
12608
|
const files = await (0, import_fast_glob4.default)(patterns, { absolute: true });
|
|
12465
12609
|
for (const filePath of files) {
|
|
12466
12610
|
let content;
|
|
12467
12611
|
try {
|
|
12468
|
-
content = await (0,
|
|
12612
|
+
content = await (0, import_promises40.readFile)(filePath, "utf-8");
|
|
12469
12613
|
} catch {
|
|
12470
12614
|
continue;
|
|
12471
12615
|
}
|
|
12472
|
-
const rel =
|
|
12616
|
+
const rel = import_node_path42.default.relative(root, filePath).replace(/\\/g, "/");
|
|
12473
12617
|
const blocks = extractFencedCodeBlocks(content);
|
|
12474
12618
|
for (const block of blocks) {
|
|
12475
12619
|
if (block.language !== "mermaid") continue;
|
|
@@ -12567,10 +12711,10 @@ function parseTransitions(content) {
|
|
|
12567
12711
|
}
|
|
12568
12712
|
|
|
12569
12713
|
// src/core/validators/bpApDb.ts
|
|
12570
|
-
var
|
|
12571
|
-
var
|
|
12714
|
+
var import_promises41 = require("fs/promises");
|
|
12715
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
12572
12716
|
var import_fast_glob5 = __toESM(require("fast-glob"), 1);
|
|
12573
|
-
var
|
|
12717
|
+
var import_yaml8 = require("yaml");
|
|
12574
12718
|
var BP_ID_RE = /^BP-\d{4}$/;
|
|
12575
12719
|
var AP_ID_RE = /^AP-\d{4}$/;
|
|
12576
12720
|
var VALID_SEVERITIES = ["critical", "major", "minor"];
|
|
@@ -12605,9 +12749,9 @@ var AP_REQUIRED_FIELDS = [
|
|
|
12605
12749
|
];
|
|
12606
12750
|
async function validateBpApDb(root, config) {
|
|
12607
12751
|
const issues = [];
|
|
12608
|
-
const designDir =
|
|
12609
|
-
const bpPattern =
|
|
12610
|
-
const apPattern =
|
|
12752
|
+
const designDir = import_node_path43.default.join(root, config.paths.contractsDir, "design");
|
|
12753
|
+
const bpPattern = import_node_path43.default.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
|
|
12754
|
+
const apPattern = import_node_path43.default.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
|
|
12611
12755
|
const globOptions = {
|
|
12612
12756
|
absolute: true,
|
|
12613
12757
|
ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
|
|
@@ -12620,14 +12764,14 @@ async function validateBpApDb(root, config) {
|
|
|
12620
12764
|
const seenBpIds = /* @__PURE__ */ new Set();
|
|
12621
12765
|
const seenApIds = /* @__PURE__ */ new Set();
|
|
12622
12766
|
for (const filePath of bpFiles) {
|
|
12623
|
-
const rel =
|
|
12767
|
+
const rel = import_node_path43.default.relative(root, filePath).replace(/\\/g, "/");
|
|
12624
12768
|
const entries = await parseRuleFile(filePath, rel, issues);
|
|
12625
12769
|
for (const entry of entries) {
|
|
12626
12770
|
validateBpEntry(entry, rel, seenBpIds, issues);
|
|
12627
12771
|
}
|
|
12628
12772
|
}
|
|
12629
12773
|
for (const filePath of apFiles) {
|
|
12630
|
-
const rel =
|
|
12774
|
+
const rel = import_node_path43.default.relative(root, filePath).replace(/\\/g, "/");
|
|
12631
12775
|
const entries = await parseRuleFile(filePath, rel, issues);
|
|
12632
12776
|
for (const entry of entries) {
|
|
12633
12777
|
validateApEntry(entry, rel, seenApIds, issues);
|
|
@@ -12638,7 +12782,7 @@ async function validateBpApDb(root, config) {
|
|
|
12638
12782
|
async function parseRuleFile(filePath, rel, issues) {
|
|
12639
12783
|
let content;
|
|
12640
12784
|
try {
|
|
12641
|
-
content = await (0,
|
|
12785
|
+
content = await (0, import_promises41.readFile)(filePath, "utf-8");
|
|
12642
12786
|
} catch {
|
|
12643
12787
|
issues.push(
|
|
12644
12788
|
issue("QFAI-BPAP-001", `BP/AP file unreadable: ${rel}`, "error", rel, "bpApDb.readFile")
|
|
@@ -12647,7 +12791,7 @@ async function parseRuleFile(filePath, rel, issues) {
|
|
|
12647
12791
|
}
|
|
12648
12792
|
let parsed;
|
|
12649
12793
|
try {
|
|
12650
|
-
parsed = (0,
|
|
12794
|
+
parsed = (0, import_yaml8.parse)(content);
|
|
12651
12795
|
} catch (error) {
|
|
12652
12796
|
issues.push(
|
|
12653
12797
|
issue(
|
|
@@ -12802,8 +12946,8 @@ function toSafeString(value) {
|
|
|
12802
12946
|
}
|
|
12803
12947
|
|
|
12804
12948
|
// src/core/validators/platformDetection.ts
|
|
12805
|
-
var
|
|
12806
|
-
var
|
|
12949
|
+
var import_promises42 = require("fs/promises");
|
|
12950
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
12807
12951
|
var KNOWN_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
|
|
12808
12952
|
async function detectPlatform(root, config, cliPlatform) {
|
|
12809
12953
|
const issues = [];
|
|
@@ -12846,13 +12990,13 @@ async function detectPlatform(root, config, cliPlatform) {
|
|
|
12846
12990
|
return { platform: "web", source: "fallback", issues };
|
|
12847
12991
|
}
|
|
12848
12992
|
async function inferPlatform(root, issues) {
|
|
12849
|
-
if (await exists5(
|
|
12850
|
-
const hasAndroid = await exists5(
|
|
12851
|
-
const hasIos = await exists5(
|
|
12852
|
-
const hasWeb = await exists5(
|
|
12853
|
-
const hasWindows = await exists5(
|
|
12854
|
-
const hasMacos = await exists5(
|
|
12855
|
-
const hasLinux = await exists5(
|
|
12993
|
+
if (await exists5(import_node_path44.default.join(root, "pubspec.yaml"))) {
|
|
12994
|
+
const hasAndroid = await exists5(import_node_path44.default.join(root, "android"));
|
|
12995
|
+
const hasIos = await exists5(import_node_path44.default.join(root, "ios"));
|
|
12996
|
+
const hasWeb = await exists5(import_node_path44.default.join(root, "web"));
|
|
12997
|
+
const hasWindows = await exists5(import_node_path44.default.join(root, "windows"));
|
|
12998
|
+
const hasMacos = await exists5(import_node_path44.default.join(root, "macos"));
|
|
12999
|
+
const hasLinux = await exists5(import_node_path44.default.join(root, "linux"));
|
|
12856
13000
|
const mobileTargets = [hasAndroid, hasIos].filter(Boolean).length;
|
|
12857
13001
|
const desktopTargets = [hasWeb, hasWindows, hasMacos, hasLinux].filter(Boolean).length;
|
|
12858
13002
|
if (mobileTargets + desktopTargets > 1) {
|
|
@@ -12872,10 +13016,10 @@ async function inferPlatform(root, issues) {
|
|
|
12872
13016
|
}
|
|
12873
13017
|
return null;
|
|
12874
13018
|
}
|
|
12875
|
-
const pkgJsonPath =
|
|
13019
|
+
const pkgJsonPath = import_node_path44.default.join(root, "package.json");
|
|
12876
13020
|
if (await exists5(pkgJsonPath)) {
|
|
12877
13021
|
try {
|
|
12878
|
-
const raw = await (0,
|
|
13022
|
+
const raw = await (0, import_promises42.readFile)(pkgJsonPath, "utf-8");
|
|
12879
13023
|
const pkg = JSON.parse(raw);
|
|
12880
13024
|
const deps = {
|
|
12881
13025
|
...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
|
|
@@ -12894,8 +13038,8 @@ async function inferPlatform(root, issues) {
|
|
|
12894
13038
|
return "cross-platform";
|
|
12895
13039
|
}
|
|
12896
13040
|
if ("react-native" in deps) {
|
|
12897
|
-
const hasAndroid = await exists5(
|
|
12898
|
-
const hasIos = await exists5(
|
|
13041
|
+
const hasAndroid = await exists5(import_node_path44.default.join(root, "android"));
|
|
13042
|
+
const hasIos = await exists5(import_node_path44.default.join(root, "ios"));
|
|
12899
13043
|
if (hasAndroid && hasIos) {
|
|
12900
13044
|
return "cross-platform";
|
|
12901
13045
|
}
|
|
@@ -12917,15 +13061,15 @@ function normalizePlatformInput(platform) {
|
|
|
12917
13061
|
}
|
|
12918
13062
|
|
|
12919
13063
|
// src/core/validators/uiDefinitionConsistency.ts
|
|
12920
|
-
var
|
|
12921
|
-
var
|
|
13064
|
+
var import_promises43 = require("fs/promises");
|
|
13065
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
12922
13066
|
var import_fast_glob6 = __toESM(require("fast-glob"), 1);
|
|
12923
|
-
var
|
|
13067
|
+
var import_yaml9 = require("yaml");
|
|
12924
13068
|
async function validateUiDefinitionConsistency(root, config) {
|
|
12925
13069
|
const issues = [];
|
|
12926
13070
|
const configuredDir = config.uiux?.designTokensDir;
|
|
12927
|
-
const designDir = configuredDir ?
|
|
12928
|
-
const tokenPattern =
|
|
13071
|
+
const designDir = configuredDir ? import_node_path45.default.resolve(root, configuredDir) : import_node_path45.default.join(root, config.paths.contractsDir, "design");
|
|
13072
|
+
const tokenPattern = import_node_path45.default.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
|
|
12929
13073
|
const tokenFiles = await (0, import_fast_glob6.default)(tokenPattern, {
|
|
12930
13074
|
absolute: true,
|
|
12931
13075
|
ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
|
|
@@ -12933,7 +13077,7 @@ async function validateUiDefinitionConsistency(root, config) {
|
|
|
12933
13077
|
const resolvedTokens = /* @__PURE__ */ new Map();
|
|
12934
13078
|
for (const tokenFile of tokenFiles) {
|
|
12935
13079
|
try {
|
|
12936
|
-
const content = await (0,
|
|
13080
|
+
const content = await (0, import_promises43.readFile)(tokenFile, "utf-8");
|
|
12937
13081
|
const result = parseDesignToken(content);
|
|
12938
13082
|
for (const [key, val] of result.resolved) {
|
|
12939
13083
|
resolvedTokens.set(key, val);
|
|
@@ -12941,14 +13085,14 @@ async function validateUiDefinitionConsistency(root, config) {
|
|
|
12941
13085
|
} catch {
|
|
12942
13086
|
}
|
|
12943
13087
|
}
|
|
12944
|
-
const uiContractDir =
|
|
12945
|
-
const uiPattern =
|
|
13088
|
+
const uiContractDir = import_node_path45.default.join(root, config.paths.contractsDir, "ui");
|
|
13089
|
+
const uiPattern = import_node_path45.default.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
|
|
12946
13090
|
const uiFiles = await (0, import_fast_glob6.default)(uiPattern, { absolute: true });
|
|
12947
13091
|
const contractScreenIds = /* @__PURE__ */ new Set();
|
|
12948
13092
|
for (const uiFile of uiFiles) {
|
|
12949
13093
|
try {
|
|
12950
|
-
const content = await (0,
|
|
12951
|
-
const parsed = (0,
|
|
13094
|
+
const content = await (0, import_promises43.readFile)(uiFile, "utf-8");
|
|
13095
|
+
const parsed = (0, import_yaml9.parse)(content);
|
|
12952
13096
|
if (parsed && typeof parsed === "object") {
|
|
12953
13097
|
const screens = parsed.screens;
|
|
12954
13098
|
if (Array.isArray(screens)) {
|
|
@@ -12963,15 +13107,15 @@ async function validateUiDefinitionConsistency(root, config) {
|
|
|
12963
13107
|
}
|
|
12964
13108
|
}
|
|
12965
13109
|
const mdPatterns = [
|
|
12966
|
-
|
|
12967
|
-
|
|
13110
|
+
import_node_path45.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
|
|
13111
|
+
import_node_path45.default.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
|
|
12968
13112
|
];
|
|
12969
13113
|
const mdFiles = await (0, import_fast_glob6.default)(mdPatterns, { absolute: true });
|
|
12970
13114
|
const mockScreenIds = /* @__PURE__ */ new Set();
|
|
12971
13115
|
for (const mdFile of mdFiles) {
|
|
12972
13116
|
try {
|
|
12973
|
-
const content = await (0,
|
|
12974
|
-
const rel =
|
|
13117
|
+
const content = await (0, import_promises43.readFile)(mdFile, "utf-8");
|
|
13118
|
+
const rel = import_node_path45.default.relative(root, mdFile).replace(/\\/g, "/");
|
|
12975
13119
|
const htmlBlocks = collectHtmlMockBlocks(content);
|
|
12976
13120
|
if (resolvedTokens.size > 0) {
|
|
12977
13121
|
for (const htmlBlock of htmlBlocks) {
|
|
@@ -13028,8 +13172,8 @@ async function validateUiDefinitionConsistency(root, config) {
|
|
|
13028
13172
|
}
|
|
13029
13173
|
|
|
13030
13174
|
// src/core/validators/researchSummary.ts
|
|
13031
|
-
var
|
|
13032
|
-
var
|
|
13175
|
+
var import_promises44 = require("fs/promises");
|
|
13176
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
13033
13177
|
var import_fast_glob7 = __toESM(require("fast-glob"), 1);
|
|
13034
13178
|
var RESEARCH_SUMMARY_HEADING_RE = /^#{1,3}\s+Research\s+Summary/im;
|
|
13035
13179
|
var SOURCE_ENTRY_RE = /^\s*-\s*id:\s*(\S+)/gm;
|
|
@@ -13037,17 +13181,17 @@ var REFLECTION_APPLY_RE = /action:\s*apply/i;
|
|
|
13037
13181
|
var FULL_DATE_RE = /^\s+published:\s*["']?(\d{4}-\d{2}-\d{2})["']?/m;
|
|
13038
13182
|
async function validateResearchSummary(root, config) {
|
|
13039
13183
|
const issues = [];
|
|
13040
|
-
const pattern =
|
|
13184
|
+
const pattern = import_node_path46.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
|
|
13041
13185
|
const files = await (0, import_fast_glob7.default)(pattern, { absolute: true });
|
|
13042
13186
|
for (const filePath of files) {
|
|
13043
13187
|
let content;
|
|
13044
13188
|
try {
|
|
13045
|
-
content = await (0,
|
|
13189
|
+
content = await (0, import_promises44.readFile)(filePath, "utf-8");
|
|
13046
13190
|
} catch {
|
|
13047
13191
|
continue;
|
|
13048
13192
|
}
|
|
13049
13193
|
if (!RESEARCH_SUMMARY_HEADING_RE.test(content)) continue;
|
|
13050
|
-
const rel =
|
|
13194
|
+
const rel = import_node_path46.default.relative(root, filePath).replace(/\\/g, "/");
|
|
13051
13195
|
const section = extractResearchSummarySection(content);
|
|
13052
13196
|
if (!section) continue;
|
|
13053
13197
|
const sourceEntries = extractSourceEntries(section);
|
|
@@ -13278,9 +13422,9 @@ function resolveFreshnessReferenceNow() {
|
|
|
13278
13422
|
}
|
|
13279
13423
|
|
|
13280
13424
|
// src/core/validators/agentDefinition.ts
|
|
13281
|
-
var
|
|
13282
|
-
var
|
|
13283
|
-
var
|
|
13425
|
+
var import_promises45 = require("fs/promises");
|
|
13426
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
13427
|
+
var import_yaml10 = require("yaml");
|
|
13284
13428
|
var REQUIRED_AGENTS = [
|
|
13285
13429
|
"uiux-expert.md",
|
|
13286
13430
|
"design-expert.md",
|
|
@@ -13299,13 +13443,13 @@ var REQUIRED_SECTIONS = [
|
|
|
13299
13443
|
var REQUIRED_PHASES = ["discussion", "SDD", "prototyping", "ATDD"];
|
|
13300
13444
|
async function validateAgentDefinition(root, _config) {
|
|
13301
13445
|
const issues = [];
|
|
13302
|
-
const agentsDir =
|
|
13446
|
+
const agentsDir = import_node_path47.default.join(root, ".qfai", "assistant", "agents");
|
|
13303
13447
|
if (!await exists5(agentsDir)) {
|
|
13304
13448
|
return [];
|
|
13305
13449
|
}
|
|
13306
13450
|
let anyAgentExists = false;
|
|
13307
13451
|
for (const agentFile of REQUIRED_AGENTS) {
|
|
13308
|
-
if (await exists5(
|
|
13452
|
+
if (await exists5(import_node_path47.default.join(agentsDir, agentFile))) {
|
|
13309
13453
|
anyAgentExists = true;
|
|
13310
13454
|
break;
|
|
13311
13455
|
}
|
|
@@ -13314,7 +13458,7 @@ async function validateAgentDefinition(root, _config) {
|
|
|
13314
13458
|
return [];
|
|
13315
13459
|
}
|
|
13316
13460
|
for (const agentFile of REQUIRED_AGENTS) {
|
|
13317
|
-
const filePath =
|
|
13461
|
+
const filePath = import_node_path47.default.join(agentsDir, agentFile);
|
|
13318
13462
|
const rel = `.qfai/assistant/agents/${agentFile}`;
|
|
13319
13463
|
if (!await exists5(filePath)) {
|
|
13320
13464
|
issues.push(
|
|
@@ -13330,7 +13474,7 @@ async function validateAgentDefinition(root, _config) {
|
|
|
13330
13474
|
}
|
|
13331
13475
|
let content;
|
|
13332
13476
|
try {
|
|
13333
|
-
content = await (0,
|
|
13477
|
+
content = await (0, import_promises45.readFile)(filePath, "utf-8");
|
|
13334
13478
|
} catch {
|
|
13335
13479
|
issues.push(
|
|
13336
13480
|
issue(
|
|
@@ -13436,11 +13580,11 @@ async function validateAgentDefinition(root, _config) {
|
|
|
13436
13580
|
}
|
|
13437
13581
|
}
|
|
13438
13582
|
}
|
|
13439
|
-
const rosterPath =
|
|
13583
|
+
const rosterPath = import_node_path47.default.join(root, ".qfai", "assistant", "steering", "review-roster.yml");
|
|
13440
13584
|
if (await exists5(rosterPath)) {
|
|
13441
13585
|
try {
|
|
13442
|
-
const rosterContent = await (0,
|
|
13443
|
-
const roster = (0,
|
|
13586
|
+
const rosterContent = await (0, import_promises45.readFile)(rosterPath, "utf-8");
|
|
13587
|
+
const roster = (0, import_yaml10.parse)(rosterContent);
|
|
13444
13588
|
if (roster && typeof roster === "object") {
|
|
13445
13589
|
const rosterObj = roster;
|
|
13446
13590
|
const entries = Array.isArray(rosterObj.roster) ? rosterObj.roster : [];
|
|
@@ -13484,8 +13628,8 @@ function extractPhaseBody(phaseSection, phaseName) {
|
|
|
13484
13628
|
}
|
|
13485
13629
|
|
|
13486
13630
|
// src/core/validators/tddList.ts
|
|
13487
|
-
var
|
|
13488
|
-
var
|
|
13631
|
+
var import_promises46 = require("fs/promises");
|
|
13632
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
13489
13633
|
var REQUIRED_COLUMNS = [
|
|
13490
13634
|
"TDD-ID",
|
|
13491
13635
|
"TC-Refs",
|
|
@@ -13499,7 +13643,7 @@ var REQUIRED_COLUMNS = [
|
|
|
13499
13643
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["todo", "red", "green", "refactor", "done", "exception"]);
|
|
13500
13644
|
var TEST_FILE_CHECK_STATUSES = /* @__PURE__ */ new Set(["green", "refactor", "done"]);
|
|
13501
13645
|
var TDD_ID_FORMAT = /^TDD-\d{4}$/;
|
|
13502
|
-
var TDD_LIST_REL_PATH =
|
|
13646
|
+
var TDD_LIST_REL_PATH = import_node_path48.default.join("tdd", "test-list.md");
|
|
13503
13647
|
async function validateTddList(root, config) {
|
|
13504
13648
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
13505
13649
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -13511,8 +13655,8 @@ async function validateTddList(root, config) {
|
|
|
13511
13655
|
return issues;
|
|
13512
13656
|
}
|
|
13513
13657
|
async function validateSpecTddList(root, specDir, specNumber) {
|
|
13514
|
-
const filePath =
|
|
13515
|
-
const relPath =
|
|
13658
|
+
const filePath = import_node_path48.default.join(specDir, TDD_LIST_REL_PATH);
|
|
13659
|
+
const relPath = import_node_path48.default.relative(root, filePath).replace(/\\/g, "/");
|
|
13516
13660
|
const issues = [];
|
|
13517
13661
|
if (!await exists5(filePath)) {
|
|
13518
13662
|
issues.push(
|
|
@@ -13699,9 +13843,9 @@ async function validateSpecTddList(root, specDir, specNumber) {
|
|
|
13699
13843
|
continue;
|
|
13700
13844
|
}
|
|
13701
13845
|
const normalized = testFile.replace(/\\/g, "/");
|
|
13702
|
-
const resolved =
|
|
13703
|
-
const relative =
|
|
13704
|
-
if (
|
|
13846
|
+
const resolved = import_node_path48.default.resolve(root, normalized);
|
|
13847
|
+
const relative = import_node_path48.default.relative(root, resolved);
|
|
13848
|
+
if (import_node_path48.default.isAbsolute(normalized) || import_node_path48.default.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + import_node_path48.default.sep)) {
|
|
13705
13849
|
issues.push(
|
|
13706
13850
|
issue(
|
|
13707
13851
|
"TDDLIST_TEST_FILE_MISSING",
|
|
@@ -13715,7 +13859,7 @@ async function validateSpecTddList(root, specDir, specNumber) {
|
|
|
13715
13859
|
}
|
|
13716
13860
|
let isFile2 = false;
|
|
13717
13861
|
try {
|
|
13718
|
-
isFile2 = (await (0,
|
|
13862
|
+
isFile2 = (await (0, import_promises46.stat)(resolved)).isFile();
|
|
13719
13863
|
} catch {
|
|
13720
13864
|
}
|
|
13721
13865
|
if (!isFile2) {
|
|
@@ -13764,11 +13908,11 @@ async function validateSpecTddList(root, specDir, specNumber) {
|
|
|
13764
13908
|
}
|
|
13765
13909
|
async function collectTestCaseIds(specDir) {
|
|
13766
13910
|
const empty = { knownTcIds: /* @__PURE__ */ new Set(), unitComponentTcIds: /* @__PURE__ */ new Set() };
|
|
13767
|
-
const testCasesPath =
|
|
13911
|
+
const testCasesPath = import_node_path48.default.join(specDir, "06_Test-Cases.md");
|
|
13768
13912
|
if (!await exists5(testCasesPath)) return empty;
|
|
13769
13913
|
let content;
|
|
13770
13914
|
try {
|
|
13771
|
-
content = await (0,
|
|
13915
|
+
content = await (0, import_promises46.readFile)(testCasesPath, "utf-8");
|
|
13772
13916
|
} catch {
|
|
13773
13917
|
return empty;
|
|
13774
13918
|
}
|
|
@@ -13794,8 +13938,8 @@ async function collectTestCaseIds(specDir) {
|
|
|
13794
13938
|
}
|
|
13795
13939
|
|
|
13796
13940
|
// src/core/validators/ddpValidation.ts
|
|
13797
|
-
var
|
|
13798
|
-
var
|
|
13941
|
+
var import_promises47 = require("fs/promises");
|
|
13942
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
13799
13943
|
var import_node_url3 = require("url");
|
|
13800
13944
|
var import_fast_glob8 = __toESM(require("fast-glob"), 1);
|
|
13801
13945
|
var DDP_HEADING_RE = /^#{1,3}\s+Design\s+Direction\s+Pack/im;
|
|
@@ -13813,9 +13957,9 @@ var _bannedPatterns = null;
|
|
|
13813
13957
|
async function loadBannedPatterns() {
|
|
13814
13958
|
if (_bannedPatterns !== null) return _bannedPatterns;
|
|
13815
13959
|
try {
|
|
13816
|
-
const thisDir =
|
|
13817
|
-
const filePath =
|
|
13818
|
-
const content = await (0,
|
|
13960
|
+
const thisDir = import_node_path49.default.dirname((0, import_node_url3.fileURLToPath)(__filename));
|
|
13961
|
+
const filePath = import_node_path49.default.join(thisDir, "ddpBannedPatterns.txt");
|
|
13962
|
+
const content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
13819
13963
|
_bannedPatterns = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
13820
13964
|
} catch {
|
|
13821
13965
|
_bannedPatterns = [];
|
|
@@ -13824,15 +13968,15 @@ async function loadBannedPatterns() {
|
|
|
13824
13968
|
}
|
|
13825
13969
|
async function validateDdpFields(root, config) {
|
|
13826
13970
|
const issues = [];
|
|
13827
|
-
const pattern =
|
|
13971
|
+
const pattern = import_node_path49.default.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
|
|
13828
13972
|
const files = await (0, import_fast_glob8.default)(pattern, { absolute: true });
|
|
13829
13973
|
let ddpFound = false;
|
|
13830
13974
|
let isUiBearing2 = false;
|
|
13831
13975
|
for (const filePath of files) {
|
|
13832
|
-
const basename =
|
|
13976
|
+
const basename = import_node_path49.default.basename(filePath);
|
|
13833
13977
|
if (basename === "03_Story-Workshop.md") {
|
|
13834
13978
|
try {
|
|
13835
|
-
const storyContent = await (0,
|
|
13979
|
+
const storyContent = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
13836
13980
|
if (UI_BEARING_KEYWORDS_RE.test(storyContent)) {
|
|
13837
13981
|
isUiBearing2 = true;
|
|
13838
13982
|
}
|
|
@@ -13843,13 +13987,13 @@ async function validateDdpFields(root, config) {
|
|
|
13843
13987
|
for (const filePath of files) {
|
|
13844
13988
|
let content;
|
|
13845
13989
|
try {
|
|
13846
|
-
content = await (0,
|
|
13990
|
+
content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
13847
13991
|
} catch {
|
|
13848
13992
|
continue;
|
|
13849
13993
|
}
|
|
13850
13994
|
if (!DDP_HEADING_RE.test(content)) continue;
|
|
13851
13995
|
ddpFound = true;
|
|
13852
|
-
const rel =
|
|
13996
|
+
const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
|
|
13853
13997
|
const section = extractDdpSection(content);
|
|
13854
13998
|
if (!section) continue;
|
|
13855
13999
|
for (const field of DDP_REQUIRED_FIELDS) {
|
|
@@ -14042,7 +14186,7 @@ var ANTI_PATTERN_CHECKS = [
|
|
|
14042
14186
|
];
|
|
14043
14187
|
async function validateResearchTraceability(root, config, issues) {
|
|
14044
14188
|
const requireResearchSummary = config.uiux?.requireResearchSummary === true;
|
|
14045
|
-
const discussionPattern =
|
|
14189
|
+
const discussionPattern = import_node_path49.default.posix.join(
|
|
14046
14190
|
root.replace(/\\/g, "/"),
|
|
14047
14191
|
config.paths.discussionDir,
|
|
14048
14192
|
"**/*.md"
|
|
@@ -14051,7 +14195,7 @@ async function validateResearchTraceability(root, config, issues) {
|
|
|
14051
14195
|
let hasResearchSummary = false;
|
|
14052
14196
|
for (const filePath of discussionFiles) {
|
|
14053
14197
|
try {
|
|
14054
|
-
const content = await (0,
|
|
14198
|
+
const content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
14055
14199
|
if (/research_summary\s*:/m.test(content)) {
|
|
14056
14200
|
hasResearchSummary = true;
|
|
14057
14201
|
break;
|
|
@@ -14070,7 +14214,7 @@ async function validateResearchTraceability(root, config, issues) {
|
|
|
14070
14214
|
)
|
|
14071
14215
|
);
|
|
14072
14216
|
}
|
|
14073
|
-
const contractPattern =
|
|
14217
|
+
const contractPattern = import_node_path49.default.posix.join(
|
|
14074
14218
|
root.replace(/\\/g, "/"),
|
|
14075
14219
|
config.paths.contractsDir,
|
|
14076
14220
|
"design",
|
|
@@ -14080,11 +14224,11 @@ async function validateResearchTraceability(root, config, issues) {
|
|
|
14080
14224
|
for (const filePath of contractFiles) {
|
|
14081
14225
|
let content;
|
|
14082
14226
|
try {
|
|
14083
|
-
content = await (0,
|
|
14227
|
+
content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
14084
14228
|
} catch {
|
|
14085
14229
|
continue;
|
|
14086
14230
|
}
|
|
14087
|
-
const rel =
|
|
14231
|
+
const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
|
|
14088
14232
|
const ruleBlocks = content.split(/(?=^-\s+(?:rule|id)\s*:)/m).filter((b) => b.trim());
|
|
14089
14233
|
for (const block of ruleBlocks) {
|
|
14090
14234
|
const idMatch = /(?:^|\n)\s*(?:-\s+)?id\s*:\s*(\S+)/m.exec(block);
|
|
@@ -14107,11 +14251,11 @@ async function validateStoryWorkshopTemplates(root, config, issues, discussionFi
|
|
|
14107
14251
|
for (const filePath of discussionFiles) {
|
|
14108
14252
|
let content;
|
|
14109
14253
|
try {
|
|
14110
|
-
content = await (0,
|
|
14254
|
+
content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
14111
14255
|
} catch {
|
|
14112
14256
|
continue;
|
|
14113
14257
|
}
|
|
14114
|
-
const rel =
|
|
14258
|
+
const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
|
|
14115
14259
|
if (/screen_type\s*:\s*list/im.test(content)) {
|
|
14116
14260
|
for (const field of LIST_TEMPLATE_REQUIRED_FIELDS) {
|
|
14117
14261
|
if (!hasNonEmptyField(content, field)) {
|
|
@@ -14211,7 +14355,7 @@ function validateQualityProfile(config, issues) {
|
|
|
14211
14355
|
}
|
|
14212
14356
|
}
|
|
14213
14357
|
async function validateOptionComparison(root, config, issues) {
|
|
14214
|
-
const contractPattern =
|
|
14358
|
+
const contractPattern = import_node_path49.default.posix.join(
|
|
14215
14359
|
root.replace(/\\/g, "/"),
|
|
14216
14360
|
config.paths.contractsDir,
|
|
14217
14361
|
"design",
|
|
@@ -14221,11 +14365,11 @@ async function validateOptionComparison(root, config, issues) {
|
|
|
14221
14365
|
for (const filePath of contractFiles) {
|
|
14222
14366
|
let content;
|
|
14223
14367
|
try {
|
|
14224
|
-
content = await (0,
|
|
14368
|
+
content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
14225
14369
|
} catch {
|
|
14226
14370
|
continue;
|
|
14227
14371
|
}
|
|
14228
|
-
const rel =
|
|
14372
|
+
const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
|
|
14229
14373
|
const optionMatches = content.match(/(?:^|\n)\s*-\s+option\s*:/gm) ?? [];
|
|
14230
14374
|
const numberedOptions = content.match(/(?:^|\n)\s*option_\d+\s*:/gm) ?? [];
|
|
14231
14375
|
const totalOptions = optionMatches.length + numberedOptions.length;
|
|
@@ -14260,7 +14404,7 @@ async function validateOptionComparison(root, config, issues) {
|
|
|
14260
14404
|
}
|
|
14261
14405
|
async function validateCompetitiveRefs(root, config, issues) {
|
|
14262
14406
|
const minRefs = config.uiux?.competitive_refs_min ?? 3;
|
|
14263
|
-
const contractPattern =
|
|
14407
|
+
const contractPattern = import_node_path49.default.posix.join(
|
|
14264
14408
|
root.replace(/\\/g, "/"),
|
|
14265
14409
|
config.paths.contractsDir,
|
|
14266
14410
|
"design",
|
|
@@ -14270,11 +14414,11 @@ async function validateCompetitiveRefs(root, config, issues) {
|
|
|
14270
14414
|
for (const filePath of contractFiles) {
|
|
14271
14415
|
let content;
|
|
14272
14416
|
try {
|
|
14273
|
-
content = await (0,
|
|
14417
|
+
content = await (0, import_promises47.readFile)(filePath, "utf-8");
|
|
14274
14418
|
} catch {
|
|
14275
14419
|
continue;
|
|
14276
14420
|
}
|
|
14277
|
-
const rel =
|
|
14421
|
+
const rel = import_node_path49.default.relative(root, filePath).replace(/\\/g, "/");
|
|
14278
14422
|
const refsBlock = extractNestedBlock(content, "competitive_refs");
|
|
14279
14423
|
if (refsBlock !== null) {
|
|
14280
14424
|
const refItems = refsBlock.split(/\n/).filter((l) => /^\s*-\s/.test(l));
|
|
@@ -14424,7 +14568,7 @@ function collectListItems(section, field) {
|
|
|
14424
14568
|
}
|
|
14425
14569
|
|
|
14426
14570
|
// src/core/validators/navigationFlow.ts
|
|
14427
|
-
var
|
|
14571
|
+
var import_promises48 = require("fs/promises");
|
|
14428
14572
|
var NODE_DEF_RE = /([A-Za-z_][\w-]*)\s*(?:\[.*?\]|\(.*?\)|\{.*?\})/g;
|
|
14429
14573
|
var EDGE_RE = /([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?\s*(?:--+>|==+>|-.->|~~>)\s*(?:\|"?([^"|]*)"?\|)?\s*([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?/g;
|
|
14430
14574
|
var SUBGRAPH_RE = /^\s*subgraph\s+([\w-]+)/gm;
|
|
@@ -14703,7 +14847,7 @@ async function validateNavigationFlow(root, config) {
|
|
|
14703
14847
|
const specFiles = allFiles.filter((f) => /[\\/]spec-\d{4}[\\/]/.test(f));
|
|
14704
14848
|
const issues = [];
|
|
14705
14849
|
for (const file of specFiles) {
|
|
14706
|
-
const content = await (0,
|
|
14850
|
+
const content = await (0, import_promises48.readFile)(file, "utf-8");
|
|
14707
14851
|
const blocks = extractFencedCodeBlocks(content);
|
|
14708
14852
|
const mermaidBlocks = blocks.filter((b) => b.language === "mermaid");
|
|
14709
14853
|
const _flowchartBlocks = mermaidBlocks.filter((b) => hasFlowchartDeclaration(b.content));
|
|
@@ -14725,8 +14869,8 @@ async function validateNavigationFlow(root, config) {
|
|
|
14725
14869
|
}
|
|
14726
14870
|
|
|
14727
14871
|
// src/core/validators/renderCritique.ts
|
|
14728
|
-
var
|
|
14729
|
-
var
|
|
14872
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
14873
|
+
var import_promises49 = require("fs/promises");
|
|
14730
14874
|
var import_fast_glob9 = __toESM(require("fast-glob"), 1);
|
|
14731
14875
|
var RENDERED_KEYWORDS_RE = /\b(rendered|screenshot|html\b|preview|visual\s*review)/i;
|
|
14732
14876
|
var DDP_REFERENCE_RE = /\b(ddp|design\s*direction\s*pack)\b/i;
|
|
@@ -14745,8 +14889,8 @@ var FOUR_STATE_CHECK_RE = /\bfour_state_check\s*:/i;
|
|
|
14745
14889
|
var MAX_PRIMARY_STEPS_RE = /\bmax_primary_steps\s*:\s*(\d+)/i;
|
|
14746
14890
|
async function validateRenderCritique(root, config) {
|
|
14747
14891
|
const issues = [];
|
|
14748
|
-
const discussionDir =
|
|
14749
|
-
const discussionFiles = await (0, import_fast_glob9.default)(
|
|
14892
|
+
const discussionDir = import_node_path50.default.join(root, config.paths.discussionDir).replace(/\\/g, "/");
|
|
14893
|
+
const discussionFiles = await (0, import_fast_glob9.default)(import_node_path50.default.posix.join(discussionDir, "**/*.md"), { absolute: true });
|
|
14750
14894
|
let hasDdp = false;
|
|
14751
14895
|
for (const df of discussionFiles) {
|
|
14752
14896
|
const content = await readSafe(df);
|
|
@@ -14756,12 +14900,12 @@ async function validateRenderCritique(root, config) {
|
|
|
14756
14900
|
}
|
|
14757
14901
|
}
|
|
14758
14902
|
if (!hasDdp) return issues;
|
|
14759
|
-
const skillsDir =
|
|
14760
|
-
const evidenceDir =
|
|
14903
|
+
const skillsDir = import_node_path50.default.join(root, config.paths.skillsDir).replace(/\\/g, "/");
|
|
14904
|
+
const evidenceDir = import_node_path50.default.join(root, ".qfai", "evidence").replace(/\\/g, "/");
|
|
14761
14905
|
const renderEvidenceViewports = await collectRenderEvidenceViewports(root);
|
|
14762
|
-
const skillPromptPattern =
|
|
14906
|
+
const skillPromptPattern = import_node_path50.default.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
|
|
14763
14907
|
const skillFiles = await (0, import_fast_glob9.default)(skillPromptPattern, { dot: true });
|
|
14764
|
-
const evidencePattern =
|
|
14908
|
+
const evidencePattern = import_node_path50.default.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
|
|
14765
14909
|
const evidenceFiles = await (0, import_fast_glob9.default)(evidencePattern, { dot: true });
|
|
14766
14910
|
for (const sf of skillFiles) {
|
|
14767
14911
|
const content = await readSafe(sf);
|
|
@@ -14769,7 +14913,7 @@ async function validateRenderCritique(root, config) {
|
|
|
14769
14913
|
issues.push(
|
|
14770
14914
|
issue(
|
|
14771
14915
|
"QFAI-CRIT-001",
|
|
14772
|
-
`Skill prompt does not mention rendered/screenshot/HTML review: ${
|
|
14916
|
+
`Skill prompt does not mention rendered/screenshot/HTML review: ${import_node_path50.default.relative(root, sf)}`,
|
|
14773
14917
|
"error",
|
|
14774
14918
|
sf,
|
|
14775
14919
|
"renderCritique.codeOnly",
|
|
@@ -14786,7 +14930,7 @@ async function validateRenderCritique(root, config) {
|
|
|
14786
14930
|
issues.push(
|
|
14787
14931
|
issue(
|
|
14788
14932
|
"QFAI-CRIT-002",
|
|
14789
|
-
`Downstream skill prompt missing DDP reference: ${
|
|
14933
|
+
`Downstream skill prompt missing DDP reference: ${import_node_path50.default.relative(root, sf)}`,
|
|
14790
14934
|
"error",
|
|
14791
14935
|
sf,
|
|
14792
14936
|
"renderCritique.ddpMissing",
|
|
@@ -14832,7 +14976,7 @@ async function validateRenderCritique(root, config) {
|
|
|
14832
14976
|
issues.push(
|
|
14833
14977
|
issue(
|
|
14834
14978
|
"QFAI-CRIT-005",
|
|
14835
|
-
`Read order not specified (DDP \u2192 Design Token \u2192 UI Contract \u2192 HTML Mock \u2192 Flow): ${
|
|
14979
|
+
`Read order not specified (DDP \u2192 Design Token \u2192 UI Contract \u2192 HTML Mock \u2192 Flow): ${import_node_path50.default.relative(root, sf)}`,
|
|
14836
14980
|
"error",
|
|
14837
14981
|
sf,
|
|
14838
14982
|
"renderCritique.readOrder",
|
|
@@ -14859,7 +15003,7 @@ async function validateRenderCritique(root, config) {
|
|
|
14859
15003
|
issues.push(
|
|
14860
15004
|
issue(
|
|
14861
15005
|
"QFAI-CRIT-006",
|
|
14862
|
-
`Critique evidence incomplete (missing: ${missing.join(", ")}): ${
|
|
15006
|
+
`Critique evidence incomplete (missing: ${missing.join(", ")}): ${import_node_path50.default.relative(root, ef)}`,
|
|
14863
15007
|
"error",
|
|
14864
15008
|
ef,
|
|
14865
15009
|
"renderCritique.incompleteEvidence",
|
|
@@ -14974,9 +15118,9 @@ async function collectContent(files) {
|
|
|
14974
15118
|
return contents.join("\n---\n");
|
|
14975
15119
|
}
|
|
14976
15120
|
async function collectRenderEvidenceViewports(root) {
|
|
14977
|
-
const prototypingJsonPath =
|
|
15121
|
+
const prototypingJsonPath = import_node_path50.default.join(root, ".qfai", "evidence", "prototyping.json");
|
|
14978
15122
|
try {
|
|
14979
|
-
const raw = await (0,
|
|
15123
|
+
const raw = await (0, import_promises49.readFile)(prototypingJsonPath, "utf-8");
|
|
14980
15124
|
const parsed = JSON.parse(raw);
|
|
14981
15125
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14982
15126
|
return /* @__PURE__ */ new Set();
|
|
@@ -15015,8 +15159,8 @@ async function collectRenderEvidenceViewports(root) {
|
|
|
15015
15159
|
}
|
|
15016
15160
|
|
|
15017
15161
|
// src/core/validators/designFidelity.ts
|
|
15018
|
-
var
|
|
15019
|
-
var
|
|
15162
|
+
var import_promises50 = require("fs/promises");
|
|
15163
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
15020
15164
|
var import_fast_glob10 = __toESM(require("fast-glob"), 1);
|
|
15021
15165
|
var SCORECARD_HEADING_RE = /^#{1,3}\s+Fidelity\s+Scorecard/im;
|
|
15022
15166
|
var BASE_DIMENSIONS = ["hierarchy", "clarity", "accessibility", "responsive"];
|
|
@@ -15041,19 +15185,19 @@ async function validateDesignFidelity(root, config) {
|
|
|
15041
15185
|
const evidenceDirs = [".qfai/evidence", ".qfai/review"];
|
|
15042
15186
|
const allFiles = [];
|
|
15043
15187
|
for (const dir of evidenceDirs) {
|
|
15044
|
-
const pattern =
|
|
15188
|
+
const pattern = import_node_path51.default.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
|
|
15045
15189
|
const files = await (0, import_fast_glob10.default)(pattern, { absolute: true });
|
|
15046
15190
|
allFiles.push(...files);
|
|
15047
15191
|
}
|
|
15048
15192
|
for (const filePath of allFiles) {
|
|
15049
15193
|
let content;
|
|
15050
15194
|
try {
|
|
15051
|
-
content = await (0,
|
|
15195
|
+
content = await (0, import_promises50.readFile)(filePath, "utf-8");
|
|
15052
15196
|
} catch {
|
|
15053
15197
|
continue;
|
|
15054
15198
|
}
|
|
15055
15199
|
if (!SCORECARD_HEADING_RE.test(content)) continue;
|
|
15056
|
-
const rel =
|
|
15200
|
+
const rel = import_node_path51.default.relative(root, filePath).replace(/\\/g, "/");
|
|
15057
15201
|
const section = extractScorecardSection(content);
|
|
15058
15202
|
if (!section) continue;
|
|
15059
15203
|
const dimensions = parseDimensions(section);
|
|
@@ -15345,7 +15489,7 @@ function hasAntiPatternMention(section, code) {
|
|
|
15345
15489
|
}
|
|
15346
15490
|
|
|
15347
15491
|
// src/core/validators/discussionDesignHardening.ts
|
|
15348
|
-
var
|
|
15492
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
15349
15493
|
var HTML_TAG_RE = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
|
|
15350
15494
|
var MERMAID_SCREEN_FLOW_RE = /```mermaid[\s\S]*?(?:stateDiagram|flowchart|graph)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
|
|
15351
15495
|
var DDS_HEADING = "## Design Direction Summary";
|
|
@@ -15360,10 +15504,17 @@ var DDS_SUBSECTIONS = [
|
|
|
15360
15504
|
var REQUIRED_STATES = ["empty", "loading", "error", "populated"];
|
|
15361
15505
|
var COMPETITIVE_REF_FIELDS = ["adopted_points", "rejected_points", "local_translation"];
|
|
15362
15506
|
var PLACEHOLDER_RE = /^(?:tbd|todo|n\/a|na|xxx|\?\?\?|placeholder)$/i;
|
|
15507
|
+
var SURFACE_TYPE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
|
|
15363
15508
|
async function isUiBearing(packRoot) {
|
|
15364
|
-
const storyPath =
|
|
15509
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15365
15510
|
const content = await readSafe(storyPath);
|
|
15366
15511
|
if (!content) return false;
|
|
15512
|
+
const surfaceMatch = SURFACE_TYPE_RE.exec(content);
|
|
15513
|
+
if (surfaceMatch?.[1]) {
|
|
15514
|
+
const surface = surfaceMatch[1].toLowerCase();
|
|
15515
|
+
if (surface === "non-ui") return false;
|
|
15516
|
+
if (["web-ui", "mobile-ui", "desktop-ui", "mixed"].includes(surface)) return true;
|
|
15517
|
+
}
|
|
15367
15518
|
if (HTML_TAG_RE.test(content)) return true;
|
|
15368
15519
|
if (MERMAID_SCREEN_FLOW_RE.test(content)) return true;
|
|
15369
15520
|
return false;
|
|
@@ -15398,7 +15549,7 @@ function extractOptionNames(optionSection) {
|
|
|
15398
15549
|
}
|
|
15399
15550
|
async function validateDdsPresence(packRoot) {
|
|
15400
15551
|
const issues = [];
|
|
15401
|
-
const storyPath =
|
|
15552
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15402
15553
|
const content = await readSafe(storyPath);
|
|
15403
15554
|
const relPath = "03_Story-Workshop.md";
|
|
15404
15555
|
if (!content || !content.includes(DDS_HEADING)) {
|
|
@@ -15432,7 +15583,7 @@ async function validateDdsPresence(packRoot) {
|
|
|
15432
15583
|
}
|
|
15433
15584
|
async function validateOptionComparison2(packRoot) {
|
|
15434
15585
|
const issues = [];
|
|
15435
|
-
const storyPath =
|
|
15586
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15436
15587
|
const content = await readSafe(storyPath);
|
|
15437
15588
|
if (!content) return issues;
|
|
15438
15589
|
const dds = extractDdsSection(content);
|
|
@@ -15456,7 +15607,7 @@ async function validateOptionComparison2(packRoot) {
|
|
|
15456
15607
|
}
|
|
15457
15608
|
async function validateAnchorScreen(packRoot) {
|
|
15458
15609
|
const issues = [];
|
|
15459
|
-
const storyPath =
|
|
15610
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15460
15611
|
const content = await readSafe(storyPath);
|
|
15461
15612
|
if (!content) return issues;
|
|
15462
15613
|
const dds = extractDdsSection(content);
|
|
@@ -15498,7 +15649,7 @@ async function validateAnchorScreen(packRoot) {
|
|
|
15498
15649
|
}
|
|
15499
15650
|
async function validateCompetitiveRefs2(packRoot) {
|
|
15500
15651
|
const issues = [];
|
|
15501
|
-
const sourcesPath =
|
|
15652
|
+
const sourcesPath = import_node_path52.default.join(packRoot, "04_Sources.md");
|
|
15502
15653
|
const content = await readSafe(sourcesPath);
|
|
15503
15654
|
if (!content) return issues;
|
|
15504
15655
|
const registryHeadingRe = /^##\s+Competitive Reference Registry\b.*$/m;
|
|
@@ -15555,7 +15706,7 @@ function fieldGuidance(field) {
|
|
|
15555
15706
|
}
|
|
15556
15707
|
async function validateCtaHierarchy(packRoot) {
|
|
15557
15708
|
const issues = [];
|
|
15558
|
-
const storyPath =
|
|
15709
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15559
15710
|
const content = await readSafe(storyPath);
|
|
15560
15711
|
if (!content) return issues;
|
|
15561
15712
|
const dds = extractDdsSection(content);
|
|
@@ -15593,7 +15744,7 @@ async function validateCtaHierarchy(packRoot) {
|
|
|
15593
15744
|
}
|
|
15594
15745
|
async function validateStateCoverage(packRoot) {
|
|
15595
15746
|
const issues = [];
|
|
15596
|
-
const storyPath =
|
|
15747
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15597
15748
|
const content = await readSafe(storyPath);
|
|
15598
15749
|
if (!content) return issues;
|
|
15599
15750
|
const dds = extractDdsSection(content);
|
|
@@ -15618,7 +15769,7 @@ async function validateStateCoverage(packRoot) {
|
|
|
15618
15769
|
}
|
|
15619
15770
|
async function validateDesignAntiGoals(packRoot) {
|
|
15620
15771
|
const issues = [];
|
|
15621
|
-
const storyPath =
|
|
15772
|
+
const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
|
|
15622
15773
|
const content = await readSafe(storyPath);
|
|
15623
15774
|
if (!content) return issues;
|
|
15624
15775
|
const dds = extractDdsSection(content);
|
|
@@ -15655,7 +15806,7 @@ async function validateDesignAntiGoals(packRoot) {
|
|
|
15655
15806
|
return issues;
|
|
15656
15807
|
}
|
|
15657
15808
|
async function validateDiscussionDesignHardening(root, config) {
|
|
15658
|
-
const discussionDir =
|
|
15809
|
+
const discussionDir = import_node_path52.default.join(root, config.paths.discussionDir);
|
|
15659
15810
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
15660
15811
|
if (!packRoot) return [];
|
|
15661
15812
|
const uiBearing = await isUiBearing(packRoot);
|
|
@@ -15672,8 +15823,8 @@ async function validateDiscussionDesignHardening(root, config) {
|
|
|
15672
15823
|
}
|
|
15673
15824
|
|
|
15674
15825
|
// src/core/validators/designAudit.ts
|
|
15675
|
-
var
|
|
15676
|
-
var
|
|
15826
|
+
var import_promises51 = require("fs/promises");
|
|
15827
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
15677
15828
|
var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
|
|
15678
15829
|
function resolveAuditConfig(config) {
|
|
15679
15830
|
const audit = config.uiux?.audit;
|
|
@@ -15755,19 +15906,19 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
|
|
|
15755
15906
|
async function checkTokenDrift(root, auditConfig, cfg) {
|
|
15756
15907
|
const findings = [];
|
|
15757
15908
|
const configuredDir = cfg.uiux?.designTokensDir;
|
|
15758
|
-
const tokensDir = configuredDir ?
|
|
15909
|
+
const tokensDir = configuredDir ? import_node_path53.default.resolve(root, configuredDir) : import_node_path53.default.join(root, cfg.paths.contractsDir, "design");
|
|
15759
15910
|
let hasTokenFiles = false;
|
|
15760
15911
|
try {
|
|
15761
|
-
const entries = await (0,
|
|
15912
|
+
const entries = await (0, import_promises51.readdir)(tokensDir);
|
|
15762
15913
|
hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
|
|
15763
15914
|
} catch {
|
|
15764
15915
|
return findings;
|
|
15765
15916
|
}
|
|
15766
15917
|
if (!hasTokenFiles) return findings;
|
|
15767
|
-
const contractsUiDir =
|
|
15918
|
+
const contractsUiDir = import_node_path53.default.join(root, cfg.paths.contractsDir, "ui");
|
|
15768
15919
|
let htmlFiles = [];
|
|
15769
15920
|
try {
|
|
15770
|
-
const entries = await (0,
|
|
15921
|
+
const entries = await (0, import_promises51.readdir)(contractsUiDir);
|
|
15771
15922
|
htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
|
|
15772
15923
|
} catch {
|
|
15773
15924
|
return findings;
|
|
@@ -15775,7 +15926,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
|
|
|
15775
15926
|
let rawCount = 0;
|
|
15776
15927
|
const sampleLiterals = [];
|
|
15777
15928
|
for (const htmlFile of htmlFiles) {
|
|
15778
|
-
const content = await readSafe(
|
|
15929
|
+
const content = await readSafe(import_node_path53.default.join(contractsUiDir, htmlFile));
|
|
15779
15930
|
if (!content) continue;
|
|
15780
15931
|
const matches = content.match(RAW_COLOR_RE);
|
|
15781
15932
|
if (matches) {
|
|
@@ -15826,12 +15977,12 @@ function deduplicateFindings(issues, maxPerRule) {
|
|
|
15826
15977
|
async function validateDesignAudit(root, config) {
|
|
15827
15978
|
const auditConfig = resolveAuditConfig(config);
|
|
15828
15979
|
if (!auditConfig.enabled) return [];
|
|
15829
|
-
const discussionDir =
|
|
15980
|
+
const discussionDir = import_node_path53.default.join(root, config.paths.discussionDir);
|
|
15830
15981
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
15831
15982
|
if (!packRoot) return [];
|
|
15832
15983
|
const uiBearing = await isUiBearing(packRoot);
|
|
15833
15984
|
if (!uiBearing) return [];
|
|
15834
|
-
const storyPath =
|
|
15985
|
+
const storyPath = import_node_path53.default.join(packRoot, "03_Story-Workshop.md");
|
|
15835
15986
|
const content = await readSafe(storyPath);
|
|
15836
15987
|
if (!content) return [];
|
|
15837
15988
|
const findings = [];
|
|
@@ -15843,8 +15994,8 @@ async function validateDesignAudit(root, config) {
|
|
|
15843
15994
|
|
|
15844
15995
|
// src/core/validators/designSlop.ts
|
|
15845
15996
|
var import_node_fs2 = require("fs");
|
|
15846
|
-
var
|
|
15847
|
-
var
|
|
15997
|
+
var import_promises52 = require("fs/promises");
|
|
15998
|
+
var import_node_path54 = __toESM(require("path"), 1);
|
|
15848
15999
|
var import_node_url4 = require("url");
|
|
15849
16000
|
function isValidSlopPattern(rule) {
|
|
15850
16001
|
if (typeof rule !== "object" || rule === null) return false;
|
|
@@ -15852,7 +16003,7 @@ function isValidSlopPattern(rule) {
|
|
|
15852
16003
|
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";
|
|
15853
16004
|
}
|
|
15854
16005
|
async function loadSlopPatterns(jsonPath) {
|
|
15855
|
-
const raw = await (0,
|
|
16006
|
+
const raw = await (0, import_promises52.readFile)(jsonPath, "utf-8");
|
|
15856
16007
|
const parsed = JSON.parse(raw);
|
|
15857
16008
|
if (!Array.isArray(parsed)) return [];
|
|
15858
16009
|
return parsed.filter((r) => isValidSlopPattern(r));
|
|
@@ -15860,11 +16011,11 @@ async function loadSlopPatterns(jsonPath) {
|
|
|
15860
16011
|
function defaultPatternsPath() {
|
|
15861
16012
|
const base = __filename;
|
|
15862
16013
|
const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
|
|
15863
|
-
const baseDir =
|
|
16014
|
+
const baseDir = import_node_path54.default.dirname(basePath);
|
|
15864
16015
|
const candidates = [
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
16016
|
+
import_node_path54.default.join(baseDir, "designSlopPatterns.json"),
|
|
16017
|
+
import_node_path54.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
|
|
16018
|
+
import_node_path54.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
|
|
15868
16019
|
];
|
|
15869
16020
|
for (const c of candidates) {
|
|
15870
16021
|
if ((0, import_node_fs2.existsSync)(c)) return c;
|
|
@@ -15875,7 +16026,7 @@ async function validateDesignSlop(root, config) {
|
|
|
15875
16026
|
const auditConfig = resolveAuditConfig(config);
|
|
15876
16027
|
if (!auditConfig.enabled) return [];
|
|
15877
16028
|
if (!auditConfig.slopDetection) return [];
|
|
15878
|
-
const discussionDir =
|
|
16029
|
+
const discussionDir = import_node_path54.default.join(root, config.paths.discussionDir);
|
|
15879
16030
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
15880
16031
|
if (!packRoot) return [];
|
|
15881
16032
|
const uiBearing = await isUiBearing(packRoot);
|
|
@@ -15896,7 +16047,7 @@ async function validateDesignSlop(root, config) {
|
|
|
15896
16047
|
continue;
|
|
15897
16048
|
}
|
|
15898
16049
|
for (const scope of pattern.scopes) {
|
|
15899
|
-
const filePath =
|
|
16050
|
+
const filePath = import_node_path54.default.join(packRoot, scope);
|
|
15900
16051
|
const content = await readSafe(filePath);
|
|
15901
16052
|
if (!content) continue;
|
|
15902
16053
|
if (regex.test(content) && !seenRules.has(pattern.id)) {
|
|
@@ -15917,6 +16068,460 @@ async function validateDesignSlop(root, config) {
|
|
|
15917
16068
|
return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
|
|
15918
16069
|
}
|
|
15919
16070
|
|
|
16071
|
+
// src/core/validators/uixValidators.ts
|
|
16072
|
+
var import_promises54 = require("fs/promises");
|
|
16073
|
+
var import_node_path56 = __toESM(require("path"), 1);
|
|
16074
|
+
|
|
16075
|
+
// src/core/validators/uixDetection.ts
|
|
16076
|
+
var import_promises53 = require("fs/promises");
|
|
16077
|
+
var import_node_path55 = __toESM(require("path"), 1);
|
|
16078
|
+
var UI_BEARING_SURFACES = /* @__PURE__ */ new Set(["web-ui", "mobile-ui", "desktop-ui", "mixed"]);
|
|
16079
|
+
var NON_UI_SURFACES = /* @__PURE__ */ new Set(["non-ui"]);
|
|
16080
|
+
function stripCodeBlocks(content) {
|
|
16081
|
+
let stripped = content.replace(/```[\s\S]*?```/g, "");
|
|
16082
|
+
stripped = stripped.replace(/`[^`]+`/g, "");
|
|
16083
|
+
return stripped;
|
|
16084
|
+
}
|
|
16085
|
+
var HTML_TAG_RE2 = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
|
|
16086
|
+
var MERMAID_SCREEN_FLOW_RE2 = /```mermaid[\s\S]*?(?:stateDiagram)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
|
|
16087
|
+
var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
|
|
16088
|
+
async function isUiBearingSpec(root) {
|
|
16089
|
+
const specContent = await readSafe(import_node_path55.default.join(root, "01_Spec.md"));
|
|
16090
|
+
const surfaceMatch = /^\s*-\s*surface:\s*(\S+)/im.exec(specContent);
|
|
16091
|
+
if (surfaceMatch?.[1]) {
|
|
16092
|
+
const surface = surfaceMatch[1].toLowerCase();
|
|
16093
|
+
if (UI_BEARING_SURFACES.has(surface)) return true;
|
|
16094
|
+
if (NON_UI_SURFACES.has(surface)) return false;
|
|
16095
|
+
}
|
|
16096
|
+
try {
|
|
16097
|
+
await (0, import_promises53.readdir)(import_node_path55.default.join(root, "uiux"));
|
|
16098
|
+
return true;
|
|
16099
|
+
} catch {
|
|
16100
|
+
}
|
|
16101
|
+
const contractsContent = await readSafe(import_node_path55.default.join(root, "uiux", "40_contracts.md"));
|
|
16102
|
+
if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return true;
|
|
16103
|
+
const storyContent = await readSafe(import_node_path55.default.join(root, "03_Story-Workshop.md"));
|
|
16104
|
+
if (storyContent) {
|
|
16105
|
+
const stripped = stripCodeBlocks(storyContent);
|
|
16106
|
+
if (HTML_TAG_RE2.test(stripped)) return true;
|
|
16107
|
+
if (MERMAID_SCREEN_FLOW_RE2.test(storyContent)) return true;
|
|
16108
|
+
}
|
|
16109
|
+
return false;
|
|
16110
|
+
}
|
|
16111
|
+
|
|
16112
|
+
// src/core/validators/uixValidators.ts
|
|
16113
|
+
function uixIssue(code, message, severity, file, suggestedAction) {
|
|
16114
|
+
return {
|
|
16115
|
+
code,
|
|
16116
|
+
severity,
|
|
16117
|
+
category: "compatibility",
|
|
16118
|
+
message,
|
|
16119
|
+
file,
|
|
16120
|
+
suggested_action: suggestedAction
|
|
16121
|
+
};
|
|
16122
|
+
}
|
|
16123
|
+
function parseSimpleYaml(content) {
|
|
16124
|
+
const result = {};
|
|
16125
|
+
const store = (rawKey, value) => {
|
|
16126
|
+
const key = rawKey.trim().toLowerCase().replace(/\s+/g, "_");
|
|
16127
|
+
if (key && value) result[key] = value;
|
|
16128
|
+
};
|
|
16129
|
+
for (const line of content.split("\n")) {
|
|
16130
|
+
const yamlMatch = /^\s*(\w[\w_]*):\s*(.*)$/.exec(line);
|
|
16131
|
+
if (yamlMatch?.[1] !== void 0 && yamlMatch[2] !== void 0) {
|
|
16132
|
+
store(yamlMatch[1], yamlMatch[2].trim());
|
|
16133
|
+
continue;
|
|
16134
|
+
}
|
|
16135
|
+
const bulletMatch = /^\s*-\s+([\w][\w\s]*?):\s+(.+)$/.exec(line);
|
|
16136
|
+
if (bulletMatch?.[1] !== void 0 && bulletMatch[2] !== void 0) {
|
|
16137
|
+
store(bulletMatch[1], bulletMatch[2].trim());
|
|
16138
|
+
continue;
|
|
16139
|
+
}
|
|
16140
|
+
const tableMatch = /^\s*\|\s*(\w[\w\s_]*?)\s*\|\s*(.+?)\s*\|\s*$/.exec(line);
|
|
16141
|
+
if (tableMatch?.[1] !== void 0 && tableMatch[2] !== void 0) {
|
|
16142
|
+
if (!/^[-:]+$/.test(tableMatch[2].trim())) {
|
|
16143
|
+
store(tableMatch[1], tableMatch[2].trim());
|
|
16144
|
+
}
|
|
16145
|
+
}
|
|
16146
|
+
}
|
|
16147
|
+
return result;
|
|
16148
|
+
}
|
|
16149
|
+
async function validateSidecarMissing(root, _config) {
|
|
16150
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16151
|
+
try {
|
|
16152
|
+
await (0, import_promises54.readdir)(import_node_path56.default.join(root, "uiux"));
|
|
16153
|
+
return [];
|
|
16154
|
+
} catch {
|
|
16155
|
+
return [
|
|
16156
|
+
uixIssue(
|
|
16157
|
+
"UIX-VAL-SIDECAR-MISSING",
|
|
16158
|
+
"UI-bearing spec detected but uiux/ sidecar directory is missing.",
|
|
16159
|
+
"error",
|
|
16160
|
+
"uiux/",
|
|
16161
|
+
"Create the uiux/ directory and populate it with the sidecar template files."
|
|
16162
|
+
)
|
|
16163
|
+
];
|
|
16164
|
+
}
|
|
16165
|
+
}
|
|
16166
|
+
var STRATEGY_REQUIRED_FIELDS_LEGACY = [
|
|
16167
|
+
"selection_required",
|
|
16168
|
+
"candidate_options",
|
|
16169
|
+
"chosen_option",
|
|
16170
|
+
"verification_expectations",
|
|
16171
|
+
"none_as_legitimate_outcome"
|
|
16172
|
+
];
|
|
16173
|
+
var STRATEGY_REQUIRED_FIELDS_CURRENT = ["surface_type", "approach", "rationale"];
|
|
16174
|
+
var STRATEGY_MIN_LENGTH_FIELDS = ["rationale", "approach"];
|
|
16175
|
+
var STRATEGY_MIN_LENGTH = 20;
|
|
16176
|
+
async function validateStrategyCompleteness(root, _config) {
|
|
16177
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16178
|
+
const strategyPath = import_node_path56.default.join(root, "uiux", "10_strategy.md");
|
|
16179
|
+
const content = await readSafe(strategyPath);
|
|
16180
|
+
if (!content) return [];
|
|
16181
|
+
const parsed = parseSimpleYaml(content);
|
|
16182
|
+
const issues = [];
|
|
16183
|
+
const relPath = "uiux/10_strategy.md";
|
|
16184
|
+
const isCurrentFormat = parsed["surface_type"] !== void 0;
|
|
16185
|
+
const requiredFields = isCurrentFormat ? STRATEGY_REQUIRED_FIELDS_CURRENT : STRATEGY_REQUIRED_FIELDS_LEGACY;
|
|
16186
|
+
for (const field of requiredFields) {
|
|
16187
|
+
const value = parsed[field];
|
|
16188
|
+
if (value === void 0 || value === "") {
|
|
16189
|
+
issues.push(
|
|
16190
|
+
uixIssue(
|
|
16191
|
+
"UIX-VAL-STRATEGY-INCOMPLETE",
|
|
16192
|
+
`Strategy field '${field}' is missing or empty in 10_strategy.md.`,
|
|
16193
|
+
"error",
|
|
16194
|
+
relPath,
|
|
16195
|
+
`Add the '${field}' field to uiux/10_strategy.md with a valid value.`
|
|
16196
|
+
)
|
|
16197
|
+
);
|
|
16198
|
+
}
|
|
16199
|
+
}
|
|
16200
|
+
for (const field of STRATEGY_MIN_LENGTH_FIELDS) {
|
|
16201
|
+
const value = parsed[field];
|
|
16202
|
+
if (value === void 0 || value.length < STRATEGY_MIN_LENGTH) {
|
|
16203
|
+
issues.push(
|
|
16204
|
+
uixIssue(
|
|
16205
|
+
"UIX-VAL-STRATEGY-INCOMPLETE",
|
|
16206
|
+
`Strategy field '${field}' must be at least ${STRATEGY_MIN_LENGTH} characters (current: ${value?.length ?? 0}).`,
|
|
16207
|
+
"error",
|
|
16208
|
+
relPath,
|
|
16209
|
+
`Expand the '${field}' field in uiux/10_strategy.md to at least ${STRATEGY_MIN_LENGTH} characters.`
|
|
16210
|
+
)
|
|
16211
|
+
);
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
16214
|
+
return issues;
|
|
16215
|
+
}
|
|
16216
|
+
async function validateScoringAxes(root, _config) {
|
|
16217
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16218
|
+
const singlePath = import_node_path56.default.join(root, "uiux", "20_eval_axes.md");
|
|
16219
|
+
let content = await readSafe(singlePath);
|
|
16220
|
+
let relPath = "uiux/20_eval_axes.md";
|
|
16221
|
+
if (!content) {
|
|
16222
|
+
const splitFiles = [
|
|
16223
|
+
"20_eval_axis_usability.md",
|
|
16224
|
+
"21_eval_axis_consistency.md",
|
|
16225
|
+
"22_eval_axis_accessibility.md",
|
|
16226
|
+
"23_eval_axis_delight.md"
|
|
16227
|
+
];
|
|
16228
|
+
const parts = [];
|
|
16229
|
+
for (const f of splitFiles) {
|
|
16230
|
+
const c = await readSafe(import_node_path56.default.join(root, "uiux", f));
|
|
16231
|
+
if (c) parts.push(c);
|
|
16232
|
+
}
|
|
16233
|
+
if (parts.length > 0) {
|
|
16234
|
+
content = parts.join("\n");
|
|
16235
|
+
relPath = "uiux/20_eval_axis_*.md";
|
|
16236
|
+
}
|
|
16237
|
+
}
|
|
16238
|
+
if (!content) return [];
|
|
16239
|
+
const issues = [];
|
|
16240
|
+
const lines = content.split("\n");
|
|
16241
|
+
let inTrendSection = false;
|
|
16242
|
+
for (const line of lines) {
|
|
16243
|
+
if (/trend[_-]derived/i.test(line)) {
|
|
16244
|
+
inTrendSection = true;
|
|
16245
|
+
continue;
|
|
16246
|
+
}
|
|
16247
|
+
if (inTrendSection && /^#+\s/.test(line)) {
|
|
16248
|
+
inTrendSection = false;
|
|
16249
|
+
continue;
|
|
16250
|
+
}
|
|
16251
|
+
const isBulletRow = /^\s*-\s/.test(line);
|
|
16252
|
+
const isTableRow2 = /^\s*\|/.test(line) && !/^\s*\|[\s-:|]+\|[\s-:|]*$/.test(line);
|
|
16253
|
+
if (inTrendSection && (isBulletRow || isTableRow2)) {
|
|
16254
|
+
if (!/source_translation/i.test(line)) {
|
|
16255
|
+
issues.push(
|
|
16256
|
+
uixIssue(
|
|
16257
|
+
"UIX-VAL-SCORING-AXIS-INCOMPLETE",
|
|
16258
|
+
"Trend-derived scoring axis row is missing source_translation field.",
|
|
16259
|
+
"error",
|
|
16260
|
+
relPath,
|
|
16261
|
+
"Add source_translation to each trend-derived evaluation axis row."
|
|
16262
|
+
)
|
|
16263
|
+
);
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
return issues;
|
|
16268
|
+
}
|
|
16269
|
+
var AGGREGATE_REQUIRED_FIELDS = ["weights", "normalization", "threshold"];
|
|
16270
|
+
async function validateAggregateScoringRules(root, _config) {
|
|
16271
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16272
|
+
const aggregatePath = import_node_path56.default.join(root, "uiux", "21_aggregate_scoring.md");
|
|
16273
|
+
let content = await readSafe(aggregatePath);
|
|
16274
|
+
let relPath = "uiux/21_aggregate_scoring.md";
|
|
16275
|
+
if (!content) {
|
|
16276
|
+
const delightContent = await readSafe(import_node_path56.default.join(root, "uiux", "23_eval_axis_delight.md"));
|
|
16277
|
+
if (delightContent) {
|
|
16278
|
+
const lines = delightContent.split("\n");
|
|
16279
|
+
let inSection = false;
|
|
16280
|
+
const sectionLines = [];
|
|
16281
|
+
for (const line of lines) {
|
|
16282
|
+
if (/^#+\s*Aggregate\s+Scoring/i.test(line)) {
|
|
16283
|
+
inSection = true;
|
|
16284
|
+
continue;
|
|
16285
|
+
}
|
|
16286
|
+
if (inSection && /^#+\s/.test(line)) break;
|
|
16287
|
+
if (inSection) sectionLines.push(line);
|
|
16288
|
+
}
|
|
16289
|
+
if (sectionLines.length > 0) {
|
|
16290
|
+
content = sectionLines.join("\n");
|
|
16291
|
+
relPath = "uiux/23_eval_axis_delight.md";
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
}
|
|
16295
|
+
if (!content) return [];
|
|
16296
|
+
const parsed = parseSimpleYaml(content);
|
|
16297
|
+
const issues = [];
|
|
16298
|
+
for (const field of AGGREGATE_REQUIRED_FIELDS) {
|
|
16299
|
+
const value = parsed[field] ?? parsed[`${field}s`];
|
|
16300
|
+
if (value === void 0 || value === "") {
|
|
16301
|
+
issues.push(
|
|
16302
|
+
uixIssue(
|
|
16303
|
+
"UIX-VAL-AGGREGATE-SCORING-INCOMPLETE",
|
|
16304
|
+
`Aggregate scoring field '${field}' is missing in ${relPath}.`,
|
|
16305
|
+
"error",
|
|
16306
|
+
relPath,
|
|
16307
|
+
`Add the '${field}' field to ${relPath}.`
|
|
16308
|
+
)
|
|
16309
|
+
);
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16312
|
+
return issues;
|
|
16313
|
+
}
|
|
16314
|
+
async function validateOptionComparison3(root, _config) {
|
|
16315
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16316
|
+
const issues = [];
|
|
16317
|
+
const compPath = import_node_path56.default.join(root, "uiux", "30_comparison.md");
|
|
16318
|
+
const compContent = await readSafe(compPath);
|
|
16319
|
+
if (compContent) {
|
|
16320
|
+
const headingOptions = compContent.match(/^##\s+Option\b/gim);
|
|
16321
|
+
const tableOptions = compContent.match(/\bOption\s+[A-Z]\b/gim);
|
|
16322
|
+
const uniqueTableOptions = tableOptions ? new Set(tableOptions.map((m) => m.trim().toUpperCase())).size : 0;
|
|
16323
|
+
const optionCount = Math.max(headingOptions?.length ?? 0, uniqueTableOptions);
|
|
16324
|
+
if (optionCount < 2) {
|
|
16325
|
+
issues.push(
|
|
16326
|
+
uixIssue(
|
|
16327
|
+
"UIX-VAL-COMPARISON-INSUFFICIENT",
|
|
16328
|
+
"30_comparison.md must contain at least 2 options for meaningful comparison.",
|
|
16329
|
+
"error",
|
|
16330
|
+
"uiux/30_comparison.md",
|
|
16331
|
+
"Add at least 2 '## Option' sections to uiux/30_comparison.md."
|
|
16332
|
+
)
|
|
16333
|
+
);
|
|
16334
|
+
}
|
|
16335
|
+
}
|
|
16336
|
+
const anchorPath = import_node_path56.default.join(root, "uiux", "31_anchor.md");
|
|
16337
|
+
const anchorContent = await readSafe(anchorPath);
|
|
16338
|
+
if (anchorContent) {
|
|
16339
|
+
if (!/selected_anchor\s*:/i.test(anchorContent) && !/chosen\s*:/i.test(anchorContent) && !/source\s+option\s*:/i.test(anchorContent)) {
|
|
16340
|
+
issues.push(
|
|
16341
|
+
uixIssue(
|
|
16342
|
+
"UIX-VAL-ANCHOR-MISSING",
|
|
16343
|
+
"31_anchor.md is missing a selected anchor declaration.",
|
|
16344
|
+
"error",
|
|
16345
|
+
"uiux/31_anchor.md",
|
|
16346
|
+
"Add a 'selected_anchor:' field to uiux/31_anchor.md."
|
|
16347
|
+
)
|
|
16348
|
+
);
|
|
16349
|
+
}
|
|
16350
|
+
}
|
|
16351
|
+
return issues;
|
|
16352
|
+
}
|
|
16353
|
+
var SCREEN_CONTRACT_REQUIRED_FIELDS = [
|
|
16354
|
+
"route",
|
|
16355
|
+
"actor",
|
|
16356
|
+
"purpose",
|
|
16357
|
+
"primary_tasks",
|
|
16358
|
+
"required_states",
|
|
16359
|
+
"transitions",
|
|
16360
|
+
"observable_outcomes"
|
|
16361
|
+
];
|
|
16362
|
+
async function validateScreenContracts(root, _config) {
|
|
16363
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16364
|
+
const contractsPath = import_node_path56.default.join(root, "uiux", "40_contracts.md");
|
|
16365
|
+
const content = await readSafe(contractsPath);
|
|
16366
|
+
if (!content) return [];
|
|
16367
|
+
const parsed = parseSimpleYaml(content);
|
|
16368
|
+
const issues = [];
|
|
16369
|
+
const relPath = "uiux/40_contracts.md";
|
|
16370
|
+
const sectionHeadings = {
|
|
16371
|
+
primary_tasks: /^#{3,4}\s+Primary\s+Tasks/im,
|
|
16372
|
+
required_states: /^#{3,4}\s+Required\s+States/im,
|
|
16373
|
+
transitions: /^#{3,4}\s+Transitions/im,
|
|
16374
|
+
observable_outcomes: /^#{3,4}\s+Observable\s+Outcomes/im
|
|
16375
|
+
};
|
|
16376
|
+
for (const field of SCREEN_CONTRACT_REQUIRED_FIELDS) {
|
|
16377
|
+
const value = parsed[field];
|
|
16378
|
+
const hasValue = value !== void 0 && value !== "";
|
|
16379
|
+
const headingPattern = sectionHeadings[field];
|
|
16380
|
+
const hasHeading = headingPattern ? headingPattern.test(content) : false;
|
|
16381
|
+
if (!hasValue && !hasHeading) {
|
|
16382
|
+
issues.push(
|
|
16383
|
+
uixIssue(
|
|
16384
|
+
"UIX-VAL-SCREEN-CONTRACT-INCOMPLETE",
|
|
16385
|
+
`Screen contract field '${field}' is missing in 40_contracts.md.`,
|
|
16386
|
+
"error",
|
|
16387
|
+
relPath,
|
|
16388
|
+
`Add the '${field}' field to screen contracts in uiux/40_contracts.md.`
|
|
16389
|
+
)
|
|
16390
|
+
);
|
|
16391
|
+
}
|
|
16392
|
+
}
|
|
16393
|
+
return issues;
|
|
16394
|
+
}
|
|
16395
|
+
async function validateOqClosure(root, _config) {
|
|
16396
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16397
|
+
const rootOqPath = import_node_path56.default.join(root, "11_OQ-Register.md");
|
|
16398
|
+
const uiuxOqPath = import_node_path56.default.join(root, "uiux", "11_OQ-Register.md");
|
|
16399
|
+
const rootContent = await readSafe(rootOqPath);
|
|
16400
|
+
const content = rootContent || await readSafe(uiuxOqPath);
|
|
16401
|
+
if (!content) return [];
|
|
16402
|
+
const issues = [];
|
|
16403
|
+
const relPath = rootContent ? "11_OQ-Register.md" : "uiux/11_OQ-Register.md";
|
|
16404
|
+
const oqBlocks = content.split(/(?=^##\s+OQ-\d{4})/m);
|
|
16405
|
+
for (const block of oqBlocks) {
|
|
16406
|
+
const idMatch = /^##\s+(OQ-\d{4})/m.exec(block);
|
|
16407
|
+
if (!idMatch?.[1]) continue;
|
|
16408
|
+
const oqId = idMatch[1];
|
|
16409
|
+
const isOpen = /status\s*:\s*open/i.test(block);
|
|
16410
|
+
const isCritical = /severity\s*:\s*(?:critical|blocking)/i.test(block);
|
|
16411
|
+
if (isOpen && isCritical) {
|
|
16412
|
+
issues.push(
|
|
16413
|
+
uixIssue(
|
|
16414
|
+
"UIX-VAL-OQ-OPEN-CRITICAL",
|
|
16415
|
+
`Open critical OQ found: ${oqId}. Must be resolved before proceeding.`,
|
|
16416
|
+
"error",
|
|
16417
|
+
relPath,
|
|
16418
|
+
`Resolve or downgrade ${oqId} in uiux/11_OQ-Register.md before validation can pass.`
|
|
16419
|
+
)
|
|
16420
|
+
);
|
|
16421
|
+
}
|
|
16422
|
+
}
|
|
16423
|
+
const tableRowRegex = /^\s*\|\s*(OQ-\d{4})\s*\|/;
|
|
16424
|
+
for (const line of content.split("\n")) {
|
|
16425
|
+
const rowMatch = tableRowRegex.exec(line);
|
|
16426
|
+
if (!rowMatch?.[1]) continue;
|
|
16427
|
+
const oqId = rowMatch[1];
|
|
16428
|
+
const cols = line.split("|").map((c) => c.trim()).filter(Boolean);
|
|
16429
|
+
const disposition = cols[3]?.toLowerCase();
|
|
16430
|
+
if (disposition === "open") {
|
|
16431
|
+
const fullRow = line.toLowerCase();
|
|
16432
|
+
if (/critical|blocking/i.test(fullRow)) {
|
|
16433
|
+
issues.push(
|
|
16434
|
+
uixIssue(
|
|
16435
|
+
"UIX-VAL-OQ-OPEN-CRITICAL",
|
|
16436
|
+
`Open critical OQ found: ${oqId}. Must be resolved before proceeding.`,
|
|
16437
|
+
"error",
|
|
16438
|
+
relPath,
|
|
16439
|
+
`Resolve or downgrade ${oqId} in ${relPath} before validation can pass.`
|
|
16440
|
+
)
|
|
16441
|
+
);
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16444
|
+
}
|
|
16445
|
+
return issues;
|
|
16446
|
+
}
|
|
16447
|
+
var CURRENT_SIDECAR_VERSION = "1.0.0";
|
|
16448
|
+
async function validateMigration(root, config) {
|
|
16449
|
+
if (!await isUiBearingSpec(root)) return [];
|
|
16450
|
+
const strict = config.uiux?.migration?.strict === true;
|
|
16451
|
+
const issues = [];
|
|
16452
|
+
try {
|
|
16453
|
+
await (0, import_promises54.readdir)(import_node_path56.default.join(root, "uiux"));
|
|
16454
|
+
} catch {
|
|
16455
|
+
issues.push(
|
|
16456
|
+
uixIssue(
|
|
16457
|
+
"UIX-VAL-MIGRATION-SIDECAR-MISSING",
|
|
16458
|
+
"UI-bearing spec detected but uiux/ sidecar directory is missing. Migration required.",
|
|
16459
|
+
strict ? "error" : "warning",
|
|
16460
|
+
"uiux/",
|
|
16461
|
+
"Run the UIX sidecar migration to create the uiux/ directory structure."
|
|
16462
|
+
)
|
|
16463
|
+
);
|
|
16464
|
+
return issues;
|
|
16465
|
+
}
|
|
16466
|
+
const versionPath = import_node_path56.default.join(root, "uiux", ".sidecar-version");
|
|
16467
|
+
const versionContent = await readSafe(versionPath);
|
|
16468
|
+
if (versionContent) {
|
|
16469
|
+
const version = versionContent.trim();
|
|
16470
|
+
if (version && version !== CURRENT_SIDECAR_VERSION) {
|
|
16471
|
+
issues.push(
|
|
16472
|
+
uixIssue(
|
|
16473
|
+
"UIX-VAL-MIGRATION-STALE-VERSION",
|
|
16474
|
+
`Sidecar template version '${version}' is outdated (current: ${CURRENT_SIDECAR_VERSION}).`,
|
|
16475
|
+
"warning",
|
|
16476
|
+
"uiux/.sidecar-version",
|
|
16477
|
+
`Upgrade sidecar template from ${version} to ${CURRENT_SIDECAR_VERSION}. Run the migration tool for upgrade steps.`
|
|
16478
|
+
)
|
|
16479
|
+
);
|
|
16480
|
+
}
|
|
16481
|
+
}
|
|
16482
|
+
return issues;
|
|
16483
|
+
}
|
|
16484
|
+
function applyPhase1Ratchet(issues, releaseDate, now = /* @__PURE__ */ new Date()) {
|
|
16485
|
+
const phase1EndMs = releaseDate.getTime() + 30 * 24 * 60 * 60 * 1e3;
|
|
16486
|
+
if (now.getTime() > phase1EndMs) return issues;
|
|
16487
|
+
return issues.map((iss) => {
|
|
16488
|
+
if (iss.code.startsWith("UIX-VAL-") && iss.severity === "error") {
|
|
16489
|
+
return { ...iss, severity: "warning" };
|
|
16490
|
+
}
|
|
16491
|
+
return iss;
|
|
16492
|
+
});
|
|
16493
|
+
}
|
|
16494
|
+
async function runAllUixValidators(root, config) {
|
|
16495
|
+
let effectiveRoot = root;
|
|
16496
|
+
const directSpec = await readSafe(import_node_path56.default.join(root, "01_Spec.md"));
|
|
16497
|
+
if (!directSpec) {
|
|
16498
|
+
const discussionDir = import_node_path56.default.join(root, config.paths.discussionDir);
|
|
16499
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
16500
|
+
if (!packRoot) return [];
|
|
16501
|
+
effectiveRoot = packRoot;
|
|
16502
|
+
}
|
|
16503
|
+
const validators = [
|
|
16504
|
+
validateSidecarMissing,
|
|
16505
|
+
validateStrategyCompleteness,
|
|
16506
|
+
validateScoringAxes,
|
|
16507
|
+
validateAggregateScoringRules,
|
|
16508
|
+
validateOptionComparison3,
|
|
16509
|
+
validateScreenContracts,
|
|
16510
|
+
validateOqClosure,
|
|
16511
|
+
validateMigration
|
|
16512
|
+
];
|
|
16513
|
+
const results = await Promise.all(validators.map((v) => v(effectiveRoot, config)));
|
|
16514
|
+
let issues = results.flat();
|
|
16515
|
+
const relDateStr = config.uiux?.phase1ReleaseDate;
|
|
16516
|
+
if (relDateStr) {
|
|
16517
|
+
const releaseDate = new Date(relDateStr);
|
|
16518
|
+
if (!isNaN(releaseDate.getTime())) {
|
|
16519
|
+
issues = applyPhase1Ratchet(issues, releaseDate);
|
|
16520
|
+
}
|
|
16521
|
+
}
|
|
16522
|
+
return issues;
|
|
16523
|
+
}
|
|
16524
|
+
|
|
15920
16525
|
// src/core/validate.ts
|
|
15921
16526
|
var UIUX_VALIDATION_BUDGET_MS = 2e3;
|
|
15922
16527
|
async function validateProject(root, configResult, options = {}) {
|
|
@@ -15936,7 +16541,8 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
15936
16541
|
() => validateResearchSummary(root, config),
|
|
15937
16542
|
() => validateAgentDefinition(root, config),
|
|
15938
16543
|
() => validateDesignAudit(root, config),
|
|
15939
|
-
() => validateDesignSlop(root, config)
|
|
16544
|
+
() => validateDesignSlop(root, config),
|
|
16545
|
+
() => runAllUixValidators(root, config)
|
|
15940
16546
|
];
|
|
15941
16547
|
const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
|
|
15942
16548
|
const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
|
|
@@ -16023,15 +16629,15 @@ var REPORT_GUARDRAILS_MAX = 20;
|
|
|
16023
16629
|
var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
|
|
16024
16630
|
var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
|
|
16025
16631
|
async function createReportData(root, validation, configResult) {
|
|
16026
|
-
const resolvedRoot =
|
|
16632
|
+
const resolvedRoot = import_node_path57.default.resolve(root);
|
|
16027
16633
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
16028
16634
|
const config = resolved.config;
|
|
16029
16635
|
const configPath = resolved.configPath;
|
|
16030
16636
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
16031
16637
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
16032
|
-
const apiRoot =
|
|
16033
|
-
const uiRoot =
|
|
16034
|
-
const dbRoot =
|
|
16638
|
+
const apiRoot = import_node_path57.default.join(contractsRoot, "api");
|
|
16639
|
+
const uiRoot = import_node_path57.default.join(contractsRoot, "ui");
|
|
16640
|
+
const dbRoot = import_node_path57.default.join(contractsRoot, "db");
|
|
16035
16641
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
16036
16642
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
16037
16643
|
const specEntries = await collectSpecEntries(specsRoot);
|
|
@@ -16968,7 +17574,7 @@ async function collectChangeTypeSummary(specsRoot) {
|
|
|
16968
17574
|
};
|
|
16969
17575
|
const deltaFiles = await collectDeltaFiles(specsRoot);
|
|
16970
17576
|
for (const deltaFile of deltaFiles) {
|
|
16971
|
-
const text = await (0,
|
|
17577
|
+
const text = await (0, import_promises55.readFile)(deltaFile, "utf-8");
|
|
16972
17578
|
const parsed = parseDeltaV1(text);
|
|
16973
17579
|
for (const entry of parsed.entries) {
|
|
16974
17580
|
if (!entry.meta) {
|
|
@@ -17005,7 +17611,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
17005
17611
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
17006
17612
|
}
|
|
17007
17613
|
for (const file of specFiles) {
|
|
17008
|
-
const text = await (0,
|
|
17614
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17009
17615
|
const parsed = parseSpec(text, file);
|
|
17010
17616
|
const specKey = parsed.specId;
|
|
17011
17617
|
if (!specKey) {
|
|
@@ -17042,7 +17648,7 @@ async function collectIds(files) {
|
|
|
17042
17648
|
result[prefix] = /* @__PURE__ */ new Set();
|
|
17043
17649
|
}
|
|
17044
17650
|
for (const file of files) {
|
|
17045
|
-
const text = await (0,
|
|
17651
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17046
17652
|
for (const prefix of ID_PREFIXES) {
|
|
17047
17653
|
const ids = extractIds(text, prefix);
|
|
17048
17654
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -17057,7 +17663,7 @@ async function collectIds(files) {
|
|
|
17057
17663
|
async function collectUpstreamIds(files) {
|
|
17058
17664
|
const ids = /* @__PURE__ */ new Set();
|
|
17059
17665
|
for (const file of files) {
|
|
17060
|
-
const text = await (0,
|
|
17666
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17061
17667
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
17062
17668
|
}
|
|
17063
17669
|
return ids;
|
|
@@ -17078,7 +17684,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
17078
17684
|
}
|
|
17079
17685
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
17080
17686
|
for (const file of targetFiles) {
|
|
17081
|
-
const text = await (0,
|
|
17687
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17082
17688
|
if (pattern.test(text)) {
|
|
17083
17689
|
return true;
|
|
17084
17690
|
}
|
|
@@ -17197,7 +17803,7 @@ function normalizeScSources(root, sources) {
|
|
|
17197
17803
|
async function countScenarios(scenarioFiles) {
|
|
17198
17804
|
let total = 0;
|
|
17199
17805
|
for (const file of scenarioFiles) {
|
|
17200
|
-
const text = await (0,
|
|
17806
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17201
17807
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
17202
17808
|
if (!document || errors.length > 0) {
|
|
17203
17809
|
continue;
|
|
@@ -17228,7 +17834,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
|
|
|
17228
17834
|
let totalScenarios = 0;
|
|
17229
17835
|
let e2eCount = 0;
|
|
17230
17836
|
for (const file of scenarioFiles) {
|
|
17231
|
-
const text = await (0,
|
|
17837
|
+
const text = await (0, import_promises55.readFile)(file, "utf-8");
|
|
17232
17838
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
17233
17839
|
if (!document || errors.length > 0) {
|
|
17234
17840
|
continue;
|
|
@@ -17316,10 +17922,10 @@ function buildHotspots(issues) {
|
|
|
17316
17922
|
async function collectTddCoverage(entries) {
|
|
17317
17923
|
const specs = [];
|
|
17318
17924
|
for (const entry of entries) {
|
|
17319
|
-
const testCasesPath =
|
|
17925
|
+
const testCasesPath = import_node_path57.default.join(entry.dir, "06_Test-Cases.md");
|
|
17320
17926
|
let tcContent;
|
|
17321
17927
|
try {
|
|
17322
|
-
tcContent = await (0,
|
|
17928
|
+
tcContent = await (0, import_promises55.readFile)(testCasesPath, "utf-8");
|
|
17323
17929
|
} catch {
|
|
17324
17930
|
continue;
|
|
17325
17931
|
}
|
|
@@ -17351,10 +17957,10 @@ async function collectTddCoverage(entries) {
|
|
|
17351
17957
|
});
|
|
17352
17958
|
continue;
|
|
17353
17959
|
}
|
|
17354
|
-
const tddListPath =
|
|
17960
|
+
const tddListPath = import_node_path57.default.join(entry.dir, "tdd", "test-list.md");
|
|
17355
17961
|
let tddContent;
|
|
17356
17962
|
try {
|
|
17357
|
-
tddContent = await (0,
|
|
17963
|
+
tddContent = await (0, import_promises55.readFile)(tddListPath, "utf-8");
|
|
17358
17964
|
} catch {
|
|
17359
17965
|
specs.push({
|
|
17360
17966
|
specNumber: entry.specNumber,
|
|
@@ -17440,6 +18046,8 @@ async function collectTddCoverage(entries) {
|
|
|
17440
18046
|
ID_PREFIXES,
|
|
17441
18047
|
RUNTIME_HEAVY_CHECKS,
|
|
17442
18048
|
STATIC_OBLIGATIONS,
|
|
18049
|
+
VALID_MODE_SOURCES,
|
|
18050
|
+
adaptSurfaceEvidence,
|
|
17443
18051
|
autogenerateUiFidelity,
|
|
17444
18052
|
buildUiFidelityScreens,
|
|
17445
18053
|
checkDecisionGuardrails,
|
|
@@ -17465,6 +18073,7 @@ async function collectTddCoverage(entries) {
|
|
|
17465
18073
|
filterDecisionGuardrailsByKeyword,
|
|
17466
18074
|
findConfigRoot,
|
|
17467
18075
|
formatGuardrailsForLlm,
|
|
18076
|
+
formatModeLog,
|
|
17468
18077
|
formatReportJson,
|
|
17469
18078
|
formatReportMarkdown,
|
|
17470
18079
|
getConfigPath,
|
|
@@ -17472,9 +18081,11 @@ async function collectTddCoverage(entries) {
|
|
|
17472
18081
|
loadConfig,
|
|
17473
18082
|
loadDecisionGuardrails,
|
|
17474
18083
|
normalizeDecisionGuardrails,
|
|
18084
|
+
readDiscussionRecommendation,
|
|
17475
18085
|
resolveObligations,
|
|
17476
18086
|
resolveObligationsWithOptIn,
|
|
17477
18087
|
resolvePath,
|
|
18088
|
+
resolvePrecedence,
|
|
17478
18089
|
resolveToolVersion,
|
|
17479
18090
|
runMockPaths,
|
|
17480
18091
|
runSddPreflight,
|