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/dist/index.mjs CHANGED
@@ -473,6 +473,69 @@ function normalizeUiux(raw, configPath, issues) {
473
473
  result.renderEvidence = renderEvidence;
474
474
  }
475
475
  }
476
+ if (raw.audit !== void 0) {
477
+ const audit = normalizeUiuxAudit(raw.audit, configPath, issues);
478
+ if (audit) {
479
+ result.audit = audit;
480
+ }
481
+ }
482
+ return Object.keys(result).length > 0 ? result : void 0;
483
+ }
484
+ function normalizeUiuxAudit(raw, configPath, issues) {
485
+ if (!isRecord(raw)) {
486
+ issues.push(configIssue(configPath, "uiux.audit \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
487
+ return void 0;
488
+ }
489
+ const result = {};
490
+ if (raw.enabled !== void 0) {
491
+ if (typeof raw.enabled === "boolean") {
492
+ result.enabled = raw.enabled;
493
+ } else {
494
+ issues.push(configIssue(configPath, "uiux.audit.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
495
+ }
496
+ }
497
+ if (raw.slopDetection !== void 0) {
498
+ if (typeof raw.slopDetection === "boolean") {
499
+ result.slopDetection = raw.slopDetection;
500
+ } else {
501
+ issues.push(
502
+ configIssue(configPath, "uiux.audit.slopDetection \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
503
+ );
504
+ }
505
+ }
506
+ if (raw.maxPrimaryCtas !== void 0) {
507
+ if (typeof raw.maxPrimaryCtas === "number" && Number.isFinite(raw.maxPrimaryCtas) && raw.maxPrimaryCtas >= 0) {
508
+ result.maxPrimaryCtas = raw.maxPrimaryCtas;
509
+ } else {
510
+ issues.push(
511
+ configIssue(configPath, "uiux.audit.maxPrimaryCtas \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
512
+ );
513
+ }
514
+ }
515
+ if (raw.maxRawTokenLiteralWarnings !== void 0) {
516
+ if (typeof raw.maxRawTokenLiteralWarnings === "number" && Number.isFinite(raw.maxRawTokenLiteralWarnings) && raw.maxRawTokenLiteralWarnings >= 0) {
517
+ result.maxRawTokenLiteralWarnings = raw.maxRawTokenLiteralWarnings;
518
+ } else {
519
+ issues.push(
520
+ configIssue(
521
+ configPath,
522
+ "uiux.audit.maxRawTokenLiteralWarnings \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
523
+ )
524
+ );
525
+ }
526
+ }
527
+ if (raw.maxDuplicateFindingsPerRule !== void 0) {
528
+ if (typeof raw.maxDuplicateFindingsPerRule === "number" && Number.isFinite(raw.maxDuplicateFindingsPerRule) && raw.maxDuplicateFindingsPerRule >= 0) {
529
+ result.maxDuplicateFindingsPerRule = raw.maxDuplicateFindingsPerRule;
530
+ } else {
531
+ issues.push(
532
+ configIssue(
533
+ configPath,
534
+ "uiux.audit.maxDuplicateFindingsPerRule \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
535
+ )
536
+ );
537
+ }
538
+ }
476
539
  return Object.keys(result).length > 0 ? result : void 0;
477
540
  }
478
541
  function normalizeRenderEvidence(raw, configPath, issues) {
@@ -3504,8 +3567,8 @@ function formatError4(error) {
3504
3567
  }
3505
3568
 
3506
3569
  // src/core/report.ts
3507
- import { readFile as readFile41 } from "fs/promises";
3508
- import path52 from "path";
3570
+ import { readFile as readFile42 } from "fs/promises";
3571
+ import path54 from "path";
3509
3572
 
3510
3573
  // src/core/contractIndex.ts
3511
3574
  import { readFile as readFile10 } from "fs/promises";
@@ -4263,8 +4326,8 @@ import { readFile as readFile11 } from "fs/promises";
4263
4326
  import path14 from "path";
4264
4327
  import { fileURLToPath } from "url";
4265
4328
  async function resolveToolVersion() {
4266
- if ("1.7.1".length > 0) {
4267
- return "1.7.1";
4329
+ if ("1.7.2".length > 0) {
4330
+ return "1.7.2";
4268
4331
  }
4269
4332
  try {
4270
4333
  const packagePath = resolvePackageJsonPath();
@@ -11354,7 +11417,7 @@ function collectLayer(layer, layerName, target, errors) {
11354
11417
  }
11355
11418
  function flattenTokens(obj, prefix, target, errors) {
11356
11419
  for (const [key, value] of Object.entries(obj)) {
11357
- const path53 = `${prefix}.${key}`;
11420
+ const path55 = `${prefix}.${key}`;
11358
11421
  if (value && typeof value === "object" && !Array.isArray(value)) {
11359
11422
  const record2 = value;
11360
11423
  if ("$value" in record2) {
@@ -11370,9 +11433,9 @@ function flattenTokens(obj, prefix, target, errors) {
11370
11433
  if (typeof record2.platform === "string") {
11371
11434
  token.platform = record2.platform;
11372
11435
  }
11373
- target.set(path53, token);
11436
+ target.set(path55, token);
11374
11437
  } else {
11375
- flattenTokens(record2, path53, target, errors);
11438
+ flattenTokens(record2, path55, target, errors);
11376
11439
  }
11377
11440
  }
11378
11441
  }
@@ -11382,44 +11445,44 @@ function resolveAllReferences(result) {
11382
11445
  for (const [key, val] of result.primitives) allTokens.set(key, val);
11383
11446
  for (const [key, val] of result.semantics) allTokens.set(key, val);
11384
11447
  for (const [key, val] of result.components) allTokens.set(key, val);
11385
- for (const [path53] of allTokens) {
11386
- resolveTokenRef(path53, allTokens, /* @__PURE__ */ new Set(), 0, result);
11448
+ for (const [path55] of allTokens) {
11449
+ resolveTokenRef(path55, allTokens, /* @__PURE__ */ new Set(), 0, result);
11387
11450
  }
11388
11451
  }
11389
- function resolveTokenRef(path53, allTokens, visited, depth, result) {
11390
- if (result.resolved.has(path53)) {
11391
- return result.resolved.get(path53);
11452
+ function resolveTokenRef(path55, allTokens, visited, depth, result) {
11453
+ if (result.resolved.has(path55)) {
11454
+ return result.resolved.get(path55);
11392
11455
  }
11393
11456
  if (depth > MAX_RESOLVE_DEPTH) {
11394
11457
  result.errors.push({
11395
- message: `Max reference depth exceeded at: ${path53}`,
11396
- path: path53
11458
+ message: `Max reference depth exceeded at: ${path55}`,
11459
+ path: path55
11397
11460
  });
11398
11461
  return void 0;
11399
11462
  }
11400
- if (visited.has(path53)) {
11463
+ if (visited.has(path55)) {
11401
11464
  result.errors.push({
11402
- message: `Circular reference detected: ${path53}`,
11403
- path: path53
11465
+ message: `Circular reference detected: ${path55}`,
11466
+ path: path55
11404
11467
  });
11405
11468
  return void 0;
11406
11469
  }
11407
- const token = allTokens.get(path53);
11470
+ const token = allTokens.get(path55);
11408
11471
  if (!token) {
11409
11472
  return void 0;
11410
11473
  }
11411
11474
  if (typeof token.$value !== "string") {
11412
11475
  const rawValue2 = stringifyTokenValue(token.$value);
11413
- result.resolved.set(path53, rawValue2);
11476
+ result.resolved.set(path55, rawValue2);
11414
11477
  return rawValue2;
11415
11478
  }
11416
11479
  const rawValue = stringifyTokenValue(token.$value);
11417
11480
  const refs = [...rawValue.matchAll(REF_PATTERN)];
11418
11481
  if (refs.length === 0) {
11419
- result.resolved.set(path53, rawValue);
11482
+ result.resolved.set(path55, rawValue);
11420
11483
  return rawValue;
11421
11484
  }
11422
- visited.add(path53);
11485
+ visited.add(path55);
11423
11486
  let resolved = rawValue;
11424
11487
  for (const ref of refs) {
11425
11488
  const refPath = ref[1];
@@ -11427,8 +11490,8 @@ function resolveTokenRef(path53, allTokens, visited, depth, result) {
11427
11490
  const refToken = allTokens.get(refPath);
11428
11491
  if (!refToken) {
11429
11492
  result.errors.push({
11430
- message: `Unresolved token reference: {${refPath}} at ${path53}`,
11431
- path: path53
11493
+ message: `Unresolved token reference: {${refPath}} at ${path55}`,
11494
+ path: path55
11432
11495
  });
11433
11496
  continue;
11434
11497
  }
@@ -11437,7 +11500,7 @@ function resolveTokenRef(path53, allTokens, visited, depth, result) {
11437
11500
  resolved = resolved.split(`{${refPath}}`).join(refValue);
11438
11501
  }
11439
11502
  }
11440
- result.resolved.set(path53, resolved);
11503
+ result.resolved.set(path55, resolved);
11441
11504
  return resolved;
11442
11505
  }
11443
11506
  function stringifyTokenValue(value) {
@@ -15494,6 +15557,252 @@ async function validateDiscussionDesignHardening(root, config) {
15494
15557
  return issues;
15495
15558
  }
15496
15559
 
15560
+ // src/core/validators/designAudit.ts
15561
+ import { readdir as readdir9 } from "fs/promises";
15562
+ import path52 from "path";
15563
+ var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
15564
+ function resolveAuditConfig(config) {
15565
+ const audit = config.uiux?.audit;
15566
+ const profile = config.uiux?.qualityProfile ?? "default";
15567
+ return {
15568
+ enabled: audit?.enabled ?? true,
15569
+ slopDetection: audit?.slopDetection ?? true,
15570
+ qualityProfile: profile,
15571
+ maxPrimaryCtas: audit?.maxPrimaryCtas ?? 1,
15572
+ maxRawTokenLiteralWarnings: audit?.maxRawTokenLiteralWarnings ?? 5,
15573
+ maxDuplicateFindingsPerRule: audit?.maxDuplicateFindingsPerRule ?? 5
15574
+ };
15575
+ }
15576
+ function mapSeverity(tier, profile, category) {
15577
+ if (tier === 1) return "error";
15578
+ if (tier === 2) return profile === "strict" ? "error" : "warning";
15579
+ if (profile === "default") {
15580
+ return category && COSMETIC_CATEGORIES.includes(category) ? "info" : "warning";
15581
+ }
15582
+ return "warning";
15583
+ }
15584
+ function findingToIssue(finding, profile, rulePrefix = "audit") {
15585
+ const severity = mapSeverity(finding.severityTier, profile, finding.dimension);
15586
+ return issue(
15587
+ finding.ruleId,
15588
+ finding.message,
15589
+ severity,
15590
+ finding.file,
15591
+ `${rulePrefix}.${finding.dimension}`,
15592
+ finding.evidence.length > 0 ? finding.evidence : void 0,
15593
+ "compatibility",
15594
+ finding.guidance
15595
+ );
15596
+ }
15597
+ function extractSection2(content, heading) {
15598
+ const idx = content.indexOf(heading);
15599
+ if (idx === -1) return null;
15600
+ const start = idx + heading.length;
15601
+ const headingLevel = heading.match(/^#+/)?.[0]?.length ?? 3;
15602
+ const rest = content.slice(start);
15603
+ const headingPattern = new RegExp(`^#{1,${headingLevel}} `, "m");
15604
+ const nextHeadingMatch = headingPattern.exec(rest);
15605
+ const sectionContent = nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest;
15606
+ return sectionContent.trim() || null;
15607
+ }
15608
+ function checkCtaHierarchy(content, auditConfig, file) {
15609
+ const findings = [];
15610
+ const ctaSection = extractSection2(content, "### CTA Hierarchy");
15611
+ if (!ctaSection) return findings;
15612
+ const primaryLines = ctaSection.match(/^-\s*Primary:/gm) || [];
15613
+ const primaryCount = primaryLines.length;
15614
+ if (primaryCount === 0) {
15615
+ findings.push({
15616
+ ruleId: "QFAI-AUD-001",
15617
+ dimension: "visualHierarchy",
15618
+ severityTier: 1,
15619
+ message: "No primary CTA defined in CTA Hierarchy",
15620
+ why: "Every UI screen needs a clear primary action to guide users",
15621
+ evidence: [],
15622
+ guidance: "Define at least one primary CTA in the CTA Hierarchy section",
15623
+ file
15624
+ });
15625
+ }
15626
+ if (primaryCount > auditConfig.maxPrimaryCtas) {
15627
+ findings.push({
15628
+ ruleId: "QFAI-AUD-020",
15629
+ dimension: "visualHierarchy",
15630
+ severityTier: 2,
15631
+ message: `Multiple primary CTAs detected (${primaryCount} > ${auditConfig.maxPrimaryCtas})`,
15632
+ why: "Multiple primary CTAs create decision paralysis and weaken visual hierarchy",
15633
+ evidence: primaryLines.map((l) => l.trim()),
15634
+ guidance: "Reduce to a single primary CTA per screen; demote others to secondary",
15635
+ file
15636
+ });
15637
+ }
15638
+ return findings;
15639
+ }
15640
+ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)/g;
15641
+ async function checkTokenDrift(root, auditConfig, cfg) {
15642
+ const findings = [];
15643
+ const configuredDir = cfg.uiux?.designTokensDir;
15644
+ const tokensDir = configuredDir ? path52.resolve(root, configuredDir) : path52.join(root, cfg.paths.contractsDir, "design");
15645
+ let hasTokenFiles = false;
15646
+ try {
15647
+ const entries = await readdir9(tokensDir);
15648
+ hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
15649
+ } catch {
15650
+ return findings;
15651
+ }
15652
+ if (!hasTokenFiles) return findings;
15653
+ const contractsUiDir = path52.join(root, cfg.paths.contractsDir, "ui");
15654
+ let htmlFiles = [];
15655
+ try {
15656
+ const entries = await readdir9(contractsUiDir);
15657
+ htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
15658
+ } catch {
15659
+ return findings;
15660
+ }
15661
+ let rawCount = 0;
15662
+ const sampleLiterals = [];
15663
+ for (const htmlFile of htmlFiles) {
15664
+ const content = await readSafe(path52.join(contractsUiDir, htmlFile));
15665
+ if (!content) continue;
15666
+ const matches = content.match(RAW_COLOR_RE);
15667
+ if (matches) {
15668
+ rawCount += matches.length;
15669
+ for (const m of matches) {
15670
+ if (sampleLiterals.length < 10) {
15671
+ sampleLiterals.push(m.toLowerCase());
15672
+ }
15673
+ }
15674
+ }
15675
+ }
15676
+ if (rawCount > auditConfig.maxRawTokenLiteralWarnings) {
15677
+ findings.push({
15678
+ ruleId: "QFAI-AUD-004",
15679
+ dimension: "tokenDiscipline",
15680
+ severityTier: 1,
15681
+ message: `Token drift: ${rawCount} raw color literal occurrences found (threshold: ${auditConfig.maxRawTokenLiteralWarnings})`,
15682
+ why: "Raw color values bypass design tokens, causing visual inconsistency",
15683
+ evidence: sampleLiterals,
15684
+ guidance: "Replace raw color literals with design token references"
15685
+ });
15686
+ }
15687
+ return findings;
15688
+ }
15689
+ function deduplicateFindings(issues, maxPerRule) {
15690
+ const counts = /* @__PURE__ */ new Map();
15691
+ const result = [];
15692
+ for (const iss of issues) {
15693
+ const count = counts.get(iss.code) ?? 0;
15694
+ if (count < maxPerRule) {
15695
+ result.push(iss);
15696
+ }
15697
+ counts.set(iss.code, count + 1);
15698
+ }
15699
+ for (const [code, count] of counts) {
15700
+ if (count > maxPerRule) {
15701
+ result.push({
15702
+ code,
15703
+ severity: "info",
15704
+ category: "compatibility",
15705
+ message: `${count - maxPerRule} additional "${code}" finding(s) suppressed (max ${maxPerRule} per rule)`,
15706
+ rule: `audit.dedup.${code}`
15707
+ });
15708
+ }
15709
+ }
15710
+ return result;
15711
+ }
15712
+ async function validateDesignAudit(root, config) {
15713
+ const auditConfig = resolveAuditConfig(config);
15714
+ if (!auditConfig.enabled) return [];
15715
+ const discussionDir = path52.join(root, config.paths.discussionDir);
15716
+ const packRoot = await findLatestDiscussionPackDir(discussionDir);
15717
+ if (!packRoot) return [];
15718
+ const uiBearing = await isUiBearing(packRoot);
15719
+ if (!uiBearing) return [];
15720
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15721
+ const content = await readSafe(storyPath);
15722
+ if (!content) return [];
15723
+ const findings = [];
15724
+ findings.push(...checkCtaHierarchy(content, auditConfig, "03_Story-Workshop.md"));
15725
+ findings.push(...await checkTokenDrift(root, auditConfig, config));
15726
+ const issues = findings.map((f) => findingToIssue(f, auditConfig.qualityProfile));
15727
+ return deduplicateFindings(issues, auditConfig.maxDuplicateFindingsPerRule);
15728
+ }
15729
+
15730
+ // src/core/validators/designSlop.ts
15731
+ import { existsSync as existsSync2 } from "fs";
15732
+ import { readFile as readFile41 } from "fs/promises";
15733
+ import path53 from "path";
15734
+ import { fileURLToPath as fileURLToPath4 } from "url";
15735
+ function isValidSlopPattern(rule) {
15736
+ if (typeof rule !== "object" || rule === null) return false;
15737
+ const r = rule;
15738
+ 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";
15739
+ }
15740
+ async function loadSlopPatterns(jsonPath) {
15741
+ const raw = await readFile41(jsonPath, "utf-8");
15742
+ const parsed = JSON.parse(raw);
15743
+ if (!Array.isArray(parsed)) return [];
15744
+ return parsed.filter((r) => isValidSlopPattern(r));
15745
+ }
15746
+ function defaultPatternsPath() {
15747
+ const base = import.meta.url;
15748
+ const basePath = base.startsWith("file:") ? fileURLToPath4(base) : base;
15749
+ const baseDir = path53.dirname(basePath);
15750
+ const candidates = [
15751
+ path53.join(baseDir, "designSlopPatterns.json"),
15752
+ path53.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
15753
+ path53.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
15754
+ ];
15755
+ for (const c of candidates) {
15756
+ if (existsSync2(c)) return c;
15757
+ }
15758
+ return candidates[0];
15759
+ }
15760
+ async function validateDesignSlop(root, config) {
15761
+ const auditConfig = resolveAuditConfig(config);
15762
+ if (!auditConfig.enabled) return [];
15763
+ if (!auditConfig.slopDetection) return [];
15764
+ const discussionDir = path53.join(root, config.paths.discussionDir);
15765
+ const packRoot = await findLatestDiscussionPackDir(discussionDir);
15766
+ if (!packRoot) return [];
15767
+ const uiBearing = await isUiBearing(packRoot);
15768
+ if (!uiBearing) return [];
15769
+ let patterns;
15770
+ try {
15771
+ patterns = await loadSlopPatterns(defaultPatternsPath());
15772
+ } catch {
15773
+ return [];
15774
+ }
15775
+ const findings = [];
15776
+ const seenRules = /* @__PURE__ */ new Set();
15777
+ for (const pattern of patterns) {
15778
+ let regex;
15779
+ try {
15780
+ regex = new RegExp(pattern.match, "gi");
15781
+ } catch {
15782
+ continue;
15783
+ }
15784
+ for (const scope of pattern.scopes) {
15785
+ const filePath = path53.join(packRoot, scope);
15786
+ const content = await readSafe(filePath);
15787
+ if (!content) continue;
15788
+ if (regex.test(content) && !seenRules.has(pattern.id)) {
15789
+ seenRules.add(pattern.id);
15790
+ findings.push({
15791
+ ruleId: pattern.id,
15792
+ dimension: pattern.category,
15793
+ severityTier: pattern.tier,
15794
+ message: pattern.message,
15795
+ why: `Slop pattern "${pattern.id}" matched in ${scope}`,
15796
+ evidence: [],
15797
+ guidance: pattern.guidance,
15798
+ file: scope
15799
+ });
15800
+ }
15801
+ }
15802
+ }
15803
+ return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
15804
+ }
15805
+
15497
15806
  // src/core/validate.ts
15498
15807
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
15499
15808
  async function validateProject(root, configResult, options = {}) {
@@ -15511,7 +15820,9 @@ async function validateProject(root, configResult, options = {}) {
15511
15820
  () => validateBpApDb(root, config),
15512
15821
  () => validateUiDefinitionConsistency(root, config),
15513
15822
  () => validateResearchSummary(root, config),
15514
- () => validateAgentDefinition(root, config)
15823
+ () => validateAgentDefinition(root, config),
15824
+ () => validateDesignAudit(root, config),
15825
+ () => validateDesignSlop(root, config)
15515
15826
  ];
15516
15827
  const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
15517
15828
  const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
@@ -15598,15 +15909,15 @@ var REPORT_GUARDRAILS_MAX = 20;
15598
15909
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
15599
15910
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
15600
15911
  async function createReportData(root, validation, configResult) {
15601
- const resolvedRoot = path52.resolve(root);
15912
+ const resolvedRoot = path54.resolve(root);
15602
15913
  const resolved = configResult ?? await loadConfig(resolvedRoot);
15603
15914
  const config = resolved.config;
15604
15915
  const configPath = resolved.configPath;
15605
15916
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
15606
15917
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
15607
- const apiRoot = path52.join(contractsRoot, "api");
15608
- const uiRoot = path52.join(contractsRoot, "ui");
15609
- const dbRoot = path52.join(contractsRoot, "db");
15918
+ const apiRoot = path54.join(contractsRoot, "api");
15919
+ const uiRoot = path54.join(contractsRoot, "ui");
15920
+ const dbRoot = path54.join(contractsRoot, "db");
15610
15921
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
15611
15922
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
15612
15923
  const specEntries = await collectSpecEntries(specsRoot);
@@ -15934,6 +16245,8 @@ function formatReportMarkdown(data, options = {}) {
15934
16245
  lines.push("");
15935
16246
  lines.push("- [Compatibility Issues](#compatibility-issues)");
15936
16247
  lines.push("- [Change Issues](#change-issues)");
16248
+ lines.push("- [Design Audit Findings](#design-audit-findings)");
16249
+ lines.push("- [Slop Guardrails Findings](#slop-guardrails-findings)");
15937
16250
  lines.push("- [Change Type](#change-type)");
15938
16251
  lines.push("- [Waivers](#waivers)");
15939
16252
  lines.push("- [Decision Guardrails](#decision-guardrails)");
@@ -16082,6 +16395,46 @@ function formatReportMarkdown(data, options = {}) {
16082
16395
  lines.push("### Issues");
16083
16396
  lines.push("");
16084
16397
  lines.push(...formatIssueCards(issuesByCategory.change));
16398
+ const auditIssues = data.issues.filter((i) => /^QFAI-AUD-/.test(i.code));
16399
+ if (auditIssues.length > 0) {
16400
+ lines.push("## Design Audit Findings");
16401
+ lines.push("");
16402
+ const byDimension = /* @__PURE__ */ new Map();
16403
+ for (const iss of auditIssues) {
16404
+ const dim = iss.rule?.replace(/^audit\./, "").split(".")[0] ?? "unknown";
16405
+ const group = byDimension.get(dim) ?? [];
16406
+ group.push(iss);
16407
+ byDimension.set(dim, group);
16408
+ }
16409
+ for (const [dim, dimIssues] of byDimension) {
16410
+ lines.push(`### ${dim}`);
16411
+ lines.push("");
16412
+ for (const iss of dimIssues) {
16413
+ lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
16414
+ }
16415
+ lines.push("");
16416
+ }
16417
+ }
16418
+ const slopIssues = data.issues.filter((i) => /^SLP-/.test(i.code));
16419
+ if (slopIssues.length > 0) {
16420
+ lines.push("## Slop Guardrails Findings");
16421
+ lines.push("");
16422
+ const byCategory = /* @__PURE__ */ new Map();
16423
+ for (const iss of slopIssues) {
16424
+ const cat = iss.rule?.replace(/^slop\./, "").split(".")[0] ?? "unknown";
16425
+ const group = byCategory.get(cat) ?? [];
16426
+ group.push(iss);
16427
+ byCategory.set(cat, group);
16428
+ }
16429
+ for (const [cat, catIssues] of byCategory) {
16430
+ lines.push(`### ${cat}`);
16431
+ lines.push("");
16432
+ for (const iss of catIssues) {
16433
+ lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
16434
+ }
16435
+ lines.push("");
16436
+ }
16437
+ }
16085
16438
  lines.push("## Change Type");
16086
16439
  lines.push("");
16087
16440
  lines.push("### Summary");
@@ -16501,7 +16854,7 @@ async function collectChangeTypeSummary(specsRoot) {
16501
16854
  };
16502
16855
  const deltaFiles = await collectDeltaFiles(specsRoot);
16503
16856
  for (const deltaFile of deltaFiles) {
16504
- const text = await readFile41(deltaFile, "utf-8");
16857
+ const text = await readFile42(deltaFile, "utf-8");
16505
16858
  const parsed = parseDeltaV1(text);
16506
16859
  for (const entry of parsed.entries) {
16507
16860
  if (!entry.meta) {
@@ -16538,7 +16891,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
16538
16891
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
16539
16892
  }
16540
16893
  for (const file of specFiles) {
16541
- const text = await readFile41(file, "utf-8");
16894
+ const text = await readFile42(file, "utf-8");
16542
16895
  const parsed = parseSpec(text, file);
16543
16896
  const specKey = parsed.specId;
16544
16897
  if (!specKey) {
@@ -16575,7 +16928,7 @@ async function collectIds(files) {
16575
16928
  result[prefix] = /* @__PURE__ */ new Set();
16576
16929
  }
16577
16930
  for (const file of files) {
16578
- const text = await readFile41(file, "utf-8");
16931
+ const text = await readFile42(file, "utf-8");
16579
16932
  for (const prefix of ID_PREFIXES) {
16580
16933
  const ids = extractIds(text, prefix);
16581
16934
  ids.forEach((id) => result[prefix].add(id));
@@ -16590,7 +16943,7 @@ async function collectIds(files) {
16590
16943
  async function collectUpstreamIds(files) {
16591
16944
  const ids = /* @__PURE__ */ new Set();
16592
16945
  for (const file of files) {
16593
- const text = await readFile41(file, "utf-8");
16946
+ const text = await readFile42(file, "utf-8");
16594
16947
  extractAllIds(text).forEach((id) => ids.add(id));
16595
16948
  }
16596
16949
  return ids;
@@ -16611,7 +16964,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
16611
16964
  }
16612
16965
  const pattern = buildIdPattern(Array.from(upstreamIds));
16613
16966
  for (const file of targetFiles) {
16614
- const text = await readFile41(file, "utf-8");
16967
+ const text = await readFile42(file, "utf-8");
16615
16968
  if (pattern.test(text)) {
16616
16969
  return true;
16617
16970
  }
@@ -16730,7 +17083,7 @@ function normalizeScSources(root, sources) {
16730
17083
  async function countScenarios(scenarioFiles) {
16731
17084
  let total = 0;
16732
17085
  for (const file of scenarioFiles) {
16733
- const text = await readFile41(file, "utf-8");
17086
+ const text = await readFile42(file, "utf-8");
16734
17087
  const { document, errors } = parseScenarioDocument(text, file);
16735
17088
  if (!document || errors.length > 0) {
16736
17089
  continue;
@@ -16761,7 +17114,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
16761
17114
  let totalScenarios = 0;
16762
17115
  let e2eCount = 0;
16763
17116
  for (const file of scenarioFiles) {
16764
- const text = await readFile41(file, "utf-8");
17117
+ const text = await readFile42(file, "utf-8");
16765
17118
  const { document, errors } = parseScenarioDocument(text, file);
16766
17119
  if (!document || errors.length > 0) {
16767
17120
  continue;
@@ -16849,10 +17202,10 @@ function buildHotspots(issues) {
16849
17202
  async function collectTddCoverage(entries) {
16850
17203
  const specs = [];
16851
17204
  for (const entry of entries) {
16852
- const testCasesPath = path52.join(entry.dir, "06_Test-Cases.md");
17205
+ const testCasesPath = path54.join(entry.dir, "06_Test-Cases.md");
16853
17206
  let tcContent;
16854
17207
  try {
16855
- tcContent = await readFile41(testCasesPath, "utf-8");
17208
+ tcContent = await readFile42(testCasesPath, "utf-8");
16856
17209
  } catch {
16857
17210
  continue;
16858
17211
  }
@@ -16884,10 +17237,10 @@ async function collectTddCoverage(entries) {
16884
17237
  });
16885
17238
  continue;
16886
17239
  }
16887
- const tddListPath = path52.join(entry.dir, "tdd", "test-list.md");
17240
+ const tddListPath = path54.join(entry.dir, "tdd", "test-list.md");
16888
17241
  let tddContent;
16889
17242
  try {
16890
- tddContent = await readFile41(tddListPath, "utf-8");
17243
+ tddContent = await readFile42(tddListPath, "utf-8");
16891
17244
  } catch {
16892
17245
  specs.push({
16893
17246
  specNumber: entry.specNumber,