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.mjs
CHANGED
|
@@ -483,6 +483,69 @@ function normalizeUiux(raw, configPath, issues) {
|
|
|
483
483
|
result.renderEvidence = renderEvidence;
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
|
+
if (raw.audit !== void 0) {
|
|
487
|
+
const audit = normalizeUiuxAudit(raw.audit, configPath, issues);
|
|
488
|
+
if (audit) {
|
|
489
|
+
result.audit = audit;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
493
|
+
}
|
|
494
|
+
function normalizeUiuxAudit(raw, configPath, issues) {
|
|
495
|
+
if (!isRecord(raw)) {
|
|
496
|
+
issues.push(configIssue(configPath, "uiux.audit \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
const result = {};
|
|
500
|
+
if (raw.enabled !== void 0) {
|
|
501
|
+
if (typeof raw.enabled === "boolean") {
|
|
502
|
+
result.enabled = raw.enabled;
|
|
503
|
+
} else {
|
|
504
|
+
issues.push(configIssue(configPath, "uiux.audit.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (raw.slopDetection !== void 0) {
|
|
508
|
+
if (typeof raw.slopDetection === "boolean") {
|
|
509
|
+
result.slopDetection = raw.slopDetection;
|
|
510
|
+
} else {
|
|
511
|
+
issues.push(
|
|
512
|
+
configIssue(configPath, "uiux.audit.slopDetection \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (raw.maxPrimaryCtas !== void 0) {
|
|
517
|
+
if (typeof raw.maxPrimaryCtas === "number" && Number.isFinite(raw.maxPrimaryCtas) && raw.maxPrimaryCtas >= 0) {
|
|
518
|
+
result.maxPrimaryCtas = raw.maxPrimaryCtas;
|
|
519
|
+
} else {
|
|
520
|
+
issues.push(
|
|
521
|
+
configIssue(configPath, "uiux.audit.maxPrimaryCtas \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (raw.maxRawTokenLiteralWarnings !== void 0) {
|
|
526
|
+
if (typeof raw.maxRawTokenLiteralWarnings === "number" && Number.isFinite(raw.maxRawTokenLiteralWarnings) && raw.maxRawTokenLiteralWarnings >= 0) {
|
|
527
|
+
result.maxRawTokenLiteralWarnings = raw.maxRawTokenLiteralWarnings;
|
|
528
|
+
} else {
|
|
529
|
+
issues.push(
|
|
530
|
+
configIssue(
|
|
531
|
+
configPath,
|
|
532
|
+
"uiux.audit.maxRawTokenLiteralWarnings \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
533
|
+
)
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (raw.maxDuplicateFindingsPerRule !== void 0) {
|
|
538
|
+
if (typeof raw.maxDuplicateFindingsPerRule === "number" && Number.isFinite(raw.maxDuplicateFindingsPerRule) && raw.maxDuplicateFindingsPerRule >= 0) {
|
|
539
|
+
result.maxDuplicateFindingsPerRule = raw.maxDuplicateFindingsPerRule;
|
|
540
|
+
} else {
|
|
541
|
+
issues.push(
|
|
542
|
+
configIssue(
|
|
543
|
+
configPath,
|
|
544
|
+
"uiux.audit.maxDuplicateFindingsPerRule \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
545
|
+
)
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
486
549
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
487
550
|
}
|
|
488
551
|
function normalizeRenderEvidence(raw, configPath, issues) {
|
|
@@ -1621,8 +1684,8 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
1621
1684
|
import path8 from "path";
|
|
1622
1685
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1623
1686
|
async function resolveToolVersion() {
|
|
1624
|
-
if ("1.7.
|
|
1625
|
-
return "1.7.
|
|
1687
|
+
if ("1.7.2".length > 0) {
|
|
1688
|
+
return "1.7.2";
|
|
1626
1689
|
}
|
|
1627
1690
|
try {
|
|
1628
1691
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -4195,9 +4258,9 @@ function applyRenderEvidence(evidence, options, autogenEnabled, outputPath) {
|
|
|
4195
4258
|
};
|
|
4196
4259
|
}
|
|
4197
4260
|
function buildRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
4198
|
-
const
|
|
4261
|
+
const path64 = resolveRenderOutPath(options.root, options.renderOut);
|
|
4199
4262
|
return {
|
|
4200
|
-
path:
|
|
4263
|
+
path: path64,
|
|
4201
4264
|
bundle: {
|
|
4202
4265
|
meta: {
|
|
4203
4266
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4209,7 +4272,7 @@ function buildRenderBundle(toolVersion, command, options, autogenEnabled) {
|
|
|
4209
4272
|
requested: true,
|
|
4210
4273
|
autogenEnabled,
|
|
4211
4274
|
viewports: normalizeRenderViewports(options.renderViewports),
|
|
4212
|
-
outputPath:
|
|
4275
|
+
outputPath: path64,
|
|
4213
4276
|
reason: autogenEnabled ? "render evidence capture not implemented in this slice" : "render requested without autogen-ui-fidelity"
|
|
4214
4277
|
}
|
|
4215
4278
|
}
|
|
@@ -4275,8 +4338,8 @@ function extractRouteHintsFromEvidence(evidence) {
|
|
|
4275
4338
|
}
|
|
4276
4339
|
|
|
4277
4340
|
// src/cli/commands/report.ts
|
|
4278
|
-
import { mkdir as mkdir8, readFile as
|
|
4279
|
-
import
|
|
4341
|
+
import { mkdir as mkdir8, readFile as readFile45, writeFile as writeFile7 } from "fs/promises";
|
|
4342
|
+
import path61 from "path";
|
|
4280
4343
|
|
|
4281
4344
|
// src/core/normalize.ts
|
|
4282
4345
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -4371,8 +4434,8 @@ async function createPhaseGuardResult(phase, blockedIssue) {
|
|
|
4371
4434
|
}
|
|
4372
4435
|
|
|
4373
4436
|
// src/core/report.ts
|
|
4374
|
-
import { readFile as
|
|
4375
|
-
import
|
|
4437
|
+
import { readFile as readFile43 } from "fs/promises";
|
|
4438
|
+
import path59 from "path";
|
|
4376
4439
|
|
|
4377
4440
|
// src/core/contractIndex.ts
|
|
4378
4441
|
import { readFile as readFile10 } from "fs/promises";
|
|
@@ -12781,7 +12844,7 @@ function collectLayer(layer, layerName, target, errors) {
|
|
|
12781
12844
|
}
|
|
12782
12845
|
function flattenTokens(obj, prefix, target, errors) {
|
|
12783
12846
|
for (const [key, value] of Object.entries(obj)) {
|
|
12784
|
-
const
|
|
12847
|
+
const path64 = `${prefix}.${key}`;
|
|
12785
12848
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
12786
12849
|
const record2 = value;
|
|
12787
12850
|
if ("$value" in record2) {
|
|
@@ -12797,9 +12860,9 @@ function flattenTokens(obj, prefix, target, errors) {
|
|
|
12797
12860
|
if (typeof record2.platform === "string") {
|
|
12798
12861
|
token.platform = record2.platform;
|
|
12799
12862
|
}
|
|
12800
|
-
target.set(
|
|
12863
|
+
target.set(path64, token);
|
|
12801
12864
|
} else {
|
|
12802
|
-
flattenTokens(record2,
|
|
12865
|
+
flattenTokens(record2, path64, target, errors);
|
|
12803
12866
|
}
|
|
12804
12867
|
}
|
|
12805
12868
|
}
|
|
@@ -12809,44 +12872,44 @@ function resolveAllReferences(result) {
|
|
|
12809
12872
|
for (const [key, val] of result.primitives) allTokens.set(key, val);
|
|
12810
12873
|
for (const [key, val] of result.semantics) allTokens.set(key, val);
|
|
12811
12874
|
for (const [key, val] of result.components) allTokens.set(key, val);
|
|
12812
|
-
for (const [
|
|
12813
|
-
resolveTokenRef(
|
|
12875
|
+
for (const [path64] of allTokens) {
|
|
12876
|
+
resolveTokenRef(path64, allTokens, /* @__PURE__ */ new Set(), 0, result);
|
|
12814
12877
|
}
|
|
12815
12878
|
}
|
|
12816
|
-
function resolveTokenRef(
|
|
12817
|
-
if (result.resolved.has(
|
|
12818
|
-
return result.resolved.get(
|
|
12879
|
+
function resolveTokenRef(path64, allTokens, visited, depth, result) {
|
|
12880
|
+
if (result.resolved.has(path64)) {
|
|
12881
|
+
return result.resolved.get(path64);
|
|
12819
12882
|
}
|
|
12820
12883
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
12821
12884
|
result.errors.push({
|
|
12822
|
-
message: `Max reference depth exceeded at: ${
|
|
12823
|
-
path:
|
|
12885
|
+
message: `Max reference depth exceeded at: ${path64}`,
|
|
12886
|
+
path: path64
|
|
12824
12887
|
});
|
|
12825
12888
|
return void 0;
|
|
12826
12889
|
}
|
|
12827
|
-
if (visited.has(
|
|
12890
|
+
if (visited.has(path64)) {
|
|
12828
12891
|
result.errors.push({
|
|
12829
|
-
message: `Circular reference detected: ${
|
|
12830
|
-
path:
|
|
12892
|
+
message: `Circular reference detected: ${path64}`,
|
|
12893
|
+
path: path64
|
|
12831
12894
|
});
|
|
12832
12895
|
return void 0;
|
|
12833
12896
|
}
|
|
12834
|
-
const token = allTokens.get(
|
|
12897
|
+
const token = allTokens.get(path64);
|
|
12835
12898
|
if (!token) {
|
|
12836
12899
|
return void 0;
|
|
12837
12900
|
}
|
|
12838
12901
|
if (typeof token.$value !== "string") {
|
|
12839
12902
|
const rawValue2 = stringifyTokenValue(token.$value);
|
|
12840
|
-
result.resolved.set(
|
|
12903
|
+
result.resolved.set(path64, rawValue2);
|
|
12841
12904
|
return rawValue2;
|
|
12842
12905
|
}
|
|
12843
12906
|
const rawValue = stringifyTokenValue(token.$value);
|
|
12844
12907
|
const refs = [...rawValue.matchAll(REF_PATTERN)];
|
|
12845
12908
|
if (refs.length === 0) {
|
|
12846
|
-
result.resolved.set(
|
|
12909
|
+
result.resolved.set(path64, rawValue);
|
|
12847
12910
|
return rawValue;
|
|
12848
12911
|
}
|
|
12849
|
-
visited.add(
|
|
12912
|
+
visited.add(path64);
|
|
12850
12913
|
let resolved = rawValue;
|
|
12851
12914
|
for (const ref of refs) {
|
|
12852
12915
|
const refPath = ref[1];
|
|
@@ -12854,8 +12917,8 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12854
12917
|
const refToken = allTokens.get(refPath);
|
|
12855
12918
|
if (!refToken) {
|
|
12856
12919
|
result.errors.push({
|
|
12857
|
-
message: `Unresolved token reference: {${refPath}} at ${
|
|
12858
|
-
path:
|
|
12920
|
+
message: `Unresolved token reference: {${refPath}} at ${path64}`,
|
|
12921
|
+
path: path64
|
|
12859
12922
|
});
|
|
12860
12923
|
continue;
|
|
12861
12924
|
}
|
|
@@ -12864,7 +12927,7 @@ function resolveTokenRef(path62, allTokens, visited, depth, result) {
|
|
|
12864
12927
|
resolved = resolved.split(`{${refPath}}`).join(refValue);
|
|
12865
12928
|
}
|
|
12866
12929
|
}
|
|
12867
|
-
result.resolved.set(
|
|
12930
|
+
result.resolved.set(path64, resolved);
|
|
12868
12931
|
return resolved;
|
|
12869
12932
|
}
|
|
12870
12933
|
function stringifyTokenValue(value) {
|
|
@@ -16921,6 +16984,252 @@ async function validateDiscussionDesignHardening(root, config) {
|
|
|
16921
16984
|
return issues;
|
|
16922
16985
|
}
|
|
16923
16986
|
|
|
16987
|
+
// src/core/validators/designAudit.ts
|
|
16988
|
+
import { readdir as readdir11 } from "fs/promises";
|
|
16989
|
+
import path57 from "path";
|
|
16990
|
+
var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
|
|
16991
|
+
function resolveAuditConfig(config) {
|
|
16992
|
+
const audit = config.uiux?.audit;
|
|
16993
|
+
const profile = config.uiux?.qualityProfile ?? "default";
|
|
16994
|
+
return {
|
|
16995
|
+
enabled: audit?.enabled ?? true,
|
|
16996
|
+
slopDetection: audit?.slopDetection ?? true,
|
|
16997
|
+
qualityProfile: profile,
|
|
16998
|
+
maxPrimaryCtas: audit?.maxPrimaryCtas ?? 1,
|
|
16999
|
+
maxRawTokenLiteralWarnings: audit?.maxRawTokenLiteralWarnings ?? 5,
|
|
17000
|
+
maxDuplicateFindingsPerRule: audit?.maxDuplicateFindingsPerRule ?? 5
|
|
17001
|
+
};
|
|
17002
|
+
}
|
|
17003
|
+
function mapSeverity(tier, profile, category) {
|
|
17004
|
+
if (tier === 1) return "error";
|
|
17005
|
+
if (tier === 2) return profile === "strict" ? "error" : "warning";
|
|
17006
|
+
if (profile === "default") {
|
|
17007
|
+
return category && COSMETIC_CATEGORIES.includes(category) ? "info" : "warning";
|
|
17008
|
+
}
|
|
17009
|
+
return "warning";
|
|
17010
|
+
}
|
|
17011
|
+
function findingToIssue(finding, profile, rulePrefix = "audit") {
|
|
17012
|
+
const severity = mapSeverity(finding.severityTier, profile, finding.dimension);
|
|
17013
|
+
return issue(
|
|
17014
|
+
finding.ruleId,
|
|
17015
|
+
finding.message,
|
|
17016
|
+
severity,
|
|
17017
|
+
finding.file,
|
|
17018
|
+
`${rulePrefix}.${finding.dimension}`,
|
|
17019
|
+
finding.evidence.length > 0 ? finding.evidence : void 0,
|
|
17020
|
+
"compatibility",
|
|
17021
|
+
finding.guidance
|
|
17022
|
+
);
|
|
17023
|
+
}
|
|
17024
|
+
function extractSection2(content, heading) {
|
|
17025
|
+
const idx = content.indexOf(heading);
|
|
17026
|
+
if (idx === -1) return null;
|
|
17027
|
+
const start = idx + heading.length;
|
|
17028
|
+
const headingLevel = heading.match(/^#+/)?.[0]?.length ?? 3;
|
|
17029
|
+
const rest = content.slice(start);
|
|
17030
|
+
const headingPattern = new RegExp(`^#{1,${headingLevel}} `, "m");
|
|
17031
|
+
const nextHeadingMatch = headingPattern.exec(rest);
|
|
17032
|
+
const sectionContent = nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest;
|
|
17033
|
+
return sectionContent.trim() || null;
|
|
17034
|
+
}
|
|
17035
|
+
function checkCtaHierarchy(content, auditConfig, file) {
|
|
17036
|
+
const findings = [];
|
|
17037
|
+
const ctaSection = extractSection2(content, "### CTA Hierarchy");
|
|
17038
|
+
if (!ctaSection) return findings;
|
|
17039
|
+
const primaryLines = ctaSection.match(/^-\s*Primary:/gm) || [];
|
|
17040
|
+
const primaryCount = primaryLines.length;
|
|
17041
|
+
if (primaryCount === 0) {
|
|
17042
|
+
findings.push({
|
|
17043
|
+
ruleId: "QFAI-AUD-001",
|
|
17044
|
+
dimension: "visualHierarchy",
|
|
17045
|
+
severityTier: 1,
|
|
17046
|
+
message: "No primary CTA defined in CTA Hierarchy",
|
|
17047
|
+
why: "Every UI screen needs a clear primary action to guide users",
|
|
17048
|
+
evidence: [],
|
|
17049
|
+
guidance: "Define at least one primary CTA in the CTA Hierarchy section",
|
|
17050
|
+
file
|
|
17051
|
+
});
|
|
17052
|
+
}
|
|
17053
|
+
if (primaryCount > auditConfig.maxPrimaryCtas) {
|
|
17054
|
+
findings.push({
|
|
17055
|
+
ruleId: "QFAI-AUD-020",
|
|
17056
|
+
dimension: "visualHierarchy",
|
|
17057
|
+
severityTier: 2,
|
|
17058
|
+
message: `Multiple primary CTAs detected (${primaryCount} > ${auditConfig.maxPrimaryCtas})`,
|
|
17059
|
+
why: "Multiple primary CTAs create decision paralysis and weaken visual hierarchy",
|
|
17060
|
+
evidence: primaryLines.map((l) => l.trim()),
|
|
17061
|
+
guidance: "Reduce to a single primary CTA per screen; demote others to secondary",
|
|
17062
|
+
file
|
|
17063
|
+
});
|
|
17064
|
+
}
|
|
17065
|
+
return findings;
|
|
17066
|
+
}
|
|
17067
|
+
var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)/g;
|
|
17068
|
+
async function checkTokenDrift(root, auditConfig, cfg) {
|
|
17069
|
+
const findings = [];
|
|
17070
|
+
const configuredDir = cfg.uiux?.designTokensDir;
|
|
17071
|
+
const tokensDir = configuredDir ? path57.resolve(root, configuredDir) : path57.join(root, cfg.paths.contractsDir, "design");
|
|
17072
|
+
let hasTokenFiles = false;
|
|
17073
|
+
try {
|
|
17074
|
+
const entries = await readdir11(tokensDir);
|
|
17075
|
+
hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
|
|
17076
|
+
} catch {
|
|
17077
|
+
return findings;
|
|
17078
|
+
}
|
|
17079
|
+
if (!hasTokenFiles) return findings;
|
|
17080
|
+
const contractsUiDir = path57.join(root, cfg.paths.contractsDir, "ui");
|
|
17081
|
+
let htmlFiles = [];
|
|
17082
|
+
try {
|
|
17083
|
+
const entries = await readdir11(contractsUiDir);
|
|
17084
|
+
htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
|
|
17085
|
+
} catch {
|
|
17086
|
+
return findings;
|
|
17087
|
+
}
|
|
17088
|
+
let rawCount = 0;
|
|
17089
|
+
const sampleLiterals = [];
|
|
17090
|
+
for (const htmlFile of htmlFiles) {
|
|
17091
|
+
const content = await readSafe(path57.join(contractsUiDir, htmlFile));
|
|
17092
|
+
if (!content) continue;
|
|
17093
|
+
const matches = content.match(RAW_COLOR_RE);
|
|
17094
|
+
if (matches) {
|
|
17095
|
+
rawCount += matches.length;
|
|
17096
|
+
for (const m of matches) {
|
|
17097
|
+
if (sampleLiterals.length < 10) {
|
|
17098
|
+
sampleLiterals.push(m.toLowerCase());
|
|
17099
|
+
}
|
|
17100
|
+
}
|
|
17101
|
+
}
|
|
17102
|
+
}
|
|
17103
|
+
if (rawCount > auditConfig.maxRawTokenLiteralWarnings) {
|
|
17104
|
+
findings.push({
|
|
17105
|
+
ruleId: "QFAI-AUD-004",
|
|
17106
|
+
dimension: "tokenDiscipline",
|
|
17107
|
+
severityTier: 1,
|
|
17108
|
+
message: `Token drift: ${rawCount} raw color literal occurrences found (threshold: ${auditConfig.maxRawTokenLiteralWarnings})`,
|
|
17109
|
+
why: "Raw color values bypass design tokens, causing visual inconsistency",
|
|
17110
|
+
evidence: sampleLiterals,
|
|
17111
|
+
guidance: "Replace raw color literals with design token references"
|
|
17112
|
+
});
|
|
17113
|
+
}
|
|
17114
|
+
return findings;
|
|
17115
|
+
}
|
|
17116
|
+
function deduplicateFindings(issues, maxPerRule) {
|
|
17117
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17118
|
+
const result = [];
|
|
17119
|
+
for (const iss of issues) {
|
|
17120
|
+
const count = counts.get(iss.code) ?? 0;
|
|
17121
|
+
if (count < maxPerRule) {
|
|
17122
|
+
result.push(iss);
|
|
17123
|
+
}
|
|
17124
|
+
counts.set(iss.code, count + 1);
|
|
17125
|
+
}
|
|
17126
|
+
for (const [code, count] of counts) {
|
|
17127
|
+
if (count > maxPerRule) {
|
|
17128
|
+
result.push({
|
|
17129
|
+
code,
|
|
17130
|
+
severity: "info",
|
|
17131
|
+
category: "compatibility",
|
|
17132
|
+
message: `${count - maxPerRule} additional "${code}" finding(s) suppressed (max ${maxPerRule} per rule)`,
|
|
17133
|
+
rule: `audit.dedup.${code}`
|
|
17134
|
+
});
|
|
17135
|
+
}
|
|
17136
|
+
}
|
|
17137
|
+
return result;
|
|
17138
|
+
}
|
|
17139
|
+
async function validateDesignAudit(root, config) {
|
|
17140
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17141
|
+
if (!auditConfig.enabled) return [];
|
|
17142
|
+
const discussionDir = path57.join(root, config.paths.discussionDir);
|
|
17143
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17144
|
+
if (!packRoot) return [];
|
|
17145
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17146
|
+
if (!uiBearing) return [];
|
|
17147
|
+
const storyPath = path57.join(packRoot, "03_Story-Workshop.md");
|
|
17148
|
+
const content = await readSafe(storyPath);
|
|
17149
|
+
if (!content) return [];
|
|
17150
|
+
const findings = [];
|
|
17151
|
+
findings.push(...checkCtaHierarchy(content, auditConfig, "03_Story-Workshop.md"));
|
|
17152
|
+
findings.push(...await checkTokenDrift(root, auditConfig, config));
|
|
17153
|
+
const issues = findings.map((f) => findingToIssue(f, auditConfig.qualityProfile));
|
|
17154
|
+
return deduplicateFindings(issues, auditConfig.maxDuplicateFindingsPerRule);
|
|
17155
|
+
}
|
|
17156
|
+
|
|
17157
|
+
// src/core/validators/designSlop.ts
|
|
17158
|
+
import { existsSync as existsSync2 } from "fs";
|
|
17159
|
+
import { readFile as readFile42 } from "fs/promises";
|
|
17160
|
+
import path58 from "path";
|
|
17161
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17162
|
+
function isValidSlopPattern(rule) {
|
|
17163
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
17164
|
+
const r = rule;
|
|
17165
|
+
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";
|
|
17166
|
+
}
|
|
17167
|
+
async function loadSlopPatterns(jsonPath) {
|
|
17168
|
+
const raw = await readFile42(jsonPath, "utf-8");
|
|
17169
|
+
const parsed = JSON.parse(raw);
|
|
17170
|
+
if (!Array.isArray(parsed)) return [];
|
|
17171
|
+
return parsed.filter((r) => isValidSlopPattern(r));
|
|
17172
|
+
}
|
|
17173
|
+
function defaultPatternsPath() {
|
|
17174
|
+
const base = import.meta.url;
|
|
17175
|
+
const basePath = base.startsWith("file:") ? fileURLToPath4(base) : base;
|
|
17176
|
+
const baseDir = path58.dirname(basePath);
|
|
17177
|
+
const candidates = [
|
|
17178
|
+
path58.join(baseDir, "designSlopPatterns.json"),
|
|
17179
|
+
path58.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
|
|
17180
|
+
path58.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
|
|
17181
|
+
];
|
|
17182
|
+
for (const c of candidates) {
|
|
17183
|
+
if (existsSync2(c)) return c;
|
|
17184
|
+
}
|
|
17185
|
+
return candidates[0];
|
|
17186
|
+
}
|
|
17187
|
+
async function validateDesignSlop(root, config) {
|
|
17188
|
+
const auditConfig = resolveAuditConfig(config);
|
|
17189
|
+
if (!auditConfig.enabled) return [];
|
|
17190
|
+
if (!auditConfig.slopDetection) return [];
|
|
17191
|
+
const discussionDir = path58.join(root, config.paths.discussionDir);
|
|
17192
|
+
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17193
|
+
if (!packRoot) return [];
|
|
17194
|
+
const uiBearing = await isUiBearing(packRoot);
|
|
17195
|
+
if (!uiBearing) return [];
|
|
17196
|
+
let patterns;
|
|
17197
|
+
try {
|
|
17198
|
+
patterns = await loadSlopPatterns(defaultPatternsPath());
|
|
17199
|
+
} catch {
|
|
17200
|
+
return [];
|
|
17201
|
+
}
|
|
17202
|
+
const findings = [];
|
|
17203
|
+
const seenRules = /* @__PURE__ */ new Set();
|
|
17204
|
+
for (const pattern of patterns) {
|
|
17205
|
+
let regex;
|
|
17206
|
+
try {
|
|
17207
|
+
regex = new RegExp(pattern.match, "gi");
|
|
17208
|
+
} catch {
|
|
17209
|
+
continue;
|
|
17210
|
+
}
|
|
17211
|
+
for (const scope of pattern.scopes) {
|
|
17212
|
+
const filePath = path58.join(packRoot, scope);
|
|
17213
|
+
const content = await readSafe(filePath);
|
|
17214
|
+
if (!content) continue;
|
|
17215
|
+
if (regex.test(content) && !seenRules.has(pattern.id)) {
|
|
17216
|
+
seenRules.add(pattern.id);
|
|
17217
|
+
findings.push({
|
|
17218
|
+
ruleId: pattern.id,
|
|
17219
|
+
dimension: pattern.category,
|
|
17220
|
+
severityTier: pattern.tier,
|
|
17221
|
+
message: pattern.message,
|
|
17222
|
+
why: `Slop pattern "${pattern.id}" matched in ${scope}`,
|
|
17223
|
+
evidence: [],
|
|
17224
|
+
guidance: pattern.guidance,
|
|
17225
|
+
file: scope
|
|
17226
|
+
});
|
|
17227
|
+
}
|
|
17228
|
+
}
|
|
17229
|
+
}
|
|
17230
|
+
return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
|
|
17231
|
+
}
|
|
17232
|
+
|
|
16924
17233
|
// src/core/validate.ts
|
|
16925
17234
|
var UIUX_VALIDATION_BUDGET_MS = 2e3;
|
|
16926
17235
|
async function validateProject(root, configResult, options = {}) {
|
|
@@ -16938,7 +17247,9 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
16938
17247
|
() => validateBpApDb(root, config),
|
|
16939
17248
|
() => validateUiDefinitionConsistency(root, config),
|
|
16940
17249
|
() => validateResearchSummary(root, config),
|
|
16941
|
-
() => validateAgentDefinition(root, config)
|
|
17250
|
+
() => validateAgentDefinition(root, config),
|
|
17251
|
+
() => validateDesignAudit(root, config),
|
|
17252
|
+
() => validateDesignSlop(root, config)
|
|
16942
17253
|
];
|
|
16943
17254
|
const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
|
|
16944
17255
|
const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
|
|
@@ -17025,15 +17336,15 @@ var REPORT_GUARDRAILS_MAX = 20;
|
|
|
17025
17336
|
var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
|
|
17026
17337
|
var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
|
|
17027
17338
|
async function createReportData(root, validation, configResult) {
|
|
17028
|
-
const resolvedRoot =
|
|
17339
|
+
const resolvedRoot = path59.resolve(root);
|
|
17029
17340
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
17030
17341
|
const config = resolved.config;
|
|
17031
17342
|
const configPath = resolved.configPath;
|
|
17032
17343
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
17033
17344
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
17034
|
-
const apiRoot =
|
|
17035
|
-
const uiRoot =
|
|
17036
|
-
const dbRoot =
|
|
17345
|
+
const apiRoot = path59.join(contractsRoot, "api");
|
|
17346
|
+
const uiRoot = path59.join(contractsRoot, "ui");
|
|
17347
|
+
const dbRoot = path59.join(contractsRoot, "db");
|
|
17037
17348
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
17038
17349
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
17039
17350
|
const specEntries = await collectSpecEntries(specsRoot);
|
|
@@ -17361,6 +17672,8 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17361
17672
|
lines.push("");
|
|
17362
17673
|
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
17363
17674
|
lines.push("- [Change Issues](#change-issues)");
|
|
17675
|
+
lines.push("- [Design Audit Findings](#design-audit-findings)");
|
|
17676
|
+
lines.push("- [Slop Guardrails Findings](#slop-guardrails-findings)");
|
|
17364
17677
|
lines.push("- [Change Type](#change-type)");
|
|
17365
17678
|
lines.push("- [Waivers](#waivers)");
|
|
17366
17679
|
lines.push("- [Decision Guardrails](#decision-guardrails)");
|
|
@@ -17509,6 +17822,46 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
17509
17822
|
lines.push("### Issues");
|
|
17510
17823
|
lines.push("");
|
|
17511
17824
|
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
17825
|
+
const auditIssues = data.issues.filter((i) => /^QFAI-AUD-/.test(i.code));
|
|
17826
|
+
if (auditIssues.length > 0) {
|
|
17827
|
+
lines.push("## Design Audit Findings");
|
|
17828
|
+
lines.push("");
|
|
17829
|
+
const byDimension = /* @__PURE__ */ new Map();
|
|
17830
|
+
for (const iss of auditIssues) {
|
|
17831
|
+
const dim = iss.rule?.replace(/^audit\./, "").split(".")[0] ?? "unknown";
|
|
17832
|
+
const group = byDimension.get(dim) ?? [];
|
|
17833
|
+
group.push(iss);
|
|
17834
|
+
byDimension.set(dim, group);
|
|
17835
|
+
}
|
|
17836
|
+
for (const [dim, dimIssues] of byDimension) {
|
|
17837
|
+
lines.push(`### ${dim}`);
|
|
17838
|
+
lines.push("");
|
|
17839
|
+
for (const iss of dimIssues) {
|
|
17840
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17841
|
+
}
|
|
17842
|
+
lines.push("");
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
17845
|
+
const slopIssues = data.issues.filter((i) => /^SLP-/.test(i.code));
|
|
17846
|
+
if (slopIssues.length > 0) {
|
|
17847
|
+
lines.push("## Slop Guardrails Findings");
|
|
17848
|
+
lines.push("");
|
|
17849
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
17850
|
+
for (const iss of slopIssues) {
|
|
17851
|
+
const cat = iss.rule?.replace(/^slop\./, "").split(".")[0] ?? "unknown";
|
|
17852
|
+
const group = byCategory.get(cat) ?? [];
|
|
17853
|
+
group.push(iss);
|
|
17854
|
+
byCategory.set(cat, group);
|
|
17855
|
+
}
|
|
17856
|
+
for (const [cat, catIssues] of byCategory) {
|
|
17857
|
+
lines.push(`### ${cat}`);
|
|
17858
|
+
lines.push("");
|
|
17859
|
+
for (const iss of catIssues) {
|
|
17860
|
+
lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
|
|
17861
|
+
}
|
|
17862
|
+
lines.push("");
|
|
17863
|
+
}
|
|
17864
|
+
}
|
|
17512
17865
|
lines.push("## Change Type");
|
|
17513
17866
|
lines.push("");
|
|
17514
17867
|
lines.push("### Summary");
|
|
@@ -17928,7 +18281,7 @@ async function collectChangeTypeSummary(specsRoot) {
|
|
|
17928
18281
|
};
|
|
17929
18282
|
const deltaFiles = await collectDeltaFiles(specsRoot);
|
|
17930
18283
|
for (const deltaFile of deltaFiles) {
|
|
17931
|
-
const text = await
|
|
18284
|
+
const text = await readFile43(deltaFile, "utf-8");
|
|
17932
18285
|
const parsed = parseDeltaV1(text);
|
|
17933
18286
|
for (const entry of parsed.entries) {
|
|
17934
18287
|
if (!entry.meta) {
|
|
@@ -17965,7 +18318,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
17965
18318
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
17966
18319
|
}
|
|
17967
18320
|
for (const file of specFiles) {
|
|
17968
|
-
const text = await
|
|
18321
|
+
const text = await readFile43(file, "utf-8");
|
|
17969
18322
|
const parsed = parseSpec(text, file);
|
|
17970
18323
|
const specKey = parsed.specId;
|
|
17971
18324
|
if (!specKey) {
|
|
@@ -18002,7 +18355,7 @@ async function collectIds(files) {
|
|
|
18002
18355
|
result[prefix] = /* @__PURE__ */ new Set();
|
|
18003
18356
|
}
|
|
18004
18357
|
for (const file of files) {
|
|
18005
|
-
const text = await
|
|
18358
|
+
const text = await readFile43(file, "utf-8");
|
|
18006
18359
|
for (const prefix of ID_PREFIXES) {
|
|
18007
18360
|
const ids = extractIds(text, prefix);
|
|
18008
18361
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -18017,7 +18370,7 @@ async function collectIds(files) {
|
|
|
18017
18370
|
async function collectUpstreamIds(files) {
|
|
18018
18371
|
const ids = /* @__PURE__ */ new Set();
|
|
18019
18372
|
for (const file of files) {
|
|
18020
|
-
const text = await
|
|
18373
|
+
const text = await readFile43(file, "utf-8");
|
|
18021
18374
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
18022
18375
|
}
|
|
18023
18376
|
return ids;
|
|
@@ -18038,7 +18391,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
18038
18391
|
}
|
|
18039
18392
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
18040
18393
|
for (const file of targetFiles) {
|
|
18041
|
-
const text = await
|
|
18394
|
+
const text = await readFile43(file, "utf-8");
|
|
18042
18395
|
if (pattern.test(text)) {
|
|
18043
18396
|
return true;
|
|
18044
18397
|
}
|
|
@@ -18157,7 +18510,7 @@ function normalizeScSources(root, sources) {
|
|
|
18157
18510
|
async function countScenarios(scenarioFiles) {
|
|
18158
18511
|
let total = 0;
|
|
18159
18512
|
for (const file of scenarioFiles) {
|
|
18160
|
-
const text = await
|
|
18513
|
+
const text = await readFile43(file, "utf-8");
|
|
18161
18514
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
18162
18515
|
if (!document || errors.length > 0) {
|
|
18163
18516
|
continue;
|
|
@@ -18188,7 +18541,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
|
|
|
18188
18541
|
let totalScenarios = 0;
|
|
18189
18542
|
let e2eCount = 0;
|
|
18190
18543
|
for (const file of scenarioFiles) {
|
|
18191
|
-
const text = await
|
|
18544
|
+
const text = await readFile43(file, "utf-8");
|
|
18192
18545
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
18193
18546
|
if (!document || errors.length > 0) {
|
|
18194
18547
|
continue;
|
|
@@ -18276,10 +18629,10 @@ function buildHotspots(issues) {
|
|
|
18276
18629
|
async function collectTddCoverage(entries) {
|
|
18277
18630
|
const specs = [];
|
|
18278
18631
|
for (const entry of entries) {
|
|
18279
|
-
const testCasesPath =
|
|
18632
|
+
const testCasesPath = path59.join(entry.dir, "06_Test-Cases.md");
|
|
18280
18633
|
let tcContent;
|
|
18281
18634
|
try {
|
|
18282
|
-
tcContent = await
|
|
18635
|
+
tcContent = await readFile43(testCasesPath, "utf-8");
|
|
18283
18636
|
} catch {
|
|
18284
18637
|
continue;
|
|
18285
18638
|
}
|
|
@@ -18311,10 +18664,10 @@ async function collectTddCoverage(entries) {
|
|
|
18311
18664
|
});
|
|
18312
18665
|
continue;
|
|
18313
18666
|
}
|
|
18314
|
-
const tddListPath =
|
|
18667
|
+
const tddListPath = path59.join(entry.dir, "tdd", "test-list.md");
|
|
18315
18668
|
let tddContent;
|
|
18316
18669
|
try {
|
|
18317
|
-
tddContent = await
|
|
18670
|
+
tddContent = await readFile43(tddListPath, "utf-8");
|
|
18318
18671
|
} catch {
|
|
18319
18672
|
specs.push({
|
|
18320
18673
|
specNumber: entry.specNumber,
|
|
@@ -18397,8 +18750,8 @@ async function collectTddCoverage(entries) {
|
|
|
18397
18750
|
}
|
|
18398
18751
|
|
|
18399
18752
|
// src/core/specPackReport.ts
|
|
18400
|
-
import { mkdir as mkdir7, readFile as
|
|
18401
|
-
import
|
|
18753
|
+
import { mkdir as mkdir7, readFile as readFile44, writeFile as writeFile6 } from "fs/promises";
|
|
18754
|
+
import path60 from "path";
|
|
18402
18755
|
var REQUIRED_LEDGER_COLUMNS = [
|
|
18403
18756
|
"trace_id",
|
|
18404
18757
|
"obj_id",
|
|
@@ -18416,8 +18769,8 @@ async function writeSpecPackReports(root, config) {
|
|
|
18416
18769
|
const entries = await collectSpecEntries(specsRoot);
|
|
18417
18770
|
const contractIndex = await buildContractIndex(root, config);
|
|
18418
18771
|
for (const entry of entries) {
|
|
18419
|
-
const specName =
|
|
18420
|
-
const outputDir =
|
|
18772
|
+
const specName = path60.basename(entry.dir);
|
|
18773
|
+
const outputDir = path60.join(outRoot, specName);
|
|
18421
18774
|
await mkdir7(outputDir, { recursive: true });
|
|
18422
18775
|
const [acText, tcText, exText, ledgerText] = await Promise.all([
|
|
18423
18776
|
readSafe12(entry.acceptanceCriteriaPath),
|
|
@@ -18444,13 +18797,13 @@ async function writeSpecPackReports(root, config) {
|
|
|
18444
18797
|
});
|
|
18445
18798
|
const graph = buildTraceabilityGraph(ledgerRows);
|
|
18446
18799
|
await writeFile6(
|
|
18447
|
-
|
|
18800
|
+
path60.join(outputDir, "coverage.md"),
|
|
18448
18801
|
`${formatCoverageMarkdown(specName, coverage)}
|
|
18449
18802
|
`,
|
|
18450
18803
|
"utf-8"
|
|
18451
18804
|
);
|
|
18452
18805
|
await writeFile6(
|
|
18453
|
-
|
|
18806
|
+
path60.join(outputDir, "traceability-graph.json"),
|
|
18454
18807
|
`${JSON.stringify(graph, null, 2)}
|
|
18455
18808
|
`,
|
|
18456
18809
|
"utf-8"
|
|
@@ -18625,7 +18978,7 @@ function getCell(row, indexByColumn, column) {
|
|
|
18625
18978
|
}
|
|
18626
18979
|
async function readSafe12(filePath) {
|
|
18627
18980
|
try {
|
|
18628
|
-
return await
|
|
18981
|
+
return await readFile44(filePath, "utf-8");
|
|
18629
18982
|
} catch {
|
|
18630
18983
|
return "";
|
|
18631
18984
|
}
|
|
@@ -18643,7 +18996,7 @@ function warnIfTruncated(scan, context) {
|
|
|
18643
18996
|
|
|
18644
18997
|
// src/cli/commands/report.ts
|
|
18645
18998
|
async function runReport(options) {
|
|
18646
|
-
const root =
|
|
18999
|
+
const root = path61.resolve(options.root);
|
|
18647
19000
|
const configResult = await loadConfig(root);
|
|
18648
19001
|
let validation;
|
|
18649
19002
|
let blockedByPhaseGuard = false;
|
|
@@ -18659,7 +19012,7 @@ async function runReport(options) {
|
|
|
18659
19012
|
validation = normalized;
|
|
18660
19013
|
} else {
|
|
18661
19014
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
18662
|
-
const inputPath =
|
|
19015
|
+
const inputPath = path61.isAbsolute(input) ? input : path61.resolve(root, input);
|
|
18663
19016
|
try {
|
|
18664
19017
|
validation = await readValidationResult(inputPath);
|
|
18665
19018
|
} catch (err) {
|
|
@@ -18686,10 +19039,10 @@ async function runReport(options) {
|
|
|
18686
19039
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
18687
19040
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
18688
19041
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
18689
|
-
const defaultOut = options.format === "json" ?
|
|
19042
|
+
const defaultOut = options.format === "json" ? path61.join(outRoot, "report.json") : path61.join(outRoot, "report.md");
|
|
18690
19043
|
const out = options.outPath ?? defaultOut;
|
|
18691
|
-
const outPath =
|
|
18692
|
-
await mkdir8(
|
|
19044
|
+
const outPath = path61.isAbsolute(out) ? out : path61.resolve(root, out);
|
|
19045
|
+
await mkdir8(path61.dirname(outPath), { recursive: true });
|
|
18693
19046
|
await writeFile7(outPath, `${output}
|
|
18694
19047
|
`, "utf-8");
|
|
18695
19048
|
await writeSpecPackReports(root, configResult.config);
|
|
@@ -18705,7 +19058,7 @@ async function runReport(options) {
|
|
|
18705
19058
|
info(`wrote report: ${outPath}`);
|
|
18706
19059
|
}
|
|
18707
19060
|
async function readValidationResult(inputPath) {
|
|
18708
|
-
const raw = await
|
|
19061
|
+
const raw = await readFile45(inputPath, "utf-8");
|
|
18709
19062
|
const parsed = JSON.parse(raw);
|
|
18710
19063
|
if (!isValidationResult(parsed)) {
|
|
18711
19064
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -18765,21 +19118,21 @@ function isMissingFileError2(error2) {
|
|
|
18765
19118
|
return record2.code === "ENOENT";
|
|
18766
19119
|
}
|
|
18767
19120
|
async function writeValidationResult(root, outputPath, result) {
|
|
18768
|
-
const abs =
|
|
18769
|
-
await mkdir8(
|
|
19121
|
+
const abs = path61.isAbsolute(outputPath) ? outputPath : path61.resolve(root, outputPath);
|
|
19122
|
+
await mkdir8(path61.dirname(abs), { recursive: true });
|
|
18770
19123
|
await writeFile7(abs, `${JSON.stringify(result, null, 2)}
|
|
18771
19124
|
`, "utf-8");
|
|
18772
19125
|
}
|
|
18773
19126
|
|
|
18774
19127
|
// src/cli/commands/validate.ts
|
|
18775
19128
|
import { mkdir as mkdir10, writeFile as writeFile9 } from "fs/promises";
|
|
18776
|
-
import
|
|
19129
|
+
import path63 from "path";
|
|
18777
19130
|
|
|
18778
19131
|
// src/core/runLog.ts
|
|
18779
19132
|
import { mkdir as mkdir9, writeFile as writeFile8 } from "fs/promises";
|
|
18780
|
-
import
|
|
19133
|
+
import path62 from "path";
|
|
18781
19134
|
async function writeValidateRunLog(input) {
|
|
18782
|
-
const root =
|
|
19135
|
+
const root = path62.resolve(input.root);
|
|
18783
19136
|
const outDir = resolvePath(root, input.config, "outDir");
|
|
18784
19137
|
await mkdir9(outDir, { recursive: true });
|
|
18785
19138
|
const { runId, reportDir } = await allocateRunReportDir(outDir, input.startedAt);
|
|
@@ -18826,10 +19179,10 @@ async function writeValidateRunLog(input) {
|
|
|
18826
19179
|
errors,
|
|
18827
19180
|
warnings
|
|
18828
19181
|
});
|
|
18829
|
-
await writeJson(
|
|
18830
|
-
await writeJson(
|
|
18831
|
-
await writeJson(
|
|
18832
|
-
await writeFile8(
|
|
19182
|
+
await writeJson(path62.join(reportDir, "run.json"), runJson);
|
|
19183
|
+
await writeJson(path62.join(reportDir, "validator.json"), validatorJson);
|
|
19184
|
+
await writeJson(path62.join(reportDir, "traceability.json"), traceabilityJson);
|
|
19185
|
+
await writeFile8(path62.join(reportDir, "summary.md"), `${summaryMd}
|
|
18833
19186
|
`, "utf-8");
|
|
18834
19187
|
return {
|
|
18835
19188
|
runId,
|
|
@@ -18953,7 +19306,7 @@ async function allocateRunReportDir(outDir, startedAt) {
|
|
|
18953
19306
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
18954
19307
|
const candidateDate = new Date(startedAt.getTime() + attempt);
|
|
18955
19308
|
const runId = `run-${formatTimestamp17(candidateDate)}`;
|
|
18956
|
-
const reportDir =
|
|
19309
|
+
const reportDir = path62.join(outDir, runId);
|
|
18957
19310
|
try {
|
|
18958
19311
|
await mkdir9(reportDir);
|
|
18959
19312
|
return { runId, reportDir };
|
|
@@ -18984,7 +19337,7 @@ function shouldFail(result, failOn) {
|
|
|
18984
19337
|
// src/cli/commands/validate.ts
|
|
18985
19338
|
async function runValidate(options) {
|
|
18986
19339
|
const startedAt = /* @__PURE__ */ new Date();
|
|
18987
|
-
const root =
|
|
19340
|
+
const root = path63.resolve(options.root);
|
|
18988
19341
|
const configResult = await loadConfig(root);
|
|
18989
19342
|
const blockedIssue = buildCiRefinementIssue(options.phase);
|
|
18990
19343
|
const blockedByPhaseGuard = blockedIssue !== null;
|
|
@@ -19140,12 +19493,12 @@ function issueKey(issue2) {
|
|
|
19140
19493
|
}
|
|
19141
19494
|
async function emitJson(result, root, jsonPath) {
|
|
19142
19495
|
const abs = resolveJsonPath(root, jsonPath);
|
|
19143
|
-
await mkdir10(
|
|
19496
|
+
await mkdir10(path63.dirname(abs), { recursive: true });
|
|
19144
19497
|
await writeFile9(abs, `${JSON.stringify(result, null, 2)}
|
|
19145
19498
|
`, "utf-8");
|
|
19146
19499
|
}
|
|
19147
19500
|
function resolveJsonPath(root, jsonPath) {
|
|
19148
|
-
return
|
|
19501
|
+
return path63.isAbsolute(jsonPath) ? jsonPath : path63.resolve(root, jsonPath);
|
|
19149
19502
|
}
|
|
19150
19503
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
19151
19504
|
var ISSUE_EXPECTED_BY_CODE = {
|