qfai 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/validators/designSlopPatterns.json +56 -0
- package/dist/cli/index.cjs +435 -82
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +424 -71
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +393 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.mjs +393 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -506,6 +506,69 @@ function normalizeUiux(raw, configPath, issues) {
|
|
|
506
506
|
result.renderEvidence = renderEvidence;
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
|
+
if (raw.audit !== void 0) {
|
|
510
|
+
const audit = normalizeUiuxAudit(raw.audit, configPath, issues);
|
|
511
|
+
if (audit) {
|
|
512
|
+
result.audit = audit;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
516
|
+
}
|
|
517
|
+
function normalizeUiuxAudit(raw, configPath, issues) {
|
|
518
|
+
if (!isRecord(raw)) {
|
|
519
|
+
issues.push(configIssue(configPath, "uiux.audit \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
const result = {};
|
|
523
|
+
if (raw.enabled !== void 0) {
|
|
524
|
+
if (typeof raw.enabled === "boolean") {
|
|
525
|
+
result.enabled = raw.enabled;
|
|
526
|
+
} else {
|
|
527
|
+
issues.push(configIssue(configPath, "uiux.audit.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (raw.slopDetection !== void 0) {
|
|
531
|
+
if (typeof raw.slopDetection === "boolean") {
|
|
532
|
+
result.slopDetection = raw.slopDetection;
|
|
533
|
+
} else {
|
|
534
|
+
issues.push(
|
|
535
|
+
configIssue(configPath, "uiux.audit.slopDetection \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (raw.maxPrimaryCtas !== void 0) {
|
|
540
|
+
if (typeof raw.maxPrimaryCtas === "number" && Number.isFinite(raw.maxPrimaryCtas) && raw.maxPrimaryCtas >= 0) {
|
|
541
|
+
result.maxPrimaryCtas = raw.maxPrimaryCtas;
|
|
542
|
+
} else {
|
|
543
|
+
issues.push(
|
|
544
|
+
configIssue(configPath, "uiux.audit.maxPrimaryCtas \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (raw.maxRawTokenLiteralWarnings !== void 0) {
|
|
549
|
+
if (typeof raw.maxRawTokenLiteralWarnings === "number" && Number.isFinite(raw.maxRawTokenLiteralWarnings) && raw.maxRawTokenLiteralWarnings >= 0) {
|
|
550
|
+
result.maxRawTokenLiteralWarnings = raw.maxRawTokenLiteralWarnings;
|
|
551
|
+
} else {
|
|
552
|
+
issues.push(
|
|
553
|
+
configIssue(
|
|
554
|
+
configPath,
|
|
555
|
+
"uiux.audit.maxRawTokenLiteralWarnings \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (raw.maxDuplicateFindingsPerRule !== void 0) {
|
|
561
|
+
if (typeof raw.maxDuplicateFindingsPerRule === "number" && Number.isFinite(raw.maxDuplicateFindingsPerRule) && raw.maxDuplicateFindingsPerRule >= 0) {
|
|
562
|
+
result.maxDuplicateFindingsPerRule = raw.maxDuplicateFindingsPerRule;
|
|
563
|
+
} else {
|
|
564
|
+
issues.push(
|
|
565
|
+
configIssue(
|
|
566
|
+
configPath,
|
|
567
|
+
"uiux.audit.maxDuplicateFindingsPerRule \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
509
572
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
510
573
|
}
|
|
511
574
|
function normalizeRenderEvidence(raw, configPath, issues) {
|
|
@@ -1644,8 +1707,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1644
1707
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
1645
1708
|
var import_node_url2 = require("url");
|
|
1646
1709
|
async function resolveToolVersion() {
|
|
1647
|
-
if ("1.7.
|
|
1648
|
-
return "1.7.
|
|
1710
|
+
if ("1.7.2".length > 0) {
|
|
1711
|
+
return "1.7.2";
|
|
1649
1712
|
}
|
|
1650
1713
|
try {
|
|
1651
1714
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -4208,9 +4271,9 @@ function applyRenderEvidence(evidence, options, autogenEnabled, outputPath) {
|
|
|
4208
4271
|
};
|
|
4209
4272
|
}
|
|
4210
4273
|
function buildRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
4211
|
-
const
|
|
4274
|
+
const path64 = resolveRenderOutPath(options.root, options.renderOut);
|
|
4212
4275
|
return {
|
|
4213
|
-
path:
|
|
4276
|
+
path: path64,
|
|
4214
4277
|
bundle: {
|
|
4215
4278
|
meta: {
|
|
4216
4279
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4222,7 +4285,7 @@ function buildRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
|
4222
4285
|
requested: true,
|
|
4223
4286
|
autogenEnabled,
|
|
4224
4287
|
viewports: normalizeRenderViewports(options.renderViewports),
|
|
4225
|
-
outputPath:
|
|
4288
|
+
outputPath: path64,
|
|
4226
4289
|
reason: autogenEnabled ? "render evidence capture not implemented in this slice" : "render requested without autogen-ui-fidelity"
|
|
4227
4290
|
}
|
|
4228
4291
|
}
|
|
@@ -4288,8 +4351,8 @@ function extractRouteHintsFromEvidence(evidence) {
|
|
|
4288
4351
|
}
|
|
4289
4352
|
|
|
4290
4353
|
// src/cli/commands/report.ts
|
|
4291
|
-
var
|
|
4292
|
-
var
|
|
4354
|
+
var import_promises58 = require("fs/promises");
|
|
4355
|
+
var import_node_path61 = __toESM(require("path"), 1);
|
|
4293
4356
|
|
|
4294
4357
|
// src/core/normalize.ts
|
|
4295
4358
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -4384,8 +4447,8 @@ async function createPhaseGuardResult(phase, blockedIssue) {
|
|
|
4384
4447
|
}
|
|
4385
4448
|
|
|
4386
4449
|
// src/core/report.ts
|
|
4387
|
-
var
|
|
4388
|
-
var
|
|
4450
|
+
var import_promises56 = require("fs/promises");
|
|
4451
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
4389
4452
|
|
|
4390
4453
|
// src/core/contractIndex.ts
|
|
4391
4454
|
var import_promises15 = require("fs/promises");
|
|
@@ -12794,7 +12857,7 @@ function collectLayer(layer, layerName, target, errors) {
|
|
|
12794
12857
|
}
|
|
12795
12858
|
function flattenTokens(obj, prefix, target, errors) {
|
|
12796
12859
|
for (const [key, value] of Object.entries(obj)) {
|
|
12797
|
-
const
|
|
12860
|
+
const path64 = `${prefix}.${key}`;
|
|
12798
12861
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
12799
12862
|
const record2 = value;
|
|
12800
12863
|
if ("$value" in record2) {
|
|
@@ -12810,9 +12873,9 @@ function flattenTokens(obj, prefix, target, errors) {
|
|
|
12810
12873
|
if (typeof record2.platform === "string") {
|
|
12811
12874
|
token.platform = record2.platform;
|
|
12812
12875
|
}
|
|
12813
|
-
target.set(
|
|
12876
|
+
target.set(path64, token);
|
|
12814
12877
|
} else {
|
|
12815
|
-
flattenTokens(record2,
|
|
12878
|
+
flattenTokens(record2, path64, target, errors);
|
|
12816
12879
|
}
|
|
12817
12880
|
}
|
|
12818
12881
|
}
|
|
@@ -12822,44 +12885,44 @@ function resolveAllReferences(result) {
|
|
|
12822
12885
|
for (const [key, val] of result.primitives) allTokens.set(key, val);
|
|
12823
12886
|
for (const [key, val] of result.semantics) allTokens.set(key, val);
|
|
12824
12887
|
for (const [key, val] of result.components) allTokens.set(key, val);
|
|
12825
|
-
for (const [
|
|
12826
|
-
resolveTokenRef(
|
|
12888
|
+
for (const [path64] of allTokens) {
|
|
12889
|
+
resolveTokenRef(path64, allTokens, /* @__PURE__ */ new Set(), 0, result);
|
|
12827
12890
|
}
|
|
12828
12891
|
}
|
|
12829
|
-
function resolveTokenRef(
|
|
12830
|
-
if (result.resolved.has(
|
|
12831
|
-
return result.resolved.get(
|
|
12892
|
+
function resolveTokenRef(path64, allTokens, visited, depth, result) {
|
|
12893
|
+
if (result.resolved.has(path64)) {
|
|
12894
|
+
return result.resolved.get(path64);
|
|
12832
12895
|
}
|
|
12833
12896
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
12834
12897
|
result.errors.push({
|
|
12835
|
-
message: `Max reference depth exceeded at: ${
|
|
12836
|
-
path:
|
|
12898
|
+
message: `Max reference depth exceeded at: ${path64}`,
|
|
12899
|
+
path: path64
|
|
12837
12900
|
});
|
|
12838
12901
|
return void 0;
|
|
12839
12902
|
}
|
|
12840
|
-
if (visited.has(
|
|
12903
|
+
if (visited.has(path64)) {
|
|
12841
12904
|
result.errors.push({
|
|
12842
|
-
message: `Circular reference detected: ${
|
|
12843
|
-
path:
|
|
12905
|
+
message: `Circular reference detected: ${path64}`,
|
|
12906
|
+
path: path64
|
|
12844
12907
|
});
|
|
12845
12908
|
return void 0;
|
|
12846
12909
|
}
|
|
12847
|
-
const token = allTokens.get(
|
|
12910
|
+
const token = allTokens.get(path64);
|
|
12848
12911
|
if (!token) {
|
|
12849
12912
|
return void 0;
|
|
12850
12913
|
}
|
|
12851
12914
|
if (typeof token.$value !== "string") {
|
|
12852
12915
|
const rawValue2 = stringifyTokenValue(token.$value);
|
|
12853
|
-
result.resolved.set(
|
|
12916
|
+
result.resolved.set(path64, rawValue2);
|
|
12854
12917
|
return rawValue2;
|
|
12855
12918
|
}
|
|
12856
12919
|
const rawValue = stringifyTokenValue(token.$value);
|
|
12857
12920
|
const refs = [...rawValue.matchAll(REF_PATTERN)];
|
|
12858
12921
|
if (refs.length === 0) {
|
|
12859
|
-
result.resolved.set(
|
|
12922
|
+
result.resolved.set(path64, rawValue);
|
|
12860
12923
|
return rawValue;
|
|
12861
12924
|
}
|
|
12862
|
-
visited.add(
|
|
12925
|
+
visited.add(path64);
|
|
12863
12926
|
let resolved = rawValue;
|
|
12864
12927
|
for (const ref of refs) {
|
|
12865
12928
|
const refPath = ref[1];
|
|
@@ -12867,8 +12930,8 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12867
12930
|
const refToken = allTokens.get(refPath);
|
|
12868
12931
|
if (!refToken) {
|
|
12869
12932
|
result.errors.push({
|
|
12870
|
-
message: `Unresolved token reference: {${refPath}} at ${
|
|
12871
|
-
path:
|
|
12933
|
+
message: `Unresolved token reference: {${refPath}} at ${path64}`,
|
|
12934
|
+
path: path64
|
|
12872
12935
|
});
|
|
12873
12936
|
continue;
|
|
12874
12937
|
}
|
|
@@ -12877,7 +12940,7 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12877
12940
|
resolved = resolved.split(`{${refPath}}`).join(refValue);
|
|
12878
12941
|
}
|
|
12879
12942
|
}
|
|
12880
|
-
result.resolved.set(
|
|
12943
|
+
result.resolved.set(path64, resolved);
|
|
12881
12944
|
return resolved;
|
|
12882
12945
|
}
|
|
12883
12946
|
function stringifyTokenValue(value) {
|
|
@@ -16934,6 +16997,252 @@ async function validateDiscussionDesignHardening(root, config) {
|
|
|
16934
16997
|
return issues;
|
|
16935
16998
|
}
|
|
16936
16999
|
|
|
17000
|
+
// src/core/validators/designAudit.ts
|
|
17001
|
+
var import_promises54 = require("fs/promises");
|
|
17002
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
17003
|
+
var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
|
|
17004
|
+
function resolveAuditConfig(config) {
|
|
17005
|
+
const audit = config.uiux?.audit;
|
|
17006
|
+
const profile = config.uiux?.qualityProfile ?? "default";
|
|
17007
|
+
return {
|
|
17008
|
+
enabled: audit?.enabled ?? true,
|
|
17009
|
+
slopDetection: audit?.slopDetection ?? true,
|
|
17010
|
+
qualityProfile: profile,
|
|
17011
|
+
maxPrimaryCtas: audit?.maxPrimaryCtas ?? 1,
|
|
17012
|
+
maxRawTokenLiteralWarnings: audit?.maxRawTokenLiteralWarnings ?? 5,
|
|
17013
|
+
maxDuplicateFindingsPerRule: audit?.maxDuplicateFindingsPerRule ?? 5
|
|
17014
|
+
};
|
|
17015
|
+
}
|
|
17016
|
+
function mapSeverity(tier, profile, category) {
|
|
17017
|
+
if (tier === 1) return "error";
|
|
17018
|
+
if (tier === 2) return profile === "strict" ? "error" : "warning";
|
|
17019
|
+
if (profile === "default") {
|
|
17020
|
+
return category && COSMETIC_CATEGORIES.includes(category) ? "info" : "warning";
|
|
17021
|
+
}
|
|
17022
|
+
return "warning";
|
|
17023
|
+
}
|
|
17024
|
+
function findingToIssue(finding, profile, rulePrefix = "audit") {
|
|
17025
|
+
const severity = mapSeverity(finding.severityTier, profile, finding.dimension);
|
|
17026
|
+
return issue(
|
|
17027
|
+
finding.ruleId,
|
|
17028
|
+
finding.message,
|
|
17029
|
+
severity,
|
|
17030
|
+
finding.file,
|
|
17031
|
+
`${rulePrefix}.${finding.dimension}`,
|
|
17032
|
+
finding.evidence.length > 0 ? finding.evidence : void 0,
|
|
17033
|
+
"compatibility",
|
|
17034
|
+
finding.guidance
|
|
17035
|
+
);
|
|
17036
|
+
}
|
|
17037
|
+
function extractSection2(content, heading) {
|
|
17038
|
+
const idx = content.indexOf(heading);
|
|
17039
|
+
if (idx === -1) return null;
|
|
17040
|
+
const start = idx + heading.length;
|
|
17041
|
+
const headingLevel = heading.match(/^#+/)?.[0]?.length ?? 3;
|
|
17042
|
+
const rest = content.slice(start);
|
|
17043
|
+
const headingPattern = new RegExp(`^#{1,${headingLevel}} `, "m");
|
|
17044
|
+
const nextHeadingMatch = headingPattern.exec(rest);
|
|
17045
|
+
const sectionContent = nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest;
|
|
17046
|
+
return sectionContent.trim() || null;
|
|
17047
|
+
}
|
|
17048
|
+
function checkCtaHierarchy(content, auditConfig, file) {
|
|
17049
|
+
const findings = [];
|
|
17050
|
+
const ctaSection = extractSection2(content, "### CTA Hierarchy");
|
|
17051
|
+
if (!ctaSection) return findings;
|
|
17052
|
+
const primaryLines = ctaSection.match(/^-\s*Primary:/gm) || [];
|
|
17053
|
+
const primaryCount = primaryLines.length;
|
|
17054
|
+
if (primaryCount === 0) {
|
|
17055
|
+
findings.push({
|
|
17056
|
+
ruleId: "QFAI-AUD-001",
|
|
17057
|
+
dimension: "visualHierarchy",
|
|
17058
|
+
severityTier: 1,
|
|
17059
|
+
message: "No primary CTA defined in CTA Hierarchy",
|
|
17060
|
+
why: "Every UI screen needs a clear primary action to guide users",
|
|
17061
|
+
evidence: [],
|
|
17062
|
+
guidance: "Define at least one primary CTA in the CTA Hierarchy section",
|
|
17063
|
+
file
|
|
17064
|
+
});
|
|
17065
|
+
}
|
|
17066
|
+
if (primaryCount > auditConfig.maxPrimaryCtas) {
|
|
17067
|
+
findings.push({
|
|
17068
|
+
ruleId: "QFAI-AUD-020",
|
|
17069
|
+
dimension: "visualHierarchy",
|
|
17070
|
+
severityTier: 2,
|
|
17071
|
+
message: `Multiple primary CTAs detected (${primaryCount} > ${auditConfig.maxPrimaryCtas})`,
|
|
17072
|
+
why: "Multiple primary CTAs create decision paralysis and weaken visual hierarchy",
|
|
17073
|
+
evidence: primaryLines.map((l) => l.trim()),
|
|
17074
|
+
guidance: "Reduce to a single primary CTA per screen; demote others to secondary",
|
|
17075
|
+
file
|
|
17076
|
+
});
|
|
17077
|
+
}
|
|
17078
|
+
return findings;
|
|
17079
|
+
}
|
|
17080
|
+
var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)/g;
|
|
17081
|
+
async function checkTokenDrift(root, auditConfig, cfg) {
|
|
17082
|
+
const findings = [];
|
|
17083
|
+
const configuredDir = cfg.uiux?.designTokensDir;
|
|
17084
|
+
const tokensDir = configuredDir ? import_node_path57.default.resolve(root, configuredDir) : import_node_path57.default.join(root, cfg.paths.contractsDir, "design");
|
|
17085
|
+
let hasTokenFiles = false;
|
|
17086
|
+
try {
|
|
17087
|
+
const entries = await (0, import_promises54.readdir)(tokensDir);
|
|
17088
|
+
hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
|
|
17089
|
+
} catch {
|
|
17090
|
+
return findings;
|
|
17091
|
+
}
|
|
17092
|
+
if (!hasTokenFiles) return findings;
|
|
17093
|
+
const contractsUiDir = import_node_path57.default.join(root, cfg.paths.contractsDir, "ui");
|
|
17094
|
+
let htmlFiles = [];
|
|
17095
|
+
try {
|
|
17096
|
+
const entries = await (0, import_promises54.readdir)(contractsUiDir);
|
|
17097
|
+
htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
|
|
17098
|
+
} catch {
|
|
17099
|
+
return findings;
|
|
17100
|
+
}
|
|
17101
|
+
let rawCount = 0;
|
|
17102
|
+
const sampleLiterals = [];
|
|
17103
|
+
for (const htmlFile of htmlFiles) {
|
|
17104
|
+
const content = await readSafe(import_node_path57.default.join(contractsUiDir, htmlFile));
|
|
17105
|
+
if (!content) continue;
|
|
17106
|
+
const matches = content.match(RAW_COLOR_RE);
|
|
17107
|
+
if (matches) {
|
|
17108
|
+
rawCount += matches.length;
|
|
17109
|
+
for (const m of matches) {
|
|
17110
|
+
if (sampleLiterals.length < 10) {
|
|
17111
|
+
sampleLiterals.push(m.toLowerCase());
|
|
17112
|
+
}
|
|
17113
|
+
}
|
|
17114
|
+
}
|
|
17115
|
+
}
|
|
17116
|
+
if (rawCount > auditConfig.maxRawTokenLiteralWarnings) {
|
|
17117
|
+
findings.push({
|
|
17118
|
+
ruleId: "QFAI-AUD-004",
|
|
17119
|
+
dimension: "tokenDiscipline",
|
|
17120
|
+
severityTier: 1,
|
|
17121
|
+
message: `Token drift: ${rawCount} raw color literal occurrences found (threshold: ${auditConfig.maxRawTokenLiteralWarnings})`,
|
|
17122
|
+
why: "Raw color values bypass design tokens, causing visual inconsistency",
|
|
17123
|
+
evidence: sampleLiterals,
|
|
17124
|
+
guidance: "Replace raw color literals with design token references"
|
|
17125
|
+
});
|
|
17126
|
+
}
|
|
17127
|
+
return findings;
|
|
17128
|
+
}
|
|
17129
|
+
function deduplicateFindings(issues, maxPerRule) {
|
|
17130
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17131
|
+
const result = [];
|
|
17132
|
+
for (const iss of issues) {
|
|
17133
|
+
const count = counts.get(iss.code) ?? 0;
|
|
17134
|
+
if (count < maxPerRule) {
|
|
17135
|
+
result.push(iss);
|
|
17136
|
+
}
|
|
17137
|
+
counts.set(iss.code, count + 1);
|
|
17138
|
+
}
|
|
17139
|
+
for (const [code, count] of counts) {
|
|
17140
|
+
if (count > maxPerRule) {
|
|
17141
|
+
result.push({
|
|
17142
|
+
code,
|
|
17143
|
+
severity: "info",
|
|
17144
|
+
category: "compatibility",
|
|
17145
|
+
message: `${count - maxPerRule} additional "${code}" finding(s) suppressed (max ${maxPerRule} per rule)`,
|
|
17146
|
+
rule: `audit.dedup.${code}`
|
|
17147
|
+
});
|
|
17148
|
+
}
|
|
17149
|
+
}
|
|
17150
|
+
return result;
|
|
17151
|
+
}
|
|
17152
|
+
async function validateDesignAudit(root, config) {
|
|
17153
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17154
|
+
if (!auditConfig.enabled) return [];
|
|
17155
|
+
const discussionDir = import_node_path57.default.join(root, config.paths.discussionDir);
|
|
17156
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17157
|
+
if (!packRoot) return [];
|
|
17158
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17159
|
+
if (!uiBearing) return [];
|
|
17160
|
+
const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
|
|
17161
|
+
const content = await readSafe(storyPath);
|
|
17162
|
+
if (!content) return [];
|
|
17163
|
+
const findings = [];
|
|
17164
|
+
findings.push(...checkCtaHierarchy(content, auditConfig, "03_Story-Workshop.md"));
|
|
17165
|
+
findings.push(...await checkTokenDrift(root, auditConfig, config));
|
|
17166
|
+
const issues = findings.map((f) => findingToIssue(f, auditConfig.qualityProfile));
|
|
17167
|
+
return deduplicateFindings(issues, auditConfig.maxDuplicateFindingsPerRule);
|
|
17168
|
+
}
|
|
17169
|
+
|
|
17170
|
+
// src/core/validators/designSlop.ts
|
|
17171
|
+
var import_node_fs2 = require("fs");
|
|
17172
|
+
var import_promises55 = require("fs/promises");
|
|
17173
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
17174
|
+
var import_node_url4 = require("url");
|
|
17175
|
+
function isValidSlopPattern(rule) {
|
|
17176
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
17177
|
+
const r = rule;
|
|
17178
|
+
return typeof r.id === "string" && typeof r.category === "string" && typeof r.tier === "number" && Array.isArray(r.scopes) && typeof r.match === "string" && typeof r.message === "string" && typeof r.guidance === "string";
|
|
17179
|
+
}
|
|
17180
|
+
async function loadSlopPatterns(jsonPath) {
|
|
17181
|
+
const raw = await (0, import_promises55.readFile)(jsonPath, "utf-8");
|
|
17182
|
+
const parsed = JSON.parse(raw);
|
|
17183
|
+
if (!Array.isArray(parsed)) return [];
|
|
17184
|
+
return parsed.filter((r) => isValidSlopPattern(r));
|
|
17185
|
+
}
|
|
17186
|
+
function defaultPatternsPath() {
|
|
17187
|
+
const base = __filename;
|
|
17188
|
+
const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
|
|
17189
|
+
const baseDir = import_node_path58.default.dirname(basePath);
|
|
17190
|
+
const candidates = [
|
|
17191
|
+
import_node_path58.default.join(baseDir, "designSlopPatterns.json"),
|
|
17192
|
+
import_node_path58.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
|
|
17193
|
+
import_node_path58.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
|
|
17194
|
+
];
|
|
17195
|
+
for (const c of candidates) {
|
|
17196
|
+
if ((0, import_node_fs2.existsSync)(c)) return c;
|
|
17197
|
+
}
|
|
17198
|
+
return candidates[0];
|
|
17199
|
+
}
|
|
17200
|
+
async function validateDesignSlop(root, config) {
|
|
17201
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17202
|
+
if (!auditConfig.enabled) return [];
|
|
17203
|
+
if (!auditConfig.slopDetection) return [];
|
|
17204
|
+
const discussionDir = import_node_path58.default.join(root, config.paths.discussionDir);
|
|
17205
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17206
|
+
if (!packRoot) return [];
|
|
17207
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17208
|
+
if (!uiBearing) return [];
|
|
17209
|
+
let patterns;
|
|
17210
|
+
try {
|
|
17211
|
+
patterns = await loadSlopPatterns(defaultPatternsPath());
|
|
17212
|
+
} catch {
|
|
17213
|
+
return [];
|
|
17214
|
+
}
|
|
17215
|
+
const findings = [];
|
|
17216
|
+
const seenRules = /* @__PURE__ */ new Set();
|
|
17217
|
+
for (const pattern of patterns) {
|
|
17218
|
+
let regex;
|
|
17219
|
+
try {
|
|
17220
|
+
regex = new RegExp(pattern.match, "gi");
|
|
17221
|
+
} catch {
|
|
17222
|
+
continue;
|
|
17223
|
+
}
|
|
17224
|
+
for (const scope of pattern.scopes) {
|
|
17225
|
+
const filePath = import_node_path58.default.join(packRoot, scope);
|
|
17226
|
+
const content = await readSafe(filePath);
|
|
17227
|
+
if (!content) continue;
|
|
17228
|
+
if (regex.test(content) && !seenRules.has(pattern.id)) {
|
|
17229
|
+
seenRules.add(pattern.id);
|
|
17230
|
+
findings.push({
|
|
17231
|
+
ruleId: pattern.id,
|
|
17232
|
+
dimension: pattern.category,
|
|
17233
|
+
severityTier: pattern.tier,
|
|
17234
|
+
message: pattern.message,
|
|
17235
|
+
why: `Slop pattern "${pattern.id}" matched in ${scope}`,
|
|
17236
|
+
evidence: [],
|
|
17237
|
+
guidance: pattern.guidance,
|
|
17238
|
+
file: scope
|
|
17239
|
+
});
|
|
17240
|
+
}
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17243
|
+
return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
|
|
17244
|
+
}
|
|
17245
|
+
|
|
16937
17246
|
// src/core/validate.ts
|
|
16938
17247
|
var UIUX_VALIDATION_BUDGET_MS = 2e3;
|
|
16939
17248
|
async function validateProject(root, configResult, options = {}) {
|
|
@@ -16951,7 +17260,9 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
16951
17260
|
() => validateBpApDb(root, config),
|
|
16952
17261
|
() => validateUiDefinitionConsistency(root, config),
|
|
16953
17262
|
() => validateResearchSummary(root, config),
|
|
16954
|
-
() => validateAgentDefinition(root, config)
|
|
17263
|
+
() => validateAgentDefinition(root, config),
|
|
17264
|
+
() => validateDesignAudit(root, config),
|
|
17265
|
+
() => validateDesignSlop(root, config)
|
|
16955
17266
|
];
|
|
16956
17267
|
const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
|
|
16957
17268
|
const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
|
|
@@ -17038,15 +17349,15 @@ var REPORT_GUARDRAILS_MAX = 20;
|
|
|
17038
17349
|
var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
|
|
17039
17350
|
var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
|
|
17040
17351
|
async function createReportData(root, validation, configResult) {
|
|
17041
|
-
const resolvedRoot =
|
|
17352
|
+
const resolvedRoot = import_node_path59.default.resolve(root);
|
|
17042
17353
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
17043
17354
|
const config = resolved.config;
|
|
17044
17355
|
const configPath = resolved.configPath;
|
|
17045
17356
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
17046
17357
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
17047
|
-
const apiRoot =
|
|
17048
|
-
const uiRoot =
|
|
17049
|
-
const dbRoot =
|
|
17358
|
+
const apiRoot = import_node_path59.default.join(contractsRoot, "api");
|
|
17359
|
+
const uiRoot = import_node_path59.default.join(contractsRoot, "ui");
|
|
17360
|
+
const dbRoot = import_node_path59.default.join(contractsRoot, "db");
|
|
17050
17361
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
17051
17362
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
17052
17363
|
const specEntries = await collectSpecEntries(specsRoot);
|
|
@@ -17374,6 +17685,8 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17374
17685
|
lines.push("");
|
|
17375
17686
|
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
17376
17687
|
lines.push("- [Change Issues](#change-issues)");
|
|
17688
|
+
lines.push("- [Design Audit Findings](#design-audit-findings)");
|
|
17689
|
+
lines.push("- [Slop Guardrails Findings](#slop-guardrails-findings)");
|
|
17377
17690
|
lines.push("- [Change Type](#change-type)");
|
|
17378
17691
|
lines.push("- [Waivers](#waivers)");
|
|
17379
17692
|
lines.push("- [Decision Guardrails](#decision-guardrails)");
|
|
@@ -17522,6 +17835,46 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17522
17835
|
lines.push("### Issues");
|
|
17523
17836
|
lines.push("");
|
|
17524
17837
|
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
17838
|
+
const auditIssues = data.issues.filter((i) => /^QFAI-AUD-/.test(i.code));
|
|
17839
|
+
if (auditIssues.length > 0) {
|
|
17840
|
+
lines.push("## Design Audit Findings");
|
|
17841
|
+
lines.push("");
|
|
17842
|
+
const byDimension = /* @__PURE__ */ new Map();
|
|
17843
|
+
for (const iss of auditIssues) {
|
|
17844
|
+
const dim = iss.rule?.replace(/^audit\./, "").split(".")[0] ?? "unknown";
|
|
17845
|
+
const group = byDimension.get(dim) ?? [];
|
|
17846
|
+
group.push(iss);
|
|
17847
|
+
byDimension.set(dim, group);
|
|
17848
|
+
}
|
|
17849
|
+
for (const [dim, dimIssues] of byDimension) {
|
|
17850
|
+
lines.push(`### ${dim}`);
|
|
17851
|
+
lines.push("");
|
|
17852
|
+
for (const iss of dimIssues) {
|
|
17853
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17854
|
+
}
|
|
17855
|
+
lines.push("");
|
|
17856
|
+
}
|
|
17857
|
+
}
|
|
17858
|
+
const slopIssues = data.issues.filter((i) => /^SLP-/.test(i.code));
|
|
17859
|
+
if (slopIssues.length > 0) {
|
|
17860
|
+
lines.push("## Slop Guardrails Findings");
|
|
17861
|
+
lines.push("");
|
|
17862
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
17863
|
+
for (const iss of slopIssues) {
|
|
17864
|
+
const cat = iss.rule?.replace(/^slop\./, "").split(".")[0] ?? "unknown";
|
|
17865
|
+
const group = byCategory.get(cat) ?? [];
|
|
17866
|
+
group.push(iss);
|
|
17867
|
+
byCategory.set(cat, group);
|
|
17868
|
+
}
|
|
17869
|
+
for (const [cat, catIssues] of byCategory) {
|
|
17870
|
+
lines.push(`### ${cat}`);
|
|
17871
|
+
lines.push("");
|
|
17872
|
+
for (const iss of catIssues) {
|
|
17873
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17874
|
+
}
|
|
17875
|
+
lines.push("");
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17525
17878
|
lines.push("## Change Type");
|
|
17526
17879
|
lines.push("");
|
|
17527
17880
|
lines.push("### Summary");
|
|
@@ -17941,7 +18294,7 @@ async function collectChangeTypeSummary(specsRoot) {
|
|
|
17941
18294
|
};
|
|
17942
18295
|
const deltaFiles = await collectDeltaFiles(specsRoot);
|
|
17943
18296
|
for (const deltaFile of deltaFiles) {
|
|
17944
|
-
const text = await (0,
|
|
18297
|
+
const text = await (0, import_promises56.readFile)(deltaFile, "utf-8");
|
|
17945
18298
|
const parsed = parseDeltaV1(text);
|
|
17946
18299
|
for (const entry of parsed.entries) {
|
|
17947
18300
|
if (!entry.meta) {
|
|
@@ -17978,7 +18331,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
17978
18331
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
17979
18332
|
}
|
|
17980
18333
|
for (const file of specFiles) {
|
|
17981
|
-
const text = await (0,
|
|
18334
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
17982
18335
|
const parsed = parseSpec(text, file);
|
|
17983
18336
|
const specKey = parsed.specId;
|
|
17984
18337
|
if (!specKey) {
|
|
@@ -18015,7 +18368,7 @@ async function collectIds(files) {
|
|
|
18015
18368
|
result[prefix] = /* @__PURE__ */ new Set();
|
|
18016
18369
|
}
|
|
18017
18370
|
for (const file of files) {
|
|
18018
|
-
const text = await (0,
|
|
18371
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
18019
18372
|
for (const prefix of ID_PREFIXES) {
|
|
18020
18373
|
const ids = extractIds(text, prefix);
|
|
18021
18374
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -18030,7 +18383,7 @@ async function collectIds(files) {
|
|
|
18030
18383
|
async function collectUpstreamIds(files) {
|
|
18031
18384
|
const ids = /* @__PURE__ */ new Set();
|
|
18032
18385
|
for (const file of files) {
|
|
18033
|
-
const text = await (0,
|
|
18386
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
18034
18387
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
18035
18388
|
}
|
|
18036
18389
|
return ids;
|
|
@@ -18051,7 +18404,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
18051
18404
|
}
|
|
18052
18405
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
18053
18406
|
for (const file of targetFiles) {
|
|
18054
|
-
const text = await (0,
|
|
18407
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
18055
18408
|
if (pattern.test(text)) {
|
|
18056
18409
|
return true;
|
|
18057
18410
|
}
|
|
@@ -18170,7 +18523,7 @@ function normalizeScSources(root, sources) {
|
|
|
18170
18523
|
async function countScenarios(scenarioFiles) {
|
|
18171
18524
|
let total = 0;
|
|
18172
18525
|
for (const file of scenarioFiles) {
|
|
18173
|
-
const text = await (0,
|
|
18526
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
18174
18527
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
18175
18528
|
if (!document || errors.length > 0) {
|
|
18176
18529
|
continue;
|
|
@@ -18201,7 +18554,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
|
|
|
18201
18554
|
let totalScenarios = 0;
|
|
18202
18555
|
let e2eCount = 0;
|
|
18203
18556
|
for (const file of scenarioFiles) {
|
|
18204
|
-
const text = await (0,
|
|
18557
|
+
const text = await (0, import_promises56.readFile)(file, "utf-8");
|
|
18205
18558
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
18206
18559
|
if (!document || errors.length > 0) {
|
|
18207
18560
|
continue;
|
|
@@ -18289,10 +18642,10 @@ function buildHotspots(issues) {
|
|
|
18289
18642
|
async function collectTddCoverage(entries) {
|
|
18290
18643
|
const specs = [];
|
|
18291
18644
|
for (const entry of entries) {
|
|
18292
|
-
const testCasesPath =
|
|
18645
|
+
const testCasesPath = import_node_path59.default.join(entry.dir, "06_Test-Cases.md");
|
|
18293
18646
|
let tcContent;
|
|
18294
18647
|
try {
|
|
18295
|
-
tcContent = await (0,
|
|
18648
|
+
tcContent = await (0, import_promises56.readFile)(testCasesPath, "utf-8");
|
|
18296
18649
|
} catch {
|
|
18297
18650
|
continue;
|
|
18298
18651
|
}
|
|
@@ -18324,10 +18677,10 @@ async function collectTddCoverage(entries) {
|
|
|
18324
18677
|
});
|
|
18325
18678
|
continue;
|
|
18326
18679
|
}
|
|
18327
|
-
const tddListPath =
|
|
18680
|
+
const tddListPath = import_node_path59.default.join(entry.dir, "tdd", "test-list.md");
|
|
18328
18681
|
let tddContent;
|
|
18329
18682
|
try {
|
|
18330
|
-
tddContent = await (0,
|
|
18683
|
+
tddContent = await (0, import_promises56.readFile)(tddListPath, "utf-8");
|
|
18331
18684
|
} catch {
|
|
18332
18685
|
specs.push({
|
|
18333
18686
|
specNumber: entry.specNumber,
|
|
@@ -18410,8 +18763,8 @@ async function collectTddCoverage(entries) {
|
|
|
18410
18763
|
}
|
|
18411
18764
|
|
|
18412
18765
|
// src/core/specPackReport.ts
|
|
18413
|
-
var
|
|
18414
|
-
var
|
|
18766
|
+
var import_promises57 = require("fs/promises");
|
|
18767
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
18415
18768
|
var REQUIRED_LEDGER_COLUMNS = [
|
|
18416
18769
|
"trace_id",
|
|
18417
18770
|
"obj_id",
|
|
@@ -18429,9 +18782,9 @@ async function writeSpecPackReports(root, config) {
|
|
|
18429
18782
|
const entries = await collectSpecEntries(specsRoot);
|
|
18430
18783
|
const contractIndex = await buildContractIndex(root, config);
|
|
18431
18784
|
for (const entry of entries) {
|
|
18432
|
-
const specName =
|
|
18433
|
-
const outputDir =
|
|
18434
|
-
await (0,
|
|
18785
|
+
const specName = import_node_path60.default.basename(entry.dir);
|
|
18786
|
+
const outputDir = import_node_path60.default.join(outRoot, specName);
|
|
18787
|
+
await (0, import_promises57.mkdir)(outputDir, { recursive: true });
|
|
18435
18788
|
const [acText, tcText, exText, ledgerText] = await Promise.all([
|
|
18436
18789
|
readSafe12(entry.acceptanceCriteriaPath),
|
|
18437
18790
|
readSafe12(entry.testCasesPath),
|
|
@@ -18456,14 +18809,14 @@ async function writeSpecPackReports(root, config) {
|
|
|
18456
18809
|
contractIds: contractIndex.ids
|
|
18457
18810
|
});
|
|
18458
18811
|
const graph = buildTraceabilityGraph(ledgerRows);
|
|
18459
|
-
await (0,
|
|
18460
|
-
|
|
18812
|
+
await (0, import_promises57.writeFile)(
|
|
18813
|
+
import_node_path60.default.join(outputDir, "coverage.md"),
|
|
18461
18814
|
`${formatCoverageMarkdown(specName, coverage)}
|
|
18462
18815
|
`,
|
|
18463
18816
|
"utf-8"
|
|
18464
18817
|
);
|
|
18465
|
-
await (0,
|
|
18466
|
-
|
|
18818
|
+
await (0, import_promises57.writeFile)(
|
|
18819
|
+
import_node_path60.default.join(outputDir, "traceability-graph.json"),
|
|
18467
18820
|
`${JSON.stringify(graph, null, 2)}
|
|
18468
18821
|
`,
|
|
18469
18822
|
"utf-8"
|
|
@@ -18638,7 +18991,7 @@ function getCell(row, indexByColumn, column) {
|
|
|
18638
18991
|
}
|
|
18639
18992
|
async function readSafe12(filePath) {
|
|
18640
18993
|
try {
|
|
18641
|
-
return await (0,
|
|
18994
|
+
return await (0, import_promises57.readFile)(filePath, "utf-8");
|
|
18642
18995
|
} catch {
|
|
18643
18996
|
return "";
|
|
18644
18997
|
}
|
|
@@ -18656,7 +19009,7 @@ function warnIfTruncated(scan, context) {
|
|
|
18656
19009
|
|
|
18657
19010
|
// src/cli/commands/report.ts
|
|
18658
19011
|
async function runReport(options) {
|
|
18659
|
-
const root =
|
|
19012
|
+
const root = import_node_path61.default.resolve(options.root);
|
|
18660
19013
|
const configResult = await loadConfig(root);
|
|
18661
19014
|
let validation;
|
|
18662
19015
|
let blockedByPhaseGuard = false;
|
|
@@ -18672,7 +19025,7 @@ async function runReport(options) {
|
|
|
18672
19025
|
validation = normalized;
|
|
18673
19026
|
} else {
|
|
18674
19027
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
18675
|
-
const inputPath =
|
|
19028
|
+
const inputPath = import_node_path61.default.isAbsolute(input) ? input : import_node_path61.default.resolve(root, input);
|
|
18676
19029
|
try {
|
|
18677
19030
|
validation = await readValidationResult(inputPath);
|
|
18678
19031
|
} catch (err) {
|
|
@@ -18699,11 +19052,11 @@ async function runReport(options) {
|
|
|
18699
19052
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
18700
19053
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
18701
19054
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
18702
|
-
const defaultOut = options.format === "json" ?
|
|
19055
|
+
const defaultOut = options.format === "json" ? import_node_path61.default.join(outRoot, "report.json") : import_node_path61.default.join(outRoot, "report.md");
|
|
18703
19056
|
const out = options.outPath ?? defaultOut;
|
|
18704
|
-
const outPath =
|
|
18705
|
-
await (0,
|
|
18706
|
-
await (0,
|
|
19057
|
+
const outPath = import_node_path61.default.isAbsolute(out) ? out : import_node_path61.default.resolve(root, out);
|
|
19058
|
+
await (0, import_promises58.mkdir)(import_node_path61.default.dirname(outPath), { recursive: true });
|
|
19059
|
+
await (0, import_promises58.writeFile)(outPath, `${output}
|
|
18707
19060
|
`, "utf-8");
|
|
18708
19061
|
await writeSpecPackReports(root, configResult.config);
|
|
18709
19062
|
if (blockedByPhaseGuard) {
|
|
@@ -18718,7 +19071,7 @@ async function runReport(options) {
|
|
|
18718
19071
|
info(`wrote report: ${outPath}`);
|
|
18719
19072
|
}
|
|
18720
19073
|
async function readValidationResult(inputPath) {
|
|
18721
|
-
const raw = await (0,
|
|
19074
|
+
const raw = await (0, import_promises58.readFile)(inputPath, "utf-8");
|
|
18722
19075
|
const parsed = JSON.parse(raw);
|
|
18723
19076
|
if (!isValidationResult(parsed)) {
|
|
18724
19077
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -18778,23 +19131,23 @@ function isMissingFileError2(error2) {
|
|
|
18778
19131
|
return record2.code === "ENOENT";
|
|
18779
19132
|
}
|
|
18780
19133
|
async function writeValidationResult(root, outputPath, result) {
|
|
18781
|
-
const abs =
|
|
18782
|
-
await (0,
|
|
18783
|
-
await (0,
|
|
19134
|
+
const abs = import_node_path61.default.isAbsolute(outputPath) ? outputPath : import_node_path61.default.resolve(root, outputPath);
|
|
19135
|
+
await (0, import_promises58.mkdir)(import_node_path61.default.dirname(abs), { recursive: true });
|
|
19136
|
+
await (0, import_promises58.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
18784
19137
|
`, "utf-8");
|
|
18785
19138
|
}
|
|
18786
19139
|
|
|
18787
19140
|
// src/cli/commands/validate.ts
|
|
18788
|
-
var
|
|
18789
|
-
var
|
|
19141
|
+
var import_promises60 = require("fs/promises");
|
|
19142
|
+
var import_node_path63 = __toESM(require("path"), 1);
|
|
18790
19143
|
|
|
18791
19144
|
// src/core/runLog.ts
|
|
18792
|
-
var
|
|
18793
|
-
var
|
|
19145
|
+
var import_promises59 = require("fs/promises");
|
|
19146
|
+
var import_node_path62 = __toESM(require("path"), 1);
|
|
18794
19147
|
async function writeValidateRunLog(input) {
|
|
18795
|
-
const root =
|
|
19148
|
+
const root = import_node_path62.default.resolve(input.root);
|
|
18796
19149
|
const outDir = resolvePath(root, input.config, "outDir");
|
|
18797
|
-
await (0,
|
|
19150
|
+
await (0, import_promises59.mkdir)(outDir, { recursive: true });
|
|
18798
19151
|
const { runId, reportDir } = await allocateRunReportDir(outDir, input.startedAt);
|
|
18799
19152
|
const relativeSpecsRoot = toRelativePath(root, resolvePath(root, input.config, "specsDir"));
|
|
18800
19153
|
const latestDiscussion = await findLatestPack(
|
|
@@ -18839,10 +19192,10 @@ async function writeValidateRunLog(input) {
|
|
|
18839
19192
|
errors,
|
|
18840
19193
|
warnings
|
|
18841
19194
|
});
|
|
18842
|
-
await writeJson(
|
|
18843
|
-
await writeJson(
|
|
18844
|
-
await writeJson(
|
|
18845
|
-
await (0,
|
|
19195
|
+
await writeJson(import_node_path62.default.join(reportDir, "run.json"), runJson);
|
|
19196
|
+
await writeJson(import_node_path62.default.join(reportDir, "validator.json"), validatorJson);
|
|
19197
|
+
await writeJson(import_node_path62.default.join(reportDir, "traceability.json"), traceabilityJson);
|
|
19198
|
+
await (0, import_promises59.writeFile)(import_node_path62.default.join(reportDir, "summary.md"), `${summaryMd}
|
|
18846
19199
|
`, "utf-8");
|
|
18847
19200
|
return {
|
|
18848
19201
|
runId,
|
|
@@ -18850,7 +19203,7 @@ async function writeValidateRunLog(input) {
|
|
|
18850
19203
|
};
|
|
18851
19204
|
}
|
|
18852
19205
|
async function writeJson(filePath, value) {
|
|
18853
|
-
await (0,
|
|
19206
|
+
await (0, import_promises59.writeFile)(filePath, `${JSON.stringify(value, null, 2)}
|
|
18854
19207
|
`, "utf-8");
|
|
18855
19208
|
}
|
|
18856
19209
|
function resolveStatus(result, override) {
|
|
@@ -18966,9 +19319,9 @@ async function allocateRunReportDir(outDir, startedAt) {
|
|
|
18966
19319
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
18967
19320
|
const candidateDate = new Date(startedAt.getTime() + attempt);
|
|
18968
19321
|
const runId = `run-${formatTimestamp17(candidateDate)}`;
|
|
18969
|
-
const reportDir =
|
|
19322
|
+
const reportDir = import_node_path62.default.join(outDir, runId);
|
|
18970
19323
|
try {
|
|
18971
|
-
await (0,
|
|
19324
|
+
await (0, import_promises59.mkdir)(reportDir);
|
|
18972
19325
|
return { runId, reportDir };
|
|
18973
19326
|
} catch (error2) {
|
|
18974
19327
|
if (isAlreadyExistsError(error2)) {
|
|
@@ -18997,7 +19350,7 @@ function shouldFail(result, failOn) {
|
|
|
18997
19350
|
// src/cli/commands/validate.ts
|
|
18998
19351
|
async function runValidate(options) {
|
|
18999
19352
|
const startedAt = /* @__PURE__ */ new Date();
|
|
19000
|
-
const root =
|
|
19353
|
+
const root = import_node_path63.default.resolve(options.root);
|
|
19001
19354
|
const configResult = await loadConfig(root);
|
|
19002
19355
|
const blockedIssue = buildCiRefinementIssue(options.phase);
|
|
19003
19356
|
const blockedByPhaseGuard = blockedIssue !== null;
|
|
@@ -19153,12 +19506,12 @@ function issueKey(issue2) {
|
|
|
19153
19506
|
}
|
|
19154
19507
|
async function emitJson(result, root, jsonPath) {
|
|
19155
19508
|
const abs = resolveJsonPath(root, jsonPath);
|
|
19156
|
-
await (0,
|
|
19157
|
-
await (0,
|
|
19509
|
+
await (0, import_promises60.mkdir)(import_node_path63.default.dirname(abs), { recursive: true });
|
|
19510
|
+
await (0, import_promises60.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
19158
19511
|
`, "utf-8");
|
|
19159
19512
|
}
|
|
19160
19513
|
function resolveJsonPath(root, jsonPath) {
|
|
19161
|
-
return
|
|
19514
|
+
return import_node_path63.default.isAbsolute(jsonPath) ? jsonPath : import_node_path63.default.resolve(root, jsonPath);
|
|
19162
19515
|
}
|
|
19163
19516
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
19164
19517
|
var ISSUE_EXPECTED_BY_CODE = {
|