qfai 1.7.7 → 1.7.9

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.
@@ -1739,8 +1739,8 @@ var import_promises7 = require("fs/promises");
1739
1739
  var import_node_path8 = __toESM(require("path"), 1);
1740
1740
  var import_node_url2 = require("url");
1741
1741
  async function resolveToolVersion() {
1742
- if ("1.7.7".length > 0) {
1743
- return "1.7.7";
1742
+ if ("1.7.9".length > 0) {
1743
+ return "1.7.9";
1744
1744
  }
1745
1745
  try {
1746
1746
  const packagePath = resolvePackageJsonPath();
@@ -4906,7 +4906,7 @@ function extractRouteHintsFromEvidence(evidence) {
4906
4906
 
4907
4907
  // src/cli/commands/report.ts
4908
4908
  var import_promises61 = require("fs/promises");
4909
- var import_node_path64 = __toESM(require("path"), 1);
4909
+ var import_node_path66 = __toESM(require("path"), 1);
4910
4910
 
4911
4911
  // src/core/normalize.ts
4912
4912
  function normalizeIssuePaths(root, issues) {
@@ -5002,7 +5002,7 @@ async function createPhaseGuardResult(phase, blockedIssue) {
5002
5002
 
5003
5003
  // src/core/report.ts
5004
5004
  var import_promises59 = require("fs/promises");
5005
- var import_node_path62 = __toESM(require("path"), 1);
5005
+ var import_node_path64 = __toESM(require("path"), 1);
5006
5006
 
5007
5007
  // src/core/contractIndex.ts
5008
5008
  var import_promises18 = require("fs/promises");
@@ -5699,6 +5699,9 @@ function asTagArray(value) {
5699
5699
  return [];
5700
5700
  }
5701
5701
 
5702
+ // src/core/validate.ts
5703
+ var import_node_path63 = __toESM(require("path"), 1);
5704
+
5702
5705
  // src/core/waivers.ts
5703
5706
  var import_promises20 = require("fs/promises");
5704
5707
  var import_node_path22 = __toESM(require("path"), 1);
@@ -13098,7 +13101,7 @@ function collectLayer(layer, layerName, target, errors) {
13098
13101
  }
13099
13102
  function flattenTokens(obj, prefix, target, errors) {
13100
13103
  for (const [key, value] of Object.entries(obj)) {
13101
- const path67 = `${prefix}.${key}`;
13104
+ const path69 = `${prefix}.${key}`;
13102
13105
  if (value && typeof value === "object" && !Array.isArray(value)) {
13103
13106
  const record2 = value;
13104
13107
  if ("$value" in record2) {
@@ -13114,9 +13117,9 @@ function flattenTokens(obj, prefix, target, errors) {
13114
13117
  if (typeof record2.platform === "string") {
13115
13118
  token.platform = record2.platform;
13116
13119
  }
13117
- target.set(path67, token);
13120
+ target.set(path69, token);
13118
13121
  } else {
13119
- flattenTokens(record2, path67, target, errors);
13122
+ flattenTokens(record2, path69, target, errors);
13120
13123
  }
13121
13124
  }
13122
13125
  }
@@ -13126,44 +13129,44 @@ function resolveAllReferences(result) {
13126
13129
  for (const [key, val] of result.primitives) allTokens.set(key, val);
13127
13130
  for (const [key, val] of result.semantics) allTokens.set(key, val);
13128
13131
  for (const [key, val] of result.components) allTokens.set(key, val);
13129
- for (const [path67] of allTokens) {
13130
- resolveTokenRef(path67, allTokens, /* @__PURE__ */ new Set(), 0, result);
13132
+ for (const [path69] of allTokens) {
13133
+ resolveTokenRef(path69, allTokens, /* @__PURE__ */ new Set(), 0, result);
13131
13134
  }
13132
13135
  }
13133
- function resolveTokenRef(path67, allTokens, visited, depth, result) {
13134
- if (result.resolved.has(path67)) {
13135
- return result.resolved.get(path67);
13136
+ function resolveTokenRef(path69, allTokens, visited, depth, result) {
13137
+ if (result.resolved.has(path69)) {
13138
+ return result.resolved.get(path69);
13136
13139
  }
13137
13140
  if (depth > MAX_RESOLVE_DEPTH) {
13138
13141
  result.errors.push({
13139
- message: `Max reference depth exceeded at: ${path67}`,
13140
- path: path67
13142
+ message: `Max reference depth exceeded at: ${path69}`,
13143
+ path: path69
13141
13144
  });
13142
13145
  return void 0;
13143
13146
  }
13144
- if (visited.has(path67)) {
13147
+ if (visited.has(path69)) {
13145
13148
  result.errors.push({
13146
- message: `Circular reference detected: ${path67}`,
13147
- path: path67
13149
+ message: `Circular reference detected: ${path69}`,
13150
+ path: path69
13148
13151
  });
13149
13152
  return void 0;
13150
13153
  }
13151
- const token = allTokens.get(path67);
13154
+ const token = allTokens.get(path69);
13152
13155
  if (!token) {
13153
13156
  return void 0;
13154
13157
  }
13155
13158
  if (typeof token.$value !== "string") {
13156
13159
  const rawValue2 = stringifyTokenValue(token.$value);
13157
- result.resolved.set(path67, rawValue2);
13160
+ result.resolved.set(path69, rawValue2);
13158
13161
  return rawValue2;
13159
13162
  }
13160
13163
  const rawValue = stringifyTokenValue(token.$value);
13161
13164
  const refs = [...rawValue.matchAll(REF_PATTERN)];
13162
13165
  if (refs.length === 0) {
13163
- result.resolved.set(path67, rawValue);
13166
+ result.resolved.set(path69, rawValue);
13164
13167
  return rawValue;
13165
13168
  }
13166
- visited.add(path67);
13169
+ visited.add(path69);
13167
13170
  let resolved = rawValue;
13168
13171
  for (const ref of refs) {
13169
13172
  const refPath = ref[1];
@@ -13171,8 +13174,8 @@ function resolveTokenRef(path67, allTokens, visited, depth, result) {
13171
13174
  const refToken = allTokens.get(refPath);
13172
13175
  if (!refToken) {
13173
13176
  result.errors.push({
13174
- message: `Unresolved token reference: {${refPath}} at ${path67}`,
13175
- path: path67
13177
+ message: `Unresolved token reference: {${refPath}} at ${path69}`,
13178
+ path: path69
13176
13179
  });
13177
13180
  continue;
13178
13181
  }
@@ -13181,7 +13184,7 @@ function resolveTokenRef(path67, allTokens, visited, depth, result) {
13181
13184
  resolved = resolved.split(`{${refPath}}`).join(refValue);
13182
13185
  }
13183
13186
  }
13184
- result.resolved.set(path67, resolved);
13187
+ result.resolved.set(path69, resolved);
13185
13188
  return resolved;
13186
13189
  }
13187
13190
  function stringifyTokenValue(value) {
@@ -16912,9 +16915,64 @@ function hasAntiPatternMention(section, code) {
16912
16915
  }
16913
16916
 
16914
16917
  // src/core/validators/discussionDesignHardening.ts
16918
+ var import_node_path58 = __toESM(require("path"), 1);
16919
+
16920
+ // src/core/detection/surfaceType.ts
16921
+ var import_promises55 = require("fs/promises");
16915
16922
  var import_node_path57 = __toESM(require("path"), 1);
16923
+ var VALID_SURFACES = /* @__PURE__ */ new Set([
16924
+ "web-ui",
16925
+ "mobile-ui",
16926
+ "desktop-ui",
16927
+ "mixed",
16928
+ "non-ui"
16929
+ ]);
16916
16930
  var HTML_TAG_RE = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
16917
16931
  var MERMAID_SCREEN_FLOW_RE = /```mermaid[\s\S]*?(?:stateDiagram|flowchart|graph)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
16932
+ var YAML_SURFACE_RE = /^\s*-\s*surface(?:_type)?:\s*(\S+)/im;
16933
+ var TABLE_SURFACE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
16934
+ var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
16935
+ function parseSurface(raw) {
16936
+ const s = raw.toLowerCase();
16937
+ return VALID_SURFACES.has(s) ? s : void 0;
16938
+ }
16939
+ async function detectSurfaceType(root) {
16940
+ for (const fileName of ["01_Spec.md", "01_Context.md"]) {
16941
+ const content = await readSafe2(import_node_path57.default.join(root, fileName));
16942
+ const match = YAML_SURFACE_RE.exec(content);
16943
+ if (match?.[1]) {
16944
+ const surface = parseSurface(match[1]);
16945
+ if (surface) return surface;
16946
+ }
16947
+ }
16948
+ const storyContent = await readSafe2(import_node_path57.default.join(root, "03_Story-Workshop.md"));
16949
+ if (storyContent) {
16950
+ const tableMatch = TABLE_SURFACE_RE.exec(storyContent);
16951
+ if (tableMatch?.[1]) {
16952
+ const surface = parseSurface(tableMatch[1]);
16953
+ if (surface) return surface;
16954
+ }
16955
+ }
16956
+ try {
16957
+ await (0, import_promises55.readdir)(import_node_path57.default.join(root, "uiux"));
16958
+ return "web-ui";
16959
+ } catch {
16960
+ }
16961
+ const contractsContent = await readSafe2(import_node_path57.default.join(root, "uiux", "40_contracts.md"));
16962
+ if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return "web-ui";
16963
+ if (storyContent) {
16964
+ const stripped = storyContent.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "");
16965
+ if (HTML_TAG_RE.test(stripped)) return "web-ui";
16966
+ if (MERMAID_SCREEN_FLOW_RE.test(storyContent)) return "web-ui";
16967
+ }
16968
+ return "non-ui";
16969
+ }
16970
+ async function isUiBearingSurface(root) {
16971
+ const surface = await detectSurfaceType(root);
16972
+ return surface !== "non-ui";
16973
+ }
16974
+
16975
+ // src/core/validators/discussionDesignHardening.ts
16918
16976
  var DDS_HEADING = "## Design Direction Summary";
16919
16977
  var DDS_SUBSECTIONS = [
16920
16978
  "Option Comparison",
@@ -16927,20 +16985,8 @@ var DDS_SUBSECTIONS = [
16927
16985
  var REQUIRED_STATES = ["empty", "loading", "error", "populated"];
16928
16986
  var COMPETITIVE_REF_FIELDS = ["adopted_points", "rejected_points", "local_translation"];
16929
16987
  var PLACEHOLDER_RE = /^(?:tbd|todo|n\/a|na|xxx|\?\?\?|placeholder)$/i;
16930
- var SURFACE_TYPE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
16931
16988
  async function isUiBearing(packRoot) {
16932
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
16933
- const content = await readSafe2(storyPath);
16934
- if (!content) return false;
16935
- const surfaceMatch = SURFACE_TYPE_RE.exec(content);
16936
- if (surfaceMatch?.[1]) {
16937
- const surface = surfaceMatch[1].toLowerCase();
16938
- if (surface === "non-ui") return false;
16939
- if (["web-ui", "mobile-ui", "desktop-ui", "mixed"].includes(surface)) return true;
16940
- }
16941
- if (HTML_TAG_RE.test(content)) return true;
16942
- if (MERMAID_SCREEN_FLOW_RE.test(content)) return true;
16943
- return false;
16989
+ return isUiBearingSurface(packRoot);
16944
16990
  }
16945
16991
  function extractDdsSection(content) {
16946
16992
  const idx = content.indexOf(DDS_HEADING);
@@ -16972,7 +17018,7 @@ function extractOptionNames(optionSection) {
16972
17018
  }
16973
17019
  async function validateDdsPresence(packRoot) {
16974
17020
  const issues = [];
16975
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17021
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
16976
17022
  const content = await readSafe2(storyPath);
16977
17023
  const relPath = "03_Story-Workshop.md";
16978
17024
  if (!content || !content.includes(DDS_HEADING)) {
@@ -17006,7 +17052,7 @@ async function validateDdsPresence(packRoot) {
17006
17052
  }
17007
17053
  async function validateOptionComparison2(packRoot) {
17008
17054
  const issues = [];
17009
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17055
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17010
17056
  const content = await readSafe2(storyPath);
17011
17057
  if (!content) return issues;
17012
17058
  const dds = extractDdsSection(content);
@@ -17030,7 +17076,7 @@ async function validateOptionComparison2(packRoot) {
17030
17076
  }
17031
17077
  async function validateAnchorScreen(packRoot) {
17032
17078
  const issues = [];
17033
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17079
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17034
17080
  const content = await readSafe2(storyPath);
17035
17081
  if (!content) return issues;
17036
17082
  const dds = extractDdsSection(content);
@@ -17072,7 +17118,7 @@ async function validateAnchorScreen(packRoot) {
17072
17118
  }
17073
17119
  async function validateCompetitiveRefs2(packRoot) {
17074
17120
  const issues = [];
17075
- const sourcesPath = import_node_path57.default.join(packRoot, "04_Sources.md");
17121
+ const sourcesPath = import_node_path58.default.join(packRoot, "04_Sources.md");
17076
17122
  const content = await readSafe2(sourcesPath);
17077
17123
  if (!content) return issues;
17078
17124
  const registryHeadingRe = /^##\s+Competitive Reference Registry\b.*$/m;
@@ -17129,7 +17175,7 @@ function fieldGuidance(field) {
17129
17175
  }
17130
17176
  async function validateCtaHierarchy(packRoot) {
17131
17177
  const issues = [];
17132
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17178
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17133
17179
  const content = await readSafe2(storyPath);
17134
17180
  if (!content) return issues;
17135
17181
  const dds = extractDdsSection(content);
@@ -17167,7 +17213,7 @@ async function validateCtaHierarchy(packRoot) {
17167
17213
  }
17168
17214
  async function validateStateCoverage(packRoot) {
17169
17215
  const issues = [];
17170
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17216
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17171
17217
  const content = await readSafe2(storyPath);
17172
17218
  if (!content) return issues;
17173
17219
  const dds = extractDdsSection(content);
@@ -17192,7 +17238,7 @@ async function validateStateCoverage(packRoot) {
17192
17238
  }
17193
17239
  async function validateDesignAntiGoals(packRoot) {
17194
17240
  const issues = [];
17195
- const storyPath = import_node_path57.default.join(packRoot, "03_Story-Workshop.md");
17241
+ const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17196
17242
  const content = await readSafe2(storyPath);
17197
17243
  if (!content) return issues;
17198
17244
  const dds = extractDdsSection(content);
@@ -17229,7 +17275,7 @@ async function validateDesignAntiGoals(packRoot) {
17229
17275
  return issues;
17230
17276
  }
17231
17277
  async function validateDiscussionDesignHardening(root, config) {
17232
- const discussionDir = import_node_path57.default.join(root, config.paths.discussionDir);
17278
+ const discussionDir = import_node_path58.default.join(root, config.paths.discussionDir);
17233
17279
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
17234
17280
  if (!packRoot) return [];
17235
17281
  const uiBearing = await isUiBearing(packRoot);
@@ -17246,8 +17292,8 @@ async function validateDiscussionDesignHardening(root, config) {
17246
17292
  }
17247
17293
 
17248
17294
  // src/core/validators/designAudit.ts
17249
- var import_promises55 = require("fs/promises");
17250
- var import_node_path58 = __toESM(require("path"), 1);
17295
+ var import_promises56 = require("fs/promises");
17296
+ var import_node_path59 = __toESM(require("path"), 1);
17251
17297
  var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
17252
17298
  function resolveAuditConfig(config) {
17253
17299
  const audit = config.uiux?.audit;
@@ -17329,19 +17375,19 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
17329
17375
  async function checkTokenDrift(root, auditConfig, cfg) {
17330
17376
  const findings = [];
17331
17377
  const configuredDir = cfg.uiux?.designTokensDir;
17332
- const tokensDir = configuredDir ? import_node_path58.default.resolve(root, configuredDir) : import_node_path58.default.join(root, cfg.paths.contractsDir, "design");
17378
+ const tokensDir = configuredDir ? import_node_path59.default.resolve(root, configuredDir) : import_node_path59.default.join(root, cfg.paths.contractsDir, "design");
17333
17379
  let hasTokenFiles = false;
17334
17380
  try {
17335
- const entries = await (0, import_promises55.readdir)(tokensDir);
17381
+ const entries = await (0, import_promises56.readdir)(tokensDir);
17336
17382
  hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
17337
17383
  } catch {
17338
17384
  return findings;
17339
17385
  }
17340
17386
  if (!hasTokenFiles) return findings;
17341
- const contractsUiDir = import_node_path58.default.join(root, cfg.paths.contractsDir, "ui");
17387
+ const contractsUiDir = import_node_path59.default.join(root, cfg.paths.contractsDir, "ui");
17342
17388
  let htmlFiles = [];
17343
17389
  try {
17344
- const entries = await (0, import_promises55.readdir)(contractsUiDir);
17390
+ const entries = await (0, import_promises56.readdir)(contractsUiDir);
17345
17391
  htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
17346
17392
  } catch {
17347
17393
  return findings;
@@ -17349,7 +17395,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
17349
17395
  let rawCount = 0;
17350
17396
  const sampleLiterals = [];
17351
17397
  for (const htmlFile of htmlFiles) {
17352
- const content = await readSafe2(import_node_path58.default.join(contractsUiDir, htmlFile));
17398
+ const content = await readSafe2(import_node_path59.default.join(contractsUiDir, htmlFile));
17353
17399
  if (!content) continue;
17354
17400
  const matches = content.match(RAW_COLOR_RE);
17355
17401
  if (matches) {
@@ -17400,12 +17446,12 @@ function deduplicateFindings(issues, maxPerRule) {
17400
17446
  async function validateDesignAudit(root, config) {
17401
17447
  const auditConfig = resolveAuditConfig(config);
17402
17448
  if (!auditConfig.enabled) return [];
17403
- const discussionDir = import_node_path58.default.join(root, config.paths.discussionDir);
17449
+ const discussionDir = import_node_path59.default.join(root, config.paths.discussionDir);
17404
17450
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
17405
17451
  if (!packRoot) return [];
17406
17452
  const uiBearing = await isUiBearing(packRoot);
17407
17453
  if (!uiBearing) return [];
17408
- const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
17454
+ const storyPath = import_node_path59.default.join(packRoot, "03_Story-Workshop.md");
17409
17455
  const content = await readSafe2(storyPath);
17410
17456
  if (!content) return [];
17411
17457
  const findings = [];
@@ -17417,8 +17463,8 @@ async function validateDesignAudit(root, config) {
17417
17463
 
17418
17464
  // src/core/validators/designSlop.ts
17419
17465
  var import_node_fs2 = require("fs");
17420
- var import_promises56 = require("fs/promises");
17421
- var import_node_path59 = __toESM(require("path"), 1);
17466
+ var import_promises57 = require("fs/promises");
17467
+ var import_node_path60 = __toESM(require("path"), 1);
17422
17468
  var import_node_url4 = require("url");
17423
17469
  function isValidSlopPattern(rule) {
17424
17470
  if (typeof rule !== "object" || rule === null) return false;
@@ -17426,7 +17472,7 @@ function isValidSlopPattern(rule) {
17426
17472
  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";
17427
17473
  }
17428
17474
  async function loadSlopPatterns(jsonPath) {
17429
- const raw = await (0, import_promises56.readFile)(jsonPath, "utf-8");
17475
+ const raw = await (0, import_promises57.readFile)(jsonPath, "utf-8");
17430
17476
  const parsed = JSON.parse(raw);
17431
17477
  if (!Array.isArray(parsed)) return [];
17432
17478
  return parsed.filter((r) => isValidSlopPattern(r));
@@ -17434,11 +17480,11 @@ async function loadSlopPatterns(jsonPath) {
17434
17480
  function defaultPatternsPath() {
17435
17481
  const base = __filename;
17436
17482
  const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
17437
- const baseDir = import_node_path59.default.dirname(basePath);
17483
+ const baseDir = import_node_path60.default.dirname(basePath);
17438
17484
  const candidates = [
17439
- import_node_path59.default.join(baseDir, "designSlopPatterns.json"),
17440
- import_node_path59.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
17441
- import_node_path59.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
17485
+ import_node_path60.default.join(baseDir, "designSlopPatterns.json"),
17486
+ import_node_path60.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
17487
+ import_node_path60.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
17442
17488
  ];
17443
17489
  for (const c of candidates) {
17444
17490
  if ((0, import_node_fs2.existsSync)(c)) return c;
@@ -17449,7 +17495,7 @@ async function validateDesignSlop(root, config) {
17449
17495
  const auditConfig = resolveAuditConfig(config);
17450
17496
  if (!auditConfig.enabled) return [];
17451
17497
  if (!auditConfig.slopDetection) return [];
17452
- const discussionDir = import_node_path59.default.join(root, config.paths.discussionDir);
17498
+ const discussionDir = import_node_path60.default.join(root, config.paths.discussionDir);
17453
17499
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
17454
17500
  if (!packRoot) return [];
17455
17501
  const uiBearing = await isUiBearing(packRoot);
@@ -17470,7 +17516,7 @@ async function validateDesignSlop(root, config) {
17470
17516
  continue;
17471
17517
  }
17472
17518
  for (const scope of pattern.scopes) {
17473
- const filePath = import_node_path59.default.join(packRoot, scope);
17519
+ const filePath = import_node_path60.default.join(packRoot, scope);
17474
17520
  const content = await readSafe2(filePath);
17475
17521
  if (!content) continue;
17476
17522
  if (regex.test(content) && !seenRules.has(pattern.id)) {
@@ -17496,40 +17542,8 @@ var import_promises58 = require("fs/promises");
17496
17542
  var import_node_path61 = __toESM(require("path"), 1);
17497
17543
 
17498
17544
  // src/core/validators/uixDetection.ts
17499
- var import_promises57 = require("fs/promises");
17500
- var import_node_path60 = __toESM(require("path"), 1);
17501
- var UI_BEARING_SURFACES = /* @__PURE__ */ new Set(["web-ui", "mobile-ui", "desktop-ui", "mixed"]);
17502
- var NON_UI_SURFACES = /* @__PURE__ */ new Set(["non-ui"]);
17503
- function stripCodeBlocks(content) {
17504
- let stripped = content.replace(/```[\s\S]*?```/g, "");
17505
- stripped = stripped.replace(/`[^`]+`/g, "");
17506
- return stripped;
17507
- }
17508
- var HTML_TAG_RE2 = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
17509
- var MERMAID_SCREEN_FLOW_RE2 = /```mermaid[\s\S]*?(?:stateDiagram)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
17510
- var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
17511
17545
  async function isUiBearingSpec(root) {
17512
- const specContent = await readSafe2(import_node_path60.default.join(root, "01_Spec.md"));
17513
- const surfaceMatch = /^\s*-\s*surface:\s*(\S+)/im.exec(specContent);
17514
- if (surfaceMatch?.[1]) {
17515
- const surface = surfaceMatch[1].toLowerCase();
17516
- if (UI_BEARING_SURFACES.has(surface)) return true;
17517
- if (NON_UI_SURFACES.has(surface)) return false;
17518
- }
17519
- try {
17520
- await (0, import_promises57.readdir)(import_node_path60.default.join(root, "uiux"));
17521
- return true;
17522
- } catch {
17523
- }
17524
- const contractsContent = await readSafe2(import_node_path60.default.join(root, "uiux", "40_contracts.md"));
17525
- if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return true;
17526
- const storyContent = await readSafe2(import_node_path60.default.join(root, "03_Story-Workshop.md"));
17527
- if (storyContent) {
17528
- const stripped = stripCodeBlocks(storyContent);
17529
- if (HTML_TAG_RE2.test(stripped)) return true;
17530
- if (MERMAID_SCREEN_FLOW_RE2.test(storyContent)) return true;
17531
- }
17532
- return false;
17546
+ return isUiBearingSurface(root);
17533
17547
  }
17534
17548
 
17535
17549
  // src/core/validators/uixValidators.ts
@@ -17945,6 +17959,177 @@ async function runAllUixValidators(root, config) {
17945
17959
  return issues;
17946
17960
  }
17947
17961
 
17962
+ // src/core/validators/skill/fullHarnessSkill.ts
17963
+ var import_node_path62 = __toESM(require("path"), 1);
17964
+ function harnessIssue(code, message, severity, file, suggestedAction) {
17965
+ return {
17966
+ code,
17967
+ severity,
17968
+ category: "compatibility",
17969
+ message,
17970
+ file,
17971
+ suggested_action: suggestedAction
17972
+ };
17973
+ }
17974
+ var WORKFLOW_INDICATORS = ["iteration", "loop", "convergence", "evidence"];
17975
+ var EVIDENCE_INDICATORS = [
17976
+ "render evidence",
17977
+ "test results",
17978
+ "validator output",
17979
+ "evidence collection"
17980
+ ];
17981
+ var REVIEWER_INDICATORS = ["reviewer", "review findings", "review summary"];
17982
+ var CALIBRATION_INDICATORS = ["calibration", "scoring-ready", "threshold"];
17983
+ async function validateFullHarnessSkill(skillsDir) {
17984
+ const issues = [];
17985
+ const skillPath = import_node_path62.default.join(skillsDir, "qfai-prototyping-full-harness");
17986
+ const skillMdPath = import_node_path62.default.join(skillPath, "SKILL.md");
17987
+ const content = await readSafe2(skillMdPath);
17988
+ const skillFileExists = content.length > 0;
17989
+ if (!skillFileExists) {
17990
+ issues.push(
17991
+ harnessIssue(
17992
+ "UIX-VAL-FULL-HARNESS-ENTRYPOINT-MISSING",
17993
+ "Dedicated full-harness skill file is missing.",
17994
+ "error",
17995
+ "qfai-prototyping-full-harness/SKILL.md",
17996
+ "Create a dedicated full-harness skill file at the skills directory."
17997
+ )
17998
+ );
17999
+ return {
18000
+ skillFileExists: false,
18001
+ hasWorkflowLoop: false,
18002
+ hasEvidenceCollection: false,
18003
+ hasReviewerInvocation: false,
18004
+ hasCalibrationObligation: false,
18005
+ issues
18006
+ };
18007
+ }
18008
+ const lower = content.toLowerCase();
18009
+ const hasWorkflowLoop = WORKFLOW_INDICATORS.some((i) => lower.includes(i)) && !isRoutingOnly(lower);
18010
+ const hasEvidenceCollection = EVIDENCE_INDICATORS.some((i) => lower.includes(i));
18011
+ const hasReviewerInvocation = REVIEWER_INDICATORS.some((i) => lower.includes(i));
18012
+ const hasCalibrationObligation = CALIBRATION_INDICATORS.some((i) => lower.includes(i));
18013
+ if (!hasWorkflowLoop) {
18014
+ issues.push(
18015
+ harnessIssue(
18016
+ "UIX-VAL-FULL-HARNESS-NO-WORKFLOW",
18017
+ "Full-harness skill file does not define a workflow loop.",
18018
+ "error",
18019
+ "qfai-prototyping-full-harness/SKILL.md",
18020
+ "Add workflow loop definition with iteration, evidence, review, and calibration steps."
18021
+ )
18022
+ );
18023
+ }
18024
+ return {
18025
+ skillFileExists,
18026
+ hasWorkflowLoop,
18027
+ hasEvidenceCollection,
18028
+ hasReviewerInvocation,
18029
+ hasCalibrationObligation,
18030
+ issues
18031
+ };
18032
+ }
18033
+ function isRoutingOnly(content) {
18034
+ const routingPatterns = [
18035
+ /^this\s+skill\s+(?:routes?|redirects?|delegates?)\s+to/im,
18036
+ /^see\s+`?qfai\s+prototyping/im
18037
+ ];
18038
+ return routingPatterns.some((p) => p.test(content));
18039
+ }
18040
+
18041
+ // src/core/validators/skill/prototypingSkill.ts
18042
+ var BANNED_PHRASES = [
18043
+ "must run runtime checks",
18044
+ "ui routes reachable",
18045
+ "api non-404",
18046
+ "db objects present"
18047
+ ];
18048
+ var REQUIRED_MODES = ["low-cost", "standard", "full-harness"];
18049
+ var STATIC_FIRST_INDICATORS = [
18050
+ "static-first",
18051
+ "static checks",
18052
+ "no runtime",
18053
+ "file-based"
18054
+ ];
18055
+ var RUNTIME_HEAVY_INDICATORS = [
18056
+ "browser required by default",
18057
+ "runtime mandatory",
18058
+ "always execute runtime"
18059
+ ];
18060
+ function skillIssue(code, message, severity, suggestedAction) {
18061
+ return {
18062
+ code,
18063
+ severity,
18064
+ category: "compatibility",
18065
+ message,
18066
+ file: "SKILL.md",
18067
+ suggested_action: suggestedAction
18068
+ };
18069
+ }
18070
+ function scanBannedPhrases(content) {
18071
+ const lower = content.toLowerCase();
18072
+ return BANNED_PHRASES.filter((phrase) => lower.includes(phrase));
18073
+ }
18074
+ function checkModeHeadings(content) {
18075
+ const present = [];
18076
+ const missing = [];
18077
+ for (const mode of REQUIRED_MODES) {
18078
+ const pattern = new RegExp(`^#+\\s+.*${mode.replace("-", "[-\\s]")}`, "im");
18079
+ if (pattern.test(content)) {
18080
+ present.push(mode);
18081
+ } else {
18082
+ missing.push(mode);
18083
+ }
18084
+ }
18085
+ return { present, missing };
18086
+ }
18087
+ function hasNonUiNaDocumentation(content) {
18088
+ const lower = content.toLowerCase();
18089
+ return lower.includes("n/a") && (lower.includes("non-ui") || lower.includes("non_ui"));
18090
+ }
18091
+ function isStaticFirstAligned(content) {
18092
+ const lower = content.toLowerCase();
18093
+ const hasStatic = STATIC_FIRST_INDICATORS.some((i) => lower.includes(i));
18094
+ const hasRuntime = RUNTIME_HEAVY_INDICATORS.some((i) => lower.includes(i));
18095
+ return hasStatic && !hasRuntime;
18096
+ }
18097
+ function validatePrototypingSkillContent(content) {
18098
+ const bannedPhraseMatches = scanBannedPhrases(content);
18099
+ const { present: modesPresent, missing: modesMissing } = checkModeHeadings(content);
18100
+ const nonUiPath = hasNonUiNaDocumentation(content);
18101
+ const staticFirst = isStaticFirstAligned(content);
18102
+ const issues = [];
18103
+ if (bannedPhraseMatches.length > 0) {
18104
+ issues.push(
18105
+ skillIssue(
18106
+ "UIX-VAL-SKILL-BANNED-PHRASE",
18107
+ `Prototyping skill contains banned phrases: ${bannedPhraseMatches.join(", ")}`,
18108
+ "error",
18109
+ "Remove banned runtime-heavy phrases from the prototyping skill body."
18110
+ )
18111
+ );
18112
+ }
18113
+ if (modesMissing.length > 0) {
18114
+ issues.push(
18115
+ skillIssue(
18116
+ "UIX-VAL-SKILL-MODE-MISSING",
18117
+ `Prototyping skill missing mode sections: ${modesMissing.join(", ")}`,
18118
+ "error",
18119
+ `Add section headings for: ${modesMissing.join(", ")}`
18120
+ )
18121
+ );
18122
+ }
18123
+ return {
18124
+ bannedPhraseMatches,
18125
+ modesPresent,
18126
+ modesMissing,
18127
+ hasNonUiNaPath: nonUiPath,
18128
+ isStaticFirstAligned: staticFirst,
18129
+ issues
18130
+ };
18131
+ }
18132
+
17948
18133
  // src/core/validate.ts
17949
18134
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
17950
18135
  async function validateProject(root, configResult, options = {}) {
@@ -17979,6 +18164,11 @@ async function validateProject(root, configResult, options = {}) {
17979
18164
  rule: "uiux.performanceBudget"
17980
18165
  });
17981
18166
  }
18167
+ const skillsDir = resolvePath(root, config, "skillsDir");
18168
+ const fullHarnessResult = await validateFullHarnessSkill(skillsDir);
18169
+ const prototypingSkillPath = import_node_path63.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
18170
+ const prototypingSkillContent = await readSafe2(prototypingSkillPath);
18171
+ const prototypingSkillResult = prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent) : { issues: [] };
17982
18172
  const findings = [
17983
18173
  ...configIssues,
17984
18174
  ...await validateRepositoryHygiene(root, config),
@@ -18009,6 +18199,8 @@ async function validateProject(root, configResult, options = {}) {
18009
18199
  ...await validateNavigationFlow(root, config),
18010
18200
  ...await validateRenderCritique(root, config),
18011
18201
  ...await validateDesignFidelity(root, config),
18202
+ ...fullHarnessResult.issues,
18203
+ ...prototypingSkillResult.issues,
18012
18204
  ...uiuxIssues
18013
18205
  ];
18014
18206
  const { issues, waivers } = await applyWaivers(root, findings);
@@ -18052,15 +18244,15 @@ var REPORT_GUARDRAILS_MAX = 20;
18052
18244
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
18053
18245
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
18054
18246
  async function createReportData(root, validation, configResult) {
18055
- const resolvedRoot = import_node_path62.default.resolve(root);
18247
+ const resolvedRoot = import_node_path64.default.resolve(root);
18056
18248
  const resolved = configResult ?? await loadConfig(resolvedRoot);
18057
18249
  const config = resolved.config;
18058
18250
  const configPath = resolved.configPath;
18059
18251
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
18060
18252
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
18061
- const apiRoot = import_node_path62.default.join(contractsRoot, "api");
18062
- const uiRoot = import_node_path62.default.join(contractsRoot, "ui");
18063
- const dbRoot = import_node_path62.default.join(contractsRoot, "db");
18253
+ const apiRoot = import_node_path64.default.join(contractsRoot, "api");
18254
+ const uiRoot = import_node_path64.default.join(contractsRoot, "ui");
18255
+ const dbRoot = import_node_path64.default.join(contractsRoot, "db");
18064
18256
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
18065
18257
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
18066
18258
  const specEntries = await collectSpecEntries(specsRoot);
@@ -19345,7 +19537,7 @@ function buildHotspots(issues) {
19345
19537
  async function collectTddCoverage(entries) {
19346
19538
  const specs = [];
19347
19539
  for (const entry of entries) {
19348
- const testCasesPath = import_node_path62.default.join(entry.dir, "06_Test-Cases.md");
19540
+ const testCasesPath = import_node_path64.default.join(entry.dir, "06_Test-Cases.md");
19349
19541
  let tcContent;
19350
19542
  try {
19351
19543
  tcContent = await (0, import_promises59.readFile)(testCasesPath, "utf-8");
@@ -19380,7 +19572,7 @@ async function collectTddCoverage(entries) {
19380
19572
  });
19381
19573
  continue;
19382
19574
  }
19383
- const tddListPath = import_node_path62.default.join(entry.dir, "tdd", "test-list.md");
19575
+ const tddListPath = import_node_path64.default.join(entry.dir, "tdd", "test-list.md");
19384
19576
  let tddContent;
19385
19577
  try {
19386
19578
  tddContent = await (0, import_promises59.readFile)(tddListPath, "utf-8");
@@ -19467,7 +19659,7 @@ async function collectTddCoverage(entries) {
19467
19659
 
19468
19660
  // src/core/specPackReport.ts
19469
19661
  var import_promises60 = require("fs/promises");
19470
- var import_node_path63 = __toESM(require("path"), 1);
19662
+ var import_node_path65 = __toESM(require("path"), 1);
19471
19663
  var REQUIRED_LEDGER_COLUMNS = [
19472
19664
  "trace_id",
19473
19665
  "obj_id",
@@ -19485,8 +19677,8 @@ async function writeSpecPackReports(root, config) {
19485
19677
  const entries = await collectSpecEntries(specsRoot);
19486
19678
  const contractIndex = await buildContractIndex(root, config);
19487
19679
  for (const entry of entries) {
19488
- const specName = import_node_path63.default.basename(entry.dir);
19489
- const outputDir = import_node_path63.default.join(outRoot, specName);
19680
+ const specName = import_node_path65.default.basename(entry.dir);
19681
+ const outputDir = import_node_path65.default.join(outRoot, specName);
19490
19682
  await (0, import_promises60.mkdir)(outputDir, { recursive: true });
19491
19683
  const [acText, tcText, exText, ledgerText] = await Promise.all([
19492
19684
  readSafe12(entry.acceptanceCriteriaPath),
@@ -19513,13 +19705,13 @@ async function writeSpecPackReports(root, config) {
19513
19705
  });
19514
19706
  const graph = buildTraceabilityGraph(ledgerRows);
19515
19707
  await (0, import_promises60.writeFile)(
19516
- import_node_path63.default.join(outputDir, "coverage.md"),
19708
+ import_node_path65.default.join(outputDir, "coverage.md"),
19517
19709
  `${formatCoverageMarkdown(specName, coverage)}
19518
19710
  `,
19519
19711
  "utf-8"
19520
19712
  );
19521
19713
  await (0, import_promises60.writeFile)(
19522
- import_node_path63.default.join(outputDir, "traceability-graph.json"),
19714
+ import_node_path65.default.join(outputDir, "traceability-graph.json"),
19523
19715
  `${JSON.stringify(graph, null, 2)}
19524
19716
  `,
19525
19717
  "utf-8"
@@ -19712,7 +19904,7 @@ function warnIfTruncated(scan, context) {
19712
19904
 
19713
19905
  // src/cli/commands/report.ts
19714
19906
  async function runReport(options) {
19715
- const root = import_node_path64.default.resolve(options.root);
19907
+ const root = import_node_path66.default.resolve(options.root);
19716
19908
  const configResult = await loadConfig(root);
19717
19909
  let validation;
19718
19910
  let blockedByPhaseGuard = false;
@@ -19728,7 +19920,7 @@ async function runReport(options) {
19728
19920
  validation = normalized;
19729
19921
  } else {
19730
19922
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
19731
- const inputPath = import_node_path64.default.isAbsolute(input) ? input : import_node_path64.default.resolve(root, input);
19923
+ const inputPath = import_node_path66.default.isAbsolute(input) ? input : import_node_path66.default.resolve(root, input);
19732
19924
  try {
19733
19925
  validation = await readValidationResult(inputPath);
19734
19926
  } catch (err) {
@@ -19755,10 +19947,10 @@ async function runReport(options) {
19755
19947
  warnIfTruncated(data.traceability.testFiles, "report");
19756
19948
  const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
19757
19949
  const outRoot = resolvePath(root, configResult.config, "outDir");
19758
- const defaultOut = options.format === "json" ? import_node_path64.default.join(outRoot, "report.json") : import_node_path64.default.join(outRoot, "report.md");
19950
+ const defaultOut = options.format === "json" ? import_node_path66.default.join(outRoot, "report.json") : import_node_path66.default.join(outRoot, "report.md");
19759
19951
  const out = options.outPath ?? defaultOut;
19760
- const outPath = import_node_path64.default.isAbsolute(out) ? out : import_node_path64.default.resolve(root, out);
19761
- await (0, import_promises61.mkdir)(import_node_path64.default.dirname(outPath), { recursive: true });
19952
+ const outPath = import_node_path66.default.isAbsolute(out) ? out : import_node_path66.default.resolve(root, out);
19953
+ await (0, import_promises61.mkdir)(import_node_path66.default.dirname(outPath), { recursive: true });
19762
19954
  await (0, import_promises61.writeFile)(outPath, `${output}
19763
19955
  `, "utf-8");
19764
19956
  await writeSpecPackReports(root, configResult.config);
@@ -19834,21 +20026,21 @@ function isMissingFileError2(error2) {
19834
20026
  return record2.code === "ENOENT";
19835
20027
  }
19836
20028
  async function writeValidationResult(root, outputPath, result) {
19837
- const abs = import_node_path64.default.isAbsolute(outputPath) ? outputPath : import_node_path64.default.resolve(root, outputPath);
19838
- await (0, import_promises61.mkdir)(import_node_path64.default.dirname(abs), { recursive: true });
20029
+ const abs = import_node_path66.default.isAbsolute(outputPath) ? outputPath : import_node_path66.default.resolve(root, outputPath);
20030
+ await (0, import_promises61.mkdir)(import_node_path66.default.dirname(abs), { recursive: true });
19839
20031
  await (0, import_promises61.writeFile)(abs, `${JSON.stringify(result, null, 2)}
19840
20032
  `, "utf-8");
19841
20033
  }
19842
20034
 
19843
20035
  // src/cli/commands/validate.ts
19844
20036
  var import_promises63 = require("fs/promises");
19845
- var import_node_path66 = __toESM(require("path"), 1);
20037
+ var import_node_path68 = __toESM(require("path"), 1);
19846
20038
 
19847
20039
  // src/core/runLog.ts
19848
20040
  var import_promises62 = require("fs/promises");
19849
- var import_node_path65 = __toESM(require("path"), 1);
20041
+ var import_node_path67 = __toESM(require("path"), 1);
19850
20042
  async function writeValidateRunLog(input) {
19851
- const root = import_node_path65.default.resolve(input.root);
20043
+ const root = import_node_path67.default.resolve(input.root);
19852
20044
  const outDir = resolvePath(root, input.config, "outDir");
19853
20045
  await (0, import_promises62.mkdir)(outDir, { recursive: true });
19854
20046
  const { runId, reportDir } = await allocateRunReportDir(outDir, input.startedAt);
@@ -19895,10 +20087,10 @@ async function writeValidateRunLog(input) {
19895
20087
  errors,
19896
20088
  warnings
19897
20089
  });
19898
- await writeJson(import_node_path65.default.join(reportDir, "run.json"), runJson);
19899
- await writeJson(import_node_path65.default.join(reportDir, "validator.json"), validatorJson);
19900
- await writeJson(import_node_path65.default.join(reportDir, "traceability.json"), traceabilityJson);
19901
- await (0, import_promises62.writeFile)(import_node_path65.default.join(reportDir, "summary.md"), `${summaryMd}
20090
+ await writeJson(import_node_path67.default.join(reportDir, "run.json"), runJson);
20091
+ await writeJson(import_node_path67.default.join(reportDir, "validator.json"), validatorJson);
20092
+ await writeJson(import_node_path67.default.join(reportDir, "traceability.json"), traceabilityJson);
20093
+ await (0, import_promises62.writeFile)(import_node_path67.default.join(reportDir, "summary.md"), `${summaryMd}
19902
20094
  `, "utf-8");
19903
20095
  return {
19904
20096
  runId,
@@ -20022,7 +20214,7 @@ async function allocateRunReportDir(outDir, startedAt) {
20022
20214
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
20023
20215
  const candidateDate = new Date(startedAt.getTime() + attempt);
20024
20216
  const runId = `run-${formatTimestamp17(candidateDate)}`;
20025
- const reportDir = import_node_path65.default.join(outDir, runId);
20217
+ const reportDir = import_node_path67.default.join(outDir, runId);
20026
20218
  try {
20027
20219
  await (0, import_promises62.mkdir)(reportDir);
20028
20220
  return { runId, reportDir };
@@ -20053,7 +20245,7 @@ function shouldFail(result, failOn) {
20053
20245
  // src/cli/commands/validate.ts
20054
20246
  async function runValidate(options) {
20055
20247
  const startedAt = /* @__PURE__ */ new Date();
20056
- const root = import_node_path66.default.resolve(options.root);
20248
+ const root = import_node_path68.default.resolve(options.root);
20057
20249
  const configResult = await loadConfig(root);
20058
20250
  const blockedIssue = buildCiRefinementIssue(options.phase);
20059
20251
  const blockedByPhaseGuard = blockedIssue !== null;
@@ -20209,12 +20401,12 @@ function issueKey(issue2) {
20209
20401
  }
20210
20402
  async function emitJson(result, root, jsonPath) {
20211
20403
  const abs = resolveJsonPath(root, jsonPath);
20212
- await (0, import_promises63.mkdir)(import_node_path66.default.dirname(abs), { recursive: true });
20404
+ await (0, import_promises63.mkdir)(import_node_path68.default.dirname(abs), { recursive: true });
20213
20405
  await (0, import_promises63.writeFile)(abs, `${JSON.stringify(result, null, 2)}
20214
20406
  `, "utf-8");
20215
20407
  }
20216
20408
  function resolveJsonPath(root, jsonPath) {
20217
- return import_node_path66.default.isAbsolute(jsonPath) ? jsonPath : import_node_path66.default.resolve(root, jsonPath);
20409
+ return import_node_path68.default.isAbsolute(jsonPath) ? jsonPath : import_node_path68.default.resolve(root, jsonPath);
20218
20410
  }
20219
20411
  var GITHUB_ANNOTATION_LIMIT = 100;
20220
20412
  var ISSUE_EXPECTED_BY_CODE = {