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.cjs CHANGED
@@ -554,6 +554,69 @@ function normalizeUiux(raw, configPath, issues) {
554
554
  result.renderEvidence = renderEvidence;
555
555
  }
556
556
  }
557
+ if (raw.audit !== void 0) {
558
+ const audit = normalizeUiuxAudit(raw.audit, configPath, issues);
559
+ if (audit) {
560
+ result.audit = audit;
561
+ }
562
+ }
563
+ return Object.keys(result).length > 0 ? result : void 0;
564
+ }
565
+ function normalizeUiuxAudit(raw, configPath, issues) {
566
+ if (!isRecord(raw)) {
567
+ issues.push(configIssue(configPath, "uiux.audit \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
568
+ return void 0;
569
+ }
570
+ const result = {};
571
+ if (raw.enabled !== void 0) {
572
+ if (typeof raw.enabled === "boolean") {
573
+ result.enabled = raw.enabled;
574
+ } else {
575
+ issues.push(configIssue(configPath, "uiux.audit.enabled \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
576
+ }
577
+ }
578
+ if (raw.slopDetection !== void 0) {
579
+ if (typeof raw.slopDetection === "boolean") {
580
+ result.slopDetection = raw.slopDetection;
581
+ } else {
582
+ issues.push(
583
+ configIssue(configPath, "uiux.audit.slopDetection \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
584
+ );
585
+ }
586
+ }
587
+ if (raw.maxPrimaryCtas !== void 0) {
588
+ if (typeof raw.maxPrimaryCtas === "number" && Number.isFinite(raw.maxPrimaryCtas) && raw.maxPrimaryCtas >= 0) {
589
+ result.maxPrimaryCtas = raw.maxPrimaryCtas;
590
+ } else {
591
+ issues.push(
592
+ configIssue(configPath, "uiux.audit.maxPrimaryCtas \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
593
+ );
594
+ }
595
+ }
596
+ if (raw.maxRawTokenLiteralWarnings !== void 0) {
597
+ if (typeof raw.maxRawTokenLiteralWarnings === "number" && Number.isFinite(raw.maxRawTokenLiteralWarnings) && raw.maxRawTokenLiteralWarnings >= 0) {
598
+ result.maxRawTokenLiteralWarnings = raw.maxRawTokenLiteralWarnings;
599
+ } else {
600
+ issues.push(
601
+ configIssue(
602
+ configPath,
603
+ "uiux.audit.maxRawTokenLiteralWarnings \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
604
+ )
605
+ );
606
+ }
607
+ }
608
+ if (raw.maxDuplicateFindingsPerRule !== void 0) {
609
+ if (typeof raw.maxDuplicateFindingsPerRule === "number" && Number.isFinite(raw.maxDuplicateFindingsPerRule) && raw.maxDuplicateFindingsPerRule >= 0) {
610
+ result.maxDuplicateFindingsPerRule = raw.maxDuplicateFindingsPerRule;
611
+ } else {
612
+ issues.push(
613
+ configIssue(
614
+ configPath,
615
+ "uiux.audit.maxDuplicateFindingsPerRule \u306F0\u4EE5\u4E0A\u306E\u6570\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
616
+ )
617
+ );
618
+ }
619
+ }
557
620
  return Object.keys(result).length > 0 ? result : void 0;
558
621
  }
559
622
  function normalizeRenderEvidence(raw, configPath, issues) {
@@ -3585,8 +3648,8 @@ function formatError4(error) {
3585
3648
  }
3586
3649
 
3587
3650
  // src/core/report.ts
3588
- var import_promises50 = require("fs/promises");
3589
- var import_node_path52 = __toESM(require("path"), 1);
3651
+ var import_promises52 = require("fs/promises");
3652
+ var import_node_path54 = __toESM(require("path"), 1);
3590
3653
 
3591
3654
  // src/core/contractIndex.ts
3592
3655
  var import_promises13 = require("fs/promises");
@@ -4344,8 +4407,8 @@ var import_promises14 = require("fs/promises");
4344
4407
  var import_node_path14 = __toESM(require("path"), 1);
4345
4408
  var import_node_url = require("url");
4346
4409
  async function resolveToolVersion() {
4347
- if ("1.7.1".length > 0) {
4348
- return "1.7.1";
4410
+ if ("1.7.2".length > 0) {
4411
+ return "1.7.2";
4349
4412
  }
4350
4413
  try {
4351
4414
  const packagePath = resolvePackageJsonPath();
@@ -11435,7 +11498,7 @@ function collectLayer(layer, layerName, target, errors) {
11435
11498
  }
11436
11499
  function flattenTokens(obj, prefix, target, errors) {
11437
11500
  for (const [key, value] of Object.entries(obj)) {
11438
- const path53 = `${prefix}.${key}`;
11501
+ const path55 = `${prefix}.${key}`;
11439
11502
  if (value && typeof value === "object" && !Array.isArray(value)) {
11440
11503
  const record2 = value;
11441
11504
  if ("$value" in record2) {
@@ -11451,9 +11514,9 @@ function flattenTokens(obj, prefix, target, errors) {
11451
11514
  if (typeof record2.platform === "string") {
11452
11515
  token.platform = record2.platform;
11453
11516
  }
11454
- target.set(path53, token);
11517
+ target.set(path55, token);
11455
11518
  } else {
11456
- flattenTokens(record2, path53, target, errors);
11519
+ flattenTokens(record2, path55, target, errors);
11457
11520
  }
11458
11521
  }
11459
11522
  }
@@ -11463,44 +11526,44 @@ function resolveAllReferences(result) {
11463
11526
  for (const [key, val] of result.primitives) allTokens.set(key, val);
11464
11527
  for (const [key, val] of result.semantics) allTokens.set(key, val);
11465
11528
  for (const [key, val] of result.components) allTokens.set(key, val);
11466
- for (const [path53] of allTokens) {
11467
- resolveTokenRef(path53, allTokens, /* @__PURE__ */ new Set(), 0, result);
11529
+ for (const [path55] of allTokens) {
11530
+ resolveTokenRef(path55, allTokens, /* @__PURE__ */ new Set(), 0, result);
11468
11531
  }
11469
11532
  }
11470
- function resolveTokenRef(path53, allTokens, visited, depth, result) {
11471
- if (result.resolved.has(path53)) {
11472
- return result.resolved.get(path53);
11533
+ function resolveTokenRef(path55, allTokens, visited, depth, result) {
11534
+ if (result.resolved.has(path55)) {
11535
+ return result.resolved.get(path55);
11473
11536
  }
11474
11537
  if (depth > MAX_RESOLVE_DEPTH) {
11475
11538
  result.errors.push({
11476
- message: `Max reference depth exceeded at: ${path53}`,
11477
- path: path53
11539
+ message: `Max reference depth exceeded at: ${path55}`,
11540
+ path: path55
11478
11541
  });
11479
11542
  return void 0;
11480
11543
  }
11481
- if (visited.has(path53)) {
11544
+ if (visited.has(path55)) {
11482
11545
  result.errors.push({
11483
- message: `Circular reference detected: ${path53}`,
11484
- path: path53
11546
+ message: `Circular reference detected: ${path55}`,
11547
+ path: path55
11485
11548
  });
11486
11549
  return void 0;
11487
11550
  }
11488
- const token = allTokens.get(path53);
11551
+ const token = allTokens.get(path55);
11489
11552
  if (!token) {
11490
11553
  return void 0;
11491
11554
  }
11492
11555
  if (typeof token.$value !== "string") {
11493
11556
  const rawValue2 = stringifyTokenValue(token.$value);
11494
- result.resolved.set(path53, rawValue2);
11557
+ result.resolved.set(path55, rawValue2);
11495
11558
  return rawValue2;
11496
11559
  }
11497
11560
  const rawValue = stringifyTokenValue(token.$value);
11498
11561
  const refs = [...rawValue.matchAll(REF_PATTERN)];
11499
11562
  if (refs.length === 0) {
11500
- result.resolved.set(path53, rawValue);
11563
+ result.resolved.set(path55, rawValue);
11501
11564
  return rawValue;
11502
11565
  }
11503
- visited.add(path53);
11566
+ visited.add(path55);
11504
11567
  let resolved = rawValue;
11505
11568
  for (const ref of refs) {
11506
11569
  const refPath = ref[1];
@@ -11508,8 +11571,8 @@ function resolveTokenRef(path53, allTokens, visited, depth, result) {
11508
11571
  const refToken = allTokens.get(refPath);
11509
11572
  if (!refToken) {
11510
11573
  result.errors.push({
11511
- message: `Unresolved token reference: {${refPath}} at ${path53}`,
11512
- path: path53
11574
+ message: `Unresolved token reference: {${refPath}} at ${path55}`,
11575
+ path: path55
11513
11576
  });
11514
11577
  continue;
11515
11578
  }
@@ -11518,7 +11581,7 @@ function resolveTokenRef(path53, allTokens, visited, depth, result) {
11518
11581
  resolved = resolved.split(`{${refPath}}`).join(refValue);
11519
11582
  }
11520
11583
  }
11521
- result.resolved.set(path53, resolved);
11584
+ result.resolved.set(path55, resolved);
11522
11585
  return resolved;
11523
11586
  }
11524
11587
  function stringifyTokenValue(value) {
@@ -15575,6 +15638,252 @@ async function validateDiscussionDesignHardening(root, config) {
15575
15638
  return issues;
15576
15639
  }
15577
15640
 
15641
+ // src/core/validators/designAudit.ts
15642
+ var import_promises50 = require("fs/promises");
15643
+ var import_node_path52 = __toESM(require("path"), 1);
15644
+ var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
15645
+ function resolveAuditConfig(config) {
15646
+ const audit = config.uiux?.audit;
15647
+ const profile = config.uiux?.qualityProfile ?? "default";
15648
+ return {
15649
+ enabled: audit?.enabled ?? true,
15650
+ slopDetection: audit?.slopDetection ?? true,
15651
+ qualityProfile: profile,
15652
+ maxPrimaryCtas: audit?.maxPrimaryCtas ?? 1,
15653
+ maxRawTokenLiteralWarnings: audit?.maxRawTokenLiteralWarnings ?? 5,
15654
+ maxDuplicateFindingsPerRule: audit?.maxDuplicateFindingsPerRule ?? 5
15655
+ };
15656
+ }
15657
+ function mapSeverity(tier, profile, category) {
15658
+ if (tier === 1) return "error";
15659
+ if (tier === 2) return profile === "strict" ? "error" : "warning";
15660
+ if (profile === "default") {
15661
+ return category && COSMETIC_CATEGORIES.includes(category) ? "info" : "warning";
15662
+ }
15663
+ return "warning";
15664
+ }
15665
+ function findingToIssue(finding, profile, rulePrefix = "audit") {
15666
+ const severity = mapSeverity(finding.severityTier, profile, finding.dimension);
15667
+ return issue(
15668
+ finding.ruleId,
15669
+ finding.message,
15670
+ severity,
15671
+ finding.file,
15672
+ `${rulePrefix}.${finding.dimension}`,
15673
+ finding.evidence.length > 0 ? finding.evidence : void 0,
15674
+ "compatibility",
15675
+ finding.guidance
15676
+ );
15677
+ }
15678
+ function extractSection2(content, heading) {
15679
+ const idx = content.indexOf(heading);
15680
+ if (idx === -1) return null;
15681
+ const start = idx + heading.length;
15682
+ const headingLevel = heading.match(/^#+/)?.[0]?.length ?? 3;
15683
+ const rest = content.slice(start);
15684
+ const headingPattern = new RegExp(`^#{1,${headingLevel}} `, "m");
15685
+ const nextHeadingMatch = headingPattern.exec(rest);
15686
+ const sectionContent = nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest;
15687
+ return sectionContent.trim() || null;
15688
+ }
15689
+ function checkCtaHierarchy(content, auditConfig, file) {
15690
+ const findings = [];
15691
+ const ctaSection = extractSection2(content, "### CTA Hierarchy");
15692
+ if (!ctaSection) return findings;
15693
+ const primaryLines = ctaSection.match(/^-\s*Primary:/gm) || [];
15694
+ const primaryCount = primaryLines.length;
15695
+ if (primaryCount === 0) {
15696
+ findings.push({
15697
+ ruleId: "QFAI-AUD-001",
15698
+ dimension: "visualHierarchy",
15699
+ severityTier: 1,
15700
+ message: "No primary CTA defined in CTA Hierarchy",
15701
+ why: "Every UI screen needs a clear primary action to guide users",
15702
+ evidence: [],
15703
+ guidance: "Define at least one primary CTA in the CTA Hierarchy section",
15704
+ file
15705
+ });
15706
+ }
15707
+ if (primaryCount > auditConfig.maxPrimaryCtas) {
15708
+ findings.push({
15709
+ ruleId: "QFAI-AUD-020",
15710
+ dimension: "visualHierarchy",
15711
+ severityTier: 2,
15712
+ message: `Multiple primary CTAs detected (${primaryCount} > ${auditConfig.maxPrimaryCtas})`,
15713
+ why: "Multiple primary CTAs create decision paralysis and weaken visual hierarchy",
15714
+ evidence: primaryLines.map((l) => l.trim()),
15715
+ guidance: "Reduce to a single primary CTA per screen; demote others to secondary",
15716
+ file
15717
+ });
15718
+ }
15719
+ return findings;
15720
+ }
15721
+ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)/g;
15722
+ async function checkTokenDrift(root, auditConfig, cfg) {
15723
+ const findings = [];
15724
+ const configuredDir = cfg.uiux?.designTokensDir;
15725
+ const tokensDir = configuredDir ? import_node_path52.default.resolve(root, configuredDir) : import_node_path52.default.join(root, cfg.paths.contractsDir, "design");
15726
+ let hasTokenFiles = false;
15727
+ try {
15728
+ const entries = await (0, import_promises50.readdir)(tokensDir);
15729
+ hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
15730
+ } catch {
15731
+ return findings;
15732
+ }
15733
+ if (!hasTokenFiles) return findings;
15734
+ const contractsUiDir = import_node_path52.default.join(root, cfg.paths.contractsDir, "ui");
15735
+ let htmlFiles = [];
15736
+ try {
15737
+ const entries = await (0, import_promises50.readdir)(contractsUiDir);
15738
+ htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
15739
+ } catch {
15740
+ return findings;
15741
+ }
15742
+ let rawCount = 0;
15743
+ const sampleLiterals = [];
15744
+ for (const htmlFile of htmlFiles) {
15745
+ const content = await readSafe(import_node_path52.default.join(contractsUiDir, htmlFile));
15746
+ if (!content) continue;
15747
+ const matches = content.match(RAW_COLOR_RE);
15748
+ if (matches) {
15749
+ rawCount += matches.length;
15750
+ for (const m of matches) {
15751
+ if (sampleLiterals.length < 10) {
15752
+ sampleLiterals.push(m.toLowerCase());
15753
+ }
15754
+ }
15755
+ }
15756
+ }
15757
+ if (rawCount > auditConfig.maxRawTokenLiteralWarnings) {
15758
+ findings.push({
15759
+ ruleId: "QFAI-AUD-004",
15760
+ dimension: "tokenDiscipline",
15761
+ severityTier: 1,
15762
+ message: `Token drift: ${rawCount} raw color literal occurrences found (threshold: ${auditConfig.maxRawTokenLiteralWarnings})`,
15763
+ why: "Raw color values bypass design tokens, causing visual inconsistency",
15764
+ evidence: sampleLiterals,
15765
+ guidance: "Replace raw color literals with design token references"
15766
+ });
15767
+ }
15768
+ return findings;
15769
+ }
15770
+ function deduplicateFindings(issues, maxPerRule) {
15771
+ const counts = /* @__PURE__ */ new Map();
15772
+ const result = [];
15773
+ for (const iss of issues) {
15774
+ const count = counts.get(iss.code) ?? 0;
15775
+ if (count < maxPerRule) {
15776
+ result.push(iss);
15777
+ }
15778
+ counts.set(iss.code, count + 1);
15779
+ }
15780
+ for (const [code, count] of counts) {
15781
+ if (count > maxPerRule) {
15782
+ result.push({
15783
+ code,
15784
+ severity: "info",
15785
+ category: "compatibility",
15786
+ message: `${count - maxPerRule} additional "${code}" finding(s) suppressed (max ${maxPerRule} per rule)`,
15787
+ rule: `audit.dedup.${code}`
15788
+ });
15789
+ }
15790
+ }
15791
+ return result;
15792
+ }
15793
+ async function validateDesignAudit(root, config) {
15794
+ const auditConfig = resolveAuditConfig(config);
15795
+ if (!auditConfig.enabled) return [];
15796
+ const discussionDir = import_node_path52.default.join(root, config.paths.discussionDir);
15797
+ const packRoot = await findLatestDiscussionPackDir(discussionDir);
15798
+ if (!packRoot) return [];
15799
+ const uiBearing = await isUiBearing(packRoot);
15800
+ if (!uiBearing) return [];
15801
+ const storyPath = import_node_path52.default.join(packRoot, "03_Story-Workshop.md");
15802
+ const content = await readSafe(storyPath);
15803
+ if (!content) return [];
15804
+ const findings = [];
15805
+ findings.push(...checkCtaHierarchy(content, auditConfig, "03_Story-Workshop.md"));
15806
+ findings.push(...await checkTokenDrift(root, auditConfig, config));
15807
+ const issues = findings.map((f) => findingToIssue(f, auditConfig.qualityProfile));
15808
+ return deduplicateFindings(issues, auditConfig.maxDuplicateFindingsPerRule);
15809
+ }
15810
+
15811
+ // src/core/validators/designSlop.ts
15812
+ var import_node_fs2 = require("fs");
15813
+ var import_promises51 = require("fs/promises");
15814
+ var import_node_path53 = __toESM(require("path"), 1);
15815
+ var import_node_url4 = require("url");
15816
+ function isValidSlopPattern(rule) {
15817
+ if (typeof rule !== "object" || rule === null) return false;
15818
+ const r = rule;
15819
+ 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";
15820
+ }
15821
+ async function loadSlopPatterns(jsonPath) {
15822
+ const raw = await (0, import_promises51.readFile)(jsonPath, "utf-8");
15823
+ const parsed = JSON.parse(raw);
15824
+ if (!Array.isArray(parsed)) return [];
15825
+ return parsed.filter((r) => isValidSlopPattern(r));
15826
+ }
15827
+ function defaultPatternsPath() {
15828
+ const base = __filename;
15829
+ const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
15830
+ const baseDir = import_node_path53.default.dirname(basePath);
15831
+ const candidates = [
15832
+ import_node_path53.default.join(baseDir, "designSlopPatterns.json"),
15833
+ import_node_path53.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
15834
+ import_node_path53.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
15835
+ ];
15836
+ for (const c of candidates) {
15837
+ if ((0, import_node_fs2.existsSync)(c)) return c;
15838
+ }
15839
+ return candidates[0];
15840
+ }
15841
+ async function validateDesignSlop(root, config) {
15842
+ const auditConfig = resolveAuditConfig(config);
15843
+ if (!auditConfig.enabled) return [];
15844
+ if (!auditConfig.slopDetection) return [];
15845
+ const discussionDir = import_node_path53.default.join(root, config.paths.discussionDir);
15846
+ const packRoot = await findLatestDiscussionPackDir(discussionDir);
15847
+ if (!packRoot) return [];
15848
+ const uiBearing = await isUiBearing(packRoot);
15849
+ if (!uiBearing) return [];
15850
+ let patterns;
15851
+ try {
15852
+ patterns = await loadSlopPatterns(defaultPatternsPath());
15853
+ } catch {
15854
+ return [];
15855
+ }
15856
+ const findings = [];
15857
+ const seenRules = /* @__PURE__ */ new Set();
15858
+ for (const pattern of patterns) {
15859
+ let regex;
15860
+ try {
15861
+ regex = new RegExp(pattern.match, "gi");
15862
+ } catch {
15863
+ continue;
15864
+ }
15865
+ for (const scope of pattern.scopes) {
15866
+ const filePath = import_node_path53.default.join(packRoot, scope);
15867
+ const content = await readSafe(filePath);
15868
+ if (!content) continue;
15869
+ if (regex.test(content) && !seenRules.has(pattern.id)) {
15870
+ seenRules.add(pattern.id);
15871
+ findings.push({
15872
+ ruleId: pattern.id,
15873
+ dimension: pattern.category,
15874
+ severityTier: pattern.tier,
15875
+ message: pattern.message,
15876
+ why: `Slop pattern "${pattern.id}" matched in ${scope}`,
15877
+ evidence: [],
15878
+ guidance: pattern.guidance,
15879
+ file: scope
15880
+ });
15881
+ }
15882
+ }
15883
+ }
15884
+ return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
15885
+ }
15886
+
15578
15887
  // src/core/validate.ts
15579
15888
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
15580
15889
  async function validateProject(root, configResult, options = {}) {
@@ -15592,7 +15901,9 @@ async function validateProject(root, configResult, options = {}) {
15592
15901
  () => validateBpApDb(root, config),
15593
15902
  () => validateUiDefinitionConsistency(root, config),
15594
15903
  () => validateResearchSummary(root, config),
15595
- () => validateAgentDefinition(root, config)
15904
+ () => validateAgentDefinition(root, config),
15905
+ () => validateDesignAudit(root, config),
15906
+ () => validateDesignSlop(root, config)
15596
15907
  ];
15597
15908
  const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
15598
15909
  const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
@@ -15679,15 +15990,15 @@ var REPORT_GUARDRAILS_MAX = 20;
15679
15990
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
15680
15991
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
15681
15992
  async function createReportData(root, validation, configResult) {
15682
- const resolvedRoot = import_node_path52.default.resolve(root);
15993
+ const resolvedRoot = import_node_path54.default.resolve(root);
15683
15994
  const resolved = configResult ?? await loadConfig(resolvedRoot);
15684
15995
  const config = resolved.config;
15685
15996
  const configPath = resolved.configPath;
15686
15997
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
15687
15998
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
15688
- const apiRoot = import_node_path52.default.join(contractsRoot, "api");
15689
- const uiRoot = import_node_path52.default.join(contractsRoot, "ui");
15690
- const dbRoot = import_node_path52.default.join(contractsRoot, "db");
15999
+ const apiRoot = import_node_path54.default.join(contractsRoot, "api");
16000
+ const uiRoot = import_node_path54.default.join(contractsRoot, "ui");
16001
+ const dbRoot = import_node_path54.default.join(contractsRoot, "db");
15691
16002
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
15692
16003
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
15693
16004
  const specEntries = await collectSpecEntries(specsRoot);
@@ -16015,6 +16326,8 @@ function formatReportMarkdown(data, options = {}) {
16015
16326
  lines.push("");
16016
16327
  lines.push("- [Compatibility Issues](#compatibility-issues)");
16017
16328
  lines.push("- [Change Issues](#change-issues)");
16329
+ lines.push("- [Design Audit Findings](#design-audit-findings)");
16330
+ lines.push("- [Slop Guardrails Findings](#slop-guardrails-findings)");
16018
16331
  lines.push("- [Change Type](#change-type)");
16019
16332
  lines.push("- [Waivers](#waivers)");
16020
16333
  lines.push("- [Decision Guardrails](#decision-guardrails)");
@@ -16163,6 +16476,46 @@ function formatReportMarkdown(data, options = {}) {
16163
16476
  lines.push("### Issues");
16164
16477
  lines.push("");
16165
16478
  lines.push(...formatIssueCards(issuesByCategory.change));
16479
+ const auditIssues = data.issues.filter((i) => /^QFAI-AUD-/.test(i.code));
16480
+ if (auditIssues.length > 0) {
16481
+ lines.push("## Design Audit Findings");
16482
+ lines.push("");
16483
+ const byDimension = /* @__PURE__ */ new Map();
16484
+ for (const iss of auditIssues) {
16485
+ const dim = iss.rule?.replace(/^audit\./, "").split(".")[0] ?? "unknown";
16486
+ const group = byDimension.get(dim) ?? [];
16487
+ group.push(iss);
16488
+ byDimension.set(dim, group);
16489
+ }
16490
+ for (const [dim, dimIssues] of byDimension) {
16491
+ lines.push(`### ${dim}`);
16492
+ lines.push("");
16493
+ for (const iss of dimIssues) {
16494
+ lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
16495
+ }
16496
+ lines.push("");
16497
+ }
16498
+ }
16499
+ const slopIssues = data.issues.filter((i) => /^SLP-/.test(i.code));
16500
+ if (slopIssues.length > 0) {
16501
+ lines.push("## Slop Guardrails Findings");
16502
+ lines.push("");
16503
+ const byCategory = /* @__PURE__ */ new Map();
16504
+ for (const iss of slopIssues) {
16505
+ const cat = iss.rule?.replace(/^slop\./, "").split(".")[0] ?? "unknown";
16506
+ const group = byCategory.get(cat) ?? [];
16507
+ group.push(iss);
16508
+ byCategory.set(cat, group);
16509
+ }
16510
+ for (const [cat, catIssues] of byCategory) {
16511
+ lines.push(`### ${cat}`);
16512
+ lines.push("");
16513
+ for (const iss of catIssues) {
16514
+ lines.push(`- **${iss.severity.toUpperCase()}** [${iss.code}] ${iss.message}`);
16515
+ }
16516
+ lines.push("");
16517
+ }
16518
+ }
16166
16519
  lines.push("## Change Type");
16167
16520
  lines.push("");
16168
16521
  lines.push("### Summary");
@@ -16582,7 +16935,7 @@ async function collectChangeTypeSummary(specsRoot) {
16582
16935
  };
16583
16936
  const deltaFiles = await collectDeltaFiles(specsRoot);
16584
16937
  for (const deltaFile of deltaFiles) {
16585
- const text = await (0, import_promises50.readFile)(deltaFile, "utf-8");
16938
+ const text = await (0, import_promises52.readFile)(deltaFile, "utf-8");
16586
16939
  const parsed = parseDeltaV1(text);
16587
16940
  for (const entry of parsed.entries) {
16588
16941
  if (!entry.meta) {
@@ -16619,7 +16972,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
16619
16972
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
16620
16973
  }
16621
16974
  for (const file of specFiles) {
16622
- const text = await (0, import_promises50.readFile)(file, "utf-8");
16975
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16623
16976
  const parsed = parseSpec(text, file);
16624
16977
  const specKey = parsed.specId;
16625
16978
  if (!specKey) {
@@ -16656,7 +17009,7 @@ async function collectIds(files) {
16656
17009
  result[prefix] = /* @__PURE__ */ new Set();
16657
17010
  }
16658
17011
  for (const file of files) {
16659
- const text = await (0, import_promises50.readFile)(file, "utf-8");
17012
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16660
17013
  for (const prefix of ID_PREFIXES) {
16661
17014
  const ids = extractIds(text, prefix);
16662
17015
  ids.forEach((id) => result[prefix].add(id));
@@ -16671,7 +17024,7 @@ async function collectIds(files) {
16671
17024
  async function collectUpstreamIds(files) {
16672
17025
  const ids = /* @__PURE__ */ new Set();
16673
17026
  for (const file of files) {
16674
- const text = await (0, import_promises50.readFile)(file, "utf-8");
17027
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16675
17028
  extractAllIds(text).forEach((id) => ids.add(id));
16676
17029
  }
16677
17030
  return ids;
@@ -16692,7 +17045,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
16692
17045
  }
16693
17046
  const pattern = buildIdPattern(Array.from(upstreamIds));
16694
17047
  for (const file of targetFiles) {
16695
- const text = await (0, import_promises50.readFile)(file, "utf-8");
17048
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16696
17049
  if (pattern.test(text)) {
16697
17050
  return true;
16698
17051
  }
@@ -16811,7 +17164,7 @@ function normalizeScSources(root, sources) {
16811
17164
  async function countScenarios(scenarioFiles) {
16812
17165
  let total = 0;
16813
17166
  for (const file of scenarioFiles) {
16814
- const text = await (0, import_promises50.readFile)(file, "utf-8");
17167
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16815
17168
  const { document, errors } = parseScenarioDocument(text, file);
16816
17169
  if (!document || errors.length > 0) {
16817
17170
  continue;
@@ -16842,7 +17195,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
16842
17195
  let totalScenarios = 0;
16843
17196
  let e2eCount = 0;
16844
17197
  for (const file of scenarioFiles) {
16845
- const text = await (0, import_promises50.readFile)(file, "utf-8");
17198
+ const text = await (0, import_promises52.readFile)(file, "utf-8");
16846
17199
  const { document, errors } = parseScenarioDocument(text, file);
16847
17200
  if (!document || errors.length > 0) {
16848
17201
  continue;
@@ -16930,10 +17283,10 @@ function buildHotspots(issues) {
16930
17283
  async function collectTddCoverage(entries) {
16931
17284
  const specs = [];
16932
17285
  for (const entry of entries) {
16933
- const testCasesPath = import_node_path52.default.join(entry.dir, "06_Test-Cases.md");
17286
+ const testCasesPath = import_node_path54.default.join(entry.dir, "06_Test-Cases.md");
16934
17287
  let tcContent;
16935
17288
  try {
16936
- tcContent = await (0, import_promises50.readFile)(testCasesPath, "utf-8");
17289
+ tcContent = await (0, import_promises52.readFile)(testCasesPath, "utf-8");
16937
17290
  } catch {
16938
17291
  continue;
16939
17292
  }
@@ -16965,10 +17318,10 @@ async function collectTddCoverage(entries) {
16965
17318
  });
16966
17319
  continue;
16967
17320
  }
16968
- const tddListPath = import_node_path52.default.join(entry.dir, "tdd", "test-list.md");
17321
+ const tddListPath = import_node_path54.default.join(entry.dir, "tdd", "test-list.md");
16969
17322
  let tddContent;
16970
17323
  try {
16971
- tddContent = await (0, import_promises50.readFile)(tddListPath, "utf-8");
17324
+ tddContent = await (0, import_promises52.readFile)(tddListPath, "utf-8");
16972
17325
  } catch {
16973
17326
  specs.push({
16974
17327
  specNumber: entry.specNumber,