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.
@@ -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.1".length > 0) {
1625
- return "1.7.1";
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 path62 = resolveRenderOutPath(options.root, options.renderOut);
4261
+ const path64 = resolveRenderOutPath(options.root, options.renderOut);
4199
4262
  return {
4200
- path: path62,
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: path62,
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 readFile44, writeFile as writeFile7 } from "fs/promises";
4279
- import path59 from "path";
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 readFile42 } from "fs/promises";
4375
- import path57 from "path";
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 path62 = `${prefix}.${key}`;
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(path62, token);
12863
+ target.set(path64, token);
12801
12864
  } else {
12802
- flattenTokens(record2, path62, target, errors);
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 [path62] of allTokens) {
12813
- resolveTokenRef(path62, allTokens, /* @__PURE__ */ new Set(), 0, result);
12875
+ for (const [path64] of allTokens) {
12876
+ resolveTokenRef(path64, allTokens, /* @__PURE__ */ new Set(), 0, result);
12814
12877
  }
12815
12878
  }
12816
- function resolveTokenRef(path62, allTokens, visited, depth, result) {
12817
- if (result.resolved.has(path62)) {
12818
- return result.resolved.get(path62);
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: ${path62}`,
12823
- path: path62
12885
+ message: `Max reference depth exceeded at: ${path64}`,
12886
+ path: path64
12824
12887
  });
12825
12888
  return void 0;
12826
12889
  }
12827
- if (visited.has(path62)) {
12890
+ if (visited.has(path64)) {
12828
12891
  result.errors.push({
12829
- message: `Circular reference detected: ${path62}`,
12830
- path: path62
12892
+ message: `Circular reference detected: ${path64}`,
12893
+ path: path64
12831
12894
  });
12832
12895
  return void 0;
12833
12896
  }
12834
- const token = allTokens.get(path62);
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(path62, rawValue2);
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(path62, rawValue);
12909
+ result.resolved.set(path64, rawValue);
12847
12910
  return rawValue;
12848
12911
  }
12849
- visited.add(path62);
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 ${path62}`,
12858
- path: path62
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(path62, resolved);
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 = path57.resolve(root);
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 = path57.join(contractsRoot, "api");
17035
- const uiRoot = path57.join(contractsRoot, "ui");
17036
- const dbRoot = path57.join(contractsRoot, "db");
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 readFile42(deltaFile, "utf-8");
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 readFile42(file, "utf-8");
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 readFile42(file, "utf-8");
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 readFile42(file, "utf-8");
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 readFile42(file, "utf-8");
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 readFile42(file, "utf-8");
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 readFile42(file, "utf-8");
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 = path57.join(entry.dir, "06_Test-Cases.md");
18632
+ const testCasesPath = path59.join(entry.dir, "06_Test-Cases.md");
18280
18633
  let tcContent;
18281
18634
  try {
18282
- tcContent = await readFile42(testCasesPath, "utf-8");
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 = path57.join(entry.dir, "tdd", "test-list.md");
18667
+ const tddListPath = path59.join(entry.dir, "tdd", "test-list.md");
18315
18668
  let tddContent;
18316
18669
  try {
18317
- tddContent = await readFile42(tddListPath, "utf-8");
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 readFile43, writeFile as writeFile6 } from "fs/promises";
18401
- import path58 from "path";
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 = path58.basename(entry.dir);
18420
- const outputDir = path58.join(outRoot, specName);
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
- path58.join(outputDir, "coverage.md"),
18800
+ path60.join(outputDir, "coverage.md"),
18448
18801
  `${formatCoverageMarkdown(specName, coverage)}
18449
18802
  `,
18450
18803
  "utf-8"
18451
18804
  );
18452
18805
  await writeFile6(
18453
- path58.join(outputDir, "traceability-graph.json"),
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 readFile43(filePath, "utf-8");
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 = path59.resolve(options.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 = path59.isAbsolute(input) ? input : path59.resolve(root, input);
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" ? path59.join(outRoot, "report.json") : path59.join(outRoot, "report.md");
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 = path59.isAbsolute(out) ? out : path59.resolve(root, out);
18692
- await mkdir8(path59.dirname(outPath), { recursive: true });
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 readFile44(inputPath, "utf-8");
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 = path59.isAbsolute(outputPath) ? outputPath : path59.resolve(root, outputPath);
18769
- await mkdir8(path59.dirname(abs), { recursive: true });
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 path61 from "path";
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 path60 from "path";
19133
+ import path62 from "path";
18781
19134
  async function writeValidateRunLog(input) {
18782
- const root = path60.resolve(input.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(path60.join(reportDir, "run.json"), runJson);
18830
- await writeJson(path60.join(reportDir, "validator.json"), validatorJson);
18831
- await writeJson(path60.join(reportDir, "traceability.json"), traceabilityJson);
18832
- await writeFile8(path60.join(reportDir, "summary.md"), `${summaryMd}
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 = path60.join(outDir, runId);
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 = path61.resolve(options.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(path61.dirname(abs), { recursive: true });
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 path61.isAbsolute(jsonPath) ? jsonPath : path61.resolve(root, jsonPath);
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 = {