@harness-engineering/cli 3.1.0 → 4.0.0

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.
Files changed (126) hide show
  1. package/dist/agents/personas/harness-pm.yaml +33 -0
  2. package/dist/agents/skills/claude-code/acceptance-eval/SKILL.md +92 -0
  3. package/dist/agents/skills/claude-code/acceptance-eval/skill.yaml +54 -0
  4. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +46 -38
  5. package/dist/agents/skills/claude-code/harness-brainstorming/skill.yaml +1 -0
  6. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +10 -11
  7. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +24 -26
  8. package/dist/agents/skills/claude-code/harness-integration/SKILL.md +21 -19
  9. package/dist/agents/skills/claude-code/harness-maintenance-pipeline/SKILL.md +173 -0
  10. package/dist/agents/skills/claude-code/harness-maintenance-pipeline/skill.yaml +43 -0
  11. package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +2 -0
  12. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +17 -40
  13. package/dist/agents/skills/claude-code/harness-pulse/SKILL.md +1 -1
  14. package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +17 -0
  15. package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +8 -0
  16. package/dist/agents/skills/claude-code/harness-verification/SKILL.md +9 -12
  17. package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +112 -126
  18. package/dist/agents/skills/claude-code/outcome-eval/SKILL.md +1 -0
  19. package/dist/agents/skills/codex/acceptance-eval/SKILL.md +92 -0
  20. package/dist/agents/skills/codex/acceptance-eval/skill.yaml +54 -0
  21. package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +46 -38
  22. package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +1 -0
  23. package/dist/agents/skills/codex/harness-code-review/SKILL.md +10 -11
  24. package/dist/agents/skills/codex/harness-execution/SKILL.md +24 -26
  25. package/dist/agents/skills/codex/harness-integration/SKILL.md +21 -19
  26. package/dist/agents/skills/codex/harness-maintenance-pipeline/SKILL.md +173 -0
  27. package/dist/agents/skills/codex/harness-maintenance-pipeline/skill.yaml +43 -0
  28. package/dist/agents/skills/codex/harness-onboarding/SKILL.md +2 -0
  29. package/dist/agents/skills/codex/harness-planning/SKILL.md +17 -40
  30. package/dist/agents/skills/codex/harness-pulse/SKILL.md +1 -1
  31. package/dist/agents/skills/codex/harness-roadmap/SKILL.md +17 -0
  32. package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +8 -0
  33. package/dist/agents/skills/codex/harness-verification/SKILL.md +9 -12
  34. package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +112 -126
  35. package/dist/agents/skills/codex/outcome-eval/SKILL.md +1 -0
  36. package/dist/agents/skills/cursor/acceptance-eval/SKILL.md +92 -0
  37. package/dist/agents/skills/cursor/acceptance-eval/skill.yaml +54 -0
  38. package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +46 -38
  39. package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +1 -0
  40. package/dist/agents/skills/cursor/harness-code-review/SKILL.md +10 -11
  41. package/dist/agents/skills/cursor/harness-execution/SKILL.md +24 -26
  42. package/dist/agents/skills/cursor/harness-integration/SKILL.md +21 -19
  43. package/dist/agents/skills/cursor/harness-maintenance-pipeline/SKILL.md +173 -0
  44. package/dist/agents/skills/cursor/harness-maintenance-pipeline/skill.yaml +43 -0
  45. package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +2 -0
  46. package/dist/agents/skills/cursor/harness-planning/SKILL.md +17 -40
  47. package/dist/agents/skills/cursor/harness-pulse/SKILL.md +1 -1
  48. package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +17 -0
  49. package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +8 -0
  50. package/dist/agents/skills/cursor/harness-verification/SKILL.md +9 -12
  51. package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +112 -126
  52. package/dist/agents/skills/cursor/outcome-eval/SKILL.md +1 -0
  53. package/dist/agents/skills/gemini-cli/acceptance-eval/SKILL.md +92 -0
  54. package/dist/agents/skills/gemini-cli/acceptance-eval/skill.yaml +54 -0
  55. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +46 -38
  56. package/dist/agents/skills/gemini-cli/harness-brainstorming/skill.yaml +1 -0
  57. package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +10 -11
  58. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +24 -26
  59. package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +21 -19
  60. package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/SKILL.md +173 -0
  61. package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/skill.yaml +43 -0
  62. package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +2 -0
  63. package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +17 -40
  64. package/dist/agents/skills/gemini-cli/harness-pulse/SKILL.md +1 -1
  65. package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +17 -0
  66. package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +8 -0
  67. package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +9 -12
  68. package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +112 -126
  69. package/dist/agents/skills/gemini-cli/outcome-eval/SKILL.md +1 -0
  70. package/dist/agents/skills/tests/interaction-channel.test.ts +64 -0
  71. package/dist/{agents-md-YYCLGMBF.js → agents-md-7OSNWU52.js} +2 -2
  72. package/dist/{architecture-5KPP4ID4.js → architecture-2XYGJGT3.js} +3 -3
  73. package/dist/{assess-project-B6ENHUAU.js → assess-project-3KPP2D6M.js} +1 -1
  74. package/dist/bin/harness-mcp.js +16 -15
  75. package/dist/bin/harness.js +27 -25
  76. package/dist/{check-phase-gate-REG3HWYC.js → check-phase-gate-46U5AAXE.js} +4 -3
  77. package/dist/{chunk-HIPBU2Q4.js → chunk-363V6GPR.js} +5 -5
  78. package/dist/{chunk-DNX35Q5K.js → chunk-6NCREWWM.js} +2 -2
  79. package/dist/{chunk-FL5SLUKZ.js → chunk-6YXCZV33.js} +2141 -929
  80. package/dist/{chunk-UBTMPO5H.js → chunk-AK67S3O4.js} +3 -3
  81. package/dist/{chunk-ERZCC5VH.js → chunk-BV45354Q.js} +3 -3
  82. package/dist/{chunk-ZOUJP3EY.js → chunk-ERILXQBP.js} +1153 -484
  83. package/dist/{chunk-BXKNWSG4.js → chunk-FZ4Q73HA.js} +1 -1
  84. package/dist/{chunk-PZX2Y3RV.js → chunk-GXCPDAS5.js} +3 -3
  85. package/dist/{chunk-C2ZGWMTN.js → chunk-HMW3C5DP.js} +2 -2
  86. package/dist/{chunk-TE5YHHHZ.js → chunk-HXMAIVV2.js} +1 -1
  87. package/dist/{chunk-TCVQVHEF.js → chunk-IYWT3JJW.js} +1 -1
  88. package/dist/{chunk-A65QO5KN.js → chunk-KLPX55TI.js} +2953 -1162
  89. package/dist/{chunk-OQFEFVIN.js → chunk-MSBJ445U.js} +4 -4
  90. package/dist/{chunk-FUH4J43G.js → chunk-MYVTYFQD.js} +2 -2
  91. package/dist/{chunk-LMZ2JTNP.js → chunk-PHX45JYN.js} +5 -9
  92. package/dist/{chunk-ZRKFDE6M.js → chunk-S2RQPPL6.js} +1 -1
  93. package/dist/chunk-SPE7P4GL.js +61 -0
  94. package/dist/{chunk-YF3AY6KS.js → chunk-TTWNGOBT.js} +6 -6
  95. package/dist/{chunk-6B6UN6SG.js → chunk-UTJJJDNN.js} +11 -1
  96. package/dist/{chunk-WWACIIBA.js → chunk-V3ZQUCRH.js} +1 -1
  97. package/dist/{chunk-XEOSSKIB.js → chunk-VTU3QOJT.js} +48 -4
  98. package/dist/{chunk-IF3JK3G5.js → chunk-VTUINXWR.js} +1 -1
  99. package/dist/{chunk-27AJKSQY.js → chunk-WBVCN35T.js} +1 -1
  100. package/dist/{chunk-RGQXHCCH.js → chunk-WL3O2PKJ.js} +6 -6
  101. package/dist/{chunk-YQAFKOOL.js → chunk-XFHZ7GCI.js} +1 -1
  102. package/dist/chunk-Y6SUQC32.js +9 -0
  103. package/dist/{chunk-XZ2Q4UTW.js → chunk-ZEYAOYJL.js} +1 -1
  104. package/dist/{ci-workflow-JEYYCBOC.js → ci-workflow-XJN4UO3Q.js} +2 -2
  105. package/dist/{dist-3HW4LCHE.js → dist-IPVFYGY2.js} +71 -15
  106. package/dist/{docs-YRMW7UED.js → docs-RSFZTNPG.js} +3 -3
  107. package/dist/{engine-E7H5U3AC.js → engine-DJIHY4JT.js} +2 -2
  108. package/dist/{entropy-3JBKCGJJ.js → entropy-EAEKJKRM.js} +2 -2
  109. package/dist/{feedback-U6VJ6AUJ.js → feedback-JT5X3DBC.js} +1 -1
  110. package/dist/{generate-agent-definitions-S2FRZF4Y.js → generate-agent-definitions-CC6KED3E.js} +4 -4
  111. package/dist/hooks/adoption-tracker.js +6 -5
  112. package/dist/hooks/protect-config.js +15 -5
  113. package/dist/{index-builder-3AOQ3ZII.js → index-builder-52IJJHJC.js} +2 -2
  114. package/dist/index.d.ts +8 -8
  115. package/dist/index.js +27 -25
  116. package/dist/{loader-RGQJYERV.js → loader-UWNVTYBY.js} +2 -2
  117. package/dist/{mcp-NDTFZXWF.js → mcp-TCLNK7HG.js} +18 -17
  118. package/dist/{performance-FPWKC4CN.js → performance-R6O3N3UB.js} +3 -3
  119. package/dist/{review-pipeline-WUHG7PHO.js → review-pipeline-MOOWXWSQ.js} +2 -2
  120. package/dist/{runtime-Q3ORD7LW.js → runtime-RIWQUKBQ.js} +2 -2
  121. package/dist/{security-JDHTRBGC.js → security-U76C54W6.js} +1 -1
  122. package/dist/{skill-executor-MOCUIAYS.js → skill-executor-BGUCAVBU.js} +2 -2
  123. package/dist/state-events-F44CUUZG.js +25 -0
  124. package/dist/{validate-G3LKBOYA.js → validate-IUAV7MOU.js} +3 -3
  125. package/dist/{validate-cross-check-4ITA72DF.js → validate-cross-check-M2ZM6RFY.js} +2 -2
  126. package/package.json +7 -7
@@ -1,22 +1,34 @@
1
+ import {
2
+ analyzeDiffDefinition,
3
+ createSelfReviewDefinition,
4
+ handleAnalyzeDiff,
5
+ handleCreateSelfReview,
6
+ handleRequestPeerReview,
7
+ requestPeerReviewDefinition
8
+ } from "./chunk-BV45354Q.js";
1
9
  import {
2
10
  getSecurityTrendsDefinition,
3
11
  handleGetSecurityTrends,
4
12
  handleRunSecurityScan,
5
13
  runSecurityScanDefinition
6
- } from "./chunk-DNX35Q5K.js";
14
+ } from "./chunk-6NCREWWM.js";
7
15
  import {
8
16
  handleRunCodeReview,
9
17
  runCodeReviewDefinition,
10
18
  sortFindingsBySeverity
11
- } from "./chunk-C2ZGWMTN.js";
19
+ } from "./chunk-HMW3C5DP.js";
12
20
  import {
13
21
  assessProjectDefinition,
14
22
  handleAssessProject
15
- } from "./chunk-YF3AY6KS.js";
23
+ } from "./chunk-TTWNGOBT.js";
24
+ import {
25
+ checkDocsDefinition,
26
+ handleCheckDocs
27
+ } from "./chunk-363V6GPR.js";
16
28
  import {
17
29
  detectEntropyDefinition,
18
30
  handleDetectEntropy
19
- } from "./chunk-PZX2Y3RV.js";
31
+ } from "./chunk-GXCPDAS5.js";
20
32
  import {
21
33
  checkPerformanceDefinition,
22
34
  getCriticalPathsDefinition,
@@ -26,18 +38,10 @@ import {
26
38
  handleGetPerfBaselines,
27
39
  handleUpdatePerfBaselines,
28
40
  updatePerfBaselinesDefinition
29
- } from "./chunk-RGQXHCCH.js";
41
+ } from "./chunk-WL3O2PKJ.js";
30
42
  import {
31
43
  getBusinessKnowledgeResource
32
44
  } from "./chunk-IOW3MW2K.js";
33
- import {
34
- analyzeDiffDefinition,
35
- createSelfReviewDefinition,
36
- handleAnalyzeDiff,
37
- handleCreateSelfReview,
38
- handleRequestPeerReview,
39
- requestPeerReviewDefinition
40
- } from "./chunk-ERZCC5VH.js";
41
45
  import {
42
46
  GENERATED_HEADER_CLAUDE,
43
47
  GENERATED_HEADER_CODEX,
@@ -53,27 +57,23 @@ import {
53
57
  import {
54
58
  handleValidateProject,
55
59
  validateToolDefinition
56
- } from "./chunk-UBTMPO5H.js";
60
+ } from "./chunk-AK67S3O4.js";
57
61
  import {
58
62
  checkDependenciesDefinition,
59
63
  handleCheckDependencies
60
- } from "./chunk-OQFEFVIN.js";
61
- import {
62
- resolveProjectConfig
63
- } from "./chunk-XZ2Q4UTW.js";
64
- import {
65
- checkDocsDefinition,
66
- handleCheckDocs
67
- } from "./chunk-HIPBU2Q4.js";
64
+ } from "./chunk-MSBJ445U.js";
68
65
  import {
69
66
  resultToMcpResponse
70
67
  } from "./chunk-RC3OI5NK.js";
68
+ import {
69
+ resolveProjectConfig
70
+ } from "./chunk-ZEYAOYJL.js";
71
71
  import {
72
72
  sanitizePath
73
73
  } from "./chunk-W6Y7ZW3Y.js";
74
74
  import {
75
75
  loadOrRebuildIndex
76
- } from "./chunk-27AJKSQY.js";
76
+ } from "./chunk-WBVCN35T.js";
77
77
  import {
78
78
  resolveCommunitySkillsDir,
79
79
  resolveGlobalCommunitySkillsDir,
@@ -82,15 +82,18 @@ import {
82
82
  resolveProjectSkillsDir,
83
83
  resolveSkillsDir,
84
84
  resolveTemplatesDir
85
- } from "./chunk-6B6UN6SG.js";
85
+ } from "./chunk-UTJJJDNN.js";
86
86
  import {
87
87
  SkillMetadataSchema
88
88
  } from "./chunk-HIK6OEUF.js";
89
+ import {
90
+ findFiles
91
+ } from "./chunk-Y6SUQC32.js";
89
92
  import {
90
93
  findConfigFile,
91
94
  loadConfig,
92
95
  resolveConfig
93
- } from "./chunk-YQAFKOOL.js";
96
+ } from "./chunk-XFHZ7GCI.js";
94
97
  import {
95
98
  logger
96
99
  } from "./chunk-EBJQ6N4M.js";
@@ -104,8 +107,10 @@ import {
104
107
  ConflictError,
105
108
  DEFAULT_TOKEN_BUDGET,
106
109
  DESTRUCTIVE_BASH,
110
+ SIGNAL_CATEGORY_MAP,
107
111
  StructuralStrategy,
108
112
  TruncationStrategy,
113
+ applyRoadmapDiff,
109
114
  checkTaint,
110
115
  computeLoadPlan,
111
116
  createTrackerClient,
@@ -118,10 +123,13 @@ import {
118
123
  loadTrackerClientConfigFromProject,
119
124
  loadTrackerSyncConfig,
120
125
  paginate,
126
+ reconcilePassed,
127
+ resolveRoadmapStore,
128
+ roadmapSourceExists,
121
129
  scanForInjection,
122
130
  serializeEnvelope,
123
131
  writeTaint
124
- } from "./chunk-A65QO5KN.js";
132
+ } from "./chunk-KLPX55TI.js";
125
133
  import {
126
134
  Err,
127
135
  INDEXED_FILE_KINDS,
@@ -317,7 +325,6 @@ release-readiness.json
317
325
  skills-index.json
318
326
  stack-profile.json
319
327
  metrics/
320
- events.jsonl
321
328
  .install-id
322
329
  telemetry.json
323
330
  .telemetry-notice-shown
@@ -738,7 +745,7 @@ async function handleInsightsSummary(input) {
738
745
  const skip = Array.isArray(input.skip) ? input.skip.filter(
739
746
  (k) => typeof k === "string" && INSIGHTS_KEYS.includes(k)
740
747
  ) : void 0;
741
- const { composeInsights } = await import("./dist-3HW4LCHE.js");
748
+ const { composeInsights } = await import("./dist-IPVFYGY2.js");
742
749
  const report = await composeInsights(projectPath, {
743
750
  ...skip && skip.length > 0 && { skip }
744
751
  });
@@ -914,7 +921,7 @@ ${skippedMsg}`
914
921
  async function handleInitProject(input) {
915
922
  const i = input;
916
923
  try {
917
- const { TemplateEngine } = await import("./engine-E7H5U3AC.js");
924
+ const { TemplateEngine } = await import("./engine-DJIHY4JT.js");
918
925
  const engine = new TemplateEngine(resolveTemplatesDir());
919
926
  const safePath = sanitizePath(i.path);
920
927
  const detected = tryDetectFramework(engine, safePath, i);
@@ -936,7 +943,7 @@ var listPersonasDefinition = {
936
943
  inputSchema: { type: "object", properties: {} }
937
944
  };
938
945
  async function handleListPersonas() {
939
- const { listPersonas } = await import("./loader-RGQJYERV.js");
946
+ const { listPersonas } = await import("./loader-UWNVTYBY.js");
940
947
  const result = listPersonas(resolvePersonasDir());
941
948
  return resultToMcpResponse(result);
942
949
  }
@@ -960,10 +967,10 @@ async function handleGeneratePersonaArtifacts(input) {
960
967
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.name)) {
961
968
  return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.name}`)));
962
969
  }
963
- const { loadPersona } = await import("./loader-RGQJYERV.js");
964
- const { generateRuntime } = await import("./runtime-Q3ORD7LW.js");
965
- const { generateAgentsMd } = await import("./agents-md-YYCLGMBF.js");
966
- const { generateCIWorkflow } = await import("./ci-workflow-JEYYCBOC.js");
970
+ const { loadPersona } = await import("./loader-UWNVTYBY.js");
971
+ const { generateRuntime } = await import("./runtime-RIWQUKBQ.js");
972
+ const { generateAgentsMd } = await import("./agents-md-7OSNWU52.js");
973
+ const { generateCIWorkflow } = await import("./ci-workflow-XJN4UO3Q.js");
967
974
  const personasDir = resolvePersonasDir();
968
975
  const filePath = path4.join(personasDir, `${input.name}.yaml`);
969
976
  if (!filePath.startsWith(personasDir)) {
@@ -1018,9 +1025,9 @@ async function handleRunPersona(input) {
1018
1025
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.persona)) {
1019
1026
  return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.persona}`)));
1020
1027
  }
1021
- const { loadPersona } = await import("./loader-RGQJYERV.js");
1028
+ const { loadPersona } = await import("./loader-UWNVTYBY.js");
1022
1029
  const { runPersona } = await import("./runner-AAEF2TXY.js");
1023
- const { executeSkill } = await import("./skill-executor-MOCUIAYS.js");
1030
+ const { executeSkill } = await import("./skill-executor-BGUCAVBU.js");
1024
1031
  const personasDir = resolvePersonasDir();
1025
1032
  const filePath = path4.join(personasDir, `${input.persona}.yaml`);
1026
1033
  if (!filePath.startsWith(personasDir)) {
@@ -1460,11 +1467,16 @@ function loadOrGenerateProfile(projectRoot) {
1460
1467
  return profile;
1461
1468
  }
1462
1469
 
1463
- // src/mcp/tools/event-emitter.ts
1470
+ // src/mcp/tools/skill-telemetry.ts
1471
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
1472
+ import { join as join4 } from "path";
1473
+ var SKILL_EVENTS_FILE = join4(".harness", "metrics", "skill-events.jsonl");
1464
1474
  async function emitSkillEvent(projectPath, event) {
1465
1475
  try {
1466
- const { emitEvent } = await import("./dist-3HW4LCHE.js");
1467
- await emitEvent(projectPath, event);
1476
+ const metricsDir = join4(projectPath, ".harness", "metrics");
1477
+ mkdirSync2(metricsDir, { recursive: true });
1478
+ const record = { ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1479
+ appendFileSync2(join4(metricsDir, "skill-events.jsonl"), JSON.stringify(record) + "\n");
1468
1480
  } catch {
1469
1481
  }
1470
1482
  }
@@ -1536,9 +1548,10 @@ async function handleRunSkill(input) {
1536
1548
  }
1537
1549
  if (input.path) {
1538
1550
  const projectPath = sanitizePath(input.path);
1539
- const stateFile = path6.join(projectPath, ".harness", "state.json");
1540
- if (fs5.existsSync(stateFile)) {
1541
- const stateContent = fs5.readFileSync(stateFile, "utf-8");
1551
+ const { readHarnessState, isEmptyHarnessState } = await import("./state-events-F44CUUZG.js");
1552
+ const stateResult = await readHarnessState(projectPath);
1553
+ if (stateResult.ok && !isEmptyHarnessState(stateResult.value)) {
1554
+ const stateContent = JSON.stringify(stateResult.value, null, 2);
1542
1555
  content += `
1543
1556
 
1544
1557
  ---
@@ -1733,25 +1746,25 @@ import * as fs10 from "fs";
1733
1746
  import * as path11 from "path";
1734
1747
  async function autoSyncRoadmap(projectPath) {
1735
1748
  try {
1736
- const roadmapFile = path11.join(projectPath, "docs", "roadmap.md");
1737
- if (!fs10.existsSync(roadmapFile)) return;
1738
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-3HW4LCHE.js");
1739
- const raw = fs10.readFileSync(roadmapFile, "utf-8");
1740
- const parseResult = parseRoadmap(raw);
1741
- if (!parseResult.ok) return;
1742
- const roadmap = parseResult.value;
1743
- const syncResult = syncRoadmap({ projectPath, roadmap });
1749
+ if (!roadmapSourceExists(projectPath)) return;
1750
+ const { syncRoadmap, applySyncChanges } = await import("./dist-IPVFYGY2.js");
1751
+ const store = resolveRoadmapStore({ projectRoot: projectPath });
1752
+ const loaded = await store.load();
1753
+ if (!loaded.ok) return;
1754
+ const roadmap = loaded.value;
1755
+ const before = structuredClone(roadmap);
1756
+ const syncResult = await syncRoadmap({ projectPath, roadmap });
1744
1757
  if (!syncResult.ok || syncResult.value.length === 0) {
1745
- await triggerExternalSync(projectPath, roadmapFile);
1758
+ await triggerExternalSync(projectPath);
1746
1759
  return;
1747
1760
  }
1748
1761
  applySyncChanges(roadmap, syncResult.value);
1749
- fs10.writeFileSync(roadmapFile, serializeRoadmap(roadmap), "utf-8");
1750
- await triggerExternalSync(projectPath, roadmapFile);
1762
+ await applyRoadmapDiff(store, before, roadmap);
1763
+ await triggerExternalSync(projectPath);
1751
1764
  } catch {
1752
1765
  }
1753
1766
  }
1754
- async function triggerExternalSync(projectPath, roadmapFile) {
1767
+ async function triggerExternalSync(projectPath) {
1755
1768
  try {
1756
1769
  const trackerConfig = loadTrackerSyncConfig(projectPath);
1757
1770
  if (!trackerConfig) return;
@@ -1765,12 +1778,12 @@ async function triggerExternalSync(projectPath, roadmapFile) {
1765
1778
  console.warn("[roadmap-sync] GITHUB_TOKEN not found \u2014 external sync skipped");
1766
1779
  return;
1767
1780
  }
1768
- const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-3HW4LCHE.js");
1781
+ const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-IPVFYGY2.js");
1769
1782
  const adapter = new GitHubIssuesSyncAdapter({
1770
1783
  token,
1771
1784
  config: trackerConfig
1772
1785
  });
1773
- const result = await fullSync(roadmapFile, adapter, trackerConfig);
1786
+ const result = await fullSync(projectPath, adapter, trackerConfig);
1774
1787
  if (result.errors.length > 0) {
1775
1788
  for (const err2 of result.errors) {
1776
1789
  console.error(
@@ -1817,7 +1830,8 @@ var manageStateDefinition = {
1817
1830
  "task-start",
1818
1831
  "task-complete",
1819
1832
  "phase-start",
1820
- "phase-complete"
1833
+ "phase-complete",
1834
+ "task-transition"
1821
1835
  ],
1822
1836
  description: "Action to perform"
1823
1837
  },
@@ -1856,18 +1870,42 @@ var manageStateDefinition = {
1856
1870
  type: "string",
1857
1871
  enum: ["active", "resolved", "superseded"],
1858
1872
  description: "New status for the entry: active, resolved, or superseded (required for update_entry_status)"
1859
- }
1873
+ },
1874
+ taskId: { type: "string", description: "Task id (required for task-transition)" },
1875
+ toLane: {
1876
+ type: "string",
1877
+ enum: ["planned", "claimed", "in_progress", "in_review", "done", "blocked", "canceled"],
1878
+ description: "Target lane (required for task-transition)"
1879
+ },
1880
+ dependsOn: {
1881
+ type: "array",
1882
+ items: { type: "string" },
1883
+ description: "Dependency task ids; when set, the task is registered before transitioning"
1884
+ },
1885
+ evidence: {
1886
+ type: "array",
1887
+ items: { type: "string" },
1888
+ description: "PR/commit/test refs (required to enter done)"
1889
+ },
1890
+ force: {
1891
+ type: "boolean",
1892
+ description: "Force an off-table transition (requires actor+reason)"
1893
+ },
1894
+ actor: { type: "string", description: "Actor for a forced transition" },
1895
+ reason: { type: "string", description: "Reason for a forced transition" }
1860
1896
  },
1861
1897
  required: ["path", "action"]
1862
1898
  }
1863
1899
  };
1864
1900
  async function handleShow(projectPath, input) {
1865
- const { loadState } = await import("./dist-3HW4LCHE.js");
1866
- return resultToMcpResponse(await loadState(projectPath, input.stream, input.session));
1901
+ const { readHarnessState } = await import("./state-events-F44CUUZG.js");
1902
+ return resultToMcpResponse(
1903
+ await readHarnessState(projectPath, { stream: input.stream, session: input.session })
1904
+ );
1867
1905
  }
1868
1906
  async function handleLearn(projectPath, input) {
1869
1907
  if (!input.learning) return mcpError("Error: learning is required for learn action");
1870
- const { appendLearning } = await import("./dist-3HW4LCHE.js");
1908
+ const { appendLearning } = await import("./dist-IPVFYGY2.js");
1871
1909
  const result = await appendLearning(
1872
1910
  projectPath,
1873
1911
  input.learning,
@@ -1882,7 +1920,7 @@ async function handleLearn(projectPath, input) {
1882
1920
  async function handleFailure(projectPath, input) {
1883
1921
  if (!input.description) return mcpError("Error: description is required for failure action");
1884
1922
  if (!input.failureType) return mcpError("Error: failureType is required for failure action");
1885
- const { appendFailure } = await import("./dist-3HW4LCHE.js");
1923
+ const { appendFailure } = await import("./dist-IPVFYGY2.js");
1886
1924
  const result = await appendFailure(
1887
1925
  projectPath,
1888
1926
  input.description,
@@ -1901,19 +1939,22 @@ async function handleFailure(projectPath, input) {
1901
1939
  return resultToMcpResponse(Ok({ recorded: true }));
1902
1940
  }
1903
1941
  async function handleArchive(projectPath, input) {
1904
- const { archiveFailures } = await import("./dist-3HW4LCHE.js");
1942
+ const { archiveFailures } = await import("./dist-IPVFYGY2.js");
1905
1943
  const result = await archiveFailures(projectPath, input.stream, input.session);
1906
1944
  if (!result.ok) return resultToMcpResponse(result);
1907
1945
  return resultToMcpResponse(Ok({ archived: true }));
1908
1946
  }
1909
1947
  async function handleReset(projectPath, input) {
1910
- const { saveState, DEFAULT_STATE } = await import("./dist-3HW4LCHE.js");
1911
- const result = await saveState(projectPath, { ...DEFAULT_STATE }, input.stream, input.session);
1948
+ const { eventSourcing } = await import("./dist-IPVFYGY2.js");
1949
+ const result = await eventSourcing.resetEventLog(projectPath, {
1950
+ stream: input.stream,
1951
+ session: input.session
1952
+ });
1912
1953
  if (!result.ok) return resultToMcpResponse(result);
1913
1954
  return resultToMcpResponse(Ok({ reset: true }));
1914
1955
  }
1915
1956
  async function handleGate(projectPath, input) {
1916
- const { runMechanicalGate } = await import("./dist-3HW4LCHE.js");
1957
+ const { runMechanicalGate } = await import("./dist-IPVFYGY2.js");
1917
1958
  const result = await runMechanicalGate(projectPath);
1918
1959
  if (result.ok) {
1919
1960
  await emitSkillEvent(projectPath, {
@@ -1933,7 +1974,7 @@ async function handleGate(projectPath, input) {
1933
1974
  }
1934
1975
  async function handleSaveHandoff(projectPath, input) {
1935
1976
  if (!input.handoff) return mcpError("Error: handoff is required for save-handoff action");
1936
- const { saveHandoff } = await import("./dist-3HW4LCHE.js");
1977
+ const { saveHandoff } = await import("./dist-IPVFYGY2.js");
1937
1978
  const result = await saveHandoff(
1938
1979
  projectPath,
1939
1980
  input.handoff,
@@ -1955,7 +1996,7 @@ async function handleSaveHandoff(projectPath, input) {
1955
1996
  return resultToMcpResponse(Ok({ saved: true }));
1956
1997
  }
1957
1998
  async function handleLoadHandoff(projectPath, input) {
1958
- const { loadHandoff } = await import("./dist-3HW4LCHE.js");
1999
+ const { loadHandoff } = await import("./dist-IPVFYGY2.js");
1959
2000
  return resultToMcpResponse(await loadHandoff(projectPath, input.stream, input.session));
1960
2001
  }
1961
2002
  async function handleAppendEntry(projectPath, input) {
@@ -1963,7 +2004,7 @@ async function handleAppendEntry(projectPath, input) {
1963
2004
  if (!input.authorSkill) return mcpError("Error: authorSkill is required for append_entry action");
1964
2005
  if (!input.content) return mcpError("Error: content is required for append_entry action");
1965
2006
  if (input.session) {
1966
- const { appendSessionEntry } = await import("./dist-3HW4LCHE.js");
2007
+ const { appendSessionEntry } = await import("./dist-IPVFYGY2.js");
1967
2008
  const result = await appendSessionEntry(
1968
2009
  projectPath,
1969
2010
  input.session,
@@ -1976,16 +2017,17 @@ async function handleAppendEntry(projectPath, input) {
1976
2017
  if (input.section !== "decisions") {
1977
2018
  return mcpError("Error: session is required for non-decisions sections");
1978
2019
  }
1979
- const { loadState, saveState } = await import("./dist-3HW4LCHE.js");
1980
- const lr = await loadState(projectPath, input.stream);
1981
- if (!lr.ok) return resultToMcpResponse(lr);
1982
- lr.value.decisions.push({
1983
- date: (/* @__PURE__ */ new Date()).toISOString(),
1984
- decision: input.content,
1985
- context: input.authorSkill
1986
- });
1987
- const sr = await saveState(projectPath, lr.value, input.stream);
1988
- if (!sr.ok) return resultToMcpResponse(sr);
2020
+ const { randomUUID: randomUUID10 } = await import("crypto");
2021
+ const { emitCoreEvent } = await import("./state-events-F44CUUZG.js");
2022
+ const r = await emitCoreEvent(
2023
+ projectPath,
2024
+ {
2025
+ type: "decision_recorded",
2026
+ payload: { id: randomUUID10(), text: input.content, context: input.authorSkill }
2027
+ },
2028
+ { stream: input.stream }
2029
+ );
2030
+ if (!r.ok) return resultToMcpResponse(r);
1989
2031
  return resultToMcpResponse(Ok({ appended: true, target: "global-state" }));
1990
2032
  }
1991
2033
  async function handleUpdateEntryStatus(projectPath, input) {
@@ -1994,7 +2036,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
1994
2036
  if (!input.entryId) return mcpError("Error: entryId is required for update_entry_status action");
1995
2037
  if (!input.newStatus)
1996
2038
  return mcpError("Error: newStatus is required for update_entry_status action");
1997
- const { updateSessionEntryStatus } = await import("./dist-3HW4LCHE.js");
2039
+ const { updateSessionEntryStatus } = await import("./dist-IPVFYGY2.js");
1998
2040
  const result = await updateSessionEntryStatus(
1999
2041
  projectPath,
2000
2042
  input.session,
@@ -2002,12 +2044,21 @@ async function handleUpdateEntryStatus(projectPath, input) {
2002
2044
  input.entryId,
2003
2045
  input.newStatus
2004
2046
  );
2047
+ if (result.ok && input.section === "decisions" && input.newStatus === "resolved") {
2048
+ try {
2049
+ const { emitApprovalResolved } = await import("./state-events-F44CUUZG.js");
2050
+ await emitApprovalResolved(projectPath, input.entryId, input.newStatus, {
2051
+ stream: input.stream
2052
+ });
2053
+ } catch {
2054
+ }
2055
+ }
2005
2056
  return resultToMcpResponse(result);
2006
2057
  }
2007
2058
  async function handleReadSection(projectPath, input) {
2008
2059
  if (!input.session) return mcpError("Error: session is required for read_section action");
2009
2060
  if (!input.section) return mcpError("Error: section is required for read_section action");
2010
- const { readSessionSection } = await import("./dist-3HW4LCHE.js");
2061
+ const { readSessionSection } = await import("./dist-IPVFYGY2.js");
2011
2062
  const result = await readSessionSection(
2012
2063
  projectPath,
2013
2064
  input.session,
@@ -2017,13 +2068,13 @@ async function handleReadSection(projectPath, input) {
2017
2068
  }
2018
2069
  async function handleReadSections(projectPath, input) {
2019
2070
  if (!input.session) return mcpError("Error: session is required for read_sections action");
2020
- const { readSessionSections } = await import("./dist-3HW4LCHE.js");
2071
+ const { readSessionSections } = await import("./dist-IPVFYGY2.js");
2021
2072
  const result = await readSessionSections(projectPath, input.session);
2022
2073
  return resultToMcpResponse(result);
2023
2074
  }
2024
2075
  async function handleArchiveSession(projectPath, input) {
2025
2076
  if (!input.session) return mcpError("Error: session is required for archive_session action");
2026
- const { archiveSession } = await import("./dist-3HW4LCHE.js");
2077
+ const { archiveSession } = await import("./dist-IPVFYGY2.js");
2027
2078
  const { buildArchiveHooks } = await import("@harness-engineering/orchestrator");
2028
2079
  const hooks = buildArchiveHooks({ projectPath });
2029
2080
  const result = await archiveSession(projectPath, input.session, { hooks });
@@ -2063,6 +2114,30 @@ async function handlePhaseComplete(projectPath, input) {
2063
2114
  }
2064
2115
  return resultToMcpResponse(Ok({ synced: true, trigger: "phase-complete" }));
2065
2116
  }
2117
+ async function handleTaskTransition(projectPath, input) {
2118
+ if (!input.taskId) return mcpError("Error: taskId is required for task-transition action");
2119
+ if (!input.toLane) return mcpError("Error: toLane is required for task-transition action");
2120
+ const { eventSourcing } = await import("./dist-IPVFYGY2.js");
2121
+ const scope = { stream: input.stream, session: input.session };
2122
+ if (input.dependsOn) {
2123
+ const reg = await eventSourcing.registerTask(projectPath, input.taskId, input.dependsOn, scope);
2124
+ if (!reg.ok) return resultToMcpResponse(reg);
2125
+ }
2126
+ const result = await eventSourcing.transitionLane(
2127
+ projectPath,
2128
+ input.taskId,
2129
+ input.toLane,
2130
+ {
2131
+ ...input.evidence !== void 0 ? { evidence: input.evidence } : {},
2132
+ ...input.force !== void 0 ? { force: input.force } : {},
2133
+ ...input.actor !== void 0 ? { actor: input.actor } : {},
2134
+ ...input.reason !== void 0 ? { reason: input.reason } : {}
2135
+ },
2136
+ scope
2137
+ );
2138
+ if (!result.ok) return resultToMcpResponse(result);
2139
+ return resultToMcpResponse(Ok({ taskId: input.taskId, lane: input.toLane }));
2140
+ }
2066
2141
  var ACTION_HANDLERS = {
2067
2142
  show: handleShow,
2068
2143
  learn: handleLearn,
@@ -2080,7 +2155,8 @@ var ACTION_HANDLERS = {
2080
2155
  "task-start": handleTaskStart,
2081
2156
  "task-complete": handleTaskComplete,
2082
2157
  "phase-start": handlePhaseStart,
2083
- "phase-complete": handlePhaseComplete
2158
+ "phase-complete": handlePhaseComplete,
2159
+ "task-transition": handleTaskTransition
2084
2160
  };
2085
2161
  async function handleManageState(input) {
2086
2162
  try {
@@ -2105,7 +2181,7 @@ var listStreamsDefinition = {
2105
2181
  };
2106
2182
  async function handleListStreams(input) {
2107
2183
  try {
2108
- const { listStreams, loadStreamIndex } = await import("./dist-3HW4LCHE.js");
2184
+ const { listStreams, loadStreamIndex } = await import("./dist-IPVFYGY2.js");
2109
2185
  const projectPath = sanitizePath(input.path);
2110
2186
  const indexResult = await loadStreamIndex(projectPath);
2111
2187
  const streamsResult = await listStreams(projectPath);
@@ -2143,7 +2219,7 @@ var checkPhaseGateDefinition = {
2143
2219
  };
2144
2220
  async function handleCheckPhaseGate(input) {
2145
2221
  try {
2146
- const { runCheckPhaseGate } = await import("./check-phase-gate-REG3HWYC.js");
2222
+ const { runCheckPhaseGate } = await import("./check-phase-gate-46U5AAXE.js");
2147
2223
  const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
2148
2224
  if (result.ok) {
2149
2225
  return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
@@ -2199,7 +2275,7 @@ async function handleValidateCrossCheck(input) {
2199
2275
  };
2200
2276
  }
2201
2277
  try {
2202
- const { runCrossCheck } = await import("./validate-cross-check-4ITA72DF.js");
2278
+ const { runCrossCheck } = await import("./validate-cross-check-M2ZM6RFY.js");
2203
2279
  const specsDir = path12.resolve(projectPath, input.specsDir ?? "docs/specs");
2204
2280
  if (!specsDir.startsWith(projectPath)) {
2205
2281
  return {
@@ -2708,11 +2784,11 @@ function resolveOutputDir(platform, opts) {
2708
2784
  }
2709
2785
  async function confirmDeletion(files) {
2710
2786
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2711
- return new Promise((resolve9) => {
2787
+ return new Promise((resolve11) => {
2712
2788
  rl.question(`
2713
2789
  Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
2714
2790
  rl.close();
2715
- resolve9(answer.toLowerCase() === "y");
2791
+ resolve11(answer.toLowerCase() === "y");
2716
2792
  });
2717
2793
  });
2718
2794
  }
@@ -3022,9 +3098,10 @@ async function handleGenerateSlashCommands(input) {
3022
3098
  // src/mcp/resources/state.ts
3023
3099
  async function getStateResource(projectRoot) {
3024
3100
  try {
3025
- const { loadState, migrateToStreams } = await import("./dist-3HW4LCHE.js");
3101
+ const { migrateToStreams } = await import("./dist-IPVFYGY2.js");
3102
+ const { readHarnessState } = await import("./state-events-F44CUUZG.js");
3026
3103
  await migrateToStreams(projectRoot);
3027
- const result = await loadState(projectRoot);
3104
+ const result = await readHarnessState(projectRoot);
3028
3105
  if (result.ok) {
3029
3106
  return JSON.stringify(result.value, null, 2);
3030
3107
  }
@@ -3633,8 +3710,8 @@ async function handleIngestSource(input) {
3633
3710
  const projectPath = sanitizePath(input.path);
3634
3711
  const graphDir = path16.join(projectPath, ".harness", "graph");
3635
3712
  const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-OWHMNL4W.js");
3636
- const fs50 = await import("fs/promises");
3637
- await fs50.mkdir(graphDir, { recursive: true });
3713
+ const fs53 = await import("fs/promises");
3714
+ await fs53.mkdir(graphDir, { recursive: true });
3638
3715
  const store = new GraphStore();
3639
3716
  await store.load(graphDir);
3640
3717
  const results = [];
@@ -4042,7 +4119,7 @@ var generateAgentDefinitionsDefinition = {
4042
4119
  }
4043
4120
  };
4044
4121
  async function handleGenerateAgentDefinitions(input) {
4045
- const { generateAgentDefinitions } = await import("./generate-agent-definitions-S2FRZF4Y.js");
4122
+ const { generateAgentDefinitions } = await import("./generate-agent-definitions-CC6KED3E.js");
4046
4123
  const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
4047
4124
  const results = generateAgentDefinitions({
4048
4125
  platforms: [...platforms],
@@ -4078,7 +4155,7 @@ async function handleManageRoadmapFileLess(input, client) {
4078
4155
  case "sync":
4079
4156
  return handleSync();
4080
4157
  case "groom":
4081
- return err("Error: groom is only supported in file-based roadmap mode (docs/roadmap.md).");
4158
+ return err("Error: groom is only supported in file-based roadmap mode.");
4082
4159
  default:
4083
4160
  return err(`Error: unknown action`);
4084
4161
  }
@@ -4278,7 +4355,7 @@ function renderRoadmap(features) {
4278
4355
  // src/mcp/tools/roadmap.ts
4279
4356
  var manageRoadmapDefinition = {
4280
4357
  name: "manage_roadmap",
4281
- description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes docs/roadmap.md. The "promote" action transitions an existing row toward planned (backlog\u2192planned) and links its spec atomically \u2014 creating a new planned row under the "Intake" lane if the feature does not exist \u2014 returning a structured RoadmapPromoteResult envelope. The "groom" action tidies the roadmap: it demotes unactionable planned rows (no spec & no plan) to backlog and moves completed features into docs/roadmap-archive.md, returning the list of changes.',
4358
+ description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes the project roadmap (sharded or single-file). The "promote" action transitions an existing row toward planned (backlog\u2192planned) and links its spec atomically \u2014 creating a new planned row under the "Intake" lane if the feature does not exist \u2014 returning a structured RoadmapPromoteResult envelope. The "groom" action tidies the roadmap: it demotes unactionable planned rows (no spec & no plan) to backlog and moves completed features into docs/roadmap-archive.md, returning the list of changes.',
4282
4359
  inputSchema: {
4283
4360
  type: "object",
4284
4361
  properties: {
@@ -4339,22 +4416,12 @@ var manageRoadmapDefinition = {
4339
4416
  required: ["path", "action"]
4340
4417
  }
4341
4418
  };
4342
- function roadmapPath(projectRoot) {
4343
- return path18.join(projectRoot, "docs", "roadmap.md");
4344
- }
4345
- function readRoadmapFile(projectRoot) {
4346
- const filePath = roadmapPath(projectRoot);
4347
- try {
4348
- return fs15.readFileSync(filePath, "utf-8");
4349
- } catch {
4350
- return null;
4351
- }
4419
+ async function loadRoadmap(projectRoot) {
4420
+ return resolveRoadmapStore({ projectRoot }).load();
4352
4421
  }
4353
- function writeRoadmapFile(projectRoot, content) {
4354
- const filePath = roadmapPath(projectRoot);
4355
- const dir = path18.dirname(filePath);
4356
- fs15.mkdirSync(dir, { recursive: true });
4357
- fs15.writeFileSync(filePath, content, "utf-8");
4422
+ async function persistRoadmap(projectRoot, before, after) {
4423
+ const store = resolveRoadmapStore({ projectRoot });
4424
+ return applyRoadmapDiff(store, before, after);
4358
4425
  }
4359
4426
  function archiveFilePath(projectRoot) {
4360
4427
  return path18.join(projectRoot, "docs", "roadmap-archive.md");
@@ -4393,17 +4460,16 @@ function roadmapNotFoundError() {
4393
4460
  content: [
4394
4461
  {
4395
4462
  type: "text",
4396
- text: "Error: docs/roadmap.md not found. Create a roadmap first."
4463
+ text: "Error: roadmap not found. Create a roadmap first."
4397
4464
  }
4398
4465
  ],
4399
4466
  isError: true
4400
4467
  };
4401
4468
  }
4402
- function handleShow3(projectPath, input, deps) {
4403
- const { parseRoadmap, Ok: Ok2 } = deps;
4404
- const raw = readRoadmapFile(projectPath);
4405
- if (raw === null) return roadmapNotFoundError();
4406
- const result = parseRoadmap(raw);
4469
+ async function handleShow3(projectPath, input, deps) {
4470
+ const { Ok: Ok2 } = deps;
4471
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4472
+ const result = await loadRoadmap(projectPath);
4407
4473
  if (!result.ok) return resultToMcpResponse(result);
4408
4474
  let roadmap = result.value;
4409
4475
  if (input.milestone) {
@@ -4459,15 +4525,15 @@ function buildFeatureFromInput(input) {
4459
4525
  updatedAt: null
4460
4526
  };
4461
4527
  }
4462
- function handleAdd2(projectPath, input, deps) {
4463
- const { parseRoadmap, serializeRoadmap, Ok: Ok2 } = deps;
4528
+ async function handleAdd2(projectPath, input, deps) {
4529
+ const { Ok: Ok2 } = deps;
4464
4530
  const validationError = validateAddFields(input);
4465
4531
  if (validationError) return validationError;
4466
- const raw = readRoadmapFile(projectPath);
4467
- if (raw === null) return roadmapNotFoundError();
4468
- const result = parseRoadmap(raw);
4532
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4533
+ const result = await loadRoadmap(projectPath);
4469
4534
  if (!result.ok) return resultToMcpResponse(result);
4470
4535
  const roadmap = result.value;
4536
+ const before = structuredClone(roadmap);
4471
4537
  const milestone = roadmap.milestones.find(
4472
4538
  (m) => m.name.toLowerCase() === input.milestone.toLowerCase()
4473
4539
  );
@@ -4479,7 +4545,8 @@ function handleAdd2(projectPath, input, deps) {
4479
4545
  }
4480
4546
  milestone.features.push(buildFeatureFromInput(input));
4481
4547
  roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
4482
- writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
4548
+ const persisted = await persistRoadmap(projectPath, before, roadmap);
4549
+ if (!persisted.ok) return resultToMcpResponse(persisted);
4483
4550
  return resultToMcpResponse(Ok2(roadmap));
4484
4551
  }
4485
4552
  function claimRefusedResponse(featureName, roadmap, currentAssignee) {
@@ -4494,19 +4561,19 @@ function claimRefusedResponse(featureName, roadmap, currentAssignee) {
4494
4561
  isError: true
4495
4562
  };
4496
4563
  }
4497
- function handleUpdate2(projectPath, input, deps) {
4498
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges, Ok: Ok2 } = deps;
4564
+ async function handleUpdate2(projectPath, input, deps) {
4565
+ const { syncRoadmap, applySyncChanges, Ok: Ok2 } = deps;
4499
4566
  if (!input.feature) {
4500
4567
  return {
4501
4568
  content: [{ type: "text", text: "Error: feature is required for update action" }],
4502
4569
  isError: true
4503
4570
  };
4504
4571
  }
4505
- const raw = readRoadmapFile(projectPath);
4506
- if (raw === null) return roadmapNotFoundError();
4507
- const result = parseRoadmap(raw);
4572
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4573
+ const result = await loadRoadmap(projectPath);
4508
4574
  if (!result.ok) return resultToMcpResponse(result);
4509
4575
  const roadmap = result.value;
4576
+ const before = structuredClone(roadmap);
4510
4577
  let found = false;
4511
4578
  for (const m of roadmap.milestones) {
4512
4579
  const feature = m.features.find((f) => f.name.toLowerCase() === input.feature.toLowerCase());
@@ -4537,27 +4604,31 @@ function handleUpdate2(projectPath, input, deps) {
4537
4604
  isError: true
4538
4605
  };
4539
4606
  }
4540
- const cascade = syncRoadmap({ projectPath, roadmap });
4541
- if (cascade.ok && cascade.value.length > 0) {
4542
- applySyncChanges(roadmap, cascade.value);
4607
+ const cascade = await syncRoadmap({ projectPath, roadmap });
4608
+ if (cascade.ok) {
4609
+ const unblocking = cascade.value.filter((change) => change.to !== "blocked");
4610
+ if (unblocking.length > 0) {
4611
+ applySyncChanges(roadmap, unblocking);
4612
+ }
4543
4613
  }
4544
4614
  roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
4545
- writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
4615
+ const persisted = await persistRoadmap(projectPath, before, roadmap);
4616
+ if (!persisted.ok) return resultToMcpResponse(persisted);
4546
4617
  return resultToMcpResponse(Ok2(roadmap));
4547
4618
  }
4548
- function handleRemove2(projectPath, input, deps) {
4549
- const { parseRoadmap, serializeRoadmap, Ok: Ok2 } = deps;
4619
+ async function handleRemove2(projectPath, input, deps) {
4620
+ const { Ok: Ok2 } = deps;
4550
4621
  if (!input.feature) {
4551
4622
  return {
4552
4623
  content: [{ type: "text", text: "Error: feature is required for remove action" }],
4553
4624
  isError: true
4554
4625
  };
4555
4626
  }
4556
- const raw = readRoadmapFile(projectPath);
4557
- if (raw === null) return roadmapNotFoundError();
4558
- const result = parseRoadmap(raw);
4627
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4628
+ const result = await loadRoadmap(projectPath);
4559
4629
  if (!result.ok) return resultToMcpResponse(result);
4560
4630
  const roadmap = result.value;
4631
+ const before = structuredClone(roadmap);
4561
4632
  let found = false;
4562
4633
  for (const m of roadmap.milestones) {
4563
4634
  const idx = m.features.findIndex((f) => f.name.toLowerCase() === input.feature.toLowerCase());
@@ -4574,7 +4645,8 @@ function handleRemove2(projectPath, input, deps) {
4574
4645
  };
4575
4646
  }
4576
4647
  roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
4577
- writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
4648
+ const persisted = await persistRoadmap(projectPath, before, roadmap);
4649
+ if (!persisted.ok) return resultToMcpResponse(persisted);
4578
4650
  return resultToMcpResponse(Ok2(roadmap));
4579
4651
  }
4580
4652
  function promoteEnvelopeResponse(envelope2) {
@@ -4583,8 +4655,8 @@ function promoteEnvelopeResponse(envelope2) {
4583
4655
  isError: envelope2.ok === false
4584
4656
  };
4585
4657
  }
4586
- function handlePromote2(projectPath, input, deps) {
4587
- const { parseRoadmap, serializeRoadmap, promoteFeature } = deps;
4658
+ async function handlePromote2(projectPath, input, deps) {
4659
+ const { promoteFeature } = deps;
4588
4660
  if (!input.feature) {
4589
4661
  return {
4590
4662
  content: [{ type: "text", text: "Error: feature is required for promote action" }],
@@ -4597,9 +4669,8 @@ function handlePromote2(projectPath, input, deps) {
4597
4669
  isError: true
4598
4670
  };
4599
4671
  }
4600
- const raw = readRoadmapFile(projectPath);
4601
- if (raw === null) return roadmapNotFoundError();
4602
- const result = parseRoadmap(raw);
4672
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4673
+ const result = await loadRoadmap(projectPath);
4603
4674
  if (!result.ok) {
4604
4675
  return promoteEnvelopeResponse({
4605
4676
  ok: false,
@@ -4608,6 +4679,7 @@ function handlePromote2(projectPath, input, deps) {
4608
4679
  detail: result.error.message
4609
4680
  });
4610
4681
  }
4682
+ const before = structuredClone(result.value);
4611
4683
  const { result: envelope2, nextRoadmap } = promoteFeature(result.value, {
4612
4684
  feature: input.feature,
4613
4685
  spec: input.spec,
@@ -4615,30 +4687,28 @@ function handlePromote2(projectPath, input, deps) {
4615
4687
  });
4616
4688
  if (envelope2.ok && envelope2.transitioned !== "noop") {
4617
4689
  nextRoadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
4618
- try {
4619
- writeRoadmapFile(projectPath, serializeRoadmap(nextRoadmap));
4620
- } catch (error) {
4690
+ const persisted = await persistRoadmap(projectPath, before, nextRoadmap);
4691
+ if (!persisted.ok) {
4621
4692
  return promoteEnvelopeResponse({
4622
4693
  ok: false,
4623
4694
  reason: "write-failed",
4624
4695
  feature: input.feature,
4625
- detail: error instanceof Error ? error.message : String(error)
4696
+ detail: persisted.error.message
4626
4697
  });
4627
4698
  }
4628
4699
  }
4629
4700
  return promoteEnvelopeResponse(envelope2);
4630
4701
  }
4631
- function handleQuery2(projectPath, input, deps) {
4632
- const { parseRoadmap, Ok: Ok2 } = deps;
4702
+ async function handleQuery2(projectPath, input, deps) {
4703
+ const { Ok: Ok2 } = deps;
4633
4704
  if (!input.filter) {
4634
4705
  return {
4635
4706
  content: [{ type: "text", text: "Error: filter is required for query action" }],
4636
4707
  isError: true
4637
4708
  };
4638
4709
  }
4639
- const raw = readRoadmapFile(projectPath);
4640
- if (raw === null) return roadmapNotFoundError();
4641
- const result = parseRoadmap(raw);
4710
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4711
+ const result = await loadRoadmap(projectPath);
4642
4712
  if (!result.ok) return resultToMcpResponse(result);
4643
4713
  const roadmap = result.value;
4644
4714
  const allFeatures = roadmap.milestones.flatMap(
@@ -4654,14 +4724,14 @@ function handleQuery2(projectPath, input, deps) {
4654
4724
  }
4655
4725
  return resultToMcpResponse(Ok2(filtered));
4656
4726
  }
4657
- function handleSync2(projectPath, input, deps) {
4658
- const { parseRoadmap, serializeRoadmap, syncRoadmap, Ok: Ok2 } = deps;
4659
- const raw = readRoadmapFile(projectPath);
4660
- if (raw === null) return roadmapNotFoundError();
4661
- const result = parseRoadmap(raw);
4727
+ async function handleSync2(projectPath, input, deps) {
4728
+ const { syncRoadmap, Ok: Ok2 } = deps;
4729
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4730
+ const result = await loadRoadmap(projectPath);
4662
4731
  if (!result.ok) return resultToMcpResponse(result);
4663
4732
  const roadmap = result.value;
4664
- const syncResult = syncRoadmap({
4733
+ const before = structuredClone(roadmap);
4734
+ const syncResult = await syncRoadmap({
4665
4735
  projectPath,
4666
4736
  roadmap,
4667
4737
  forceSync: input.force_sync ?? false
@@ -4673,17 +4743,18 @@ function handleSync2(projectPath, input, deps) {
4673
4743
  }
4674
4744
  if (input.apply) {
4675
4745
  deps.applySyncChanges(roadmap, changes);
4676
- writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
4746
+ const persisted = await persistRoadmap(projectPath, before, roadmap);
4747
+ if (!persisted.ok) return resultToMcpResponse(persisted);
4677
4748
  return resultToMcpResponse(Ok2({ changes, applied: true, roadmap }));
4678
4749
  }
4679
4750
  return resultToMcpResponse(Ok2({ changes, applied: false }));
4680
4751
  }
4681
- function handleGroom(projectPath, _input, deps) {
4682
- const { parseRoadmap, serializeRoadmap, groomRoadmap, Ok: Ok2 } = deps;
4683
- const raw = readRoadmapFile(projectPath);
4684
- if (raw === null) return roadmapNotFoundError();
4685
- const result = parseRoadmap(raw);
4752
+ async function handleGroom(projectPath, _input, deps) {
4753
+ const { groomRoadmap, Ok: Ok2 } = deps;
4754
+ if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
4755
+ const result = await loadRoadmap(projectPath);
4686
4756
  if (!result.ok) return resultToMcpResponse(result);
4757
+ const before = structuredClone(result.value);
4687
4758
  const { roadmap: groomed, archived, changes } = groomRoadmap(result.value);
4688
4759
  if (changes.length === 0) {
4689
4760
  return resultToMcpResponse(
@@ -4692,7 +4763,8 @@ function handleGroom(projectPath, _input, deps) {
4692
4763
  }
4693
4764
  appendToArchive(projectPath, archived, groomed.frontmatter.project, deps);
4694
4765
  groomed.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
4695
- writeRoadmapFile(projectPath, serializeRoadmap(groomed));
4766
+ const persisted = await persistRoadmap(projectPath, before, groomed);
4767
+ if (!persisted.ok) return resultToMcpResponse(persisted);
4696
4768
  const demoted = changes.filter((c) => c.kind === "demoted").length;
4697
4769
  return resultToMcpResponse(
4698
4770
  Ok2({
@@ -4704,7 +4776,7 @@ function handleGroom(projectPath, _input, deps) {
4704
4776
  );
4705
4777
  }
4706
4778
  var readOnlyActions = /* @__PURE__ */ new Set(["show", "query"]);
4707
- function dispatchAction(action, projectPath, input, deps) {
4779
+ async function dispatchAction(action, projectPath, input, deps) {
4708
4780
  switch (action) {
4709
4781
  case "show":
4710
4782
  return handleShow3(projectPath, input, deps);
@@ -4764,7 +4836,7 @@ async function handleManageRoadmap(input) {
4764
4836
  release,
4765
4837
  promoteFeature,
4766
4838
  groomRoadmap
4767
- } = await import("./dist-3HW4LCHE.js");
4839
+ } = await import("./dist-IPVFYGY2.js");
4768
4840
  const { Ok: Ok2 } = await import("./dist-ICW4QUEW.js");
4769
4841
  const projectPath = projectPathPre;
4770
4842
  const deps = {
@@ -4780,9 +4852,9 @@ async function handleManageRoadmap(input) {
4780
4852
  groomRoadmap,
4781
4853
  Ok: Ok2
4782
4854
  };
4783
- const response = dispatchAction(input.action, projectPath, input, deps);
4855
+ const response = await dispatchAction(input.action, projectPath, input, deps);
4784
4856
  if (shouldTriggerExternalSync(input, response)) {
4785
- await triggerExternalSync(projectPath, roadmapPath(projectPath)).catch(() => {
4857
+ await triggerExternalSync(projectPath).catch(() => {
4786
4858
  });
4787
4859
  }
4788
4860
  return response;
@@ -5208,7 +5280,7 @@ async function handleTransition(validInput, projectPath, id) {
5208
5280
  const transition = transitionResult.data;
5209
5281
  const prompt = renderTransition(transition);
5210
5282
  try {
5211
- const { saveHandoff } = await import("./dist-3HW4LCHE.js");
5283
+ const { saveHandoff } = await import("./dist-IPVFYGY2.js");
5212
5284
  await saveHandoff(
5213
5285
  projectPath,
5214
5286
  {
@@ -5289,17 +5361,17 @@ async function handleEmitInteraction(input) {
5289
5361
  }
5290
5362
  async function recordInteraction(projectPath, id, type, decision, stream) {
5291
5363
  try {
5292
- const { loadState, saveState } = await import("./dist-3HW4LCHE.js");
5293
- const stateResult = await loadState(projectPath, stream);
5294
- if (stateResult.ok) {
5295
- const state = stateResult.value;
5296
- state.decisions.push({
5297
- date: (/* @__PURE__ */ new Date()).toISOString(),
5298
- decision: `[${type}:${id}] ${decision}`,
5299
- context: "pending user response"
5300
- });
5301
- await saveState(projectPath, state, stream);
5302
- }
5364
+ const { emitCoreEvent, emitUserInputCaptured, emitApprovalRequested } = await import("./state-events-F44CUUZG.js");
5365
+ await emitCoreEvent(
5366
+ projectPath,
5367
+ {
5368
+ type: "decision_recorded",
5369
+ payload: { id, text: `[${type}:${id}] ${decision}`, context: "pending user response" }
5370
+ },
5371
+ { stream }
5372
+ );
5373
+ await emitUserInputCaptured(projectPath, decision, id, { stream });
5374
+ await emitApprovalRequested(projectPath, id, type, decision, { stream });
5303
5375
  } catch {
5304
5376
  }
5305
5377
  }
@@ -5450,10 +5522,10 @@ async function handleGatherContext(input) {
5450
5522
  input.include ?? ["state", "learnings", "handoff", "graph", "validation", "businessKnowledge"]
5451
5523
  );
5452
5524
  const errors = [];
5453
- const statePromise = includeSet.has("state") ? import("./dist-3HW4LCHE.js").then(
5454
- (core) => core.loadState(projectPath, void 0, input.session)
5525
+ const statePromise = includeSet.has("state") ? import("./state-events-F44CUUZG.js").then(
5526
+ (m) => m.readHarnessState(projectPath, { session: input.session })
5455
5527
  ) : Promise.resolve(null);
5456
- const learningsPromise = includeSet.has("learnings") ? import("./dist-3HW4LCHE.js").then(
5528
+ const learningsPromise = includeSet.has("learnings") ? import("./dist-IPVFYGY2.js").then(
5457
5529
  (core) => core.loadBudgetedLearnings(projectPath, {
5458
5530
  intent: input.intent,
5459
5531
  tokenBudget: input.learningsBudget ?? 1e3,
@@ -5462,7 +5534,7 @@ async function handleGatherContext(input) {
5462
5534
  ...input.depth !== void 0 && { depth: input.depth }
5463
5535
  })
5464
5536
  ) : Promise.resolve(null);
5465
- const handoffPromise = includeSet.has("handoff") ? import("./dist-3HW4LCHE.js").then(
5537
+ const handoffPromise = includeSet.has("handoff") ? import("./dist-IPVFYGY2.js").then(
5466
5538
  (core) => core.loadHandoff(projectPath, void 0, input.session)
5467
5539
  ) : Promise.resolve(null);
5468
5540
  const graphPromise = includeSet.has("graph") ? (async () => {
@@ -5506,19 +5578,15 @@ async function handleGatherContext(input) {
5506
5578
  context: contextBlocks
5507
5579
  };
5508
5580
  })() : Promise.resolve(null);
5509
- const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-3HW4LCHE.js").then(
5581
+ const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-IPVFYGY2.js").then(
5510
5582
  (core) => core.readSessionSections(projectPath, input.session)
5511
5583
  ) : Promise.resolve(null);
5512
5584
  const shouldIncludeEvents = input.includeEvents !== void 0 ? input.includeEvents : includeSet.has("events") || !!input.session && !input.include;
5513
- const eventsPromise = shouldIncludeEvents ? import("./dist-3HW4LCHE.js").then(async (core) => {
5514
- const result = await core.loadEvents(projectPath, {
5515
- session: input.session
5516
- });
5517
- if (!result.ok) return null;
5518
- return core.formatEventTimeline(result.value);
5519
- }) : Promise.resolve(null);
5585
+ const eventsPromise = shouldIncludeEvents ? import("./state-events-F44CUUZG.js").then(
5586
+ (m) => m.readAuditTimeline(projectPath, { session: input.session })
5587
+ ) : Promise.resolve(null);
5520
5588
  const validationPromise = includeSet.has("validation") ? (async () => {
5521
- const { handleValidateProject: handleValidateProject2 } = await import("./validate-G3LKBOYA.js");
5589
+ const { handleValidateProject: handleValidateProject2 } = await import("./validate-IUAV7MOU.js");
5522
5590
  const result = await handleValidateProject2({ path: projectPath });
5523
5591
  const first = result.content[0];
5524
5592
  return first ? JSON.parse(first.text) : null;
@@ -5621,7 +5689,7 @@ async function handleGatherContext(input) {
5621
5689
  };
5622
5690
  if (input.session) {
5623
5691
  try {
5624
- const core = await import("./dist-3HW4LCHE.js");
5692
+ const core = await import("./dist-IPVFYGY2.js");
5625
5693
  core.updateSessionIndex(
5626
5694
  projectPath,
5627
5695
  input.session,
@@ -5819,7 +5887,7 @@ async function handleReviewChanges(input) {
5819
5887
  }
5820
5888
  }
5821
5889
  async function runQuickReview(projectPath, diff, diffLines, downgraded, offset, limit) {
5822
- const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-U6VJ6AUJ.js");
5890
+ const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-JT5X3DBC.js");
5823
5891
  const result = await handleAnalyzeDiff2({ diff, path: projectPath });
5824
5892
  const firstContent = result.content[0];
5825
5893
  if (!firstContent) throw new Error("Empty analyze_diff response");
@@ -5854,7 +5922,7 @@ function extractFileCount(diffParsed) {
5854
5922
  return files?.length ?? 0;
5855
5923
  }
5856
5924
  async function runStandardReview(projectPath, diff, diffLines, downgraded, offset, limit) {
5857
- const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-U6VJ6AUJ.js");
5925
+ const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-JT5X3DBC.js");
5858
5926
  const [diffResult, reviewResult] = await Promise.all([
5859
5927
  handleAnalyzeDiff2({ diff, path: projectPath }),
5860
5928
  handleCreateSelfReview2({ path: projectPath, diff })
@@ -5889,7 +5957,7 @@ async function runStandardReview(projectPath, diff, diffLines, downgraded, offse
5889
5957
  };
5890
5958
  }
5891
5959
  async function runDeepReview(projectPath, diff, diffLines, _downgraded, offset, limit) {
5892
- const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-WUHG7PHO.js");
5960
+ const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-MOOWXWSQ.js");
5893
5961
  const result = await handleRunCodeReview2({
5894
5962
  path: projectPath,
5895
5963
  diff,
@@ -6184,7 +6252,7 @@ async function handleDetectStaleConstraints(input) {
6184
6252
  ]
6185
6253
  };
6186
6254
  }
6187
- const { detectStaleConstraints } = await import("./dist-3HW4LCHE.js");
6255
+ const { detectStaleConstraints } = await import("./dist-IPVFYGY2.js");
6188
6256
  const result = detectStaleConstraints(
6189
6257
  store,
6190
6258
  windowDays,
@@ -6215,6 +6283,7 @@ async function handleDetectStaleConstraints(input) {
6215
6283
  init_dist2();
6216
6284
 
6217
6285
  // src/skill/health-snapshot.ts
6286
+ init_dist2();
6218
6287
  import { execSync } from "child_process";
6219
6288
  import * as fs16 from "fs";
6220
6289
  import * as path19 from "path";
@@ -6314,10 +6383,10 @@ function countCriticalFindings(securityData) {
6314
6383
  return findings.filter((f) => f.severity === "error").length;
6315
6384
  }
6316
6385
  async function runHealthChecks(projectPath) {
6317
- const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-B6ENHUAU.js");
6318
- const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-5KPP4ID4.js");
6319
- const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-3JBKCGJJ.js");
6320
- const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-JDHTRBGC.js");
6386
+ const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-3KPP2D6M.js");
6387
+ const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-2XYGJGT3.js");
6388
+ const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-EAEKJKRM.js");
6389
+ const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-U76C54W6.js");
6321
6390
  const [assessResult, depsResult, entropyResult, securityResult] = await Promise.all([
6322
6391
  handleAssessProject2({
6323
6392
  path: projectPath,
@@ -6421,11 +6490,12 @@ async function captureHealthSnapshot(projectPath) {
6421
6490
  runGraphMetrics(projectPath)
6422
6491
  ]);
6423
6492
  const signals = deriveSignals(checks, metrics);
6493
+ const reconciledChecks = reconcilePassed(checks, signals);
6424
6494
  const snapshot = {
6425
6495
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
6426
6496
  gitHead,
6427
6497
  projectPath,
6428
- checks,
6498
+ checks: reconciledChecks,
6429
6499
  metrics,
6430
6500
  signals
6431
6501
  };
@@ -6793,17 +6863,7 @@ function recommend(snapshot, skills, options = {}) {
6793
6863
  }
6794
6864
 
6795
6865
  // src/skill/dispatch-engine.ts
6796
- var SIGNAL_CATEGORIES = {
6797
- "circular-deps": "structure",
6798
- "layer-violations": "structure",
6799
- "high-coupling": "structure",
6800
- "dead-code": "quality",
6801
- drift: "quality",
6802
- "doc-gaps": "quality",
6803
- "security-findings": "security",
6804
- "perf-regression": "performance",
6805
- "low-coverage": "coverage"
6806
- };
6866
+ var SIGNAL_CATEGORIES = SIGNAL_CATEGORY_MAP;
6807
6867
  function getSignalCategory(signal) {
6808
6868
  return SIGNAL_CATEGORIES[signal] ?? null;
6809
6869
  }
@@ -7073,7 +7133,7 @@ async function handleDispatchSkills(input) {
7073
7133
  let skillTriggers;
7074
7134
  if (trigger) {
7075
7135
  try {
7076
- const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-3AOQ3ZII.js");
7136
+ const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-52IJJHJC.js");
7077
7137
  const index = loadOrRebuildIndex2("claude-code", projectRoot);
7078
7138
  skillTriggers = /* @__PURE__ */ new Map();
7079
7139
  for (const [name, entry] of Object.entries(index.skills)) {
@@ -7160,7 +7220,7 @@ async function handleGetDecayTrends(input) {
7160
7220
  };
7161
7221
  }
7162
7222
  try {
7163
- const core = await import("./dist-3HW4LCHE.js");
7223
+ const core = await import("./dist-IPVFYGY2.js");
7164
7224
  const { TimelineManager } = core;
7165
7225
  const manager = new TimelineManager(projectPath);
7166
7226
  const timeline = manager.load();
@@ -7295,7 +7355,7 @@ async function handleCodeOutline(input) {
7295
7355
  };
7296
7356
  }
7297
7357
  try {
7298
- const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-3HW4LCHE.js");
7358
+ const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-IPVFYGY2.js");
7299
7359
  const { stat } = await import("fs/promises");
7300
7360
  const stats = await stat(targetPath).catch(() => null);
7301
7361
  if (stats?.isFile()) {
@@ -7314,7 +7374,7 @@ async function handleCodeOutline(input) {
7314
7374
  })
7315
7375
  );
7316
7376
  fileStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
7317
- const { paginate: paginate2 } = await import("./dist-3HW4LCHE.js");
7377
+ const { paginate: paginate2 } = await import("./dist-IPVFYGY2.js");
7318
7378
  const paged = paginate2(fileStats, input.offset ?? 0, input.limit ?? 30);
7319
7379
  const results = [];
7320
7380
  for (const entry of paged.items) {
@@ -7387,7 +7447,7 @@ async function handleCodeSearch(input) {
7387
7447
  };
7388
7448
  }
7389
7449
  try {
7390
- const { searchSymbols } = await import("./dist-3HW4LCHE.js");
7450
+ const { searchSymbols } = await import("./dist-IPVFYGY2.js");
7391
7451
  const result = await searchSymbols(input.query, directory, input.glob);
7392
7452
  const lines = [`Search: "${result.query}" \u2014 ${result.matches.length} matches`];
7393
7453
  for (const match of result.matches) {
@@ -7456,7 +7516,7 @@ async function handleCodeUnfold(input) {
7456
7516
  }
7457
7517
  try {
7458
7518
  if (input.symbol) {
7459
- const { unfoldSymbol } = await import("./dist-3HW4LCHE.js");
7519
+ const { unfoldSymbol } = await import("./dist-IPVFYGY2.js");
7460
7520
  const result = await unfoldSymbol(filePath, input.symbol);
7461
7521
  const header = result.warning ? `${result.file}:${result.startLine}-${result.endLine} ${result.warning}
7462
7522
  ` : `${result.file}:${result.startLine}-${result.endLine}
@@ -7464,7 +7524,7 @@ async function handleCodeUnfold(input) {
7464
7524
  return { content: [{ type: "text", text: header + result.content }] };
7465
7525
  }
7466
7526
  if (input.startLine != null && input.endLine != null) {
7467
- const { unfoldRange } = await import("./dist-3HW4LCHE.js");
7527
+ const { unfoldRange } = await import("./dist-IPVFYGY2.js");
7468
7528
  const result = await unfoldRange(filePath, input.startLine, input.endLine);
7469
7529
  const header = `${result.file}:${result.startLine}-${result.endLine}
7470
7530
  `;
@@ -7649,7 +7709,7 @@ async function handlePredictFailures(input) {
7649
7709
  };
7650
7710
  }
7651
7711
  try {
7652
- const core = await import("./dist-3HW4LCHE.js");
7712
+ const core = await import("./dist-IPVFYGY2.js");
7653
7713
  const { TimelineManager, PredictionEngine, SpecImpactEstimator } = core;
7654
7714
  const manager = new TimelineManager(projectPath);
7655
7715
  const includeRoadmap = input.includeRoadmap !== false;
@@ -7660,7 +7720,7 @@ async function handlePredictFailures(input) {
7660
7720
  };
7661
7721
  if (input.horizon !== void 0) opts.horizon = input.horizon;
7662
7722
  if (input.category) opts.categories = [input.category];
7663
- const result = engine.predict(opts);
7723
+ const result = await engine.predict(opts);
7664
7724
  return {
7665
7725
  content: [
7666
7726
  {
@@ -8758,7 +8818,7 @@ async function handleDetectConstraintEmergence(input) {
8758
8818
  }
8759
8819
  const nodePath = await import("path");
8760
8820
  const historyPath = nodePath.resolve(projectPath, ".harness/arch/violation-history.json");
8761
- const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-3HW4LCHE.js");
8821
+ const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-IPVFYGY2.js");
8762
8822
  const manager = new ViolationHistoryManager(historyPath);
8763
8823
  const history = manager.load();
8764
8824
  const options = { windowWeeks, minOccurrences };
@@ -8846,7 +8906,7 @@ async function handleRunCIChecks(input) {
8846
8906
  };
8847
8907
  }
8848
8908
  try {
8849
- const { runCIChecks } = await import("./dist-3HW4LCHE.js");
8909
+ const { runCIChecks } = await import("./dist-IPVFYGY2.js");
8850
8910
  const skip = input.checks ? VALID_CHECKS.filter((c) => !input.checks.includes(c)) : [];
8851
8911
  const result = await runCIChecks({
8852
8912
  projectRoot: projectPath,
@@ -8908,7 +8968,7 @@ async function handleGenerateBlueprint(input) {
8908
8968
  };
8909
8969
  }
8910
8970
  try {
8911
- const { ProjectScanner } = await import("./dist-3HW4LCHE.js");
8971
+ const { ProjectScanner } = await import("./dist-IPVFYGY2.js");
8912
8972
  const scanner = new ProjectScanner(projectPath);
8913
8973
  const data = await scanner.scan();
8914
8974
  return {
@@ -9107,7 +9167,7 @@ async function handleEmitSkillProposal(input) {
9107
9167
  let createProposal;
9108
9168
  let ProposalConflictError;
9109
9169
  try {
9110
- const core = await import("./dist-3HW4LCHE.js");
9170
+ const core = await import("./dist-IPVFYGY2.js");
9111
9171
  createProposal = core.createProposal;
9112
9172
  ProposalConflictError = core.ProposalConflictError;
9113
9173
  } catch (err2) {
@@ -9150,8 +9210,8 @@ async function handleEmitSkillProposal(input) {
9150
9210
  }
9151
9211
 
9152
9212
  // src/mcp/tools/audit-anatomy.ts
9153
- import * as fs19 from "fs";
9154
- import * as path22 from "path";
9213
+ import * as fs22 from "fs";
9214
+ import * as path24 from "path";
9155
9215
 
9156
9216
  // src/audit/component-anatomy/parsers/ast.ts
9157
9217
  import * as fs18 from "fs";
@@ -9260,6 +9320,10 @@ function findTypeDeclaration(sourceFile, typeName) {
9260
9320
  return null;
9261
9321
  }
9262
9322
 
9323
+ // src/audit/component-anatomy/resolvers/component-type.ts
9324
+ import * as fs20 from "fs";
9325
+ import * as path23 from "path";
9326
+
9263
9327
  // src/audit/component-anatomy/catalog/conventions/button.ts
9264
9328
  var buttonConvention = {
9265
9329
  componentType: "Button",
@@ -9843,6 +9907,65 @@ function lookupConvention(componentType) {
9843
9907
  return conventionByType.get(componentType) ?? null;
9844
9908
  }
9845
9909
 
9910
+ // src/audit/component-anatomy/parsers/jsdoc.ts
9911
+ function extractLeadingJsDoc(source) {
9912
+ const re = /^\s*(?:#![^\n]*\n)?\s*(?:['"]use (?:client|server)['"];?\s*)?\s*\/\*\*([\s\S]*?)\*\//;
9913
+ const match = re.exec(source);
9914
+ if (!match) return null;
9915
+ return match[1].split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
9916
+ }
9917
+ function readJsDocTag(jsdoc, tag) {
9918
+ const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9919
+ const re = new RegExp(`@${escaped}\\b[ \\t]*(.*)$`, "gm");
9920
+ const values = [];
9921
+ for (const m of jsdoc.matchAll(re)) {
9922
+ values.push((m[1] ?? "").trim());
9923
+ }
9924
+ return values;
9925
+ }
9926
+ function readJsDocTagValue(jsdoc, tag) {
9927
+ const [first] = readJsDocTag(jsdoc, tag);
9928
+ return first ? first : null;
9929
+ }
9930
+
9931
+ // src/audit/component-anatomy/parsers/design-registry.ts
9932
+ import * as fs19 from "fs";
9933
+ import * as path22 from "path";
9934
+ function parseComponentRegistry(designMd) {
9935
+ const lines = designMd.split("\n");
9936
+ const headingIdx = lines.findIndex((l) => /^#{1,6}\s+Component Registry\b/i.test(l));
9937
+ if (headingIdx === -1) return [];
9938
+ const entries = [];
9939
+ for (let i = headingIdx + 1; i < lines.length; i++) {
9940
+ const line = lines[i];
9941
+ if (/^#{1,6}\s+/.test(line)) break;
9942
+ const trimmed = line.trim();
9943
+ if (!trimmed.startsWith("|")) continue;
9944
+ const cells = trimmed.slice(1, trimmed.endsWith("|") ? -1 : void 0).split("|").map((c) => c.trim());
9945
+ const [type, file] = cells;
9946
+ if (!type || !file) continue;
9947
+ if (/^type$/i.test(type) || /^:?-{2,}:?$/.test(type)) continue;
9948
+ entries.push({ type, file });
9949
+ }
9950
+ return entries;
9951
+ }
9952
+ function findDesignMd(fromPath) {
9953
+ let dir = path22.dirname(path22.resolve(fromPath));
9954
+ for (; ; ) {
9955
+ let names;
9956
+ try {
9957
+ names = fs19.readdirSync(dir);
9958
+ } catch {
9959
+ names = [];
9960
+ }
9961
+ const hit = names.find((n) => n.toLowerCase() === "design.md");
9962
+ if (hit) return path22.join(dir, hit);
9963
+ const parent = path22.dirname(dir);
9964
+ if (parent === dir) return null;
9965
+ dir = parent;
9966
+ }
9967
+ }
9968
+
9846
9969
  // src/audit/component-anatomy/resolvers/component-type.ts
9847
9970
  var componentTypeSet = new Set(getCatalogTypes());
9848
9971
  function resolveComponentType(filePath, fileContents) {
@@ -9852,11 +9975,31 @@ function resolveComponentType(filePath, fileContents) {
9852
9975
  if (registryType !== null) return registryType;
9853
9976
  return resolveFromExportName(fileContents);
9854
9977
  }
9855
- function resolveFromJSDoc(_filePath, _fileContents) {
9856
- return null;
9857
- }
9858
- function resolveFromDesignRegistry(_filePath) {
9859
- return null;
9978
+ function resolveFromJSDoc(_filePath, fileContents) {
9979
+ const jsdoc = extractLeadingJsDoc(fileContents);
9980
+ if (jsdoc === null) return null;
9981
+ return readJsDocTagValue(jsdoc, "component-type");
9982
+ }
9983
+ var registryCache = /* @__PURE__ */ new Map();
9984
+ function resolveFromDesignRegistry(filePath) {
9985
+ const designPath = findDesignMd(filePath);
9986
+ if (designPath === null) return null;
9987
+ let byFile = registryCache.get(designPath);
9988
+ if (!byFile) {
9989
+ byFile = /* @__PURE__ */ new Map();
9990
+ let contents;
9991
+ try {
9992
+ contents = fs20.readFileSync(designPath, "utf8");
9993
+ } catch {
9994
+ contents = "";
9995
+ }
9996
+ const designDir = path23.dirname(designPath);
9997
+ for (const entry of parseComponentRegistry(contents)) {
9998
+ byFile.set(path23.resolve(designDir, entry.file), entry.type);
9999
+ }
10000
+ registryCache.set(designPath, byFile);
10001
+ }
10002
+ return byFile.get(path23.resolve(filePath)) ?? null;
9860
10003
  }
9861
10004
  function resolveFromExportName(fileContents) {
9862
10005
  const patterns = [
@@ -9875,20 +10018,142 @@ function resolveFromExportName(fileContents) {
9875
10018
  return null;
9876
10019
  }
9877
10020
 
10021
+ // src/audit/component-anatomy/resolvers/source-of-truth.ts
10022
+ import * as fs21 from "fs";
10023
+
10024
+ // src/audit/component-anatomy/parsers/anatomy-tags.ts
10025
+ var JSDOC_SOURCE_REF = "design-component-anatomy/jsdoc";
10026
+ function fixHint(name, axis) {
10027
+ return `Add the \`${name}\` ${axis} to this component, or drop the \`@anatomy-${axis} ${name}\` tag if it no longer applies.`;
10028
+ }
10029
+ function parseFlaggedPart(value, axis) {
10030
+ const tokens = value.split(/\s+/).filter(Boolean);
10031
+ const name = tokens[0];
10032
+ if (!name) return null;
10033
+ const flags = new Set(tokens.slice(1).map((t) => t.toLowerCase()));
10034
+ return {
10035
+ name,
10036
+ required: flags.has("required"),
10037
+ ...flags.has("exclusive") ? { exclusive: true } : {},
10038
+ fixHint: fixHint(name, axis)
10039
+ };
10040
+ }
10041
+ function parsePipeList(value, axis) {
10042
+ return value.split("|").map((n) => n.trim()).filter(Boolean).map((name) => ({ name, required: false, fixHint: fixHint(name, axis) }));
10043
+ }
10044
+ function buildAnatomyRuleFromJsDoc(jsdoc, componentType) {
10045
+ const slots = readJsDocTag(jsdoc, "anatomy-slot").map((v) => parseFlaggedPart(v, "slot")).filter((p) => p !== null);
10046
+ const states = readJsDocTag(jsdoc, "anatomy-state").map((v) => parseFlaggedPart(v, "state")).filter((p) => p !== null);
10047
+ const variants = readJsDocTag(jsdoc, "anatomy-variant").flatMap(
10048
+ (v) => parsePipeList(v, "variant")
10049
+ );
10050
+ const sizes = readJsDocTag(jsdoc, "anatomy-size").flatMap((v) => parsePipeList(v, "size"));
10051
+ if (slots.length === 0 && states.length === 0 && variants.length === 0 && sizes.length === 0) {
10052
+ return null;
10053
+ }
10054
+ return { componentType, slots, states, variants, sizes, source: { ref: JSDOC_SOURCE_REF } };
10055
+ }
10056
+
10057
+ // src/audit/component-anatomy/parsers/design-overrides.ts
10058
+ var SINGULAR = { slots: "slot", states: "state", variants: "variant", sizes: "size" };
10059
+ var SOURCE_REF = "design-component-anatomy/design-md";
10060
+ function partFrom(raw, axis) {
10061
+ const m = /^([A-Za-z0-9][A-Za-z0-9-]*)\s*(?:\(([^)]*)\))?/.exec(raw.trim());
10062
+ const name = m?.[1];
10063
+ if (!name) return null;
10064
+ const flags = (m?.[2] ?? "").toLowerCase();
10065
+ return {
10066
+ name,
10067
+ required: /\brequired\b/.test(flags),
10068
+ .../\bexclusive\b/.test(flags) ? { exclusive: true } : {},
10069
+ fixHint: `Add the \`${name}\` ${SINGULAR[axis]} to this component (declared in the DESIGN.md anatomy override).`
10070
+ };
10071
+ }
10072
+ function parseAnatomyOverrides(designMd) {
10073
+ const lines = designMd.split("\n");
10074
+ const out = /* @__PURE__ */ new Map();
10075
+ let i = lines.findIndex((l) => /^#{1,6}\s+Component Anatomy Overrides\b/i.test(l));
10076
+ if (i === -1) return out;
10077
+ let current = null;
10078
+ let currentAxis = null;
10079
+ const flush = () => {
10080
+ if (!current) return;
10081
+ out.set(current.type, {
10082
+ componentType: current.type,
10083
+ slots: current.axes.slots,
10084
+ states: current.axes.states,
10085
+ variants: current.axes.variants,
10086
+ sizes: current.axes.sizes,
10087
+ source: { ref: SOURCE_REF }
10088
+ });
10089
+ };
10090
+ for (i += 1; i < lines.length; i++) {
10091
+ const line = lines[i];
10092
+ if (/^#{1,2}\s/.test(line)) break;
10093
+ const compHeading = /^#{3,6}\s+(.+?)\s*$/.exec(line);
10094
+ if (compHeading) {
10095
+ flush();
10096
+ current = {
10097
+ type: compHeading[1].trim(),
10098
+ axes: { slots: [], states: [], variants: [], sizes: [] }
10099
+ };
10100
+ currentAxis = null;
10101
+ continue;
10102
+ }
10103
+ if (!current) continue;
10104
+ const axisMatch = /^\s*(slots|states|variants|sizes)\s*:\s*(.*)$/i.exec(line);
10105
+ if (axisMatch) {
10106
+ currentAxis = axisMatch[1].toLowerCase();
10107
+ const inline = axisMatch[2].trim();
10108
+ if (inline) {
10109
+ for (const tok of inline.split(",")) {
10110
+ const part = partFrom(tok, currentAxis);
10111
+ if (part) current.axes[currentAxis].push(part);
10112
+ }
10113
+ currentAxis = null;
10114
+ }
10115
+ continue;
10116
+ }
10117
+ const listItem = /^\s*[-*]\s+(.*)$/.exec(line);
10118
+ if (listItem && currentAxis) {
10119
+ const part = partFrom(listItem[1], currentAxis);
10120
+ if (part) current.axes[currentAxis].push(part);
10121
+ }
10122
+ }
10123
+ flush();
10124
+ return out;
10125
+ }
10126
+
9878
10127
  // src/audit/component-anatomy/resolvers/source-of-truth.ts
9879
10128
  function resolveAnatomyRules(filePath, fileContents, componentType) {
9880
10129
  if (componentType === null) return null;
9881
- const jsdocRule = resolveFromJSDoc2(filePath, fileContents, componentType);
10130
+ const jsdocRule = resolveFromJSDoc2(fileContents, componentType);
9882
10131
  if (jsdocRule !== null) return jsdocRule;
9883
10132
  const overrideRule = resolveFromDesignOverrides(filePath, componentType);
9884
10133
  if (overrideRule !== null) return overrideRule;
9885
10134
  return lookupConvention(componentType);
9886
10135
  }
9887
- function resolveFromJSDoc2(_filePath, _fileContents, _componentType) {
9888
- return null;
9889
- }
9890
- function resolveFromDesignOverrides(_filePath, _componentType) {
9891
- return null;
10136
+ function resolveFromJSDoc2(fileContents, componentType) {
10137
+ const jsdoc = extractLeadingJsDoc(fileContents);
10138
+ if (jsdoc === null) return null;
10139
+ return buildAnatomyRuleFromJsDoc(jsdoc, componentType);
10140
+ }
10141
+ var overridesCache = /* @__PURE__ */ new Map();
10142
+ function resolveFromDesignOverrides(filePath, componentType) {
10143
+ const designPath = findDesignMd(filePath);
10144
+ if (designPath === null) return null;
10145
+ let byType = overridesCache.get(designPath);
10146
+ if (!byType) {
10147
+ let contents;
10148
+ try {
10149
+ contents = fs21.readFileSync(designPath, "utf8");
10150
+ } catch {
10151
+ contents = "";
10152
+ }
10153
+ byType = parseAnatomyOverrides(contents);
10154
+ overridesCache.set(designPath, byType);
10155
+ }
10156
+ return byType.get(componentType) ?? null;
9892
10157
  }
9893
10158
 
9894
10159
  // src/audit/component-anatomy/findings/severity.ts
@@ -10007,10 +10272,197 @@ function runConventionRule(rule, parsed, options) {
10007
10272
  return findings;
10008
10273
  }
10009
10274
 
10275
+ // src/audit/component-anatomy/catalog/patterns/index.ts
10276
+ function lineAt(contents, index) {
10277
+ let line = 1;
10278
+ for (let i = 0; i < index && i < contents.length; i++) {
10279
+ if (contents[i] === "\n") line += 1;
10280
+ }
10281
+ return line;
10282
+ }
10283
+ var anyMatch = (contents, res) => res.some((re) => re.test(contents));
10284
+ function firstTrigger(contents, triggers) {
10285
+ let best = -1;
10286
+ for (const re of triggers) {
10287
+ const m = re.exec(contents);
10288
+ if (m && (best === -1 || m.index < best)) best = m.index;
10289
+ }
10290
+ return best;
10291
+ }
10292
+ function presencePattern(spec) {
10293
+ return {
10294
+ code: spec.code,
10295
+ id: spec.id,
10296
+ source: `design-component-anatomy/pattern-${spec.id}`,
10297
+ detect(file, contents, componentType) {
10298
+ const index = firstTrigger(contents, spec.triggers);
10299
+ if (index === -1) return [];
10300
+ if (anyMatch(contents, spec.mitigations)) return [];
10301
+ const line = lineAt(contents, index);
10302
+ const snippet = (contents.split("\n")[line - 1] ?? "").trim();
10303
+ return [
10304
+ {
10305
+ code: spec.code,
10306
+ severity: "warn",
10307
+ file,
10308
+ line,
10309
+ componentType,
10310
+ message: spec.message,
10311
+ evidence: { snippet },
10312
+ rule: { id: spec.code, source: `design-component-anatomy/pattern-${spec.id}` },
10313
+ fix: { kind: "manual", description: spec.fix }
10314
+ }
10315
+ ];
10316
+ }
10317
+ };
10318
+ }
10319
+ var SPECS = [
10320
+ {
10321
+ code: "ANAT-P001",
10322
+ id: "map-without-empty",
10323
+ triggers: [/\.map\s*\(/],
10324
+ mitigations: [
10325
+ /\.length\s*===\s*0/,
10326
+ /\.length\s*<\s*1/,
10327
+ /\.length\s*>\s*0/,
10328
+ /\.length\s*\?/,
10329
+ /\blength\s*&&/,
10330
+ /\?\.length\b/,
10331
+ /\bisEmpty\b/,
10332
+ /\bEmptyState\b/,
10333
+ /\bno\s+(results|items|data|entries)\b/i
10334
+ ],
10335
+ message: "List rendered with `.map(...)` but no empty state was found. An empty list renders as a blank region \u2014 add a length-zero branch (e.g. an EmptyState).",
10336
+ fix: "Guard the list: `items.length === 0 ? <EmptyState/> : items.map(...)`."
10337
+ },
10338
+ {
10339
+ code: "ANAT-P002",
10340
+ id: "fetch-without-loading",
10341
+ triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
10342
+ mitigations: [
10343
+ /\bis?Loading\b/i,
10344
+ /\bisPending\b/i,
10345
+ /\bpending\b/i,
10346
+ /\bSkeleton\b/,
10347
+ /\bSpinner\b/,
10348
+ /<Suspense\b/,
10349
+ /\bplaceholder\b/i
10350
+ ],
10351
+ message: "Async data loading found but no loading state. The surface renders empty until data resolves \u2014 add a loading boundary (skeleton, spinner, or Suspense).",
10352
+ fix: "Render a loading affordance while the request is in flight (e.g. `if (isLoading) return <Skeleton/>`)."
10353
+ },
10354
+ {
10355
+ code: "ANAT-P003",
10356
+ id: "fetch-without-error",
10357
+ triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
10358
+ mitigations: [
10359
+ /\bis?Error\b/i,
10360
+ /\berror\b/i,
10361
+ /\bcatch\s*\(/,
10362
+ /\.catch\s*\(/,
10363
+ /\bErrorState\b/,
10364
+ /\bErrorBoundary\b/,
10365
+ /role=["']alert["']/
10366
+ ],
10367
+ message: "Async data loading found but no error state. A failed request renders nothing or throws \u2014 handle and display the error case.",
10368
+ fix: "Add an error branch (e.g. `if (isError) return <ErrorState/>`) or a try/catch around the request."
10369
+ },
10370
+ {
10371
+ code: "ANAT-P004",
10372
+ id: "conditional-render-without-fallback",
10373
+ triggers: [/\{\s*[\w.]*(data|items|list|results|rows|user|profile)[\w.]*\s*&&/i],
10374
+ mitigations: [/\?\s*\(?</, /\?\?/, /\bEmptyState\b/, /:\s*null/, /:\s*</, /\.length\s*===\s*0/],
10375
+ message: "Data-driven `{value && <\u2026>}` render with no fallback. When `value` is falsy the region collapses silently \u2014 provide an explicit empty/else branch.",
10376
+ fix: "Use a ternary with a fallback: `value ? <View/> : <EmptyState/>`."
10377
+ },
10378
+ {
10379
+ code: "ANAT-P005",
10380
+ id: "form-without-submit-feedback",
10381
+ triggers: [/<form\b/i, /\bonSubmit\b/, /\bhandleSubmit\b/],
10382
+ mitigations: [
10383
+ /\bisSubmitting\b/i,
10384
+ /\bpending\b/i,
10385
+ /\bsuccess\b/i,
10386
+ /\berror\b/i,
10387
+ /\bdisabled\b/,
10388
+ /role=["']alert["']/,
10389
+ /\btoast\b/i
10390
+ ],
10391
+ message: "Form submission found but no submit feedback (pending / success / error). The user gets no signal that submit worked or failed.",
10392
+ fix: "Disable the submit control while pending and surface a success/error state after submission."
10393
+ },
10394
+ {
10395
+ code: "ANAT-P006",
10396
+ id: "modal-without-dismiss",
10397
+ triggers: [/<(Modal|Dialog|Drawer|Sheet|Popover)\b/],
10398
+ mitigations: [/\bonClose\b/, /\bonDismiss\b/, /\bonOpenChange\b/, /\bclose\b/i, /\bonCancel\b/],
10399
+ message: "Modal/Dialog/Drawer used with no dismiss affordance (`onClose` / `onDismiss` / `onOpenChange`). A modal the user cannot close is a trap.",
10400
+ fix: "Wire a dismiss handler (`onClose` / `onOpenChange`) and a visible close control."
10401
+ },
10402
+ {
10403
+ code: "ANAT-P007",
10404
+ id: "async-action-without-pending",
10405
+ triggers: [
10406
+ /on[A-Z]\w*=\{?\s*async\b/,
10407
+ /\bhandle\w*\s*=\s*async\b/,
10408
+ /async\s+function\s+handle/i
10409
+ ],
10410
+ mitigations: [
10411
+ /\bdisabled\b/,
10412
+ /\bis?Pending\b/i,
10413
+ /\bis?Loading\b/i,
10414
+ /\bis?Submitting\b/i,
10415
+ /\bSpinner\b/
10416
+ ],
10417
+ message: "Async action handler with no pending state. Without disabling/feedback the user can double-submit and gets no progress signal.",
10418
+ fix: "Track a pending flag and disable the control (and show a spinner) while the action runs."
10419
+ },
10420
+ {
10421
+ code: "ANAT-P008",
10422
+ id: "list-without-key",
10423
+ triggers: [/\.map\s*\(\s*\(?[^)]*\)?\s*=>\s*</],
10424
+ mitigations: [/\bkey=/],
10425
+ message: "Elements rendered from `.map(...)` with no `key` prop. Missing keys break reconciliation (lost state, mis-ordered DOM).",
10426
+ fix: "Add a stable `key` to the top-level mapped element: `items.map((i) => <Row key={i.id} />)`."
10427
+ },
10428
+ {
10429
+ code: "ANAT-P009",
10430
+ id: "router-without-not-found",
10431
+ triggers: [/<Routes\b/, /<Switch\b/, /\bcreateBrowserRouter\b/],
10432
+ mitigations: [
10433
+ /path=["']\*["']/,
10434
+ /path=["']\/\*["']/,
10435
+ /\bNotFound\b/,
10436
+ /\bcatchAll\b/i,
10437
+ /\b404\b/
10438
+ ],
10439
+ message: "Route table with no catch-all / not-found route. Unmatched URLs render a blank page.",
10440
+ fix: 'Add a catch-all route (`<Route path="*" element={<NotFound/>} />`).'
10441
+ },
10442
+ {
10443
+ code: "ANAT-P010",
10444
+ id: "destructive-action-without-confirm",
10445
+ triggers: [
10446
+ /\b(onDelete|handleDelete|handleRemove|onRemove)\b/,
10447
+ /\b(delete|remove|destroy)\w*\s*\(/i
10448
+ ],
10449
+ mitigations: [
10450
+ /\bconfirm\s*\(/i,
10451
+ /\bAlertDialog\b/,
10452
+ /\bConfirm(ation)?(Dialog|Modal)?\b/,
10453
+ /\bareYouSure\b/i,
10454
+ /\bwindow\.confirm\b/
10455
+ ],
10456
+ message: "Destructive action (delete/remove) with no confirmation step. A single click irreversibly destroys data.",
10457
+ fix: "Gate the action behind a confirmation (e.g. an AlertDialog) before performing it."
10458
+ }
10459
+ ];
10460
+ var PATTERN_CHECKS = SPECS.map(presencePattern);
10461
+
10010
10462
  // src/mcp/tools/audit-anatomy.ts
10011
10463
  var auditAnatomyDefinition = {
10012
10464
  name: "audit_anatomy",
10013
- description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the Button convention only; pattern-presence checks (ANAT-P*) return empty pending follow-up.",
10465
+ description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the component conventions, plus 10 ANAT-P* composition patterns (missing empty/loading/error states, modal dismiss, submit feedback, list keys, route 404, destructive-action confirm, \u2026) in full mode.",
10014
10466
  inputSchema: {
10015
10467
  type: "object",
10016
10468
  properties: {
@@ -10018,7 +10470,7 @@ var auditAnatomyDefinition = {
10018
10470
  mode: {
10019
10471
  type: "string",
10020
10472
  enum: ["fast", "full"],
10021
- description: "fast = conventions only (cheap AST scan). full = conventions + patterns. In v1 both modes run conventions only because pattern engine is not yet wired."
10473
+ description: "fast = conventions only (cheap AST scan); full additionally runs the ANAT-P* composition patterns."
10022
10474
  },
10023
10475
  files: {
10024
10476
  type: "array",
@@ -10046,31 +10498,38 @@ async function runAudit(input) {
10046
10498
  const projectRoot = sanitizePath(input.path);
10047
10499
  const candidateFiles = input.files ?? [];
10048
10500
  const conventionsApplied = /* @__PURE__ */ new Set();
10501
+ const patternsApplied = /* @__PURE__ */ new Set();
10049
10502
  let totalFiles = 0;
10050
10503
  for (const candidatePath of candidateFiles) {
10051
- const absolute = path22.isAbsolute(candidatePath) ? candidatePath : path22.join(projectRoot, candidatePath);
10504
+ const absolute = path24.isAbsolute(candidatePath) ? candidatePath : path24.join(projectRoot, candidatePath);
10052
10505
  let contents;
10053
10506
  try {
10054
- contents = fs19.readFileSync(absolute, "utf8");
10507
+ contents = fs22.readFileSync(absolute, "utf8");
10055
10508
  } catch {
10056
10509
  continue;
10057
10510
  }
10058
10511
  totalFiles += 1;
10512
+ const fileRelative = path24.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
10059
10513
  const componentType = resolveComponentType(absolute, contents);
10514
+ if (mode2 === "full") {
10515
+ for (const pattern of PATTERN_CHECKS) {
10516
+ const patternFindings = pattern.detect(fileRelative, contents, componentType);
10517
+ if (patternFindings.length > 0) patternsApplied.add(pattern.id);
10518
+ findings.push(...patternFindings);
10519
+ }
10520
+ }
10060
10521
  if (componentType === null) continue;
10061
10522
  const rule = resolveAnatomyRules(absolute, contents, componentType);
10062
10523
  if (rule === null) continue;
10063
10524
  const parsed = parseComponentDefinitionFromSource(absolute, contents);
10064
10525
  if (parsed === null) continue;
10065
10526
  conventionsApplied.add(rule.componentType);
10066
- const fileRelative = path22.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
10067
10527
  const runnerOptions = { filePath: fileRelative };
10068
10528
  if (input.designStrictness !== void 0) {
10069
10529
  runnerOptions.strictness = input.designStrictness;
10070
10530
  }
10071
10531
  const fileFindings = runConventionRule(rule, parsed, runnerOptions);
10072
10532
  findings.push(...fileFindings);
10073
- void mode2;
10074
10533
  }
10075
10534
  const summary = buildSummary(findings, totalFiles, Date.now() - start);
10076
10535
  return {
@@ -10078,7 +10537,7 @@ async function runAudit(input) {
10078
10537
  summary,
10079
10538
  catalog: {
10080
10539
  conventionsApplied: [...conventionsApplied].sort(),
10081
- patternsApplied: []
10540
+ patternsApplied: [...patternsApplied].sort()
10082
10541
  },
10083
10542
  meta: { mode: mode2, deferredToA11y: 0 }
10084
10543
  };
@@ -10110,10 +10569,11 @@ async function handleAuditAnatomy(input) {
10110
10569
  // src/mcp/tools/design-craft.ts
10111
10570
  init_dist2();
10112
10571
  import * as crypto2 from "crypto";
10572
+ import { execSync as execSync3 } from "child_process";
10113
10573
 
10114
10574
  // src/design-craft/phases/critique.ts
10115
- import * as fs20 from "fs";
10116
- import * as path23 from "path";
10575
+ import * as fs23 from "fs";
10576
+ import * as path25 from "path";
10117
10577
 
10118
10578
  // src/shared/craft/findings/derived.ts
10119
10579
  var TIER_BAND = {
@@ -10195,8 +10655,8 @@ function parseFindingResponse(raw) {
10195
10655
  }
10196
10656
  function readSource(target) {
10197
10657
  if (typeof target.source === "string") return target.source;
10198
- const resolved = path23.isAbsolute(target.file) ? target.file : path23.resolve(process.cwd(), target.file);
10199
- return fs20.readFileSync(resolved, "utf8");
10658
+ const resolved = path25.isAbsolute(target.file) ? target.file : path25.resolve(process.cwd(), target.file);
10659
+ return fs23.readFileSync(resolved, "utf8");
10200
10660
  }
10201
10661
  function formatPrompt(template, targetId, source) {
10202
10662
  return template.replace(/\{target\}/g, targetId).replace(/\{source\}/g, source);
@@ -10258,10 +10718,37 @@ async function runCritique(args) {
10258
10718
  }
10259
10719
  return findings;
10260
10720
  }
10721
+ function readImage(imagePath) {
10722
+ const resolved = path25.isAbsolute(imagePath) ? imagePath : path25.resolve(process.cwd(), imagePath);
10723
+ const imageBuffer = fs23.readFileSync(resolved);
10724
+ const ext = path25.extname(resolved).toLowerCase();
10725
+ const mediaType = ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : ext === ".webp" ? "image/webp" : "image/png";
10726
+ return { imageBuffer, mediaType };
10727
+ }
10728
+ async function runVisionCritique(args) {
10729
+ const findings = [];
10730
+ for (const target of args.targets) {
10731
+ const image = readImage(target.image);
10732
+ const targetId = target.component ?? target.file;
10733
+ for (const rubric of args.rubrics) {
10734
+ const prompt = formatPrompt(
10735
+ rubric.prompt,
10736
+ targetId,
10737
+ "(the rendered component is attached as an image \u2014 judge the visual result, not source code)"
10738
+ );
10739
+ const raw = await args.provider.callVision(prompt, image);
10740
+ const parsed = parseFindingResponse(raw);
10741
+ findings.push(
10742
+ parsed ? buildFinding(target, rubric, parsed) : buildSentinelFinding(target, rubric, raw)
10743
+ );
10744
+ }
10745
+ }
10746
+ return findings;
10747
+ }
10261
10748
 
10262
10749
  // src/design-craft/phases/polish.ts
10263
- import * as fs21 from "fs";
10264
- import * as path24 from "path";
10750
+ import * as fs24 from "fs";
10751
+ import * as path26 from "path";
10265
10752
  var TIER_VALUES2 = ["foundational", "polish", "aspirational"];
10266
10753
  var IMPACT_VALUES2 = ["small", "medium", "large"];
10267
10754
  var CONFIDENCE_VALUES2 = ["high", "medium", "low"];
@@ -10322,8 +10809,8 @@ function parsePolishResponse(raw) {
10322
10809
  }
10323
10810
  function readSource2(target) {
10324
10811
  if (typeof target.source === "string") return target.source;
10325
- const resolved = path24.isAbsolute(target.file) ? target.file : path24.resolve(process.cwd(), target.file);
10326
- return fs21.readFileSync(resolved, "utf8");
10812
+ const resolved = path26.isAbsolute(target.file) ? target.file : path26.resolve(process.cwd(), target.file);
10813
+ return fs24.readFileSync(resolved, "utf8");
10327
10814
  }
10328
10815
  function patternIsPlausible(source, pattern) {
10329
10816
  if (pattern.applicableTo.length === 0) return true;
@@ -10406,8 +10893,8 @@ async function runPolish(args) {
10406
10893
  }
10407
10894
 
10408
10895
  // src/design-craft/phases/benchmark.ts
10409
- import * as fs22 from "fs";
10410
- import * as path25 from "path";
10896
+ import * as fs25 from "fs";
10897
+ import * as path27 from "path";
10411
10898
  var CONFIDENCE_VALUES3 = ["high", "medium", "low"];
10412
10899
  function isConfidence3(v) {
10413
10900
  return typeof v === "string" && CONFIDENCE_VALUES3.includes(v);
@@ -10473,8 +10960,8 @@ function parseBenchmarkResponse(raw) {
10473
10960
  }
10474
10961
  function readSource3(target) {
10475
10962
  if (typeof target.source === "string") return target.source;
10476
- const resolved = path25.isAbsolute(target.file) ? target.file : path25.resolve(process.cwd(), target.file);
10477
- return fs22.readFileSync(resolved, "utf8");
10963
+ const resolved = path27.isAbsolute(target.file) ? target.file : path27.resolve(process.cwd(), target.file);
10964
+ return fs25.readFileSync(resolved, "utf8");
10478
10965
  }
10479
10966
  var CONFIDENCE_RANK = {
10480
10967
  high: 3,
@@ -12575,17 +13062,17 @@ import {
12575
13062
  } from "@harness-engineering/intelligence";
12576
13063
 
12577
13064
  // src/shared/craft/llm/orchestrator-md.ts
12578
- import * as fs23 from "fs";
12579
- import * as path26 from "path";
13065
+ import * as fs26 from "fs";
13066
+ import * as path28 from "path";
12580
13067
  import { parse as parse4 } from "yaml";
12581
13068
  var FILENAME = "harness.orchestrator.md";
12582
13069
  function findOrchestratorMd(startDir) {
12583
- let cur = path26.resolve(startDir);
12584
- const root = path26.parse(cur).root;
13070
+ let cur = path28.resolve(startDir);
13071
+ const root = path28.parse(cur).root;
12585
13072
  while (cur !== root) {
12586
- const candidate = path26.join(cur, FILENAME);
12587
- if (fs23.existsSync(candidate)) return candidate;
12588
- cur = path26.dirname(cur);
13073
+ const candidate = path28.join(cur, FILENAME);
13074
+ if (fs26.existsSync(candidate)) return candidate;
13075
+ cur = path28.dirname(cur);
12589
13076
  }
12590
13077
  return null;
12591
13078
  }
@@ -12594,7 +13081,7 @@ function readBackendsFromOrchestratorMd(startDir) {
12594
13081
  if (file === null) return null;
12595
13082
  let raw;
12596
13083
  try {
12597
- raw = fs23.readFileSync(file, "utf-8");
13084
+ raw = fs26.readFileSync(file, "utf-8");
12598
13085
  } catch {
12599
13086
  return null;
12600
13087
  }
@@ -12865,15 +13352,15 @@ function stringField(obj, key) {
12865
13352
  }
12866
13353
 
12867
13354
  // src/design-craft/measurement/usage.ts
12868
- import * as fs24 from "fs";
12869
- import * as path27 from "path";
12870
- var DEFAULT_RELATIVE_PATH = path27.join(".harness", "design-craft", "usage.json");
13355
+ import * as fs27 from "fs";
13356
+ import * as path29 from "path";
13357
+ var DEFAULT_RELATIVE_PATH = path29.join(".harness", "design-craft", "usage.json");
12871
13358
  function emptyCounters() {
12872
13359
  return { rubrics: {}, patterns: {}, exemplars: {} };
12873
13360
  }
12874
13361
  function resolveStorePath(projectRoot) {
12875
13362
  const root = projectRoot ?? process.cwd();
12876
- return path27.resolve(root, DEFAULT_RELATIVE_PATH);
13363
+ return path29.resolve(root, DEFAULT_RELATIVE_PATH);
12877
13364
  }
12878
13365
  function isCounters(value) {
12879
13366
  if (value === null || typeof value !== "object") return false;
@@ -12882,7 +13369,7 @@ function isCounters(value) {
12882
13369
  }
12883
13370
  function readCountersFromDisk(filePath) {
12884
13371
  try {
12885
- const raw = fs24.readFileSync(filePath, "utf8");
13372
+ const raw = fs27.readFileSync(filePath, "utf8");
12886
13373
  const parsed = JSON.parse(raw);
12887
13374
  if (!isCounters(parsed)) return emptyCounters();
12888
13375
  return {
@@ -12895,8 +13382,8 @@ function readCountersFromDisk(filePath) {
12895
13382
  }
12896
13383
  }
12897
13384
  function writeCountersToDisk(filePath, counters) {
12898
- fs24.mkdirSync(path27.dirname(filePath), { recursive: true });
12899
- fs24.writeFileSync(filePath, JSON.stringify(counters, null, 2), "utf8");
13385
+ fs27.mkdirSync(path29.dirname(filePath), { recursive: true });
13386
+ fs27.writeFileSync(filePath, JSON.stringify(counters, null, 2), "utf8");
12900
13387
  }
12901
13388
  function bumpCounter(family, id, projectRoot, count = 1) {
12902
13389
  if (id.length === 0 || count <= 0) return;
@@ -12917,13 +13404,13 @@ function recordCite(exemplarId, projectRoot) {
12917
13404
  }
12918
13405
 
12919
13406
  // src/design-craft/measurement/signal.ts
12920
- import * as fs25 from "fs";
12921
- import * as path28 from "path";
13407
+ import * as fs28 from "fs";
13408
+ import * as path30 from "path";
12922
13409
  import * as crypto from "crypto";
12923
- var EVENTS_RELATIVE_PATH = path28.join(".harness", "design-craft", "signal-events.jsonl");
12924
- var PROPOSALS_RELATIVE_DIR = path28.join(".harness", "design-craft", "proposals");
13410
+ var EVENTS_RELATIVE_PATH = path30.join(".harness", "design-craft", "signal-events.jsonl");
13411
+ var PROPOSALS_RELATIVE_DIR = path30.join(".harness", "design-craft", "proposals");
12925
13412
  function resolveEventsPath(storeRoot) {
12926
- return path28.resolve(storeRoot ?? process.cwd(), EVENTS_RELATIVE_PATH);
13413
+ return path30.resolve(storeRoot ?? process.cwd(), EVENTS_RELATIVE_PATH);
12927
13414
  }
12928
13415
  function fingerprintFinding(finding) {
12929
13416
  const raw = `${finding.code}|${finding.tier}|${finding.cite.rubricOrPatternId}`;
@@ -12944,8 +13431,8 @@ function recordSignalEvent(finding, projectRoot, storeRoot) {
12944
13431
  };
12945
13432
  try {
12946
13433
  const file = resolveEventsPath(storeRoot);
12947
- fs25.mkdirSync(path28.dirname(file), { recursive: true });
12948
- fs25.appendFileSync(file, JSON.stringify(event) + "\n", "utf8");
13434
+ fs28.mkdirSync(path30.dirname(file), { recursive: true });
13435
+ fs28.appendFileSync(file, JSON.stringify(event) + "\n", "utf8");
12949
13436
  } catch {
12950
13437
  }
12951
13438
  }
@@ -12962,7 +13449,7 @@ var designCraftToolDefinition = {
12962
13449
  mode: {
12963
13450
  type: "string",
12964
13451
  enum: ["fast", "deep"],
12965
- description: "fast (code-only LLM critique) or deep (render + vision). MVP supports fast only."
13452
+ description: "fast (code-only LLM critique) or deep (vision critique of rendered screenshots \u2014 requires `captures`)."
12966
13453
  },
12967
13454
  phases: {
12968
13455
  type: "array",
@@ -12977,7 +13464,11 @@ var designCraftToolDefinition = {
12977
13464
  autoCapture: {
12978
13465
  type: "string",
12979
13466
  enum: ["prompt", "auto", "skip"],
12980
- description: `B' detect-and-offer behavior when preconditions are missing. MVP: only "skip" is fully implemented.`
13467
+ description: 'Deep-mode capture behavior when no `captures` are supplied. "skip" never runs the capture command; "prompt"/"auto" run `captureCommand` when one is configured.'
13468
+ },
13469
+ captureCommand: {
13470
+ type: "string",
13471
+ description: "Deep-mode render+screenshot command. Receives the candidate files via the HARNESS_DESIGN_CRAFT_FILES env var (JSON array) and must print a JSON array of { file, image, component? } to stdout. Used to obtain captures without a built-in browser."
12981
13472
  },
12982
13473
  designStrictness: {
12983
13474
  type: "string",
@@ -12996,6 +13487,19 @@ var designCraftToolDefinition = {
12996
13487
  },
12997
13488
  required: ["file", "component"]
12998
13489
  }
13490
+ },
13491
+ captures: {
13492
+ type: "array",
13493
+ description: 'Deep-mode (vision) captures: rendered component screenshots. Required when mode="deep" and the critique phase runs. Each entry: { file, image, component? }, where `image` is a path to a PNG/JPEG/WebP screenshot (the CLI does not render components itself).',
13494
+ items: {
13495
+ type: "object",
13496
+ properties: {
13497
+ file: { type: "string" },
13498
+ image: { type: "string" },
13499
+ component: { type: "string" }
13500
+ },
13501
+ required: ["file", "image"]
13502
+ }
12999
13503
  }
13000
13504
  },
13001
13505
  required: ["path"]
@@ -13036,16 +13540,63 @@ function summarizeLlmCalls(provider) {
13036
13540
  async function runDesignCraft(input) {
13037
13541
  return runPipeline(input);
13038
13542
  }
13039
- async function runPipeline(input) {
13040
- const mode2 = input.mode ?? "fast";
13041
- if (mode2 === "deep") {
13543
+ function runCaptureCommand(command, files, exec) {
13544
+ let stdout;
13545
+ try {
13546
+ stdout = exec ? exec(command, files) : execSync3(command, {
13547
+ encoding: "utf-8",
13548
+ env: { ...process.env, HARNESS_DESIGN_CRAFT_FILES: JSON.stringify(files) },
13549
+ maxBuffer: 16 * 1024 * 1024
13550
+ });
13551
+ } catch (err2) {
13042
13552
  return Err({
13043
- message: 'design-craft deep mode (render + vision LLM) is not implemented in the Phase 1 MVP. Use mode: "fast".'
13553
+ message: `design-craft capture command failed: ${err2 instanceof Error ? err2.message : String(err2)}`
13044
13554
  });
13045
13555
  }
13556
+ let parsed;
13557
+ try {
13558
+ parsed = JSON.parse(stdout);
13559
+ } catch {
13560
+ return Err({
13561
+ message: 'design-craft capture command did not emit valid JSON. Expected `[{ "file": "...", "image": "..." }]` on stdout.'
13562
+ });
13563
+ }
13564
+ if (!Array.isArray(parsed)) {
13565
+ return Err({
13566
+ message: "design-craft capture command output must be a JSON array of { file, image }."
13567
+ });
13568
+ }
13569
+ const captures = [];
13570
+ for (const item of parsed) {
13571
+ if (item !== null && typeof item === "object" && typeof item.file === "string" && typeof item.image === "string") {
13572
+ const entry = item;
13573
+ const capture = { file: entry.file, image: entry.image };
13574
+ if (typeof entry.component === "string") capture.component = entry.component;
13575
+ captures.push(capture);
13576
+ }
13577
+ }
13578
+ if (captures.length === 0) {
13579
+ return Err({
13580
+ message: "design-craft capture command produced no valid { file, image } entries."
13581
+ });
13582
+ }
13583
+ return Ok(captures);
13584
+ }
13585
+ async function runPipeline(input) {
13586
+ const mode2 = input.mode ?? "fast";
13046
13587
  const phases = selectPhases(input.phases);
13047
13588
  const autoCapture = input.autoCapture ?? "prompt";
13048
- if (autoCapture !== "skip") {
13589
+ let captures = input.captures ?? [];
13590
+ const needsCaptures = mode2 === "deep" && phases.includes("critique") && captures.length === 0;
13591
+ if (needsCaptures && input.captureCommand && autoCapture !== "skip") {
13592
+ const captured = runCaptureCommand(input.captureCommand, input.files ?? [], input.__runCapture);
13593
+ if (!captured.ok) return captured;
13594
+ captures = captured.value;
13595
+ }
13596
+ if (mode2 === "deep" && phases.includes("critique") && captures.length === 0) {
13597
+ return Err({
13598
+ message: 'design-craft deep mode critiques rendered screenshots \u2014 supply `captures` ([{ file, image }]) or configure `captureCommand` to render them (the CLI does not render components itself). Or use mode: "fast".'
13599
+ });
13049
13600
  }
13050
13601
  const provider = input.__testProvider ?? getProvider();
13051
13602
  const critiqueTargets = buildTargetsFromFiles(input.files);
@@ -13057,12 +13608,14 @@ async function runPipeline(input) {
13057
13608
  const findings = [];
13058
13609
  const scores = [];
13059
13610
  let rubricsApplied = [];
13060
- if (phases.includes("critique") && critiqueTargets.length > 0) {
13611
+ if (phases.includes("critique")) {
13061
13612
  const rubrics = [...SEED_RUBRICS];
13062
- rubricsApplied = rubrics.map((r) => r.id);
13063
- const critiqueFindings = await runCritique({ targets: critiqueTargets, rubrics, provider });
13613
+ const critiqueFindings = mode2 === "deep" ? await runVisionCritique({ targets: captures, rubrics, provider }) : critiqueTargets.length > 0 ? await runCritique({ targets: critiqueTargets, rubrics, provider }) : [];
13614
+ if (critiqueFindings.length > 0 || mode2 === "deep") {
13615
+ rubricsApplied = rubrics.map((r) => r.id);
13616
+ }
13064
13617
  findings.push(...critiqueFindings);
13065
- if (recordMeasurement) {
13618
+ if (recordMeasurement && critiqueFindings.length > 0) {
13066
13619
  for (const rubric of rubrics) recordTrigger(rubric.id, measurementRoot);
13067
13620
  for (const f of critiqueFindings) recordSignalEvent(f, measurementRoot, measurementRoot);
13068
13621
  }
@@ -13135,18 +13688,18 @@ async function handleDesignCraft(input) {
13135
13688
  }
13136
13689
 
13137
13690
  // src/drift/index.ts
13138
- import * as fs28 from "fs";
13139
- import * as path31 from "path";
13691
+ import * as fs31 from "fs";
13692
+ import * as path33 from "path";
13140
13693
 
13141
13694
  // src/drift/resolvers/tokens.ts
13142
- import * as fs26 from "fs";
13143
- import * as path29 from "path";
13695
+ import * as fs29 from "fs";
13696
+ import * as path31 from "path";
13144
13697
  function loadTokenSet(projectRoot) {
13145
- const tokenPath = path29.join(projectRoot, "design-system", "tokens.json");
13146
- if (!fs26.existsSync(tokenPath)) return null;
13698
+ const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
13699
+ if (!fs29.existsSync(tokenPath)) return null;
13147
13700
  let raw;
13148
13701
  try {
13149
- raw = fs26.readFileSync(tokenPath, "utf-8");
13702
+ raw = fs29.readFileSync(tokenPath, "utf-8");
13150
13703
  } catch {
13151
13704
  return null;
13152
13705
  }
@@ -13170,11 +13723,11 @@ function extractTokens(root) {
13170
13723
  return set;
13171
13724
  }
13172
13725
  function loadTokenPathIndex(projectRoot) {
13173
- const tokenPath = path29.join(projectRoot, "design-system", "tokens.json");
13174
- if (!fs26.existsSync(tokenPath)) return null;
13726
+ const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
13727
+ if (!fs29.existsSync(tokenPath)) return null;
13175
13728
  let raw;
13176
13729
  try {
13177
- raw = fs26.readFileSync(tokenPath, "utf-8");
13730
+ raw = fs29.readFileSync(tokenPath, "utf-8");
13178
13731
  } catch {
13179
13732
  return null;
13180
13733
  }
@@ -13224,9 +13777,9 @@ function walkForPaths(node, breadcrumb, idx) {
13224
13777
  }
13225
13778
  }
13226
13779
  }
13227
- function pushPath(map, key, path54) {
13780
+ function pushPath(map, key, path56) {
13228
13781
  const list = map.get(key) ?? [];
13229
- list.push(path54);
13782
+ list.push(path56);
13230
13783
  map.set(key, list);
13231
13784
  }
13232
13785
  function walk(node, breadcrumb, set) {
@@ -13273,8 +13826,8 @@ function parsePxValue(s) {
13273
13826
  }
13274
13827
 
13275
13828
  // src/drift/resolvers/component-registry.ts
13276
- import * as fs27 from "fs";
13277
- import * as path30 from "path";
13829
+ import * as fs30 from "fs";
13830
+ import * as path32 from "path";
13278
13831
  var HTML_PRIMITIVE_MAP = {
13279
13832
  Button: "button",
13280
13833
  Input: "input",
@@ -13283,11 +13836,11 @@ var HTML_PRIMITIVE_MAP = {
13283
13836
  Anchor: "a"
13284
13837
  };
13285
13838
  function loadComponentRegistry(projectRoot) {
13286
- const designMdPath = path30.join(projectRoot, "design-system", "DESIGN.md");
13287
- if (!fs27.existsSync(designMdPath)) return null;
13839
+ const designMdPath = path32.join(projectRoot, "design-system", "DESIGN.md");
13840
+ if (!fs30.existsSync(designMdPath)) return null;
13288
13841
  let raw;
13289
13842
  try {
13290
- raw = fs27.readFileSync(designMdPath, "utf-8");
13843
+ raw = fs30.readFileSync(designMdPath, "utf-8");
13291
13844
  } catch {
13292
13845
  return null;
13293
13846
  }
@@ -13624,7 +14177,7 @@ async function runDetectDrift(input) {
13624
14177
  for (const file of filesToScan) {
13625
14178
  let source;
13626
14179
  try {
13627
- source = fs28.readFileSync(file, "utf-8");
14180
+ source = fs31.readFileSync(file, "utf-8");
13628
14181
  } catch {
13629
14182
  continue;
13630
14183
  }
@@ -13659,7 +14212,7 @@ async function runDetectDrift(input) {
13659
14212
  }
13660
14213
  async function collectFiles(projectRoot, explicitFiles) {
13661
14214
  if (explicitFiles !== void 0 && explicitFiles.length > 0) {
13662
- return explicitFiles.map((f) => path31.isAbsolute(f) ? f : path31.join(projectRoot, f));
14215
+ return explicitFiles.map((f) => path33.isAbsolute(f) ? f : path33.join(projectRoot, f));
13663
14216
  }
13664
14217
  const out = [];
13665
14218
  walk2(projectRoot, out, 0);
@@ -13669,7 +14222,7 @@ function walk2(dir, out, depth) {
13669
14222
  if (depth > 8) return;
13670
14223
  let entries;
13671
14224
  try {
13672
- entries = fs28.readdirSync(dir, { withFileTypes: true });
14225
+ entries = fs31.readdirSync(dir, { withFileTypes: true });
13673
14226
  } catch {
13674
14227
  return;
13675
14228
  }
@@ -13677,7 +14230,7 @@ function walk2(dir, out, depth) {
13677
14230
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
13678
14231
  continue;
13679
14232
  }
13680
- const full = path31.join(dir, entry.name);
14233
+ const full = path33.join(dir, entry.name);
13681
14234
  if (entry.isDirectory()) {
13682
14235
  walk2(full, out, depth + 1);
13683
14236
  } else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
@@ -13745,8 +14298,8 @@ async function handleDetectDrift(input) {
13745
14298
  }
13746
14299
 
13747
14300
  // src/align/index.ts
13748
- import * as fs30 from "fs";
13749
- import * as path34 from "path";
14301
+ import * as fs33 from "fs";
14302
+ import * as path36 from "path";
13750
14303
 
13751
14304
  // src/align/classifier/token-import.ts
13752
14305
  var NAMED_IMPORT = /^\s*import\s*\{\s*tokens(?:\s+as\s+\w+)?\s*\}\s*from\s+['"][^'"]+['"];?\s*$/m;
@@ -13886,9 +14439,9 @@ function sourceLine(source, line) {
13886
14439
  }
13887
14440
 
13888
14441
  // src/align/codemods/common.ts
13889
- import * as path32 from "path";
14442
+ import * as path34 from "path";
13890
14443
  function renderTokenReference(file, tokenPath) {
13891
- const ext = path32.extname(file).toLowerCase();
14444
+ const ext = path34.extname(file).toLowerCase();
13892
14445
  if (ext === ".css" || ext === ".scss") {
13893
14446
  return `var(--${tokenPath.replace(/\./g, "-")})`;
13894
14447
  }
@@ -14040,8 +14593,8 @@ function emitPrimitiveSuggestion(finding) {
14040
14593
  }
14041
14594
 
14042
14595
  // src/align/revert/state.ts
14043
- import * as fs29 from "fs";
14044
- import * as path33 from "path";
14596
+ import * as fs32 from "fs";
14597
+ import * as path35 from "path";
14045
14598
  import { createHash as createHash2 } from "crypto";
14046
14599
  var LAST_BATCH_PATH = ".harness/align/last-batch.json";
14047
14600
  function hashContent(content) {
@@ -14073,15 +14626,15 @@ function saveLastBatch(projectRoot, outcomes, mode2, fileReader) {
14073
14626
  mode: mode2,
14074
14627
  entries
14075
14628
  };
14076
- const full = path33.join(projectRoot, LAST_BATCH_PATH);
14077
- fs29.mkdirSync(path33.dirname(full), { recursive: true });
14078
- fs29.writeFileSync(full, JSON.stringify(batch, null, 2) + "\n", "utf-8");
14629
+ const full = path35.join(projectRoot, LAST_BATCH_PATH);
14630
+ fs32.mkdirSync(path35.dirname(full), { recursive: true });
14631
+ fs32.writeFileSync(full, JSON.stringify(batch, null, 2) + "\n", "utf-8");
14079
14632
  }
14080
14633
  function loadLastBatch(projectRoot) {
14081
- const full = path33.join(projectRoot, LAST_BATCH_PATH);
14082
- if (!fs29.existsSync(full)) return null;
14634
+ const full = path35.join(projectRoot, LAST_BATCH_PATH);
14635
+ if (!fs32.existsSync(full)) return null;
14083
14636
  try {
14084
- const raw = fs29.readFileSync(full, "utf-8");
14637
+ const raw = fs32.readFileSync(full, "utf-8");
14085
14638
  const parsed = JSON.parse(raw);
14086
14639
  if (parsed.version !== 1 || !Array.isArray(parsed.entries)) return null;
14087
14640
  return parsed;
@@ -14178,7 +14731,7 @@ function persistRunArtifacts(args) {
14178
14731
  saveLastBatch(args.projectRoot, args.outcomes, args.mode, (file) => {
14179
14732
  const cached = args.sourceCache.get(file);
14180
14733
  if (cached !== void 0) return cached;
14181
- return fs30.readFileSync(file, "utf-8");
14734
+ return fs33.readFileSync(file, "utf-8");
14182
14735
  });
14183
14736
  }
14184
14737
  async function runRevert(projectRoot, mode2, dryRun, startedAt) {
@@ -14244,7 +14797,7 @@ function readForRevert(file, sourceCache) {
14244
14797
  const cached = sourceCache.get(file);
14245
14798
  if (cached !== void 0) return { kind: "ok", text: cached };
14246
14799
  try {
14247
- return { kind: "ok", text: fs30.readFileSync(file, "utf-8") };
14800
+ return { kind: "ok", text: fs33.readFileSync(file, "utf-8") };
14248
14801
  } catch (err2) {
14249
14802
  return {
14250
14803
  kind: "failed",
@@ -14254,7 +14807,7 @@ function readForRevert(file, sourceCache) {
14254
14807
  }
14255
14808
  function tryWrite(file, content) {
14256
14809
  try {
14257
- fs30.writeFileSync(file, content, "utf-8");
14810
+ fs33.writeFileSync(file, content, "utf-8");
14258
14811
  return null;
14259
14812
  } catch (err2) {
14260
14813
  return `cannot write source file: ${err2 instanceof Error ? err2.message : String(err2)}`;
@@ -14262,10 +14815,10 @@ function tryWrite(file, content) {
14262
14815
  }
14263
14816
  async function loadFindings(projectRoot, input, mode2) {
14264
14817
  if (mode2 === "pipeline") {
14265
- const handoffPath = path34.join(projectRoot, HANDOFF_PATH);
14266
- if (!fs30.existsSync(handoffPath)) return [];
14818
+ const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
14819
+ if (!fs33.existsSync(handoffPath)) return [];
14267
14820
  try {
14268
- const raw = fs30.readFileSync(handoffPath, "utf-8");
14821
+ const raw = fs33.readFileSync(handoffPath, "utf-8");
14269
14822
  const parsed = JSON.parse(raw);
14270
14823
  return parsed.pipeline?.driftFindings ?? [];
14271
14824
  } catch {
@@ -14290,7 +14843,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
14290
14843
  }
14291
14844
  let source;
14292
14845
  try {
14293
- source = sourceCache.get(finding.file) ?? fs30.readFileSync(finding.file, "utf-8");
14846
+ source = sourceCache.get(finding.file) ?? fs33.readFileSync(finding.file, "utf-8");
14294
14847
  sourceCache.set(finding.file, source);
14295
14848
  } catch (err2) {
14296
14849
  return {
@@ -14318,7 +14871,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
14318
14871
  }
14319
14872
  if (!dryRun) {
14320
14873
  try {
14321
- fs30.writeFileSync(finding.file, result.newSource, "utf-8");
14874
+ fs33.writeFileSync(finding.file, result.newSource, "utf-8");
14322
14875
  sourceCache.set(finding.file, result.newSource);
14323
14876
  } catch (err2) {
14324
14877
  return {
@@ -14380,19 +14933,19 @@ function aggregateOutput(outcomes, meta) {
14380
14933
  };
14381
14934
  }
14382
14935
  function writePipelineFixesApplied(projectRoot, outcomes) {
14383
- const handoffPath = path34.join(projectRoot, HANDOFF_PATH);
14936
+ const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
14384
14937
  let handoff = {};
14385
- if (fs30.existsSync(handoffPath)) {
14938
+ if (fs33.existsSync(handoffPath)) {
14386
14939
  try {
14387
- handoff = JSON.parse(fs30.readFileSync(handoffPath, "utf-8"));
14940
+ handoff = JSON.parse(fs33.readFileSync(handoffPath, "utf-8"));
14388
14941
  } catch {
14389
14942
  handoff = {};
14390
14943
  }
14391
14944
  }
14392
14945
  handoff.pipeline = handoff.pipeline ?? {};
14393
14946
  handoff.pipeline.fixesApplied = outcomes;
14394
- fs30.mkdirSync(path34.dirname(handoffPath), { recursive: true });
14395
- fs30.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n", "utf-8");
14947
+ fs33.mkdirSync(path36.dirname(handoffPath), { recursive: true });
14948
+ fs33.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n", "utf-8");
14396
14949
  }
14397
14950
  function findingKey(finding) {
14398
14951
  return `${finding.code}@${finding.file}:${finding.line ?? "?"}`;
@@ -14468,18 +15021,18 @@ async function handleAlignDesignSystem(input) {
14468
15021
  }
14469
15022
 
14470
15023
  // src/brand/index.ts
14471
- import * as fs33 from "fs";
14472
- import * as path37 from "path";
15024
+ import * as fs36 from "fs";
15025
+ import * as path39 from "path";
14473
15026
 
14474
15027
  // src/brand/resolvers/design-md-brand.ts
14475
- import * as fs31 from "fs";
14476
- import * as path35 from "path";
15028
+ import * as fs34 from "fs";
15029
+ import * as path37 from "path";
14477
15030
  function loadBrandRules(projectRoot) {
14478
- const designMdPath = path35.join(projectRoot, "design-system", "DESIGN.md");
14479
- if (!fs31.existsSync(designMdPath)) return null;
15031
+ const designMdPath = path37.join(projectRoot, "design-system", "DESIGN.md");
15032
+ if (!fs34.existsSync(designMdPath)) return null;
14480
15033
  let raw;
14481
15034
  try {
14482
- raw = fs31.readFileSync(designMdPath, "utf-8");
15035
+ raw = fs34.readFileSync(designMdPath, "utf-8");
14483
15036
  } catch {
14484
15037
  return null;
14485
15038
  }
@@ -14590,14 +15143,14 @@ function parseAssets(block) {
14590
15143
  }
14591
15144
 
14592
15145
  // src/brand/resolvers/token-extensions.ts
14593
- import * as fs32 from "fs";
14594
- import * as path36 from "path";
15146
+ import * as fs35 from "fs";
15147
+ import * as path38 from "path";
14595
15148
  function loadBrandTokenIndex(projectRoot) {
14596
- const tokenPath = path36.join(projectRoot, "design-system", "tokens.json");
14597
- if (!fs32.existsSync(tokenPath)) return null;
15149
+ const tokenPath = path38.join(projectRoot, "design-system", "tokens.json");
15150
+ if (!fs35.existsSync(tokenPath)) return null;
14598
15151
  let raw;
14599
15152
  try {
14600
- raw = fs32.readFileSync(tokenPath, "utf-8");
15153
+ raw = fs35.readFileSync(tokenPath, "utf-8");
14601
15154
  } catch {
14602
15155
  return null;
14603
15156
  }
@@ -14846,7 +15399,7 @@ async function runAuditBrand(input) {
14846
15399
  for (const file of filesToScan) {
14847
15400
  let source;
14848
15401
  try {
14849
- source = fs33.readFileSync(file, "utf-8");
15402
+ source = fs36.readFileSync(file, "utf-8");
14850
15403
  } catch {
14851
15404
  continue;
14852
15405
  }
@@ -14888,7 +15441,7 @@ async function runAuditBrand(input) {
14888
15441
  }
14889
15442
  function collectFiles2(projectRoot, explicitFiles) {
14890
15443
  if (explicitFiles !== void 0 && explicitFiles.length > 0) {
14891
- return explicitFiles.map((f) => path37.isAbsolute(f) ? f : path37.join(projectRoot, f));
15444
+ return explicitFiles.map((f) => path39.isAbsolute(f) ? f : path39.join(projectRoot, f));
14892
15445
  }
14893
15446
  const out = [];
14894
15447
  walk4(projectRoot, out, 0);
@@ -14898,7 +15451,7 @@ function walk4(dir, out, depth) {
14898
15451
  if (depth > 8) return;
14899
15452
  let entries;
14900
15453
  try {
14901
- entries = fs33.readdirSync(dir, { withFileTypes: true });
15454
+ entries = fs36.readdirSync(dir, { withFileTypes: true });
14902
15455
  } catch {
14903
15456
  return;
14904
15457
  }
@@ -14906,7 +15459,7 @@ function walk4(dir, out, depth) {
14906
15459
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
14907
15460
  continue;
14908
15461
  }
14909
- const full = path37.join(dir, entry.name);
15462
+ const full = path39.join(dir, entry.name);
14910
15463
  if (entry.isDirectory()) {
14911
15464
  walk4(full, out, depth + 1);
14912
15465
  } else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS2.some((ext) => entry.name.endsWith(ext))) {
@@ -15024,15 +15577,15 @@ var VerifierRegistry = class {
15024
15577
  };
15025
15578
 
15026
15579
  // src/design-pipeline/phases/freshen.ts
15027
- import * as fs34 from "fs";
15028
- import * as path38 from "path";
15580
+ import * as fs37 from "fs";
15581
+ import * as path40 from "path";
15029
15582
  function runFreshen(input) {
15030
15583
  const { projectRoot, context } = input;
15031
- context.graphAvailable = fs34.existsSync(path38.join(projectRoot, ".harness", "graph"));
15032
- const designMdPath = path38.join(projectRoot, "design-system", "DESIGN.md");
15033
- const tokensJsonPath = path38.join(projectRoot, "design-system", "tokens.json");
15034
- context.inputs.designMdExists = fs34.existsSync(designMdPath);
15035
- context.inputs.tokensJsonExists = fs34.existsSync(tokensJsonPath);
15584
+ context.graphAvailable = fs37.existsSync(path40.join(projectRoot, ".harness", "graph"));
15585
+ const designMdPath = path40.join(projectRoot, "design-system", "DESIGN.md");
15586
+ const tokensJsonPath = path40.join(projectRoot, "design-system", "tokens.json");
15587
+ context.inputs.designMdExists = fs37.existsSync(designMdPath);
15588
+ context.inputs.tokensJsonExists = fs37.existsSync(tokensJsonPath);
15036
15589
  if (context.inputs.designMdExists) {
15037
15590
  const content = readFileSafe(designMdPath);
15038
15591
  context.inputs.componentRegistryExists = content !== null && /^##\s+component\s+registry\b/im.test(content);
@@ -15041,7 +15594,7 @@ function runFreshen(input) {
15041
15594
  }
15042
15595
  function readFileSafe(p) {
15043
15596
  try {
15044
- return fs34.readFileSync(p, "utf-8");
15597
+ return fs37.readFileSync(p, "utf-8");
15045
15598
  } catch {
15046
15599
  return null;
15047
15600
  }
@@ -15068,8 +15621,8 @@ async function runDetect(input) {
15068
15621
  }
15069
15622
 
15070
15623
  // src/design-pipeline/phases/fix.ts
15071
- import * as fs35 from "fs";
15072
- import * as path39 from "path";
15624
+ import * as fs38 from "fs";
15625
+ import * as path41 from "path";
15073
15626
  var MAX_ITERATIONS = 5;
15074
15627
  async function runFix(input) {
15075
15628
  const { projectRoot, context, mode: mode2, files, designStrictness } = input;
@@ -15118,11 +15671,11 @@ async function safelyRedetect(projectRoot, mode2, files, designStrictness) {
15118
15671
  }
15119
15672
  }
15120
15673
  function writePipelineHandoff(projectRoot, context) {
15121
- const handoffPath = path39.join(projectRoot, ".harness", "handoff.json");
15674
+ const handoffPath = path41.join(projectRoot, ".harness", "handoff.json");
15122
15675
  let handoff = {};
15123
- if (fs35.existsSync(handoffPath)) {
15676
+ if (fs38.existsSync(handoffPath)) {
15124
15677
  try {
15125
- handoff = JSON.parse(fs35.readFileSync(handoffPath, "utf-8"));
15678
+ handoff = JSON.parse(fs38.readFileSync(handoffPath, "utf-8"));
15126
15679
  } catch {
15127
15680
  handoff = {};
15128
15681
  }
@@ -15130,8 +15683,8 @@ function writePipelineHandoff(projectRoot, context) {
15130
15683
  handoff.pipeline = {
15131
15684
  driftFindings: context.driftFindings
15132
15685
  };
15133
- fs35.mkdirSync(path39.dirname(handoffPath), { recursive: true });
15134
- fs35.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n");
15686
+ fs38.mkdirSync(path41.dirname(handoffPath), { recursive: true });
15687
+ fs38.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n");
15135
15688
  }
15136
15689
 
15137
15690
  // src/design-pipeline/phases/audit.ts
@@ -15161,8 +15714,8 @@ async function runAudit2(input) {
15161
15714
  }
15162
15715
 
15163
15716
  // src/design-pipeline/phases/fill.ts
15164
- import * as fs36 from "fs";
15165
- import * as path40 from "path";
15717
+ import * as fs39 from "fs";
15718
+ import * as path42 from "path";
15166
15719
  var DESIGN_MD_STUB = `# Design System
15167
15720
 
15168
15721
  <!-- TODO: author this file. See harness-design skill for guidance. -->
@@ -15222,11 +15775,11 @@ async function runFill(input) {
15222
15775
  }
15223
15776
  function bootstrapMissingInputs(input) {
15224
15777
  const { projectRoot, context } = input;
15225
- const designMdPath = path40.join(projectRoot, "design-system", "DESIGN.md");
15226
- const tokensJsonPath = path40.join(projectRoot, "design-system", "tokens.json");
15227
- if (!fs36.existsSync(designMdPath)) {
15228
- fs36.mkdirSync(path40.dirname(designMdPath), { recursive: true });
15229
- fs36.writeFileSync(designMdPath, DESIGN_MD_STUB);
15778
+ const designMdPath = path42.join(projectRoot, "design-system", "DESIGN.md");
15779
+ const tokensJsonPath = path42.join(projectRoot, "design-system", "tokens.json");
15780
+ if (!fs39.existsSync(designMdPath)) {
15781
+ fs39.mkdirSync(path42.dirname(designMdPath), { recursive: true });
15782
+ fs39.writeFileSync(designMdPath, DESIGN_MD_STUB);
15230
15783
  context.bootstrapped.designMd = true;
15231
15784
  context.inputs.designMdExists = true;
15232
15785
  context.inputs.componentRegistryExists = true;
@@ -15246,16 +15799,16 @@ function bootstrapMissingInputs(input) {
15246
15799
  }
15247
15800
  }
15248
15801
  }
15249
- if (!fs36.existsSync(tokensJsonPath)) {
15250
- fs36.mkdirSync(path40.dirname(tokensJsonPath), { recursive: true });
15251
- fs36.writeFileSync(tokensJsonPath, TOKENS_JSON_STUB);
15802
+ if (!fs39.existsSync(tokensJsonPath)) {
15803
+ fs39.mkdirSync(path42.dirname(tokensJsonPath), { recursive: true });
15804
+ fs39.writeFileSync(tokensJsonPath, TOKENS_JSON_STUB);
15252
15805
  context.bootstrapped.tokensJson = true;
15253
15806
  context.inputs.tokensJsonExists = true;
15254
15807
  }
15255
15808
  }
15256
15809
  function readFileSafe2(p) {
15257
15810
  try {
15258
- return fs36.readFileSync(p, "utf-8");
15811
+ return fs39.readFileSync(p, "utf-8");
15259
15812
  } catch {
15260
15813
  return null;
15261
15814
  }
@@ -15290,10 +15843,10 @@ async function invokeCraftPolish(input) {
15290
15843
  function appendToFile(p, content) {
15291
15844
  let existing = "";
15292
15845
  try {
15293
- existing = fs36.readFileSync(p, "utf-8");
15846
+ existing = fs39.readFileSync(p, "utf-8");
15294
15847
  } catch {
15295
15848
  }
15296
- fs36.writeFileSync(p, existing + content);
15849
+ fs39.writeFileSync(p, existing + content);
15297
15850
  }
15298
15851
 
15299
15852
  // src/design-pipeline/phases/report.ts
@@ -15442,8 +15995,8 @@ async function handleDesignPipeline(input) {
15442
15995
  }
15443
15996
 
15444
15997
  // src/naming-craft/index.ts
15445
- import * as fs38 from "fs";
15446
- import * as path42 from "path";
15998
+ import * as fs41 from "fs";
15999
+ import * as path44 from "path";
15447
16000
  import { randomUUID as randomUUID3 } from "crypto";
15448
16001
 
15449
16002
  // src/naming-craft/extract/identifiers.ts
@@ -15767,27 +16320,27 @@ function isConfidence4(v) {
15767
16320
  }
15768
16321
 
15769
16322
  // src/shared/craft/runs/store.ts
15770
- import * as fs37 from "fs";
15771
- import * as path41 from "path";
16323
+ import * as fs40 from "fs";
16324
+ import * as path43 from "path";
15772
16325
  var RUN_TTL_MS = 24 * 60 * 60 * 1e3;
15773
16326
  function runsDir(projectRoot) {
15774
- return path41.join(projectRoot, ".harness", "craft", "runs");
16327
+ return path43.join(projectRoot, ".harness", "craft", "runs");
15775
16328
  }
15776
16329
  function runPath(projectRoot, runId) {
15777
- return path41.join(runsDir(projectRoot), `${runId}.json`);
16330
+ return path43.join(runsDir(projectRoot), `${runId}.json`);
15778
16331
  }
15779
16332
  function saveRunState(projectRoot, state) {
15780
16333
  const dir = runsDir(projectRoot);
15781
- fs37.mkdirSync(dir, { recursive: true });
16334
+ fs40.mkdirSync(dir, { recursive: true });
15782
16335
  const file = runPath(projectRoot, state.runId);
15783
- fs37.writeFileSync(file, JSON.stringify(state, null, 2));
16336
+ fs40.writeFileSync(file, JSON.stringify(state, null, 2));
15784
16337
  return { runFile: file };
15785
16338
  }
15786
16339
  function loadRunState(projectRoot, runId) {
15787
16340
  const file = runPath(projectRoot, runId);
15788
- if (!fs37.existsSync(file)) return null;
16341
+ if (!fs40.existsSync(file)) return null;
15789
16342
  try {
15790
- const raw = fs37.readFileSync(file, "utf-8");
16343
+ const raw = fs40.readFileSync(file, "utf-8");
15791
16344
  return JSON.parse(raw);
15792
16345
  } catch {
15793
16346
  return null;
@@ -15796,7 +16349,7 @@ function loadRunState(projectRoot, runId) {
15796
16349
  function deleteRunState(projectRoot, runId) {
15797
16350
  const file = runPath(projectRoot, runId);
15798
16351
  try {
15799
- fs37.unlinkSync(file);
16352
+ fs40.unlinkSync(file);
15800
16353
  } catch {
15801
16354
  }
15802
16355
  }
@@ -15804,16 +16357,16 @@ function pruneOldRuns(projectRoot, now = Date.now()) {
15804
16357
  const dir = runsDir(projectRoot);
15805
16358
  let entries;
15806
16359
  try {
15807
- entries = fs37.readdirSync(dir, { withFileTypes: true });
16360
+ entries = fs40.readdirSync(dir, { withFileTypes: true });
15808
16361
  } catch {
15809
16362
  return;
15810
16363
  }
15811
16364
  for (const entry of entries) {
15812
16365
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
15813
- const full = path41.join(dir, entry.name);
16366
+ const full = path43.join(dir, entry.name);
15814
16367
  try {
15815
- const stat = fs37.statSync(full);
15816
- if (now - stat.mtimeMs > RUN_TTL_MS) fs37.unlinkSync(full);
16368
+ const stat = fs40.statSync(full);
16369
+ if (now - stat.mtimeMs > RUN_TTL_MS) fs40.unlinkSync(full);
15817
16370
  } catch {
15818
16371
  }
15819
16372
  }
@@ -15833,7 +16386,7 @@ function gatherProjectContext(input) {
15833
16386
  for (const file of files) {
15834
16387
  let source;
15835
16388
  try {
15836
- source = fs38.readFileSync(file, "utf-8");
16389
+ source = fs41.readFileSync(file, "utf-8");
15837
16390
  } catch {
15838
16391
  continue;
15839
16392
  }
@@ -15990,7 +16543,7 @@ async function finalizeNamingCraft(input) {
15990
16543
  }
15991
16544
  function collectFiles3(projectRoot, explicitFiles) {
15992
16545
  if (explicitFiles !== void 0 && explicitFiles.length > 0) {
15993
- return explicitFiles.map((f) => path42.isAbsolute(f) ? f : path42.join(projectRoot, f));
16546
+ return explicitFiles.map((f) => path44.isAbsolute(f) ? f : path44.join(projectRoot, f));
15994
16547
  }
15995
16548
  const out = [];
15996
16549
  walk5(projectRoot, out, 0);
@@ -16000,7 +16553,7 @@ function walk5(dir, out, depth) {
16000
16553
  if (depth > 8) return;
16001
16554
  let entries;
16002
16555
  try {
16003
- entries = fs38.readdirSync(dir, { withFileTypes: true });
16556
+ entries = fs41.readdirSync(dir, { withFileTypes: true });
16004
16557
  } catch {
16005
16558
  return;
16006
16559
  }
@@ -16008,7 +16561,7 @@ function walk5(dir, out, depth) {
16008
16561
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
16009
16562
  continue;
16010
16563
  }
16011
- const full = path42.join(dir, entry.name);
16564
+ const full = path44.join(dir, entry.name);
16012
16565
  if (entry.isDirectory()) walk5(full, out, depth + 1);
16013
16566
  else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) out.push(full);
16014
16567
  }
@@ -16192,32 +16745,32 @@ async function handleNamingCraftFinalize(input) {
16192
16745
  }
16193
16746
 
16194
16747
  // src/spec-craft/index.ts
16195
- import * as fs40 from "fs";
16748
+ import * as fs43 from "fs";
16196
16749
  import { randomUUID as randomUUID4 } from "crypto";
16197
16750
 
16198
16751
  // src/spec-craft/extract/discover.ts
16199
- import * as fs39 from "fs";
16200
- import * as path43 from "path";
16752
+ import * as fs42 from "fs";
16753
+ import * as path45 from "path";
16201
16754
  function discoverSpecs(projectRoot, kindsFilter) {
16202
16755
  const wantProposal = kindsFilter === void 0 || kindsFilter.includes("proposal");
16203
16756
  const wantADR = kindsFilter === void 0 || kindsFilter.includes("adr");
16204
16757
  const out = [];
16205
16758
  if (wantProposal) {
16206
- const changesDir = path43.join(projectRoot, "docs", "changes");
16207
- if (fs39.existsSync(changesDir)) {
16208
- for (const entry of fs39.readdirSync(changesDir, { withFileTypes: true })) {
16759
+ const changesDir = path45.join(projectRoot, "docs", "changes");
16760
+ if (fs42.existsSync(changesDir)) {
16761
+ for (const entry of fs42.readdirSync(changesDir, { withFileTypes: true })) {
16209
16762
  if (!entry.isDirectory()) continue;
16210
16763
  if (entry.name.startsWith(".")) continue;
16211
- const topicDir = path43.join(changesDir, entry.name);
16212
- const direct = path43.join(topicDir, "proposal.md");
16213
- if (fs39.existsSync(direct)) {
16764
+ const topicDir = path45.join(changesDir, entry.name);
16765
+ const direct = path45.join(topicDir, "proposal.md");
16766
+ if (fs42.existsSync(direct)) {
16214
16767
  out.push({ file: direct, kind: "proposal" });
16215
16768
  }
16216
- for (const sub of fs39.readdirSync(topicDir, { withFileTypes: true })) {
16769
+ for (const sub of fs42.readdirSync(topicDir, { withFileTypes: true })) {
16217
16770
  if (!sub.isDirectory()) continue;
16218
16771
  if (sub.name.startsWith(".")) continue;
16219
- const subProposal = path43.join(topicDir, sub.name, "proposal.md");
16220
- if (fs39.existsSync(subProposal)) {
16772
+ const subProposal = path45.join(topicDir, sub.name, "proposal.md");
16773
+ if (fs42.existsSync(subProposal)) {
16221
16774
  out.push({ file: subProposal, kind: "proposal" });
16222
16775
  }
16223
16776
  }
@@ -16225,13 +16778,13 @@ function discoverSpecs(projectRoot, kindsFilter) {
16225
16778
  }
16226
16779
  }
16227
16780
  if (wantADR) {
16228
- const decisionsDir = path43.join(projectRoot, "docs", "knowledge", "decisions");
16229
- if (fs39.existsSync(decisionsDir)) {
16230
- for (const entry of fs39.readdirSync(decisionsDir, { withFileTypes: true })) {
16781
+ const decisionsDir = path45.join(projectRoot, "docs", "knowledge", "decisions");
16782
+ if (fs42.existsSync(decisionsDir)) {
16783
+ for (const entry of fs42.readdirSync(decisionsDir, { withFileTypes: true })) {
16231
16784
  if (!entry.isFile()) continue;
16232
16785
  if (!entry.name.endsWith(".md")) continue;
16233
16786
  if (entry.name.startsWith(".") || entry.name.toUpperCase() === "README.MD") continue;
16234
- out.push({ file: path43.join(decisionsDir, entry.name), kind: "adr" });
16787
+ out.push({ file: path45.join(decisionsDir, entry.name), kind: "adr" });
16235
16788
  }
16236
16789
  }
16237
16790
  }
@@ -16479,7 +17032,7 @@ async function runSpecCraft(input) {
16479
17032
  for (const spec of specs) {
16480
17033
  let source;
16481
17034
  try {
16482
- source = fs40.readFileSync(spec.file, "utf-8");
17035
+ source = fs43.readFileSync(spec.file, "utf-8");
16483
17036
  } catch {
16484
17037
  continue;
16485
17038
  }
@@ -16599,13 +17152,13 @@ async function handleSpecCraft(input) {
16599
17152
  }
16600
17153
 
16601
17154
  // src/copy-craft/index.ts
16602
- import * as fs42 from "fs";
16603
- import * as path46 from "path";
17155
+ import * as fs45 from "fs";
17156
+ import * as path48 from "path";
16604
17157
  import { randomUUID as randomUUID5 } from "crypto";
16605
17158
 
16606
17159
  // src/copy-craft/extract/source.ts
16607
17160
  import ts5 from "typescript";
16608
- import * as path44 from "path";
17161
+ import * as path46 from "path";
16609
17162
  var LOG_LEVELS = ["log", "info", "warn", "warning", "error", "debug", "trace", "fatal"];
16610
17163
  function extractFromSource(input) {
16611
17164
  if (!/\.(?:ts|tsx|js|jsx)$/i.test(input.file)) return [];
@@ -16745,7 +17298,7 @@ function isLikelyLogger(name) {
16745
17298
  }
16746
17299
  function fileMatchesCliPaths(file, cliPaths) {
16747
17300
  const paths = cliPaths ?? defaultCliOutputPaths();
16748
- const normalized = file.split(path44.sep).join("/");
17301
+ const normalized = file.split(path46.sep).join("/");
16749
17302
  return paths.some((p) => normalized.includes(p));
16750
17303
  }
16751
17304
  function defaultCliOutputPaths() {
@@ -16765,8 +17318,8 @@ function cleanCommentText(raw) {
16765
17318
 
16766
17319
  // src/copy-craft/extract/commits.ts
16767
17320
  import { spawnSync } from "child_process";
16768
- import * as fs41 from "fs";
16769
- import * as path45 from "path";
17321
+ import * as fs44 from "fs";
17322
+ import * as path47 from "path";
16770
17323
  var GIT_TIMEOUT_MS = 1e4;
16771
17324
  function extractCommits(input) {
16772
17325
  const { projectRoot, since = "1 month ago", limit = 100 } = input;
@@ -16805,8 +17358,8 @@ function extractCommits(input) {
16805
17358
  function isGitRepo(projectRoot) {
16806
17359
  let dir = projectRoot;
16807
17360
  for (let i = 0; i < 10; i++) {
16808
- if (fs41.existsSync(path45.join(dir, ".git"))) return true;
16809
- const parent = path45.dirname(dir);
17361
+ if (fs44.existsSync(path47.join(dir, ".git"))) return true;
17362
+ const parent = path47.dirname(dir);
16810
17363
  if (parent === dir) break;
16811
17364
  dir = parent;
16812
17365
  }
@@ -17112,7 +17665,7 @@ async function runCopyCraft(input) {
17112
17665
  for (const file of files) {
17113
17666
  let source;
17114
17667
  try {
17115
- source = fs42.readFileSync(file, "utf-8");
17668
+ source = fs45.readFileSync(file, "utf-8");
17116
17669
  } catch {
17117
17670
  continue;
17118
17671
  }
@@ -17196,7 +17749,7 @@ async function runCopyCraft(input) {
17196
17749
  }
17197
17750
  function collectSourceFiles(projectRoot, explicitFiles) {
17198
17751
  if (explicitFiles !== void 0 && explicitFiles.length > 0) {
17199
- return explicitFiles.map((f) => path46.isAbsolute(f) ? f : path46.join(projectRoot, f));
17752
+ return explicitFiles.map((f) => path48.isAbsolute(f) ? f : path48.join(projectRoot, f));
17200
17753
  }
17201
17754
  const out = [];
17202
17755
  walk6(projectRoot, out, 0);
@@ -17206,7 +17759,7 @@ function walk6(dir, out, depth) {
17206
17759
  if (depth > 8) return;
17207
17760
  let entries;
17208
17761
  try {
17209
- entries = fs42.readdirSync(dir, { withFileTypes: true });
17762
+ entries = fs45.readdirSync(dir, { withFileTypes: true });
17210
17763
  } catch {
17211
17764
  return;
17212
17765
  }
@@ -17214,7 +17767,7 @@ function walk6(dir, out, depth) {
17214
17767
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
17215
17768
  continue;
17216
17769
  }
17217
- const full = path46.join(dir, entry.name);
17770
+ const full = path48.join(dir, entry.name);
17218
17771
  if (entry.isDirectory()) walk6(full, out, depth + 1);
17219
17772
  else if (entry.isFile() && SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext)))
17220
17773
  out.push(full);
@@ -17284,8 +17837,8 @@ async function handleCopyCraft(input) {
17284
17837
  }
17285
17838
 
17286
17839
  // src/test-craft/index.ts
17287
- import * as fs44 from "fs";
17288
- import * as path48 from "path";
17840
+ import * as fs47 from "fs";
17841
+ import * as path50 from "path";
17289
17842
  import { randomUUID as randomUUID6 } from "crypto";
17290
17843
 
17291
17844
  // src/test-craft/extract/framework.ts
@@ -17391,25 +17944,25 @@ function extractCallbackText(node, fullSource) {
17391
17944
  }
17392
17945
 
17393
17946
  // src/test-craft/extract/source-pair.ts
17394
- import * as fs43 from "fs";
17395
- import * as path47 from "path";
17947
+ import * as fs46 from "fs";
17948
+ import * as path49 from "path";
17396
17949
  var MAX_SOURCE_CHARS = 2e3;
17397
17950
  function resolveSourceFile(testFile) {
17398
- const dir = path47.dirname(testFile);
17951
+ const dir = path49.dirname(testFile);
17399
17952
  const ext = /\.tsx$/.test(testFile) ? ".tsx" : ".ts";
17400
- const base = path47.basename(testFile).replace(/\.(?:test|spec)\.(?:tsx?|jsx?)$/, "");
17953
+ const base = path49.basename(testFile).replace(/\.(?:test|spec)\.(?:tsx?|jsx?)$/, "");
17401
17954
  const candidates = [
17402
17955
  // Sibling (most common in co-located setups)
17403
- path47.join(dir, base + ext),
17956
+ path49.join(dir, base + ext),
17404
17957
  // tests/ peer to src/
17405
- path47.join(dir, "..", "src", base + ext),
17958
+ path49.join(dir, "..", "src", base + ext),
17406
17959
  // tests/ one level deeper than src/
17407
- path47.join(dir, "..", "..", "src", base + ext)
17960
+ path49.join(dir, "..", "..", "src", base + ext)
17408
17961
  ];
17409
17962
  for (const candidate of candidates) {
17410
- if (fs43.existsSync(candidate)) {
17963
+ if (fs46.existsSync(candidate)) {
17411
17964
  try {
17412
- const raw = fs43.readFileSync(candidate, "utf-8");
17965
+ const raw = fs46.readFileSync(candidate, "utf-8");
17413
17966
  const content = raw.length > MAX_SOURCE_CHARS ? raw.slice(0, MAX_SOURCE_CHARS) + "\n[\u2026truncated for prompt cost\u2026]" : raw;
17414
17967
  return { file: candidate, content };
17415
17968
  } catch {
@@ -17645,7 +18198,7 @@ async function runTestCraft(input) {
17645
18198
  for (const file of files) {
17646
18199
  let source;
17647
18200
  try {
17648
- source = fs44.readFileSync(file, "utf-8");
18201
+ source = fs47.readFileSync(file, "utf-8");
17649
18202
  } catch {
17650
18203
  continue;
17651
18204
  }
@@ -17710,7 +18263,7 @@ async function runTestCraft(input) {
17710
18263
  }
17711
18264
  function collectTestFiles(projectRoot, explicitFiles) {
17712
18265
  if (explicitFiles !== void 0 && explicitFiles.length > 0) {
17713
- return explicitFiles.map((f) => path48.isAbsolute(f) ? f : path48.join(projectRoot, f));
18266
+ return explicitFiles.map((f) => path50.isAbsolute(f) ? f : path50.join(projectRoot, f));
17714
18267
  }
17715
18268
  const out = [];
17716
18269
  walk7(projectRoot, out, 0);
@@ -17720,7 +18273,7 @@ function walk7(dir, out, depth) {
17720
18273
  if (depth > 10) return;
17721
18274
  let entries;
17722
18275
  try {
17723
- entries = fs44.readdirSync(dir, { withFileTypes: true });
18276
+ entries = fs47.readdirSync(dir, { withFileTypes: true });
17724
18277
  } catch {
17725
18278
  return;
17726
18279
  }
@@ -17728,7 +18281,7 @@ function walk7(dir, out, depth) {
17728
18281
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
17729
18282
  continue;
17730
18283
  }
17731
- const full = path48.join(dir, entry.name);
18284
+ const full = path50.join(dir, entry.name);
17732
18285
  if (entry.isDirectory()) walk7(full, out, depth + 1);
17733
18286
  else if (entry.isFile() && TEST_FILE_EXTS.some((ext) => entry.name.endsWith(ext))) {
17734
18287
  out.push(full);
@@ -17801,18 +18354,18 @@ async function handleTestCraft(input) {
17801
18354
  }
17802
18355
 
17803
18356
  // src/knowledge-craft/index.ts
17804
- import * as fs46 from "fs";
17805
- import * as path50 from "path";
18357
+ import * as fs49 from "fs";
18358
+ import * as path52 from "path";
17806
18359
  import { randomUUID as randomUUID7 } from "crypto";
17807
18360
 
17808
18361
  // src/knowledge-craft/extract/discover.ts
17809
- import * as fs45 from "fs";
17810
- import * as path49 from "path";
18362
+ import * as fs48 from "fs";
18363
+ import * as path51 from "path";
17811
18364
  var KNOWLEDGE_ROOT = "docs/knowledge";
17812
18365
  var DEFAULT_EXCLUDED_DIRS = ["decisions"];
17813
18366
  function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
17814
- const root = path49.join(projectRoot, KNOWLEDGE_ROOT);
17815
- if (!fs45.existsSync(root)) return [];
18367
+ const root = path51.join(projectRoot, KNOWLEDGE_ROOT);
18368
+ if (!fs48.existsSync(root)) return [];
17816
18369
  const exclude = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_DIRS, ...extraExcludeDirs ?? []]);
17817
18370
  const out = [];
17818
18371
  walk8(root, root, out, exclude);
@@ -17821,13 +18374,13 @@ function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
17821
18374
  function walk8(dir, root, out, exclude) {
17822
18375
  let entries;
17823
18376
  try {
17824
- entries = fs45.readdirSync(dir, { withFileTypes: true });
18377
+ entries = fs48.readdirSync(dir, { withFileTypes: true });
17825
18378
  } catch {
17826
18379
  return;
17827
18380
  }
17828
18381
  for (const entry of entries) {
17829
18382
  if (entry.name.startsWith(".")) continue;
17830
- const full = path49.join(dir, entry.name);
18383
+ const full = path51.join(dir, entry.name);
17831
18384
  if (entry.isDirectory()) {
17832
18385
  if (exclude.has(entry.name)) continue;
17833
18386
  walk8(full, root, out, exclude);
@@ -17836,7 +18389,7 @@ function walk8(dir, root, out, exclude) {
17836
18389
  if (!entry.isFile()) continue;
17837
18390
  if (!entry.name.endsWith(".md")) continue;
17838
18391
  if (entry.name.toLowerCase() === "readme.md") continue;
17839
- const rel = path49.relative(root, full).replaceAll("\\", "/");
18392
+ const rel = path51.relative(root, full).replaceAll("\\", "/");
17840
18393
  out.push({ file: full, relative: rel });
17841
18394
  }
17842
18395
  }
@@ -18016,7 +18569,7 @@ async function runKnowledgeCraft(input) {
18016
18569
  for (const entry of entries) {
18017
18570
  let content;
18018
18571
  try {
18019
- content = fs46.readFileSync(entry.file, "utf-8");
18572
+ content = fs49.readFileSync(entry.file, "utf-8");
18020
18573
  } catch {
18021
18574
  filesSkipped++;
18022
18575
  continue;
@@ -18057,10 +18610,10 @@ async function runKnowledgeCraft(input) {
18057
18610
  }
18058
18611
  function collectEntries(projectRoot, input) {
18059
18612
  if (input.files !== void 0 && input.files.length > 0) {
18060
- const root = path50.join(projectRoot, KNOWLEDGE_ROOT);
18613
+ const root = path52.join(projectRoot, KNOWLEDGE_ROOT);
18061
18614
  return input.files.map((f) => ({
18062
18615
  file: f,
18063
- relative: path50.relative(root, f).replaceAll("\\", "/")
18616
+ relative: path52.relative(root, f).replaceAll("\\", "/")
18064
18617
  }));
18065
18618
  }
18066
18619
  return discoverKnowledgeEntries(projectRoot, input.excludeDirs);
@@ -18122,12 +18675,12 @@ async function handleKnowledgeCraft(input) {
18122
18675
  }
18123
18676
 
18124
18677
  // src/security-craft/index.ts
18125
- import * as fs48 from "fs";
18678
+ import * as fs51 from "fs";
18126
18679
  import { randomUUID as randomUUID8 } from "crypto";
18127
18680
 
18128
18681
  // src/security-craft/extract/discover.ts
18129
- import * as fs47 from "fs";
18130
- import * as path51 from "path";
18682
+ import * as fs50 from "fs";
18683
+ import * as path53 from "path";
18131
18684
  var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
18132
18685
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
18133
18686
  "node_modules",
@@ -18147,12 +18700,12 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
18147
18700
  ]);
18148
18701
  var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/i;
18149
18702
  function discoverSourceFiles(projectRoot, packagesFilter) {
18150
- const packagesDir = path51.join(projectRoot, "packages");
18151
- if (!fs47.existsSync(packagesDir)) return [];
18703
+ const packagesDir = path53.join(projectRoot, "packages");
18704
+ if (!fs50.existsSync(packagesDir)) return [];
18152
18705
  const out = [];
18153
18706
  let entries;
18154
18707
  try {
18155
- entries = fs47.readdirSync(packagesDir, { withFileTypes: true });
18708
+ entries = fs50.readdirSync(packagesDir, { withFileTypes: true });
18156
18709
  } catch {
18157
18710
  return [];
18158
18711
  }
@@ -18160,8 +18713,8 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
18160
18713
  if (!entry.isDirectory()) continue;
18161
18714
  if (entry.name.startsWith(".")) continue;
18162
18715
  if (packagesFilter !== void 0 && !packagesFilter.includes(entry.name)) continue;
18163
- const srcDir = path51.join(packagesDir, entry.name, "src");
18164
- if (!fs47.existsSync(srcDir)) continue;
18716
+ const srcDir = path53.join(packagesDir, entry.name, "src");
18717
+ if (!fs50.existsSync(srcDir)) continue;
18165
18718
  walk9(srcDir, out);
18166
18719
  }
18167
18720
  return out;
@@ -18169,13 +18722,13 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
18169
18722
  function walk9(dir, out) {
18170
18723
  let entries;
18171
18724
  try {
18172
- entries = fs47.readdirSync(dir, { withFileTypes: true });
18725
+ entries = fs50.readdirSync(dir, { withFileTypes: true });
18173
18726
  } catch {
18174
18727
  return;
18175
18728
  }
18176
18729
  for (const entry of entries) {
18177
18730
  if (entry.name.startsWith(".")) continue;
18178
- const full = path51.join(dir, entry.name);
18731
+ const full = path53.join(dir, entry.name);
18179
18732
  if (entry.isDirectory()) {
18180
18733
  if (EXCLUDED_DIRS.has(entry.name)) continue;
18181
18734
  walk9(full, out);
@@ -18183,7 +18736,7 @@ function walk9(dir, out) {
18183
18736
  }
18184
18737
  if (!entry.isFile()) continue;
18185
18738
  if (TEST_FILE_PATTERN.test(entry.name)) continue;
18186
- const ext = path51.extname(entry.name);
18739
+ const ext = path53.extname(entry.name);
18187
18740
  if (!SOURCE_EXTENSIONS2.has(ext)) continue;
18188
18741
  out.push(full);
18189
18742
  }
@@ -18680,7 +19233,7 @@ async function runSecurityCraft(input) {
18680
19233
  for (const file of candidateFiles) {
18681
19234
  let source;
18682
19235
  try {
18683
- source = fs48.readFileSync(file, "utf-8");
19236
+ source = fs51.readFileSync(file, "utf-8");
18684
19237
  } catch {
18685
19238
  continue;
18686
19239
  }
@@ -18888,9 +19441,123 @@ function errorResponse(message) {
18888
19441
  };
18889
19442
  }
18890
19443
 
19444
+ // src/mcp/tools/acceptance-eval.ts
19445
+ import { readFile as readFile2 } from "fs/promises";
19446
+ var acceptanceEvalDefinition = {
19447
+ name: "acceptance_eval",
19448
+ description: "Pre-execution LLM-judgment: does a spec carry measurable, testable, complete acceptance criteria? The upstream twin of outcome_eval. Reads the spec's success/acceptance section, emits a confidence-rated AcceptanceVerdict (MEASURABLE | NOT_MEASURABLE | INCONCLUSIVE) with criteriaFindings (a, advisory), coverageFindings (b, advisory) and a rationale. Authority is DERIVED in TypeScript, never trusted from the LLM: a high-confidence NOT_MEASURABLE is blocking; every other verdict is advisory. testGlobs/testContent are optional evidence for (b) \u2014 omitting them degrades coverage findings to advisory-empty but never affects the measurability gate.",
19449
+ inputSchema: {
19450
+ type: "object",
19451
+ properties: {
19452
+ specPath: {
19453
+ type: "string",
19454
+ description: "Absolute or repo-relative path to the spec markdown to judge"
19455
+ },
19456
+ testGlobs: {
19457
+ type: "array",
19458
+ items: { type: "string" },
19459
+ description: "Optional globs locating test files; their contents supply the (b) coverage evidence. Ignored when testContent is provided. Absolute globs are recommended; relative globs resolve against the MCP server cwd."
19460
+ },
19461
+ testContent: {
19462
+ type: "string",
19463
+ description: "Optional pre-collected test snippets (the (b) evidence). Takes precedence over testGlobs."
19464
+ },
19465
+ model: {
19466
+ type: "string",
19467
+ description: "Optional model override for the acceptance-eval LLM call"
19468
+ }
19469
+ },
19470
+ required: ["specPath"]
19471
+ }
19472
+ };
19473
+ async function resolveAnalysisProvider2(model) {
19474
+ try {
19475
+ const intelligence = await import("@harness-engineering/intelligence");
19476
+ const apiKey = process.env.ANTHROPIC_API_KEY;
19477
+ if (!apiKey) return null;
19478
+ const Provider = intelligence.AnthropicAnalysisProvider;
19479
+ if (typeof Provider !== "function") return null;
19480
+ return new Provider(model !== void 0 ? { apiKey, defaultModel: model } : { apiKey });
19481
+ } catch {
19482
+ return null;
19483
+ }
19484
+ }
19485
+ function validateInput2(input) {
19486
+ if (typeof input?.specPath !== "string" || input.specPath.length === 0) {
19487
+ return "acceptance_eval: `specPath` is required";
19488
+ }
19489
+ return null;
19490
+ }
19491
+ async function resolveTestContent(input) {
19492
+ if (typeof input.testContent === "string" && input.testContent.length > 0) {
19493
+ return input.testContent;
19494
+ }
19495
+ if (!Array.isArray(input.testGlobs) || input.testGlobs.length === 0) {
19496
+ return void 0;
19497
+ }
19498
+ const seen = /* @__PURE__ */ new Set();
19499
+ const parts = [];
19500
+ for (const pattern of input.testGlobs) {
19501
+ const normalized = pattern.replace(/\\/g, "/");
19502
+ let files;
19503
+ try {
19504
+ files = await findFiles(normalized);
19505
+ } catch {
19506
+ continue;
19507
+ }
19508
+ for (const file of files) {
19509
+ if (seen.has(file)) continue;
19510
+ seen.add(file);
19511
+ try {
19512
+ parts.push(`// ${file}
19513
+ ${await readFile2(file, "utf8")}`);
19514
+ } catch {
19515
+ }
19516
+ }
19517
+ }
19518
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
19519
+ }
19520
+ async function handleAcceptanceEval(input) {
19521
+ const validationError = validateInput2(input);
19522
+ if (validationError !== null) return errorResponse2(validationError);
19523
+ try {
19524
+ const { AcceptanceEvaluator } = await import("@harness-engineering/intelligence");
19525
+ const provider = await resolveAnalysisProvider2(input.model);
19526
+ const evaluator = new AcceptanceEvaluator(
19527
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19528
+ provider ?? unconfiguredProvider2(),
19529
+ input.model !== void 0 ? { model: input.model } : {}
19530
+ );
19531
+ const testContent = await resolveTestContent(input);
19532
+ const verdict = await evaluator.evaluate({
19533
+ specPath: input.specPath,
19534
+ ...testContent !== void 0 && { testContent }
19535
+ });
19536
+ return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
19537
+ } catch (err2) {
19538
+ const message = err2 instanceof Error ? err2.message : String(err2);
19539
+ return errorResponse2(`acceptance_eval failed: ${message}`);
19540
+ }
19541
+ }
19542
+ function unconfiguredProvider2() {
19543
+ return {
19544
+ analyze: () => Promise.reject(
19545
+ new Error(
19546
+ "No analysis provider configured (set ANTHROPIC_API_KEY). Degrading to an inconclusive, advisory verdict."
19547
+ )
19548
+ )
19549
+ };
19550
+ }
19551
+ function errorResponse2(message) {
19552
+ return {
19553
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
19554
+ isError: true
19555
+ };
19556
+ }
19557
+
18891
19558
  // src/mcp/tools/strategy.ts
18892
- import * as fs49 from "fs/promises";
18893
- import * as path52 from "path";
19559
+ import * as fs52 from "fs/promises";
19560
+ import * as path54 from "path";
18894
19561
  var STRATEGY_FILENAME = "STRATEGY.md";
18895
19562
  function mcpJson(payload, isError = false) {
18896
19563
  return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
@@ -18916,7 +19583,7 @@ async function handleValidateStrategy(input) {
18916
19583
  } catch (error) {
18917
19584
  return mcpError2(error instanceof Error ? error.message : String(error));
18918
19585
  }
18919
- const { validateStrategy } = await import("./dist-3HW4LCHE.js");
19586
+ const { validateStrategy } = await import("./dist-IPVFYGY2.js");
18920
19587
  const result = await validateStrategy(projectPath);
18921
19588
  if (!result.ok) {
18922
19589
  return mcpJson({ present: true, valid: false, error: result.error.message });
@@ -18941,7 +19608,7 @@ async function handleReadStrategy(input) {
18941
19608
  } catch (error) {
18942
19609
  return mcpError2(error instanceof Error ? error.message : String(error));
18943
19610
  }
18944
- const core = await import("./dist-3HW4LCHE.js");
19611
+ const core = await import("./dist-IPVFYGY2.js");
18945
19612
  const validation = await core.validateStrategy(projectPath);
18946
19613
  if (!validation.ok) {
18947
19614
  return mcpJson({ present: true, valid: false, error: validation.error.message });
@@ -18949,10 +19616,10 @@ async function handleReadStrategy(input) {
18949
19616
  if (!validation.value.present) {
18950
19617
  return mcpJson({ present: false, valid: true });
18951
19618
  }
18952
- const strategyPath = path52.join(projectPath, STRATEGY_FILENAME);
19619
+ const strategyPath = path54.join(projectPath, STRATEGY_FILENAME);
18953
19620
  let raw;
18954
19621
  try {
18955
- raw = await fs49.readFile(strategyPath, "utf-8");
19622
+ raw = await fs52.readFile(strategyPath, "utf-8");
18956
19623
  } catch (error) {
18957
19624
  return mcpError2(error instanceof Error ? error.message : String(error));
18958
19625
  }
@@ -18993,7 +19660,7 @@ async function handleWriteStrategy(input) {
18993
19660
  } catch (error) {
18994
19661
  return mcpError2(error instanceof Error ? error.message : String(error));
18995
19662
  }
18996
- const core = await import("./dist-3HW4LCHE.js");
19663
+ const core = await import("./dist-IPVFYGY2.js");
18997
19664
  try {
18998
19665
  core.writeStrategyDoc(input.doc, {
18999
19666
  cwd: projectPath,
@@ -19008,11 +19675,11 @@ async function handleWriteStrategy(input) {
19008
19675
  true
19009
19676
  );
19010
19677
  }
19011
- return mcpJson({ written: true, path: path52.join(projectPath, STRATEGY_FILENAME) });
19678
+ return mcpJson({ written: true, path: path54.join(projectPath, STRATEGY_FILENAME) });
19012
19679
  }
19013
19680
 
19014
19681
  // src/mcp/tools/pulse.ts
19015
- import * as path53 from "path";
19682
+ import * as path55 from "path";
19016
19683
  function mcpJson2(payload, isError = false) {
19017
19684
  return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
19018
19685
  }
@@ -19037,7 +19704,7 @@ async function handleSeedPulseFromStrategy(input) {
19037
19704
  } catch (error) {
19038
19705
  return mcpError3(error instanceof Error ? error.message : String(error));
19039
19706
  }
19040
- const core = await import("./dist-3HW4LCHE.js");
19707
+ const core = await import("./dist-IPVFYGY2.js");
19041
19708
  const seed = core.seedFromStrategy({ cwd: projectPath });
19042
19709
  return mcpJson2(seed);
19043
19710
  }
@@ -19071,8 +19738,8 @@ async function handleWritePulseConfig(input) {
19071
19738
  } catch (error) {
19072
19739
  return mcpError3(error instanceof Error ? error.message : String(error));
19073
19740
  }
19074
- const resolvedConfigPath = input.configPath !== void 0 ? path53.isAbsolute(input.configPath) ? input.configPath : path53.join(projectPath, input.configPath) : path53.join(projectPath, "harness.config.json");
19075
- const core = await import("./dist-3HW4LCHE.js");
19741
+ const resolvedConfigPath = input.configPath !== void 0 ? path55.isAbsolute(input.configPath) ? input.configPath : path55.join(projectPath, input.configPath) : path55.join(projectPath, "harness.config.json");
19742
+ const core = await import("./dist-IPVFYGY2.js");
19076
19743
  try {
19077
19744
  core.writePulseConfig(input.config, {
19078
19745
  configPath: resolvedConfigPath,
@@ -19121,7 +19788,7 @@ async function handleAcquireCompoundLock(input) {
19121
19788
  } catch (error) {
19122
19789
  return mcpError4(error instanceof Error ? error.message : String(error));
19123
19790
  }
19124
- const core = await import("./dist-3HW4LCHE.js");
19791
+ const core = await import("./dist-IPVFYGY2.js");
19125
19792
  try {
19126
19793
  const handle = core.acquireCompoundLock(
19127
19794
  input.category,
@@ -19282,6 +19949,7 @@ var TOOL_DEFINITIONS = [
19282
19949
  knowledgeCraftDefinition,
19283
19950
  securityCraftDefinition,
19284
19951
  outcomeEvalDefinition,
19952
+ acceptanceEvalDefinition,
19285
19953
  validateStrategyDefinition,
19286
19954
  readStrategyDefinition,
19287
19955
  writeStrategyDefinition,
@@ -19375,6 +20043,7 @@ var TOOL_HANDLERS = {
19375
20043
  knowledge_craft: handleKnowledgeCraft,
19376
20044
  security_craft: handleSecurityCraft,
19377
20045
  outcome_eval: handleOutcomeEval,
20046
+ acceptance_eval: handleAcceptanceEval,
19378
20047
  validate_strategy: handleValidateStrategy,
19379
20048
  read_strategy: handleReadStrategy,
19380
20049
  write_strategy: handleWriteStrategy,
@@ -19483,7 +20152,7 @@ async function appendUpdateNotification(result, resolvedRoot) {
19483
20152
  shouldRunCheck,
19484
20153
  readCheckState,
19485
20154
  spawnBackgroundCheck
19486
- } = await import("./dist-3HW4LCHE.js");
20155
+ } = await import("./dist-IPVFYGY2.js");
19487
20156
  const { CLI_VERSION } = await import("./version-CUI434OP.js");
19488
20157
  const configInterval = readConfigInterval(resolvedRoot);
19489
20158
  const DEFAULT_INTERVAL = 864e5;