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.
@@ -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.1".length > 0) {
1648
- return "1.7.1";
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 path62 = resolveRenderOutPath(options.root, options.renderOut);
4274
+ const path64 = resolveRenderOutPath(options.root, options.renderOut);
4212
4275
  return {
4213
- path: path62,
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: path62,
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 import_promises56 = require("fs/promises");
4292
- var import_node_path59 = __toESM(require("path"), 1);
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 import_promises54 = require("fs/promises");
4388
- var import_node_path57 = __toESM(require("path"), 1);
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 path62 = `${prefix}.${key}`;
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(path62, token);
12876
+ target.set(path64, token);
12814
12877
  } else {
12815
- flattenTokens(record2, path62, target, errors);
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 [path62] of allTokens) {
12826
- resolveTokenRef(path62, allTokens, /* @__PURE__ */ new Set(), 0, result);
12888
+ for (const [path64] of allTokens) {
12889
+ resolveTokenRef(path64, allTokens, /* @__PURE__ */ new Set(), 0, result);
12827
12890
  }
12828
12891
  }
12829
- function resolveTokenRef(path62, allTokens, visited, depth, result) {
12830
- if (result.resolved.has(path62)) {
12831
- return result.resolved.get(path62);
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: ${path62}`,
12836
- path: path62
12898
+ message: `Max reference depth exceeded at: ${path64}`,
12899
+ path: path64
12837
12900
  });
12838
12901
  return void 0;
12839
12902
  }
12840
- if (visited.has(path62)) {
12903
+ if (visited.has(path64)) {
12841
12904
  result.errors.push({
12842
- message: `Circular reference detected: ${path62}`,
12843
- path: path62
12905
+ message: `Circular reference detected: ${path64}`,
12906
+ path: path64
12844
12907
  });
12845
12908
  return void 0;
12846
12909
  }
12847
- const token = allTokens.get(path62);
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(path62, rawValue2);
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(path62, rawValue);
12922
+ result.resolved.set(path64, rawValue);
12860
12923
  return rawValue;
12861
12924
  }
12862
- visited.add(path62);
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 ${path62}`,
12871
- path: path62
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(path62, resolved);
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 = import_node_path57.default.resolve(root);
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 = import_node_path57.default.join(contractsRoot, "api");
17048
- const uiRoot = import_node_path57.default.join(contractsRoot, "ui");
17049
- const dbRoot = import_node_path57.default.join(contractsRoot, "db");
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, import_promises54.readFile)(deltaFile, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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, import_promises54.readFile)(file, "utf-8");
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 = import_node_path57.default.join(entry.dir, "06_Test-Cases.md");
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, import_promises54.readFile)(testCasesPath, "utf-8");
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 = import_node_path57.default.join(entry.dir, "tdd", "test-list.md");
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, import_promises54.readFile)(tddListPath, "utf-8");
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 import_promises55 = require("fs/promises");
18414
- var import_node_path58 = __toESM(require("path"), 1);
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 = import_node_path58.default.basename(entry.dir);
18433
- const outputDir = import_node_path58.default.join(outRoot, specName);
18434
- await (0, import_promises55.mkdir)(outputDir, { recursive: true });
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, import_promises55.writeFile)(
18460
- import_node_path58.default.join(outputDir, "coverage.md"),
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, import_promises55.writeFile)(
18466
- import_node_path58.default.join(outputDir, "traceability-graph.json"),
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, import_promises55.readFile)(filePath, "utf-8");
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 = import_node_path59.default.resolve(options.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 = import_node_path59.default.isAbsolute(input) ? input : import_node_path59.default.resolve(root, input);
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" ? import_node_path59.default.join(outRoot, "report.json") : import_node_path59.default.join(outRoot, "report.md");
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 = import_node_path59.default.isAbsolute(out) ? out : import_node_path59.default.resolve(root, out);
18705
- await (0, import_promises56.mkdir)(import_node_path59.default.dirname(outPath), { recursive: true });
18706
- await (0, import_promises56.writeFile)(outPath, `${output}
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, import_promises56.readFile)(inputPath, "utf-8");
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 = import_node_path59.default.isAbsolute(outputPath) ? outputPath : import_node_path59.default.resolve(root, outputPath);
18782
- await (0, import_promises56.mkdir)(import_node_path59.default.dirname(abs), { recursive: true });
18783
- await (0, import_promises56.writeFile)(abs, `${JSON.stringify(result, null, 2)}
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 import_promises58 = require("fs/promises");
18789
- var import_node_path61 = __toESM(require("path"), 1);
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 import_promises57 = require("fs/promises");
18793
- var import_node_path60 = __toESM(require("path"), 1);
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 = import_node_path60.default.resolve(input.root);
19148
+ const root = import_node_path62.default.resolve(input.root);
18796
19149
  const outDir = resolvePath(root, input.config, "outDir");
18797
- await (0, import_promises57.mkdir)(outDir, { recursive: true });
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(import_node_path60.default.join(reportDir, "run.json"), runJson);
18843
- await writeJson(import_node_path60.default.join(reportDir, "validator.json"), validatorJson);
18844
- await writeJson(import_node_path60.default.join(reportDir, "traceability.json"), traceabilityJson);
18845
- await (0, import_promises57.writeFile)(import_node_path60.default.join(reportDir, "summary.md"), `${summaryMd}
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, import_promises57.writeFile)(filePath, `${JSON.stringify(value, null, 2)}
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 = import_node_path60.default.join(outDir, runId);
19322
+ const reportDir = import_node_path62.default.join(outDir, runId);
18970
19323
  try {
18971
- await (0, import_promises57.mkdir)(reportDir);
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 = import_node_path61.default.resolve(options.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, import_promises58.mkdir)(import_node_path61.default.dirname(abs), { recursive: true });
19157
- await (0, import_promises58.writeFile)(abs, `${JSON.stringify(result, null, 2)}
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 import_node_path61.default.isAbsolute(jsonPath) ? jsonPath : import_node_path61.default.resolve(root, jsonPath);
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 = {