qfai 1.7.6 → 1.7.7

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
@@ -455,6 +455,15 @@ function normalizeUiux(raw, configPath, issues) {
455
455
  );
456
456
  }
457
457
  }
458
+ if (raw.phase1ReleaseDate !== void 0) {
459
+ if (typeof raw.phase1ReleaseDate === "string" && !isNaN(new Date(raw.phase1ReleaseDate).getTime())) {
460
+ result.phase1ReleaseDate = raw.phase1ReleaseDate;
461
+ } else {
462
+ issues.push(
463
+ configIssue(configPath, "uiux.phase1ReleaseDate \u306F\u6709\u52B9\u306A\u65E5\u4ED8\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
464
+ );
465
+ }
466
+ }
458
467
  if (raw.warning_as_error_override !== void 0) {
459
468
  if (Array.isArray(raw.warning_as_error_override) && raw.warning_as_error_override.every((v) => typeof v === "string")) {
460
469
  result.warning_as_error_override = raw.warning_as_error_override;
@@ -479,6 +488,12 @@ function normalizeUiux(raw, configPath, issues) {
479
488
  result.audit = audit;
480
489
  }
481
490
  }
491
+ if (raw.migration !== void 0) {
492
+ const migration = normalizeUiuxMigration(raw.migration, configPath, issues);
493
+ if (migration) {
494
+ result.migration = migration;
495
+ }
496
+ }
482
497
  return Object.keys(result).length > 0 ? result : void 0;
483
498
  }
484
499
  function normalizeUiuxAudit(raw, configPath, issues) {
@@ -538,6 +553,23 @@ function normalizeUiuxAudit(raw, configPath, issues) {
538
553
  }
539
554
  return Object.keys(result).length > 0 ? result : void 0;
540
555
  }
556
+ function normalizeUiuxMigration(raw, configPath, issues) {
557
+ if (!isRecord(raw)) {
558
+ issues.push(configIssue(configPath, "uiux.migration \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"));
559
+ return void 0;
560
+ }
561
+ const result = {};
562
+ if (raw.strict !== void 0) {
563
+ if (typeof raw.strict === "boolean") {
564
+ result.strict = raw.strict;
565
+ } else {
566
+ issues.push(
567
+ configIssue(configPath, "uiux.migration.strict \u306F\u30D6\u30FC\u30EB\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
568
+ );
569
+ }
570
+ }
571
+ return Object.keys(result).length > 0 ? result : void 0;
572
+ }
541
573
  function normalizeRenderEvidence(raw, configPath, issues) {
542
574
  if (!isRecord(raw)) {
543
575
  issues.push(
@@ -3595,18 +3627,125 @@ function resolveObligationsWithOptIn(mode, optIn) {
3595
3627
  return [.../* @__PURE__ */ new Set([...base, ...validOptIns])];
3596
3628
  }
3597
3629
 
3630
+ // src/core/prototyping/discussionReader.ts
3631
+ import { readFile as readFile10 } from "fs/promises";
3632
+ import path12 from "path";
3633
+ import { parse as parseYaml3 } from "yaml";
3634
+ var VALID_MODES = /* @__PURE__ */ new Set(["low-cost", "standard", "full-harness"]);
3635
+ var SIDECAR_FILENAME = "prototyping.yaml";
3636
+ async function readDiscussionRecommendation(discussionPackDir) {
3637
+ const filePath = path12.join(discussionPackDir, SIDECAR_FILENAME);
3638
+ let raw;
3639
+ try {
3640
+ raw = await readFile10(filePath, "utf-8");
3641
+ } catch {
3642
+ return { recommended_mode: null, rationale: null };
3643
+ }
3644
+ let doc;
3645
+ try {
3646
+ doc = parseYaml3(raw);
3647
+ } catch {
3648
+ return { recommended_mode: null, rationale: null };
3649
+ }
3650
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
3651
+ return { recommended_mode: null, rationale: null };
3652
+ }
3653
+ const record2 = doc;
3654
+ const prototyping = record2.prototyping;
3655
+ if (!prototyping || typeof prototyping !== "object" || Array.isArray(prototyping)) {
3656
+ return { recommended_mode: null, rationale: null };
3657
+ }
3658
+ const section = prototyping;
3659
+ const rawMode = typeof section.recommended_mode === "string" ? section.recommended_mode.trim() : null;
3660
+ const rationale = typeof section.rationale === "string" ? section.rationale.trim() : null;
3661
+ const recommended_mode = rawMode && VALID_MODES.has(rawMode) ? rawMode : null;
3662
+ return { recommended_mode, rationale };
3663
+ }
3664
+
3665
+ // src/core/prototyping/precedenceResolver.ts
3666
+ var VALID_MODES2 = /* @__PURE__ */ new Set([
3667
+ "default",
3668
+ "standard",
3669
+ "low-cost",
3670
+ "full-harness"
3671
+ ]);
3672
+ function isPrototypingMode(value) {
3673
+ return VALID_MODES2.has(value);
3674
+ }
3675
+ var SYSTEM_DEFAULT_MODE = "standard";
3676
+ function resolvePrecedence(input) {
3677
+ const { cliMode, discussion } = input;
3678
+ const rawRecommended = discussion?.recommended_mode ?? null;
3679
+ const recommended_mode = rawRecommended && isPrototypingMode(rawRecommended) ? rawRecommended : null;
3680
+ if (cliMode) {
3681
+ return {
3682
+ effective_mode: cliMode,
3683
+ mode_source: "cli-override",
3684
+ recommended_mode,
3685
+ rationale: "CLI override"
3686
+ };
3687
+ }
3688
+ if (recommended_mode) {
3689
+ return {
3690
+ effective_mode: recommended_mode,
3691
+ mode_source: "discussion-recommendation",
3692
+ recommended_mode,
3693
+ rationale: discussion?.rationale ?? "discussion recommendation"
3694
+ };
3695
+ }
3696
+ return {
3697
+ effective_mode: SYSTEM_DEFAULT_MODE,
3698
+ mode_source: "default",
3699
+ recommended_mode: null,
3700
+ rationale: "system default"
3701
+ };
3702
+ }
3703
+
3704
+ // src/core/prototyping/modeLogger.ts
3705
+ var VALID_MODE_SOURCES = /* @__PURE__ */ new Set([
3706
+ "cli-override",
3707
+ "discussion-recommendation",
3708
+ "default"
3709
+ ]);
3710
+ var EVIDENCE_EXPECTATIONS = {
3711
+ "low-cost": "L1/L2",
3712
+ standard: "L2/L3",
3713
+ "full-harness": "L3/L4/L5"
3714
+ };
3715
+ function formatModeLog(resolution) {
3716
+ return {
3717
+ mode_source: resolution.mode_source,
3718
+ recommended_mode: resolution.recommended_mode,
3719
+ effective_mode: resolution.effective_mode,
3720
+ rationale: resolution.rationale,
3721
+ evidence_expectations: EVIDENCE_EXPECTATIONS[resolution.effective_mode] ?? "L2/L3"
3722
+ };
3723
+ }
3724
+
3725
+ // src/core/prototyping/surfaceAdapter.ts
3726
+ var VISUAL_SURFACES = /* @__PURE__ */ new Set(["web", "mobile", "desktop"]);
3727
+ function adaptSurfaceEvidence(input) {
3728
+ const { surfaceType, effectiveMode } = input;
3729
+ const isVisual = VISUAL_SURFACES.has(surfaceType);
3730
+ const isLowCost = effectiveMode === "low-cost";
3731
+ const static_evidence = "collected";
3732
+ const runtime_evidence = isLowCost ? "n/a" : "collected";
3733
+ const visual_review = isVisual && !isLowCost ? "collected" : "n/a";
3734
+ return { visual_review, static_evidence, runtime_evidence };
3735
+ }
3736
+
3598
3737
  // src/core/report.ts
3599
- import { readFile as readFile42 } from "fs/promises";
3600
- import path54 from "path";
3738
+ import { readFile as readFile43 } from "fs/promises";
3739
+ import path57 from "path";
3601
3740
 
3602
3741
  // src/core/contractIndex.ts
3603
- import { readFile as readFile10 } from "fs/promises";
3604
- import path12 from "path";
3742
+ import { readFile as readFile11 } from "fs/promises";
3743
+ import path13 from "path";
3605
3744
  async function buildContractIndex(root, config) {
3606
3745
  const contractsRoot = resolvePath(root, config, "contractsDir");
3607
- const uiRoot = path12.join(contractsRoot, "ui");
3608
- const apiRoot = path12.join(contractsRoot, "api");
3609
- const dbRoot = path12.join(contractsRoot, "db");
3746
+ const uiRoot = path13.join(contractsRoot, "ui");
3747
+ const apiRoot = path13.join(contractsRoot, "api");
3748
+ const dbRoot = path13.join(contractsRoot, "db");
3610
3749
  const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
3611
3750
  collectUiContractFiles(uiRoot),
3612
3751
  collectThemaContractFiles(),
@@ -3626,7 +3765,7 @@ async function buildContractIndex(root, config) {
3626
3765
  }
3627
3766
  async function indexContractFiles(files, index) {
3628
3767
  for (const file of files) {
3629
- const text = await readFile10(file, "utf-8");
3768
+ const text = await readFile11(file, "utf-8");
3630
3769
  extractDeclaredContractIds(text).forEach((id) => {
3631
3770
  record(index, id, file);
3632
3771
  });
@@ -3896,15 +4035,15 @@ function resolveParentTcId(tcRef) {
3896
4035
  }
3897
4036
 
3898
4037
  // src/core/paths.ts
3899
- import path13 from "path";
4038
+ import path14 from "path";
3900
4039
  function toRelativePath(root, target) {
3901
4040
  if (!target) {
3902
4041
  return target;
3903
4042
  }
3904
- if (!path13.isAbsolute(target)) {
4043
+ if (!path14.isAbsolute(target)) {
3905
4044
  return toPosixPath2(target);
3906
4045
  }
3907
- const relative = path13.relative(root, target);
4046
+ const relative = path14.relative(root, target);
3908
4047
  if (!relative) {
3909
4048
  return ".";
3910
4049
  }
@@ -4075,7 +4214,7 @@ function parseSpec(md, file) {
4075
4214
  }
4076
4215
 
4077
4216
  // src/core/deltaV1.ts
4078
- import { parse as parseYaml3 } from "yaml";
4217
+ import { parse as parseYaml4 } from "yaml";
4079
4218
  var REQUIRED_DELTA_META_KEYS = [
4080
4219
  "id",
4081
4220
  "date",
@@ -4230,7 +4369,7 @@ function parseYamlMeta(block) {
4230
4369
  return { value: null, error: null };
4231
4370
  }
4232
4371
  try {
4233
- const parsed = parseYaml3(sanitizeMetaYaml(block));
4372
+ const parsed = parseYaml4(sanitizeMetaYaml(block));
4234
4373
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4235
4374
  return {
4236
4375
  value: null,
@@ -4264,7 +4403,7 @@ function parseVerificationPlan(body) {
4264
4403
  return { planHeadingLine: planHeading.line, parseError: null, items: [] };
4265
4404
  }
4266
4405
  try {
4267
- const parsed = parseYaml3(yamlSource);
4406
+ const parsed = parseYaml4(yamlSource);
4268
4407
  if (!Array.isArray(parsed)) {
4269
4408
  return {
4270
4409
  planHeadingLine: planHeading.line,
@@ -4351,16 +4490,16 @@ function asTagArray(value) {
4351
4490
  }
4352
4491
 
4353
4492
  // src/core/version.ts
4354
- import { readFile as readFile11 } from "fs/promises";
4355
- import path14 from "path";
4493
+ import { readFile as readFile12 } from "fs/promises";
4494
+ import path15 from "path";
4356
4495
  import { fileURLToPath } from "url";
4357
4496
  async function resolveToolVersion() {
4358
- if ("1.7.6".length > 0) {
4359
- return "1.7.6";
4497
+ if ("1.7.7".length > 0) {
4498
+ return "1.7.7";
4360
4499
  }
4361
4500
  try {
4362
4501
  const packagePath = resolvePackageJsonPath();
4363
- const raw = await readFile11(packagePath, "utf-8");
4502
+ const raw = await readFile12(packagePath, "utf-8");
4364
4503
  const parsed = JSON.parse(raw);
4365
4504
  const version = typeof parsed.version === "string" ? parsed.version : "";
4366
4505
  return version.length > 0 ? version : "unknown";
@@ -4371,13 +4510,13 @@ async function resolveToolVersion() {
4371
4510
  function resolvePackageJsonPath() {
4372
4511
  const base = import.meta.url;
4373
4512
  const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
4374
- return path14.resolve(path14.dirname(basePath), "../../package.json");
4513
+ return path15.resolve(path15.dirname(basePath), "../../package.json");
4375
4514
  }
4376
4515
 
4377
4516
  // src/core/waivers.ts
4378
- import { access as access6, readFile as readFile12 } from "fs/promises";
4379
- import path15 from "path";
4380
- import { parse as parseYaml4 } from "yaml";
4517
+ import { access as access6, readFile as readFile13 } from "fs/promises";
4518
+ import path16 from "path";
4519
+ import { parse as parseYaml5 } from "yaml";
4381
4520
 
4382
4521
  // src/core/regex.ts
4383
4522
  function escapeRegExp(value) {
@@ -4385,12 +4524,12 @@ function escapeRegExp(value) {
4385
4524
  }
4386
4525
 
4387
4526
  // src/core/waivers.ts
4388
- var WAIVER_FILE = path15.join(".qfai", "waivers.yml");
4389
- var UNSUPPORTED_WAIVER_FILE = path15.join(".qfai", "waivers.yaml");
4527
+ var WAIVER_FILE = path16.join(".qfai", "waivers.yml");
4528
+ var UNSUPPORTED_WAIVER_FILE = path16.join(".qfai", "waivers.yaml");
4390
4529
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
4391
4530
  var RULE_ID_RE = /^[A-Z]+-\d{3}$/;
4392
4531
  async function applyWaivers(root, findings) {
4393
- const resolvedRoot = path15.resolve(root);
4532
+ const resolvedRoot = path16.resolve(root);
4394
4533
  const ruleSeverityIndex = buildRuleSeverityIndex(findings);
4395
4534
  const loaded = await loadWaivers(resolvedRoot, ruleSeverityIndex);
4396
4535
  const applied = applyWaiversToFindings(resolvedRoot, findings, loaded.applicableWaivers);
@@ -4403,8 +4542,8 @@ async function applyWaivers(root, findings) {
4403
4542
  };
4404
4543
  }
4405
4544
  async function loadWaivers(root, ruleSeverityIndex) {
4406
- const waiverPath = path15.join(root, WAIVER_FILE);
4407
- const unsupportedPath = path15.join(root, UNSUPPORTED_WAIVER_FILE);
4545
+ const waiverPath = path16.join(root, WAIVER_FILE);
4546
+ const unsupportedPath = path16.join(root, UNSUPPORTED_WAIVER_FILE);
4408
4547
  const validationIssues = [];
4409
4548
  if (await exists6(unsupportedPath)) {
4410
4549
  validationIssues.push(
@@ -4428,7 +4567,7 @@ async function loadWaivers(root, ruleSeverityIndex) {
4428
4567
  }
4429
4568
  let rawText;
4430
4569
  try {
4431
- rawText = await readFile12(waiverPath, "utf-8");
4570
+ rawText = await readFile13(waiverPath, "utf-8");
4432
4571
  } catch (error) {
4433
4572
  validationIssues.push(
4434
4573
  issue(
@@ -4449,7 +4588,7 @@ async function loadWaivers(root, ruleSeverityIndex) {
4449
4588
  }
4450
4589
  let parsed;
4451
4590
  try {
4452
- parsed = parseYaml4(rawText);
4591
+ parsed = parseYaml5(rawText);
4453
4592
  } catch (error) {
4454
4593
  validationIssues.push(
4455
4594
  issue(
@@ -5121,8 +5260,8 @@ var STATIC_RULE_SEVERITY = {
5121
5260
  };
5122
5261
 
5123
5262
  // src/core/validators/contracts.ts
5124
- import { readFile as readFile13 } from "fs/promises";
5125
- import path16 from "path";
5263
+ import { readFile as readFile14 } from "fs/promises";
5264
+ import path17 from "path";
5126
5265
  var SQL_DANGEROUS_PATTERNS = [
5127
5266
  { pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
5128
5267
  { pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
@@ -5135,9 +5274,9 @@ var SQL_DANGEROUS_PATTERNS = [
5135
5274
  async function validateContracts(root, config) {
5136
5275
  const issues = [];
5137
5276
  const contractsRoot = resolvePath(root, config, "contractsDir");
5138
- const uiRoot = path16.join(contractsRoot, "ui");
5139
- const apiRoot = path16.join(contractsRoot, "api");
5140
- const dbRoot = path16.join(contractsRoot, "db");
5277
+ const uiRoot = path17.join(contractsRoot, "ui");
5278
+ const apiRoot = path17.join(contractsRoot, "api");
5279
+ const dbRoot = path17.join(contractsRoot, "db");
5141
5280
  const [uiFiles, apiFiles, dbFiles] = await Promise.all([
5142
5281
  collectUiContractFiles(uiRoot),
5143
5282
  collectApiContractFiles(apiRoot),
@@ -5191,7 +5330,7 @@ async function validateContracts(root, config) {
5191
5330
  }
5192
5331
  async function validateContractFile(file, kind) {
5193
5332
  const issues = [];
5194
- const text = await readFile13(file, "utf-8");
5333
+ const text = await readFile14(file, "utf-8");
5195
5334
  const declaredIds = extractDeclaredContractIds(text);
5196
5335
  issues.push(...validateDeclaredContractIds(declaredIds, file, kind));
5197
5336
  if (kind === "DB") {
@@ -5312,23 +5451,23 @@ function formatError5(error) {
5312
5451
  }
5313
5452
 
5314
5453
  // src/core/validators/discussMermaid.ts
5315
- import { readFile as readFile14 } from "fs/promises";
5316
- import path17 from "path";
5454
+ import { readFile as readFile15 } from "fs/promises";
5455
+ import path18 from "path";
5317
5456
  var DISCUSSION_PACK_FLOW_FILE = "03_Story-Workshop.md";
5318
5457
  var MERMAID_START_RE = /^\s*(`{3,}|~{3,})\s*mermaid\b/i;
5319
5458
  var FLOW_OR_SEQUENCE_RE = /\b(?:sequenceDiagram|flowchart)\b/;
5320
5459
  async function validateDiscussionMermaid(root) {
5321
- const discussionRootDir = path17.join(root, ".qfai", "discussion");
5460
+ const discussionRootDir = path18.join(root, ".qfai", "discussion");
5322
5461
  const discussionPackMarkdownFiles = await collectFiles(discussionRootDir, {
5323
5462
  extensions: [".md"]
5324
5463
  });
5325
5464
  const discussionPackFiles = [];
5326
5465
  for (const file of discussionPackMarkdownFiles) {
5327
- const fileName = path17.basename(file);
5466
+ const fileName = path18.basename(file);
5328
5467
  if (fileName !== DISCUSSION_PACK_FLOW_FILE) {
5329
5468
  continue;
5330
5469
  }
5331
- const discussionDirName = path17.basename(path17.dirname(file));
5470
+ const discussionDirName = path18.basename(path18.dirname(file));
5332
5471
  const nameValidation = validatePackName("discussion", discussionDirName);
5333
5472
  if (nameValidation.isCanonical) {
5334
5473
  discussionPackFiles.push({ file, kind: "current" });
@@ -5358,7 +5497,7 @@ async function validateDiscussionMermaid(root) {
5358
5497
  );
5359
5498
  }
5360
5499
  for (const { file } of discussionPackFiles) {
5361
- const text = await readFile14(file, "utf-8");
5500
+ const text = await readFile15(file, "utf-8");
5362
5501
  if (containsMermaidFlowDiagram(text)) {
5363
5502
  continue;
5364
5503
  }
@@ -5408,17 +5547,17 @@ function containsMermaidFlowDiagram(text) {
5408
5547
  }
5409
5548
 
5410
5549
  // src/core/validators/assistantAssets.ts
5411
- import { access as access7, readFile as readFile15 } from "fs/promises";
5412
- import path18 from "path";
5550
+ import { access as access7, readFile as readFile16 } from "fs/promises";
5551
+ import path19 from "path";
5413
5552
  var DRIFT_PROTOCOL_MARKER = "[DRIFT-PROTOCOL:MANDATORY]";
5414
5553
  var REVIEWER_GATE_HEADING_PATTERN = /^###\s+Reviewer Gate\b.*$/im;
5415
5554
  var ANY_MARKDOWN_HEADING_PATTERN = /^\s*#{1,6}\s+/m;
5416
5555
  async function validateAssistantAssets(root, config) {
5417
5556
  const skillsDir = resolvePath(root, config, "skillsDir");
5418
- const assistantDir = path18.dirname(skillsDir);
5419
- const skillsLocalDir = path18.join(path18.dirname(skillsDir), `${path18.basename(skillsDir)}.local`);
5420
- const driftProtocolPath = path18.join(assistantDir, "instructions", "drift-protocol.md");
5421
- const testLayersPath = path18.join(assistantDir, "steering", "test-layers.md");
5557
+ const assistantDir = path19.dirname(skillsDir);
5558
+ const skillsLocalDir = path19.join(path19.dirname(skillsDir), `${path19.basename(skillsDir)}.local`);
5559
+ const driftProtocolPath = path19.join(assistantDir, "instructions", "drift-protocol.md");
5560
+ const testLayersPath = path19.join(assistantDir, "steering", "test-layers.md");
5422
5561
  const issues = [];
5423
5562
  if (!await exists7(driftProtocolPath)) {
5424
5563
  issues.push(
@@ -5444,7 +5583,7 @@ async function validateAssistantAssets(root, config) {
5444
5583
  }
5445
5584
  const skillFiles = await collectSkillFiles([skillsDir, skillsLocalDir]);
5446
5585
  for (const skillFile of skillFiles) {
5447
- const content = await readFile15(skillFile, "utf-8");
5586
+ const content = await readFile16(skillFile, "utf-8");
5448
5587
  if (!content.includes(DRIFT_PROTOCOL_MARKER)) {
5449
5588
  issues.push(
5450
5589
  issue(
@@ -5486,7 +5625,7 @@ async function validateAssistantAssets(root, config) {
5486
5625
  }
5487
5626
  async function collectSkillFiles(dirs) {
5488
5627
  const files = await Promise.all(dirs.map((dir) => collectFiles(dir)));
5489
- return files.flat().filter((filePath) => path18.basename(filePath) === "SKILL.md").sort((a, b) => a.localeCompare(b));
5628
+ return files.flat().filter((filePath) => path19.basename(filePath) === "SKILL.md").sort((a, b) => a.localeCompare(b));
5490
5629
  }
5491
5630
  function extractReviewerGateSection(content) {
5492
5631
  const headingMatch = REVIEWER_GATE_HEADING_PATTERN.exec(content);
@@ -5527,20 +5666,20 @@ async function exists7(target) {
5527
5666
  }
5528
5667
 
5529
5668
  // src/core/skillsIntegrity.ts
5530
- import { readFile as readFile16 } from "fs/promises";
5531
- import path20 from "path";
5669
+ import { readFile as readFile17 } from "fs/promises";
5670
+ import path21 from "path";
5532
5671
 
5533
5672
  // src/shared/assets.ts
5534
5673
  import { existsSync } from "fs";
5535
- import path19 from "path";
5674
+ import path20 from "path";
5536
5675
  import { fileURLToPath as fileURLToPath2 } from "url";
5537
5676
  function getInitAssetsDir() {
5538
5677
  const base = import.meta.url;
5539
5678
  const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
5540
- const baseDir = path19.dirname(basePath);
5679
+ const baseDir = path20.dirname(basePath);
5541
5680
  const candidates = [
5542
- path19.resolve(baseDir, "../../../assets/init"),
5543
- path19.resolve(baseDir, "../../assets/init")
5681
+ path20.resolve(baseDir, "../../../assets/init"),
5682
+ path20.resolve(baseDir, "../../assets/init")
5544
5683
  ];
5545
5684
  for (const candidate of candidates) {
5546
5685
  if (existsSync(candidate)) {
@@ -5559,10 +5698,10 @@ function getInitAssetsDir() {
5559
5698
  // src/core/skillsIntegrity.ts
5560
5699
  async function diffProjectSkillsAgainstInitAssets(root, config) {
5561
5700
  const skillsDirConfig = config.paths.skillsDir;
5562
- const skillsDir = path20.isAbsolute(skillsDirConfig) ? skillsDirConfig : path20.resolve(root, skillsDirConfig);
5701
+ const skillsDir = path21.isAbsolute(skillsDirConfig) ? skillsDirConfig : path21.resolve(root, skillsDirConfig);
5563
5702
  let templateDir;
5564
5703
  try {
5565
- const rel = path20.isAbsolute(skillsDirConfig) ? path20.relative(root, skillsDirConfig) : skillsDirConfig;
5704
+ const rel = path21.isAbsolute(skillsDirConfig) ? path21.relative(root, skillsDirConfig) : skillsDirConfig;
5566
5705
  const normalized = rel.replace(/^[\\/]+/, "");
5567
5706
  if (normalized.length === 0 || normalized.startsWith("..")) {
5568
5707
  return {
@@ -5574,7 +5713,7 @@ async function diffProjectSkillsAgainstInitAssets(root, config) {
5574
5713
  changed: []
5575
5714
  };
5576
5715
  }
5577
- templateDir = path20.join(getInitAssetsDir(), normalized);
5716
+ templateDir = path21.join(getInitAssetsDir(), normalized);
5578
5717
  } catch {
5579
5718
  return {
5580
5719
  status: "skipped_missing_assets",
@@ -5627,8 +5766,8 @@ async function diffProjectSkillsAgainstInitAssets(root, config) {
5627
5766
  }
5628
5767
  try {
5629
5768
  const [a, b] = await Promise.all([
5630
- readFile16(templateAbs, "utf-8"),
5631
- readFile16(projectAbs, "utf-8")
5769
+ readFile17(templateAbs, "utf-8"),
5770
+ readFile17(projectAbs, "utf-8")
5632
5771
  ]);
5633
5772
  if (normalizeNewlines(a) !== normalizeNewlines(b)) {
5634
5773
  changed.push(rel);
@@ -5651,7 +5790,7 @@ function normalizeNewlines(text) {
5651
5790
  return text.replace(/\r\n/g, "\n");
5652
5791
  }
5653
5792
  function toRel(base, abs) {
5654
- const rel = path20.relative(base, abs);
5793
+ const rel = path21.relative(base, abs);
5655
5794
  return rel.replace(/[\\/]+/g, "/");
5656
5795
  }
5657
5796
  function intersectKeys(a, b) {
@@ -5696,8 +5835,8 @@ async function validateSkillsIntegrity(root, config) {
5696
5835
  }
5697
5836
 
5698
5837
  // src/core/validators/ids.ts
5699
- import { readFile as readFile17 } from "fs/promises";
5700
- import path21 from "path";
5838
+ import { readFile as readFile18 } from "fs/promises";
5839
+ import path22 from "path";
5701
5840
  var SC_TAG_RE2 = /^SC-\d{4}-\d{4}$/;
5702
5841
  var AC_ID_RE2 = /\bAC-[A-Za-z0-9_-]+\b/g;
5703
5842
  var CASE_ID_RE = /\b(?:CASE|TC)-[A-Za-z0-9_-]+\b/g;
@@ -5813,7 +5952,7 @@ async function collectSpecDefinitionIds(files, out) {
5813
5952
  }
5814
5953
  async function readSafe5(file) {
5815
5954
  try {
5816
- return await readFile17(file, "utf-8");
5955
+ return await readFile18(file, "utf-8");
5817
5956
  } catch {
5818
5957
  return "";
5819
5958
  }
@@ -5844,23 +5983,23 @@ function recordId(out, id, file) {
5844
5983
  }
5845
5984
  function formatFileList(files, root) {
5846
5985
  return files.map((file) => {
5847
- const relative = path21.relative(root, file);
5986
+ const relative = path22.relative(root, file);
5848
5987
  return relative.length > 0 ? relative : file;
5849
5988
  }).join(", ");
5850
5989
  }
5851
5990
 
5852
5991
  // src/core/validators/reviewArtifacts.ts
5853
- import { readFile as readFile18, readdir as readdir4, stat as stat3 } from "fs/promises";
5854
- import path22 from "path";
5992
+ import { readFile as readFile19, readdir as readdir4, stat as stat3 } from "fs/promises";
5993
+ import path23 from "path";
5855
5994
  var REVIEW_PACK_DIR_RE = /^review-(\d{17})$/i;
5856
5995
  var REVIEWER_FILE_RE = /^R\d+_.+\.md$/i;
5857
5996
  var ALLOWED_TARGET_KINDS = /* @__PURE__ */ new Set(["spec", "discussion"]);
5858
5997
  var ALLOWED_ROSTER_STATUS = /* @__PURE__ */ new Set(["PASS", "FAIL", "NA"]);
5859
5998
  var ALLOWED_OVERALL_STATUS = /* @__PURE__ */ new Set(["PASS", "FAIL"]);
5860
5999
  async function validateReviewArtifacts(root) {
5861
- const reviewRoot = path22.join(root, ".qfai", "review");
6000
+ const reviewRoot = path23.join(root, ".qfai", "review");
5862
6001
  const issues = [];
5863
- const gitignorePath = path22.join(reviewRoot, ".gitignore");
6002
+ const gitignorePath = path23.join(reviewRoot, ".gitignore");
5864
6003
  if (!await isFile(gitignorePath)) {
5865
6004
  issues.push(
5866
6005
  issue(
@@ -5898,8 +6037,8 @@ async function validateReviewArtifacts(root) {
5898
6037
  }
5899
6038
  async function validateReviewPack(reviewPackDir) {
5900
6039
  const issues = [];
5901
- const reviewRequestPath = path22.join(reviewPackDir, "review_request.md");
5902
- const summaryPath = path22.join(reviewPackDir, "summary.json");
6040
+ const reviewRequestPath = path23.join(reviewPackDir, "review_request.md");
6041
+ const summaryPath = path23.join(reviewPackDir, "summary.json");
5903
6042
  if (!await isFile(reviewRequestPath)) {
5904
6043
  issues.push(
5905
6044
  issue(
@@ -5942,7 +6081,7 @@ async function validateReviewPack(reviewPackDir) {
5942
6081
  async function validateSummarySchema(summaryPath) {
5943
6082
  let parsed;
5944
6083
  try {
5945
- parsed = JSON.parse(await readFile18(summaryPath, "utf-8"));
6084
+ parsed = JSON.parse(await readFile19(summaryPath, "utf-8"));
5946
6085
  } catch (error) {
5947
6086
  return [
5948
6087
  issue(
@@ -6031,7 +6170,7 @@ async function listReviewPackDirs(reviewRoot) {
6031
6170
  } catch {
6032
6171
  return [];
6033
6172
  }
6034
- return entries.map((name) => path22.join(reviewRoot, name));
6173
+ return entries.map((name) => path23.join(reviewRoot, name));
6035
6174
  }
6036
6175
  async function listReviewerFiles(reviewPackDir) {
6037
6176
  try {
@@ -6079,8 +6218,8 @@ function formatError6(error) {
6079
6218
  }
6080
6219
 
6081
6220
  // src/core/validators/specPack.ts
6082
- import { readFile as readFile19 } from "fs/promises";
6083
- import path23 from "path";
6221
+ import { readFile as readFile20 } from "fs/promises";
6222
+ import path24 from "path";
6084
6223
  var LEDGER_REQUIRED_COLUMNS = [
6085
6224
  "trace_id",
6086
6225
  "obj_id",
@@ -6498,7 +6637,7 @@ function validateLayeredNamespace(entry, filePath, ids, prefix) {
6498
6637
  "specPack.layered.namespace",
6499
6638
  mismatched,
6500
6639
  "compatibility",
6501
- `${path23.basename(filePath)} \u306E ${prefix} ID \u3092 spec-${entry.specNumber} \u306B\u5408\u308F\u305B\u3066\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
6640
+ `${path24.basename(filePath)} \u306E ${prefix} ID \u3092 spec-${entry.specNumber} \u306B\u5408\u308F\u305B\u3066\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
6502
6641
  )
6503
6642
  ];
6504
6643
  }
@@ -6506,7 +6645,7 @@ async function collectExistingLayeredDeltaFiles(entry) {
6506
6645
  const candidates = Array.from(new Set(entry.deltaCandidates));
6507
6646
  const existing = [];
6508
6647
  for (const candidate of candidates) {
6509
- if (!/(?:^|_)delta\.md$/i.test(path23.basename(candidate))) {
6648
+ if (!/(?:^|_)delta\.md$/i.test(path24.basename(candidate))) {
6510
6649
  continue;
6511
6650
  }
6512
6651
  if (await fileExists(candidate)) {
@@ -6517,7 +6656,7 @@ async function collectExistingLayeredDeltaFiles(entry) {
6517
6656
  }
6518
6657
  async function fileExists(target) {
6519
6658
  try {
6520
- await readFile19(target, "utf-8");
6659
+ await readFile20(target, "utf-8");
6521
6660
  return true;
6522
6661
  } catch {
6523
6662
  return false;
@@ -6678,7 +6817,7 @@ async function validateTraceabilityLedger(entry, contractIds) {
6678
6817
  const issues = [];
6679
6818
  let ledgerText;
6680
6819
  try {
6681
- ledgerText = await readFile19(entry.traceabilityLedgerPath, "utf-8");
6820
+ ledgerText = await readFile20(entry.traceabilityLedgerPath, "utf-8");
6682
6821
  } catch {
6683
6822
  return issues;
6684
6823
  }
@@ -7076,10 +7215,10 @@ async function loadExistingRequiredTexts(entry, missingFiles) {
7076
7215
  }
7077
7216
  async function loadLayerPolicy(root, config) {
7078
7217
  const skillsDir = resolvePath(root, config, "skillsDir");
7079
- const assistantRoot = path23.dirname(skillsDir);
7080
- const policyPath = path23.join(assistantRoot, "steering", "test-layers.md");
7218
+ const assistantRoot = path24.dirname(skillsDir);
7219
+ const policyPath = path24.join(assistantRoot, "steering", "test-layers.md");
7081
7220
  try {
7082
- const policyText = await readFile19(policyPath, "utf-8");
7221
+ const policyText = await readFile20(policyPath, "utf-8");
7083
7222
  return {
7084
7223
  tags: resolveAllowedLayerTagsFromPolicy(policyText),
7085
7224
  issues: []
@@ -7187,15 +7326,15 @@ function extractMarkdownSection(text, heading) {
7187
7326
  }
7188
7327
  async function readSafe6(filePath) {
7189
7328
  try {
7190
- return await readFile19(filePath, "utf-8");
7329
+ return await readFile20(filePath, "utf-8");
7191
7330
  } catch {
7192
7331
  return "";
7193
7332
  }
7194
7333
  }
7195
7334
 
7196
7335
  // src/core/validators/traceability.ts
7197
- import { access as access8, readFile as readFile20 } from "fs/promises";
7198
- import path24 from "path";
7336
+ import { access as access8, readFile as readFile21 } from "fs/promises";
7337
+ import path25 from "path";
7199
7338
  var SC_TAG_RE3 = /^SC-\d{4}-\d{4}$/;
7200
7339
  var SC_TAG_RE_GLOBAL = /\bSC-\d{4}-\d{4}\b/g;
7201
7340
  var SPEC_TAG_RE = /^SPEC-\d{4}$/;
@@ -7212,8 +7351,8 @@ async function validateTraceability(root, config, phase) {
7212
7351
  if (layeredEntries.length === 0) {
7213
7352
  return issues;
7214
7353
  }
7215
- const policiesDir = layeredEntries[0]?.sharedDir ?? path24.join(specsRoot, "_policies");
7216
- const capabilitiesPath = path24.join(policiesDir, "03_Capabilities.md");
7354
+ const policiesDir = layeredEntries[0]?.sharedDir ?? path25.join(specsRoot, "_policies");
7355
+ const capabilitiesPath = path25.join(policiesDir, "03_Capabilities.md");
7217
7356
  const capabilitiesExists = await exists8(capabilitiesPath);
7218
7357
  const capabilitiesText = await readSafe7(capabilitiesPath);
7219
7358
  const capIds = new Set(extractIds(capabilitiesText, "CAP"));
@@ -7294,7 +7433,7 @@ async function validatePoliciesLayerDownRef(policiesDir) {
7294
7433
  "04_Business-flow.md"
7295
7434
  ];
7296
7435
  for (const fileName of targets) {
7297
- const targetPath = path24.join(policiesDir, fileName);
7436
+ const targetPath = path25.join(policiesDir, fileName);
7298
7437
  const text = await readSafe7(targetPath);
7299
7438
  if (text.length === 0) {
7300
7439
  continue;
@@ -7753,7 +7892,7 @@ function cloneGlobal3(re) {
7753
7892
  }
7754
7893
  async function readSafe7(target) {
7755
7894
  try {
7756
- return await readFile20(target, "utf-8");
7895
+ return await readFile21(target, "utf-8");
7757
7896
  } catch {
7758
7897
  return "";
7759
7898
  }
@@ -7769,7 +7908,7 @@ async function exists8(target) {
7769
7908
 
7770
7909
  // src/core/validators/atddCodeTraceability.ts
7771
7910
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
7772
- import path25 from "path";
7911
+ import path26 from "path";
7773
7912
  async function validateAtddCodeTraceability(root, config) {
7774
7913
  const result = await evaluateAtddCodeTraceability(root, config);
7775
7914
  const issues = [];
@@ -7852,7 +7991,7 @@ async function validateAtddCodeTraceability(root, config) {
7852
7991
  "QFAI-ATDD-901",
7853
7992
  `atdd-traceability report \u306E\u51FA\u529B\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError7(error)}`,
7854
7993
  "warning",
7855
- path25.join(resolvePath(root, config, "outDir"), "atdd-traceability"),
7994
+ path26.join(resolvePath(root, config, "outDir"), "atdd-traceability"),
7856
7995
  "atddCodeTraceability.report"
7857
7996
  )
7858
7997
  );
@@ -7918,7 +8057,7 @@ function buildUnknownIssues(unknown) {
7918
8057
  });
7919
8058
  }
7920
8059
  async function writeAtddTraceabilityReport(root, config, result) {
7921
- const outputDir = path25.join(resolvePath(root, config, "outDir"), "atdd-traceability");
8060
+ const outputDir = path26.join(resolvePath(root, config, "outDir"), "atdd-traceability");
7922
8061
  await mkdir2(outputDir, { recursive: true });
7923
8062
  const summary = {
7924
8063
  missing: {
@@ -7942,12 +8081,12 @@ async function writeAtddTraceabilityReport(root, config, result) {
7942
8081
  }
7943
8082
  };
7944
8083
  await writeFile2(
7945
- path25.join(outputDir, "summary.json"),
8084
+ path26.join(outputDir, "summary.json"),
7946
8085
  `${JSON.stringify(summary, null, 2)}
7947
8086
  `,
7948
8087
  "utf-8"
7949
8088
  );
7950
- await writeFile2(path25.join(outputDir, "summary.md"), buildSummaryMarkdown(summary), "utf-8");
8089
+ await writeFile2(path26.join(outputDir, "summary.md"), buildSummaryMarkdown(summary), "utf-8");
7951
8090
  }
7952
8091
  function buildSummaryMarkdown(summary) {
7953
8092
  const lines = [];
@@ -8012,8 +8151,8 @@ function formatError7(error) {
8012
8151
  }
8013
8152
 
8014
8153
  // src/core/validators/discussionPack.ts
8015
- import { readFile as readFile21, stat as stat4 } from "fs/promises";
8016
- import path26 from "path";
8154
+ import { readFile as readFile22, stat as stat4 } from "fs/promises";
8155
+ import path27 from "path";
8017
8156
  async function validateDiscussionPackReadiness(root, config) {
8018
8157
  const discussionRoot = resolvePath(root, config, "discussionDir");
8019
8158
  const readiness = await inspectLatestDiscussionPack(discussionRoot);
@@ -8105,7 +8244,7 @@ async function validateDiscussionPackReadiness(root, config) {
8105
8244
  );
8106
8245
  }
8107
8246
  if (readiness.blockingOqIds.length > 0) {
8108
- const oqPath = path26.join(readiness.latestPackDir, "11_OQ-Register.md");
8247
+ const oqPath = path27.join(readiness.latestPackDir, "11_OQ-Register.md");
8109
8248
  issues.push(
8110
8249
  issue(
8111
8250
  "QFAI-DPACK-004",
@@ -8120,7 +8259,7 @@ async function validateDiscussionPackReadiness(root, config) {
8120
8259
  );
8121
8260
  }
8122
8261
  if (readiness.deferredWithoutDetails.length > 0) {
8123
- const deferredPath = path26.join(readiness.latestPackDir, "13_Deferred.md");
8262
+ const deferredPath = path27.join(readiness.latestPackDir, "13_Deferred.md");
8124
8263
  issues.push(
8125
8264
  issue(
8126
8265
  "QFAI-DPACK-007",
@@ -8134,7 +8273,7 @@ async function validateDiscussionPackReadiness(root, config) {
8134
8273
  )
8135
8274
  );
8136
8275
  }
8137
- const storyWorkshopPath = path26.join(readiness.latestPackDir, "03_Story-Workshop.md");
8276
+ const storyWorkshopPath = path27.join(readiness.latestPackDir, "03_Story-Workshop.md");
8138
8277
  const storyWorkshopText = await readSafe8(storyWorkshopPath);
8139
8278
  if (storyWorkshopText !== null && !containsMermaidBlock(storyWorkshopText)) {
8140
8279
  issues.push(
@@ -8161,28 +8300,28 @@ async function readSafe8(filePath) {
8161
8300
  if (!stats.isFile()) {
8162
8301
  return null;
8163
8302
  }
8164
- return await readFile21(filePath, "utf-8");
8303
+ return await readFile22(filePath, "utf-8");
8165
8304
  } catch {
8166
8305
  return null;
8167
8306
  }
8168
8307
  }
8169
8308
 
8170
8309
  // src/core/validators/discussionVisuals.ts
8171
- import { readFile as readFile22 } from "fs/promises";
8172
- import path27 from "path";
8310
+ import { readFile as readFile23 } from "fs/promises";
8311
+ import path28 from "path";
8173
8312
  var MERMAID_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*mermaid\b/im;
8174
8313
  var SCREEN_MOCK_HEADING_RE = /^\s*#{1,6}\s*screen mock(?:\s*[-\u2014]+\s*fallback)?\s*\(html\+css\)\s*$/im;
8175
8314
  var HTML_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*html\b/im;
8176
8315
  var CSS_FENCE_RE = /^\s*(?:`{3,}|~{3,})\s*css\b/im;
8177
8316
  var UI_HINT_RE = /\b(?:ui|ux|screen|screens|page|pages|layout|wireframe|mock|form|button|frontend|navigation)\b|画面|モック|遷移|フォーム|ボタン/i;
8178
8317
  async function validateDiscussionVisuals(root) {
8179
- const discussionRoot = path27.join(root, ".qfai", "discussion");
8318
+ const discussionRoot = path28.join(root, ".qfai", "discussion");
8180
8319
  const latestPackDir = await findLatestDiscussionPackDir(discussionRoot);
8181
8320
  if (!latestPackDir) {
8182
8321
  return [];
8183
8322
  }
8184
8323
  const issues = [];
8185
- const inceptionPath = path27.join(latestPackDir, "02_Inception-Deck.md");
8324
+ const inceptionPath = path28.join(latestPackDir, "02_Inception-Deck.md");
8186
8325
  const inceptionText = await readSafe9(inceptionPath);
8187
8326
  if (inceptionText !== null && !MERMAID_FENCE_RE.test(inceptionText)) {
8188
8327
  issues.push(
@@ -8198,7 +8337,7 @@ async function validateDiscussionVisuals(root) {
8198
8337
  )
8199
8338
  );
8200
8339
  }
8201
- const storyWorkshopPath = path27.join(latestPackDir, "03_Story-Workshop.md");
8340
+ const storyWorkshopPath = path28.join(latestPackDir, "03_Story-Workshop.md");
8202
8341
  const storyWorkshopText = await readSafe9(storyWorkshopPath);
8203
8342
  const storyWorkshopPlainText = storyWorkshopText === null ? null : stripFencedCodeBlocks(storyWorkshopText);
8204
8343
  if (storyWorkshopText === null || storyWorkshopPlainText === null) {
@@ -8262,14 +8401,14 @@ function escapeRegExp2(value) {
8262
8401
  }
8263
8402
  async function readSafe9(filePath) {
8264
8403
  try {
8265
- return await readFile22(filePath, "utf-8");
8404
+ return await readFile23(filePath, "utf-8");
8266
8405
  } catch {
8267
8406
  return null;
8268
8407
  }
8269
8408
  }
8270
8409
 
8271
8410
  // src/core/validators/densityHints.ts
8272
- import { readFile as readFile23 } from "fs/promises";
8411
+ import { readFile as readFile24 } from "fs/promises";
8273
8412
  var BR_ID_RE = /\bBR-[A-Za-z0-9-]+\b/g;
8274
8413
  var SCENARIO_RE = /^\s*Scenario(?:\s+Outline)?\s*:/gim;
8275
8414
  var TC_OR_CASE_RE = /\b(?:TC|CASE)-[A-Za-z0-9-]+\b/g;
@@ -8368,7 +8507,7 @@ function isSeparatorRow(line) {
8368
8507
  }
8369
8508
  async function readSafe10(filePath) {
8370
8509
  try {
8371
- return await readFile23(filePath, "utf-8");
8510
+ return await readFile24(filePath, "utf-8");
8372
8511
  } catch {
8373
8512
  return "";
8374
8513
  }
@@ -8376,11 +8515,11 @@ async function readSafe10(filePath) {
8376
8515
 
8377
8516
  // src/core/validators/importLite.ts
8378
8517
  import { readdir as readdir5 } from "fs/promises";
8379
- import path28 from "path";
8518
+ import path29 from "path";
8380
8519
 
8381
8520
  // src/core/validators/layerCoverage.ts
8382
8521
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
8383
- import path29 from "path";
8522
+ import path30 from "path";
8384
8523
  var ID_PATTERNS = {
8385
8524
  us: /^US-\d{4}(?:-\d{4})?$/,
8386
8525
  ac: /^AC-\d{4}(?:-\d{4})?$/,
@@ -8401,7 +8540,7 @@ async function validateLayerCoverage(root, config) {
8401
8540
  if (layeredEntries.length === 0) {
8402
8541
  return issues;
8403
8542
  }
8404
- const coverageRoot = path29.join(resolvePath(root, config, "outDir"), "specs-coverage");
8543
+ const coverageRoot = path30.join(resolvePath(root, config, "outDir"), "specs-coverage");
8405
8544
  for (const entry of layeredEntries) {
8406
8545
  if (isV1421LayeredEntry(entry)) {
8407
8546
  const coverageResult = await validateV1421Coverage(entry);
@@ -8436,7 +8575,7 @@ async function validateLayerCoverage(root, config) {
8436
8575
  }
8437
8576
  async function validatePlanArtifacts(specsRoot, entries) {
8438
8577
  const issues = [];
8439
- const deprecatedPlanPath = path29.join(specsRoot, "plan.md");
8578
+ const deprecatedPlanPath = path30.join(specsRoot, "plan.md");
8440
8579
  if (await exists5(deprecatedPlanPath)) {
8441
8580
  issues.push(
8442
8581
  issue(
@@ -8452,7 +8591,7 @@ async function validatePlanArtifacts(specsRoot, entries) {
8452
8591
  );
8453
8592
  }
8454
8593
  for (const entry of entries) {
8455
- const planFileName = path29.basename(entry.planPath).toLowerCase();
8594
+ const planFileName = path30.basename(entry.planPath).toLowerCase();
8456
8595
  if (planFileName !== "10_plan.md") {
8457
8596
  continue;
8458
8597
  }
@@ -8834,7 +8973,7 @@ function buildSignalRows(prefix, counts, target) {
8834
8973
  }
8835
8974
  async function writeCoverageReport(coverageRoot, specNumber, snapshot) {
8836
8975
  await mkdir3(coverageRoot, { recursive: true });
8837
- const reportPath = path29.join(coverageRoot, `spec-${specNumber}.md`);
8976
+ const reportPath = path30.join(coverageRoot, `spec-${specNumber}.md`);
8838
8977
  const lines = [];
8839
8978
  lines.push(`# Spec Coverage (spec-${specNumber})`);
8840
8979
  lines.push("");
@@ -8879,7 +9018,7 @@ function isV1421LayeredEntry(entry) {
8879
9018
  if (entry.layeredStyle === "v1421") {
8880
9019
  return true;
8881
9020
  }
8882
- return path29.extname(entry.examplesPath).toLowerCase() === ".md";
9021
+ return path30.extname(entry.examplesPath).toLowerCase() === ".md";
8883
9022
  }
8884
9023
  function extractHeadings(text) {
8885
9024
  const headings = [];
@@ -9071,7 +9210,7 @@ async function validateExToTcCoverage(examplesPath, testCasesPath) {
9071
9210
 
9072
9211
  // src/core/validators/layeredTraceability.ts
9073
9212
  import { readdir as readdir6 } from "fs/promises";
9074
- import path30 from "path";
9213
+ import path31 from "path";
9075
9214
  var POLICIES_FILES = [
9076
9215
  "01_Objective.md",
9077
9216
  "02_Initiative.md",
@@ -9120,7 +9259,7 @@ async function validateLayeredTraceability(root, config) {
9120
9259
  }
9121
9260
  const issues = [];
9122
9261
  if (layeredV1417Entries.length > 0) {
9123
- const policiesDir = layeredV1417Entries[0]?.sharedDir ?? path30.join(specsRoot, "_policies");
9262
+ const policiesDir = layeredV1417Entries[0]?.sharedDir ?? path31.join(specsRoot, "_policies");
9124
9263
  issues.push(...await validatePoliciesDownstreamReferences(policiesDir));
9125
9264
  for (const entry of layeredV1417Entries) {
9126
9265
  issues.push(...await validateSpecRootParent(entry));
@@ -9161,7 +9300,7 @@ async function validateLayeredTraceability(root, config) {
9161
9300
  }
9162
9301
  }
9163
9302
  if (layeredV1421Entries.length > 0) {
9164
- const policiesDir = layeredV1421Entries[0]?.sharedDir ?? path30.join(specsRoot, "_policies");
9303
+ const policiesDir = layeredV1421Entries[0]?.sharedDir ?? path31.join(specsRoot, "_policies");
9165
9304
  issues.push(...await validatePoliciesScopeForV1421(policiesDir));
9166
9305
  for (const entry of layeredV1421Entries) {
9167
9306
  issues.push(...await validateDownstreamRefsForV1421(entry));
@@ -9172,7 +9311,7 @@ async function validateLayeredTraceability(root, config) {
9172
9311
  async function validatePoliciesDownstreamReferences(policiesDir) {
9173
9312
  const issues = [];
9174
9313
  for (const fileName of POLICIES_FILES) {
9175
- const filePath = path30.join(policiesDir, fileName);
9314
+ const filePath = path31.join(policiesDir, fileName);
9176
9315
  const text = await readSafe(filePath);
9177
9316
  if (text.trim().length === 0) {
9178
9317
  continue;
@@ -9245,7 +9384,7 @@ async function validateDownstreamRefsForV1421(entry) {
9245
9384
  issues.push(
9246
9385
  issue(
9247
9386
  "TRACE_DOWNSTREAM_REF",
9248
- `${path30.basename(check.filePath)} \u3067\u4E0B\u4F4D\u30EC\u30A4\u30E4\u30FC\u53C2\u7167\u306F\u7981\u6B62\u3067\u3059: ${refs.join(", ")}`,
9387
+ `${path31.basename(check.filePath)} \u3067\u4E0B\u4F4D\u30EC\u30A4\u30E4\u30FC\u53C2\u7167\u306F\u7981\u6B62\u3067\u3059: ${refs.join(", ")}`,
9249
9388
  "error",
9250
9389
  check.filePath,
9251
9390
  "layeredTraceability.downstream",
@@ -9275,13 +9414,13 @@ function resolveLayerFromId(id) {
9275
9414
  async function collectMarkdownFiles(policiesDir) {
9276
9415
  try {
9277
9416
  const entries = await readdir6(policiesDir, { withFileTypes: true });
9278
- return entries.filter((entry) => entry.isFile() && path30.extname(entry.name).toLowerCase() === ".md").map((entry) => path30.join(policiesDir, entry.name)).sort((left, right) => left.localeCompare(right));
9417
+ return entries.filter((entry) => entry.isFile() && path31.extname(entry.name).toLowerCase() === ".md").map((entry) => path31.join(policiesDir, entry.name)).sort((left, right) => left.localeCompare(right));
9279
9418
  } catch {
9280
9419
  return [];
9281
9420
  }
9282
9421
  }
9283
9422
  async function validateSpecRootParent(entry) {
9284
- const specPath = path30.join(entry.dir, "01_Spec.md");
9423
+ const specPath = path31.join(entry.dir, "01_Spec.md");
9285
9424
  const text = await readSafe(specPath);
9286
9425
  if (text.trim().length > 0 && /\bCAP-\d{4}\b/.test(text)) {
9287
9426
  return [];
@@ -9382,10 +9521,10 @@ async function validateForbiddenRefs(filePath, pattern, messagePrefix) {
9382
9521
 
9383
9522
  // src/core/validators/legacyStatusDir.ts
9384
9523
  import { readdir as readdir7, stat as stat5 } from "fs/promises";
9385
- import path31 from "path";
9524
+ import path32 from "path";
9386
9525
  var BASELINE_STATUS_ARTIFACTS = /* @__PURE__ */ new Set(["README.md", ".gitignore"]);
9387
9526
  async function validateLegacyStatusDir(root) {
9388
- const statusDir = path31.join(root, ".qfai", "status");
9527
+ const statusDir = path32.join(root, ".qfai", "status");
9389
9528
  if (!await isDirectory(statusDir)) {
9390
9529
  return [];
9391
9530
  }
@@ -9437,15 +9576,15 @@ async function isDirectory(target) {
9437
9576
  }
9438
9577
 
9439
9578
  // src/core/validators/mermaidEnforcement.ts
9440
- import { readFile as readFile24 } from "fs/promises";
9441
- import path32 from "path";
9579
+ import { readFile as readFile25 } from "fs/promises";
9580
+ import path33 from "path";
9442
9581
  var TARGETS = [
9443
9582
  { segments: [".qfai", "specs"], extensions: [".md", ".feature"] },
9444
9583
  { segments: [".qfai", "discussion"], extensions: [".md"] }
9445
9584
  ];
9446
9585
  var BUSINESS_FLOW_RELATIVE_CANDIDATES = [
9447
- path32.join(".qfai", "specs", "_policies", "04_Business-Flow.md"),
9448
- path32.join(".qfai", "specs", "_policies", "04_Business-flow.md")
9586
+ path33.join(".qfai", "specs", "_policies", "04_Business-Flow.md"),
9587
+ path33.join(".qfai", "specs", "_policies", "04_Business-flow.md")
9449
9588
  ];
9450
9589
  var MERMAID_DIRECTIVE_RE = /^\s*(?:sequenceDiagram|flowchart|erDiagram|classDiagram|stateDiagram(?:-v2)?|journey|gantt|graph\s+(?:TB|BT|RL|LR|TD))\b/i;
9451
9590
  var FLOW_OR_SEQUENCE_RE2 = /\b(?:sequenceDiagram|flowchart)\b/i;
@@ -9455,13 +9594,13 @@ async function validateMermaidEnforcement(root) {
9455
9594
  const issues = [];
9456
9595
  const scanResults = /* @__PURE__ */ new Map();
9457
9596
  for (const filePath of targetFiles) {
9458
- const text = await readFile24(filePath, "utf-8");
9597
+ const text = await readFile25(filePath, "utf-8");
9459
9598
  const result = scanMermaidUsage(filePath, text);
9460
9599
  scanResults.set(filePath, result);
9461
9600
  issues.push(...result.issues);
9462
9601
  }
9463
9602
  if (businessFlowPath) {
9464
- const businessFlowScan = scanResults.get(businessFlowPath) ?? scanMermaidUsage(businessFlowPath, await readFile24(businessFlowPath, "utf-8"));
9603
+ const businessFlowScan = scanResults.get(businessFlowPath) ?? scanMermaidUsage(businessFlowPath, await readFile25(businessFlowPath, "utf-8"));
9465
9604
  if (businessFlowScan.mermaidFenceCount === 0) {
9466
9605
  issues.push(
9467
9606
  issue(
@@ -9489,7 +9628,7 @@ async function validateMermaidEnforcement(root) {
9489
9628
  )
9490
9629
  );
9491
9630
  }
9492
- if (path32.basename(businessFlowPath) === "04_Business-flow.md") {
9631
+ if (path33.basename(businessFlowPath) === "04_Business-flow.md") {
9493
9632
  issues.push(
9494
9633
  issue(
9495
9634
  "QFAI-MMD-005",
@@ -9510,14 +9649,14 @@ async function validateMermaidEnforcement(root) {
9510
9649
  async function collectTargetFiles(root) {
9511
9650
  const files = [];
9512
9651
  for (const target of TARGETS) {
9513
- const targetDir = path32.join(root, ...target.segments);
9652
+ const targetDir = path33.join(root, ...target.segments);
9514
9653
  files.push(
9515
9654
  ...await collectFiles(targetDir, {
9516
9655
  extensions: [...target.extensions]
9517
9656
  })
9518
9657
  );
9519
9658
  }
9520
- return Array.from(new Set(files)).filter((filePath) => path32.basename(filePath).toLowerCase() !== "readme.md").sort((a, b) => a.localeCompare(b));
9659
+ return Array.from(new Set(files)).filter((filePath) => path33.basename(filePath).toLowerCase() !== "readme.md").sort((a, b) => a.localeCompare(b));
9521
9660
  }
9522
9661
  function scanMermaidUsage(filePath, text) {
9523
9662
  const lines = text.replace(/\r\n/g, "\n").split("\n");
@@ -9607,13 +9746,13 @@ function parseFenceStart(line) {
9607
9746
  };
9608
9747
  }
9609
9748
  async function collectDeprecatedBusinessFlowFeatureWarnings(root) {
9610
- const policiesDir = path32.join(root, ".qfai", "specs", "_policies");
9749
+ const policiesDir = path33.join(root, ".qfai", "specs", "_policies");
9611
9750
  const featureFiles = await collectFiles(policiesDir, {
9612
9751
  extensions: [".feature"]
9613
9752
  });
9614
9753
  const issues = [];
9615
9754
  for (const filePath of featureFiles) {
9616
- if (!/business-flow/i.test(path32.basename(filePath))) {
9755
+ if (!/business-flow/i.test(path33.basename(filePath))) {
9617
9756
  continue;
9618
9757
  }
9619
9758
  issues.push(
@@ -9633,7 +9772,7 @@ async function collectDeprecatedBusinessFlowFeatureWarnings(root) {
9633
9772
  }
9634
9773
  async function resolveBusinessFlowPath(root) {
9635
9774
  for (const relativePath of BUSINESS_FLOW_RELATIVE_CANDIDATES) {
9636
- const target = path32.join(root, relativePath);
9775
+ const target = path33.join(root, relativePath);
9637
9776
  if (await exists5(target)) {
9638
9777
  return target;
9639
9778
  }
@@ -9771,7 +9910,7 @@ function normalizeHeaderKey(column) {
9771
9910
  }
9772
9911
 
9773
9912
  // src/core/validators/orphanProhibition.ts
9774
- import path33 from "path";
9913
+ import path34 from "path";
9775
9914
  async function validateOrphanProhibition(root, config) {
9776
9915
  const specsRoot = resolvePath(root, config, "specsDir");
9777
9916
  const entries = await collectSpecEntries(specsRoot);
@@ -9782,9 +9921,9 @@ async function validateOrphanProhibition(root, config) {
9782
9921
  return [];
9783
9922
  }
9784
9923
  const issues = [];
9785
- const policiesDir = layeredEntries[0]?.sharedDir ?? path33.join(specsRoot, "_policies");
9924
+ const policiesDir = layeredEntries[0]?.sharedDir ?? path34.join(specsRoot, "_policies");
9786
9925
  const capIds = new Set(
9787
- uniqueMatches(await readSafe(path33.join(policiesDir, "03_Capabilities.md")), /\bCAP-\d{4}\b/g)
9926
+ uniqueMatches(await readSafe(path34.join(policiesDir, "03_Capabilities.md")), /\bCAP-\d{4}\b/g)
9788
9927
  );
9789
9928
  for (const entry of layeredEntries) {
9790
9929
  const usItems = collectMarkdownItems(await readSafe(entry.userStoriesPath), "US");
@@ -9934,8 +10073,8 @@ function validateExParentExists(filePath, exItems, acIds, brIds) {
9934
10073
  }
9935
10074
 
9936
10075
  // src/core/validators/prototypingEvidence.ts
9937
- import { access as access9, readFile as readFile25 } from "fs/promises";
9938
- import path34 from "path";
10076
+ import { access as access9, readFile as readFile26 } from "fs/promises";
10077
+ import path35 from "path";
9939
10078
  var EVIDENCE_MARKDOWN_FILE = "prototyping.md";
9940
10079
  var EVIDENCE_JSON_FILE = "prototyping.json";
9941
10080
  async function validatePrototypingEvidence(root, config) {
@@ -9944,10 +10083,10 @@ async function validatePrototypingEvidence(root, config) {
9944
10083
  if (specEntries.length === 0) {
9945
10084
  return [];
9946
10085
  }
9947
- const qfaiRoot = path34.dirname(specsRoot);
9948
- const evidenceRoot = path34.join(qfaiRoot, "evidence");
9949
- const evidenceMarkdownPath = path34.join(evidenceRoot, EVIDENCE_MARKDOWN_FILE);
9950
- const evidenceJsonPath = path34.join(evidenceRoot, EVIDENCE_JSON_FILE);
10086
+ const qfaiRoot = path35.dirname(specsRoot);
10087
+ const evidenceRoot = path35.join(qfaiRoot, "evidence");
10088
+ const evidenceMarkdownPath = path35.join(evidenceRoot, EVIDENCE_MARKDOWN_FILE);
10089
+ const evidenceJsonPath = path35.join(evidenceRoot, EVIDENCE_JSON_FILE);
9951
10090
  const [markdownRaw, jsonRaw] = await Promise.all([
9952
10091
  readSafe11(evidenceMarkdownPath),
9953
10092
  readSafe11(evidenceJsonPath)
@@ -10124,7 +10263,7 @@ function extractSpecRefs(rows) {
10124
10263
  }
10125
10264
  async function readSafe11(filePath) {
10126
10265
  try {
10127
- return await readFile25(filePath, "utf-8");
10266
+ return await readFile26(filePath, "utf-8");
10128
10267
  } catch {
10129
10268
  return null;
10130
10269
  }
@@ -10280,7 +10419,7 @@ async function validateUiFidelity(root, config, evidenceJsonPath, evidence) {
10280
10419
  for (const screen of uiFidelity.screens) {
10281
10420
  const contractFiles = contractIndex.idToFiles.get(screen.uiContractId);
10282
10421
  const contractFile = contractFiles ? Array.from(contractFiles).sort((left, right) => left.localeCompare(right))[0] : void 0;
10283
- const contractRefFile = contractFile ? toPosixPath3(path34.relative(root, contractFile)) : void 0;
10422
+ const contractRefFile = contractFile ? toPosixPath3(path35.relative(root, contractFile)) : void 0;
10284
10423
  if (!contractFiles || contractFiles.size === 0) {
10285
10424
  mismatches.push({
10286
10425
  contractId: screen.uiContractId,
@@ -11089,7 +11228,7 @@ async function collectMissingRenderArtifacts(root, render) {
11089
11228
  { label: "htmlPath", target: render.htmlPath }
11090
11229
  ];
11091
11230
  for (const candidate of candidates) {
11092
- const resolved = path34.isAbsolute(candidate.target) ? candidate.target : path34.resolve(root, candidate.target);
11231
+ const resolved = path35.isAbsolute(candidate.target) ? candidate.target : path35.resolve(root, candidate.target);
11093
11232
  try {
11094
11233
  await access9(resolved);
11095
11234
  } catch {
@@ -11112,12 +11251,12 @@ function formatError9(error) {
11112
11251
  }
11113
11252
 
11114
11253
  // src/core/validators/requireIndex.ts
11115
- import { readFile as readFile26 } from "fs/promises";
11116
- import path35 from "path";
11254
+ import { readFile as readFile27 } from "fs/promises";
11255
+ import path36 from "path";
11117
11256
 
11118
11257
  // src/core/validators/repositoryHygiene.ts
11119
11258
  import { readdir as readdir8, stat as stat6 } from "fs/promises";
11120
- import path36 from "path";
11259
+ import path37 from "path";
11121
11260
  var LEGACY_DIR_RULES = [
11122
11261
  { legacy: "discussions", canonical: "discussion" },
11123
11262
  { legacy: "discuss", canonical: "discussion" },
@@ -11128,11 +11267,11 @@ var LEGACY_DIR_RULES = [
11128
11267
  ];
11129
11268
  var SUSPICIOUS_TEMPLATE_NAME_RE = /^(?:_?templates?|_?sample(?:s)?|sample-template)$/i;
11130
11269
  async function validateRepositoryHygiene(root, config) {
11131
- const qfaiRoot = path36.join(root, ".qfai");
11270
+ const qfaiRoot = path37.join(root, ".qfai");
11132
11271
  const specsRoot = resolvePath(root, config, "specsDir");
11133
11272
  const issues = [];
11134
11273
  for (const rule of LEGACY_DIR_RULES) {
11135
- const legacyPath = path36.join(qfaiRoot, rule.legacy);
11274
+ const legacyPath = path37.join(qfaiRoot, rule.legacy);
11136
11275
  if (!await isDirectory2(legacyPath)) {
11137
11276
  continue;
11138
11277
  }
@@ -11187,10 +11326,10 @@ async function collectSuspiciousTemplatePaths(root) {
11187
11326
  continue;
11188
11327
  }
11189
11328
  for (const entry of entries) {
11190
- const absolute = path36.join(current, entry.name);
11329
+ const absolute = path37.join(current, entry.name);
11191
11330
  if (entry.isDirectory()) {
11192
11331
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(entry.name)) {
11193
- matches.push(toPosix(path36.relative(root, absolute)));
11332
+ matches.push(toPosix(path37.relative(root, absolute)));
11194
11333
  }
11195
11334
  queue.push(absolute);
11196
11335
  continue;
@@ -11198,9 +11337,9 @@ async function collectSuspiciousTemplatePaths(root) {
11198
11337
  if (!entry.isFile()) {
11199
11338
  continue;
11200
11339
  }
11201
- const baseName = path36.parse(entry.name).name;
11340
+ const baseName = path37.parse(entry.name).name;
11202
11341
  if (SUSPICIOUS_TEMPLATE_NAME_RE.test(baseName)) {
11203
- matches.push(toPosix(path36.relative(root, absolute)));
11342
+ matches.push(toPosix(path37.relative(root, absolute)));
11204
11343
  }
11205
11344
  }
11206
11345
  }
@@ -11218,7 +11357,7 @@ function toPosix(value) {
11218
11357
  }
11219
11358
 
11220
11359
  // src/core/validators/specSplitByCapability.ts
11221
- import path37 from "path";
11360
+ import path38 from "path";
11222
11361
  var CAP_ID_RE2 = /\bCAP-\d{4}\b/g;
11223
11362
  async function validateSpecSplitByCapability(root, config) {
11224
11363
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -11229,8 +11368,8 @@ async function validateSpecSplitByCapability(root, config) {
11229
11368
  if (layeredEntries.length === 0) {
11230
11369
  return [];
11231
11370
  }
11232
- const policiesDir = layeredEntries[0]?.sharedDir ?? path37.join(specsRoot, "_policies");
11233
- const capabilitiesPath = path37.join(policiesDir, "03_Capabilities.md");
11371
+ const policiesDir = layeredEntries[0]?.sharedDir ?? path38.join(specsRoot, "_policies");
11372
+ const capabilitiesPath = path38.join(policiesDir, "03_Capabilities.md");
11234
11373
  const capabilityText = await readSafe(capabilitiesPath);
11235
11374
  const issues = [];
11236
11375
  if (!await exists5(capabilitiesPath)) {
@@ -11270,7 +11409,7 @@ async function validateSpecSplitByCapability(root, config) {
11270
11409
  );
11271
11410
  }
11272
11411
  const actualSpecIds = new Set(
11273
- layeredEntries.map((entry) => path37.basename(entry.dir).toLowerCase())
11412
+ layeredEntries.map((entry) => path38.basename(entry.dir).toLowerCase())
11274
11413
  );
11275
11414
  const expectedSpecIds = capIds.map((_, index) => `spec-${to4(index + 1)}`);
11276
11415
  const missingSpecIds = expectedSpecIds.filter((specId) => !actualSpecIds.has(specId));
@@ -11307,11 +11446,11 @@ async function validateSpecSplitByCapability(root, config) {
11307
11446
  continue;
11308
11447
  }
11309
11448
  const specId = `spec-${to4(index + 1)}`;
11310
- const entry = layeredEntries.find((value) => path37.basename(value.dir).toLowerCase() === specId);
11449
+ const entry = layeredEntries.find((value) => path38.basename(value.dir).toLowerCase() === specId);
11311
11450
  if (!entry) {
11312
11451
  continue;
11313
11452
  }
11314
- const specFilePath = path37.join(entry.dir, "01_Spec.md");
11453
+ const specFilePath = path38.join(entry.dir, "01_Spec.md");
11315
11454
  const specText = await readSafe(specFilePath);
11316
11455
  if (specText.trim().length === 0 || !specText.includes(capId)) {
11317
11456
  issues.push(
@@ -11330,8 +11469,8 @@ async function validateSpecSplitByCapability(root, config) {
11330
11469
  }
11331
11470
 
11332
11471
  // src/core/validators/statusInSpecs.ts
11333
- import { readFile as readFile27 } from "fs/promises";
11334
- import path38 from "path";
11472
+ import { readFile as readFile28 } from "fs/promises";
11473
+ import path39 from "path";
11335
11474
  var STRONG_PATTERNS = [
11336
11475
  { label: "release_candidate:", pattern: /\brelease_candidate\s*:/i },
11337
11476
  { label: "status:", pattern: /^\s*(?:-\s*)?status\s*:/im },
@@ -11388,23 +11527,23 @@ function hasMatch(text, pattern) {
11388
11527
  }
11389
11528
  async function readSafe12(filePath) {
11390
11529
  try {
11391
- return await readFile27(filePath, "utf-8");
11530
+ return await readFile28(filePath, "utf-8");
11392
11531
  } catch {
11393
11532
  return "";
11394
11533
  }
11395
11534
  }
11396
11535
  function isOpenQuestionsFile(filePath) {
11397
- return /open-questions\.md$/i.test(path38.basename(filePath));
11536
+ return /open-questions\.md$/i.test(path39.basename(filePath));
11398
11537
  }
11399
11538
 
11400
11539
  // src/core/validators/designToken.ts
11401
- import { readFile as readFile28 } from "fs/promises";
11402
- import path39 from "path";
11540
+ import { readFile as readFile29 } from "fs/promises";
11541
+ import path40 from "path";
11403
11542
  import fg2 from "fast-glob";
11404
- import { parse as parseYaml6 } from "yaml";
11543
+ import { parse as parseYaml7 } from "yaml";
11405
11544
 
11406
11545
  // src/core/parse/designToken.ts
11407
- import { parse as parseYaml5 } from "yaml";
11546
+ import { parse as parseYaml6 } from "yaml";
11408
11547
  var REF_PATTERN = /\{([^}]+)\}/g;
11409
11548
  var MAX_RESOLVE_DEPTH = 10;
11410
11549
  function parseDesignToken(yamlContent) {
@@ -11417,7 +11556,7 @@ function parseDesignToken(yamlContent) {
11417
11556
  };
11418
11557
  let parsed;
11419
11558
  try {
11420
- parsed = parseYaml5(yamlContent);
11559
+ parsed = parseYaml6(yamlContent);
11421
11560
  } catch (error) {
11422
11561
  const msg = error instanceof Error ? error.message : String(error);
11423
11562
  result.errors.push({ message: `YAML parse error: ${msg}` });
@@ -11446,7 +11585,7 @@ function collectLayer(layer, layerName, target, errors) {
11446
11585
  }
11447
11586
  function flattenTokens(obj, prefix, target, errors) {
11448
11587
  for (const [key, value] of Object.entries(obj)) {
11449
- const path55 = `${prefix}.${key}`;
11588
+ const path58 = `${prefix}.${key}`;
11450
11589
  if (value && typeof value === "object" && !Array.isArray(value)) {
11451
11590
  const record2 = value;
11452
11591
  if ("$value" in record2) {
@@ -11462,9 +11601,9 @@ function flattenTokens(obj, prefix, target, errors) {
11462
11601
  if (typeof record2.platform === "string") {
11463
11602
  token.platform = record2.platform;
11464
11603
  }
11465
- target.set(path55, token);
11604
+ target.set(path58, token);
11466
11605
  } else {
11467
- flattenTokens(record2, path55, target, errors);
11606
+ flattenTokens(record2, path58, target, errors);
11468
11607
  }
11469
11608
  }
11470
11609
  }
@@ -11474,44 +11613,44 @@ function resolveAllReferences(result) {
11474
11613
  for (const [key, val] of result.primitives) allTokens.set(key, val);
11475
11614
  for (const [key, val] of result.semantics) allTokens.set(key, val);
11476
11615
  for (const [key, val] of result.components) allTokens.set(key, val);
11477
- for (const [path55] of allTokens) {
11478
- resolveTokenRef(path55, allTokens, /* @__PURE__ */ new Set(), 0, result);
11616
+ for (const [path58] of allTokens) {
11617
+ resolveTokenRef(path58, allTokens, /* @__PURE__ */ new Set(), 0, result);
11479
11618
  }
11480
11619
  }
11481
- function resolveTokenRef(path55, allTokens, visited, depth, result) {
11482
- if (result.resolved.has(path55)) {
11483
- return result.resolved.get(path55);
11620
+ function resolveTokenRef(path58, allTokens, visited, depth, result) {
11621
+ if (result.resolved.has(path58)) {
11622
+ return result.resolved.get(path58);
11484
11623
  }
11485
11624
  if (depth > MAX_RESOLVE_DEPTH) {
11486
11625
  result.errors.push({
11487
- message: `Max reference depth exceeded at: ${path55}`,
11488
- path: path55
11626
+ message: `Max reference depth exceeded at: ${path58}`,
11627
+ path: path58
11489
11628
  });
11490
11629
  return void 0;
11491
11630
  }
11492
- if (visited.has(path55)) {
11631
+ if (visited.has(path58)) {
11493
11632
  result.errors.push({
11494
- message: `Circular reference detected: ${path55}`,
11495
- path: path55
11633
+ message: `Circular reference detected: ${path58}`,
11634
+ path: path58
11496
11635
  });
11497
11636
  return void 0;
11498
11637
  }
11499
- const token = allTokens.get(path55);
11638
+ const token = allTokens.get(path58);
11500
11639
  if (!token) {
11501
11640
  return void 0;
11502
11641
  }
11503
11642
  if (typeof token.$value !== "string") {
11504
11643
  const rawValue2 = stringifyTokenValue(token.$value);
11505
- result.resolved.set(path55, rawValue2);
11644
+ result.resolved.set(path58, rawValue2);
11506
11645
  return rawValue2;
11507
11646
  }
11508
11647
  const rawValue = stringifyTokenValue(token.$value);
11509
11648
  const refs = [...rawValue.matchAll(REF_PATTERN)];
11510
11649
  if (refs.length === 0) {
11511
- result.resolved.set(path55, rawValue);
11650
+ result.resolved.set(path58, rawValue);
11512
11651
  return rawValue;
11513
11652
  }
11514
- visited.add(path55);
11653
+ visited.add(path58);
11515
11654
  let resolved = rawValue;
11516
11655
  for (const ref of refs) {
11517
11656
  const refPath = ref[1];
@@ -11519,8 +11658,8 @@ function resolveTokenRef(path55, allTokens, visited, depth, result) {
11519
11658
  const refToken = allTokens.get(refPath);
11520
11659
  if (!refToken) {
11521
11660
  result.errors.push({
11522
- message: `Unresolved token reference: {${refPath}} at ${path55}`,
11523
- path: path55
11661
+ message: `Unresolved token reference: {${refPath}} at ${path58}`,
11662
+ path: path58
11524
11663
  });
11525
11664
  continue;
11526
11665
  }
@@ -11529,7 +11668,7 @@ function resolveTokenRef(path55, allTokens, visited, depth, result) {
11529
11668
  resolved = resolved.split(`{${refPath}}`).join(refValue);
11530
11669
  }
11531
11670
  }
11532
- result.resolved.set(path55, resolved);
11671
+ result.resolved.set(path58, resolved);
11533
11672
  return resolved;
11534
11673
  }
11535
11674
  function stringifyTokenValue(value) {
@@ -11584,8 +11723,8 @@ var VALID_TYPES = [
11584
11723
  var VALID_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
11585
11724
  async function validateDesignToken(root, config) {
11586
11725
  const configuredDir = config.uiux?.designTokensDir;
11587
- const designDir = configuredDir ? path39.resolve(root, configuredDir) : path39.join(root, config.paths.contractsDir, "design");
11588
- const pattern = path39.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
11726
+ const designDir = configuredDir ? path40.resolve(root, configuredDir) : path40.join(root, config.paths.contractsDir, "design");
11727
+ const pattern = path40.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
11589
11728
  const files = await fg2(pattern, {
11590
11729
  absolute: true,
11591
11730
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -11595,11 +11734,11 @@ async function validateDesignToken(root, config) {
11595
11734
  }
11596
11735
  const issues = [];
11597
11736
  for (const filePath of files) {
11598
- const rel = path39.relative(root, filePath).replace(/\\/g, "/");
11737
+ const rel = path40.relative(root, filePath).replace(/\\/g, "/");
11599
11738
  let hasRootObjectError = false;
11600
11739
  let content;
11601
11740
  try {
11602
- content = await readFile28(filePath, "utf-8");
11741
+ content = await readFile29(filePath, "utf-8");
11603
11742
  } catch {
11604
11743
  issues.push(
11605
11744
  issue(
@@ -11613,7 +11752,7 @@ async function validateDesignToken(root, config) {
11613
11752
  continue;
11614
11753
  }
11615
11754
  try {
11616
- const parsed = parseYaml6(content);
11755
+ const parsed = parseYaml7(content);
11617
11756
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
11618
11757
  hasRootObjectError = true;
11619
11758
  issues.push(
@@ -11762,8 +11901,8 @@ function normalizePlatform(value) {
11762
11901
  }
11763
11902
 
11764
11903
  // src/core/validators/htmlMock.ts
11765
- import { readFile as readFile29 } from "fs/promises";
11766
- import path40 from "path";
11904
+ import { readFile as readFile30 } from "fs/promises";
11905
+ import path41 from "path";
11767
11906
  import fg3 from "fast-glob";
11768
11907
 
11769
11908
  // src/core/uiux/contrastRatio.ts
@@ -12143,19 +12282,19 @@ async function validateHtmlMock(root, platform, config) {
12143
12282
  const startTime = performance.now();
12144
12283
  const budget = config.uiux?.htmlMockTimeout ?? 2e3;
12145
12284
  const patterns = [
12146
- path40.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12147
- path40.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12285
+ path41.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12286
+ path41.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12148
12287
  ];
12149
12288
  const files = await fg3(patterns, { absolute: true });
12150
12289
  const mockBlocks = [];
12151
12290
  for (const filePath of files) {
12152
12291
  let content;
12153
12292
  try {
12154
- content = await readFile29(filePath, "utf-8");
12293
+ content = await readFile30(filePath, "utf-8");
12155
12294
  } catch {
12156
12295
  continue;
12157
12296
  }
12158
- const rel = path40.relative(root, filePath).replace(/\\/g, "/");
12297
+ const rel = path41.relative(root, filePath).replace(/\\/g, "/");
12159
12298
  for (const block of collectHtmlMockBlocks(content)) {
12160
12299
  mockBlocks.push({ file: rel, html: block.html, rawBlock: block.rawBlock });
12161
12300
  }
@@ -12321,8 +12460,8 @@ async function validateHtmlMock(root, platform, config) {
12321
12460
  }
12322
12461
 
12323
12462
  // src/core/validators/mermaidScreenFlow.ts
12324
- import { readFile as readFile30 } from "fs/promises";
12325
- import path41 from "path";
12463
+ import { readFile as readFile31 } from "fs/promises";
12464
+ import path42 from "path";
12326
12465
  import fg4 from "fast-glob";
12327
12466
 
12328
12467
  // src/core/validators/mermaidUtils.ts
@@ -12373,18 +12512,18 @@ var FLOWCHART_RE = /^\s*flowchart\s+(TD|LR|TB|RL|BT)\b/;
12373
12512
  async function validateMermaidScreenFlow(root, config) {
12374
12513
  const issues = [];
12375
12514
  const patterns = [
12376
- path41.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12377
- path41.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12515
+ path42.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12516
+ path42.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12378
12517
  ];
12379
12518
  const files = await fg4(patterns, { absolute: true });
12380
12519
  for (const filePath of files) {
12381
12520
  let content;
12382
12521
  try {
12383
- content = await readFile30(filePath, "utf-8");
12522
+ content = await readFile31(filePath, "utf-8");
12384
12523
  } catch {
12385
12524
  continue;
12386
12525
  }
12387
- const rel = path41.relative(root, filePath).replace(/\\/g, "/");
12526
+ const rel = path42.relative(root, filePath).replace(/\\/g, "/");
12388
12527
  const blocks = extractFencedCodeBlocks(content);
12389
12528
  for (const block of blocks) {
12390
12529
  if (block.language !== "mermaid") continue;
@@ -12482,10 +12621,10 @@ function parseTransitions(content) {
12482
12621
  }
12483
12622
 
12484
12623
  // src/core/validators/bpApDb.ts
12485
- import { readFile as readFile31 } from "fs/promises";
12486
- import path42 from "path";
12624
+ import { readFile as readFile32 } from "fs/promises";
12625
+ import path43 from "path";
12487
12626
  import fg5 from "fast-glob";
12488
- import { parse as parseYaml7 } from "yaml";
12627
+ import { parse as parseYaml8 } from "yaml";
12489
12628
  var BP_ID_RE = /^BP-\d{4}$/;
12490
12629
  var AP_ID_RE = /^AP-\d{4}$/;
12491
12630
  var VALID_SEVERITIES = ["critical", "major", "minor"];
@@ -12520,9 +12659,9 @@ var AP_REQUIRED_FIELDS = [
12520
12659
  ];
12521
12660
  async function validateBpApDb(root, config) {
12522
12661
  const issues = [];
12523
- const designDir = path42.join(root, config.paths.contractsDir, "design");
12524
- const bpPattern = path42.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
12525
- const apPattern = path42.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
12662
+ const designDir = path43.join(root, config.paths.contractsDir, "design");
12663
+ const bpPattern = path43.posix.join(designDir.replace(/\\/g, "/"), "best-practices*.yaml");
12664
+ const apPattern = path43.posix.join(designDir.replace(/\\/g, "/"), "anti-patterns*.yaml");
12526
12665
  const globOptions = {
12527
12666
  absolute: true,
12528
12667
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12535,14 +12674,14 @@ async function validateBpApDb(root, config) {
12535
12674
  const seenBpIds = /* @__PURE__ */ new Set();
12536
12675
  const seenApIds = /* @__PURE__ */ new Set();
12537
12676
  for (const filePath of bpFiles) {
12538
- const rel = path42.relative(root, filePath).replace(/\\/g, "/");
12677
+ const rel = path43.relative(root, filePath).replace(/\\/g, "/");
12539
12678
  const entries = await parseRuleFile(filePath, rel, issues);
12540
12679
  for (const entry of entries) {
12541
12680
  validateBpEntry(entry, rel, seenBpIds, issues);
12542
12681
  }
12543
12682
  }
12544
12683
  for (const filePath of apFiles) {
12545
- const rel = path42.relative(root, filePath).replace(/\\/g, "/");
12684
+ const rel = path43.relative(root, filePath).replace(/\\/g, "/");
12546
12685
  const entries = await parseRuleFile(filePath, rel, issues);
12547
12686
  for (const entry of entries) {
12548
12687
  validateApEntry(entry, rel, seenApIds, issues);
@@ -12553,7 +12692,7 @@ async function validateBpApDb(root, config) {
12553
12692
  async function parseRuleFile(filePath, rel, issues) {
12554
12693
  let content;
12555
12694
  try {
12556
- content = await readFile31(filePath, "utf-8");
12695
+ content = await readFile32(filePath, "utf-8");
12557
12696
  } catch {
12558
12697
  issues.push(
12559
12698
  issue("QFAI-BPAP-001", `BP/AP file unreadable: ${rel}`, "error", rel, "bpApDb.readFile")
@@ -12562,7 +12701,7 @@ async function parseRuleFile(filePath, rel, issues) {
12562
12701
  }
12563
12702
  let parsed;
12564
12703
  try {
12565
- parsed = parseYaml7(content);
12704
+ parsed = parseYaml8(content);
12566
12705
  } catch (error) {
12567
12706
  issues.push(
12568
12707
  issue(
@@ -12717,8 +12856,8 @@ function toSafeString(value) {
12717
12856
  }
12718
12857
 
12719
12858
  // src/core/validators/platformDetection.ts
12720
- import { readFile as readFile32 } from "fs/promises";
12721
- import path43 from "path";
12859
+ import { readFile as readFile33 } from "fs/promises";
12860
+ import path44 from "path";
12722
12861
  var KNOWN_PLATFORMS = ["web", "windows", "mobile-ios", "mobile-android", "cross-platform"];
12723
12862
  async function detectPlatform(root, config, cliPlatform) {
12724
12863
  const issues = [];
@@ -12761,13 +12900,13 @@ async function detectPlatform(root, config, cliPlatform) {
12761
12900
  return { platform: "web", source: "fallback", issues };
12762
12901
  }
12763
12902
  async function inferPlatform(root, issues) {
12764
- if (await exists5(path43.join(root, "pubspec.yaml"))) {
12765
- const hasAndroid = await exists5(path43.join(root, "android"));
12766
- const hasIos = await exists5(path43.join(root, "ios"));
12767
- const hasWeb = await exists5(path43.join(root, "web"));
12768
- const hasWindows = await exists5(path43.join(root, "windows"));
12769
- const hasMacos = await exists5(path43.join(root, "macos"));
12770
- const hasLinux = await exists5(path43.join(root, "linux"));
12903
+ if (await exists5(path44.join(root, "pubspec.yaml"))) {
12904
+ const hasAndroid = await exists5(path44.join(root, "android"));
12905
+ const hasIos = await exists5(path44.join(root, "ios"));
12906
+ const hasWeb = await exists5(path44.join(root, "web"));
12907
+ const hasWindows = await exists5(path44.join(root, "windows"));
12908
+ const hasMacos = await exists5(path44.join(root, "macos"));
12909
+ const hasLinux = await exists5(path44.join(root, "linux"));
12771
12910
  const mobileTargets = [hasAndroid, hasIos].filter(Boolean).length;
12772
12911
  const desktopTargets = [hasWeb, hasWindows, hasMacos, hasLinux].filter(Boolean).length;
12773
12912
  if (mobileTargets + desktopTargets > 1) {
@@ -12787,10 +12926,10 @@ async function inferPlatform(root, issues) {
12787
12926
  }
12788
12927
  return null;
12789
12928
  }
12790
- const pkgJsonPath = path43.join(root, "package.json");
12929
+ const pkgJsonPath = path44.join(root, "package.json");
12791
12930
  if (await exists5(pkgJsonPath)) {
12792
12931
  try {
12793
- const raw = await readFile32(pkgJsonPath, "utf-8");
12932
+ const raw = await readFile33(pkgJsonPath, "utf-8");
12794
12933
  const pkg = JSON.parse(raw);
12795
12934
  const deps = {
12796
12935
  ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
@@ -12809,8 +12948,8 @@ async function inferPlatform(root, issues) {
12809
12948
  return "cross-platform";
12810
12949
  }
12811
12950
  if ("react-native" in deps) {
12812
- const hasAndroid = await exists5(path43.join(root, "android"));
12813
- const hasIos = await exists5(path43.join(root, "ios"));
12951
+ const hasAndroid = await exists5(path44.join(root, "android"));
12952
+ const hasIos = await exists5(path44.join(root, "ios"));
12814
12953
  if (hasAndroid && hasIos) {
12815
12954
  return "cross-platform";
12816
12955
  }
@@ -12832,15 +12971,15 @@ function normalizePlatformInput(platform) {
12832
12971
  }
12833
12972
 
12834
12973
  // src/core/validators/uiDefinitionConsistency.ts
12835
- import { readFile as readFile33 } from "fs/promises";
12836
- import path44 from "path";
12974
+ import { readFile as readFile34 } from "fs/promises";
12975
+ import path45 from "path";
12837
12976
  import fg6 from "fast-glob";
12838
- import { parse as parseYaml8 } from "yaml";
12977
+ import { parse as parseYaml9 } from "yaml";
12839
12978
  async function validateUiDefinitionConsistency(root, config) {
12840
12979
  const issues = [];
12841
12980
  const configuredDir = config.uiux?.designTokensDir;
12842
- const designDir = configuredDir ? path44.resolve(root, configuredDir) : path44.join(root, config.paths.contractsDir, "design");
12843
- const tokenPattern = path44.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12981
+ const designDir = configuredDir ? path45.resolve(root, configuredDir) : path45.join(root, config.paths.contractsDir, "design");
12982
+ const tokenPattern = path45.posix.join(designDir.replace(/\\/g, "/"), "design-tokens*.yaml");
12844
12983
  const tokenFiles = await fg6(tokenPattern, {
12845
12984
  absolute: true,
12846
12985
  ignore: ["**/*.schema.yaml", "**/*.schema.yml"]
@@ -12848,7 +12987,7 @@ async function validateUiDefinitionConsistency(root, config) {
12848
12987
  const resolvedTokens = /* @__PURE__ */ new Map();
12849
12988
  for (const tokenFile of tokenFiles) {
12850
12989
  try {
12851
- const content = await readFile33(tokenFile, "utf-8");
12990
+ const content = await readFile34(tokenFile, "utf-8");
12852
12991
  const result = parseDesignToken(content);
12853
12992
  for (const [key, val] of result.resolved) {
12854
12993
  resolvedTokens.set(key, val);
@@ -12856,14 +12995,14 @@ async function validateUiDefinitionConsistency(root, config) {
12856
12995
  } catch {
12857
12996
  }
12858
12997
  }
12859
- const uiContractDir = path44.join(root, config.paths.contractsDir, "ui");
12860
- const uiPattern = path44.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
12998
+ const uiContractDir = path45.join(root, config.paths.contractsDir, "ui");
12999
+ const uiPattern = path45.posix.join(uiContractDir.replace(/\\/g, "/"), "**/*.yaml");
12861
13000
  const uiFiles = await fg6(uiPattern, { absolute: true });
12862
13001
  const contractScreenIds = /* @__PURE__ */ new Set();
12863
13002
  for (const uiFile of uiFiles) {
12864
13003
  try {
12865
- const content = await readFile33(uiFile, "utf-8");
12866
- const parsed = parseYaml8(content);
13004
+ const content = await readFile34(uiFile, "utf-8");
13005
+ const parsed = parseYaml9(content);
12867
13006
  if (parsed && typeof parsed === "object") {
12868
13007
  const screens = parsed.screens;
12869
13008
  if (Array.isArray(screens)) {
@@ -12878,15 +13017,15 @@ async function validateUiDefinitionConsistency(root, config) {
12878
13017
  }
12879
13018
  }
12880
13019
  const mdPatterns = [
12881
- path44.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
12882
- path44.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
13020
+ path45.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md"),
13021
+ path45.posix.join(root.replace(/\\/g, "/"), config.paths.specsDir, "**/*.md")
12883
13022
  ];
12884
13023
  const mdFiles = await fg6(mdPatterns, { absolute: true });
12885
13024
  const mockScreenIds = /* @__PURE__ */ new Set();
12886
13025
  for (const mdFile of mdFiles) {
12887
13026
  try {
12888
- const content = await readFile33(mdFile, "utf-8");
12889
- const rel = path44.relative(root, mdFile).replace(/\\/g, "/");
13027
+ const content = await readFile34(mdFile, "utf-8");
13028
+ const rel = path45.relative(root, mdFile).replace(/\\/g, "/");
12890
13029
  const htmlBlocks = collectHtmlMockBlocks(content);
12891
13030
  if (resolvedTokens.size > 0) {
12892
13031
  for (const htmlBlock of htmlBlocks) {
@@ -12943,8 +13082,8 @@ async function validateUiDefinitionConsistency(root, config) {
12943
13082
  }
12944
13083
 
12945
13084
  // src/core/validators/researchSummary.ts
12946
- import { readFile as readFile34 } from "fs/promises";
12947
- import path45 from "path";
13085
+ import { readFile as readFile35 } from "fs/promises";
13086
+ import path46 from "path";
12948
13087
  import fg7 from "fast-glob";
12949
13088
  var RESEARCH_SUMMARY_HEADING_RE = /^#{1,3}\s+Research\s+Summary/im;
12950
13089
  var SOURCE_ENTRY_RE = /^\s*-\s*id:\s*(\S+)/gm;
@@ -12952,17 +13091,17 @@ var REFLECTION_APPLY_RE = /action:\s*apply/i;
12952
13091
  var FULL_DATE_RE = /^\s+published:\s*["']?(\d{4}-\d{2}-\d{2})["']?/m;
12953
13092
  async function validateResearchSummary(root, config) {
12954
13093
  const issues = [];
12955
- const pattern = path45.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
13094
+ const pattern = path46.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
12956
13095
  const files = await fg7(pattern, { absolute: true });
12957
13096
  for (const filePath of files) {
12958
13097
  let content;
12959
13098
  try {
12960
- content = await readFile34(filePath, "utf-8");
13099
+ content = await readFile35(filePath, "utf-8");
12961
13100
  } catch {
12962
13101
  continue;
12963
13102
  }
12964
13103
  if (!RESEARCH_SUMMARY_HEADING_RE.test(content)) continue;
12965
- const rel = path45.relative(root, filePath).replace(/\\/g, "/");
13104
+ const rel = path46.relative(root, filePath).replace(/\\/g, "/");
12966
13105
  const section = extractResearchSummarySection(content);
12967
13106
  if (!section) continue;
12968
13107
  const sourceEntries = extractSourceEntries(section);
@@ -13193,9 +13332,9 @@ function resolveFreshnessReferenceNow() {
13193
13332
  }
13194
13333
 
13195
13334
  // src/core/validators/agentDefinition.ts
13196
- import { readFile as readFile35 } from "fs/promises";
13197
- import path46 from "path";
13198
- import { parse as parseYaml9 } from "yaml";
13335
+ import { readFile as readFile36 } from "fs/promises";
13336
+ import path47 from "path";
13337
+ import { parse as parseYaml10 } from "yaml";
13199
13338
  var REQUIRED_AGENTS = [
13200
13339
  "uiux-expert.md",
13201
13340
  "design-expert.md",
@@ -13214,13 +13353,13 @@ var REQUIRED_SECTIONS = [
13214
13353
  var REQUIRED_PHASES = ["discussion", "SDD", "prototyping", "ATDD"];
13215
13354
  async function validateAgentDefinition(root, _config) {
13216
13355
  const issues = [];
13217
- const agentsDir = path46.join(root, ".qfai", "assistant", "agents");
13356
+ const agentsDir = path47.join(root, ".qfai", "assistant", "agents");
13218
13357
  if (!await exists5(agentsDir)) {
13219
13358
  return [];
13220
13359
  }
13221
13360
  let anyAgentExists = false;
13222
13361
  for (const agentFile of REQUIRED_AGENTS) {
13223
- if (await exists5(path46.join(agentsDir, agentFile))) {
13362
+ if (await exists5(path47.join(agentsDir, agentFile))) {
13224
13363
  anyAgentExists = true;
13225
13364
  break;
13226
13365
  }
@@ -13229,7 +13368,7 @@ async function validateAgentDefinition(root, _config) {
13229
13368
  return [];
13230
13369
  }
13231
13370
  for (const agentFile of REQUIRED_AGENTS) {
13232
- const filePath = path46.join(agentsDir, agentFile);
13371
+ const filePath = path47.join(agentsDir, agentFile);
13233
13372
  const rel = `.qfai/assistant/agents/${agentFile}`;
13234
13373
  if (!await exists5(filePath)) {
13235
13374
  issues.push(
@@ -13245,7 +13384,7 @@ async function validateAgentDefinition(root, _config) {
13245
13384
  }
13246
13385
  let content;
13247
13386
  try {
13248
- content = await readFile35(filePath, "utf-8");
13387
+ content = await readFile36(filePath, "utf-8");
13249
13388
  } catch {
13250
13389
  issues.push(
13251
13390
  issue(
@@ -13351,11 +13490,11 @@ async function validateAgentDefinition(root, _config) {
13351
13490
  }
13352
13491
  }
13353
13492
  }
13354
- const rosterPath = path46.join(root, ".qfai", "assistant", "steering", "review-roster.yml");
13493
+ const rosterPath = path47.join(root, ".qfai", "assistant", "steering", "review-roster.yml");
13355
13494
  if (await exists5(rosterPath)) {
13356
13495
  try {
13357
- const rosterContent = await readFile35(rosterPath, "utf-8");
13358
- const roster = parseYaml9(rosterContent);
13496
+ const rosterContent = await readFile36(rosterPath, "utf-8");
13497
+ const roster = parseYaml10(rosterContent);
13359
13498
  if (roster && typeof roster === "object") {
13360
13499
  const rosterObj = roster;
13361
13500
  const entries = Array.isArray(rosterObj.roster) ? rosterObj.roster : [];
@@ -13399,8 +13538,8 @@ function extractPhaseBody(phaseSection, phaseName) {
13399
13538
  }
13400
13539
 
13401
13540
  // src/core/validators/tddList.ts
13402
- import { readFile as readFile36, stat as stat7 } from "fs/promises";
13403
- import path47 from "path";
13541
+ import { readFile as readFile37, stat as stat7 } from "fs/promises";
13542
+ import path48 from "path";
13404
13543
  var REQUIRED_COLUMNS = [
13405
13544
  "TDD-ID",
13406
13545
  "TC-Refs",
@@ -13414,7 +13553,7 @@ var REQUIRED_COLUMNS = [
13414
13553
  var VALID_STATUSES = /* @__PURE__ */ new Set(["todo", "red", "green", "refactor", "done", "exception"]);
13415
13554
  var TEST_FILE_CHECK_STATUSES = /* @__PURE__ */ new Set(["green", "refactor", "done"]);
13416
13555
  var TDD_ID_FORMAT = /^TDD-\d{4}$/;
13417
- var TDD_LIST_REL_PATH = path47.join("tdd", "test-list.md");
13556
+ var TDD_LIST_REL_PATH = path48.join("tdd", "test-list.md");
13418
13557
  async function validateTddList(root, config) {
13419
13558
  const specsRoot = resolvePath(root, config, "specsDir");
13420
13559
  const entries = await collectSpecEntries(specsRoot);
@@ -13426,8 +13565,8 @@ async function validateTddList(root, config) {
13426
13565
  return issues;
13427
13566
  }
13428
13567
  async function validateSpecTddList(root, specDir, specNumber) {
13429
- const filePath = path47.join(specDir, TDD_LIST_REL_PATH);
13430
- const relPath = path47.relative(root, filePath).replace(/\\/g, "/");
13568
+ const filePath = path48.join(specDir, TDD_LIST_REL_PATH);
13569
+ const relPath = path48.relative(root, filePath).replace(/\\/g, "/");
13431
13570
  const issues = [];
13432
13571
  if (!await exists5(filePath)) {
13433
13572
  issues.push(
@@ -13614,9 +13753,9 @@ async function validateSpecTddList(root, specDir, specNumber) {
13614
13753
  continue;
13615
13754
  }
13616
13755
  const normalized = testFile.replace(/\\/g, "/");
13617
- const resolved = path47.resolve(root, normalized);
13618
- const relative = path47.relative(root, resolved);
13619
- if (path47.isAbsolute(normalized) || path47.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + path47.sep)) {
13756
+ const resolved = path48.resolve(root, normalized);
13757
+ const relative = path48.relative(root, resolved);
13758
+ if (path48.isAbsolute(normalized) || path48.win32.isAbsolute(normalized) || relative === ".." || relative.startsWith(".." + path48.sep)) {
13620
13759
  issues.push(
13621
13760
  issue(
13622
13761
  "TDDLIST_TEST_FILE_MISSING",
@@ -13679,11 +13818,11 @@ async function validateSpecTddList(root, specDir, specNumber) {
13679
13818
  }
13680
13819
  async function collectTestCaseIds(specDir) {
13681
13820
  const empty = { knownTcIds: /* @__PURE__ */ new Set(), unitComponentTcIds: /* @__PURE__ */ new Set() };
13682
- const testCasesPath = path47.join(specDir, "06_Test-Cases.md");
13821
+ const testCasesPath = path48.join(specDir, "06_Test-Cases.md");
13683
13822
  if (!await exists5(testCasesPath)) return empty;
13684
13823
  let content;
13685
13824
  try {
13686
- content = await readFile36(testCasesPath, "utf-8");
13825
+ content = await readFile37(testCasesPath, "utf-8");
13687
13826
  } catch {
13688
13827
  return empty;
13689
13828
  }
@@ -13709,8 +13848,8 @@ async function collectTestCaseIds(specDir) {
13709
13848
  }
13710
13849
 
13711
13850
  // src/core/validators/ddpValidation.ts
13712
- import { readFile as readFile37 } from "fs/promises";
13713
- import path48 from "path";
13851
+ import { readFile as readFile38 } from "fs/promises";
13852
+ import path49 from "path";
13714
13853
  import { fileURLToPath as fileURLToPath3 } from "url";
13715
13854
  import fg8 from "fast-glob";
13716
13855
  var DDP_HEADING_RE = /^#{1,3}\s+Design\s+Direction\s+Pack/im;
@@ -13728,9 +13867,9 @@ var _bannedPatterns = null;
13728
13867
  async function loadBannedPatterns() {
13729
13868
  if (_bannedPatterns !== null) return _bannedPatterns;
13730
13869
  try {
13731
- const thisDir = path48.dirname(fileURLToPath3(import.meta.url));
13732
- const filePath = path48.join(thisDir, "ddpBannedPatterns.txt");
13733
- const content = await readFile37(filePath, "utf-8");
13870
+ const thisDir = path49.dirname(fileURLToPath3(import.meta.url));
13871
+ const filePath = path49.join(thisDir, "ddpBannedPatterns.txt");
13872
+ const content = await readFile38(filePath, "utf-8");
13734
13873
  _bannedPatterns = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
13735
13874
  } catch {
13736
13875
  _bannedPatterns = [];
@@ -13739,15 +13878,15 @@ async function loadBannedPatterns() {
13739
13878
  }
13740
13879
  async function validateDdpFields(root, config) {
13741
13880
  const issues = [];
13742
- const pattern = path48.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
13881
+ const pattern = path49.posix.join(root.replace(/\\/g, "/"), config.paths.discussionDir, "**/*.md");
13743
13882
  const files = await fg8(pattern, { absolute: true });
13744
13883
  let ddpFound = false;
13745
13884
  let isUiBearing2 = false;
13746
13885
  for (const filePath of files) {
13747
- const basename = path48.basename(filePath);
13886
+ const basename = path49.basename(filePath);
13748
13887
  if (basename === "03_Story-Workshop.md") {
13749
13888
  try {
13750
- const storyContent = await readFile37(filePath, "utf-8");
13889
+ const storyContent = await readFile38(filePath, "utf-8");
13751
13890
  if (UI_BEARING_KEYWORDS_RE.test(storyContent)) {
13752
13891
  isUiBearing2 = true;
13753
13892
  }
@@ -13758,13 +13897,13 @@ async function validateDdpFields(root, config) {
13758
13897
  for (const filePath of files) {
13759
13898
  let content;
13760
13899
  try {
13761
- content = await readFile37(filePath, "utf-8");
13900
+ content = await readFile38(filePath, "utf-8");
13762
13901
  } catch {
13763
13902
  continue;
13764
13903
  }
13765
13904
  if (!DDP_HEADING_RE.test(content)) continue;
13766
13905
  ddpFound = true;
13767
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
13906
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
13768
13907
  const section = extractDdpSection(content);
13769
13908
  if (!section) continue;
13770
13909
  for (const field of DDP_REQUIRED_FIELDS) {
@@ -13957,7 +14096,7 @@ var ANTI_PATTERN_CHECKS = [
13957
14096
  ];
13958
14097
  async function validateResearchTraceability(root, config, issues) {
13959
14098
  const requireResearchSummary = config.uiux?.requireResearchSummary === true;
13960
- const discussionPattern = path48.posix.join(
14099
+ const discussionPattern = path49.posix.join(
13961
14100
  root.replace(/\\/g, "/"),
13962
14101
  config.paths.discussionDir,
13963
14102
  "**/*.md"
@@ -13966,7 +14105,7 @@ async function validateResearchTraceability(root, config, issues) {
13966
14105
  let hasResearchSummary = false;
13967
14106
  for (const filePath of discussionFiles) {
13968
14107
  try {
13969
- const content = await readFile37(filePath, "utf-8");
14108
+ const content = await readFile38(filePath, "utf-8");
13970
14109
  if (/research_summary\s*:/m.test(content)) {
13971
14110
  hasResearchSummary = true;
13972
14111
  break;
@@ -13985,7 +14124,7 @@ async function validateResearchTraceability(root, config, issues) {
13985
14124
  )
13986
14125
  );
13987
14126
  }
13988
- const contractPattern = path48.posix.join(
14127
+ const contractPattern = path49.posix.join(
13989
14128
  root.replace(/\\/g, "/"),
13990
14129
  config.paths.contractsDir,
13991
14130
  "design",
@@ -13995,11 +14134,11 @@ async function validateResearchTraceability(root, config, issues) {
13995
14134
  for (const filePath of contractFiles) {
13996
14135
  let content;
13997
14136
  try {
13998
- content = await readFile37(filePath, "utf-8");
14137
+ content = await readFile38(filePath, "utf-8");
13999
14138
  } catch {
14000
14139
  continue;
14001
14140
  }
14002
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
14141
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
14003
14142
  const ruleBlocks = content.split(/(?=^-\s+(?:rule|id)\s*:)/m).filter((b) => b.trim());
14004
14143
  for (const block of ruleBlocks) {
14005
14144
  const idMatch = /(?:^|\n)\s*(?:-\s+)?id\s*:\s*(\S+)/m.exec(block);
@@ -14022,11 +14161,11 @@ async function validateStoryWorkshopTemplates(root, config, issues, discussionFi
14022
14161
  for (const filePath of discussionFiles) {
14023
14162
  let content;
14024
14163
  try {
14025
- content = await readFile37(filePath, "utf-8");
14164
+ content = await readFile38(filePath, "utf-8");
14026
14165
  } catch {
14027
14166
  continue;
14028
14167
  }
14029
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
14168
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
14030
14169
  if (/screen_type\s*:\s*list/im.test(content)) {
14031
14170
  for (const field of LIST_TEMPLATE_REQUIRED_FIELDS) {
14032
14171
  if (!hasNonEmptyField(content, field)) {
@@ -14126,7 +14265,7 @@ function validateQualityProfile(config, issues) {
14126
14265
  }
14127
14266
  }
14128
14267
  async function validateOptionComparison(root, config, issues) {
14129
- const contractPattern = path48.posix.join(
14268
+ const contractPattern = path49.posix.join(
14130
14269
  root.replace(/\\/g, "/"),
14131
14270
  config.paths.contractsDir,
14132
14271
  "design",
@@ -14136,11 +14275,11 @@ async function validateOptionComparison(root, config, issues) {
14136
14275
  for (const filePath of contractFiles) {
14137
14276
  let content;
14138
14277
  try {
14139
- content = await readFile37(filePath, "utf-8");
14278
+ content = await readFile38(filePath, "utf-8");
14140
14279
  } catch {
14141
14280
  continue;
14142
14281
  }
14143
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
14282
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
14144
14283
  const optionMatches = content.match(/(?:^|\n)\s*-\s+option\s*:/gm) ?? [];
14145
14284
  const numberedOptions = content.match(/(?:^|\n)\s*option_\d+\s*:/gm) ?? [];
14146
14285
  const totalOptions = optionMatches.length + numberedOptions.length;
@@ -14175,7 +14314,7 @@ async function validateOptionComparison(root, config, issues) {
14175
14314
  }
14176
14315
  async function validateCompetitiveRefs(root, config, issues) {
14177
14316
  const minRefs = config.uiux?.competitive_refs_min ?? 3;
14178
- const contractPattern = path48.posix.join(
14317
+ const contractPattern = path49.posix.join(
14179
14318
  root.replace(/\\/g, "/"),
14180
14319
  config.paths.contractsDir,
14181
14320
  "design",
@@ -14185,11 +14324,11 @@ async function validateCompetitiveRefs(root, config, issues) {
14185
14324
  for (const filePath of contractFiles) {
14186
14325
  let content;
14187
14326
  try {
14188
- content = await readFile37(filePath, "utf-8");
14327
+ content = await readFile38(filePath, "utf-8");
14189
14328
  } catch {
14190
14329
  continue;
14191
14330
  }
14192
- const rel = path48.relative(root, filePath).replace(/\\/g, "/");
14331
+ const rel = path49.relative(root, filePath).replace(/\\/g, "/");
14193
14332
  const refsBlock = extractNestedBlock(content, "competitive_refs");
14194
14333
  if (refsBlock !== null) {
14195
14334
  const refItems = refsBlock.split(/\n/).filter((l) => /^\s*-\s/.test(l));
@@ -14339,7 +14478,7 @@ function collectListItems(section, field) {
14339
14478
  }
14340
14479
 
14341
14480
  // src/core/validators/navigationFlow.ts
14342
- import { readFile as readFile38 } from "fs/promises";
14481
+ import { readFile as readFile39 } from "fs/promises";
14343
14482
  var NODE_DEF_RE = /([A-Za-z_][\w-]*)\s*(?:\[.*?\]|\(.*?\)|\{.*?\})/g;
14344
14483
  var EDGE_RE = /([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?\s*(?:--+>|==+>|-.->|~~>)\s*(?:\|"?([^"|]*)"?\|)?\s*([A-Za-z_][\w-]*)(?:\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\}))?(?:::\w+)?/g;
14345
14484
  var SUBGRAPH_RE = /^\s*subgraph\s+([\w-]+)/gm;
@@ -14618,7 +14757,7 @@ async function validateNavigationFlow(root, config) {
14618
14757
  const specFiles = allFiles.filter((f) => /[\\/]spec-\d{4}[\\/]/.test(f));
14619
14758
  const issues = [];
14620
14759
  for (const file of specFiles) {
14621
- const content = await readFile38(file, "utf-8");
14760
+ const content = await readFile39(file, "utf-8");
14622
14761
  const blocks = extractFencedCodeBlocks(content);
14623
14762
  const mermaidBlocks = blocks.filter((b) => b.language === "mermaid");
14624
14763
  const _flowchartBlocks = mermaidBlocks.filter((b) => hasFlowchartDeclaration(b.content));
@@ -14640,8 +14779,8 @@ async function validateNavigationFlow(root, config) {
14640
14779
  }
14641
14780
 
14642
14781
  // src/core/validators/renderCritique.ts
14643
- import path49 from "path";
14644
- import { readFile as readFile39 } from "fs/promises";
14782
+ import path50 from "path";
14783
+ import { readFile as readFile40 } from "fs/promises";
14645
14784
  import fg9 from "fast-glob";
14646
14785
  var RENDERED_KEYWORDS_RE = /\b(rendered|screenshot|html\b|preview|visual\s*review)/i;
14647
14786
  var DDP_REFERENCE_RE = /\b(ddp|design\s*direction\s*pack)\b/i;
@@ -14660,8 +14799,8 @@ var FOUR_STATE_CHECK_RE = /\bfour_state_check\s*:/i;
14660
14799
  var MAX_PRIMARY_STEPS_RE = /\bmax_primary_steps\s*:\s*(\d+)/i;
14661
14800
  async function validateRenderCritique(root, config) {
14662
14801
  const issues = [];
14663
- const discussionDir = path49.join(root, config.paths.discussionDir).replace(/\\/g, "/");
14664
- const discussionFiles = await fg9(path49.posix.join(discussionDir, "**/*.md"), { absolute: true });
14802
+ const discussionDir = path50.join(root, config.paths.discussionDir).replace(/\\/g, "/");
14803
+ const discussionFiles = await fg9(path50.posix.join(discussionDir, "**/*.md"), { absolute: true });
14665
14804
  let hasDdp = false;
14666
14805
  for (const df of discussionFiles) {
14667
14806
  const content = await readSafe(df);
@@ -14671,12 +14810,12 @@ async function validateRenderCritique(root, config) {
14671
14810
  }
14672
14811
  }
14673
14812
  if (!hasDdp) return issues;
14674
- const skillsDir = path49.join(root, config.paths.skillsDir).replace(/\\/g, "/");
14675
- const evidenceDir = path49.join(root, ".qfai", "evidence").replace(/\\/g, "/");
14813
+ const skillsDir = path50.join(root, config.paths.skillsDir).replace(/\\/g, "/");
14814
+ const evidenceDir = path50.join(root, ".qfai", "evidence").replace(/\\/g, "/");
14676
14815
  const renderEvidenceViewports = await collectRenderEvidenceViewports(root);
14677
- const skillPromptPattern = path49.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
14816
+ const skillPromptPattern = path50.posix.join(skillsDir, "qfai-{prototyping,implement}*/SKILL.md");
14678
14817
  const skillFiles = await fg9(skillPromptPattern, { dot: true });
14679
- const evidencePattern = path49.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
14818
+ const evidencePattern = path50.posix.join(evidenceDir, "{prototyping*,critique-*}.md");
14680
14819
  const evidenceFiles = await fg9(evidencePattern, { dot: true });
14681
14820
  for (const sf of skillFiles) {
14682
14821
  const content = await readSafe(sf);
@@ -14684,7 +14823,7 @@ async function validateRenderCritique(root, config) {
14684
14823
  issues.push(
14685
14824
  issue(
14686
14825
  "QFAI-CRIT-001",
14687
- `Skill prompt does not mention rendered/screenshot/HTML review: ${path49.relative(root, sf)}`,
14826
+ `Skill prompt does not mention rendered/screenshot/HTML review: ${path50.relative(root, sf)}`,
14688
14827
  "error",
14689
14828
  sf,
14690
14829
  "renderCritique.codeOnly",
@@ -14701,7 +14840,7 @@ async function validateRenderCritique(root, config) {
14701
14840
  issues.push(
14702
14841
  issue(
14703
14842
  "QFAI-CRIT-002",
14704
- `Downstream skill prompt missing DDP reference: ${path49.relative(root, sf)}`,
14843
+ `Downstream skill prompt missing DDP reference: ${path50.relative(root, sf)}`,
14705
14844
  "error",
14706
14845
  sf,
14707
14846
  "renderCritique.ddpMissing",
@@ -14747,7 +14886,7 @@ async function validateRenderCritique(root, config) {
14747
14886
  issues.push(
14748
14887
  issue(
14749
14888
  "QFAI-CRIT-005",
14750
- `Read order not specified (DDP \u2192 Design Token \u2192 UI Contract \u2192 HTML Mock \u2192 Flow): ${path49.relative(root, sf)}`,
14889
+ `Read order not specified (DDP \u2192 Design Token \u2192 UI Contract \u2192 HTML Mock \u2192 Flow): ${path50.relative(root, sf)}`,
14751
14890
  "error",
14752
14891
  sf,
14753
14892
  "renderCritique.readOrder",
@@ -14774,7 +14913,7 @@ async function validateRenderCritique(root, config) {
14774
14913
  issues.push(
14775
14914
  issue(
14776
14915
  "QFAI-CRIT-006",
14777
- `Critique evidence incomplete (missing: ${missing.join(", ")}): ${path49.relative(root, ef)}`,
14916
+ `Critique evidence incomplete (missing: ${missing.join(", ")}): ${path50.relative(root, ef)}`,
14778
14917
  "error",
14779
14918
  ef,
14780
14919
  "renderCritique.incompleteEvidence",
@@ -14889,9 +15028,9 @@ async function collectContent(files) {
14889
15028
  return contents.join("\n---\n");
14890
15029
  }
14891
15030
  async function collectRenderEvidenceViewports(root) {
14892
- const prototypingJsonPath = path49.join(root, ".qfai", "evidence", "prototyping.json");
15031
+ const prototypingJsonPath = path50.join(root, ".qfai", "evidence", "prototyping.json");
14893
15032
  try {
14894
- const raw = await readFile39(prototypingJsonPath, "utf-8");
15033
+ const raw = await readFile40(prototypingJsonPath, "utf-8");
14895
15034
  const parsed = JSON.parse(raw);
14896
15035
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14897
15036
  return /* @__PURE__ */ new Set();
@@ -14930,8 +15069,8 @@ async function collectRenderEvidenceViewports(root) {
14930
15069
  }
14931
15070
 
14932
15071
  // src/core/validators/designFidelity.ts
14933
- import { readFile as readFile40 } from "fs/promises";
14934
- import path50 from "path";
15072
+ import { readFile as readFile41 } from "fs/promises";
15073
+ import path51 from "path";
14935
15074
  import fg10 from "fast-glob";
14936
15075
  var SCORECARD_HEADING_RE = /^#{1,3}\s+Fidelity\s+Scorecard/im;
14937
15076
  var BASE_DIMENSIONS = ["hierarchy", "clarity", "accessibility", "responsive"];
@@ -14956,19 +15095,19 @@ async function validateDesignFidelity(root, config) {
14956
15095
  const evidenceDirs = [".qfai/evidence", ".qfai/review"];
14957
15096
  const allFiles = [];
14958
15097
  for (const dir of evidenceDirs) {
14959
- const pattern = path50.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
15098
+ const pattern = path51.posix.join(root.replace(/\\/g, "/"), dir, "**/*.md");
14960
15099
  const files = await fg10(pattern, { absolute: true });
14961
15100
  allFiles.push(...files);
14962
15101
  }
14963
15102
  for (const filePath of allFiles) {
14964
15103
  let content;
14965
15104
  try {
14966
- content = await readFile40(filePath, "utf-8");
15105
+ content = await readFile41(filePath, "utf-8");
14967
15106
  } catch {
14968
15107
  continue;
14969
15108
  }
14970
15109
  if (!SCORECARD_HEADING_RE.test(content)) continue;
14971
- const rel = path50.relative(root, filePath).replace(/\\/g, "/");
15110
+ const rel = path51.relative(root, filePath).replace(/\\/g, "/");
14972
15111
  const section = extractScorecardSection(content);
14973
15112
  if (!section) continue;
14974
15113
  const dimensions = parseDimensions(section);
@@ -15260,7 +15399,7 @@ function hasAntiPatternMention(section, code) {
15260
15399
  }
15261
15400
 
15262
15401
  // src/core/validators/discussionDesignHardening.ts
15263
- import path51 from "path";
15402
+ import path52 from "path";
15264
15403
  var HTML_TAG_RE = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
15265
15404
  var MERMAID_SCREEN_FLOW_RE = /```mermaid[\s\S]*?(?:stateDiagram|flowchart|graph)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
15266
15405
  var DDS_HEADING = "## Design Direction Summary";
@@ -15275,10 +15414,17 @@ var DDS_SUBSECTIONS = [
15275
15414
  var REQUIRED_STATES = ["empty", "loading", "error", "populated"];
15276
15415
  var COMPETITIVE_REF_FIELDS = ["adopted_points", "rejected_points", "local_translation"];
15277
15416
  var PLACEHOLDER_RE = /^(?:tbd|todo|n\/a|na|xxx|\?\?\?|placeholder)$/i;
15417
+ var SURFACE_TYPE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
15278
15418
  async function isUiBearing(packRoot) {
15279
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15419
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15280
15420
  const content = await readSafe(storyPath);
15281
15421
  if (!content) return false;
15422
+ const surfaceMatch = SURFACE_TYPE_RE.exec(content);
15423
+ if (surfaceMatch?.[1]) {
15424
+ const surface = surfaceMatch[1].toLowerCase();
15425
+ if (surface === "non-ui") return false;
15426
+ if (["web-ui", "mobile-ui", "desktop-ui", "mixed"].includes(surface)) return true;
15427
+ }
15282
15428
  if (HTML_TAG_RE.test(content)) return true;
15283
15429
  if (MERMAID_SCREEN_FLOW_RE.test(content)) return true;
15284
15430
  return false;
@@ -15313,7 +15459,7 @@ function extractOptionNames(optionSection) {
15313
15459
  }
15314
15460
  async function validateDdsPresence(packRoot) {
15315
15461
  const issues = [];
15316
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15462
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15317
15463
  const content = await readSafe(storyPath);
15318
15464
  const relPath = "03_Story-Workshop.md";
15319
15465
  if (!content || !content.includes(DDS_HEADING)) {
@@ -15347,7 +15493,7 @@ async function validateDdsPresence(packRoot) {
15347
15493
  }
15348
15494
  async function validateOptionComparison2(packRoot) {
15349
15495
  const issues = [];
15350
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15496
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15351
15497
  const content = await readSafe(storyPath);
15352
15498
  if (!content) return issues;
15353
15499
  const dds = extractDdsSection(content);
@@ -15371,7 +15517,7 @@ async function validateOptionComparison2(packRoot) {
15371
15517
  }
15372
15518
  async function validateAnchorScreen(packRoot) {
15373
15519
  const issues = [];
15374
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15520
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15375
15521
  const content = await readSafe(storyPath);
15376
15522
  if (!content) return issues;
15377
15523
  const dds = extractDdsSection(content);
@@ -15413,7 +15559,7 @@ async function validateAnchorScreen(packRoot) {
15413
15559
  }
15414
15560
  async function validateCompetitiveRefs2(packRoot) {
15415
15561
  const issues = [];
15416
- const sourcesPath = path51.join(packRoot, "04_Sources.md");
15562
+ const sourcesPath = path52.join(packRoot, "04_Sources.md");
15417
15563
  const content = await readSafe(sourcesPath);
15418
15564
  if (!content) return issues;
15419
15565
  const registryHeadingRe = /^##\s+Competitive Reference Registry\b.*$/m;
@@ -15470,7 +15616,7 @@ function fieldGuidance(field) {
15470
15616
  }
15471
15617
  async function validateCtaHierarchy(packRoot) {
15472
15618
  const issues = [];
15473
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15619
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15474
15620
  const content = await readSafe(storyPath);
15475
15621
  if (!content) return issues;
15476
15622
  const dds = extractDdsSection(content);
@@ -15508,7 +15654,7 @@ async function validateCtaHierarchy(packRoot) {
15508
15654
  }
15509
15655
  async function validateStateCoverage(packRoot) {
15510
15656
  const issues = [];
15511
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15657
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15512
15658
  const content = await readSafe(storyPath);
15513
15659
  if (!content) return issues;
15514
15660
  const dds = extractDdsSection(content);
@@ -15533,7 +15679,7 @@ async function validateStateCoverage(packRoot) {
15533
15679
  }
15534
15680
  async function validateDesignAntiGoals(packRoot) {
15535
15681
  const issues = [];
15536
- const storyPath = path51.join(packRoot, "03_Story-Workshop.md");
15682
+ const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15537
15683
  const content = await readSafe(storyPath);
15538
15684
  if (!content) return issues;
15539
15685
  const dds = extractDdsSection(content);
@@ -15570,7 +15716,7 @@ async function validateDesignAntiGoals(packRoot) {
15570
15716
  return issues;
15571
15717
  }
15572
15718
  async function validateDiscussionDesignHardening(root, config) {
15573
- const discussionDir = path51.join(root, config.paths.discussionDir);
15719
+ const discussionDir = path52.join(root, config.paths.discussionDir);
15574
15720
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
15575
15721
  if (!packRoot) return [];
15576
15722
  const uiBearing = await isUiBearing(packRoot);
@@ -15588,7 +15734,7 @@ async function validateDiscussionDesignHardening(root, config) {
15588
15734
 
15589
15735
  // src/core/validators/designAudit.ts
15590
15736
  import { readdir as readdir9 } from "fs/promises";
15591
- import path52 from "path";
15737
+ import path53 from "path";
15592
15738
  var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
15593
15739
  function resolveAuditConfig(config) {
15594
15740
  const audit = config.uiux?.audit;
@@ -15670,7 +15816,7 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
15670
15816
  async function checkTokenDrift(root, auditConfig, cfg) {
15671
15817
  const findings = [];
15672
15818
  const configuredDir = cfg.uiux?.designTokensDir;
15673
- const tokensDir = configuredDir ? path52.resolve(root, configuredDir) : path52.join(root, cfg.paths.contractsDir, "design");
15819
+ const tokensDir = configuredDir ? path53.resolve(root, configuredDir) : path53.join(root, cfg.paths.contractsDir, "design");
15674
15820
  let hasTokenFiles = false;
15675
15821
  try {
15676
15822
  const entries = await readdir9(tokensDir);
@@ -15679,7 +15825,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
15679
15825
  return findings;
15680
15826
  }
15681
15827
  if (!hasTokenFiles) return findings;
15682
- const contractsUiDir = path52.join(root, cfg.paths.contractsDir, "ui");
15828
+ const contractsUiDir = path53.join(root, cfg.paths.contractsDir, "ui");
15683
15829
  let htmlFiles = [];
15684
15830
  try {
15685
15831
  const entries = await readdir9(contractsUiDir);
@@ -15690,7 +15836,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
15690
15836
  let rawCount = 0;
15691
15837
  const sampleLiterals = [];
15692
15838
  for (const htmlFile of htmlFiles) {
15693
- const content = await readSafe(path52.join(contractsUiDir, htmlFile));
15839
+ const content = await readSafe(path53.join(contractsUiDir, htmlFile));
15694
15840
  if (!content) continue;
15695
15841
  const matches = content.match(RAW_COLOR_RE);
15696
15842
  if (matches) {
@@ -15741,12 +15887,12 @@ function deduplicateFindings(issues, maxPerRule) {
15741
15887
  async function validateDesignAudit(root, config) {
15742
15888
  const auditConfig = resolveAuditConfig(config);
15743
15889
  if (!auditConfig.enabled) return [];
15744
- const discussionDir = path52.join(root, config.paths.discussionDir);
15890
+ const discussionDir = path53.join(root, config.paths.discussionDir);
15745
15891
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
15746
15892
  if (!packRoot) return [];
15747
15893
  const uiBearing = await isUiBearing(packRoot);
15748
15894
  if (!uiBearing) return [];
15749
- const storyPath = path52.join(packRoot, "03_Story-Workshop.md");
15895
+ const storyPath = path53.join(packRoot, "03_Story-Workshop.md");
15750
15896
  const content = await readSafe(storyPath);
15751
15897
  if (!content) return [];
15752
15898
  const findings = [];
@@ -15758,8 +15904,8 @@ async function validateDesignAudit(root, config) {
15758
15904
 
15759
15905
  // src/core/validators/designSlop.ts
15760
15906
  import { existsSync as existsSync2 } from "fs";
15761
- import { readFile as readFile41 } from "fs/promises";
15762
- import path53 from "path";
15907
+ import { readFile as readFile42 } from "fs/promises";
15908
+ import path54 from "path";
15763
15909
  import { fileURLToPath as fileURLToPath4 } from "url";
15764
15910
  function isValidSlopPattern(rule) {
15765
15911
  if (typeof rule !== "object" || rule === null) return false;
@@ -15767,7 +15913,7 @@ function isValidSlopPattern(rule) {
15767
15913
  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";
15768
15914
  }
15769
15915
  async function loadSlopPatterns(jsonPath) {
15770
- const raw = await readFile41(jsonPath, "utf-8");
15916
+ const raw = await readFile42(jsonPath, "utf-8");
15771
15917
  const parsed = JSON.parse(raw);
15772
15918
  if (!Array.isArray(parsed)) return [];
15773
15919
  return parsed.filter((r) => isValidSlopPattern(r));
@@ -15775,11 +15921,11 @@ async function loadSlopPatterns(jsonPath) {
15775
15921
  function defaultPatternsPath() {
15776
15922
  const base = import.meta.url;
15777
15923
  const basePath = base.startsWith("file:") ? fileURLToPath4(base) : base;
15778
- const baseDir = path53.dirname(basePath);
15924
+ const baseDir = path54.dirname(basePath);
15779
15925
  const candidates = [
15780
- path53.join(baseDir, "designSlopPatterns.json"),
15781
- path53.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
15782
- path53.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
15926
+ path54.join(baseDir, "designSlopPatterns.json"),
15927
+ path54.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
15928
+ path54.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
15783
15929
  ];
15784
15930
  for (const c of candidates) {
15785
15931
  if (existsSync2(c)) return c;
@@ -15790,7 +15936,7 @@ async function validateDesignSlop(root, config) {
15790
15936
  const auditConfig = resolveAuditConfig(config);
15791
15937
  if (!auditConfig.enabled) return [];
15792
15938
  if (!auditConfig.slopDetection) return [];
15793
- const discussionDir = path53.join(root, config.paths.discussionDir);
15939
+ const discussionDir = path54.join(root, config.paths.discussionDir);
15794
15940
  const packRoot = await findLatestDiscussionPackDir(discussionDir);
15795
15941
  if (!packRoot) return [];
15796
15942
  const uiBearing = await isUiBearing(packRoot);
@@ -15811,7 +15957,7 @@ async function validateDesignSlop(root, config) {
15811
15957
  continue;
15812
15958
  }
15813
15959
  for (const scope of pattern.scopes) {
15814
- const filePath = path53.join(packRoot, scope);
15960
+ const filePath = path54.join(packRoot, scope);
15815
15961
  const content = await readSafe(filePath);
15816
15962
  if (!content) continue;
15817
15963
  if (regex.test(content) && !seenRules.has(pattern.id)) {
@@ -15832,6 +15978,460 @@ async function validateDesignSlop(root, config) {
15832
15978
  return findings.map((f) => findingToIssue(f, auditConfig.qualityProfile, "slop"));
15833
15979
  }
15834
15980
 
15981
+ // src/core/validators/uixValidators.ts
15982
+ import { readdir as readdir11 } from "fs/promises";
15983
+ import path56 from "path";
15984
+
15985
+ // src/core/validators/uixDetection.ts
15986
+ import { readdir as readdir10 } from "fs/promises";
15987
+ import path55 from "path";
15988
+ var UI_BEARING_SURFACES = /* @__PURE__ */ new Set(["web-ui", "mobile-ui", "desktop-ui", "mixed"]);
15989
+ var NON_UI_SURFACES = /* @__PURE__ */ new Set(["non-ui"]);
15990
+ function stripCodeBlocks(content) {
15991
+ let stripped = content.replace(/```[\s\S]*?```/g, "");
15992
+ stripped = stripped.replace(/`[^`]+`/g, "");
15993
+ return stripped;
15994
+ }
15995
+ var HTML_TAG_RE2 = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
15996
+ var MERMAID_SCREEN_FLOW_RE2 = /```mermaid[\s\S]*?(?:stateDiagram)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
15997
+ var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
15998
+ async function isUiBearingSpec(root) {
15999
+ const specContent = await readSafe(path55.join(root, "01_Spec.md"));
16000
+ const surfaceMatch = /^\s*-\s*surface:\s*(\S+)/im.exec(specContent);
16001
+ if (surfaceMatch?.[1]) {
16002
+ const surface = surfaceMatch[1].toLowerCase();
16003
+ if (UI_BEARING_SURFACES.has(surface)) return true;
16004
+ if (NON_UI_SURFACES.has(surface)) return false;
16005
+ }
16006
+ try {
16007
+ await readdir10(path55.join(root, "uiux"));
16008
+ return true;
16009
+ } catch {
16010
+ }
16011
+ const contractsContent = await readSafe(path55.join(root, "uiux", "40_contracts.md"));
16012
+ if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return true;
16013
+ const storyContent = await readSafe(path55.join(root, "03_Story-Workshop.md"));
16014
+ if (storyContent) {
16015
+ const stripped = stripCodeBlocks(storyContent);
16016
+ if (HTML_TAG_RE2.test(stripped)) return true;
16017
+ if (MERMAID_SCREEN_FLOW_RE2.test(storyContent)) return true;
16018
+ }
16019
+ return false;
16020
+ }
16021
+
16022
+ // src/core/validators/uixValidators.ts
16023
+ function uixIssue(code, message, severity, file, suggestedAction) {
16024
+ return {
16025
+ code,
16026
+ severity,
16027
+ category: "compatibility",
16028
+ message,
16029
+ file,
16030
+ suggested_action: suggestedAction
16031
+ };
16032
+ }
16033
+ function parseSimpleYaml(content) {
16034
+ const result = {};
16035
+ const store = (rawKey, value) => {
16036
+ const key = rawKey.trim().toLowerCase().replace(/\s+/g, "_");
16037
+ if (key && value) result[key] = value;
16038
+ };
16039
+ for (const line of content.split("\n")) {
16040
+ const yamlMatch = /^\s*(\w[\w_]*):\s*(.*)$/.exec(line);
16041
+ if (yamlMatch?.[1] !== void 0 && yamlMatch[2] !== void 0) {
16042
+ store(yamlMatch[1], yamlMatch[2].trim());
16043
+ continue;
16044
+ }
16045
+ const bulletMatch = /^\s*-\s+([\w][\w\s]*?):\s+(.+)$/.exec(line);
16046
+ if (bulletMatch?.[1] !== void 0 && bulletMatch[2] !== void 0) {
16047
+ store(bulletMatch[1], bulletMatch[2].trim());
16048
+ continue;
16049
+ }
16050
+ const tableMatch = /^\s*\|\s*(\w[\w\s_]*?)\s*\|\s*(.+?)\s*\|\s*$/.exec(line);
16051
+ if (tableMatch?.[1] !== void 0 && tableMatch[2] !== void 0) {
16052
+ if (!/^[-:]+$/.test(tableMatch[2].trim())) {
16053
+ store(tableMatch[1], tableMatch[2].trim());
16054
+ }
16055
+ }
16056
+ }
16057
+ return result;
16058
+ }
16059
+ async function validateSidecarMissing(root, _config) {
16060
+ if (!await isUiBearingSpec(root)) return [];
16061
+ try {
16062
+ await readdir11(path56.join(root, "uiux"));
16063
+ return [];
16064
+ } catch {
16065
+ return [
16066
+ uixIssue(
16067
+ "UIX-VAL-SIDECAR-MISSING",
16068
+ "UI-bearing spec detected but uiux/ sidecar directory is missing.",
16069
+ "error",
16070
+ "uiux/",
16071
+ "Create the uiux/ directory and populate it with the sidecar template files."
16072
+ )
16073
+ ];
16074
+ }
16075
+ }
16076
+ var STRATEGY_REQUIRED_FIELDS_LEGACY = [
16077
+ "selection_required",
16078
+ "candidate_options",
16079
+ "chosen_option",
16080
+ "verification_expectations",
16081
+ "none_as_legitimate_outcome"
16082
+ ];
16083
+ var STRATEGY_REQUIRED_FIELDS_CURRENT = ["surface_type", "approach", "rationale"];
16084
+ var STRATEGY_MIN_LENGTH_FIELDS = ["rationale", "approach"];
16085
+ var STRATEGY_MIN_LENGTH = 20;
16086
+ async function validateStrategyCompleteness(root, _config) {
16087
+ if (!await isUiBearingSpec(root)) return [];
16088
+ const strategyPath = path56.join(root, "uiux", "10_strategy.md");
16089
+ const content = await readSafe(strategyPath);
16090
+ if (!content) return [];
16091
+ const parsed = parseSimpleYaml(content);
16092
+ const issues = [];
16093
+ const relPath = "uiux/10_strategy.md";
16094
+ const isCurrentFormat = parsed["surface_type"] !== void 0;
16095
+ const requiredFields = isCurrentFormat ? STRATEGY_REQUIRED_FIELDS_CURRENT : STRATEGY_REQUIRED_FIELDS_LEGACY;
16096
+ for (const field of requiredFields) {
16097
+ const value = parsed[field];
16098
+ if (value === void 0 || value === "") {
16099
+ issues.push(
16100
+ uixIssue(
16101
+ "UIX-VAL-STRATEGY-INCOMPLETE",
16102
+ `Strategy field '${field}' is missing or empty in 10_strategy.md.`,
16103
+ "error",
16104
+ relPath,
16105
+ `Add the '${field}' field to uiux/10_strategy.md with a valid value.`
16106
+ )
16107
+ );
16108
+ }
16109
+ }
16110
+ for (const field of STRATEGY_MIN_LENGTH_FIELDS) {
16111
+ const value = parsed[field];
16112
+ if (value === void 0 || value.length < STRATEGY_MIN_LENGTH) {
16113
+ issues.push(
16114
+ uixIssue(
16115
+ "UIX-VAL-STRATEGY-INCOMPLETE",
16116
+ `Strategy field '${field}' must be at least ${STRATEGY_MIN_LENGTH} characters (current: ${value?.length ?? 0}).`,
16117
+ "error",
16118
+ relPath,
16119
+ `Expand the '${field}' field in uiux/10_strategy.md to at least ${STRATEGY_MIN_LENGTH} characters.`
16120
+ )
16121
+ );
16122
+ }
16123
+ }
16124
+ return issues;
16125
+ }
16126
+ async function validateScoringAxes(root, _config) {
16127
+ if (!await isUiBearingSpec(root)) return [];
16128
+ const singlePath = path56.join(root, "uiux", "20_eval_axes.md");
16129
+ let content = await readSafe(singlePath);
16130
+ let relPath = "uiux/20_eval_axes.md";
16131
+ if (!content) {
16132
+ const splitFiles = [
16133
+ "20_eval_axis_usability.md",
16134
+ "21_eval_axis_consistency.md",
16135
+ "22_eval_axis_accessibility.md",
16136
+ "23_eval_axis_delight.md"
16137
+ ];
16138
+ const parts = [];
16139
+ for (const f of splitFiles) {
16140
+ const c = await readSafe(path56.join(root, "uiux", f));
16141
+ if (c) parts.push(c);
16142
+ }
16143
+ if (parts.length > 0) {
16144
+ content = parts.join("\n");
16145
+ relPath = "uiux/20_eval_axis_*.md";
16146
+ }
16147
+ }
16148
+ if (!content) return [];
16149
+ const issues = [];
16150
+ const lines = content.split("\n");
16151
+ let inTrendSection = false;
16152
+ for (const line of lines) {
16153
+ if (/trend[_-]derived/i.test(line)) {
16154
+ inTrendSection = true;
16155
+ continue;
16156
+ }
16157
+ if (inTrendSection && /^#+\s/.test(line)) {
16158
+ inTrendSection = false;
16159
+ continue;
16160
+ }
16161
+ const isBulletRow = /^\s*-\s/.test(line);
16162
+ const isTableRow2 = /^\s*\|/.test(line) && !/^\s*\|[\s-:|]+\|[\s-:|]*$/.test(line);
16163
+ if (inTrendSection && (isBulletRow || isTableRow2)) {
16164
+ if (!/source_translation/i.test(line)) {
16165
+ issues.push(
16166
+ uixIssue(
16167
+ "UIX-VAL-SCORING-AXIS-INCOMPLETE",
16168
+ "Trend-derived scoring axis row is missing source_translation field.",
16169
+ "error",
16170
+ relPath,
16171
+ "Add source_translation to each trend-derived evaluation axis row."
16172
+ )
16173
+ );
16174
+ }
16175
+ }
16176
+ }
16177
+ return issues;
16178
+ }
16179
+ var AGGREGATE_REQUIRED_FIELDS = ["weights", "normalization", "threshold"];
16180
+ async function validateAggregateScoringRules(root, _config) {
16181
+ if (!await isUiBearingSpec(root)) return [];
16182
+ const aggregatePath = path56.join(root, "uiux", "21_aggregate_scoring.md");
16183
+ let content = await readSafe(aggregatePath);
16184
+ let relPath = "uiux/21_aggregate_scoring.md";
16185
+ if (!content) {
16186
+ const delightContent = await readSafe(path56.join(root, "uiux", "23_eval_axis_delight.md"));
16187
+ if (delightContent) {
16188
+ const lines = delightContent.split("\n");
16189
+ let inSection = false;
16190
+ const sectionLines = [];
16191
+ for (const line of lines) {
16192
+ if (/^#+\s*Aggregate\s+Scoring/i.test(line)) {
16193
+ inSection = true;
16194
+ continue;
16195
+ }
16196
+ if (inSection && /^#+\s/.test(line)) break;
16197
+ if (inSection) sectionLines.push(line);
16198
+ }
16199
+ if (sectionLines.length > 0) {
16200
+ content = sectionLines.join("\n");
16201
+ relPath = "uiux/23_eval_axis_delight.md";
16202
+ }
16203
+ }
16204
+ }
16205
+ if (!content) return [];
16206
+ const parsed = parseSimpleYaml(content);
16207
+ const issues = [];
16208
+ for (const field of AGGREGATE_REQUIRED_FIELDS) {
16209
+ const value = parsed[field] ?? parsed[`${field}s`];
16210
+ if (value === void 0 || value === "") {
16211
+ issues.push(
16212
+ uixIssue(
16213
+ "UIX-VAL-AGGREGATE-SCORING-INCOMPLETE",
16214
+ `Aggregate scoring field '${field}' is missing in ${relPath}.`,
16215
+ "error",
16216
+ relPath,
16217
+ `Add the '${field}' field to ${relPath}.`
16218
+ )
16219
+ );
16220
+ }
16221
+ }
16222
+ return issues;
16223
+ }
16224
+ async function validateOptionComparison3(root, _config) {
16225
+ if (!await isUiBearingSpec(root)) return [];
16226
+ const issues = [];
16227
+ const compPath = path56.join(root, "uiux", "30_comparison.md");
16228
+ const compContent = await readSafe(compPath);
16229
+ if (compContent) {
16230
+ const headingOptions = compContent.match(/^##\s+Option\b/gim);
16231
+ const tableOptions = compContent.match(/\bOption\s+[A-Z]\b/gim);
16232
+ const uniqueTableOptions = tableOptions ? new Set(tableOptions.map((m) => m.trim().toUpperCase())).size : 0;
16233
+ const optionCount = Math.max(headingOptions?.length ?? 0, uniqueTableOptions);
16234
+ if (optionCount < 2) {
16235
+ issues.push(
16236
+ uixIssue(
16237
+ "UIX-VAL-COMPARISON-INSUFFICIENT",
16238
+ "30_comparison.md must contain at least 2 options for meaningful comparison.",
16239
+ "error",
16240
+ "uiux/30_comparison.md",
16241
+ "Add at least 2 '## Option' sections to uiux/30_comparison.md."
16242
+ )
16243
+ );
16244
+ }
16245
+ }
16246
+ const anchorPath = path56.join(root, "uiux", "31_anchor.md");
16247
+ const anchorContent = await readSafe(anchorPath);
16248
+ if (anchorContent) {
16249
+ if (!/selected_anchor\s*:/i.test(anchorContent) && !/chosen\s*:/i.test(anchorContent) && !/source\s+option\s*:/i.test(anchorContent)) {
16250
+ issues.push(
16251
+ uixIssue(
16252
+ "UIX-VAL-ANCHOR-MISSING",
16253
+ "31_anchor.md is missing a selected anchor declaration.",
16254
+ "error",
16255
+ "uiux/31_anchor.md",
16256
+ "Add a 'selected_anchor:' field to uiux/31_anchor.md."
16257
+ )
16258
+ );
16259
+ }
16260
+ }
16261
+ return issues;
16262
+ }
16263
+ var SCREEN_CONTRACT_REQUIRED_FIELDS = [
16264
+ "route",
16265
+ "actor",
16266
+ "purpose",
16267
+ "primary_tasks",
16268
+ "required_states",
16269
+ "transitions",
16270
+ "observable_outcomes"
16271
+ ];
16272
+ async function validateScreenContracts(root, _config) {
16273
+ if (!await isUiBearingSpec(root)) return [];
16274
+ const contractsPath = path56.join(root, "uiux", "40_contracts.md");
16275
+ const content = await readSafe(contractsPath);
16276
+ if (!content) return [];
16277
+ const parsed = parseSimpleYaml(content);
16278
+ const issues = [];
16279
+ const relPath = "uiux/40_contracts.md";
16280
+ const sectionHeadings = {
16281
+ primary_tasks: /^#{3,4}\s+Primary\s+Tasks/im,
16282
+ required_states: /^#{3,4}\s+Required\s+States/im,
16283
+ transitions: /^#{3,4}\s+Transitions/im,
16284
+ observable_outcomes: /^#{3,4}\s+Observable\s+Outcomes/im
16285
+ };
16286
+ for (const field of SCREEN_CONTRACT_REQUIRED_FIELDS) {
16287
+ const value = parsed[field];
16288
+ const hasValue = value !== void 0 && value !== "";
16289
+ const headingPattern = sectionHeadings[field];
16290
+ const hasHeading = headingPattern ? headingPattern.test(content) : false;
16291
+ if (!hasValue && !hasHeading) {
16292
+ issues.push(
16293
+ uixIssue(
16294
+ "UIX-VAL-SCREEN-CONTRACT-INCOMPLETE",
16295
+ `Screen contract field '${field}' is missing in 40_contracts.md.`,
16296
+ "error",
16297
+ relPath,
16298
+ `Add the '${field}' field to screen contracts in uiux/40_contracts.md.`
16299
+ )
16300
+ );
16301
+ }
16302
+ }
16303
+ return issues;
16304
+ }
16305
+ async function validateOqClosure(root, _config) {
16306
+ if (!await isUiBearingSpec(root)) return [];
16307
+ const rootOqPath = path56.join(root, "11_OQ-Register.md");
16308
+ const uiuxOqPath = path56.join(root, "uiux", "11_OQ-Register.md");
16309
+ const rootContent = await readSafe(rootOqPath);
16310
+ const content = rootContent || await readSafe(uiuxOqPath);
16311
+ if (!content) return [];
16312
+ const issues = [];
16313
+ const relPath = rootContent ? "11_OQ-Register.md" : "uiux/11_OQ-Register.md";
16314
+ const oqBlocks = content.split(/(?=^##\s+OQ-\d{4})/m);
16315
+ for (const block of oqBlocks) {
16316
+ const idMatch = /^##\s+(OQ-\d{4})/m.exec(block);
16317
+ if (!idMatch?.[1]) continue;
16318
+ const oqId = idMatch[1];
16319
+ const isOpen = /status\s*:\s*open/i.test(block);
16320
+ const isCritical = /severity\s*:\s*(?:critical|blocking)/i.test(block);
16321
+ if (isOpen && isCritical) {
16322
+ issues.push(
16323
+ uixIssue(
16324
+ "UIX-VAL-OQ-OPEN-CRITICAL",
16325
+ `Open critical OQ found: ${oqId}. Must be resolved before proceeding.`,
16326
+ "error",
16327
+ relPath,
16328
+ `Resolve or downgrade ${oqId} in uiux/11_OQ-Register.md before validation can pass.`
16329
+ )
16330
+ );
16331
+ }
16332
+ }
16333
+ const tableRowRegex = /^\s*\|\s*(OQ-\d{4})\s*\|/;
16334
+ for (const line of content.split("\n")) {
16335
+ const rowMatch = tableRowRegex.exec(line);
16336
+ if (!rowMatch?.[1]) continue;
16337
+ const oqId = rowMatch[1];
16338
+ const cols = line.split("|").map((c) => c.trim()).filter(Boolean);
16339
+ const disposition = cols[3]?.toLowerCase();
16340
+ if (disposition === "open") {
16341
+ const fullRow = line.toLowerCase();
16342
+ if (/critical|blocking/i.test(fullRow)) {
16343
+ issues.push(
16344
+ uixIssue(
16345
+ "UIX-VAL-OQ-OPEN-CRITICAL",
16346
+ `Open critical OQ found: ${oqId}. Must be resolved before proceeding.`,
16347
+ "error",
16348
+ relPath,
16349
+ `Resolve or downgrade ${oqId} in ${relPath} before validation can pass.`
16350
+ )
16351
+ );
16352
+ }
16353
+ }
16354
+ }
16355
+ return issues;
16356
+ }
16357
+ var CURRENT_SIDECAR_VERSION = "1.0.0";
16358
+ async function validateMigration(root, config) {
16359
+ if (!await isUiBearingSpec(root)) return [];
16360
+ const strict = config.uiux?.migration?.strict === true;
16361
+ const issues = [];
16362
+ try {
16363
+ await readdir11(path56.join(root, "uiux"));
16364
+ } catch {
16365
+ issues.push(
16366
+ uixIssue(
16367
+ "UIX-VAL-MIGRATION-SIDECAR-MISSING",
16368
+ "UI-bearing spec detected but uiux/ sidecar directory is missing. Migration required.",
16369
+ strict ? "error" : "warning",
16370
+ "uiux/",
16371
+ "Run the UIX sidecar migration to create the uiux/ directory structure."
16372
+ )
16373
+ );
16374
+ return issues;
16375
+ }
16376
+ const versionPath = path56.join(root, "uiux", ".sidecar-version");
16377
+ const versionContent = await readSafe(versionPath);
16378
+ if (versionContent) {
16379
+ const version = versionContent.trim();
16380
+ if (version && version !== CURRENT_SIDECAR_VERSION) {
16381
+ issues.push(
16382
+ uixIssue(
16383
+ "UIX-VAL-MIGRATION-STALE-VERSION",
16384
+ `Sidecar template version '${version}' is outdated (current: ${CURRENT_SIDECAR_VERSION}).`,
16385
+ "warning",
16386
+ "uiux/.sidecar-version",
16387
+ `Upgrade sidecar template from ${version} to ${CURRENT_SIDECAR_VERSION}. Run the migration tool for upgrade steps.`
16388
+ )
16389
+ );
16390
+ }
16391
+ }
16392
+ return issues;
16393
+ }
16394
+ function applyPhase1Ratchet(issues, releaseDate, now = /* @__PURE__ */ new Date()) {
16395
+ const phase1EndMs = releaseDate.getTime() + 30 * 24 * 60 * 60 * 1e3;
16396
+ if (now.getTime() > phase1EndMs) return issues;
16397
+ return issues.map((iss) => {
16398
+ if (iss.code.startsWith("UIX-VAL-") && iss.severity === "error") {
16399
+ return { ...iss, severity: "warning" };
16400
+ }
16401
+ return iss;
16402
+ });
16403
+ }
16404
+ async function runAllUixValidators(root, config) {
16405
+ let effectiveRoot = root;
16406
+ const directSpec = await readSafe(path56.join(root, "01_Spec.md"));
16407
+ if (!directSpec) {
16408
+ const discussionDir = path56.join(root, config.paths.discussionDir);
16409
+ const packRoot = await findLatestDiscussionPackDir(discussionDir);
16410
+ if (!packRoot) return [];
16411
+ effectiveRoot = packRoot;
16412
+ }
16413
+ const validators = [
16414
+ validateSidecarMissing,
16415
+ validateStrategyCompleteness,
16416
+ validateScoringAxes,
16417
+ validateAggregateScoringRules,
16418
+ validateOptionComparison3,
16419
+ validateScreenContracts,
16420
+ validateOqClosure,
16421
+ validateMigration
16422
+ ];
16423
+ const results = await Promise.all(validators.map((v) => v(effectiveRoot, config)));
16424
+ let issues = results.flat();
16425
+ const relDateStr = config.uiux?.phase1ReleaseDate;
16426
+ if (relDateStr) {
16427
+ const releaseDate = new Date(relDateStr);
16428
+ if (!isNaN(releaseDate.getTime())) {
16429
+ issues = applyPhase1Ratchet(issues, releaseDate);
16430
+ }
16431
+ }
16432
+ return issues;
16433
+ }
16434
+
15835
16435
  // src/core/validate.ts
15836
16436
  var UIUX_VALIDATION_BUDGET_MS = 2e3;
15837
16437
  async function validateProject(root, configResult, options = {}) {
@@ -15851,7 +16451,8 @@ async function validateProject(root, configResult, options = {}) {
15851
16451
  () => validateResearchSummary(root, config),
15852
16452
  () => validateAgentDefinition(root, config),
15853
16453
  () => validateDesignAudit(root, config),
15854
- () => validateDesignSlop(root, config)
16454
+ () => validateDesignSlop(root, config),
16455
+ () => runAllUixValidators(root, config)
15855
16456
  ];
15856
16457
  const uiuxIssueGroups = await Promise.all(uiuxValidators.map((validator) => validator()));
15857
16458
  const uiuxIssues = [...platformResult.issues, ...uiuxIssueGroups.flat()];
@@ -15938,15 +16539,15 @@ var REPORT_GUARDRAILS_MAX = 20;
15938
16539
  var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
15939
16540
  var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
15940
16541
  async function createReportData(root, validation, configResult) {
15941
- const resolvedRoot = path54.resolve(root);
16542
+ const resolvedRoot = path57.resolve(root);
15942
16543
  const resolved = configResult ?? await loadConfig(resolvedRoot);
15943
16544
  const config = resolved.config;
15944
16545
  const configPath = resolved.configPath;
15945
16546
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
15946
16547
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
15947
- const apiRoot = path54.join(contractsRoot, "api");
15948
- const uiRoot = path54.join(contractsRoot, "ui");
15949
- const dbRoot = path54.join(contractsRoot, "db");
16548
+ const apiRoot = path57.join(contractsRoot, "api");
16549
+ const uiRoot = path57.join(contractsRoot, "ui");
16550
+ const dbRoot = path57.join(contractsRoot, "db");
15950
16551
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
15951
16552
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
15952
16553
  const specEntries = await collectSpecEntries(specsRoot);
@@ -16883,7 +17484,7 @@ async function collectChangeTypeSummary(specsRoot) {
16883
17484
  };
16884
17485
  const deltaFiles = await collectDeltaFiles(specsRoot);
16885
17486
  for (const deltaFile of deltaFiles) {
16886
- const text = await readFile42(deltaFile, "utf-8");
17487
+ const text = await readFile43(deltaFile, "utf-8");
16887
17488
  const parsed = parseDeltaV1(text);
16888
17489
  for (const entry of parsed.entries) {
16889
17490
  if (!entry.meta) {
@@ -16920,7 +17521,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
16920
17521
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
16921
17522
  }
16922
17523
  for (const file of specFiles) {
16923
- const text = await readFile42(file, "utf-8");
17524
+ const text = await readFile43(file, "utf-8");
16924
17525
  const parsed = parseSpec(text, file);
16925
17526
  const specKey = parsed.specId;
16926
17527
  if (!specKey) {
@@ -16957,7 +17558,7 @@ async function collectIds(files) {
16957
17558
  result[prefix] = /* @__PURE__ */ new Set();
16958
17559
  }
16959
17560
  for (const file of files) {
16960
- const text = await readFile42(file, "utf-8");
17561
+ const text = await readFile43(file, "utf-8");
16961
17562
  for (const prefix of ID_PREFIXES) {
16962
17563
  const ids = extractIds(text, prefix);
16963
17564
  ids.forEach((id) => result[prefix].add(id));
@@ -16972,7 +17573,7 @@ async function collectIds(files) {
16972
17573
  async function collectUpstreamIds(files) {
16973
17574
  const ids = /* @__PURE__ */ new Set();
16974
17575
  for (const file of files) {
16975
- const text = await readFile42(file, "utf-8");
17576
+ const text = await readFile43(file, "utf-8");
16976
17577
  extractAllIds(text).forEach((id) => ids.add(id));
16977
17578
  }
16978
17579
  return ids;
@@ -16993,7 +17594,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
16993
17594
  }
16994
17595
  const pattern = buildIdPattern(Array.from(upstreamIds));
16995
17596
  for (const file of targetFiles) {
16996
- const text = await readFile42(file, "utf-8");
17597
+ const text = await readFile43(file, "utf-8");
16997
17598
  if (pattern.test(text)) {
16998
17599
  return true;
16999
17600
  }
@@ -17112,7 +17713,7 @@ function normalizeScSources(root, sources) {
17112
17713
  async function countScenarios(scenarioFiles) {
17113
17714
  let total = 0;
17114
17715
  for (const file of scenarioFiles) {
17115
- const text = await readFile42(file, "utf-8");
17716
+ const text = await readFile43(file, "utf-8");
17116
17717
  const { document, errors } = parseScenarioDocument(text, file);
17117
17718
  if (!document || errors.length > 0) {
17118
17719
  continue;
@@ -17143,7 +17744,7 @@ async function collectTestStrategy(scenarioFiles, root, config, limit) {
17143
17744
  let totalScenarios = 0;
17144
17745
  let e2eCount = 0;
17145
17746
  for (const file of scenarioFiles) {
17146
- const text = await readFile42(file, "utf-8");
17747
+ const text = await readFile43(file, "utf-8");
17147
17748
  const { document, errors } = parseScenarioDocument(text, file);
17148
17749
  if (!document || errors.length > 0) {
17149
17750
  continue;
@@ -17231,10 +17832,10 @@ function buildHotspots(issues) {
17231
17832
  async function collectTddCoverage(entries) {
17232
17833
  const specs = [];
17233
17834
  for (const entry of entries) {
17234
- const testCasesPath = path54.join(entry.dir, "06_Test-Cases.md");
17835
+ const testCasesPath = path57.join(entry.dir, "06_Test-Cases.md");
17235
17836
  let tcContent;
17236
17837
  try {
17237
- tcContent = await readFile42(testCasesPath, "utf-8");
17838
+ tcContent = await readFile43(testCasesPath, "utf-8");
17238
17839
  } catch {
17239
17840
  continue;
17240
17841
  }
@@ -17266,10 +17867,10 @@ async function collectTddCoverage(entries) {
17266
17867
  });
17267
17868
  continue;
17268
17869
  }
17269
- const tddListPath = path54.join(entry.dir, "tdd", "test-list.md");
17870
+ const tddListPath = path57.join(entry.dir, "tdd", "test-list.md");
17270
17871
  let tddContent;
17271
17872
  try {
17272
- tddContent = await readFile42(tddListPath, "utf-8");
17873
+ tddContent = await readFile43(tddListPath, "utf-8");
17273
17874
  } catch {
17274
17875
  specs.push({
17275
17876
  specNumber: entry.specNumber,
@@ -17354,6 +17955,8 @@ export {
17354
17955
  ID_PREFIXES,
17355
17956
  RUNTIME_HEAVY_CHECKS,
17356
17957
  STATIC_OBLIGATIONS,
17958
+ VALID_MODE_SOURCES,
17959
+ adaptSurfaceEvidence,
17357
17960
  autogenerateUiFidelity,
17358
17961
  buildUiFidelityScreens,
17359
17962
  checkDecisionGuardrails,
@@ -17379,6 +17982,7 @@ export {
17379
17982
  filterDecisionGuardrailsByKeyword,
17380
17983
  findConfigRoot,
17381
17984
  formatGuardrailsForLlm,
17985
+ formatModeLog,
17382
17986
  formatReportJson,
17383
17987
  formatReportMarkdown,
17384
17988
  getConfigPath,
@@ -17386,9 +17990,11 @@ export {
17386
17990
  loadConfig,
17387
17991
  loadDecisionGuardrails,
17388
17992
  normalizeDecisionGuardrails,
17993
+ readDiscussionRecommendation,
17389
17994
  resolveObligations,
17390
17995
  resolveObligationsWithOptIn,
17391
17996
  resolvePath,
17997
+ resolvePrecedence,
17392
17998
  resolveToolVersion,
17393
17999
  runMockPaths,
17394
18000
  runSddPreflight,