agentic-mermaid 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/CHANGELOG.md +144 -4
  2. package/LICENSES/Apache-2.0.txt +202 -0
  3. package/README.md +25 -32
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/dist/agent-core.d.ts +463 -0
  6. package/dist/agent-core.js +397 -0
  7. package/dist/agent.d.ts +73 -1868
  8. package/dist/agent.js +336 -37325
  9. package/dist/agentic-mermaid-mcp.js +7 -38399
  10. package/dist/am.js +33632 -40445
  11. package/dist/chunk-4QECACIA.js +4702 -0
  12. package/dist/chunk-CDPKJ2BQ.js +37 -0
  13. package/dist/chunk-QKDLMOJJ.js +3487 -0
  14. package/dist/chunk-WI35L7IR.js +615 -0
  15. package/dist/chunk-WN3YDOBG.js +149 -0
  16. package/dist/chunk-ZYMMKQ3I.js +62637 -0
  17. package/dist/index-D54dqSdC.d.ts +6777 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +310 -33014
  20. package/dist/mcp-cli-SBQSTJFC.js +17 -0
  21. package/docs/README.md +14 -6
  22. package/docs/schemas/style-spec.schema.json +1221 -30
  23. package/examples/styles/catalog.json +54 -0
  24. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  25. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  26. package/examples/styles/cupertino-prototype.mmd +10 -0
  27. package/examples/styles/cupertino-prototype.style.json +63 -0
  28. package/examples/styles/mid-century-report.style.json +2 -2
  29. package/examples/styles/star-chart-atlas.style.json +2 -1
  30. package/examples/styles/transit-route-map.style.json +2 -2
  31. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  32. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  33. package/package.json +68 -43
  34. package/server.json +2 -2
  35. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  36. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  37. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  38. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  39. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  40. package/AGENT_NATIVE.md +0 -607
  41. package/Instructions_for_agents.md +0 -89
  42. package/TODO.md +0 -684
  43. package/assets/hero.png +0 -0
  44. package/dist/index-wjRjbsP-.d.ts +0 -1102
  45. package/docs/agent-api-cookbook.md +0 -308
  46. package/docs/agent-mutation-policy.md +0 -104
  47. package/docs/agent-workflow-examples.md +0 -46
  48. package/docs/api.md +0 -211
  49. package/docs/ascii.md +0 -103
  50. package/docs/assets/improvements/box-start-connector.png +0 -0
  51. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  52. package/docs/assets/improvements/er-content-loss.png +0 -0
  53. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  54. package/docs/assets/improvements/gantt-family.png +0 -0
  55. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  56. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  57. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  58. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  59. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  60. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  61. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  62. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  63. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  64. package/docs/comparison.md +0 -94
  65. package/docs/config.md +0 -105
  66. package/docs/contributing/adding-diagram-types.md +0 -144
  67. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  68. package/docs/contributing/diagram-family-citizenship.md +0 -82
  69. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  70. package/docs/contributing/lessons-learned.md +0 -276
  71. package/docs/contributing/releasing.md +0 -82
  72. package/docs/contributing/visual-review-evidence.md +0 -53
  73. package/docs/custom-style-cookbook.md +0 -154
  74. package/docs/design/families/README.md +0 -26
  75. package/docs/design/families/architecture-beta.md +0 -48
  76. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  77. package/docs/design/families/gantt-research.md +0 -137
  78. package/docs/design/families/gantt.md +0 -329
  79. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  80. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  81. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  82. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  83. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  84. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  85. package/docs/design/families/journey-migration-parity.md +0 -439
  86. package/docs/design/families/journey-section-overlap-after.png +0 -0
  87. package/docs/design/families/journey-section-overlap-before.png +0 -0
  88. package/docs/design/families/journey-usage-research.md +0 -423
  89. package/docs/design/families/journey.md +0 -153
  90. package/docs/design/families/xychart.md +0 -85
  91. package/docs/design/system/README.md +0 -45
  92. package/docs/design/system/abstraction-audit.md +0 -224
  93. package/docs/design/system/abstraction-recommendations.md +0 -655
  94. package/docs/design/system/architecture.mmd +0 -29
  95. package/docs/design/system/architecture.svg +0 -137
  96. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  97. package/docs/design/system/layout-rubric.md +0 -138
  98. package/docs/design/system/route-contracts.md +0 -848
  99. package/docs/design/system/source-preservation-ladder.md +0 -52
  100. package/docs/design/system/styles-rollout.md +0 -216
  101. package/docs/design/system/ugly-layouts.md +0 -119
  102. package/docs/diagram-families.md +0 -193
  103. package/docs/features.md +0 -147
  104. package/docs/fork-differences.md +0 -130
  105. package/docs/getting-started.md +0 -175
  106. package/docs/issue-derived-test-cases.md +0 -156
  107. package/docs/layout-characterization/README.md +0 -245
  108. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  109. package/docs/layout-characterization/contact-sheet.md +0 -638
  110. package/docs/layout-characterization/properties.md +0 -281
  111. package/docs/layout-characterization/visual-quality.md +0 -160
  112. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  113. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  114. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  115. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  116. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  117. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  118. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  119. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  120. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  121. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  122. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  123. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  124. package/docs/mcp-code-mode-rationale.md +0 -101
  125. package/docs/mcp-http-transport.md +0 -223
  126. package/docs/mermaid-layout-complaints.md +0 -837
  127. package/docs/mutation-testing.md +0 -206
  128. package/docs/project/divergences.md +0 -98
  129. package/docs/project/dogfooding-docs-strategy.md +0 -66
  130. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  131. package/docs/project/lessons-learned.md +0 -1019
  132. package/docs/project/remove-role-styling-plan.md +0 -176
  133. package/docs/quality.md +0 -215
  134. package/docs/react.md +0 -97
  135. package/docs/style-authoring.md +0 -133
  136. package/docs/testing-strategy.md +0 -335
  137. package/docs/theming.md +0 -162
  138. package/examples/agent-improve-auth-flow.ts +0 -206
  139. package/examples/agent-loop.ts +0 -53
  140. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  141. package/llms.txt +0 -104
  142. package/skill-evals/README.md +0 -77
  143. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  144. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  145. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  146. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  147. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  148. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  149. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  150. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  151. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  152. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  153. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  154. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  155. package/skill-evals/shared-benchmark.json +0 -510
  156. package/src/agent/analyze.ts +0 -157
  157. package/src/agent/apply.ts +0 -146
  158. package/src/agent/architecture-body.ts +0 -458
  159. package/src/agent/certificates.ts +0 -110
  160. package/src/agent/class-body.ts +0 -319
  161. package/src/agent/core.ts +0 -207
  162. package/src/agent/create.ts +0 -106
  163. package/src/agent/describe.ts +0 -368
  164. package/src/agent/er-body.ts +0 -224
  165. package/src/agent/facts.ts +0 -360
  166. package/src/agent/families-builtin.ts +0 -920
  167. package/src/agent/families.ts +0 -232
  168. package/src/agent/family-layouts.ts +0 -832
  169. package/src/agent/flowchart-body.ts +0 -372
  170. package/src/agent/flowchart-unsupported.ts +0 -243
  171. package/src/agent/font-coverage.ts +0 -288
  172. package/src/agent/gantt-body.ts +0 -495
  173. package/src/agent/index.ts +0 -10
  174. package/src/agent/journey-body.ts +0 -374
  175. package/src/agent/label-metrics.ts +0 -28
  176. package/src/agent/layout-to-rendered.ts +0 -63
  177. package/src/agent/mutate.ts +0 -97
  178. package/src/agent/mutation-ops.ts +0 -51
  179. package/src/agent/op-schema.ts +0 -443
  180. package/src/agent/parse.ts +0 -223
  181. package/src/agent/pie-body.ts +0 -270
  182. package/src/agent/png.ts +0 -171
  183. package/src/agent/quadrant-body.ts +0 -336
  184. package/src/agent/quality.ts +0 -363
  185. package/src/agent/readability-audit.ts +0 -92
  186. package/src/agent/rendered-route-audit.ts +0 -61
  187. package/src/agent/sequence-body.ts +0 -345
  188. package/src/agent/serialize.ts +0 -214
  189. package/src/agent/state-body.ts +0 -589
  190. package/src/agent/structural-count.ts +0 -150
  191. package/src/agent/timeline-body.ts +0 -280
  192. package/src/agent/trace-log.ts +0 -43
  193. package/src/agent/types.ts +0 -995
  194. package/src/agent/verify.ts +0 -603
  195. package/src/agent/xychart-body.ts +0 -472
  196. package/src/architecture/config.ts +0 -282
  197. package/src/architecture/layout.ts +0 -496
  198. package/src/architecture/parser.ts +0 -375
  199. package/src/architecture/renderer.ts +0 -611
  200. package/src/architecture/types.ts +0 -113
  201. package/src/ascii/ansi.ts +0 -423
  202. package/src/ascii/architecture.ts +0 -210
  203. package/src/ascii/canvas.ts +0 -443
  204. package/src/ascii/class-diagram.ts +0 -697
  205. package/src/ascii/converter.ts +0 -475
  206. package/src/ascii/draw.ts +0 -1693
  207. package/src/ascii/edge-bundling.ts +0 -328
  208. package/src/ascii/edge-routing.ts +0 -330
  209. package/src/ascii/er-diagram.ts +0 -439
  210. package/src/ascii/gantt.ts +0 -276
  211. package/src/ascii/grid.ts +0 -747
  212. package/src/ascii/index.ts +0 -196
  213. package/src/ascii/journey.ts +0 -169
  214. package/src/ascii/meta.ts +0 -244
  215. package/src/ascii/multiline-utils.ts +0 -80
  216. package/src/ascii/pathfinder.ts +0 -301
  217. package/src/ascii/pie.ts +0 -67
  218. package/src/ascii/quadrant.ts +0 -158
  219. package/src/ascii/reverse.ts +0 -216
  220. package/src/ascii/sequence.ts +0 -499
  221. package/src/ascii/shapes/circle.ts +0 -27
  222. package/src/ascii/shapes/corners.ts +0 -143
  223. package/src/ascii/shapes/diamond.ts +0 -27
  224. package/src/ascii/shapes/hexagon.ts +0 -27
  225. package/src/ascii/shapes/index.ts +0 -106
  226. package/src/ascii/shapes/rectangle.ts +0 -168
  227. package/src/ascii/shapes/rounded.ts +0 -27
  228. package/src/ascii/shapes/special.ts +0 -332
  229. package/src/ascii/shapes/stadium.ts +0 -110
  230. package/src/ascii/shapes/state.ts +0 -192
  231. package/src/ascii/shapes/types.ts +0 -73
  232. package/src/ascii/timeline.ts +0 -141
  233. package/src/ascii/types.ts +0 -312
  234. package/src/ascii/validate.ts +0 -120
  235. package/src/ascii/width.ts +0 -81
  236. package/src/ascii/wrap.ts +0 -131
  237. package/src/ascii/xychart.ts +0 -919
  238. package/src/browser.ts +0 -29
  239. package/src/class/layout.ts +0 -240
  240. package/src/class/parser.ts +0 -332
  241. package/src/class/renderer.ts +0 -638
  242. package/src/class/types.ts +0 -129
  243. package/src/cli/agent-instructions.ts +0 -1
  244. package/src/cli/am-bin.ts +0 -6
  245. package/src/cli/exit-codes.ts +0 -18
  246. package/src/cli/index.ts +0 -1132
  247. package/src/cli/init-agent.ts +0 -147
  248. package/src/cli/run-entrypoint.ts +0 -10
  249. package/src/color-resolver.ts +0 -152
  250. package/src/elk-instance.ts +0 -114
  251. package/src/er/layout.ts +0 -187
  252. package/src/er/parser.ts +0 -226
  253. package/src/er/renderer.ts +0 -792
  254. package/src/er/types.ts +0 -99
  255. package/src/family-rubric.ts +0 -425
  256. package/src/font-manifest.ts +0 -31
  257. package/src/gantt/layout.ts +0 -365
  258. package/src/gantt/parser.ts +0 -282
  259. package/src/gantt/pipeline.ts +0 -27
  260. package/src/gantt/renderer.ts +0 -414
  261. package/src/gantt/schedule.ts +0 -513
  262. package/src/gantt/types.ts +0 -255
  263. package/src/index.ts +0 -368
  264. package/src/journey/layout.ts +0 -554
  265. package/src/journey/parse-core.ts +0 -251
  266. package/src/journey/parser.ts +0 -101
  267. package/src/journey/renderer.ts +0 -917
  268. package/src/journey/types.ts +0 -144
  269. package/src/layout/geometry.ts +0 -182
  270. package/src/layout/pass.ts +0 -142
  271. package/src/layout/passes/index.ts +0 -3916
  272. package/src/layout-engine.ts +0 -1565
  273. package/src/layout-rubric.ts +0 -659
  274. package/src/mcp/artifacts.ts +0 -137
  275. package/src/mcp/dynamic-harness.ts +0 -31
  276. package/src/mcp/dynamic-modules.d.ts +0 -7
  277. package/src/mcp/facade.ts +0 -608
  278. package/src/mcp/harness-runtime.ts +0 -203
  279. package/src/mcp/hosted-server.ts +0 -468
  280. package/src/mcp/mcp-bin.ts +0 -3
  281. package/src/mcp/mcp-cli.ts +0 -82
  282. package/src/mcp/protocol.ts +0 -13
  283. package/src/mcp/sandbox.ts +0 -195
  284. package/src/mcp/sdk-decl.ts +0 -391
  285. package/src/mcp/server.ts +0 -362
  286. package/src/mcp/tool-surface.ts +0 -146
  287. package/src/mermaid-source.ts +0 -655
  288. package/src/multiline-utils.ts +0 -228
  289. package/src/parser.ts +0 -1031
  290. package/src/pie/layout.ts +0 -168
  291. package/src/pie/parser.ts +0 -136
  292. package/src/pie/renderer.ts +0 -234
  293. package/src/pie/types.ts +0 -82
  294. package/src/quadrant/layout.ts +0 -191
  295. package/src/quadrant/parser.ts +0 -183
  296. package/src/quadrant/renderer.ts +0 -322
  297. package/src/quadrant/types.ts +0 -113
  298. package/src/render-family-hooks.ts +0 -244
  299. package/src/renderer.ts +0 -1099
  300. package/src/route-contracts.ts +0 -2723
  301. package/src/scene/backend.ts +0 -76
  302. package/src/scene/fidelity.ts +0 -183
  303. package/src/scene/hybrid-backend.ts +0 -174
  304. package/src/scene/ir.ts +0 -181
  305. package/src/scene/marks.ts +0 -127
  306. package/src/scene/rough-backend.ts +0 -378
  307. package/src/scene/seed.ts +0 -46
  308. package/src/scene/style-registry.ts +0 -556
  309. package/src/sequence/layout.ts +0 -456
  310. package/src/sequence/parser.ts +0 -255
  311. package/src/sequence/renderer.ts +0 -604
  312. package/src/sequence/types.ts +0 -154
  313. package/src/shape-clipping.ts +0 -351
  314. package/src/shared/accessibility-directives.ts +0 -22
  315. package/src/shared/batched.ts +0 -61
  316. package/src/shared/color-math.ts +0 -93
  317. package/src/shared/svg-a11y.ts +0 -29
  318. package/src/shared/syntax-error.ts +0 -17
  319. package/src/shared/unicode-ranges.ts +0 -135
  320. package/src/styles.ts +0 -318
  321. package/src/svg-paths.ts +0 -28
  322. package/src/text-metrics.ts +0 -248
  323. package/src/theme.ts +0 -710
  324. package/src/timeline/layout.ts +0 -381
  325. package/src/timeline/parser.ts +0 -215
  326. package/src/timeline/renderer.ts +0 -538
  327. package/src/timeline/types.ts +0 -105
  328. package/src/types.ts +0 -462
  329. package/src/website-worker-modules.d.ts +0 -18
  330. package/src/xychart/axis-utils.ts +0 -109
  331. package/src/xychart/colors.ts +0 -124
  332. package/src/xychart/config.ts +0 -88
  333. package/src/xychart/layout.ts +0 -656
  334. package/src/xychart/parser.ts +0 -504
  335. package/src/xychart/renderer.ts +0 -640
  336. package/src/xychart/types.ts +0 -282
@@ -0,0 +1,4702 @@
1
+ import {
2
+ BRAND_CONSTRAINT_DESCRIPTORS,
3
+ BRAND_CONSTRAINT_WARNING_POLICY,
4
+ DEFAULT_LABEL_CHAR_CAP,
5
+ FAMILY_CAPABILITY_COLUMNS,
6
+ FAMILY_CONFORMANCE_VERSION,
7
+ FONT_SIZES,
8
+ FONT_WEIGHTS,
9
+ FamilyLayoutError,
10
+ GANTT_TODAY_MARKER_STYLE_PROPS,
11
+ MAX_RADAR_TICKS,
12
+ MIX,
13
+ MermaidFamilyDetectionError,
14
+ ParsedDiagramFamilyMismatchError,
15
+ RESOLVED_TERMINAL_COLOR_MODES,
16
+ WARNING_SEVERITY,
17
+ accessibilityFromBody,
18
+ applyGanttFrontmatterConfig,
19
+ assertFamilyScopedRenderOptionDeclaration,
20
+ auditRouteContracts,
21
+ builtinFamilyMetadata,
22
+ classifyRoutes,
23
+ compareCodePointStrings,
24
+ convertToAsciiGraph,
25
+ createMapping,
26
+ emitResolvedConfigDiagnostics,
27
+ emptyRenderedLayout,
28
+ ensureAccessibilityLines,
29
+ erUnsupportedSyntaxWarnings,
30
+ err,
31
+ executeGraphicalRequest,
32
+ expandInlineNamespaceStatement,
33
+ extractLabelsGeneric,
34
+ familyConfigDiagnostics,
35
+ familyDetectionDiagnosticFromPreservedBody,
36
+ findRouteHitches,
37
+ flowchartUnsupportedSyntaxWarnings,
38
+ flowchartV11ShapeNames,
39
+ formatGanttInstant,
40
+ ganttGeometryWarnings,
41
+ ganttScheduleWarning,
42
+ geometryBounds,
43
+ getFamily,
44
+ graphemes,
45
+ isSafeActionHref,
46
+ labelOverflowWarning,
47
+ layoutGeometryWarnings,
48
+ layoutResolvedFamilyToRendered,
49
+ lowerPositionedFamilyScene,
50
+ measureMultilineText,
51
+ normalizeBrTags,
52
+ normalizeMermaidSource,
53
+ normalizeV11Shape,
54
+ ok,
55
+ parseActorLinks,
56
+ parseClassDeclaration,
57
+ parseClassDiagram,
58
+ parseClassInteraction,
59
+ parseClassReference,
60
+ parseGanttModel,
61
+ parseMermaid,
62
+ parseRegisteredMermaid,
63
+ parseTodayMarkerStyle,
64
+ plainTextFromInlineFormatting,
65
+ positionFamilyArtifact,
66
+ positionResolvedFamily,
67
+ prepareRenderInput,
68
+ receiptOf,
69
+ renderContractDigest,
70
+ renderMermaidASCII,
71
+ renderMermaidASCIIWithReceipt,
72
+ renderMermaidSVG,
73
+ renderMermaidSVGWithReceipt,
74
+ renderPortablePngGraphicalProjection,
75
+ renderPositionedMermaidSVG,
76
+ resolveGanttSchedule,
77
+ resolveRenderRequestForExecution,
78
+ sameExtensionIdentity,
79
+ sanitizeTerminalText,
80
+ sceneNodePrimitives,
81
+ sequenceMessageContexts,
82
+ sequenceMessages,
83
+ serializeMermaid,
84
+ splitFlowchartStatements,
85
+ stageFamilyCandidateForConformance,
86
+ stateBodyToGraph,
87
+ toFinite,
88
+ toHex,
89
+ toMermaidLines,
90
+ tryParseCssColor,
91
+ unknownOpMessage,
92
+ visualWidth,
93
+ wcagCssContrastRatio,
94
+ wrapperPrefix
95
+ } from "./chunk-ZYMMKQ3I.js";
96
+
97
+ // src/agent/trace-log.ts
98
+ var appendFn;
99
+ function resolveAppend() {
100
+ if (appendFn !== void 0) return appendFn;
101
+ appendFn = null;
102
+ try {
103
+ const proc = globalThis.process;
104
+ const fs = proc?.getBuiltinModule?.("node:fs");
105
+ if (typeof fs?.appendFileSync === "function") appendFn = fs.appendFileSync;
106
+ } catch {
107
+ }
108
+ return appendFn;
109
+ }
110
+ function logToolInvocation(verb, ok2) {
111
+ const path = globalThis.process?.env?.AM_TRACE_LOG;
112
+ if (!path) return;
113
+ const record = ok2 === void 0 ? { verb } : { verb, ok: ok2 };
114
+ try {
115
+ resolveAppend()?.(path, JSON.stringify(record) + "\n");
116
+ } catch {
117
+ }
118
+ }
119
+
120
+ // src/agent/op-schema.ts
121
+ var NODE_SHAPES = [
122
+ "rectangle",
123
+ "service",
124
+ "rounded",
125
+ "diamond",
126
+ "stadium",
127
+ "circle",
128
+ "subroutine",
129
+ "doublecircle",
130
+ "hexagon",
131
+ "cylinder",
132
+ "asymmetric",
133
+ "trapezoid",
134
+ "trapezoid-alt",
135
+ "lean-r",
136
+ "lean-l",
137
+ "state-start",
138
+ "state-end"
139
+ ];
140
+ var EDGE_STYLES = ["solid", "dotted", "thick", "invisible"];
141
+ var SEQUENCE_MESSAGE_STYLES = ["sync", "reply", "async", "async-dashed", "lost", "lost-dashed"];
142
+ var PARTICIPANT_KINDS = ["participant", "actor"];
143
+ var SEQUENCE_FRAGMENT_KINDS = ["alt", "opt", "loop", "par"];
144
+ var CLASS_REL_KINDS = ["inheritance", "composition", "aggregation", "association", "dependency", "realization", "link-solid", "link-dashed"];
145
+ var ER_CARDINALITIES = ["one-only", "zero-or-one", "zero-or-many", "one-or-many"];
146
+ var ARCH_SIDES = ["L", "R", "T", "B"];
147
+ var ARCH_BOUNDARIES = ["item", "group"];
148
+ var SERIES_KINDS = ["bar", "line"];
149
+ var AXIS_KINDS = ["x", "y"];
150
+ var GANTT_STATUSES = ["active", "done", "crit"];
151
+ var MINDMAP_SHAPES = ["default", "rect", "rounded", "circle", "cloud", "bang", "hexagon"];
152
+ var GIT_COMMIT_TYPES = ["NORMAL", "REVERSE", "HIGHLIGHT"];
153
+ var GRATICULE_KINDS = ["circle", "polygon"];
154
+ var str = (required = true) => ({ type: "string", required });
155
+ var num = (required = true) => ({ type: "number", required });
156
+ var bool = (required = true) => ({ type: "boolean", required });
157
+ var numOrNull = (required = true) => ({ type: "number-or-null", required });
158
+ var boolOrNull = (required = true) => ({ type: "boolean-or-null", required });
159
+ var strOrNull = (required = true) => ({ type: "string-or-null", required });
160
+ var strArr = (required = true) => ({ type: "string-array", required });
161
+ var numArr = (required = true) => ({ type: "number-array", required });
162
+ var objOrNull = (required = true) => ({ type: "object-or-null", required });
163
+ var oneOf = (values, required = true, nullable = false) => ({ type: "enum", values, nullable, required });
164
+ var withNote = (spec, note) => ({ ...spec, note });
165
+ var FLOWCHART_SHAPE_VALUES = [
166
+ ...NODE_SHAPES,
167
+ ...flowchartV11ShapeNames().filter((name) => !NODE_SHAPES.includes(name))
168
+ ];
169
+ var FLOWCHART_DIRECTIONS = ["TD", "TB", "LR", "BT", "RL"];
170
+ var FLOWCHART_SCHEMA = {
171
+ add_node: { fields: { id: str(), label: str(), shape: withNote(oneOf(FLOWCHART_SHAPE_VALUES, false), "default: rectangle; also accepts Mermaid v11 @{ shape } names/aliases (e.g. manual-input)"), parent: str(false) } },
172
+ remove_node: { fields: { id: str() } },
173
+ rename_node: { fields: { from: str(), to: str() } },
174
+ set_label: { fields: { target: withNote(str(), 'a node id, an authored edge ID (e1), or "from->to"/"from->to#k"'), label: str() } },
175
+ add_edge: { fields: { from: str(), to: str(), label: str(false), style: withNote(oneOf(EDGE_STYLES, false), "default: solid") } },
176
+ remove_edge: { fields: { id: withNote(str(), 'an authored edge ID (e1), or "from->to"/"from->to#k" for the k-th parallel edge') } },
177
+ set_shape: { fields: { id: str(), shape: withNote(oneOf(FLOWCHART_SHAPE_VALUES), "a geometry name or a Mermaid v11 @{ shape } name/alias; v11 names render with the documented geometry mapping and serialize with the authored spelling") } },
178
+ set_direction: { fields: { direction: oneOf(FLOWCHART_DIRECTIONS), subgraph: withNote(str(false), "omit to set the diagram direction; a subgraph id sets that subgraph's direction override") } },
179
+ add_subgraph: { fields: { id: str(), label: str(false), parent: withNote(str(false), "nest inside this subgraph"), members: withNote(strArr(false), "existing node ids MOVED into the new subgraph") } },
180
+ remove_subgraph: { fields: { id: str(), removeMembers: withNote(bool(false), "default false: dissolve \u2014 members move to the parent scope; true also removes member nodes and their edges") } },
181
+ move_node: { fields: { id: str(), subgraph: withNote(strOrNull(), "target subgraph id; null moves the node to the top level") } },
182
+ define_class: { fields: { name: str(), style: withNote(str(), 'CSS-like pairs, e.g. "fill:#f96,stroke:#333,stroke-width:2px"') } },
183
+ set_node_class: { fields: { id: str(), className: withNote(strOrNull(), "assigns a classDef name (define_class or source classDef); null removes the assignment") } },
184
+ set_node_style: { fields: { id: str(), style: withNote(strOrNull(), 'inline style pairs, e.g. "fill:#bbf"; null clears') } }
185
+ };
186
+ var STATE_DIRECTIONS = ["TD", "TB", "LR", "BT", "RL"];
187
+ var STATE_NOTE_SIDES = ["left", "right"];
188
+ var STATE_SCHEMA = {
189
+ add_state: { fields: { id: str(), label: strOrNull(false), parent: strOrNull(false), region: num(false) } },
190
+ remove_state: { fields: { id: str(), recursive: withNote(bool(false), "default false: refuse a non-empty composite; true removes the whole subtree (transitions + notes cascade)") } },
191
+ rename_state: { fields: { from: str(), to: str() } },
192
+ set_state_label: { fields: { id: str(), label: strOrNull() } },
193
+ add_transition: { fields: { from: withNote(str(), 'a state id, "[*]", or a history ref like "X[H]"'), to: withNote(str(), 'a state id, "[*]", or a history ref like "X[H]"'), label: strOrNull(false), parent: strOrNull(false), region: num(false) } },
194
+ remove_transition: { fields: { index: num(false), from: str(false), to: str(false), parent: strOrNull(false), region: num(false) }, requireOneOf: ["index", "from", "to"] },
195
+ set_transition_label: { fields: { index: num(false), from: str(false), to: str(false), label: strOrNull(), parent: strOrNull(false), region: num(false) }, requireOneOf: ["index", "from", "to"] },
196
+ make_composite: { fields: { id: str(), members: strArr(), label: strOrNull(false) } },
197
+ set_direction: { fields: { direction: oneOf(STATE_DIRECTIONS), state: withNote(strOrNull(false), "omit to set the diagram direction; a composite id sets that composite's direction override") } },
198
+ move_state: { fields: { id: str(), parent: withNote(strOrNull(), "target composite id; null moves the state to the top level"), region: num(false) } },
199
+ dissolve_composite: { fields: { id: withNote(str(), "children and inner transitions hoist into the parent scope; rejects while transitions/notes still reference the composite") } },
200
+ add_note: { fields: { target: str(), side: withNote(oneOf(STATE_NOTE_SIDES, false), "default: right"), text: withNote(str(), "multi-line text serializes as a block note") } },
201
+ remove_note: { fields: { index: num() } },
202
+ set_note_text: { fields: { index: num(), text: str() } },
203
+ define_class: { fields: { name: str(), style: withNote(str(), 'CSS-like pairs, e.g. "fill:#f96,stroke:#333"') } },
204
+ set_state_class: { fields: { id: str(), className: strOrNull() } },
205
+ set_state_style: { fields: { id: str(), style: withNote(strOrNull(), "inline style pairs; null clears") } },
206
+ set_transition_style: { fields: { index: num(false), default: bool(false), style: strOrNull(), parent: strOrNull(false), region: num(false) }, requireOneOf: ["index", "default"] }
207
+ };
208
+ var SEQUENCE_SCHEMA = {
209
+ add_participant: { fields: { id: str(), label: str(false), participantKind: oneOf(PARTICIPANT_KINDS, false) } },
210
+ remove_participant: { fields: { id: str() } },
211
+ add_message: { fields: { from: str(), to: str(), text: str(), style: withNote(oneOf(SEQUENCE_MESSAGE_STYLES, false), "default: sync"), index: withNote(num(false), "top-level insert position; default: append") } },
212
+ remove_message: { fields: { index: num() } },
213
+ set_message_text: { fields: { index: num(), text: str() } },
214
+ move_message: { fields: { from: withNote(num(), "top-level message index"), to: withNote(num(), "top-level target position") } },
215
+ set_participant_label: { fields: { id: str(), label: str() } },
216
+ add_fragment: { fields: { fragmentKind: oneOf(SEQUENCE_FRAGMENT_KINDS), label: str(false), index: withNote(num(false), "fragment insert position; default: append") } },
217
+ remove_fragment: { fields: { index: num() } },
218
+ set_fragment_label: { fields: { index: num(), label: strOrNull() } },
219
+ add_fragment_branch: { fields: { fragmentIndex: num(), label: str(false) } },
220
+ set_fragment_branch_label: { fields: { fragmentIndex: num(), branchIndex: num(), label: strOrNull() } },
221
+ add_fragment_message: { fields: { fragmentIndex: num(), branchIndex: withNote(num(false), "default: 0"), from: str(), to: str(), text: str(), style: withNote(oneOf(SEQUENCE_MESSAGE_STYLES, false), "default: sync"), index: withNote(num(false), "message insert position; default: append") } },
222
+ remove_fragment_message: { fields: { fragmentIndex: num(), branchIndex: withNote(num(false), "default: 0"), index: num() } },
223
+ set_fragment_message_text: { fields: { fragmentIndex: num(), branchIndex: withNote(num(false), "default: 0"), index: num(), text: str() } }
224
+ };
225
+ var TIMELINE_SCHEMA = {
226
+ set_title: { fields: { title: strOrNull() } },
227
+ add_section: { fields: { label: str(), index: withNote(num(false), "insert position; omit to append") } },
228
+ remove_section: { fields: { index: num() } },
229
+ set_section_label: { fields: { index: num(), label: str() } },
230
+ add_period: { fields: { sectionIndex: num(), label: str(), events: strArr(false), index: withNote(num(false), "insert position; omit to append") } },
231
+ remove_period: { fields: { sectionIndex: num(), periodIndex: num() } },
232
+ set_period_label: { fields: { sectionIndex: num(), periodIndex: num(), label: str() } },
233
+ add_event: { fields: { sectionIndex: num(), periodIndex: num(), text: str(), index: withNote(num(false), "insert position; omit to append") } },
234
+ remove_event: { fields: { sectionIndex: num(), periodIndex: num(), eventIndex: num() } },
235
+ set_event_text: { fields: { sectionIndex: num(), periodIndex: num(), eventIndex: num(), text: str() } },
236
+ move_period: { fields: { fromSection: num(), fromIndex: num(), toSection: num(), toIndex: withNote(num(), "insert position in the target section, applied after removal") } },
237
+ move_event: { fields: { fromSection: num(), fromPeriod: num(), fromIndex: num(), toSection: num(), toPeriod: num(), toIndex: withNote(num(), "insert position in the target period, applied after removal") } },
238
+ move_section: { fields: { from: num(), to: num() } },
239
+ set_accessibility_title: { fields: { title: strOrNull() } },
240
+ set_accessibility_description: { fields: { description: strOrNull() } }
241
+ };
242
+ var CLASS_SCHEMA = {
243
+ set_title: { fields: { title: strOrNull() } },
244
+ add_class: { fields: { id: str(), label: str(false), generic: withNote(str(false), "Mermaid generic parameter text without surrounding ~ delimiters"), members: strArr(false), namespace: withNote(str(false), 'dot path, e.g. "Platform.Auth"; declared on demand') } },
245
+ remove_class: { fields: { id: str() } },
246
+ rename_class: { fields: { from: str(), to: str() } },
247
+ set_class_generic: { fields: { class: str(), generic: withNote(strOrNull(), "parameter text without ~ delimiters; null removes it") } },
248
+ add_member: { fields: { class: str(), text: str() } },
249
+ remove_member: { fields: { class: str(), index: num() } },
250
+ add_relation: { fields: { from: str(), to: str(), relKind: oneOf(CLASS_REL_KINDS), label: str(false) } },
251
+ remove_relation: { fields: { index: num() } },
252
+ add_note: { fields: { text: str(), for: str(false) } },
253
+ remove_note: { fields: { index: num() } },
254
+ set_class_namespace: { fields: { class: str(), namespace: withNote(strOrNull(), "dot path moves the class into that namespace (declared on demand); null moves it to the top level") } },
255
+ define_class: { fields: { name: str(), style: withNote(str(), 'CSS-like pairs, e.g. "fill:#f96,stroke:#333"') } },
256
+ set_css_class: { fields: { class: str(), className: strOrNull() } },
257
+ set_class_style: { fields: { class: str(), style: strOrNull() } }
258
+ };
259
+ var ER_SCHEMA = {
260
+ add_entity: { fields: { id: str(), label: str(false), attributes: strArr(false) } },
261
+ remove_entity: { fields: { id: str() } },
262
+ rename_entity: { fields: { from: str(), to: str() } },
263
+ set_entity_label: { fields: { entity: str(), label: strOrNull() } },
264
+ add_attribute: { fields: { entity: str(), text: str() } },
265
+ remove_attribute: { fields: { entity: str(), index: num() } },
266
+ add_relation: { fields: { from: str(), to: str(), leftCard: oneOf(ER_CARDINALITIES), rightCard: oneOf(ER_CARDINALITIES), dashed: bool(false), label: str(false) } },
267
+ remove_relation: { fields: { index: num() } },
268
+ set_direction: { fields: { direction: oneOf(STATE_DIRECTIONS) } },
269
+ define_class: { fields: { name: str(), style: str() } },
270
+ set_entity_class: { fields: { entity: str(), className: strOrNull() } },
271
+ set_entity_style: { fields: { entity: str(), style: strOrNull() } }
272
+ };
273
+ var JOURNEY_SCHEMA = {
274
+ set_title: { fields: { title: strOrNull() } },
275
+ add_section: { fields: { label: str(), index: withNote(num(false), "insert position; omit to append") } },
276
+ remove_section: { fields: { index: num() } },
277
+ set_section_label: { fields: { index: num(), label: str() } },
278
+ add_task: { fields: { sectionIndex: num(), text: str(), score: withNote(num(), "integer 1..5"), actors: strArr(false), index: withNote(num(false), "insert position; omit to append") } },
279
+ remove_task: { fields: { sectionIndex: num(), taskIndex: num() } },
280
+ set_task_text: { fields: { sectionIndex: num(), taskIndex: num(), text: str() } },
281
+ set_task_score: { fields: { sectionIndex: num(), taskIndex: num(), score: withNote(num(), "integer 1..5") } },
282
+ set_task_actors: { fields: { sectionIndex: num(), taskIndex: num(), actors: strArr() } },
283
+ rename_actor: { fields: { from: str(), to: str() } },
284
+ move_task: { fields: { fromSection: num(), fromIndex: num(), toSection: num(), toIndex: withNote(num(), "insert position in the target section, applied after removal") } },
285
+ move_section: { fields: { from: num(), to: num() } },
286
+ set_accessibility_title: { fields: { title: strOrNull() } },
287
+ set_accessibility_description: { fields: { description: strOrNull() } }
288
+ };
289
+ var ARCHITECTURE_SCHEMA = {
290
+ set_title: { fields: { title: strOrNull() } },
291
+ set_accessibility_title: { fields: { title: strOrNull() } },
292
+ set_accessibility_description: { fields: { description: strOrNull() } },
293
+ add_service: { fields: { id: str(), label: str(false), icon: strOrNull(false), group: strOrNull(false) } },
294
+ remove_service: { fields: { id: str() } },
295
+ rename_service: { fields: { from: str(), to: str() } },
296
+ set_service_label: { fields: { id: str(), label: str() } },
297
+ set_service_icon: { fields: { id: str(), icon: strOrNull() } },
298
+ move_service: { fields: { id: str(), group: strOrNull() } },
299
+ add_junction: { fields: { id: str(), group: strOrNull(false) } },
300
+ remove_junction: { fields: { id: str() } },
301
+ rename_junction: { fields: { from: str(), to: str() } },
302
+ move_junction: { fields: { id: str(), group: strOrNull() } },
303
+ add_group: { fields: { id: str(), label: str(false), icon: strOrNull(false), parent: strOrNull(false) } },
304
+ set_group_label: { fields: { id: str(), label: str() } },
305
+ remove_group: { fields: { id: str() } },
306
+ add_edge: { fields: { from: str(), to: str(), fromSide: oneOf(ARCH_SIDES), toSide: oneOf(ARCH_SIDES), fromBoundary: oneOf(ARCH_BOUNDARIES, false), toBoundary: oneOf(ARCH_BOUNDARIES, false), label: strOrNull(false), hasArrowStart: bool(false), hasArrowEnd: bool(false) } },
307
+ update_edge: { fields: { index: num(), from: str(false), to: str(false), fromSide: oneOf(ARCH_SIDES, false), toSide: oneOf(ARCH_SIDES, false), fromBoundary: oneOf(ARCH_BOUNDARIES, false), toBoundary: oneOf(ARCH_BOUNDARIES, false), label: strOrNull(false), hasArrowStart: bool(false), hasArrowEnd: bool(false) } },
308
+ remove_edge: { fields: { index: num(false), id: str(false) }, requireOneOf: ["index", "id"] }
309
+ };
310
+ var XYCHART_SCHEMA = {
311
+ set_title: { fields: { title: strOrNull() } },
312
+ set_x_axis: { fields: { axis: objOrNull() } },
313
+ set_y_axis: { fields: { axis: objOrNull() } },
314
+ add_series: { fields: { kind2: withNote(oneOf(SERIES_KINDS), "series type \u2014 the field is named kind2, not kind"), name: strOrNull(false), values: numArr() } },
315
+ remove_series: { fields: { index: num() } },
316
+ set_series_values: { fields: { index: num(), values: numArr() } },
317
+ set_series_name: { fields: { index: num(), name: strOrNull() } },
318
+ reorder_series: { fields: { from: num(), to: num() } },
319
+ set_orientation: { fields: { horizontal: withNote(bool(), "true = horizontal, false = vertical (the default)") } },
320
+ set_data_point: { fields: { seriesIndex: num(), index: withNote(num(), "0-based position within the series values"), value: withNote(num(), "finite") } }
321
+ };
322
+ var PIE_SCHEMA = {
323
+ set_title: { fields: { title: strOrNull() } },
324
+ set_show_data: { fields: { showData: bool() } },
325
+ add_slice: { fields: { label: str(), value: withNote(num(), "> 0, finite") } },
326
+ remove_slice: { fields: { label: str() } },
327
+ rename_slice: { fields: { from: str(), to: str() } },
328
+ set_slice_value: { fields: { label: str(), value: withNote(num(), "> 0, finite") } },
329
+ reorder_slice: { fields: { from: num(), to: num() } }
330
+ };
331
+ var QUADRANT_SCHEMA = {
332
+ set_title: { fields: { title: strOrNull() } },
333
+ set_axis_labels: { fields: { axis: oneOf(AXIS_KINDS), near: strOrNull(), far: strOrNull(false) } },
334
+ set_quadrant_label: { fields: { quadrant: num(), label: strOrNull() } },
335
+ add_point: { fields: { label: str(), x: withNote(num(), "0..1"), y: withNote(num(), "0..1") } },
336
+ remove_point: { fields: { label: str() } },
337
+ move_point: { fields: { label: str(), x: withNote(num(), "0..1"), y: withNote(num(), "0..1") } },
338
+ rename_point: { fields: { from: str(), to: str() } }
339
+ };
340
+ var GANTT_SCHEMA = {
341
+ set_title: { fields: { title: strOrNull() } },
342
+ add_section: { fields: { label: str() } },
343
+ rename_section: { fields: { index: num(), label: str() } },
344
+ remove_section: { fields: { index: num() } },
345
+ add_task: { fields: { sectionIndex: num(), label: str(), taskId: str(false), tags: strArr(false), start: withNote(str(false), 'date, or "after <taskId>"'), end: withNote(str(), 'date, a duration like "3d", or "until <taskId>"'), index: withNote(num(false), "insert position; omit to append \u2014 inserting into an implicit-start chain re-chains the follower onto the new task") } },
346
+ remove_task: { fields: { sectionIndex: num(), taskIndex: num() } },
347
+ rename_task: { fields: { sectionIndex: num(), taskIndex: num(), label: str() } },
348
+ set_task_status: { fields: { sectionIndex: num(), taskIndex: num(), status: oneOf(GANTT_STATUSES, true, true) } },
349
+ set_task_dates: { fields: { sectionIndex: num(), taskIndex: num(), start: strOrNull(false), end: str(false) } },
350
+ set_task_flags: { fields: { sectionIndex: num(), taskIndex: num(), milestone: bool(false), vert: bool(false) } },
351
+ set_task_id: { fields: { sectionIndex: num(), taskIndex: num(), taskId: withNote(strOrNull(), "renames rewrite after/until references; null clears (rejected while referenced)") } },
352
+ move_task: { fields: { fromSection: num(), fromIndex: num(), toSection: num(), toIndex: withNote(num(), "insert position in the target section, applied after removal \u2014 rejected if the move would change an implicit-start task's predecessor") } },
353
+ move_section: { fields: { from: num(), to: num() } }
354
+ };
355
+ var MINDMAP_SCHEMA = {
356
+ add_node: { fields: { id: str(), label: str(), parent: str(), shape: oneOf(MINDMAP_SHAPES, false), index: num(false) } },
357
+ remove_node: { fields: { id: str(), recursive: bool(false) } },
358
+ rename_node: { fields: { from: str(), to: str() } },
359
+ set_label: { fields: { id: str(), label: str() } },
360
+ move_node: { fields: { id: str(), parent: str(), index: num(false) } },
361
+ set_shape: { fields: { id: str(), shape: oneOf(MINDMAP_SHAPES) } },
362
+ set_icon: { fields: { id: str(), icon: strOrNull() } },
363
+ set_node_class: { fields: { id: str(), className: strOrNull() } },
364
+ set_accessibility_title: { fields: { title: strOrNull() } },
365
+ set_accessibility_description: { fields: { description: strOrNull() } }
366
+ };
367
+ var GITGRAPH_SCHEMA = {
368
+ append_commit: { fields: { id: str(false), message: str(false), type: oneOf(GIT_COMMIT_TYPES, false), tags: strArr(false) } },
369
+ create_branch: { fields: { name: str(), order: num(false) } },
370
+ checkout_branch: { fields: { name: str() } },
371
+ merge_branch: { fields: { name: str(), id: str(false), type: oneOf(GIT_COMMIT_TYPES, false), tags: strArr(false) } },
372
+ cherry_pick: { fields: { id: str(), parent: str(false), tags: strArr(false) } },
373
+ set_commit_message: { fields: { id: str(), message: strOrNull() } },
374
+ set_commit_type: { fields: { id: str(), type: oneOf(GIT_COMMIT_TYPES) } },
375
+ set_commit_tags: { fields: { id: str(), tags: strArr() } },
376
+ rename_branch: { fields: { from: str(), to: str() } },
377
+ set_accessibility_title: { fields: { title: strOrNull() } },
378
+ set_accessibility_description: { fields: { description: strOrNull() } }
379
+ };
380
+ var RADAR_SCHEMA = {
381
+ set_title: { fields: { title: strOrNull() } },
382
+ add_axis: { fields: { id: str(), label: strOrNull(false), index: withNote(num(false), "insert position; omit to append"), fill: withNote(num(false), "value written to every existing curve for the new axis; default: min") } },
383
+ remove_axis: { fields: { id: str() } },
384
+ rename_axis: { fields: { from: str(), to: str() } },
385
+ set_axis_label: { fields: { id: str(), label: strOrNull() } },
386
+ reorder_axis: { fields: { from: num(), to: withNote(num(), "permutes every curve's value vector identically") } },
387
+ add_curve: { fields: { id: str(), label: strOrNull(false), values: withNote(numArr(), "one value per axis, in axis order"), index: num(false) } },
388
+ remove_curve: { fields: { id: str() } },
389
+ set_curve_values: { fields: { id: str(), values: withNote(numArr(), "length must equal the axis count") } },
390
+ set_curve_value: { fields: { curve: str(), axis: str(), value: withNote(num(), "non-negative, finite") } },
391
+ set_curve_label: { fields: { id: str(), label: strOrNull() } },
392
+ rename_curve: { fields: { from: str(), to: str() } },
393
+ reorder_curve: { fields: { from: num(), to: num() } },
394
+ set_config: { fields: { max: withNote(numOrNull(false), "> min; null resets auto-max"), min: withNote(numOrNull(false), "null resets 0"), ticks: withNote(numOrNull(false), `integer 1..${MAX_RADAR_TICKS}; null resets 5`), graticule: oneOf(GRATICULE_KINDS, false, true), showLegend: withNote(boolOrNull(false), "null resets true") } }
395
+ };
396
+ var SCHEMAS = {
397
+ flowchart: FLOWCHART_SCHEMA,
398
+ state: STATE_SCHEMA,
399
+ sequence: SEQUENCE_SCHEMA,
400
+ timeline: TIMELINE_SCHEMA,
401
+ class: CLASS_SCHEMA,
402
+ er: ER_SCHEMA,
403
+ journey: JOURNEY_SCHEMA,
404
+ architecture: ARCHITECTURE_SCHEMA,
405
+ xychart: XYCHART_SCHEMA,
406
+ pie: PIE_SCHEMA,
407
+ quadrant: QUADRANT_SCHEMA,
408
+ gantt: GANTT_SCHEMA,
409
+ mindmap: MINDMAP_SCHEMA,
410
+ gitgraph: GITGRAPH_SCHEMA,
411
+ radar: RADAR_SCHEMA
412
+ };
413
+ function hasOpSchema(family) {
414
+ return Object.hasOwn(SCHEMAS, family);
415
+ }
416
+ function levenshtein(a, b) {
417
+ const m = a.length, n = b.length;
418
+ const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
419
+ for (let j = 0; j <= n; j++) d[0][j] = j;
420
+ for (let i = 1; i <= m; i++)
421
+ for (let j = 1; j <= n; j++)
422
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
423
+ return d[m][n];
424
+ }
425
+ function nearest(target, candidates) {
426
+ let best;
427
+ let bestD = Infinity;
428
+ for (const c of candidates) {
429
+ const dist = levenshtein(target.toLowerCase(), c.toLowerCase());
430
+ if (dist < bestD) {
431
+ bestD = dist;
432
+ best = c;
433
+ }
434
+ }
435
+ return best !== void 0 && bestD <= Math.max(2, Math.ceil(best.length * 0.4)) ? best : void 0;
436
+ }
437
+ function typeName(f) {
438
+ switch (f.type) {
439
+ case "string":
440
+ return "string";
441
+ case "number":
442
+ return "number";
443
+ case "boolean":
444
+ return "boolean";
445
+ case "number-or-null":
446
+ return "number | null";
447
+ case "boolean-or-null":
448
+ return "boolean | null";
449
+ case "string-or-null":
450
+ return "string | null";
451
+ case "string-array":
452
+ return "string[]";
453
+ case "number-array":
454
+ return "number[]";
455
+ case "object-or-null":
456
+ return "object | null";
457
+ case "enum":
458
+ return `one of ${f.values.map((v) => JSON.stringify(v)).join(", ")}${f.nullable ? " | null" : ""}`;
459
+ }
460
+ }
461
+ function matchesType(spec, value) {
462
+ switch (spec.type) {
463
+ case "string":
464
+ return typeof value === "string";
465
+ case "number":
466
+ return typeof value === "number" && Number.isFinite(value);
467
+ case "boolean":
468
+ return typeof value === "boolean";
469
+ case "number-or-null":
470
+ return value === null || typeof value === "number" && Number.isFinite(value);
471
+ case "boolean-or-null":
472
+ return value === null || typeof value === "boolean";
473
+ case "string-or-null":
474
+ return value === null || typeof value === "string";
475
+ case "string-array":
476
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
477
+ case "number-array":
478
+ return Array.isArray(value) && value.every((v) => typeof v === "number" && Number.isFinite(v));
479
+ case "object-or-null":
480
+ return value === null || typeof value === "object" && !Array.isArray(value);
481
+ case "enum":
482
+ return spec.nullable === true && value === null || typeof value === "string" && spec.values.includes(value);
483
+ }
484
+ }
485
+ function admitOpRecord(value) {
486
+ const seen = /* @__PURE__ */ new Set();
487
+ const snapshot = (input) => {
488
+ if (input === null || input === void 0 || typeof input === "string" || typeof input === "boolean" || typeof input === "number") return input;
489
+ if (typeof input !== "object") throw new TypeError("operation values must be plain JSON data");
490
+ if (seen.has(input)) throw new TypeError("operation data must not contain cycles");
491
+ seen.add(input);
492
+ try {
493
+ if (Array.isArray(input)) {
494
+ const prototype2 = Object.getPrototypeOf(input);
495
+ if (prototype2 !== Array.prototype) throw new TypeError("operation arrays must use the standard array prototype");
496
+ const descriptors2 = Object.getOwnPropertyDescriptors(input);
497
+ const length = descriptors2.length?.value;
498
+ if (!Number.isSafeInteger(length) || length < 0) throw new TypeError("operation arrays must have a readable length");
499
+ const keys = Reflect.ownKeys(input);
500
+ if (keys.some((key) => typeof key === "symbol" || key !== "length" && !/^(0|[1-9]\d*)$/.test(key))) {
501
+ throw new TypeError("operation arrays must not carry symbols or named properties");
502
+ }
503
+ const output2 = [];
504
+ for (let index = 0; index < length; index++) {
505
+ const descriptor = descriptors2[String(index)];
506
+ if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
507
+ throw new TypeError("operation arrays must be dense own-data arrays");
508
+ }
509
+ output2.push(snapshot(descriptor.value));
510
+ }
511
+ return output2;
512
+ }
513
+ const prototype = Object.getPrototypeOf(input);
514
+ if (prototype !== Object.prototype && prototype !== null) {
515
+ throw new TypeError("operation records must be plain objects");
516
+ }
517
+ const descriptors = Object.getOwnPropertyDescriptors(input);
518
+ const output = /* @__PURE__ */ Object.create(null);
519
+ for (const key of Reflect.ownKeys(input)) {
520
+ if (typeof key === "symbol") throw new TypeError("operation records must not carry symbol keys");
521
+ const descriptor = descriptors[key];
522
+ if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
523
+ throw new TypeError("operation records must contain enumerable own data fields");
524
+ }
525
+ output[key] = snapshot(descriptor.value);
526
+ }
527
+ return output;
528
+ } finally {
529
+ seen.delete(input);
530
+ }
531
+ };
532
+ try {
533
+ return { ok: true, value: snapshot(value) };
534
+ } catch {
535
+ return { ok: false, message: "operation must be a plain own-data JSON object" };
536
+ }
537
+ }
538
+ function unreadableOp(family, message) {
539
+ return {
540
+ code: "INVALID_OP",
541
+ reason: "unreadable_op",
542
+ family,
543
+ message,
544
+ validFields: Object.keys(SCHEMAS[family])
545
+ };
546
+ }
547
+ function validateAdmittedOp(family, op) {
548
+ const schema = SCHEMAS[family];
549
+ if (Array.isArray(op)) {
550
+ return { code: "INVALID_OP", reason: "expected_single_op", family, message: unknownOpMessage(family, op), validFields: Object.keys(schema) };
551
+ }
552
+ if (!op || typeof op !== "object") {
553
+ return { code: "INVALID_OP", reason: "unknown_kind", family, message: unknownOpMessage(family, op), validFields: Object.keys(schema) };
554
+ }
555
+ const rec = op;
556
+ const kind = rec.kind;
557
+ if (typeof kind !== "string" || !Object.hasOwn(schema, kind)) {
558
+ const validKinds = Object.keys(schema);
559
+ const dym = typeof kind === "string" ? nearest(kind, validKinds) : void 0;
560
+ return {
561
+ code: "INVALID_OP",
562
+ reason: "unknown_kind",
563
+ family,
564
+ opKind: typeof kind === "string" ? kind : void 0,
565
+ message: `${unknownOpMessage(family, op)}${dym ? ` Did you mean "${dym}"?` : ""}`,
566
+ validFields: validKinds,
567
+ didYouMean: dym
568
+ };
569
+ }
570
+ const spec = schema[kind];
571
+ const validFields = Object.keys(spec.fields);
572
+ for (const key of Object.keys(rec)) {
573
+ if (key === "kind") continue;
574
+ if (!Object.hasOwn(spec.fields, key)) {
575
+ const dym = nearest(key, validFields);
576
+ return {
577
+ code: "INVALID_OP",
578
+ reason: "unknown_field",
579
+ family,
580
+ opKind: kind,
581
+ field: key,
582
+ message: `Unknown field "${key}" for ${family} op "${kind}".${dym ? ` Did you mean "${dym}"?` : ""} Valid fields: ${validFields.join(", ")}.`,
583
+ validFields,
584
+ didYouMean: dym
585
+ };
586
+ }
587
+ }
588
+ for (const [name, fspec] of Object.entries(spec.fields)) {
589
+ if (!fspec.required) continue;
590
+ if (rec[name] === void 0) {
591
+ return {
592
+ code: "INVALID_OP",
593
+ reason: "missing_field",
594
+ family,
595
+ opKind: kind,
596
+ field: name,
597
+ message: `Missing required field "${name}" (${typeName(fspec)}) for ${family} op "${kind}". Fields: ${validFields.join(", ")}.`,
598
+ validFields
599
+ };
600
+ }
601
+ }
602
+ if (spec.requireOneOf && !spec.requireOneOf.some((f) => rec[f] !== void 0)) {
603
+ return {
604
+ code: "INVALID_OP",
605
+ reason: "require_one_of",
606
+ family,
607
+ opKind: kind,
608
+ message: `${family} op "${kind}" requires at least one of: ${spec.requireOneOf.join(", ")}.`,
609
+ validFields
610
+ };
611
+ }
612
+ for (const [name, fspec] of Object.entries(spec.fields)) {
613
+ if (rec[name] === void 0) continue;
614
+ if (!matchesType(fspec, rec[name])) {
615
+ return {
616
+ code: "INVALID_OP",
617
+ reason: "wrong_type",
618
+ family,
619
+ opKind: kind,
620
+ field: name,
621
+ message: `Field "${name}" of ${family} op "${kind}" must be ${typeName(fspec)}, got ${JSON.stringify(rec[name])}.`,
622
+ validFields
623
+ };
624
+ }
625
+ }
626
+ return null;
627
+ }
628
+ function validateOp(family, op) {
629
+ const admitted = admitOpRecord(op);
630
+ if (!admitted.ok) return unreadableOp(family, admitted.message);
631
+ try {
632
+ return validateAdmittedOp(family, admitted.value);
633
+ } catch {
634
+ return unreadableOp(family, "operation could not be validated as plain JSON data");
635
+ }
636
+ }
637
+ function opMenu(family) {
638
+ if (!hasOpSchema(family)) {
639
+ throw new RangeError(`family must be one of: ${Object.keys(SCHEMAS).join(", ")}`);
640
+ }
641
+ const out = {};
642
+ for (const [kind, spec] of Object.entries(SCHEMAS[family])) {
643
+ out[kind] = Object.entries(spec.fields).map(([n, f]) => f.required ? n : `${n}?`);
644
+ }
645
+ return out;
646
+ }
647
+ function describeOps(family) {
648
+ if (!hasOpSchema(family)) {
649
+ throw new RangeError(`family must be one of: ${Object.keys(SCHEMAS).join(", ")}`);
650
+ }
651
+ const out = {};
652
+ for (const [kind, spec] of Object.entries(SCHEMAS[family])) {
653
+ out[kind] = Object.entries(spec.fields).map(([name, f]) => ({ name, required: f.required, type: typeName(f), ...f.note !== void 0 ? { note: f.note } : {} }));
654
+ }
655
+ return out;
656
+ }
657
+ function opSignatures(family) {
658
+ const menu = opMenu(family);
659
+ return Object.entries(menu).map(([kind, fields]) => `${kind}(${fields.join(", ")})`);
660
+ }
661
+
662
+ // src/agent/mutate.ts
663
+ function mutate(d, op) {
664
+ const r = applyOneMutation(d, op);
665
+ logToolInvocation("mutate", r.ok);
666
+ return r;
667
+ }
668
+ function applyOneMutation(d, op) {
669
+ if (d.body.kind === "preserved") {
670
+ return err({
671
+ code: d.body.diagnostic.code === "UNKNOWN_HEADER" ? "UNKNOWN_HEADER" : "UNSUPPORTED_FAMILY",
672
+ message: `${d.body.diagnostic.message}; the exact source remains preserved and cannot accept structured mutation`
673
+ });
674
+ }
675
+ if (d.body.kind === "extension") {
676
+ return err({
677
+ code: "INVALID_OP",
678
+ message: `Registered external family "${d.kind}" has no typed mutation contract in FamilyDescriptor v2`
679
+ });
680
+ }
681
+ const plugin = getFamily(d.kind);
682
+ if (plugin?.mutate && plugin.serialize) {
683
+ const r = plugin.mutate(d.body, op);
684
+ if (!r.ok) return r;
685
+ const bodyAccessibility = accessibilityFromBody(r.value);
686
+ const meta = bodyAccessibility === void 0 ? d.meta : { ...d.meta, accessibility: bodyAccessibility };
687
+ const canonicalSource = wrapperPrefix(meta) + ensureAccessibilityLines(
688
+ plugin.serialize(r.value),
689
+ meta.accessibility
690
+ );
691
+ const reparsed = parseRegisteredMermaid(canonicalSource);
692
+ if (!reparsed.ok) {
693
+ return err({
694
+ code: "INVALID_OP",
695
+ message: `Mutation produced source that could not be reparsed: ${reparsed.error.map((error) => error.message).join("; ")}`
696
+ });
697
+ }
698
+ return ok({ ...d, body: r.value, meta, canonicalSource, source: reparsed.value.source });
699
+ }
700
+ return err({ code: "INVALID_OP", message: `Unsupported mutable diagram kind: ${d.kind}` });
701
+ }
702
+ function mutateChecked(d, op) {
703
+ if (hasOpSchema(d.kind)) {
704
+ const admitted = admitOpRecord(op);
705
+ const checkedOp = admitted.ok ? admitted.value : op;
706
+ const invalid = validateOp(d.kind, checkedOp);
707
+ if (invalid) {
708
+ logToolInvocation("mutate", false);
709
+ return err(invalid);
710
+ }
711
+ return mutate(d, checkedOp);
712
+ }
713
+ return mutate(d, op);
714
+ }
715
+
716
+ // src/agent/create.ts
717
+ function emptyBody(kind, opts) {
718
+ switch (kind) {
719
+ case "flowchart":
720
+ return {
721
+ kind: "flowchart",
722
+ graph: {
723
+ direction: opts.direction ?? "TD",
724
+ nodes: /* @__PURE__ */ new Map(),
725
+ edges: [],
726
+ subgraphs: [],
727
+ classDefs: /* @__PURE__ */ new Map(),
728
+ classAssignments: /* @__PURE__ */ new Map(),
729
+ nodeStyles: /* @__PURE__ */ new Map(),
730
+ linkStyles: /* @__PURE__ */ new Map()
731
+ }
732
+ };
733
+ case "state":
734
+ return { kind: "state", states: [], transitions: [], direction: opts.direction };
735
+ case "sequence":
736
+ return { kind: "sequence", participants: [], messages: [], statements: [] };
737
+ case "timeline":
738
+ return { kind: "timeline", sections: [] };
739
+ case "class":
740
+ return { kind: "class", classes: [], relations: [], notes: [] };
741
+ case "er":
742
+ return { kind: "er", entities: [], relations: [] };
743
+ case "journey":
744
+ return { kind: "journey", sections: [] };
745
+ case "architecture":
746
+ return { kind: "architecture", groups: [], services: [], junctions: [], edges: [] };
747
+ case "xychart":
748
+ return { kind: "xychart", series: [] };
749
+ case "pie":
750
+ return { kind: "pie", showData: false, slices: [] };
751
+ case "quadrant":
752
+ return { kind: "quadrant", quadrants: [void 0, void 0, void 0, void 0], points: [] };
753
+ case "gantt":
754
+ return { kind: "gantt", sections: [], statements: [] };
755
+ case "mindmap":
756
+ return { kind: "mindmap", root: { id: "root", label: "root", shape: "default", children: [] } };
757
+ case "gitgraph":
758
+ return {
759
+ kind: "gitgraph",
760
+ direction: "LR",
761
+ mainBranchName: "main",
762
+ commits: [],
763
+ branches: [{ name: "main", order: 0, sequence: 0 }],
764
+ statements: []
765
+ };
766
+ case "radar":
767
+ return { kind: "radar", axes: [], curves: [], min: 0, ticks: 5, graticule: "circle", showLegend: true };
768
+ }
769
+ }
770
+ function createMermaid(kind, opts = {}) {
771
+ logToolInvocation("build");
772
+ const body = emptyBody(kind, opts);
773
+ if (!body) throw new Error(`createMermaid: unknown diagram kind "${String(kind)}"`);
774
+ const draft = {
775
+ kind,
776
+ body,
777
+ meta: { initDirectives: [], comments: [], accessibility: {} },
778
+ source: { nodes: /* @__PURE__ */ new Map(), edges: /* @__PURE__ */ new Map(), groups: /* @__PURE__ */ new Map(), labels: /* @__PURE__ */ new Map() },
779
+ canonicalSource: ""
780
+ };
781
+ return { ...draft, canonicalSource: serializeMermaid(draft) };
782
+ }
783
+ function buildMermaid(kind, ops, opts = {}) {
784
+ let d = createMermaid(kind, opts);
785
+ for (let i = 0; i < ops.length; i++) {
786
+ const r = mutate(d, ops[i]);
787
+ if (!r.ok) return err({ ...r.error, opIndex: i, message: `op[${i}] (${ops[i].kind}): ${r.error.message}` });
788
+ d = r.value;
789
+ }
790
+ return ok(d);
791
+ }
792
+
793
+ // src/agent/structural-count.ts
794
+ function countStates(states) {
795
+ let nodes = 0, edges = 0;
796
+ for (const s of states) {
797
+ nodes++;
798
+ if (s.regions) {
799
+ for (const region of s.regions) {
800
+ const inner = countStates(region.states);
801
+ nodes += inner.nodes;
802
+ edges += inner.edges + region.transitions.length;
803
+ }
804
+ } else {
805
+ if (s.states && s.states.length) {
806
+ const inner = countStates(s.states);
807
+ nodes += inner.nodes;
808
+ edges += inner.edges;
809
+ }
810
+ if (s.transitions) edges += s.transitions.length;
811
+ }
812
+ }
813
+ return { nodes, edges };
814
+ }
815
+ function countSubgraphs(subgraphs) {
816
+ let groups = 0;
817
+ for (const sg of subgraphs) {
818
+ groups++;
819
+ groups += countSubgraphs(sg.children);
820
+ }
821
+ return groups;
822
+ }
823
+ function countStructuralElements(d) {
824
+ const body = d.body;
825
+ switch (body.kind) {
826
+ case "flowchart": {
827
+ const g = body.graph;
828
+ return { nodes: g.nodes.size, edges: g.edges.length, groups: countSubgraphs(g.subgraphs) };
829
+ }
830
+ case "sequence":
831
+ return { nodes: body.participants.length, edges: sequenceMessages(body).length, groups: 0 };
832
+ case "state": {
833
+ const top = countStates(body.states);
834
+ return { nodes: top.nodes, edges: top.edges + body.transitions.length, groups: 0 };
835
+ }
836
+ case "class":
837
+ return { nodes: body.classes.length, edges: body.relations.length, groups: (body.namespaces ?? []).length };
838
+ case "er":
839
+ return { nodes: body.entities.length, edges: body.relations.length, groups: (body.groups ?? []).length };
840
+ case "timeline": {
841
+ let nodes = 0;
842
+ for (const sec of body.sections) for (const p of sec.periods) nodes += 1 + p.events.length;
843
+ return { nodes, edges: 0, groups: body.sections.length };
844
+ }
845
+ case "journey": {
846
+ let nodes = 0;
847
+ for (const sec of body.sections) nodes += sec.tasks.length;
848
+ return { nodes, edges: 0, groups: body.sections.length };
849
+ }
850
+ case "architecture":
851
+ return {
852
+ nodes: body.services.length + body.junctions.length,
853
+ edges: body.edges.length,
854
+ groups: body.groups.length
855
+ };
856
+ case "xychart":
857
+ return { nodes: body.series.length, edges: 0, groups: 0 };
858
+ case "pie":
859
+ return { nodes: body.slices.length, edges: 0, groups: 0 };
860
+ case "quadrant":
861
+ return { nodes: body.points.length, edges: 0, groups: 0 };
862
+ case "gantt": {
863
+ let nodes = 0;
864
+ for (const sec of body.sections) nodes += sec.tasks.length;
865
+ return { nodes, edges: 0, groups: body.sections.length };
866
+ }
867
+ case "mindmap": {
868
+ let nodes = 0, edges = 0;
869
+ const visit2 = (node) => {
870
+ nodes++;
871
+ edges += node.children.length;
872
+ node.children.forEach(visit2);
873
+ };
874
+ visit2(body.root);
875
+ return { nodes, edges, groups: 0 };
876
+ }
877
+ case "gitgraph":
878
+ return {
879
+ nodes: body.commits.length,
880
+ edges: body.commits.reduce((sum, commit) => sum + commit.parents.length, 0),
881
+ groups: body.branches.length
882
+ };
883
+ case "radar":
884
+ return { nodes: body.curves.reduce((sum, c) => sum + c.values.length, 0), edges: 0, groups: body.axes.length };
885
+ case "opaque":
886
+ case "extension":
887
+ case "preserved":
888
+ return null;
889
+ default: {
890
+ const _never = body;
891
+ void _never;
892
+ return null;
893
+ }
894
+ }
895
+ }
896
+ function countsEqual(a, b) {
897
+ return a.nodes === b.nodes && a.edges === b.edges && a.groups === b.groups;
898
+ }
899
+ function faithfulnessWarning(before, after) {
900
+ if (!before) return [];
901
+ if (!after) return [{ code: "CONTENT_DROPPED_ON_ROUNDTRIP", before, after: { nodes: 0, edges: 0, groups: 0 } }];
902
+ if (!countsEqual(before, after)) return [{ code: "CONTENT_DROPPED_ON_ROUNDTRIP", before, after }];
903
+ return [];
904
+ }
905
+
906
+ // src/scene/brand-constraints.ts
907
+ function visit(nodes, fn) {
908
+ for (const node of nodes) {
909
+ fn(node);
910
+ if (node.kind === "group") visit(node.children.map((child) => child.node), fn);
911
+ }
912
+ }
913
+ function mixOpaqueCss(foreground, background, percent) {
914
+ const fg = tryParseCssColor(foreground);
915
+ const bg = tryParseCssColor(background);
916
+ if (!fg || !bg || fg[3] !== 1 || bg[3] !== 1) return void 0;
917
+ const ratio = percent / 100;
918
+ return toHex(
919
+ fg[0] * ratio + bg[0] * (1 - ratio),
920
+ fg[1] * ratio + bg[1] * (1 - ratio),
921
+ fg[2] * ratio + bg[2] * (1 - ratio)
922
+ );
923
+ }
924
+ function resolvedPaint(value, request) {
925
+ if (!value) return void 0;
926
+ const colors = request.appearance.colors;
927
+ const mixed = (percent) => mixOpaqueCss(colors.fg, colors.bg, percent);
928
+ const derived = {
929
+ textSec: colors.muted ?? mixed(MIX.textSec),
930
+ textMuted: colors.muted ?? mixed(MIX.textMuted),
931
+ textFaint: mixed(MIX.textFaint),
932
+ line: colors.line ?? mixed(MIX.line),
933
+ arrow: colors.accent ?? mixed(MIX.arrow),
934
+ nodeFill: colors.surface ?? mixed(MIX.nodeFill),
935
+ nodeStroke: colors.border ?? mixed(MIX.nodeStroke),
936
+ groupHdr: mixed(MIX.groupHeader),
937
+ innerStroke: mixed(MIX.innerStroke),
938
+ keyBadge: mixed(MIX.keyBadge)
939
+ };
940
+ const styleOwnedToken = {
941
+ "--bg": colors.bg,
942
+ "--fg": colors.fg,
943
+ "--_text": colors.fg,
944
+ "--_text-sec": derived.textSec,
945
+ "--_text-muted": derived.textMuted,
946
+ "--_text-faint": derived.textFaint,
947
+ "--_line": derived.line,
948
+ "--_arrow": derived.arrow,
949
+ "--_node-fill": derived.nodeFill,
950
+ "--_node-stroke": derived.nodeStroke,
951
+ "--_group-fill": colors.bg,
952
+ "--_group-hdr": derived.groupHdr,
953
+ "--_inner-stroke": derived.innerStroke,
954
+ "--_key-badge": derived.keyBadge
955
+ };
956
+ const token = {
957
+ "--line": colors.line,
958
+ "--accent": colors.accent,
959
+ "--muted": colors.muted,
960
+ "--surface": colors.surface,
961
+ "--border": colors.border,
962
+ ...styleOwnedToken
963
+ };
964
+ const variable = value.match(/^var\(\s*(--[a-z0-9_-]+)(?:\s*,[\s\S]*)?\)$/i)?.[1];
965
+ return variable && Object.hasOwn(token, variable) ? token[variable] : value;
966
+ }
967
+ function warning(constraint, fields) {
968
+ return {
969
+ code: BRAND_CONSTRAINT_WARNING_POLICY[constraint.action].code,
970
+ constraint: constraint.kind,
971
+ ...fields,
972
+ message: `${fields.message} Recovery: ${BRAND_CONSTRAINT_DESCRIPTORS[constraint.kind].recovery}`
973
+ };
974
+ }
975
+ function sameChannels(a, b) {
976
+ const left = a.channels ?? {};
977
+ const right = b.channels ?? {};
978
+ const entries = (value) => Object.entries(value).sort(([a2], [b2]) => compareCodePointStrings(a2, b2));
979
+ return JSON.stringify(entries(left)) === JSON.stringify(entries(right));
980
+ }
981
+ function descendantText(nodes) {
982
+ const texts = [];
983
+ for (const node of nodes) {
984
+ if (node.kind === "text") texts.push(node);
985
+ else if (node.kind === "group") texts.push(...descendantText(node.children.map((child) => child.node)));
986
+ }
987
+ return texts;
988
+ }
989
+ function containsTextAnchors(shape, child) {
990
+ const bounds = geometryBounds(shape.geometry);
991
+ if (!bounds) return false;
992
+ const texts = descendantText([child]);
993
+ return texts.length > 0 && texts.every((text) => text.x >= bounds.x0 && text.x <= bounds.x1 && text.y >= bounds.y0 && text.y <= bounds.y1);
994
+ }
995
+ function childSurface(preceding, child, enclosingGroup) {
996
+ const last = (items) => items[items.length - 1];
997
+ return last(preceding.filter((shape) => shape.role === child.role && sameChannels(shape, child))) ?? (enclosingGroup ? last(preceding.filter((shape) => shape.role === enclosingGroup.role && sameChannels(shape, enclosingGroup))) : void 0) ?? last(preceding.filter((shape) => containsTextAnchors(shape, child)));
998
+ }
999
+ function collectTextContrastCandidates(nodes, inheritedBackground, candidates, role, enclosingGroup) {
1000
+ const preceding = [];
1001
+ for (const node of nodes) {
1002
+ if (node.kind === "shape") {
1003
+ if (node.paint.fill !== void 0 && node.paint.fill !== "none" && node.paint.fill !== "transparent") preceding.push(node);
1004
+ continue;
1005
+ }
1006
+ const surface = childSurface(preceding, node, enclosingGroup);
1007
+ const background = surface?.paint.fill ?? inheritedBackground;
1008
+ if (node.kind === "text") {
1009
+ if (!role || node.role === role) candidates.push({ node, background });
1010
+ continue;
1011
+ }
1012
+ if (node.kind === "group") {
1013
+ collectTextContrastCandidates(node.children.map((child) => child.node), background, candidates, role, node);
1014
+ }
1015
+ }
1016
+ }
1017
+ function contrastConstraint(constraint, scene, request) {
1018
+ const minimum = constraint.minimum ?? 4.5;
1019
+ if (request.renderOptions.transparent) {
1020
+ return warning(constraint, {
1021
+ measurement: "unmeasurable",
1022
+ minimum,
1023
+ message: "Final contrast is unmeasurable for transparent output because the host backdrop is unknown; no ratio was fabricated."
1024
+ });
1025
+ }
1026
+ const pageBackground = resolvedPaint(request.appearance.colors.bg, request);
1027
+ const candidates = [];
1028
+ collectTextContrastCandidates(scene.parts, pageBackground, candidates, constraint.role);
1029
+ if (candidates.length === 0) {
1030
+ return warning(constraint, {
1031
+ measurement: "not-applicable",
1032
+ minimum,
1033
+ ...constraint.role ? { role: constraint.role } : {},
1034
+ message: `Contrast constraint has no applicable text mark${constraint.role ? ` for role ${constraint.role}` : ""}.`
1035
+ });
1036
+ }
1037
+ const measured = candidates.map((candidate) => {
1038
+ const foreground = resolvedPaint(candidate.node.paint.fill, request);
1039
+ const background = resolvedPaint(candidate.background, request);
1040
+ const ratio = foreground && background ? wcagCssContrastRatio(foreground, background, pageBackground) : null;
1041
+ return { ...candidate, foreground, background, ratio };
1042
+ });
1043
+ const worst = measured.filter((candidate) => candidate.ratio !== null).sort((a, b) => a.ratio - b.ratio)[0];
1044
+ if (worst && worst.ratio < minimum) {
1045
+ const rounded2 = Math.round(worst.ratio * 100) / 100;
1046
+ return warning(constraint, {
1047
+ measurement: "measurable",
1048
+ minimum,
1049
+ ratio: rounded2,
1050
+ role: worst.node.role,
1051
+ mark: worst.node.id,
1052
+ foreground: worst.foreground,
1053
+ background: worst.background,
1054
+ message: `Final ${worst.node.role} contrast is ${rounded2}:1; expected at least ${minimum}:1. Paint remains unchanged.`
1055
+ });
1056
+ }
1057
+ const unresolved = measured.find((candidate) => candidate.ratio === null);
1058
+ if (unresolved) {
1059
+ return warning(constraint, {
1060
+ measurement: "unmeasurable",
1061
+ minimum,
1062
+ role: unresolved.node.role,
1063
+ mark: unresolved.node.id,
1064
+ ...unresolved.foreground ? { foreground: unresolved.foreground } : {},
1065
+ ...unresolved.background ? { background: unresolved.background } : {},
1066
+ message: "Final contrast is unmeasurable because an effective paint pair is unresolved; no ratio was fabricated."
1067
+ });
1068
+ }
1069
+ const rounded = Math.round(worst.ratio * 100) / 100;
1070
+ return warning(constraint, {
1071
+ measurement: "measurable",
1072
+ minimum,
1073
+ ratio: rounded,
1074
+ role: worst.node.role,
1075
+ mark: worst.node.id,
1076
+ foreground: worst.foreground,
1077
+ background: worst.background,
1078
+ message: `Final ${worst.node.role} contrast is ${rounded}:1 and meets ${minimum}:1.`
1079
+ });
1080
+ }
1081
+ function sameConcretePaint(left, right) {
1082
+ const a = tryParseCssColor(left);
1083
+ const b = tryParseCssColor(right);
1084
+ if (!a || !b) return left.trim().toLowerCase() === right.trim().toLowerCase();
1085
+ return a.every((channel, index) => Math.abs(channel - b[index]) < 1e-6);
1086
+ }
1087
+ function accentAreaConstraint(constraint, scene, request) {
1088
+ const accent = resolvedPaint(request.appearance.colors.accent, request);
1089
+ if (!accent) return warning(constraint, {
1090
+ measurement: "unmeasurable",
1091
+ maximum: constraint.maxFraction,
1092
+ message: "Accent-area constraint is unmeasurable because no concrete accent paint is resolved."
1093
+ });
1094
+ let total = 0;
1095
+ let accented = 0;
1096
+ visit(scene.parts, (node) => {
1097
+ if (node.kind !== "shape" || node.role === "chrome") return;
1098
+ const fill = resolvedPaint(node.paint.fill, request);
1099
+ if (!fill || fill === "none" || fill === "transparent") return;
1100
+ const bounds = geometryBounds(node.geometry);
1101
+ if (!bounds) return;
1102
+ const area = Math.max(0, bounds.x1 - bounds.x0) * Math.max(0, bounds.y1 - bounds.y0);
1103
+ total += area;
1104
+ if (sameConcretePaint(fill, accent)) accented += area;
1105
+ });
1106
+ if (total === 0) return warning(constraint, {
1107
+ measurement: "not-applicable",
1108
+ maximum: constraint.maxFraction,
1109
+ message: "Accent-area constraint has no applicable filled shape area."
1110
+ });
1111
+ const actual = Math.round(accented / total * 1e4) / 1e4;
1112
+ return warning(constraint, {
1113
+ measurement: "measurable",
1114
+ actual,
1115
+ maximum: constraint.maxFraction,
1116
+ message: actual > constraint.maxFraction ? `Accent fill covers ${(actual * 100).toFixed(2)}% of applicable shape area; maximum is ${(constraint.maxFraction * 100).toFixed(2)}%. Paint remains unchanged.` : `Accent fill area is within the configured maximum.`
1117
+ });
1118
+ }
1119
+ function monoRoleConstraint(constraint, scene, request) {
1120
+ const paints = [];
1121
+ visit(scene.parts, (node) => {
1122
+ if (node.role !== constraint.role) return;
1123
+ const values = node.kind === "connector" ? [node.stroke.color] : "paint" in node ? [node.paint.fill, node.paint.stroke] : [];
1124
+ for (const value of values) {
1125
+ const paint = resolvedPaint(value, request);
1126
+ if (paint && paint !== "none") paints.push({ mark: node.id, value: paint });
1127
+ }
1128
+ });
1129
+ if (paints.length === 0) return warning(constraint, {
1130
+ measurement: "not-applicable",
1131
+ role: constraint.role,
1132
+ message: `Mono-role constraint has no applicable paint for role ${constraint.role}.`
1133
+ });
1134
+ for (const paint of paints) {
1135
+ const parsed = tryParseCssColor(paint.value);
1136
+ if (!parsed) return warning(constraint, {
1137
+ measurement: "unmeasurable",
1138
+ role: constraint.role,
1139
+ mark: paint.mark,
1140
+ message: `Mono-role paint ${paint.value} is unresolved; no monochrome verdict was fabricated.`
1141
+ });
1142
+ if (Math.abs(parsed[0] - parsed[1]) > 1 || Math.abs(parsed[1] - parsed[2]) > 1) {
1143
+ return warning(constraint, {
1144
+ measurement: "measurable",
1145
+ role: constraint.role,
1146
+ mark: paint.mark,
1147
+ foreground: paint.value,
1148
+ message: `Role ${constraint.role} uses chromatic paint ${paint.value}; the mono-role constraint does not repaint it.`
1149
+ });
1150
+ }
1151
+ }
1152
+ return warning(constraint, {
1153
+ measurement: "measurable",
1154
+ role: constraint.role,
1155
+ message: `Role ${constraint.role} uses only measurable monochrome paint.`
1156
+ });
1157
+ }
1158
+ var CONSTRAINT_EVALUATORS = {
1159
+ contrast: contrastConstraint,
1160
+ "accent-area": accentAreaConstraint,
1161
+ "mono-role": monoRoleConstraint
1162
+ };
1163
+ function evaluateConstraint(constraint, scene, request) {
1164
+ const evaluator = CONSTRAINT_EVALUATORS[constraint.kind];
1165
+ return evaluator(constraint, scene, request);
1166
+ }
1167
+ function evaluateBrandConstraints(scene, request) {
1168
+ const constraints = request.appearance.style?.constraints ?? [];
1169
+ const results = constraints.map((constraint) => evaluateConstraint(constraint, scene, request)).filter((warning2) => {
1170
+ if (warning2.measurement !== "measurable") return true;
1171
+ if (warning2.constraint === "contrast") return (warning2.ratio ?? Infinity) < (warning2.minimum ?? 4.5);
1172
+ if (warning2.constraint === "accent-area") return (warning2.actual ?? 0) > (warning2.maximum ?? 1);
1173
+ return warning2.foreground !== void 0;
1174
+ });
1175
+ return results;
1176
+ }
1177
+
1178
+ // src/agent/verify.ts
1179
+ function familyConfigShapeWarnings(d) {
1180
+ const roots = [d.meta.frontmatter, ...d.meta.initDirectives.map((directive) => directive.parsed)];
1181
+ return familyConfigDiagnostics(d.kind, roots);
1182
+ }
1183
+ var KNOWN_SHAPES = /* @__PURE__ */ new Set([
1184
+ "rectangle",
1185
+ "service",
1186
+ "rounded",
1187
+ "diamond",
1188
+ "stadium",
1189
+ "circle",
1190
+ "subroutine",
1191
+ "doublecircle",
1192
+ "hexagon",
1193
+ "cylinder",
1194
+ "asymmetric",
1195
+ "trapezoid",
1196
+ "trapezoid-alt",
1197
+ "lean-r",
1198
+ "lean-l",
1199
+ "state-start",
1200
+ "state-end",
1201
+ "state-fork",
1202
+ "state-join",
1203
+ "state-choice",
1204
+ "state-history"
1205
+ ]);
1206
+ function opaqueSourceHasOnlyHeader(kind, source) {
1207
+ const statements = source.split(/[;\n]/).map((part) => part.trim()).filter((part) => part && !part.startsWith("%%"));
1208
+ if (statements.length === 0) return true;
1209
+ if (statements.length > 1) return false;
1210
+ const header = statements[0].toLowerCase();
1211
+ const aliases = (builtinFamilyMetadata(kind)?.headers ?? [kind]).map((h) => h.toLowerCase());
1212
+ return aliases.some((alias) => header === alias || header.startsWith(`${alias} `));
1213
+ }
1214
+ function roundtripFaithfulnessWarnings(d) {
1215
+ const before = countStructuralElements(d);
1216
+ if (!before) return [];
1217
+ try {
1218
+ const reparsed = parseRegisteredMermaid(serializeMermaid(d));
1219
+ if (!reparsed.ok) return faithfulnessWarning(before, null);
1220
+ const after = countStructuralElements(reparsed.value);
1221
+ if (!after) return [];
1222
+ return faithfulnessWarning(before, after);
1223
+ } catch {
1224
+ return [];
1225
+ }
1226
+ }
1227
+ function verifyMermaid(input, opts = {}) {
1228
+ logToolInvocation("verify");
1229
+ const parsed = typeof input === "string" ? parseRegisteredMermaid(input) : { ok: true, value: input };
1230
+ if (!parsed.ok) {
1231
+ const sourceWarnings = typeof input === "string" ? flowchartUnsupportedSyntaxWarnings(input) : [];
1232
+ return finalize([{ code: "EMPTY_DIAGRAM" }, ...sourceWarnings], emptyRenderedLayout("flowchart"), opts);
1233
+ }
1234
+ if (parsed.value.body.kind === "preserved") {
1235
+ const { diagnostic } = parsed.value.body;
1236
+ return finalize(
1237
+ [{ code: "RENDER_FAILED", reason: `${diagnostic.code}: ${diagnostic.message}` }],
1238
+ emptyRenderedLayout(parsed.value.kind),
1239
+ opts,
1240
+ false
1241
+ );
1242
+ }
1243
+ const positioned = memoizedVerificationArtifact(parsed.value, opts);
1244
+ try {
1245
+ const structured = verifyStructure(parsed.value, opts, positioned);
1246
+ return withRenderParity(withBrandConstraints(structured, opts, positioned), opts, positioned);
1247
+ } catch (error) {
1248
+ if (!(error instanceof FamilyLayoutError)) throw error;
1249
+ return finalize(
1250
+ [{ code: "RENDER_FAILED", reason: error.message }],
1251
+ emptyRenderedLayout(parsed.value.kind),
1252
+ opts,
1253
+ false
1254
+ );
1255
+ }
1256
+ }
1257
+ function withBrandConstraints(result, opts, positioned) {
1258
+ if (!result.ok) return result;
1259
+ try {
1260
+ const artifact = positioned();
1261
+ const constraints = artifact?.request.appearance.style?.constraints;
1262
+ if (!artifact || !constraints || constraints.length === 0) return result;
1263
+ const scene = lowerPositionedFamilyScene(artifact.request, artifact.layoutResult);
1264
+ const suppressed = new Set(opts.suppress ?? []);
1265
+ const warnings = evaluateBrandConstraints(scene, artifact.request).filter((warning2) => !suppressed.has(warning2.code));
1266
+ return {
1267
+ ...result,
1268
+ ok: result.ok && !warnings.some((warning2) => WARNING_SEVERITY[warning2.code] === "error"),
1269
+ warnings: [...result.warnings, ...warnings]
1270
+ };
1271
+ } catch {
1272
+ return result;
1273
+ }
1274
+ }
1275
+ function memoizedVerificationArtifact(d, opts) {
1276
+ let attempted = false;
1277
+ let artifact = null;
1278
+ let failure;
1279
+ return () => {
1280
+ if (attempted) {
1281
+ if (failure !== void 0) throw failure;
1282
+ return artifact;
1283
+ }
1284
+ attempted = true;
1285
+ try {
1286
+ artifact = positionFamilyArtifact(d, {
1287
+ renderOptions: opts.renderOptions,
1288
+ output: "svg"
1289
+ });
1290
+ return artifact;
1291
+ } catch (error) {
1292
+ failure = error;
1293
+ throw error;
1294
+ }
1295
+ };
1296
+ }
1297
+ function withRenderParity(result, opts, positioned) {
1298
+ if (!result.ok || (opts.suppress ?? []).some((code) => code === "RENDER_FAILED" || WARNING_SEVERITY[code] === "error")) return result;
1299
+ try {
1300
+ const artifact = positioned();
1301
+ if (!artifact) throw new Error(`Mermaid family "${result.layout.kind}" has no positioned graphical artifact`);
1302
+ renderPositionedMermaidSVG(artifact.request, artifact.layoutResult, []);
1303
+ return result;
1304
+ } catch (e) {
1305
+ const reason = e instanceof Error ? e.message : String(e);
1306
+ return { ...result, ok: false, warnings: [...result.warnings, { code: "RENDER_FAILED", reason }] };
1307
+ }
1308
+ }
1309
+ function ganttTodayMarkerWarnings(d) {
1310
+ try {
1311
+ const normalized = normalizeMermaidSource(d.canonicalSource);
1312
+ const model = applyGanttFrontmatterConfig(parseGanttModel(normalized.lines), normalized.frontmatter);
1313
+ const payload = model.todayMarker?.style;
1314
+ if (payload === void 0) return [];
1315
+ return [...parseTodayMarkerStyle(payload).ignored].sort().map((prop) => ({
1316
+ code: "INEFFECTIVE_CONFIG",
1317
+ field: `todayMarker.${prop}`,
1318
+ message: `Gantt todayMarker style property "${prop}" is accepted for Mermaid compatibility but is not applied to the today line (wired, sanitized properties: ${GANTT_TODAY_MARKER_STYLE_PROPS.join(", ")}).`
1319
+ }));
1320
+ } catch {
1321
+ return [];
1322
+ }
1323
+ }
1324
+ function flowchartShapeSubstitutionWarnings(d) {
1325
+ if (d.kind !== "flowchart" || d.body.kind !== "flowchart") return [];
1326
+ const warnings = [];
1327
+ for (const node of d.body.graph.nodes.values()) {
1328
+ if (!node.semanticShape) continue;
1329
+ const v11 = normalizeV11Shape(node.semanticShape);
1330
+ if (!v11 || v11.exact) continue;
1331
+ warnings.push({
1332
+ code: "UNSUPPORTED_SYNTAX",
1333
+ syntax: "flowchart_shape_substitution",
1334
+ node: node.id,
1335
+ message: `Mermaid v11 shape "${node.authoredShape ?? node.semanticShape}" (${v11.description}) has no dedicated renderer yet; node "${node.id}" renders with the nearest geometry "${v11.geometry}".`
1336
+ });
1337
+ }
1338
+ return warnings;
1339
+ }
1340
+ function quadrantInertStyleWarnings(d) {
1341
+ if (d.body.kind !== "quadrant") return [];
1342
+ const inert = /* @__PURE__ */ new Set();
1343
+ const collect = (style) => {
1344
+ for (const entry of style?.extra ?? []) inert.add(entry.slice(0, entry.indexOf(":")).trim());
1345
+ };
1346
+ for (const point of d.body.points) collect(point.style);
1347
+ for (const style of Object.values(d.body.classDefs ?? {})) collect(style);
1348
+ return [...inert].sort().map((property) => ({
1349
+ code: "UNSUPPORTED_SYNTAX",
1350
+ syntax: "quadrant_style_property",
1351
+ message: `Quadrant style property "${property}" is preserved in source for upstream compatibility but has no effect on rendered points (rendered properties: radius, color, stroke-color, stroke-width).`
1352
+ }));
1353
+ }
1354
+ function radarAuthoredContrastWarnings(positioned) {
1355
+ try {
1356
+ const artifact = positioned();
1357
+ if (artifact?.request.renderOptions.transparent) return [];
1358
+ const visual = artifact?.request.familyConfig?.visual;
1359
+ const foreground = visual?.axisColor;
1360
+ if (typeof foreground !== "string") return [];
1361
+ const background = artifact.request.appearance.colors.bg;
1362
+ const ratio = wcagCssContrastRatio(foreground, background);
1363
+ if (ratio === null || ratio >= 4.5) return [];
1364
+ const roundedRatio = Math.round(ratio * 100) / 100;
1365
+ return [{
1366
+ code: "LOW_CONTRAST",
1367
+ field: "themeVariables.radar.axisColor",
1368
+ foreground,
1369
+ background,
1370
+ ratio: roundedRatio,
1371
+ minimum: 4.5,
1372
+ message: `Authored Radar axis label color ${foreground} has contrast ${roundedRatio}:1 against ${background}; expected at least 4.5:1. The color is preserved as authored.`
1373
+ }];
1374
+ } catch {
1375
+ return [];
1376
+ }
1377
+ }
1378
+ function configWarningsForDiagram(d) {
1379
+ if (d.body.kind === "extension" || d.body.kind === "preserved") return [];
1380
+ const builtin = d;
1381
+ const familySpecific = builtin.kind === "gantt" ? ganttTodayMarkerWarnings(builtin) : [];
1382
+ return dedupedConcat(familySpecific, familyConfigShapeWarnings(builtin));
1383
+ }
1384
+ function configWarningsForMermaid(source) {
1385
+ const parsed = parseRegisteredMermaid(source);
1386
+ return parsed.ok ? configWarningsForDiagram(parsed.value) : [];
1387
+ }
1388
+ function verifyStructure(parsed, opts, positioned) {
1389
+ const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP;
1390
+ const metaWarnings = parsed.meta.droppedComments?.length ? [{ code: "COMMENT_DROPPED", count: parsed.meta.droppedComments.length, lines: parsed.meta.droppedComments.map((c) => c.line) }] : [];
1391
+ const dispatchedWarnings = dispatchFamilyVerify(parsed, opts);
1392
+ if (parsed.body.kind === "extension") {
1393
+ return verifyExtension(parsed, cap, opts, dedupedConcat(metaWarnings, dispatchedWarnings), positioned);
1394
+ }
1395
+ const d = parsed;
1396
+ const sourceWarnings = d.kind === "flowchart" ? dedupedConcat(flowchartUnsupportedSyntaxWarnings(d.canonicalSource), flowchartShapeSubstitutionWarnings(d)) : d.kind === "er" ? erUnsupportedSyntaxWarnings(d.canonicalSource) : d.kind === "quadrant" ? quadrantInertStyleWarnings(d) : [];
1397
+ const faithfulnessWarnings = roundtripFaithfulnessWarnings(d);
1398
+ const configWarnings = configWarningsForDiagram(d);
1399
+ let pluginWarnings = dedupedConcat(dedupedConcat(dedupedConcat(dedupedConcat(metaWarnings, dispatchedWarnings), sourceWarnings), faithfulnessWarnings), configWarnings);
1400
+ if (d.body.kind === "class" && (d.meta.accessibility.title !== void 0 || d.meta.accessibility.descr !== void 0)) {
1401
+ pluginWarnings = pluginWarnings.filter((warning2) => warning2.code !== "EMPTY_DIAGRAM");
1402
+ }
1403
+ if (d.body.kind === "sequence") return mergeFinalize(verifySequence(d, cap, opts, positioned), pluginWarnings, opts);
1404
+ if (d.body.kind === "timeline") return mergeFinalize(verifyTimeline(d, cap, opts, positioned), pluginWarnings, opts);
1405
+ if (d.body.kind === "gantt") {
1406
+ const schedFail = ganttScheduleWarning(d);
1407
+ if (schedFail) {
1408
+ return finalize(
1409
+ dedupedConcat(pluginWarnings, [schedFail]),
1410
+ emptyRenderedLayout(d.kind),
1411
+ opts,
1412
+ false
1413
+ );
1414
+ }
1415
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1416
+ const layout = layoutOutcome.layout;
1417
+ const geometric = dedupedConcat(
1418
+ ganttGeometryWarnings(layout),
1419
+ layoutOutcome.warnings
1420
+ );
1421
+ return finalize(dedupedConcat(pluginWarnings, geometric), layout, opts);
1422
+ }
1423
+ if (d.body.kind === "class" || d.body.kind === "er" || d.body.kind === "journey" || d.body.kind === "architecture" || d.body.kind === "xychart" || d.body.kind === "pie" || d.body.kind === "quadrant" || d.body.kind === "mindmap" || d.body.kind === "gitgraph" || d.body.kind === "radar") {
1424
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1425
+ const layout = layoutOutcome.layout;
1426
+ const familyGeometry = d.body.kind === "class" || d.body.kind === "er" ? layoutGeometryWarnings(layout, { edgeAnchors: true, nodeOverlaps: true, groupContainment: d.body.kind === "class" }) : layoutGeometryWarnings(layout, {
1427
+ nodeOverlaps: d.body.kind === "journey",
1428
+ // Journey sections are groups with task members (family-layouts.ts),
1429
+ // so a task laid outside its section band is a reportable breach.
1430
+ groupContainment: d.body.kind === "xychart" || d.body.kind === "quadrant" ? "center" : d.body.kind === "journey"
1431
+ });
1432
+ const appearanceWarnings = d.body.kind === "radar" ? radarAuthoredContrastWarnings(positioned) : [];
1433
+ return finalize(
1434
+ dedupedConcat(dedupedConcat(dedupedConcat(pluginWarnings, familyGeometry), layoutOutcome.warnings), appearanceWarnings),
1435
+ layout,
1436
+ opts
1437
+ );
1438
+ }
1439
+ if (d.body.kind === "state") {
1440
+ const graph2 = stateBodyToGraph(d.body);
1441
+ if (graph2.nodes.size === 0) return finalize(dedupedConcat([{ code: "EMPTY_DIAGRAM" }], pluginWarnings), emptyRenderedLayout(d.kind), opts);
1442
+ const { warnings, layout } = verifyGraph(graph2, d, cap, positioned);
1443
+ return finalize(dedupedConcat(warnings, pluginWarnings), layout, opts);
1444
+ }
1445
+ if (d.body.kind === "opaque") {
1446
+ const isEmpty = opaqueSourceHasOnlyHeader(d.kind, d.body.source);
1447
+ const plugin = getFamily(d.kind);
1448
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source);
1449
+ const warnings = isEmpty ? [{ code: "EMPTY_DIAGRAM" }] : [];
1450
+ if (!isEmpty && !pluginWarnings.some((w) => w.code === "UNSUPPORTED_SYNTAX")) {
1451
+ warnings.push({
1452
+ code: "UNSUPPORTED_SYNTAX",
1453
+ syntax: `${d.kind}_opaque`,
1454
+ message: `This ${d.kind} diagram uses syntax the structured parser does not model; it is preserved verbatim as source. Typed mutation (the ${d.kind} \`as*\` narrower) is unavailable \u2014 edit the source directly, or verify/render it as-is.`
1455
+ });
1456
+ }
1457
+ const seen = /* @__PURE__ */ new Set();
1458
+ for (const lbl of labels) {
1459
+ const w = labelOverflowWarning(lbl.target, lbl.text, cap);
1460
+ if (!w) continue;
1461
+ const key = `${lbl.target}:${lbl.text}`;
1462
+ if (seen.has(key)) continue;
1463
+ seen.add(key);
1464
+ warnings.push(w);
1465
+ }
1466
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1467
+ const opaqueLayout = layoutOutcome.layout;
1468
+ return finalize(dedupedConcat(dedupedConcat(warnings, pluginWarnings), layoutOutcome.warnings), opaqueLayout, opts, false);
1469
+ }
1470
+ const graph = d.body.graph;
1471
+ if (graph.nodes.size === 0) return finalize([{ code: "EMPTY_DIAGRAM" }], emptyRenderedLayout(d.kind), opts);
1472
+ const { warnings: graphWarnings, layout: graphLayout } = verifyGraph(graph, d, cap, positioned);
1473
+ return finalize(dedupedConcat(graphWarnings, pluginWarnings), graphLayout, opts);
1474
+ }
1475
+ function verifyExtension(d, cap, opts, initialWarnings, positioned) {
1476
+ const warnings = [...initialWarnings];
1477
+ const descriptor = getFamily(d.kind);
1478
+ const labels = (descriptor?.extractLabels ?? extractLabelsGeneric)(d.body.source);
1479
+ const seenLabels = /* @__PURE__ */ new Set();
1480
+ for (const label of labels) {
1481
+ const warning2 = labelOverflowWarning(label.target, label.text, cap);
1482
+ if (!warning2) continue;
1483
+ const key = `${label.target}:${label.text}`;
1484
+ if (seenLabels.has(key)) continue;
1485
+ seenLabels.add(key);
1486
+ warnings.push(warning2);
1487
+ }
1488
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1489
+ return finalize([...warnings, ...layoutOutcome.warnings], layoutOutcome.layout, opts, false);
1490
+ }
1491
+ function familyLayoutForVerify(d, positioned) {
1492
+ try {
1493
+ const artifact = positioned();
1494
+ return artifact ? { layout: artifact.rendered, warnings: [] } : {
1495
+ layout: emptyRenderedLayout(d.kind),
1496
+ warnings: [{
1497
+ code: "RENDER_FAILED",
1498
+ reason: `Mermaid family "${d.kind}" has no public layout projection registered`
1499
+ }]
1500
+ };
1501
+ } catch (error) {
1502
+ const reason = error instanceof Error ? error.message : String(error);
1503
+ return {
1504
+ layout: emptyRenderedLayout(d.kind),
1505
+ warnings: [{ code: "RENDER_FAILED", reason }]
1506
+ };
1507
+ }
1508
+ }
1509
+ function verifyGraph(graph, d, cap, positionedArtifact) {
1510
+ let artifact;
1511
+ try {
1512
+ artifact = positionedArtifact();
1513
+ } catch (error) {
1514
+ const reason = error instanceof Error ? error.message : String(error);
1515
+ return { warnings: [{ code: "RENDER_FAILED", reason }], layout: emptyRenderedLayout(d.kind) };
1516
+ }
1517
+ if (!artifact || !isPositionedGraph(artifact.positioned)) {
1518
+ return {
1519
+ warnings: [{ code: "UNSUPPORTED_SYNTAX", syntax: "positioned_artifact", message: `The ${d.kind} family did not produce its declared positioned-graph artifact.` }],
1520
+ layout: emptyRenderedLayout(d.kind)
1521
+ };
1522
+ }
1523
+ const positioned = artifact.positioned;
1524
+ const layout = artifact.rendered;
1525
+ const warnings = [];
1526
+ for (const edge of graph.edges) {
1527
+ const hasSource = graph.nodes.has(edge.source) || Boolean(findSubgraphById(graph.subgraphs, edge.source));
1528
+ const hasTarget = graph.nodes.has(edge.target) || Boolean(findSubgraphById(graph.subgraphs, edge.target));
1529
+ if (!hasSource || !hasTarget) {
1530
+ warnings.push({
1531
+ code: "EDGE_MISANCHORED",
1532
+ edge: `${edge.source}->${edge.target}`,
1533
+ from: hasSource ? edge.source : void 0,
1534
+ to: hasTarget ? edge.target : void 0
1535
+ });
1536
+ }
1537
+ }
1538
+ for (const [id, node] of graph.nodes) {
1539
+ if (!KNOWN_SHAPES.has(node.shape)) warnings.push({ code: "UNKNOWN_SHAPE", node: id, shape: String(node.shape) });
1540
+ const w = labelOverflowWarning(id, node.label, cap);
1541
+ if (w) warnings.push(w);
1542
+ }
1543
+ for (const edge of graph.edges) {
1544
+ const w = edge.label ? labelOverflowWarning(`${edge.source}->${edge.target}`, edge.label, cap) : null;
1545
+ if (w) warnings.push(w);
1546
+ }
1547
+ for (const n of positioned.nodes) {
1548
+ if (n.x < 0 || n.x + n.width > positioned.width + 1) warnings.push({ code: "OFF_CANVAS", target: n.id, axis: "x" });
1549
+ if (n.y < 0 || n.y + n.height > positioned.height + 1) warnings.push({ code: "OFF_CANVAS", target: n.id, axis: "y" });
1550
+ }
1551
+ for (const g of positioned.groups) {
1552
+ const visit2 = (group) => {
1553
+ const sg = findSubgraphById(graph.subgraphs, group.id);
1554
+ if (sg) for (const n of positioned.nodes) {
1555
+ if (!sg.nodeIds.includes(n.id)) continue;
1556
+ const inside = n.x >= group.x && n.y >= group.y && n.x + n.width <= group.x + group.width + 0.5 && n.y + n.height <= group.y + group.height + 0.5;
1557
+ if (!inside) warnings.push({ code: "GROUP_BREACH", group: group.id, member: n.id });
1558
+ }
1559
+ for (const c of group.children) visit2(c);
1560
+ };
1561
+ visit2(g);
1562
+ }
1563
+ for (let i = 0; i < positioned.nodes.length; i++) {
1564
+ for (let j = i + 1; j < positioned.nodes.length; j++) {
1565
+ const a = positioned.nodes[i], b = positioned.nodes[j];
1566
+ const o = rectIntersection(a, b);
1567
+ if (o > 0) warnings.push({ code: "NODE_OVERLAP", a: a.id, b: b.id, areaPx: Math.round(o) });
1568
+ }
1569
+ }
1570
+ for (const e of positioned.edges) {
1571
+ const c = countSelfCrossings(e.points);
1572
+ if (c > 0) warnings.push({ code: "ROUTE_SELF_CROSS", edge: `${e.source}->${e.target}`, count: c });
1573
+ }
1574
+ for (const hitch of findRouteHitches(positioned, graph)) {
1575
+ warnings.push({ code: "ROUTE_HITCH", edge: hitch.edge, deviationPx: hitch.deviationPx });
1576
+ }
1577
+ warnings.push(...auditRouteContracts(positioned, graph));
1578
+ warnings.push(...lintFlowchartGraph(graph));
1579
+ return { warnings, layout };
1580
+ }
1581
+ function dedupedConcat(a, b) {
1582
+ if (b.length === 0) return a;
1583
+ const seen = new Set(a.map(warningKey));
1584
+ const novel = b.filter((w) => !seen.has(warningKey(w)));
1585
+ return novel.length === 0 ? a : [...a, ...novel];
1586
+ }
1587
+ function dispatchFamilyVerify(d, opts) {
1588
+ const plugin = getFamily(d.kind);
1589
+ if (!plugin?.verify) return d.body.kind === "extension" ? [{ code: "RENDER_FAILED", reason: `Mermaid family "${d.kind}" has no verify hook registered` }] : [];
1590
+ try {
1591
+ let body = d.body;
1592
+ if (d.body.kind === "extension") {
1593
+ const extension = d;
1594
+ if (!sameExtensionIdentity(extension.descriptorIdentity, plugin.identity)) {
1595
+ const reparsed = parseRegisteredMermaid(serializeMermaid(d));
1596
+ if (!reparsed.ok || reparsed.value.kind !== d.kind || reparsed.value.body.kind !== "extension") {
1597
+ return [{
1598
+ code: "RENDER_FAILED",
1599
+ reason: `Mermaid family "${d.kind}" could not reparse source under its current descriptor before verification`
1600
+ }];
1601
+ }
1602
+ body = reparsed.value.body;
1603
+ }
1604
+ }
1605
+ return plugin.verify(body, opts);
1606
+ } catch (error) {
1607
+ const reason = error instanceof Error ? error.message : String(error);
1608
+ return [{ code: "RENDER_FAILED", reason: `Mermaid family "${d.kind}" verify hook failed: ${reason}` }];
1609
+ }
1610
+ }
1611
+ function mergeFinalize(prev, extra, opts) {
1612
+ if (extra.length === 0) return prev;
1613
+ const merged = dedupedConcat(prev.warnings, extra);
1614
+ if (merged === prev.warnings) return prev;
1615
+ return finalize(merged, prev.layout, opts, false);
1616
+ }
1617
+ function warningKey(w) {
1618
+ if (w.code === "UNSUPPORTED_SYNTAX") return `${w.code}:${w.syntax}:${w.line ?? ""}:${w.node ?? ""}`;
1619
+ if (w.code === "RENDER_FAILED") return `${w.code}:${w.reason}`;
1620
+ if ("target" in w) return `${w.code}:${w.target}`;
1621
+ if ("edge" in w) return `${w.code}:${w.edge}`;
1622
+ if ("node" in w) return `${w.code}:${w.node}`;
1623
+ if ("group" in w) return `${w.code}:${w.group}`;
1624
+ if ("a" in w && "b" in w) return `${w.code}:${w.a}|${w.b}`;
1625
+ return w.code;
1626
+ }
1627
+ function lintFlowchartGraph(graph) {
1628
+ const warnings = [];
1629
+ const firstBySignature = /* @__PURE__ */ new Map();
1630
+ graph.edges.forEach((edge, index) => {
1631
+ const signature = JSON.stringify({
1632
+ source: edge.source,
1633
+ target: edge.target,
1634
+ label: edge.label ?? "",
1635
+ style: edge.style,
1636
+ hasArrowStart: edge.hasArrowStart,
1637
+ hasArrowEnd: edge.hasArrowEnd,
1638
+ startMarker: edge.startMarker ?? "arrow",
1639
+ endMarker: edge.endMarker ?? "arrow"
1640
+ });
1641
+ const id = `${edge.source}->${edge.target}#${index}`;
1642
+ const first = firstBySignature.get(signature);
1643
+ if (first) {
1644
+ warnings.push({ code: "DUPLICATE_EDGE", edge: id, duplicateOf: first.edge, from: edge.source, to: edge.target, label: edge.label });
1645
+ } else {
1646
+ firstBySignature.set(signature, { edge: id, from: edge.source, to: edge.target, label: edge.label });
1647
+ }
1648
+ });
1649
+ const outEdges = /* @__PURE__ */ new Map();
1650
+ graph.edges.forEach((edge, index) => {
1651
+ const node = graph.nodes.get(edge.source);
1652
+ if (!node || node.shape !== "diamond" || edge.source === edge.target) return;
1653
+ const entry = outEdges.get(edge.source) ?? { labeled: 0, unlabeled: [] };
1654
+ if (edge.label && edge.label.trim().length > 0) entry.labeled++;
1655
+ else entry.unlabeled.push(`${edge.source}->${edge.target}#${index}`);
1656
+ outEdges.set(edge.source, entry);
1657
+ });
1658
+ for (const [nodeId, entry] of outEdges) {
1659
+ if (entry.labeled + entry.unlabeled.length < 2) continue;
1660
+ for (const edge of entry.unlabeled) {
1661
+ warnings.push({ code: "DECISION_BRANCH_UNLABELED", node: nodeId, edge });
1662
+ }
1663
+ }
1664
+ const ids = Array.from(graph.nodes.keys());
1665
+ if (ids.length === 0 || graph.edges.length === 0) return warnings;
1666
+ const incoming = new Map(ids.map((id) => [id, 0]));
1667
+ const outgoing = new Map(ids.map((id) => [id, []]));
1668
+ for (const edge of graph.edges) {
1669
+ if (!graph.nodes.has(edge.source) || !graph.nodes.has(edge.target)) continue;
1670
+ incoming.set(edge.target, (incoming.get(edge.target) ?? 0) + 1);
1671
+ outgoing.get(edge.source).push(edge.target);
1672
+ }
1673
+ const roots = ids.filter((id) => (incoming.get(id) ?? 0) === 0);
1674
+ if (roots.length === 0) return warnings;
1675
+ const seen = new Set(roots);
1676
+ const queue = [...roots];
1677
+ for (let i = 0; i < queue.length; i++) {
1678
+ for (const next of outgoing.get(queue[i]) ?? []) {
1679
+ if (seen.has(next)) continue;
1680
+ seen.add(next);
1681
+ queue.push(next);
1682
+ }
1683
+ }
1684
+ for (const id of ids) {
1685
+ if (!seen.has(id)) warnings.push({ code: "UNREACHABLE_NODE", node: id });
1686
+ }
1687
+ return warnings;
1688
+ }
1689
+ function verifyTimeline(d, cap, opts, positioned) {
1690
+ const body = d.body;
1691
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1692
+ const layout = layoutOutcome.layout;
1693
+ const warnings = [];
1694
+ const hasContent = body.sections.length > 0 || body.title !== void 0 || body.accessibilityTitle !== void 0 || body.accessibilityDescription !== void 0;
1695
+ if (!hasContent) return finalize([{ code: "EMPTY_DIAGRAM" }], layout, opts);
1696
+ const overflow = (target, text) => {
1697
+ const w = text !== void 0 ? labelOverflowWarning(target, text, cap) : null;
1698
+ if (w) warnings.push(w);
1699
+ };
1700
+ overflow("title", body.title);
1701
+ for (const s of body.sections) {
1702
+ overflow(s.id, s.label);
1703
+ for (const p of s.periods) {
1704
+ overflow(p.id, p.label);
1705
+ for (const e of p.events) overflow(e.id, e.text);
1706
+ }
1707
+ }
1708
+ return finalize(dedupedConcat(dedupedConcat(warnings, layoutGeometryWarnings(layout, { nodeOverlaps: true, groupContainment: true })), layoutOutcome.warnings), layout, opts);
1709
+ }
1710
+ function verifySequence(d, cap, opts, positioned) {
1711
+ const body = d.body;
1712
+ const layoutOutcome = familyLayoutForVerify(d, positioned);
1713
+ const layout = layoutOutcome.layout;
1714
+ const warnings = [];
1715
+ const hasOpaqueContent = body.statements.some(
1716
+ (s) => s.kind === "opaque-block" && s.lines.some((l) => l.trim().length > 0)
1717
+ );
1718
+ const allMessages = sequenceMessages(body);
1719
+ if (body.participants.length === 0 && allMessages.length === 0 && !hasOpaqueContent) {
1720
+ return finalize([{ code: "EMPTY_DIAGRAM" }], layout, opts, false);
1721
+ }
1722
+ const ids = new Set(body.participants.map((p) => p.id));
1723
+ allMessages.forEach((m, i) => {
1724
+ if (!ids.has(m.from) || !ids.has(m.to)) {
1725
+ warnings.push({
1726
+ code: "EDGE_MISANCHORED",
1727
+ edge: `msg#${i}:${m.from}->${m.to}`,
1728
+ from: ids.has(m.from) ? m.from : void 0,
1729
+ to: ids.has(m.to) ? m.to : void 0
1730
+ });
1731
+ }
1732
+ const w = labelOverflowWarning(`msg#${i}:${m.from}->${m.to}`, m.text, cap);
1733
+ if (w) warnings.push(w);
1734
+ });
1735
+ for (const p of body.participants) {
1736
+ const w = labelOverflowWarning(p.id, p.label, cap);
1737
+ if (w) warnings.push(w);
1738
+ }
1739
+ for (const statement of body.statements) if (statement.kind === "fragment") {
1740
+ if (statement.fragment.label) {
1741
+ const w = labelOverflowWarning(`fragment:${statement.fragment.fragmentKind}`, statement.fragment.label, cap);
1742
+ if (w) warnings.push(w);
1743
+ }
1744
+ for (const [index, branch] of statement.fragment.branches.entries()) if (branch.label) {
1745
+ const w = labelOverflowWarning(`fragment:${statement.fragment.fragmentKind}:branch#${index}`, branch.label, cap);
1746
+ if (w) warnings.push(w);
1747
+ }
1748
+ }
1749
+ const opaqueLines = body.statements.filter((s) => s.kind === "opaque-block").flatMap((s) => s.lines);
1750
+ if (opaqueLines.length > 0) {
1751
+ warnings.push({
1752
+ code: "UNSUPPORTED_SYNTAX",
1753
+ syntax: "sequence_opaque_segment",
1754
+ message: "This sequence contains source-preserved constructs that are not represented in describe/facts or typed fragment operations. Inspect the source directly before relying on semantic read-back."
1755
+ });
1756
+ const plugin = getFamily(d.kind);
1757
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(opaqueLines.join("\n"));
1758
+ const seen = /* @__PURE__ */ new Set();
1759
+ for (const lbl of labels) {
1760
+ const w = labelOverflowWarning(lbl.target, lbl.text, cap);
1761
+ if (!w) continue;
1762
+ const key = `${lbl.target}:${lbl.text}`;
1763
+ if (seen.has(key)) continue;
1764
+ seen.add(key);
1765
+ warnings.push(w);
1766
+ }
1767
+ }
1768
+ const guardEmpty = body.participants.length === 0 && allMessages.length === 0;
1769
+ return finalize(dedupedConcat(dedupedConcat(warnings, layoutGeometryWarnings(layout, { nodeOverlaps: true })), layoutOutcome.warnings), layout, opts, guardEmpty);
1770
+ }
1771
+ function isPositionedGraph(positioned) {
1772
+ const candidate = positioned;
1773
+ return Array.isArray(candidate.nodes) && Array.isArray(candidate.edges) && Array.isArray(candidate.groups);
1774
+ }
1775
+ function finalize(warnings, layout, opts, guardEmptyLayout = true) {
1776
+ const suppress = new Set(opts.suppress ?? []);
1777
+ const kept = warnings.filter((w) => !suppress.has(w.code));
1778
+ const ok2 = !kept.some((w) => WARNING_SEVERITY[w.code] === "error");
1779
+ if (guardEmptyLayout && !suppress.has("UNSUPPORTED_SYNTAX") && layout.nodes.length === 0 && layout.edges.length === 0 && layout.groups.length === 0 && layout.bounds.w === 0 && layout.bounds.h === 0 && !kept.some((w) => w.code === "EMPTY_DIAGRAM") && !kept.some((w) => w.code === "UNSUPPORTED_SYNTAX" && w.syntax === "empty_layout")) {
1780
+ return {
1781
+ ok: ok2,
1782
+ warnings: [...kept, { code: "UNSUPPORTED_SYNTAX", syntax: "empty_layout", message: "The source carries content, but the local layout renders nothing (0x0 canvas with no nodes, edges, or groups)." }],
1783
+ layout
1784
+ };
1785
+ }
1786
+ return { ok: ok2, warnings: kept, layout };
1787
+ }
1788
+ function rectIntersection(a, b) {
1789
+ const xo = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
1790
+ const yo = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
1791
+ return xo * yo;
1792
+ }
1793
+ function countSelfCrossings(points) {
1794
+ if (points.length < 4) return 0;
1795
+ let count = 0;
1796
+ for (let i = 0; i < points.length - 1; i++)
1797
+ for (let j = i + 2; j < points.length - 1; j++) {
1798
+ if (i === 0 && j === points.length - 2) continue;
1799
+ if (segInt(points[i], points[i + 1], points[j], points[j + 1])) count++;
1800
+ }
1801
+ return count;
1802
+ }
1803
+ function segInt(p1, p2, p3, p4) {
1804
+ const d1 = cr(p4.x - p3.x, p4.y - p3.y, p1.x - p3.x, p1.y - p3.y);
1805
+ const d2 = cr(p4.x - p3.x, p4.y - p3.y, p2.x - p3.x, p2.y - p3.y);
1806
+ const d3 = cr(p2.x - p1.x, p2.y - p1.y, p3.x - p1.x, p3.y - p1.y);
1807
+ const d4 = cr(p2.x - p1.x, p2.y - p1.y, p4.x - p1.x, p4.y - p1.y);
1808
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
1809
+ }
1810
+ function cr(x1, y1, x2, y2) {
1811
+ return x1 * y2 - x2 * y1;
1812
+ }
1813
+ function findSubgraphById(list, id) {
1814
+ for (const sg of list) {
1815
+ if (sg.id === id) return sg;
1816
+ const c = findSubgraphById(sg.children, id);
1817
+ if (c) return c;
1818
+ }
1819
+ return null;
1820
+ }
1821
+
1822
+ // src/agent/apply.ts
1823
+ function verifySummary(v) {
1824
+ return JSON.parse(JSON.stringify({ ok: v.ok, warnings: v.warnings }));
1825
+ }
1826
+ function snapshotOpArray(value) {
1827
+ try {
1828
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) return { ok: false };
1829
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1830
+ const length = descriptors.length?.value;
1831
+ if (!Number.isSafeInteger(length) || length < 0) return { ok: false };
1832
+ const keys = Reflect.ownKeys(value);
1833
+ if (keys.some((key) => typeof key === "symbol" || key !== "length" && !/^(0|[1-9]\d*)$/.test(key))) return { ok: false };
1834
+ const snapshot = [];
1835
+ for (let index = 0; index < length; index++) {
1836
+ const descriptor = descriptors[String(index)];
1837
+ if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) return { ok: false };
1838
+ snapshot.push(descriptor.value);
1839
+ }
1840
+ return { ok: true, value: snapshot };
1841
+ } catch {
1842
+ return { ok: false };
1843
+ }
1844
+ }
1845
+ function buildChecked(kind, ops, opts) {
1846
+ const admitted = snapshotOpArray(ops);
1847
+ if (!admitted.ok) {
1848
+ return err({ code: "INVALID_OP", message: "`ops` must be an array of op objects", opIndex: -1 });
1849
+ }
1850
+ const checkedOps = admitted.value;
1851
+ let d;
1852
+ try {
1853
+ d = createMermaid(kind, opts);
1854
+ } catch (e) {
1855
+ return err({ code: "INVALID_OP", message: e instanceof Error ? e.message : String(e), opIndex: -1 });
1856
+ }
1857
+ for (let i = 0; i < checkedOps.length; i++) {
1858
+ const r = mutateChecked(d, checkedOps[i]);
1859
+ if (!r.ok) {
1860
+ let kindLabel;
1861
+ try {
1862
+ kindLabel = checkedOps[i]?.kind;
1863
+ } catch {
1864
+ }
1865
+ return err({ ...r.error, opIndex: i, message: `op[${i}]${typeof kindLabel === "string" ? ` (${kindLabel})` : ""}: ${r.error.message}` });
1866
+ }
1867
+ d = r.value;
1868
+ }
1869
+ return ok(d);
1870
+ }
1871
+ function applyOps(input) {
1872
+ let source;
1873
+ let family;
1874
+ let ops;
1875
+ try {
1876
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
1877
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "applyOps input must be a plain object" } };
1878
+ }
1879
+ const prototype = Object.getPrototypeOf(input);
1880
+ if (prototype !== Object.prototype && prototype !== null) {
1881
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "applyOps input must be a plain object" } };
1882
+ }
1883
+ ;
1884
+ ({ source, family, ops } = input);
1885
+ } catch {
1886
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "applyOps input must be a readable plain object" } };
1887
+ }
1888
+ const admittedOps = snapshotOpArray(ops);
1889
+ if (!admittedOps.ok) {
1890
+ return { ok: false, family: typeof family === "string" && hasOpSchema(family) ? family : null, error: { code: "INVALID_OP", message: "`ops` must be an array of op objects" } };
1891
+ }
1892
+ const checkedOps = admittedOps.value;
1893
+ if (source !== void 0 && typeof source !== "string") {
1894
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "`source` must be a string when provided" } };
1895
+ }
1896
+ if (family !== void 0 && typeof family !== "string") {
1897
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "`family` must be a string when provided" } };
1898
+ }
1899
+ if (typeof source === "string" && typeof family === "string") {
1900
+ return { ok: false, family: hasOpSchema(family) ? family : null, error: { code: "INVALID_OP", message: "applyOps accepts either `source` or `family`, not both" } };
1901
+ }
1902
+ let d;
1903
+ if (typeof source === "string" && source.trim().length > 0) {
1904
+ const parsed = parseRegisteredMermaid(source);
1905
+ if (!parsed.ok) {
1906
+ const primary = parsed.error[0];
1907
+ return {
1908
+ ok: false,
1909
+ family: null,
1910
+ error: {
1911
+ code: primary?.code ?? "PARSE_ERROR",
1912
+ message: parsed.error.map((e) => e.message).join("; ") || "could not parse source"
1913
+ }
1914
+ };
1915
+ }
1916
+ d = parsed.value;
1917
+ if (!hasOpSchema(d.kind)) {
1918
+ return { ok: false, family: d.kind, error: { code: "INVALID_OP", message: `family "${d.kind}" has no structured ops; edit it as source text` } };
1919
+ }
1920
+ if (checkedOps.length === 0) {
1921
+ return { ok: false, family: d.kind, error: { code: "INVALID_OP", message: "`ops` must contain at least one op object" } };
1922
+ }
1923
+ } else if (typeof family === "string") {
1924
+ if (!hasOpSchema(family)) {
1925
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: `unknown family "${family}"` } };
1926
+ }
1927
+ if (checkedOps.length === 0) {
1928
+ return { ok: false, family, error: { code: "INVALID_OP", message: "`ops` must contain at least one op object" } };
1929
+ }
1930
+ const built = buildChecked(family, checkedOps);
1931
+ if (!built.ok) {
1932
+ const { opIndex, ...error } = built.error;
1933
+ return { ok: false, family, opIndex: opIndex >= 0 ? opIndex : void 0, error };
1934
+ }
1935
+ return verifiedEnvelope(built.value);
1936
+ } else {
1937
+ return { ok: false, family: null, error: { code: "INVALID_OP", message: "applyOps requires either `source` (to edit) or `family` (to build)" } };
1938
+ }
1939
+ for (let i = 0; i < checkedOps.length; i++) {
1940
+ const r = mutateChecked(d, checkedOps[i]);
1941
+ if (!r.ok) return { ok: false, family: d.kind, opIndex: i, error: r.error };
1942
+ d = r.value;
1943
+ }
1944
+ return verifiedEnvelope(d);
1945
+ }
1946
+ function verifiedEnvelope(diagram) {
1947
+ const verification = verifyMermaid(diagram);
1948
+ const summary = verifySummary(verification);
1949
+ if (!verification.ok) {
1950
+ return {
1951
+ ok: false,
1952
+ family: diagram.kind,
1953
+ error: {
1954
+ code: "VERIFY_FAILED",
1955
+ message: "mutated diagram failed verify; source was not emitted",
1956
+ details: summary.warnings
1957
+ }
1958
+ };
1959
+ }
1960
+ return {
1961
+ ok: true,
1962
+ family: diagram.kind,
1963
+ source: serializeMermaid(diagram),
1964
+ verify: summary
1965
+ };
1966
+ }
1967
+
1968
+ // src/agent/quality.ts
1969
+ var DEFAULT_BOUNDS = {
1970
+ maxCrossingsRatio: 0.05,
1971
+ minLabelLegibility: 0.85,
1972
+ whitespaceBand: [0.05, 0.55],
1973
+ minLabelEdgeProximity: 4,
1974
+ minNodeSpacing: 8,
1975
+ elementDensityBand: [0.02, 6],
1976
+ minTextContrast: 4.5,
1977
+ aspectBand: [0.2, 5]
1978
+ };
1979
+ var BOUND_PROVENANCE = {
1980
+ edgeCrossings: {
1981
+ basis: "evidence",
1982
+ severity: "primary",
1983
+ note: "Crossings are the single aesthetic with the strongest human-subject evidence (Purchase 1997/2002). Weight this violation highest."
1984
+ },
1985
+ labelLegibility: {
1986
+ basis: "derived",
1987
+ severity: "secondary",
1988
+ note: "Labels that exceed node width are mechanically unreadable; the 7px/char model under-estimates fit under condensed fonts."
1989
+ },
1990
+ labelEdgeProximity: {
1991
+ basis: "chosen",
1992
+ severity: "secondary",
1993
+ note: "Edge-labels overlapping nodes hurt readability, but the 4px floor is chosen, not validated against perception."
1994
+ },
1995
+ minimumNodeSpacing: {
1996
+ basis: "chosen",
1997
+ severity: "secondary",
1998
+ note: "Box clearance catches cramped layouts, but the 8px floor is a conservative implementation threshold rather than a perceptual constant."
1999
+ },
2000
+ elementDensity: {
2001
+ basis: "chosen",
2002
+ severity: "secondary",
2003
+ note: "Elements per canvas area distinguishes crowded marks from large boxes; the default band is a regression guard, not a universal aesthetic optimum."
2004
+ },
2005
+ minimumTextContrast: {
2006
+ basis: "standard",
2007
+ severity: "secondary",
2008
+ note: "The 4.5:1 floor follows WCAG AA normal-text contrast. Callers should supply resolved surface colors when a style uses non-default fills."
2009
+ },
2010
+ whitespaceBalance: {
2011
+ basis: "chosen",
2012
+ severity: "secondary",
2013
+ note: 'A node/canvas fill band is a rough proxy for "too sparse / too dense"; the literature gives weak support and the band is hand-set.'
2014
+ },
2015
+ aspectRatio: {
2016
+ basis: "sanity",
2017
+ severity: "sanity",
2018
+ note: "Aspect ratio is a guardrail against absurd canvases, not an aesthetic; Purchase did not find it drives comprehension."
2019
+ }
2020
+ };
2021
+ var CHAR_PX = 7;
2022
+ var EDGE_LABEL_BOX_PADDING = 8;
2023
+ var GEOM_EPS = 1e-9;
2024
+ function measureQuality(layout, options = {}) {
2025
+ return {
2026
+ edgeCrossings: countEdgeCrossings(layout.edges),
2027
+ labelLegibility: measureLabelLegibility(layout.nodes),
2028
+ whitespaceBalance: measureWhitespaceBalance(layout),
2029
+ labelEdgeProximity: measureLabelEdgeProximity(layout),
2030
+ minimumNodeSpacing: measureMinimumNodeSpacing(layout),
2031
+ elementDensity: measureElementDensity(layout),
2032
+ minimumTextContrast: measureMinimumTextContrast(options),
2033
+ aspectRatio: layout.bounds.h > 0 ? layout.bounds.w / layout.bounds.h : Infinity,
2034
+ nodeCount: layout.nodes.length,
2035
+ edgeCount: layout.edges.length
2036
+ };
2037
+ }
2038
+ function checkQuality(layout, bounds = {}, measurement = {}) {
2039
+ const b = { ...DEFAULT_BOUNDS, ...bounds };
2040
+ const m = measureQuality(layout, measurement);
2041
+ const ranked = [];
2042
+ const flag = (metric, message) => ranked.push({ metric, severity: BOUND_PROVENANCE[metric].severity, message });
2043
+ const pairs = Math.max(1, m.edgeCount * (m.edgeCount - 1) / 2);
2044
+ if (m.edgeCrossings / pairs > b.maxCrossingsRatio) {
2045
+ flag("edgeCrossings", `edge crossings ${m.edgeCrossings}/${pairs} (${(m.edgeCrossings / pairs * 100).toFixed(1)}%) > cap ${(b.maxCrossingsRatio * 100).toFixed(1)}%`);
2046
+ }
2047
+ if (m.labelLegibility < b.minLabelLegibility) {
2048
+ flag("labelLegibility", `label legibility ${(m.labelLegibility * 100).toFixed(0)}% < min ${(b.minLabelLegibility * 100).toFixed(0)}%`);
2049
+ }
2050
+ if (m.whitespaceBalance < b.whitespaceBand[0] || m.whitespaceBalance > b.whitespaceBand[1]) {
2051
+ flag("whitespaceBalance", `whitespace balance ${(m.whitespaceBalance * 100).toFixed(1)}% outside band [${b.whitespaceBand[0] * 100}\u2013${b.whitespaceBand[1] * 100}]%`);
2052
+ }
2053
+ if (m.labelEdgeProximity < b.minLabelEdgeProximity) {
2054
+ flag("labelEdgeProximity", `edge-label clearance ${m.labelEdgeProximity}px < min ${b.minLabelEdgeProximity}px`);
2055
+ }
2056
+ if (m.nodeCount >= 2 && m.minimumNodeSpacing < b.minNodeSpacing) {
2057
+ flag("minimumNodeSpacing", `minimum node spacing ${m.minimumNodeSpacing}px < min ${b.minNodeSpacing}px`);
2058
+ }
2059
+ if (m.nodeCount > 0 && (m.elementDensity < b.elementDensityBand[0] || m.elementDensity > b.elementDensityBand[1])) {
2060
+ flag("elementDensity", `element density ${m.elementDensity.toFixed(2)} outside band [${b.elementDensityBand[0]}\u2013${b.elementDensityBand[1]}] per 10,000px\xB2`);
2061
+ }
2062
+ if (m.minimumTextContrast === null) {
2063
+ flag("minimumTextContrast", "minimum text contrast is unmeasurable because one or more requested paints could not be resolved");
2064
+ } else if (m.minimumTextContrast < b.minTextContrast) {
2065
+ flag("minimumTextContrast", `minimum text contrast ${m.minimumTextContrast.toFixed(2)}:1 < min ${b.minTextContrast}:1`);
2066
+ }
2067
+ if (m.aspectRatio < b.aspectBand[0] || m.aspectRatio > b.aspectBand[1]) {
2068
+ flag("aspectRatio", `aspect ratio ${m.aspectRatio.toFixed(2)} outside band [${b.aspectBand[0]}\u2013${b.aspectBand[1]}]`);
2069
+ }
2070
+ const order = { primary: 0, secondary: 1, sanity: 2 };
2071
+ ranked.sort((x, y) => order[x.severity] - order[y.severity]);
2072
+ return { ok: ranked.length === 0, metrics: m, ranked };
2073
+ }
2074
+ function unionBoxes(items) {
2075
+ let minX = Infinity;
2076
+ let minY = Infinity;
2077
+ let maxX = -Infinity;
2078
+ let maxY = -Infinity;
2079
+ for (const item of items) {
2080
+ minX = Math.min(minX, item.box.x);
2081
+ minY = Math.min(minY, item.box.y);
2082
+ maxX = Math.max(maxX, item.box.x + item.box.w);
2083
+ maxY = Math.max(maxY, item.box.y + item.box.h);
2084
+ }
2085
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
2086
+ }
2087
+ function buildSpacingTree(items) {
2088
+ const box = unionBoxes(items);
2089
+ if (items.length <= 8) return { box, items };
2090
+ const axis = box.w >= box.h ? "x" : "y";
2091
+ const sorted = [...items].sort((a, b) => (axis === "x" ? a.box.x + a.box.w / 2 - b.box.x - b.box.w / 2 : a.box.y + a.box.h / 2 - b.box.y - b.box.h / 2) || compareCodePointStrings(a.node.id, b.node.id));
2092
+ const middle = Math.floor(sorted.length / 2);
2093
+ return { box, left: buildSpacingTree(sorted.slice(0, middle)), right: buildSpacingTree(sorted.slice(middle)) };
2094
+ }
2095
+ function xyMarkIdentity(node) {
2096
+ const match = node.id.match(/^(bar|line)#(\d+):pt#(\d+)$/);
2097
+ if (!match) return void 0;
2098
+ return { type: match[1], series: Number(match[2]), point: Number(match[3]) };
2099
+ }
2100
+ function radarMarkIdentity(node) {
2101
+ const match = node.id.match(/^dot:(.+):(\d+)$/);
2102
+ if (!match) return void 0;
2103
+ return { curve: match[1], vertex: Number(match[2]) };
2104
+ }
2105
+ function spacingComparable(kind, a, b) {
2106
+ if (kind === "xychart") {
2107
+ const left = xyMarkIdentity(a);
2108
+ const right = xyMarkIdentity(b);
2109
+ if (!left || !right) return true;
2110
+ if (left.type !== right.type) return left.point !== right.point;
2111
+ return left.series === right.series || left.point !== right.point;
2112
+ }
2113
+ if (kind === "radar") {
2114
+ const left = radarMarkIdentity(a);
2115
+ const right = radarMarkIdentity(b);
2116
+ if (!left || !right) return true;
2117
+ const coincident = a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
2118
+ return left.curve === right.curve || left.vertex !== right.vertex || !coincident;
2119
+ }
2120
+ return true;
2121
+ }
2122
+ function nearestAcross(a, b, kind, current) {
2123
+ if (rectDistance(a.box, b.box) >= current) return current;
2124
+ if (a.items && b.items) {
2125
+ for (const left of a.items) for (const right of b.items) {
2126
+ if (!spacingComparable(kind, left.node, right.node)) continue;
2127
+ current = Math.min(current, rectDistance(left.box, right.box));
2128
+ if (current === 0) return 0;
2129
+ }
2130
+ return current;
2131
+ }
2132
+ const splitA = !b.items && a.items ? false : !a.items && (b.items || a.box.w * a.box.h >= b.box.w * b.box.h);
2133
+ if (splitA) {
2134
+ current = nearestAcross(a.left, b, kind, current);
2135
+ return nearestAcross(a.right, b, kind, current);
2136
+ }
2137
+ current = nearestAcross(a, b.left, kind, current);
2138
+ return nearestAcross(a, b.right, kind, current);
2139
+ }
2140
+ function nearestWithin(tree, kind, current = Infinity) {
2141
+ if (tree.items) {
2142
+ for (let i = 0; i < tree.items.length; i++) for (let j = i + 1; j < tree.items.length; j++) {
2143
+ const a = tree.items[i], b = tree.items[j];
2144
+ if (!spacingComparable(kind, a.node, b.node)) continue;
2145
+ current = Math.min(current, rectDistance(a.box, b.box));
2146
+ if (current === 0) return 0;
2147
+ }
2148
+ return current;
2149
+ }
2150
+ current = nearestWithin(tree.left, kind, current);
2151
+ if (current === 0) return 0;
2152
+ current = nearestWithin(tree.right, kind, current);
2153
+ if (current === 0) return 0;
2154
+ return nearestAcross(tree.left, tree.right, kind, current);
2155
+ }
2156
+ function measureMinimumNodeSpacing(layout) {
2157
+ if (layout.nodes.length < 2) return Infinity;
2158
+ const items = layout.nodes.map((node) => ({ node, box: nodeBox(node) }));
2159
+ return nearestWithin(buildSpacingTree(items), layout.kind);
2160
+ }
2161
+ function measureElementDensity(layout) {
2162
+ const area = layout.bounds.w * layout.bounds.h;
2163
+ if (area <= 0) return Infinity;
2164
+ return (layout.nodes.length + layout.edges.length) / (area / 1e4);
2165
+ }
2166
+ function cssRgba(value) {
2167
+ return tryParseCssColor(value) ?? void 0;
2168
+ }
2169
+ function relativeLuminance(rgb) {
2170
+ const channels = rgb.map((channel) => {
2171
+ const value = channel / 255;
2172
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
2173
+ });
2174
+ return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
2175
+ }
2176
+ function contrastRatio(a, b) {
2177
+ const [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x);
2178
+ return (lighter + 0.05) / (darker + 0.05);
2179
+ }
2180
+ function composite(foreground, background) {
2181
+ const alpha = foreground[3];
2182
+ return [0, 1, 2].map((index) => foreground[index] * alpha + background[index] * (1 - alpha));
2183
+ }
2184
+ function opaqueBackground(value) {
2185
+ const parsed = cssRgba(value);
2186
+ return parsed && parsed[3] === 1 ? [parsed[0], parsed[1], parsed[2]] : void 0;
2187
+ }
2188
+ function pairContrast(foregroundValue, backgroundValue, canvasValue) {
2189
+ const foreground = cssRgba(foregroundValue);
2190
+ const surface = cssRgba(backgroundValue);
2191
+ const canvas = opaqueBackground(canvasValue);
2192
+ if (!foreground || !surface || !canvas) return void 0;
2193
+ const background = composite(surface, canvas);
2194
+ return contrastRatio(composite(foreground, background), background);
2195
+ }
2196
+ function measureMinimumTextContrast(options) {
2197
+ if (options.textPairs !== void 0) {
2198
+ if (options.textPairs.length === 0) return null;
2199
+ const canvas = options.background ?? "#FFFFFF";
2200
+ let minimum2 = Infinity;
2201
+ for (const pair of options.textPairs) {
2202
+ const ratio = pairContrast(pair.foreground, pair.background, canvas);
2203
+ if (ratio === void 0) return null;
2204
+ minimum2 = Math.min(minimum2, ratio);
2205
+ }
2206
+ return minimum2;
2207
+ }
2208
+ const foreground = cssRgba(options.foreground ?? "#27272A");
2209
+ const background = opaqueBackground(options.background ?? "#FFFFFF");
2210
+ if (!foreground || !background) return null;
2211
+ const requestedSurfaces = options.surfaces ?? [];
2212
+ const parsedSurfaces = requestedSurfaces.map(cssRgba);
2213
+ if (parsedSurfaces.some((surface) => surface === void 0)) return null;
2214
+ let minimum = contrastRatio(composite(foreground, background), background);
2215
+ for (const surface of parsedSurfaces) {
2216
+ const resolved = composite(surface, background);
2217
+ minimum = Math.min(minimum, contrastRatio(composite(foreground, resolved), resolved));
2218
+ }
2219
+ return minimum;
2220
+ }
2221
+ function countEdgeCrossings(edges) {
2222
+ let count = 0;
2223
+ for (let i = 0; i < edges.length; i++) {
2224
+ for (let j = i + 1; j < edges.length; j++) {
2225
+ const a = edges[i], b = edges[j];
2226
+ const shared = a.from === b.from || a.from === b.to || a.to === b.from || a.to === b.to;
2227
+ const ap = a.path, bp = b.path;
2228
+ for (let ai = 0; ai < ap.length - 1; ai++) {
2229
+ for (let bi = 0; bi < bp.length - 1; bi++) {
2230
+ if (shared && (ai === 0 || ai === ap.length - 2) && (bi === 0 || bi === bp.length - 2)) continue;
2231
+ if (segmentsCross(ap[ai], ap[ai + 1], bp[bi], bp[bi + 1])) count++;
2232
+ }
2233
+ }
2234
+ }
2235
+ }
2236
+ return count;
2237
+ }
2238
+ function segmentsCross(p1, p2, p3, p4) {
2239
+ const d = (a, b, c) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
2240
+ const d1 = d(p3, p4, p1), d2 = d(p3, p4, p2);
2241
+ const d3 = d(p1, p2, p3), d4 = d(p1, p2, p4);
2242
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
2243
+ }
2244
+ function measureLabelLegibility(nodes) {
2245
+ if (nodes.length === 0) return 1;
2246
+ let fits = 0;
2247
+ for (const n of nodes) {
2248
+ if (!n.label) {
2249
+ fits++;
2250
+ continue;
2251
+ }
2252
+ const renderedWidth = n.label.length * CHAR_PX;
2253
+ if (renderedWidth <= n.w) fits++;
2254
+ }
2255
+ return fits / nodes.length;
2256
+ }
2257
+ function measureWhitespaceBalance(layout) {
2258
+ const total = layout.bounds.w * layout.bounds.h;
2259
+ if (total <= 0) return 0;
2260
+ let filled = 0;
2261
+ for (const n of layout.nodes) filled += n.w * n.h;
2262
+ return Math.min(1, filled / total);
2263
+ }
2264
+ function nodeBox(n) {
2265
+ return { x: n.x, y: n.y, w: n.w, h: n.h };
2266
+ }
2267
+ function edgeLabelBox(label) {
2268
+ const metrics = measureMultilineText(label.text, FONT_SIZES.edgeLabel, FONT_WEIGHTS.edgeLabel);
2269
+ const w = metrics.width + EDGE_LABEL_BOX_PADDING * 2;
2270
+ const h = metrics.height + EDGE_LABEL_BOX_PADDING * 2;
2271
+ return { x: label.x - w / 2, y: label.y - h / 2, w, h };
2272
+ }
2273
+ function rectDistance(a, b) {
2274
+ const dx = Math.max(0, Math.max(a.x - (b.x + b.w), b.x - (a.x + a.w)));
2275
+ const dy = Math.max(0, Math.max(a.y - (b.y + b.h), b.y - (a.y + a.h)));
2276
+ return Math.hypot(dx, dy);
2277
+ }
2278
+ function pointInRect(p, r) {
2279
+ return p[0] >= r.x && p[0] <= r.x + r.w && p[1] >= r.y && p[1] <= r.y + r.h;
2280
+ }
2281
+ function pointToRectDistance(p, r) {
2282
+ const dx = Math.max(0, Math.max(r.x - p[0], p[0] - (r.x + r.w)));
2283
+ const dy = Math.max(0, Math.max(r.y - p[1], p[1] - (r.y + r.h)));
2284
+ return Math.hypot(dx, dy);
2285
+ }
2286
+ function pointToSegmentDistance(p, a, b) {
2287
+ const dx = b[0] - a[0];
2288
+ const dy = b[1] - a[1];
2289
+ const lenSq = dx * dx + dy * dy;
2290
+ if (lenSq === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
2291
+ const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / lenSq));
2292
+ const projX = a[0] + t * dx;
2293
+ const projY = a[1] + t * dy;
2294
+ return Math.hypot(p[0] - projX, p[1] - projY);
2295
+ }
2296
+ function segmentsIntersectInclusive(a, b, c, d) {
2297
+ const orient = (p, q, r) => (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]);
2298
+ const onSegment = (p, q, r) => q[0] >= Math.min(p[0], r[0]) - GEOM_EPS && q[0] <= Math.max(p[0], r[0]) + GEOM_EPS && q[1] >= Math.min(p[1], r[1]) - GEOM_EPS && q[1] <= Math.max(p[1], r[1]) + GEOM_EPS;
2299
+ const o1 = orient(a, b, c);
2300
+ const o2 = orient(a, b, d);
2301
+ const o3 = orient(c, d, a);
2302
+ const o4 = orient(c, d, b);
2303
+ if (Math.abs(o1) <= GEOM_EPS && onSegment(a, c, b)) return true;
2304
+ if (Math.abs(o2) <= GEOM_EPS && onSegment(a, d, b)) return true;
2305
+ if (Math.abs(o3) <= GEOM_EPS && onSegment(c, a, d)) return true;
2306
+ if (Math.abs(o4) <= GEOM_EPS && onSegment(c, b, d)) return true;
2307
+ return o1 > 0 !== o2 > 0 && o3 > 0 !== o4 > 0;
2308
+ }
2309
+ function segmentIntersectsRect(a, b, r) {
2310
+ if (pointInRect(a, r) || pointInRect(b, r)) return true;
2311
+ const topLeft = [r.x, r.y];
2312
+ const topRight = [r.x + r.w, r.y];
2313
+ const bottomRight = [r.x + r.w, r.y + r.h];
2314
+ const bottomLeft = [r.x, r.y + r.h];
2315
+ return segmentsIntersectInclusive(a, b, topLeft, topRight) || segmentsIntersectInclusive(a, b, topRight, bottomRight) || segmentsIntersectInclusive(a, b, bottomRight, bottomLeft) || segmentsIntersectInclusive(a, b, bottomLeft, topLeft);
2316
+ }
2317
+ function rectToSegmentDistance(r, a, b) {
2318
+ if (segmentIntersectsRect(a, b, r)) return 0;
2319
+ const corners = [
2320
+ [r.x, r.y],
2321
+ [r.x + r.w, r.y],
2322
+ [r.x + r.w, r.y + r.h],
2323
+ [r.x, r.y + r.h]
2324
+ ];
2325
+ return Math.min(
2326
+ pointToRectDistance(a, r),
2327
+ pointToRectDistance(b, r),
2328
+ ...corners.map((c) => pointToSegmentDistance(c, a, b))
2329
+ );
2330
+ }
2331
+ function measureLabelEdgeProximity(layout) {
2332
+ let min = Infinity;
2333
+ const labels = layout.edges.filter((e) => e.label !== void 0).map((e) => ({ edge: e, box: edgeLabelBox(e.label) }));
2334
+ for (const { edge, box } of labels) {
2335
+ for (const n of layout.nodes) {
2336
+ if (n.id === edge.from || n.id === edge.to) continue;
2337
+ const d = rectDistance(box, nodeBox(n));
2338
+ if (d < min) min = d;
2339
+ }
2340
+ }
2341
+ for (let i = 0; i < labels.length; i++) {
2342
+ for (let j = i + 1; j < labels.length; j++) {
2343
+ const d = rectDistance(labels[i].box, labels[j].box);
2344
+ if (d < min) min = d;
2345
+ }
2346
+ }
2347
+ for (const { edge, box } of labels) {
2348
+ for (const other of layout.edges) {
2349
+ if (other === edge) continue;
2350
+ for (let i = 0; i < other.path.length - 1; i++) {
2351
+ const d = rectToSegmentDistance(box, other.path[i], other.path[i + 1]);
2352
+ if (d < min) min = d;
2353
+ }
2354
+ }
2355
+ }
2356
+ return min;
2357
+ }
2358
+
2359
+ // src/agent/certificates.ts
2360
+ function layoutCertificateProof(cert) {
2361
+ if ("elementId" in cert) return regionContainmentProof(cert);
2362
+ return edgeRouteProof(cert);
2363
+ }
2364
+ function edgeRouteProof(cert) {
2365
+ if ("family" in cert) return familyEdgeRouteProof(cert);
2366
+ return flowchartEdgeRouteProof(cert);
2367
+ }
2368
+ function flowchartEdgeRouteProof(cert) {
2369
+ switch (cert.invariant) {
2370
+ case "straight":
2371
+ case "explained-detour":
2372
+ case "bundle":
2373
+ case "outer-feedback":
2374
+ case "feedback-detour":
2375
+ case "self-loop":
2376
+ case "container-attach":
2377
+ case "unverified-shape":
2378
+ return "edge-route";
2379
+ default:
2380
+ return assertNever(cert);
2381
+ }
2382
+ }
2383
+ function familyEdgeRouteProof(cert) {
2384
+ switch (cert.family) {
2385
+ case "class":
2386
+ case "er":
2387
+ return familyBoxRouteProof(cert.invariant);
2388
+ case "architecture":
2389
+ return architectureRouteProof(cert.invariant);
2390
+ case "sequence":
2391
+ return sequenceRouteProof(cert.invariant);
2392
+ default:
2393
+ return assertNever(cert);
2394
+ }
2395
+ }
2396
+ function familyBoxRouteProof(invariant) {
2397
+ switch (invariant) {
2398
+ case "orthogonal-box":
2399
+ case "unverified-family-route":
2400
+ return "edge-route";
2401
+ default:
2402
+ return assertNever(invariant);
2403
+ }
2404
+ }
2405
+ function architectureRouteProof(invariant) {
2406
+ switch (invariant) {
2407
+ case "side-anchored":
2408
+ case "unverified-family-route":
2409
+ return "edge-route";
2410
+ default:
2411
+ return assertNever(invariant);
2412
+ }
2413
+ }
2414
+ function sequenceRouteProof(invariant) {
2415
+ switch (invariant) {
2416
+ case "lifeline-message":
2417
+ case "self-message":
2418
+ case "unverified-family-route":
2419
+ return "edge-route";
2420
+ default:
2421
+ return assertNever(invariant);
2422
+ }
2423
+ }
2424
+ function regionContainmentProof(cert) {
2425
+ switch (cert.family) {
2426
+ case "timeline":
2427
+ case "xychart":
2428
+ case "pie":
2429
+ case "quadrant":
2430
+ case "gantt":
2431
+ return regionInvariantProof(cert.invariant);
2432
+ default:
2433
+ return assertNever(cert.family);
2434
+ }
2435
+ }
2436
+ function regionInvariantProof(invariant) {
2437
+ switch (invariant) {
2438
+ case "timeline-interval":
2439
+ case "plot-contained":
2440
+ case "legend-contained":
2441
+ case "section-contained":
2442
+ case "unverified-family-layout":
2443
+ return "region-containment";
2444
+ default:
2445
+ return assertNever(invariant);
2446
+ }
2447
+ }
2448
+ function assertNever(value) {
2449
+ throw new Error(`Unhandled layout certificate variant: ${JSON.stringify(value)}`);
2450
+ }
2451
+
2452
+ // src/agent/family-registration.ts
2453
+ var WITNESS_RENDER_OPTIONS = Object.freeze({
2454
+ security: "strict",
2455
+ embedFontImport: false
2456
+ });
2457
+ function reasonOf(error) {
2458
+ return error instanceof Error ? error.message : String(error);
2459
+ }
2460
+ function parsedExample(descriptor) {
2461
+ const parsed = parseRegisteredMermaid(descriptor.example);
2462
+ if (!parsed.ok) {
2463
+ throw new Error(parsed.error.map((error) => `${error.code}: ${error.message}`).join("; "));
2464
+ }
2465
+ if (parsed.value.kind !== descriptor.id) {
2466
+ throw new Error(`canonical example routed to "${parsed.value.kind}" instead of "${descriptor.id}"`);
2467
+ }
2468
+ return parsed.value;
2469
+ }
2470
+ function parsedWitness(descriptor) {
2471
+ const parsed = parsedExample(descriptor);
2472
+ return {
2473
+ kind: parsed.kind,
2474
+ ..."descriptorIdentity" in parsed ? { descriptorIdentity: parsed.descriptorIdentity } : {},
2475
+ meta: parsed.meta,
2476
+ body: parsed.body,
2477
+ canonicalSource: parsed.canonicalSource
2478
+ };
2479
+ }
2480
+ function serializationWitness(descriptor) {
2481
+ const serialized = serializeMermaid(parsedExample(descriptor));
2482
+ const reparsed = parseRegisteredMermaid(serialized);
2483
+ if (!reparsed.ok) {
2484
+ throw new Error(`serialized canonical example did not reparse: ${reparsed.error.map((error) => error.message).join("; ")}`);
2485
+ }
2486
+ if (reparsed.value.kind !== descriptor.id) {
2487
+ throw new Error(`serializer changed family identity to "${reparsed.value.kind}"`);
2488
+ }
2489
+ const serializedAgain = serializeMermaid(reparsed.value);
2490
+ if (serializedAgain !== serialized) {
2491
+ throw new Error("serializer is not byte-stable after parse \u2192 serialize \u2192 reparse");
2492
+ }
2493
+ return { serialized, reparsedKind: reparsed.value.kind, canonicalSource: reparsed.value.canonicalSource };
2494
+ }
2495
+ function layoutWitness(descriptor) {
2496
+ const parsed = parsedExample(descriptor);
2497
+ const artifact = positionFamilyArtifact(parsed, {
2498
+ output: "layout",
2499
+ renderOptions: WITNESS_RENDER_OPTIONS
2500
+ });
2501
+ if (!artifact) throw new Error(`canonical example has no public positioned-layout projection`);
2502
+ const positionedBounds = [artifact.positioned.width, artifact.positioned.height];
2503
+ const projectedBounds = [artifact.rendered.bounds.w, artifact.rendered.bounds.h];
2504
+ if ([...positionedBounds, ...projectedBounds].some((value) => !Number.isFinite(value) || value <= 0)) {
2505
+ throw new Error("canonical example layout and projected bounds must be finite and positive");
2506
+ }
2507
+ if (artifact.rendered.nodes.length + artifact.rendered.edges.length + artifact.rendered.groups.length === 0) {
2508
+ throw new Error("canonical example layout must expose at least one semantic node, edge, or group");
2509
+ }
2510
+ return {
2511
+ layout: artifact.rendered,
2512
+ receiptDigest: renderContractDigest(receiptOf(artifact.request))
2513
+ };
2514
+ }
2515
+ function visitScene(nodes, callback) {
2516
+ for (const node of nodes) {
2517
+ callback(node);
2518
+ if (node.kind === "group") visitScene(node.children.map((child) => child.node), callback);
2519
+ }
2520
+ }
2521
+ function sceneWitness(descriptor) {
2522
+ const request = resolveRenderRequestForExecution(
2523
+ descriptor.example,
2524
+ WITNESS_RENDER_OPTIONS,
2525
+ "svg"
2526
+ );
2527
+ const layout = positionResolvedFamily(descriptor.id, request);
2528
+ const scene = lowerPositionedFamilyScene(request, layout);
2529
+ const observed = /* @__PURE__ */ new Set();
2530
+ visitScene(scene.parts, (node) => {
2531
+ for (const primitive of sceneNodePrimitives(node)) observed.add(`${node.role}\0${primitive}`);
2532
+ });
2533
+ const missing = descriptor.scenePrimitiveEvidence.filter((cell) => cell.applicability === "applicable").filter((cell) => !observed.has(`${cell.role}\0${cell.primitive}`)).map((cell) => `${cell.role}/${cell.primitive}`);
2534
+ if (missing.length > 0) {
2535
+ throw new Error(`canonical example did not witness declared positive Scene cells: ${missing.join(", ")}`);
2536
+ }
2537
+ return {
2538
+ sceneDigest: renderContractDigest(scene),
2539
+ cells: [...observed].sort(),
2540
+ receiptDigest: renderContractDigest(receiptOf(request))
2541
+ };
2542
+ }
2543
+ function svgWitness(descriptor) {
2544
+ const svg = executeGraphicalRequest(
2545
+ descriptor.example,
2546
+ WITNESS_RENDER_OPTIONS,
2547
+ "svg"
2548
+ );
2549
+ const png = renderPortablePngGraphicalProjection(
2550
+ descriptor.example,
2551
+ WITNESS_RENDER_OPTIONS
2552
+ );
2553
+ return {
2554
+ svg: svg.svg,
2555
+ svgReceiptDigest: renderContractDigest(svg.receipt),
2556
+ pngSvg: png.svg,
2557
+ pngReceiptDigest: renderContractDigest(png.receipt),
2558
+ rasterBackground: png.rasterBackground,
2559
+ rasterDimensions: png.rasterDimensions
2560
+ };
2561
+ }
2562
+ function terminalWitness(descriptor) {
2563
+ const outputs = [];
2564
+ for (const useAscii of [true, false]) {
2565
+ for (const colorMode of RESOLVED_TERMINAL_COLOR_MODES) {
2566
+ const encoding = useAscii ? "ascii" : "unicode";
2567
+ let rendered;
2568
+ try {
2569
+ rendered = renderMermaidASCIIWithReceipt(descriptor.example, {
2570
+ ...WITNESS_RENDER_OPTIONS,
2571
+ useAscii,
2572
+ colorMode
2573
+ });
2574
+ } catch (error) {
2575
+ throw new Error(`${encoding}/${colorMode} terminal witness failed: ${reasonOf(error)}`);
2576
+ }
2577
+ outputs.push({
2578
+ encoding,
2579
+ colorMode,
2580
+ textDigest: renderContractDigest(rendered.text),
2581
+ receiptDigest: renderContractDigest(rendered.receipt)
2582
+ });
2583
+ }
2584
+ }
2585
+ return { outputs };
2586
+ }
2587
+ function verifyWitness(descriptor) {
2588
+ const verified = verifyMermaid(descriptor.example, { renderOptions: WITNESS_RENDER_OPTIONS });
2589
+ if (!verified.ok) {
2590
+ const diagnostics = verified.warnings.map((warning2) => warning2.code === "RENDER_FAILED" ? `${warning2.code}: ${warning2.reason}` : warning2.code);
2591
+ throw new Error(`canonical example failed verification: ${diagnostics.join(", ")}`);
2592
+ }
2593
+ return verified;
2594
+ }
2595
+ function capabilityWitness(descriptor, capability) {
2596
+ switch (capability) {
2597
+ case "detection": {
2598
+ const parsed = parsedExample(descriptor);
2599
+ return { family: parsed.kind, canonicalSource: parsed.canonicalSource };
2600
+ }
2601
+ case "source-preservation": {
2602
+ const parsed = parsedExample(descriptor);
2603
+ return {
2604
+ family: parsed.kind,
2605
+ canonicalSource: parsed.canonicalSource,
2606
+ roundtrip: serializationWitness(descriptor)
2607
+ };
2608
+ }
2609
+ case "parse":
2610
+ return parsedWitness(descriptor);
2611
+ case "serialize":
2612
+ return serializationWitness(descriptor);
2613
+ case "mutation":
2614
+ throw new Error("external structured mutation is not an executable native capability");
2615
+ case "verify":
2616
+ return verifyWitness(descriptor);
2617
+ case "layout":
2618
+ return layoutWitness(descriptor);
2619
+ case "scene":
2620
+ return sceneWitness(descriptor);
2621
+ case "svg":
2622
+ return svgWitness(descriptor);
2623
+ case "terminal":
2624
+ return terminalWitness(descriptor);
2625
+ }
2626
+ }
2627
+ function runCapabilityConformance(descriptor, capability) {
2628
+ const declaredState = descriptor.capabilityEvidence.find((claim) => claim.capability === capability).state;
2629
+ if (declaredState !== "native") {
2630
+ return Object.freeze({
2631
+ capability,
2632
+ declaredState,
2633
+ status: "unverified-extension",
2634
+ diagnostic: `Descriptor declares "${declaredState}"; no executable native claim was made.`
2635
+ });
2636
+ }
2637
+ const witnessId = `family-example@${FAMILY_CONFORMANCE_VERSION}/${descriptor.id}/${capability}`;
2638
+ try {
2639
+ const first = renderContractDigest(capabilityWitness(descriptor, capability));
2640
+ const second = renderContractDigest(capabilityWitness(descriptor, capability));
2641
+ if (first !== second) {
2642
+ throw new Error(`canonical example was nondeterministic (${first} != ${second})`);
2643
+ }
2644
+ return Object.freeze({ capability, declaredState, status: "passed", witnessId });
2645
+ } catch (error) {
2646
+ return Object.freeze({
2647
+ capability,
2648
+ declaredState,
2649
+ status: "failed",
2650
+ diagnostic: reasonOf(error)
2651
+ });
2652
+ }
2653
+ }
2654
+ function runFamilyConformance(descriptor) {
2655
+ const capabilities = Object.freeze(FAMILY_CAPABILITY_COLUMNS.map((capability) => runCapabilityConformance(descriptor, capability)));
2656
+ return Object.freeze({
2657
+ version: FAMILY_CONFORMANCE_VERSION,
2658
+ familyId: descriptor.id,
2659
+ example: descriptor.example,
2660
+ passed: capabilities.every((result) => result.declaredState !== "native" || result.status === "passed"),
2661
+ capabilities
2662
+ });
2663
+ }
2664
+ var FamilyConformanceError = class extends Error {
2665
+ report;
2666
+ constructor(report) {
2667
+ const failures = report.capabilities.filter((result) => result.status === "failed").map((result) => `${result.capability}: ${result.diagnostic}`);
2668
+ super(`Family "${report.familyId}" failed executable registration conformance: ${failures.join("; ")}`);
2669
+ this.name = "FamilyConformanceError";
2670
+ this.report = report;
2671
+ }
2672
+ };
2673
+ function registerFamily(descriptor) {
2674
+ const staged = stageFamilyCandidateForConformance(
2675
+ descriptor,
2676
+ assertFamilyScopedRenderOptionDeclaration
2677
+ );
2678
+ try {
2679
+ const report = runFamilyConformance(staged.descriptor);
2680
+ if (!report.passed) throw new FamilyConformanceError(report);
2681
+ return staged.commit(report);
2682
+ } catch (error) {
2683
+ staged.rollback();
2684
+ throw error;
2685
+ }
2686
+ }
2687
+
2688
+ // src/agent/analyze.ts
2689
+ function analyzeMermaid(d) {
2690
+ return {
2691
+ kind: d.kind,
2692
+ feedbackEdges: feedbackEdges(d),
2693
+ actions: collectActionRecords(d),
2694
+ ...d.body.kind === "gantt" ? ganttAnalysis(d) : {}
2695
+ };
2696
+ }
2697
+ function analyzeMermaidSource(source) {
2698
+ const parsed = parseRegisteredMermaid(source);
2699
+ return parsed.ok ? ok(analyzeMermaid(parsed.value)) : err(parsed.error);
2700
+ }
2701
+ function feedbackEdges(d) {
2702
+ let graph = null;
2703
+ if (d.body.kind === "flowchart") graph = d.body.graph;
2704
+ else if (d.body.kind === "state") graph = stateBodyToGraph(d.body);
2705
+ else if (d.kind === "flowchart" && d.body.kind === "opaque") {
2706
+ try {
2707
+ graph = parseMermaid(d.canonicalSource);
2708
+ } catch {
2709
+ graph = null;
2710
+ }
2711
+ }
2712
+ if (!graph) return [];
2713
+ const classes = classifyRoutes(graph);
2714
+ return graph.edges.flatMap((edge, edgeIndex) => classes[edgeIndex] === "feedback" ? [{ edgeIndex, from: edge.source, to: edge.target, label: edge.label, routeClass: "feedback" }] : []);
2715
+ }
2716
+ function ganttAnalysis(d) {
2717
+ if (d.body.kind !== "gantt") return {};
2718
+ try {
2719
+ const normalized = normalizeMermaidSource(d.canonicalSource);
2720
+ const model = applyGanttFrontmatterConfig(parseGanttModel(normalized.lines), normalized.frontmatter);
2721
+ const schedule = resolveGanttSchedule(model);
2722
+ return schedule.analysis ? { gantt: {
2723
+ criticalPathTaskIds: [...schedule.analysis.criticalPathTaskIds],
2724
+ slackByTaskId: { ...schedule.analysis.slackByTaskId },
2725
+ projectStart: schedule.analysis.projectStart,
2726
+ projectEnd: schedule.analysis.projectEnd,
2727
+ entryTaskIds: [...schedule.analysis.entryTaskIds],
2728
+ sinkTaskIds: [...schedule.analysis.sinkTaskIds]
2729
+ } } : {};
2730
+ } catch {
2731
+ return {};
2732
+ }
2733
+ }
2734
+ function collectActionRecords(d) {
2735
+ const source = d.body.kind === "opaque" || d.body.kind === "extension" || d.body.kind === "preserved" ? d.body.source : d.canonicalSource;
2736
+ const records = [];
2737
+ if (d.kind === "flowchart") records.push(...collectFlowchartActions(source));
2738
+ if (d.kind === "class") records.push(...collectClassActions(source));
2739
+ if (d.kind === "sequence") records.push(...collectSequenceActions(source));
2740
+ if (d.body.kind === "gantt") records.push(...collectGanttActions(d));
2741
+ const seen = /* @__PURE__ */ new Map();
2742
+ return records.map((record) => {
2743
+ const key = `${record.family}:${record.target}`;
2744
+ const n = seen.get(key) ?? 0;
2745
+ seen.set(key, n + 1);
2746
+ return { id: `action:${record.family}:${record.target}:${n}`, regionId: `node:${record.target}`, ...record };
2747
+ });
2748
+ }
2749
+ function collectClassActions(source) {
2750
+ const out = [];
2751
+ let inClassBody = false;
2752
+ for (const sourceLine of actionSourceLines(source)) {
2753
+ for (const text of expandInlineNamespaceStatement(sourceLine.text)) {
2754
+ if (inClassBody) {
2755
+ if (text.trim() === "}") inClassBody = false;
2756
+ continue;
2757
+ }
2758
+ const declaration = parseClassDeclaration(text);
2759
+ if (declaration?.opensBody) {
2760
+ inClassBody = true;
2761
+ continue;
2762
+ }
2763
+ const embedded = parseClassInteraction(text);
2764
+ if (embedded) {
2765
+ out.push(actionRecord("class", embedded.id, "href", embedded.href, sourceLine.line));
2766
+ continue;
2767
+ }
2768
+ const callback = text.match(/^(callback)\s+(`[^`]+`(?:~[^~]+~)?|[\w$]+(?:~[^~]+~)?)\s+(.+)$/i);
2769
+ if (callback) {
2770
+ const ref2 = parseClassReference(callback[2]);
2771
+ if (ref2) out.push(actionRecord("class", ref2.id, "callback", callback[3], sourceLine.line));
2772
+ continue;
2773
+ }
2774
+ const match = text.match(/^(click|link)\s+(`[^`]+`(?:~[^~]+~)?|[\w$]+(?:~[^~]+~)?)\s+(.+)$/i);
2775
+ if (!match) continue;
2776
+ const ref = parseClassReference(match[2]);
2777
+ if (!ref) continue;
2778
+ const rest = match[3].trim();
2779
+ const explicit = rest.match(/^(href|call|callback)\s+(.+)$/i);
2780
+ const kind = explicit?.[1]?.toLowerCase();
2781
+ if (kind === "call" || kind === "callback") {
2782
+ out.push(actionRecord("class", ref.id, kind, explicit[2], sourceLine.line));
2783
+ } else {
2784
+ const raw = kind === "href" ? explicit[2] : rest;
2785
+ out.push(actionRecord(
2786
+ "class",
2787
+ ref.id,
2788
+ match[1].toLowerCase() === "link" || looksLikeHref(raw) ? "href" : "callback",
2789
+ raw,
2790
+ sourceLine.line
2791
+ ));
2792
+ }
2793
+ }
2794
+ }
2795
+ return out;
2796
+ }
2797
+ function collectFlowchartActions(source) {
2798
+ const out = [];
2799
+ for (const sourceLine of actionSourceLines(source, true)) {
2800
+ for (const text of splitFlowchartStatements(sourceLine.text)) {
2801
+ if (!text || text.startsWith("%%")) continue;
2802
+ let m = text.match(/^href\s+(\S+)\s+(.+)$/i);
2803
+ if (m) {
2804
+ out.push(actionRecord("flowchart", m[1], "href", m[2], sourceLine.line));
2805
+ continue;
2806
+ }
2807
+ m = text.match(/^click\s+(\S+)\s+(.+)$/i);
2808
+ if (!m) continue;
2809
+ const target = m[1];
2810
+ const rest = m[2].trim();
2811
+ const explicit = rest.match(/^(href|call)\s+(.+)$/i);
2812
+ const callback = rest.match(/^callback(?:\s+(.+))?$/i);
2813
+ if (explicit?.[1]?.toLowerCase() === "href") out.push(actionRecord("flowchart", target, "href", explicit[2], sourceLine.line));
2814
+ else if (explicit?.[1]?.toLowerCase() === "call") out.push(actionRecord("flowchart", target, "call", explicit[2], sourceLine.line));
2815
+ else if (callback) out.push(actionRecord("flowchart", target, "callback", callback[1] ?? "", sourceLine.line));
2816
+ else out.push(actionRecord("flowchart", target, looksLikeHref(rest) ? "href" : "callback", rest, sourceLine.line));
2817
+ }
2818
+ }
2819
+ return out;
2820
+ }
2821
+ function actionSourceLines(source, maskMarkdownStrings = false) {
2822
+ const out = [];
2823
+ const lines = source.split(/\r?\n/);
2824
+ let inAccessibilityBlock = false;
2825
+ let inMarkdownString = false;
2826
+ for (let index = 0; index < lines.length; index++) {
2827
+ const preserveActionText = maskMarkdownStrings && !inMarkdownString && splitFlowchartStatements(lines[index]).some((statement) => /^(?:click|href)\s+/i.test(statement.trim()));
2828
+ const masked = maskMarkdownStrings && !preserveActionText ? maskMarkdownStringContent(lines[index], inMarkdownString) : { text: lines[index], open: false };
2829
+ inMarkdownString = masked.open;
2830
+ const text = masked.text.trim();
2831
+ if (inAccessibilityBlock) {
2832
+ if (text.includes("}")) inAccessibilityBlock = false;
2833
+ continue;
2834
+ }
2835
+ const block = text.match(/^accDescr\s*:?\s*\{\s*(.*)$/i);
2836
+ if (block) {
2837
+ if (!block[1].includes("}")) inAccessibilityBlock = true;
2838
+ continue;
2839
+ }
2840
+ if (/^acc(?:Title|Descr)(?:\s*:|\s+)/i.test(text)) continue;
2841
+ out.push({ text, line: index + 1 });
2842
+ }
2843
+ return out;
2844
+ }
2845
+ function collectSequenceActions(source) {
2846
+ return actionSourceLines(source).flatMap((sourceLine) => {
2847
+ const parsed = parseActorLinks(sourceLine.text);
2848
+ return parsed ? Object.values(parsed.links).map(
2849
+ (href) => actionRecord("sequence", parsed.actorId, "href", href, sourceLine.line)
2850
+ ) : [];
2851
+ });
2852
+ }
2853
+ function maskMarkdownStringContent(line, initiallyOpen) {
2854
+ let open = initiallyOpen;
2855
+ let escaped = false;
2856
+ let text = "";
2857
+ for (const character of line) {
2858
+ if (escaped) {
2859
+ text += open ? " " : character;
2860
+ escaped = false;
2861
+ continue;
2862
+ }
2863
+ if (character === "\\") {
2864
+ text += open ? " " : character;
2865
+ escaped = true;
2866
+ continue;
2867
+ }
2868
+ if (character === "`") {
2869
+ text += " ";
2870
+ open = !open;
2871
+ continue;
2872
+ }
2873
+ text += open ? " " : character;
2874
+ }
2875
+ return { text, open };
2876
+ }
2877
+ function collectGanttActions(d) {
2878
+ if (d.body.kind !== "gantt") return [];
2879
+ try {
2880
+ const model = parseGanttModel(toMermaidLines(d.canonicalSource));
2881
+ return model.clicks.map((click) => actionRecord("gantt", click.taskId, click.action, click.rest, click.line));
2882
+ } catch {
2883
+ return [];
2884
+ }
2885
+ }
2886
+ function actionRecord(family, target, action, raw, line) {
2887
+ const href = action === "href" ? firstActionToken(raw) : void 0;
2888
+ const unsafe = href !== void 0 && !isSafeHref(href);
2889
+ return {
2890
+ family,
2891
+ target,
2892
+ action,
2893
+ raw,
2894
+ line,
2895
+ href,
2896
+ executable: false,
2897
+ security: action === "href" ? unsafe ? "unsafe" : "safe" : "source-only",
2898
+ message: action === "href" ? unsafe ? "Unsafe href is source-only and must be stripped by strict renderers." : "Href is an action record; non-SVG renderers keep it as source-only metadata." : "Callbacks/calls are recorded for analysis but are never executed."
2899
+ };
2900
+ }
2901
+ function firstActionToken(raw) {
2902
+ const trimmed = raw.trim();
2903
+ const quote = trimmed[0];
2904
+ if (quote === '"' || quote === "'") {
2905
+ let token = "";
2906
+ for (let index = 1; index < trimmed.length; index++) {
2907
+ const character = trimmed[index];
2908
+ if (character === quote) return token;
2909
+ if (character === "\\" && index + 1 < trimmed.length) {
2910
+ const next = trimmed[index + 1];
2911
+ if (next === quote || next === "\\") {
2912
+ token += next;
2913
+ index++;
2914
+ continue;
2915
+ }
2916
+ }
2917
+ token += character;
2918
+ }
2919
+ return token;
2920
+ }
2921
+ return trimmed.split(/\s+/)[0] ?? "";
2922
+ }
2923
+ function isSafeHref(href) {
2924
+ return isSafeActionHref(href);
2925
+ }
2926
+ function looksLikeHref(raw) {
2927
+ const trimmed = raw.trim();
2928
+ const token = firstActionToken(raw);
2929
+ const quoted = trimmed[0] === '"' || trimmed[0] === "'";
2930
+ return quoted || isSafeHref(token) && /^(?:https?:|mailto:|#|\/|\.\/|\.\.)/i.test(token);
2931
+ }
2932
+
2933
+ // src/ascii/meta.ts
2934
+ import { decodeXML } from "entities";
2935
+ var ASCII_ROUTE_PARITY_CONTRACT = {
2936
+ version: 1,
2937
+ routeIntent: "shared-route-classes",
2938
+ svgSource: "route-contract classifyRoutes() + certificates",
2939
+ asciiSource: "grid router seeded with classifyRoutes() routeClass metadata, longest-path layering, and direct-lane-first routing",
2940
+ degradationWarnings: ["ASCII_RENDER_FAILED", "ASCII_EDGE_REGION_UNMAPPED"]
2941
+ };
2942
+ function renderMermaidASCIIWithMeta(input, opts = {}) {
2943
+ const source = prepareRenderInput(input).source;
2944
+ try {
2945
+ const projection = [];
2946
+ const ascii = renderMermaidASCII(input, {
2947
+ ...opts,
2948
+ onProjectionDiagnostic: (diagnostic) => {
2949
+ projection.push(diagnostic);
2950
+ opts.onProjectionDiagnostic?.(diagnostic);
2951
+ }
2952
+ });
2953
+ const regions = addSemanticContainerRegions(ascii, source, deriveRegions(ascii, source));
2954
+ const projectionWarnings = projection.map((diagnostic) => ({
2955
+ code: diagnostic.code,
2956
+ severity: "degraded",
2957
+ message: diagnostic.message
2958
+ }));
2959
+ return { ascii, regions, actions: actionRecords(source), warnings: [...deriveWarnings(source, regions), ...projectionWarnings], routeParity: ASCII_ROUTE_PARITY_CONTRACT };
2960
+ } catch (error) {
2961
+ if (error instanceof ParsedDiagramFamilyMismatchError) throw error;
2962
+ return { ascii: "", regions: [], actions: [], warnings: [{ code: "ASCII_RENDER_FAILED", severity: "degraded", message: "ASCII/Unicode renderer failed; no route parity evidence is available." }], routeParity: ASCII_ROUTE_PARITY_CONTRACT };
2963
+ }
2964
+ }
2965
+ function actionRecords(source) {
2966
+ const parsed = parseRegisteredMermaid(source);
2967
+ return parsed.ok ? collectActionRecords(parsed.value).map(sanitizeTerminalAction) : [];
2968
+ }
2969
+ function sanitizeTerminalAction(action) {
2970
+ const safe = (value) => value === void 0 ? void 0 : sanitizeTerminalText(value);
2971
+ return {
2972
+ ...action,
2973
+ id: safe(action.id),
2974
+ regionId: safe(action.regionId),
2975
+ target: safe(action.target),
2976
+ raw: safe(action.raw),
2977
+ href: safe(action.href),
2978
+ message: safe(action.message)
2979
+ };
2980
+ }
2981
+ function deriveWarnings(source, regions) {
2982
+ const parsed = parseRegisteredMermaid(source);
2983
+ if (!parsed.ok) return [];
2984
+ if (parsed.value.body.kind !== "flowchart" && parsed.value.body.kind !== "state") return [];
2985
+ const edgeRegions = regions.filter((r) => r.kind === "edge").length;
2986
+ const edgeCount = parsed.value.body.kind === "flowchart" ? parsed.value.body.graph.edges.length : parsed.value.body.transitions.length;
2987
+ if (edgeCount > 0 && edgeRegions === 0) {
2988
+ return [{
2989
+ code: "ASCII_EDGE_REGION_UNMAPPED",
2990
+ severity: "degraded",
2991
+ message: "ASCII route drawing follows the explicit route-intent parity mapping, but per-edge cell spans are not instrumented yet."
2992
+ }];
2993
+ }
2994
+ return [];
2995
+ }
2996
+ function addCandidate(out, id, label, sourceLine, kind = "node") {
2997
+ const normalized = label?.trim();
2998
+ if (!normalized) return;
2999
+ out.push({ id, label: normalized, sourceLine, kind });
3000
+ }
3001
+ function addCandidateWithFallback(out, id, label, sourceLine, kind = "node") {
3002
+ addCandidate(out, id, label, sourceLine, kind);
3003
+ if (label !== id) addCandidate(out, id, id, sourceLine, kind);
3004
+ }
3005
+ function deriveRegions(ascii, source) {
3006
+ const candidates = candidatesForDiagram(source);
3007
+ if (candidates.length === 0) return [];
3008
+ const lines = ascii.split("\n");
3009
+ const sorted = [...candidates].sort((a, b) => visualWidth(b.label) - visualWidth(a.label));
3010
+ const used = /* @__PURE__ */ new Set();
3011
+ const occupied = /* @__PURE__ */ new Map();
3012
+ const out = [];
3013
+ for (const c of sorted) {
3014
+ const candidateKey = `${c.kind ?? "node"}\0${c.id}`;
3015
+ if (used.has(candidateKey)) continue;
3016
+ const match = matchProjectedLabel(lines, c.label, occupied, (c.kind ?? "node") === "node");
3017
+ if (!match) continue;
3018
+ out.push({
3019
+ kind: c.kind ?? "node",
3020
+ id: c.id,
3021
+ sourceLine: c.sourceLine,
3022
+ canvasRow: match.row,
3023
+ canvasColStart: match.colStart,
3024
+ canvasColEnd: match.colEnd,
3025
+ ...match.rowSpan > 1 ? { rowSpan: match.rowSpan } : {},
3026
+ projectedText: projectedLabelText(c.label),
3027
+ authoredTextCells: match.authoredTextCells
3028
+ });
3029
+ used.add(candidateKey);
3030
+ }
3031
+ out.sort((a, b) => a.canvasRow - b.canvasRow || a.canvasColStart - b.canvasColStart);
3032
+ return out;
3033
+ }
3034
+ function overlapsOccupied(occupied, occurrence) {
3035
+ return (occupied.get(occurrence.row) ?? []).some(([start, end]) => occurrence.colStart < end && occurrence.colEnd > start);
3036
+ }
3037
+ function occurrencesOf(lines, text, occupied) {
3038
+ const out = [];
3039
+ if (!text) return out;
3040
+ for (let row = 0; row < lines.length; row++) {
3041
+ const line = lines[row];
3042
+ for (let start = line.indexOf(text); start >= 0; start = line.indexOf(text, start + Math.max(1, text.length))) {
3043
+ const end = start + text.length;
3044
+ const occurrence = {
3045
+ row,
3046
+ start,
3047
+ end,
3048
+ colStart: visualWidth(line.slice(0, start)),
3049
+ colEnd: visualWidth(line.slice(0, end)),
3050
+ text
3051
+ };
3052
+ if (!overlapsOccupied(occupied, occurrence)) out.push(occurrence);
3053
+ }
3054
+ }
3055
+ return out;
3056
+ }
3057
+ var NODE_TEXT_BOUNDARY_GLYPHS = /* @__PURE__ */ new Set(["|", "\u2502", "\u2551", "\u251C", "\u2524", "\u253C", "\u256B", "\u256A", "+"]);
3058
+ function nodeTextBandScore(lines, occurrence) {
3059
+ const line = lines[occurrence.row] ?? "";
3060
+ const before = graphemes(line.slice(0, occurrence.start).trimEnd()).at(-1);
3061
+ const after = graphemes(line.slice(occurrence.end).trimStart())[0];
3062
+ return before !== void 0 && after !== void 0 && NODE_TEXT_BOUNDARY_GLYPHS.has(before) && NODE_TEXT_BOUNDARY_GLYPHS.has(after) ? 1 : 0;
3063
+ }
3064
+ function preferredOccurrences(lines, occurrences, preferNodeTextBand) {
3065
+ return [...occurrences].sort((a, b) => (preferNodeTextBand ? nodeTextBandScore(lines, b) - nodeTextBandScore(lines, a) : 0) || a.row - b.row || a.colStart - b.colStart);
3066
+ }
3067
+ function projectedLabelText(label) {
3068
+ return sanitizeTerminalText(decodeXML(plainTextFromInlineFormatting(normalizeBrTags(label))), true).replace(/^[`]|[`]$/g, "").trim();
3069
+ }
3070
+ function claimOccurrences(occurrences, occupied) {
3071
+ for (const occurrence of occurrences) {
3072
+ const spans = occupied.get(occurrence.row) ?? [];
3073
+ spans.push([occurrence.colStart, occurrence.colEnd]);
3074
+ occupied.set(occurrence.row, spans);
3075
+ }
3076
+ const firstRow = Math.min(...occurrences.map((occurrence) => occurrence.row));
3077
+ const lastRow = Math.max(...occurrences.map((occurrence) => occurrence.row));
3078
+ const authoredTextCells = occurrences.flatMap((occurrence) => {
3079
+ let column = occurrence.colStart;
3080
+ return graphemes(occurrence.text).map((glyph) => {
3081
+ const cell = { row: occurrence.row, column, glyph };
3082
+ column += visualWidth(glyph);
3083
+ return cell;
3084
+ });
3085
+ });
3086
+ return {
3087
+ row: firstRow,
3088
+ colStart: Math.min(...occurrences.map((occurrence) => occurrence.colStart)),
3089
+ colEnd: Math.max(...occurrences.map((occurrence) => occurrence.colEnd)),
3090
+ rowSpan: lastRow - firstRow + 1,
3091
+ authoredTextCells
3092
+ };
3093
+ }
3094
+ function matchProjectedLabel(lines, label, occupied, preferNodeTextBand) {
3095
+ const projected = projectedLabelText(label);
3096
+ for (const text of /* @__PURE__ */ new Set([label, projected])) {
3097
+ const occurrence = preferredOccurrences(lines, occurrencesOf(lines, text, occupied), preferNodeTextBand)[0];
3098
+ if (occurrence) return claimOccurrences([occurrence], occupied);
3099
+ }
3100
+ const tokens = projected.split(/\s+/).filter(Boolean);
3101
+ if (tokens.length < 2) return void 0;
3102
+ for (const first of preferredOccurrences(lines, occurrencesOf(lines, tokens[0], occupied), preferNodeTextBand)) {
3103
+ const matches = [first];
3104
+ let previous = first;
3105
+ let rowStart = first.colStart;
3106
+ let rowEnd = first.colEnd;
3107
+ let complete = true;
3108
+ for (const token of tokens.slice(1)) {
3109
+ const next = preferredOccurrences(lines, occurrencesOf(lines, token, occupied), preferNodeTextBand).find((candidate) => candidate.row === previous.row && candidate.colStart >= previous.colEnd && candidate.colStart - previous.colEnd <= 3 || candidate.row === previous.row + 1 && candidate.colStart < rowEnd && candidate.colEnd > rowStart);
3110
+ if (!next) {
3111
+ complete = false;
3112
+ break;
3113
+ }
3114
+ matches.push(next);
3115
+ if (next.row === previous.row) {
3116
+ rowStart = Math.min(rowStart, next.colStart);
3117
+ rowEnd = Math.max(rowEnd, next.colEnd);
3118
+ } else {
3119
+ rowStart = next.colStart;
3120
+ rowEnd = next.colEnd;
3121
+ }
3122
+ previous = next;
3123
+ }
3124
+ if (complete) return claimOccurrences(matches, occupied);
3125
+ }
3126
+ return void 0;
3127
+ }
3128
+ function semanticContainers(source) {
3129
+ const parsed = parseRegisteredMermaid(source);
3130
+ if (!parsed.ok) return [];
3131
+ const body = parsed.value.body;
3132
+ if (body.kind === "state") {
3133
+ const out = [];
3134
+ const visit2 = (states) => states.flatMap((state) => {
3135
+ let members = [];
3136
+ if (state.regions) {
3137
+ state.regions.forEach((region, index) => {
3138
+ const regionMembers = visit2(region.states);
3139
+ out.push({ id: `${state.id}__r${index + 1}`, kind: "cluster", members: regionMembers });
3140
+ members.push(...regionMembers);
3141
+ });
3142
+ } else if (state.states) {
3143
+ members = visit2(state.states);
3144
+ }
3145
+ if (state.states || state.regions) out.push({ id: state.id, kind: "cluster", members });
3146
+ return [state.id, ...members];
3147
+ });
3148
+ visit2(body.states);
3149
+ return out;
3150
+ }
3151
+ if (body.kind === "sequence") {
3152
+ let index = 0;
3153
+ return (body.statements ?? []).flatMap((statement) => statement.kind === "fragment" ? [{ id: `block#${index++}:${statement.fragment.fragmentKind}`, kind: "compartment", members: [] }] : []);
3154
+ }
3155
+ if (body.kind === "class") {
3156
+ return (body.namespaces ?? []).map((namespace) => ({
3157
+ id: namespace.name,
3158
+ kind: "cluster",
3159
+ members: body.classes.filter((cls) => cls.namespace === namespace.name).map((cls) => cls.id)
3160
+ }));
3161
+ }
3162
+ if (body.kind === "er") return (body.groups ?? []).map((group) => ({
3163
+ id: group.id,
3164
+ kind: "cluster",
3165
+ members: [
3166
+ ...body.entities.filter((entity) => entity.groupId === group.id).map((entity) => entity.id),
3167
+ ...(body.groups ?? []).filter((child) => child.parentId === group.id).map((child) => child.id)
3168
+ ]
3169
+ }));
3170
+ if (body.kind === "xychart") return [{ id: "plot", kind: "plot", members: [] }];
3171
+ if (body.kind === "radar") return Array.from({ length: body.ticks }, (_, index) => ({
3172
+ id: `ring:${index}`,
3173
+ kind: "ring",
3174
+ members: [],
3175
+ geometry: { kind: "ring", index, count: body.ticks }
3176
+ }));
3177
+ if (body.kind === "quadrant") return Array.from({ length: 4 }, (_, index) => ({
3178
+ id: `quadrant#${index + 1}`,
3179
+ kind: "compartment",
3180
+ members: [],
3181
+ geometry: { kind: "quadrant", index: index + 1 }
3182
+ }));
3183
+ if (body.kind === "gantt") return body.sections.map((section, index) => ({
3184
+ id: `section#${index}`,
3185
+ kind: "band",
3186
+ members: section.tasks.map((task) => task.taskId ?? task.id)
3187
+ }));
3188
+ return [];
3189
+ }
3190
+ function addSemanticContainerRegions(ascii, source, regions) {
3191
+ const lines = ascii.split("\n");
3192
+ const canvasEnd = Math.max(1, ...lines.map(visualWidth));
3193
+ const canvasRows = Math.max(1, lines.length);
3194
+ const result = [...regions];
3195
+ for (const container of semanticContainers(source)) {
3196
+ if (result.some((region) => region.id === container.id && region.kind === container.kind)) continue;
3197
+ const members = result.filter((region) => container.members.includes(region.id));
3198
+ if (container.geometry?.kind === "quadrant") {
3199
+ const left = container.geometry.index === 2 || container.geometry.index === 3;
3200
+ const top = container.geometry.index === 1 || container.geometry.index === 2;
3201
+ const halfCol = Math.floor(canvasEnd / 2);
3202
+ const halfRow = Math.floor(canvasRows / 2);
3203
+ result.push({
3204
+ id: container.id,
3205
+ kind: container.kind,
3206
+ canvasRow: top ? 0 : halfRow,
3207
+ canvasColStart: left ? 0 : halfCol,
3208
+ canvasColEnd: left ? Math.max(1, halfCol) : canvasEnd,
3209
+ rowSpan: Math.max(1, top ? halfRow : canvasRows - halfRow)
3210
+ });
3211
+ continue;
3212
+ }
3213
+ if (container.geometry?.kind === "ring") {
3214
+ const maxInset = Math.max(0, Math.floor(Math.min(canvasEnd, canvasRows) / 2) - 1);
3215
+ const inset = container.geometry.count <= 1 ? 0 : Math.round((container.geometry.count - 1 - container.geometry.index) * maxInset / container.geometry.count);
3216
+ result.push({
3217
+ id: container.id,
3218
+ kind: container.kind,
3219
+ canvasRow: inset,
3220
+ canvasColStart: inset,
3221
+ canvasColEnd: Math.max(inset + 1, canvasEnd - inset),
3222
+ rowSpan: Math.max(1, canvasRows - inset * 2)
3223
+ });
3224
+ continue;
3225
+ }
3226
+ const firstRow = members.length > 0 ? Math.min(...members.map((region) => region.canvasRow)) : 0;
3227
+ const lastRow = members.length > 0 ? Math.max(...members.map((region) => region.canvasRow + (region.rowSpan ?? 1))) : Math.max(1, lines.length);
3228
+ result.push({
3229
+ id: container.id,
3230
+ kind: container.kind,
3231
+ canvasRow: firstRow,
3232
+ canvasColStart: members.length > 0 ? Math.min(...members.map((region) => region.canvasColStart)) : 0,
3233
+ canvasColEnd: members.length > 0 ? Math.max(...members.map((region) => region.canvasColEnd)) : canvasEnd,
3234
+ rowSpan: Math.max(1, lastRow - firstRow)
3235
+ });
3236
+ }
3237
+ result.sort((a, b) => a.canvasRow - b.canvasRow || a.canvasColStart - b.canvasColStart || compareCodePointStrings(a.id, b.id));
3238
+ return result;
3239
+ }
3240
+ function orderGraphNodesForTerminal(graph, nodes) {
3241
+ try {
3242
+ const config = {
3243
+ useAscii: false,
3244
+ paddingX: 5,
3245
+ paddingY: 5,
3246
+ boxBorderPadding: 1,
3247
+ graphDirection: graph.direction === "LR" || graph.direction === "RL" ? "LR" : "TD"
3248
+ };
3249
+ const projected = convertToAsciiGraph(graph, config);
3250
+ createMapping(projected);
3251
+ const position = new Map(projected.nodes.flatMap((node) => node.drawingCoord ? [[node.name, node.drawingCoord]] : []));
3252
+ const bottomToTop = graph.direction === "BT";
3253
+ nodes.sort((a, b) => {
3254
+ const pa = position.get(a.id);
3255
+ const pb = position.get(b.id);
3256
+ if (!pa || !pb) return pa ? -1 : pb ? 1 : 0;
3257
+ const row = bottomToTop ? pb.y - pa.y : pa.y - pb.y;
3258
+ return row || pa.x - pb.x;
3259
+ });
3260
+ } catch {
3261
+ }
3262
+ }
3263
+ function orderClassesForTerminal(body) {
3264
+ const classes = [...body.classes];
3265
+ const parents = /* @__PURE__ */ new Map();
3266
+ const children = /* @__PURE__ */ new Map();
3267
+ for (const relation of body.relations) {
3268
+ const hierarchical = relation.kind === "inheritance" || relation.kind === "realization";
3269
+ const parent = hierarchical && relation.markerAt === "to" ? relation.to : relation.from;
3270
+ const child = hierarchical && relation.markerAt === "to" ? relation.from : relation.to;
3271
+ if (!parents.has(child)) parents.set(child, /* @__PURE__ */ new Set());
3272
+ parents.get(child).add(parent);
3273
+ if (!children.has(parent)) children.set(parent, /* @__PURE__ */ new Set());
3274
+ children.get(parent).add(child);
3275
+ }
3276
+ const level = /* @__PURE__ */ new Map();
3277
+ const queue = classes.filter((cls) => !parents.has(cls.id) || parents.get(cls.id).size === 0).map((cls) => cls.id);
3278
+ for (const id of queue) level.set(id, 0);
3279
+ const cap = Math.max(0, classes.length - 1);
3280
+ for (let cursor = 0; cursor < queue.length; cursor++) {
3281
+ const id = queue[cursor];
3282
+ for (const child of children.get(id) ?? []) {
3283
+ const next = (level.get(id) ?? 0) + 1;
3284
+ if (next > cap) continue;
3285
+ if (!level.has(child) || level.get(child) < next) {
3286
+ level.set(child, next);
3287
+ queue.push(child);
3288
+ }
3289
+ }
3290
+ }
3291
+ for (const cls of classes) if (!level.has(cls.id)) level.set(cls.id, 0);
3292
+ const authoredIndex = new Map(classes.map((cls, index) => [cls.id, index]));
3293
+ return classes.sort((a, b) => level.get(a.id) - level.get(b.id) || authoredIndex.get(a.id) - authoredIndex.get(b.id));
3294
+ }
3295
+ function legacyClassBodyForCandidates(source) {
3296
+ try {
3297
+ const legacy = parseClassDiagram(toMermaidLines(source));
3298
+ const namespaces = [];
3299
+ const namespaceByClass = /* @__PURE__ */ new Map();
3300
+ const visitNamespaces = (items, parent = "") => {
3301
+ for (const namespace of items) {
3302
+ const path = parent ? `${parent}.${namespace.name}` : namespace.name;
3303
+ namespaces.push(namespace.label === void 0 ? { name: path } : { name: path, label: namespace.label });
3304
+ for (const id of namespace.classIds) if (!namespaceByClass.has(id)) namespaceByClass.set(id, path);
3305
+ visitNamespaces(namespace.children, path);
3306
+ }
3307
+ };
3308
+ visitNamespaces(legacy.namespaces);
3309
+ return {
3310
+ kind: "class",
3311
+ classes: legacy.classes.map((cls) => ({
3312
+ id: cls.id,
3313
+ label: cls.label,
3314
+ generic: cls.generic,
3315
+ members: [],
3316
+ ...namespaceByClass.has(cls.id) ? { namespace: namespaceByClass.get(cls.id) } : {}
3317
+ })),
3318
+ relations: legacy.relationships.map((relation) => ({
3319
+ from: relation.from,
3320
+ to: relation.to,
3321
+ kind: relation.type,
3322
+ markerAt: relation.markerAt
3323
+ })),
3324
+ notes: [],
3325
+ ...namespaces.length > 0 ? { namespaces } : {}
3326
+ };
3327
+ } catch {
3328
+ return void 0;
3329
+ }
3330
+ }
3331
+ function classCandidates(body) {
3332
+ const classes = body.classes.some((cls, index, all) => all.findIndex((other) => (other.label || other.id) === (cls.label || cls.id)) !== index) ? orderClassesForTerminal(body) : body.classes;
3333
+ const namespaces = body.namespaces ?? [];
3334
+ const namespaceLabel = (name, label) => label ?? name.split(".").at(-1) ?? name;
3335
+ const collidingNamespaces = new Set(namespaces.filter((namespace) => body.classes.some((cls) => (cls.namespace === namespace.name || cls.namespace?.startsWith(`${namespace.name}.`)) && (cls.label || cls.id) === namespaceLabel(namespace.name, namespace.label))).map((namespace) => namespace.name));
3336
+ const out = [];
3337
+ for (const namespace of namespaces) {
3338
+ if (collidingNamespaces.has(namespace.name)) {
3339
+ addCandidateWithFallback(out, namespace.name, namespaceLabel(namespace.name, namespace.label), void 0, "cluster");
3340
+ }
3341
+ }
3342
+ out.push(...classes.flatMap((c) => {
3343
+ const out2 = [];
3344
+ addCandidateWithFallback(out2, c.id, c.label || c.id);
3345
+ return out2;
3346
+ }));
3347
+ for (const namespace of namespaces) {
3348
+ if (!collidingNamespaces.has(namespace.name)) {
3349
+ addCandidateWithFallback(out, namespace.name, namespaceLabel(namespace.name, namespace.label), void 0, "cluster");
3350
+ }
3351
+ }
3352
+ return out;
3353
+ }
3354
+ function candidatesForDiagram(source) {
3355
+ const r = parseRegisteredMermaid(source);
3356
+ if (!r.ok) return [];
3357
+ const d = r.value;
3358
+ let graphBody;
3359
+ let nodeSourceLine;
3360
+ let groupSourceLine;
3361
+ if (d.body.kind === "flowchart") {
3362
+ graphBody = d.body.graph;
3363
+ nodeSourceLine = (id) => d.source.nodes.get(id)?.line;
3364
+ groupSourceLine = (id) => d.source.groups.get(id)?.line;
3365
+ } else if (d.kind === "flowchart") {
3366
+ try {
3367
+ graphBody = parseMermaid(d.canonicalSource);
3368
+ } catch {
3369
+ graphBody = void 0;
3370
+ }
3371
+ }
3372
+ if (graphBody) {
3373
+ const out = [];
3374
+ const nodes = [...graphBody.nodes.values()];
3375
+ const labels = nodes.map((node) => node.label && node.label.length > 0 ? node.label : node.id);
3376
+ if (new Set(labels).size !== labels.length) {
3377
+ orderGraphNodesForTerminal(graphBody, nodes);
3378
+ }
3379
+ const collidingGroups = /* @__PURE__ */ new Set();
3380
+ const descendantIds = (group) => [
3381
+ ...group.nodeIds,
3382
+ ...group.children.flatMap(descendantIds)
3383
+ ];
3384
+ const groupCollidesWithMember = (group) => {
3385
+ const groupLabel = group.label || group.id;
3386
+ return descendantIds(group).some((id) => {
3387
+ const node = graphBody.nodes.get(id);
3388
+ return (node?.label || node?.id) === groupLabel;
3389
+ });
3390
+ };
3391
+ const addGroups = (groups, colliding) => {
3392
+ for (const group of groups) {
3393
+ if (groupCollidesWithMember(group)) collidingGroups.add(group.id);
3394
+ if (collidingGroups.has(group.id) === colliding) {
3395
+ addCandidateWithFallback(out, group.id, group.label || group.id, groupSourceLine?.(group.id), "subgraph");
3396
+ }
3397
+ addGroups(group.children, colliding);
3398
+ }
3399
+ };
3400
+ addGroups(graphBody.subgraphs, true);
3401
+ for (const n of nodes) addCandidateWithFallback(out, n.id, n.label && n.label.length > 0 ? n.label : n.id, nodeSourceLine?.(n.id));
3402
+ const visit2 = (groups) => {
3403
+ for (const g of groups) {
3404
+ if (!collidingGroups.has(g.id)) addCandidateWithFallback(out, g.id, g.label || g.id, groupSourceLine?.(g.id), "subgraph");
3405
+ visit2(g.children);
3406
+ }
3407
+ };
3408
+ visit2(graphBody.subgraphs);
3409
+ return out;
3410
+ }
3411
+ if (d.body.kind === "state") {
3412
+ const out = [];
3413
+ const topLevel = [...d.body.states];
3414
+ const labels = topLevel.map((state) => state.label ?? state.id);
3415
+ if (new Set(labels).size !== labels.length) orderGraphNodesForTerminal(stateBodyToGraph(d.body), topLevel);
3416
+ const visit2 = (states) => {
3417
+ for (const s of states) {
3418
+ const nested = s.states ?? s.regions?.flatMap((region) => region.states) ?? [];
3419
+ addCandidateWithFallback(out, s.id, s.label ?? s.id, void 0, nested.length > 0 ? "cluster" : "node");
3420
+ if (nested.length > 0) visit2(nested);
3421
+ }
3422
+ };
3423
+ visit2(topLevel);
3424
+ return out;
3425
+ }
3426
+ if (d.body.kind === "sequence") {
3427
+ const out = d.body.participants.flatMap((p) => {
3428
+ const out2 = [];
3429
+ addCandidateWithFallback(out2, p.id, p.label || p.id);
3430
+ return out2;
3431
+ });
3432
+ let blockIndex = 0;
3433
+ for (const statement of d.body.statements ?? []) {
3434
+ if (statement.kind !== "fragment") continue;
3435
+ addCandidateWithFallback(
3436
+ out,
3437
+ `block#${blockIndex++}:${statement.fragment.fragmentKind}`,
3438
+ statement.fragment.label ?? statement.fragment.fragmentKind,
3439
+ void 0,
3440
+ "compartment"
3441
+ );
3442
+ }
3443
+ return out;
3444
+ }
3445
+ if (d.body.kind === "class") return classCandidates(d.body);
3446
+ if (d.kind === "class") {
3447
+ const classBody = legacyClassBodyForCandidates(d.canonicalSource);
3448
+ if (classBody) return classCandidates(classBody);
3449
+ }
3450
+ if (d.body.kind === "er") {
3451
+ const out = d.body.entities.map((e) => ({ id: e.id, label: e.id }));
3452
+ for (const group of d.body.groups ?? []) {
3453
+ addCandidateWithFallback(out, group.id, group.label || group.id, void 0, "cluster");
3454
+ }
3455
+ return out;
3456
+ }
3457
+ if (d.body.kind === "timeline") {
3458
+ const out = [];
3459
+ for (const s of d.body.sections) {
3460
+ addCandidate(out, s.id, s.label, void 0, "band");
3461
+ for (const p of s.periods) addCandidate(out, p.id, p.label);
3462
+ }
3463
+ return out;
3464
+ }
3465
+ if (d.body.kind === "journey") {
3466
+ const out = [];
3467
+ addCandidate(out, "title", d.body.title);
3468
+ for (const s of d.body.sections) {
3469
+ addCandidate(out, s.id, s.label, void 0, "band");
3470
+ for (const t of s.tasks) addCandidate(out, t.id, t.text);
3471
+ }
3472
+ return out;
3473
+ }
3474
+ if (d.body.kind === "architecture") {
3475
+ const out = [];
3476
+ for (const g of d.body.groups) addCandidateWithFallback(out, g.id, g.label || g.id, void 0, "cluster");
3477
+ for (const s of d.body.services) addCandidateWithFallback(out, s.id, s.label || s.id);
3478
+ return out;
3479
+ }
3480
+ if (d.body.kind === "xychart") {
3481
+ const out = [];
3482
+ addCandidate(out, "title", d.body.title);
3483
+ addCandidate(out, "x-axis", d.body.xAxis?.name);
3484
+ addCandidate(out, "y-axis", d.body.yAxis?.name);
3485
+ d.body.xAxis?.categories?.forEach((label, index) => addCandidate(out, `x-category-${index}`, label));
3486
+ for (const s of d.body.series) addCandidate(out, s.id, s.name);
3487
+ return out;
3488
+ }
3489
+ if (d.body.kind === "pie") {
3490
+ const out = [];
3491
+ addCandidate(out, "title", d.body.title);
3492
+ for (const s of d.body.slices) addCandidate(out, s.id, s.label);
3493
+ return out;
3494
+ }
3495
+ if (d.body.kind === "quadrant") {
3496
+ const out = [];
3497
+ addCandidate(out, "title", d.body.title);
3498
+ addCandidate(out, "x-axis-near", d.body.xAxis?.near);
3499
+ addCandidate(out, "x-axis-far", d.body.xAxis?.far);
3500
+ addCandidate(out, "y-axis-near", d.body.yAxis?.near);
3501
+ addCandidate(out, "y-axis-far", d.body.yAxis?.far);
3502
+ d.body.quadrants.forEach((label, index) => addCandidate(out, `quadrant-label#${index + 1}`, label, void 0, "label"));
3503
+ d.body.points.forEach((p, index) => addCandidate(out, `point-${index}`, p.label));
3504
+ return out;
3505
+ }
3506
+ if (d.body.kind === "gantt") {
3507
+ const out = [];
3508
+ for (let index = 0; index < d.body.sections.length; index++) {
3509
+ const s = d.body.sections[index];
3510
+ addCandidate(out, `section-label#${index}`, s.label, void 0, "label");
3511
+ for (const t of s.tasks) addCandidate(out, t.taskId ?? t.id, t.label);
3512
+ }
3513
+ return out;
3514
+ }
3515
+ if (d.body.kind === "gitgraph") {
3516
+ const out = [];
3517
+ for (const branch of d.body.branches) addCandidate(out, `branch:${branch.name}`, branch.name, void 0, "lane");
3518
+ for (const commit of d.body.commits) addCandidateWithFallback(out, commit.id, commit.message || commit.id);
3519
+ return out;
3520
+ }
3521
+ if (d.body.kind === "mindmap") {
3522
+ const out = [];
3523
+ const visit2 = (node) => {
3524
+ addCandidateWithFallback(out, node.id, node.label || node.id);
3525
+ for (const child of node.children) visit2(child);
3526
+ };
3527
+ visit2(d.body.root);
3528
+ return out;
3529
+ }
3530
+ if (d.body.kind === "radar") {
3531
+ const out = [];
3532
+ for (const axis of d.body.axes) addCandidateWithFallback(out, axis.id, axis.label || axis.id);
3533
+ for (const curve of d.body.curves) addCandidateWithFallback(out, curve.id, curve.label || curve.id);
3534
+ return out;
3535
+ }
3536
+ return [];
3537
+ }
3538
+
3539
+ // src/agent/facts.ts
3540
+ function describeMermaidFacts(d) {
3541
+ const out = [];
3542
+ add(out, `family ${d.kind}`);
3543
+ if (d.meta.accessibility.title) add(out, `accessibility title ${clean(d.meta.accessibility.title)}`);
3544
+ if (d.meta.accessibility.descr) add(out, `accessibility descr ${clean(d.meta.accessibility.descr)}`);
3545
+ switch (d.body.kind) {
3546
+ case "flowchart": {
3547
+ const g = d.body.graph;
3548
+ add(out, `direction ${g.direction}`);
3549
+ for (const n of g.nodes.values()) {
3550
+ add(out, `node ${clean(n.id)} : ${clean(n.label || n.id)}`);
3551
+ add(out, `node ${clean(n.id)} shape ${clean(n.shape)}`);
3552
+ }
3553
+ const visitGroups = (items, parent) => {
3554
+ for (const group of items) {
3555
+ add(out, `group ${clean(group.id)} : ${clean(group.label || group.id)}`);
3556
+ if (parent) add(out, `group ${clean(group.id)} parent ${clean(parent)}`);
3557
+ if (group.direction) add(out, `group ${clean(group.id)} direction ${group.direction}`);
3558
+ for (const nodeId of group.nodeIds) add(out, `group ${clean(group.id)} contains ${clean(nodeId)}`);
3559
+ visitGroups(group.children, group.id);
3560
+ }
3561
+ };
3562
+ visitGroups(g.subgraphs);
3563
+ g.edges.forEach((e, i) => {
3564
+ const base = `edge ${clean(e.source)} -> ${clean(e.target)}`;
3565
+ add(out, e.label ? `${base} : ${clean(e.label)}` : base);
3566
+ add(out, `edge#${i} ${clean(e.source)} -> ${clean(e.target)}${e.label ? ` : ${clean(e.label)}` : ""}`);
3567
+ if (e.style !== "solid") add(out, `${base} style ${e.style}`);
3568
+ });
3569
+ break;
3570
+ }
3571
+ case "state":
3572
+ factsState(out, d.body);
3573
+ break;
3574
+ case "sequence":
3575
+ factsSequence(out, d.body);
3576
+ break;
3577
+ case "timeline":
3578
+ factsTimeline(out, d.body);
3579
+ break;
3580
+ case "class":
3581
+ factsClass(out, d.body);
3582
+ break;
3583
+ case "er":
3584
+ factsEr(out, d.body);
3585
+ break;
3586
+ case "journey":
3587
+ factsJourney(out, d.body);
3588
+ break;
3589
+ case "architecture":
3590
+ factsArchitecture(out, d.body);
3591
+ break;
3592
+ case "xychart":
3593
+ factsXyChart(out, d.body);
3594
+ break;
3595
+ case "pie":
3596
+ factsPie(out, d.body);
3597
+ break;
3598
+ case "quadrant":
3599
+ factsQuadrant(out, d.body);
3600
+ break;
3601
+ case "gantt":
3602
+ factsGantt(out, d.body);
3603
+ break;
3604
+ case "mindmap":
3605
+ factsMindmap(out, d.body);
3606
+ break;
3607
+ case "gitgraph":
3608
+ factsGitGraph(out, d.body);
3609
+ break;
3610
+ case "radar": {
3611
+ if (d.body.title !== void 0) add(out, `title ${clean(d.body.title)}`);
3612
+ d.body.axes.forEach((a, i) => add(out, `axis#${i} ${clean(a.id)} : ${clean(a.label)}`));
3613
+ d.body.curves.forEach((c, i) => add(out, `curve#${i} ${clean(c.id)} : ${clean(c.label)} {${c.values.join(",")}}`));
3614
+ add(out, `scale ${d.body.min}..${d.body.max ?? "auto"} ticks ${d.body.ticks} graticule ${d.body.graticule}`);
3615
+ break;
3616
+ }
3617
+ case "opaque": {
3618
+ add(out, `opaque ${d.body.family}`);
3619
+ const plugin = getFamily(d.kind);
3620
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source);
3621
+ labels.forEach((label, i) => add(out, `label#${i} ${clean(label.target || "")} : ${clean(label.text)}`));
3622
+ break;
3623
+ }
3624
+ case "extension": {
3625
+ const plugin = getFamily(d.kind);
3626
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source);
3627
+ labels.forEach((label, i) => add(out, `label#${i} ${clean(label.target || "")} : ${clean(label.text)}`));
3628
+ break;
3629
+ }
3630
+ case "preserved":
3631
+ add(out, `preserved ${d.body.preservation.upstreamFamilyId ?? d.body.preservation.header}`);
3632
+ break;
3633
+ default: {
3634
+ const _never = d.body;
3635
+ void _never;
3636
+ }
3637
+ }
3638
+ return Array.from(new Set(out)).sort();
3639
+ }
3640
+ function describeMermaidFactsSource(source) {
3641
+ const parsed = parseRegisteredMermaid(source);
3642
+ return parsed.ok ? ok(describeMermaidFacts(parsed.value)) : err(parsed.error);
3643
+ }
3644
+ function checkMermaid(d, spec) {
3645
+ const facts = describeMermaidFacts(d);
3646
+ const have = new Set(facts);
3647
+ const required = specRequired(spec);
3648
+ const forbidden = specForbidden(spec);
3649
+ const missing = required.filter((f) => !have.has(f));
3650
+ let unexpected = forbidden.filter((f) => have.has(f));
3651
+ if (isObjectSpec(spec) && spec.exact) {
3652
+ const allowed = new Set(required);
3653
+ unexpected = [...unexpected, ...facts.filter((f) => !allowed.has(f))];
3654
+ }
3655
+ unexpected = Array.from(new Set(unexpected)).sort();
3656
+ return { ok: missing.length === 0 && unexpected.length === 0, missing, unexpected, facts };
3657
+ }
3658
+ function checkMermaidSource(source, spec) {
3659
+ const parsed = parseRegisteredMermaid(source);
3660
+ return parsed.ok ? ok(checkMermaid(parsed.value, spec)) : err(parsed.error);
3661
+ }
3662
+ function factsState(out, body) {
3663
+ if (body.direction) add(out, `direction ${body.direction}`);
3664
+ const visit2 = (states, transitions, path) => {
3665
+ for (const s of states) {
3666
+ add(out, `state ${clean(s.id)}`);
3667
+ if (s.label) add(out, `state ${clean(s.id)} : ${clean(s.label)}`);
3668
+ if (s.stereotype) add(out, `state ${clean(s.id)} stereotype ${clean(s.stereotype)}`);
3669
+ if (path) add(out, `state ${clean(s.id)} parent ${clean(path)}`);
3670
+ if (s.states !== void 0 || s.regions !== void 0) {
3671
+ add(out, `composite ${clean(s.id)}`);
3672
+ if (s.direction) add(out, `state ${clean(s.id)} direction ${s.direction}`);
3673
+ if (s.regions) s.regions.forEach((region, index) => {
3674
+ add(out, `state ${clean(s.id)} region ${index}`);
3675
+ visit2(region.states, region.transitions, s.id);
3676
+ });
3677
+ else visit2(s.states ?? [], s.transitions ?? [], s.id);
3678
+ }
3679
+ if (s.className) add(out, `state ${clean(s.id)} class ${clean(s.className)}`);
3680
+ if (s.style) add(out, `state ${clean(s.id)} styled`);
3681
+ }
3682
+ for (const t of transitions) add(out, edgeFact(t.from, t.to, t.label));
3683
+ };
3684
+ visit2(body.states, body.transitions);
3685
+ for (const [index, note] of (body.notes ?? []).entries()) {
3686
+ add(out, `note#${index} ${note.side} of ${clean(note.target)} : ${clean(note.text)}`);
3687
+ }
3688
+ }
3689
+ function factsSequence(out, body) {
3690
+ body.participants.forEach((p, i) => {
3691
+ add(out, `${p.kind} ${clean(p.id)} : ${clean(p.label || p.id)}`);
3692
+ add(out, `participant#${i} ${clean(p.id)} : ${clean(p.label || p.id)}`);
3693
+ });
3694
+ sequenceMessageContexts(body).forEach((context, i) => {
3695
+ const m = context.message;
3696
+ add(out, `message ${clean(m.from)} -> ${clean(m.to)} : ${clean(m.text)}`);
3697
+ add(out, `message#${i} ${context.scope === "top-level" ? "top-level" : `fragment#${context.fragmentIndex} branch#${context.branchIndex}`} ${clean(m.from)} -> ${clean(m.to)} : ${clean(m.text)}`);
3698
+ if (m.style !== "sync") add(out, `message#${i} style ${m.style}`);
3699
+ });
3700
+ const fragments = body.statements.filter((statement) => statement.kind === "fragment");
3701
+ fragments.forEach((statement, index) => {
3702
+ if (statement.kind !== "fragment") return;
3703
+ add(out, `fragment#${index} ${statement.fragment.fragmentKind}`);
3704
+ statement.fragment.branches.forEach((branch, branchIndex) => {
3705
+ const label = branch.label ?? (branchIndex === 0 ? statement.fragment.label : void 0);
3706
+ add(out, `fragment#${index} branch#${branchIndex}${label ? ` : ${clean(label)}` : ""}`);
3707
+ });
3708
+ });
3709
+ }
3710
+ function factsTimeline(out, body) {
3711
+ if (body.title) add(out, `title ${clean(body.title)}`);
3712
+ body.sections.forEach((s, si) => {
3713
+ if (s.label) add(out, `section ${clean(s.label)}`);
3714
+ s.periods.forEach((p, pi) => {
3715
+ add(out, `period ${clean(p.label)}`);
3716
+ if (s.label) add(out, `period ${clean(p.label)} section ${clean(s.label)}`);
3717
+ p.events.forEach((e, ei) => {
3718
+ add(out, `event ${clean(p.label)} : ${clean(e.text)}`);
3719
+ add(out, `event#${si}.${pi}.${ei} ${clean(p.label)} : ${clean(e.text)}`);
3720
+ });
3721
+ });
3722
+ });
3723
+ }
3724
+ function factsClass(out, body) {
3725
+ if (body.title) add(out, `title ${clean(body.title)}`);
3726
+ for (const ns of body.namespaces ?? []) {
3727
+ add(out, `namespace ${clean(ns.name)}${ns.label ? ` : ${clean(ns.label)}` : ""}`);
3728
+ }
3729
+ for (const c of body.classes) {
3730
+ add(out, `class ${clean(c.id)}`);
3731
+ if (c.generic) add(out, `class ${clean(c.id)} generic ${clean(c.generic)}`);
3732
+ if (c.label) add(out, `class ${clean(c.id)} : ${clean(c.label)}`);
3733
+ if (c.namespace) add(out, `class ${clean(c.id)} in namespace ${clean(c.namespace)}`);
3734
+ for (const member of c.members) add(out, `member ${clean(c.id)} ${clean(member)}`);
3735
+ }
3736
+ body.relations.forEach((r, i) => {
3737
+ const cards = `${r.fromCardinality ? ` ${clean(r.fromCardinality)}` : ""}${r.toCardinality ? ` ${clean(r.toCardinality)}` : ""}`;
3738
+ add(out, `relation ${clean(r.from)} ${r.kind} ${clean(r.to)}${r.label ? ` : ${clean(r.label)}` : ""}`);
3739
+ add(out, `relation#${i} ${clean(r.from)} ${r.kind} ${clean(r.to)}${cards}${r.label ? ` : ${clean(r.label)}` : ""}`);
3740
+ });
3741
+ body.notes.forEach((n, i) => add(out, `note#${i}${n.for ? ` ${clean(n.for)}` : ""} : ${clean(n.text)}`));
3742
+ }
3743
+ function factsEr(out, body) {
3744
+ if (body.direction) add(out, `direction ${body.direction}`);
3745
+ for (const group of body.groups ?? []) add(out, `er group ${clean(group.id)} label ${clean(group.label)}${group.parentId ? ` parent ${clean(group.parentId)}` : ""}${group.direction ? ` direction ${group.direction}` : ""}`);
3746
+ for (const e of body.entities) {
3747
+ add(out, `entity ${clean(e.id)}${e.label ? ` label ${clean(e.label)}` : ""}${e.groupId ? ` group ${clean(e.groupId)}` : ""}`);
3748
+ if (e.className) add(out, `entity ${clean(e.id)} class ${clean(e.className)}`);
3749
+ if (e.style) add(out, `entity ${clean(e.id)} styled`);
3750
+ for (const attr of e.attributes) add(out, `attribute ${clean(e.id)} ${clean(attr.text)}`);
3751
+ }
3752
+ body.relations.forEach((r, i) => {
3753
+ const line = `relation ${clean(r.from)} ${r.leftCard} ${r.dashed ? ".." : "--"} ${r.rightCard} ${clean(r.to)}${r.label ? ` : ${clean(r.label)}` : ""}`;
3754
+ add(out, line);
3755
+ add(out, `relation#${i} ${line.slice("relation ".length)}`);
3756
+ });
3757
+ }
3758
+ function factsJourney(out, body) {
3759
+ if (body.title) add(out, `title ${clean(body.title)}`);
3760
+ body.sections.forEach((s, si) => {
3761
+ if (s.label) add(out, `section ${clean(s.label)}`);
3762
+ s.tasks.forEach((t, ti) => {
3763
+ const actors = t.actors.length ? ` actors ${t.actors.map(clean).join(", ")}` : "";
3764
+ add(out, `journey task ${clean(t.text)} score ${num2(t.score)}${actors}`);
3765
+ add(out, `journey task#${si}.${ti} ${clean(t.text)} score ${num2(t.score)}${actors}`);
3766
+ if (s.label) add(out, `journey task ${clean(t.text)} section ${clean(s.label)}`);
3767
+ });
3768
+ });
3769
+ const tasks = body.sections.flatMap((s) => s.tasks);
3770
+ add(out, `journey sections ${body.sections.length}`);
3771
+ add(out, `journey tasks ${tasks.length}`);
3772
+ const participation = /* @__PURE__ */ new Map();
3773
+ for (const t of tasks) for (const a of t.actors) participation.set(a, (participation.get(a) ?? 0) + 1);
3774
+ add(out, `journey actors ${participation.size}`);
3775
+ if (tasks.length > 0) {
3776
+ const scores = tasks.map((t) => t.score);
3777
+ const min = Math.min(...scores);
3778
+ const max = Math.max(...scores);
3779
+ const average = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length * 100) / 100;
3780
+ add(out, `journey score range ${min}..${max}`);
3781
+ add(out, `journey average score ${num2(average)}`);
3782
+ for (const t of tasks) {
3783
+ if (t.score === min) add(out, `journey lowest task ${clean(t.text)} score ${num2(t.score)}`);
3784
+ if (t.score === max) add(out, `journey highest task ${clean(t.text)} score ${num2(t.score)}`);
3785
+ }
3786
+ }
3787
+ for (const [actor, count] of participation) add(out, `journey actor ${clean(actor)} tasks ${count}`);
3788
+ }
3789
+ function factsArchitecture(out, body) {
3790
+ if (body.title) add(out, `title ${clean(body.title)}`);
3791
+ if (body.accessibilityTitle) add(out, `accessibility title ${clean(body.accessibilityTitle)}`);
3792
+ if (body.accessibilityDescription) add(out, `accessibility description ${clean(body.accessibilityDescription)}`);
3793
+ for (const g of body.groups) {
3794
+ add(out, `group ${clean(g.id)} : ${clean(g.label || g.id)}`);
3795
+ if (g.icon) add(out, `group ${clean(g.id)} icon ${clean(g.icon)}`);
3796
+ if (g.parentId) add(out, `group ${clean(g.id)} parent ${clean(g.parentId)}`);
3797
+ }
3798
+ for (const s of body.services) {
3799
+ add(out, `service ${clean(s.id)} : ${clean(s.label || s.id)}`);
3800
+ if (s.icon) add(out, `service ${clean(s.id)} icon ${clean(s.icon)}`);
3801
+ if (s.parentId) add(out, `service ${clean(s.id)} parent ${clean(s.parentId)}`);
3802
+ }
3803
+ for (const j of body.junctions) {
3804
+ add(out, `junction ${clean(j.id)}`);
3805
+ if (j.parentId) add(out, `junction ${clean(j.id)} parent ${clean(j.parentId)}`);
3806
+ }
3807
+ body.edges.forEach((e, i) => {
3808
+ const source = `${clean(e.source.id)}${e.source.boundary === "group" ? "{group}" : ""}:${e.source.side}`;
3809
+ const target = `${clean(e.target.id)}${e.target.boundary === "group" ? "{group}" : ""}:${e.target.side}`;
3810
+ const base = `edge ${source} -> ${target}`;
3811
+ add(out, e.label ? `${base} : ${clean(e.label)}` : base);
3812
+ add(out, `edge#${i} ${source} -> ${target}${e.label ? ` : ${clean(e.label)}` : ""}`);
3813
+ });
3814
+ }
3815
+ function factsXyChart(out, body) {
3816
+ if (body.title) add(out, `title ${clean(dequote(body.title))}`);
3817
+ if (body.horizontal) add(out, "orientation horizontal");
3818
+ axisFacts(out, "x-axis", body.xAxis);
3819
+ axisFacts(out, "y-axis", body.yAxis);
3820
+ body.series.forEach((s, i) => {
3821
+ const name = s.name ? clean(dequote(s.name)) : `series#${i}`;
3822
+ add(out, `series ${name} ${s.kind} [${s.values.map(num2).join(",")}]`);
3823
+ add(out, `series#${i} ${s.kind} ${name} [${s.values.map(num2).join(",")}]`);
3824
+ });
3825
+ }
3826
+ function factsPie(out, body) {
3827
+ if (body.title) add(out, `title ${clean(dequote(body.title))}`);
3828
+ if (body.showData) add(out, "showData true");
3829
+ body.slices.forEach((s, i) => {
3830
+ add(out, `slice ${clean(dequote(s.label))} = ${num2(s.value)}`);
3831
+ add(out, `slice#${i} ${clean(dequote(s.label))} = ${num2(s.value)}`);
3832
+ });
3833
+ }
3834
+ function factsQuadrant(out, body) {
3835
+ if (body.title) add(out, `title ${clean(dequote(body.title))}`);
3836
+ if (body.xAxis) add(out, `x-axis ${clean(body.xAxis.near)}${body.xAxis.far ? ` -> ${clean(body.xAxis.far)}` : ""}`);
3837
+ if (body.yAxis) add(out, `y-axis ${clean(body.yAxis.near)}${body.yAxis.far ? ` -> ${clean(body.yAxis.far)}` : ""}`);
3838
+ body.quadrants.forEach((q, i) => {
3839
+ if (q) add(out, `quadrant ${i + 1} : ${clean(q)}`);
3840
+ });
3841
+ body.points.forEach((p, i) => {
3842
+ add(out, `point ${clean(dequote(p.label))} @ ${num2(p.x)},${num2(p.y)}`);
3843
+ add(out, `point#${i} ${clean(dequote(p.label))} @ ${num2(p.x)},${num2(p.y)}`);
3844
+ });
3845
+ }
3846
+ function factsGantt(out, body) {
3847
+ if (body.title) add(out, `title ${clean(body.title)}`);
3848
+ body.sections.forEach((s, si) => {
3849
+ if (s.label) add(out, `section ${clean(s.label)}`);
3850
+ s.tasks.forEach((t, ti) => {
3851
+ add(out, `task ${clean(t.label)}`);
3852
+ add(out, `task#${si}.${ti} ${clean(t.label)}`);
3853
+ if (t.taskId) add(out, `task ${clean(t.label)} id ${clean(t.taskId)}`);
3854
+ if (s.label) add(out, `task ${clean(t.label)} section ${clean(s.label)}`);
3855
+ for (const tag of t.tags) add(out, `task ${clean(t.label)} tag ${tag}`);
3856
+ if (t.start) add(out, `task ${clean(t.label)} start ${clean(t.start)}`);
3857
+ add(out, `task ${clean(t.label)} end ${clean(t.end)}`);
3858
+ });
3859
+ });
3860
+ }
3861
+ function factsMindmap(out, body) {
3862
+ const visit2 = (node, parent) => {
3863
+ add(out, `node ${clean(node.id)} : ${clean(node.label)}`);
3864
+ add(out, `node ${clean(node.id)} shape ${node.shape}`);
3865
+ if (parent) add(out, edgeFact(parent, node.id));
3866
+ if (node.icon) add(out, `node ${clean(node.id)} icon ${clean(node.icon)}`);
3867
+ if (node.className) add(out, `node ${clean(node.id)} class ${clean(node.className)}`);
3868
+ node.children.forEach((child) => visit2(child, node.id));
3869
+ };
3870
+ visit2(body.root);
3871
+ }
3872
+ function factsGitGraph(out, body) {
3873
+ add(out, `direction ${body.direction}`);
3874
+ for (const branch of body.branches) {
3875
+ add(out, `branch ${clean(branch.name)} order ${branch.order}`);
3876
+ if (branch.head) add(out, `branch ${clean(branch.name)} head ${clean(branch.head)}`);
3877
+ }
3878
+ for (const commit of body.commits) {
3879
+ add(out, `commit ${clean(commit.id)} branch ${clean(commit.branch)}`);
3880
+ add(out, `commit ${clean(commit.id)} type ${commit.type}`);
3881
+ if (commit.message) add(out, `commit ${clean(commit.id)} message ${clean(commit.message)}`);
3882
+ commit.tags.forEach((tag) => add(out, `commit ${clean(commit.id)} tag ${clean(tag)}`));
3883
+ commit.parents.forEach((parent) => add(out, edgeFact(parent, commit.id, commit.source === "commit" ? void 0 : commit.source)));
3884
+ }
3885
+ }
3886
+ function axisFacts(out, label, axis) {
3887
+ if (!axis) return;
3888
+ if (axis.name) add(out, `${label} name ${clean(dequote(axis.name))}`);
3889
+ if (axis.categories) add(out, `${label} categories ${axis.categories.map((c) => clean(dequote(c))).join(", ")}`);
3890
+ if (axis.range) add(out, `${label} range ${num2(axis.range.min)} -> ${num2(axis.range.max)}`);
3891
+ }
3892
+ function edgeFact(from, to, label) {
3893
+ return `edge ${clean(from)} -> ${clean(to)}${label ? ` : ${clean(label)}` : ""}`;
3894
+ }
3895
+ function add(out, fact) {
3896
+ out.push(fact.replace(/\s+$/g, ""));
3897
+ }
3898
+ function clean(value) {
3899
+ return String(value ?? "").trim().replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
3900
+ }
3901
+ function dequote(value) {
3902
+ return value.replace(/^"(.*)"$/, "$1");
3903
+ }
3904
+ function num2(value) {
3905
+ return Number.isInteger(value) ? String(value) : String(Number(value.toPrecision(12)));
3906
+ }
3907
+ function toFactList(value) {
3908
+ if (!Array.isArray(value)) return [];
3909
+ return value.map((item) => typeof item === "string" ? item : String(item));
3910
+ }
3911
+ function specRequired(spec) {
3912
+ if (Array.isArray(spec)) return toFactList(spec);
3913
+ if (!isObjectSpec(spec)) return [];
3914
+ return unique([...toFactList(spec.include), ...toFactList(spec.expected), ...toFactList(spec.require), ...toFactList(spec.required)]);
3915
+ }
3916
+ function specForbidden(spec) {
3917
+ if (!isObjectSpec(spec)) return [];
3918
+ return unique([...toFactList(spec.exclude), ...toFactList(spec.absent), ...toFactList(spec.forbid), ...toFactList(spec.forbidden), ...toFactList(spec.unexpected)]);
3919
+ }
3920
+ function isObjectSpec(spec) {
3921
+ return spec !== null && typeof spec === "object" && !Array.isArray(spec);
3922
+ }
3923
+ function unique(values) {
3924
+ return Array.from(new Set(values));
3925
+ }
3926
+
3927
+ // src/agent/describe.ts
3928
+ function describeMermaidSource(source, opts = {}) {
3929
+ const r = parseRegisteredMermaid(source);
3930
+ if (!r.ok) {
3931
+ const first = Array.isArray(r.error) ? r.error[0] : void 0;
3932
+ if (opts.format === "json") {
3933
+ return JSON.stringify({ error: first?.message ?? "parse error", nodes: [], edges: [] });
3934
+ }
3935
+ if (opts.format === "facts") return `error parse ${first?.message ?? "parse error"}`;
3936
+ return `Unparseable Mermaid source: ${first?.message ?? "parse error"}.`;
3937
+ }
3938
+ return describeMermaid(r.value, opts);
3939
+ }
3940
+ function describeMermaid(d, opts = {}) {
3941
+ if (opts.format === "json") return JSON.stringify(describeMermaidTree(d));
3942
+ if (opts.format === "facts") return describeMermaidFacts(d).join("\n");
3943
+ if (d.body.kind === "flowchart") return describeFlowchart(d);
3944
+ if (d.body.kind === "state") return describeState(d.body);
3945
+ if (d.body.kind === "sequence") return describeSequence(d);
3946
+ if (d.body.kind === "timeline") return describeTimeline(d);
3947
+ if (d.body.kind === "class") return describeClass(d);
3948
+ if (d.body.kind === "er") return describeEr(d);
3949
+ if (d.body.kind === "journey") return describeJourney(d.body);
3950
+ if (d.body.kind === "architecture") return describeArchitecture(d.body);
3951
+ if (d.body.kind === "xychart") return describeXyChart(d.body);
3952
+ if (d.body.kind === "gantt") return describeGantt(d);
3953
+ if (d.body.kind === "pie") return describePie(d.body);
3954
+ if (d.body.kind === "quadrant") return describeQuadrant(d.body);
3955
+ if (d.body.kind === "mindmap") return describeMindmap(d.body);
3956
+ if (d.body.kind === "gitgraph") return describeGitGraph(d.body);
3957
+ if (d.body.kind === "radar") {
3958
+ const axes = d.body.axes.map((a) => a.label).join(", ");
3959
+ const curves = d.body.curves.map((c) => c.label).join(", ");
3960
+ const title = d.body.title ? `"${d.body.title}" ` : "";
3961
+ return `A radar chart ${title}comparing ${d.body.curves.length} curve(s)${curves ? ` (${curves})` : ""} across ${d.body.axes.length} axis/axes${axes ? ` (${axes})` : ""}.`;
3962
+ }
3963
+ if (d.body.kind === "opaque") return describeOpaque(d.kind, d.body.source);
3964
+ if (d.body.kind === "extension") return describeOpaque(d.kind, d.body.source);
3965
+ if (d.body.kind === "preserved") return `An unregistered ${d.body.preservation.upstreamFamilyId ?? d.body.preservation.header} diagram preserved as source. ${d.body.diagnostic.message}`;
3966
+ const _never = d.body;
3967
+ void _never;
3968
+ return `A ${d.kind} diagram.`;
3969
+ }
3970
+ function describeMermaidTree(d) {
3971
+ const tree = { kind: d.kind, nodes: [], edges: [], entryPoints: [], sinks: [] };
3972
+ if (d.body.kind === "flowchart") {
3973
+ const g = d.body.graph;
3974
+ for (const n of g.nodes.values()) tree.nodes.push({ id: n.id, label: n.label || n.id });
3975
+ for (const e of g.edges) tree.edges.push({ from: e.source, to: e.target, label: e.label || void 0 });
3976
+ } else if (d.body.kind === "state") {
3977
+ const visit2 = (states, transitions) => {
3978
+ for (const s of states) {
3979
+ tree.nodes.push({ id: s.id, label: s.label || s.id });
3980
+ if (s.regions) for (const region of s.regions) visit2(region.states, region.transitions);
3981
+ else if (s.states !== void 0) visit2(s.states, s.transitions ?? []);
3982
+ }
3983
+ for (const t of transitions) tree.edges.push({ from: t.from, to: t.to, label: t.label || void 0 });
3984
+ };
3985
+ visit2(d.body.states, d.body.transitions);
3986
+ } else if (d.body.kind === "sequence") {
3987
+ for (const p of d.body.participants) tree.nodes.push({ id: p.id, label: p.label || p.id });
3988
+ sequenceMessageContexts(d.body).forEach((context) => {
3989
+ const m = context.message;
3990
+ tree.edges.push({
3991
+ from: m.from,
3992
+ to: m.to,
3993
+ label: m.text || void 0,
3994
+ ...context.scope === "fragment" ? {
3995
+ sequence: {
3996
+ fragmentIndex: context.fragmentIndex,
3997
+ branchIndex: context.branchIndex,
3998
+ fragmentKind: context.fragmentKind,
3999
+ ...context.branchLabel ?? (context.branchIndex === 0 ? context.fragmentLabel : void 0) ? { branchLabel: context.branchLabel ?? context.fragmentLabel } : {}
4000
+ }
4001
+ } : {}
4002
+ });
4003
+ });
4004
+ } else if (d.body.kind === "class") {
4005
+ for (const c of d.body.classes) tree.nodes.push({ id: c.id, label: c.label || c.id });
4006
+ for (const r of d.body.relations) tree.edges.push({ from: r.from, to: r.to, label: r.label || r.kind });
4007
+ } else if (d.body.kind === "er") {
4008
+ for (const e of d.body.entities) tree.nodes.push({ id: e.id, label: e.label || e.id });
4009
+ for (const r of d.body.relations) tree.edges.push({ from: r.from, to: r.to, label: r.label || void 0 });
4010
+ } else if (d.body.kind === "timeline") {
4011
+ for (const s of d.body.sections) for (const p of s.periods) {
4012
+ tree.nodes.push({ id: p.id, label: p.label });
4013
+ }
4014
+ } else if (d.body.kind === "journey") {
4015
+ for (const s of d.body.sections) for (const t of s.tasks) {
4016
+ const actors = t.actors.length ? `; ${t.actors.join(", ")}` : "";
4017
+ tree.nodes.push({ id: t.id, label: `${t.text} (score ${t.score}${actors})` });
4018
+ }
4019
+ } else if (d.body.kind === "architecture") {
4020
+ for (const g of d.body.groups) tree.nodes.push({ id: g.id, label: g.label || g.id });
4021
+ for (const s of d.body.services) tree.nodes.push({ id: s.id, label: s.label || s.id });
4022
+ for (const j of d.body.junctions) tree.nodes.push({ id: j.id, label: j.id });
4023
+ for (const e of d.body.edges) tree.edges.push({ from: e.source.id, to: e.target.id, label: e.label || void 0 });
4024
+ } else if (d.body.kind === "xychart") {
4025
+ for (const s of d.body.series) tree.nodes.push({ id: s.id, label: s.name || `${s.kind} series` });
4026
+ } else if (d.body.kind === "gantt") {
4027
+ for (const s of d.body.sections) for (const t of s.tasks) {
4028
+ tree.nodes.push({ id: t.taskId ?? t.id, label: t.label });
4029
+ }
4030
+ const idOf = /* @__PURE__ */ new Map();
4031
+ for (const s of d.body.sections) for (const t of s.tasks) if (t.taskId) idOf.set(t.taskId, t.taskId);
4032
+ for (const s of d.body.sections) for (const t of s.tasks) {
4033
+ for (const [expr, label] of [[t.start, "after"], [t.end, "until"]]) {
4034
+ const m = expr?.match(/^(?:after|until)\s+(.+)$/);
4035
+ if (!m || !expr.startsWith(label)) continue;
4036
+ for (const ref of m[1].split(/\s+/).filter(Boolean)) {
4037
+ if (idOf.has(ref)) tree.edges.push({ from: ref, to: t.taskId ?? t.id, label });
4038
+ }
4039
+ }
4040
+ }
4041
+ } else if (d.body.kind === "pie") {
4042
+ for (const sl of d.body.slices) tree.nodes.push({ id: sl.id, label: `${sl.label} (${sl.value})` });
4043
+ } else if (d.body.kind === "quadrant") {
4044
+ d.body.points.forEach((p, i) => tree.nodes.push({ id: `point-${i}`, label: `${p.label} [${p.x}, ${p.y}]` }));
4045
+ } else if (d.body.kind === "mindmap") {
4046
+ const visit2 = (node, parent) => {
4047
+ tree.nodes.push({ id: node.id, label: node.label });
4048
+ if (parent) tree.edges.push({ from: parent, to: node.id });
4049
+ node.children.forEach((child) => visit2(child, node.id));
4050
+ };
4051
+ visit2(d.body.root);
4052
+ } else if (d.body.kind === "gitgraph") {
4053
+ for (const commit of d.body.commits) {
4054
+ tree.nodes.push({ id: commit.id, label: commit.message || commit.id });
4055
+ commit.parents.forEach((parent) => tree.edges.push({ from: parent, to: commit.id, label: commit.source === "commit" ? void 0 : commit.source }));
4056
+ }
4057
+ } else if (d.body.kind === "radar") {
4058
+ d.body.axes.forEach((a, i) => tree.nodes.push({ id: `axis-${i}`, label: a.label }));
4059
+ d.body.curves.forEach((c, i) => tree.nodes.push({ id: `curve-${i}`, label: c.label }));
4060
+ } else if (d.body.kind === "opaque") {
4061
+ const plugin = getFamily(d.kind);
4062
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source);
4063
+ labels.forEach((label, index) => tree.nodes.push({ id: label.target || `label-${index}`, label: label.text }));
4064
+ } else if (d.body.kind === "extension") {
4065
+ const plugin = getFamily(d.kind);
4066
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source);
4067
+ labels.forEach((label, index) => tree.nodes.push({ id: label.target || `label-${index}`, label: label.text }));
4068
+ } else if (d.body.kind === "preserved") {
4069
+ } else {
4070
+ const _never = d.body;
4071
+ void _never;
4072
+ }
4073
+ const incoming = new Set(tree.edges.map((e) => e.to));
4074
+ const outgoing = new Set(tree.edges.map((e) => e.from));
4075
+ for (const n of tree.nodes) {
4076
+ if (!incoming.has(n.id) && outgoing.has(n.id)) tree.entryPoints.push(n.id);
4077
+ if (!outgoing.has(n.id) && incoming.has(n.id)) tree.sinks.push(n.id);
4078
+ }
4079
+ return tree;
4080
+ }
4081
+ function describeMindmap(body) {
4082
+ let count = 0;
4083
+ let leaves = 0;
4084
+ const top = body.root.children.map((node) => node.label);
4085
+ const visit2 = (node) => {
4086
+ count++;
4087
+ if (node.children.length === 0) leaves++;
4088
+ node.children.forEach(visit2);
4089
+ };
4090
+ visit2(body.root);
4091
+ return `A ${count}-node mindmap rooted at ${body.root.label}, with ${leaves} leaves.${top.length ? ` Main branches: ${top.join(", ")}.` : ""}`;
4092
+ }
4093
+ function describeGitGraph(body) {
4094
+ const merges = body.commits.filter((commit) => commit.source === "merge").length;
4095
+ const cherries = body.commits.filter((commit) => commit.source === "cherry-pick").length;
4096
+ return `A GitGraph with ${body.commits.length} commits across ${body.branches.length} branches, including ${merges} merges and ${cherries} cherry-picks. Branches: ${body.branches.map((branch) => branch.name).join(", ")}.`;
4097
+ }
4098
+ function describeFlowchart(d) {
4099
+ const g = d.body.graph;
4100
+ const nodes = Array.from(g.nodes.values());
4101
+ const nodeIds = nodes.map((n) => n.id);
4102
+ const edges = g.edges;
4103
+ const labels = nodes.map((n) => n.label || n.id);
4104
+ const incoming = /* @__PURE__ */ new Map();
4105
+ const outgoing = /* @__PURE__ */ new Map();
4106
+ for (const id of nodeIds) {
4107
+ incoming.set(id, 0);
4108
+ outgoing.set(id, 0);
4109
+ }
4110
+ for (const e of edges) {
4111
+ outgoing.set(e.source, (outgoing.get(e.source) ?? 0) + 1);
4112
+ incoming.set(e.target, (incoming.get(e.target) ?? 0) + 1);
4113
+ }
4114
+ const entries = nodeIds.filter((id) => (incoming.get(id) ?? 0) === 0 && (outgoing.get(id) ?? 0) > 0);
4115
+ const sinks = nodeIds.filter((id) => (outgoing.get(id) ?? 0) === 0 && (incoming.get(id) ?? 0) > 0);
4116
+ const edgeStr = edges.map((e) => {
4117
+ const lbl = e.label ? ` (${e.label})` : "";
4118
+ return `${e.source} -> ${e.target}${lbl}`;
4119
+ });
4120
+ let s = `A ${nodes.length}-node flowchart with ${edges.length} edges. Nodes: ${labels.join(", ")}.`;
4121
+ if (edgeStr.length > 0) s += ` Edges: ${edgeStr.join("; ")}.`;
4122
+ if (entries.length > 0) s += ` Entry points: ${entries.join(", ")}.`;
4123
+ if (sinks.length > 0) s += ` Sinks: ${sinks.join(", ")}.`;
4124
+ return s;
4125
+ }
4126
+ function describeState(body) {
4127
+ const flat = [];
4128
+ const allTransitions = [];
4129
+ const collect = (states, transitions) => {
4130
+ for (const s2 of states) {
4131
+ flat.push(s2);
4132
+ if (s2.regions) for (const region of s2.regions) collect(region.states, region.transitions);
4133
+ else if (s2.states !== void 0) collect(s2.states, s2.transitions ?? []);
4134
+ }
4135
+ allTransitions.push(...transitions);
4136
+ };
4137
+ collect(body.states, body.transitions);
4138
+ const composites = flat.filter((s2) => s2.states !== void 0 || s2.regions !== void 0);
4139
+ const stateLabels = flat.map((s2) => s2.label || s2.id);
4140
+ const transStr = allTransitions.map((t) => {
4141
+ const lbl = t.label ? ` (${t.label})` : "";
4142
+ return `${t.from} -> ${t.to}${lbl}`;
4143
+ });
4144
+ const starts = body.transitions.filter((t) => t.from === "[*]").map((t) => t.to);
4145
+ let s = `A state diagram with ${flat.length} states and ${allTransitions.length} transitions.`;
4146
+ if (composites.length > 0) s += ` Composite states: ${composites.map((c) => c.label || c.id).join(", ")}.`;
4147
+ if (stateLabels.length > 0) s += ` States: ${stateLabels.join(", ")}.`;
4148
+ const stereotypes = flat.filter((state) => state.stereotype).map((state) => `${state.label || state.id} (${state.stereotype})`);
4149
+ if (stereotypes.length > 0) s += ` Pseudostates: ${stereotypes.join(", ")}.`;
4150
+ if ((body.notes ?? []).length > 0) {
4151
+ s += ` Notes: ${body.notes.map((note) => `${note.side} of ${note.target}: ${note.text}`).join("; ")}.`;
4152
+ }
4153
+ if (transStr.length > 0) s += ` Transitions: ${transStr.join("; ")}.`;
4154
+ if (starts.length > 0) s += ` Initial: ${starts.join(", ")}.`;
4155
+ return s;
4156
+ }
4157
+ function describeSequence(d) {
4158
+ const parts = d.body.participants;
4159
+ const partStr = parts.map((p) => p.label || p.id).join(", ");
4160
+ let s = `A sequence diagram between ${partStr || "(no participants)"}.`;
4161
+ const interactions = [];
4162
+ let fragmentIndex = 0;
4163
+ for (const statement of d.body.statements) {
4164
+ if (statement.kind === "message") {
4165
+ const message = d.body.messages[statement.ref];
4166
+ if (message) interactions.push(`message ${message.from} -> ${message.to}: ${message.text}`);
4167
+ continue;
4168
+ }
4169
+ if (statement.kind !== "fragment") continue;
4170
+ const branches = statement.fragment.branches.map((branch, branchIndex) => {
4171
+ const label = branch.label ?? (branchIndex === 0 ? statement.fragment.label : void 0);
4172
+ const messages = branch.messages.map((m) => `${m.from} -> ${m.to}: ${m.text}`).join("; ") || "no messages";
4173
+ return `branch ${branchIndex}${label ? ` (${label})` : ""}: ${messages}`;
4174
+ });
4175
+ interactions.push(`fragment ${fragmentIndex} (${statement.fragment.fragmentKind}): ${branches.join(" | ")}`);
4176
+ fragmentIndex++;
4177
+ }
4178
+ if (interactions.length > 0) s += ` Interactions in source order: ${interactions.join("; ")}.`;
4179
+ return s;
4180
+ }
4181
+ function describeTimeline(d) {
4182
+ const sections = d.body.sections;
4183
+ const sectionLabels = sections.map((s2) => s2.label || s2.id);
4184
+ const periodLabels = [];
4185
+ for (const s2 of sections) for (const p of s2.periods) periodLabels.push(p.label);
4186
+ let s = `A timeline with ${sections.length} sections.`;
4187
+ if (sectionLabels.length > 0) s += ` Sections: ${sectionLabels.join(", ")}.`;
4188
+ if (periodLabels.length > 0) s += ` Periods: ${periodLabels.join(", ")}.`;
4189
+ return s;
4190
+ }
4191
+ function describeJourney(body) {
4192
+ const sections = body.sections;
4193
+ const taskCount = sections.reduce((n, s2) => n + s2.tasks.length, 0);
4194
+ const actors = [...new Set(sections.flatMap((s2) => s2.tasks.flatMap((t) => t.actors)))];
4195
+ let s = `A user journey${body.title ? ` titled "${body.title}"` : ""} with ${sections.length} sections and ${taskCount} tasks.`;
4196
+ const sectionLabels = sections.map((sec) => sec.label).filter((l) => l !== void 0);
4197
+ if (sectionLabels.length > 0) s += ` Sections: ${sectionLabels.join(", ")}.`;
4198
+ const taskStr = sections.flatMap((sec) => sec.tasks.map((t) => `${t.text} (${t.score})`));
4199
+ if (taskStr.length > 0) s += ` Tasks: ${taskStr.join(", ")}.`;
4200
+ if (actors.length > 0) s += ` Actors: ${actors.join(", ")}.`;
4201
+ return s;
4202
+ }
4203
+ function describeArchitecture(body) {
4204
+ const groups = body.groups;
4205
+ const services = body.services;
4206
+ const junctions = body.junctions;
4207
+ const edges = body.edges;
4208
+ let s = `An architecture diagram${body.title ? ` titled "${body.title}"` : ""} with ${groups.length} groups, ${services.length} services, and ${edges.length} connections.`;
4209
+ if (body.accessibilityTitle) s += ` Accessible title: ${body.accessibilityTitle}.`;
4210
+ if (body.accessibilityDescription) s += ` Accessible description: ${body.accessibilityDescription.replace(/\n/g, " ")}.`;
4211
+ if (groups.length > 0) s += ` Groups: ${groups.map((g) => g.label || g.id).join(", ")}.`;
4212
+ if (services.length > 0) s += ` Services: ${services.map((sv) => sv.label || sv.id).join(", ")}.`;
4213
+ if (junctions.length > 0) s += ` Junctions: ${junctions.map((j) => j.id).join(", ")}.`;
4214
+ const edgeStr = edges.map((e) => `${e.source.id} -> ${e.target.id}${e.label ? ` (${e.label})` : ""}`);
4215
+ if (edgeStr.length > 0) s += ` Connections: ${edgeStr.join("; ")}.`;
4216
+ return s;
4217
+ }
4218
+ function describeXyChart(body) {
4219
+ const series = body.series;
4220
+ const orientation = body.horizontal ? "horizontal " : "";
4221
+ let s = `An ${orientation}XY chart${body.title ? ` titled "${body.title}"` : ""} with ${series.length} series.`;
4222
+ const axisDesc = (label, axis) => {
4223
+ if (!axis) return void 0;
4224
+ const parts = [];
4225
+ if (axis.name !== void 0) parts.push(`"${axis.name}"`);
4226
+ if (axis.categories) parts.push(`categories ${axis.categories.join(", ")}`);
4227
+ if (axis.range) parts.push(`range ${axis.range.min}\u2013${axis.range.max}`);
4228
+ return parts.length > 0 ? `${label}: ${parts.join(", ")}` : void 0;
4229
+ };
4230
+ const axes = [axisDesc("x-axis", body.xAxis), axisDesc("y-axis", body.yAxis)].filter((a) => a !== void 0);
4231
+ if (axes.length > 0) s += ` Axes \u2014 ${axes.join("; ")}.`;
4232
+ const seriesStr = series.map((se) => `${se.kind}${se.name ? ` ${se.name}` : ""} [${se.values.join(", ")}]`);
4233
+ if (seriesStr.length > 0) s += ` Series: ${seriesStr.join("; ")}.`;
4234
+ return s;
4235
+ }
4236
+ function describePie(body) {
4237
+ let s = `A pie chart${body.title ? ` titled "${body.title}"` : ""} with ${body.slices.length} slices.`;
4238
+ const sliceStr = body.slices.map((sl) => `${sl.label} (${sl.value})`);
4239
+ if (sliceStr.length > 0) s += ` Slices: ${sliceStr.join(", ")}.`;
4240
+ return s;
4241
+ }
4242
+ function describeQuadrant(body) {
4243
+ let s = `A quadrant chart${body.title ? ` titled "${body.title}"` : ""} with ${body.points.length} points.`;
4244
+ const regions = body.quadrants.filter((q) => Boolean(q));
4245
+ if (regions.length > 0) s += ` Quadrants: ${regions.join(", ")}.`;
4246
+ const pointStr = body.points.map((p) => `${p.label} [${p.x}, ${p.y}]`);
4247
+ if (pointStr.length > 0) s += ` Points: ${pointStr.join(", ")}.`;
4248
+ return s;
4249
+ }
4250
+ function describeClass(d) {
4251
+ const classes = d.body.classes;
4252
+ const relations = d.body.relations;
4253
+ const names = classes.map((c) => c.generic ? `${c.id}<${c.generic}>` : c.id);
4254
+ const relStr = relations.map((r) => `${r.from} ${r.kind} ${r.to}`);
4255
+ let s = `A class diagram with ${classes.length} classes.`;
4256
+ if (names.length > 0) s += ` Classes: ${names.join(", ")}.`;
4257
+ const namespaces = d.body.namespaces ?? [];
4258
+ if (namespaces.length > 0) {
4259
+ const nsStr = namespaces.map((ns) => {
4260
+ const members = classes.filter((c) => c.namespace === ns.name).map((c) => c.id);
4261
+ return `${ns.name} [${members.join(", ")}]`;
4262
+ });
4263
+ s += ` Namespaces: ${nsStr.join("; ")}.`;
4264
+ }
4265
+ if (relStr.length > 0) s += ` Relations: ${relStr.join("; ")}.`;
4266
+ return s;
4267
+ }
4268
+ function describeEr(d) {
4269
+ const entities = d.body.entities;
4270
+ const relations = d.body.relations;
4271
+ const names = entities.map((e) => e.label ? `${e.id} ("${e.label}")` : e.id);
4272
+ const relStr = relations.map((r) => {
4273
+ const lbl = r.label ? ` (${r.label})` : "";
4274
+ return `${r.from} ${r.leftCard}-${r.rightCard} ${r.to}${lbl}`;
4275
+ });
4276
+ let s = `An ER diagram with ${entities.length} entities.`;
4277
+ if (names.length > 0) s += ` Entities: ${names.join(", ")}.`;
4278
+ if (relStr.length > 0) s += ` Relations: ${relStr.join("; ")}.`;
4279
+ return s;
4280
+ }
4281
+ function describeGantt(d) {
4282
+ const body = d.body;
4283
+ const tasks = body.sections.flatMap((s2) => s2.tasks);
4284
+ let s = `A Gantt chart${body.title ? ` titled "${body.title}"` : ""} with ${body.sections.filter((sec) => sec.label !== void 0).length} sections and ${tasks.length} tasks.`;
4285
+ try {
4286
+ const schedule = resolveGanttSchedule(parseGanttModel(toMermaidLines(d.canonicalSource)));
4287
+ const fmt = schedule.dateOnly ? "%Y-%m-%d" : "%Y-%m-%d %H:%M";
4288
+ s += ` Schedule: ${formatGanttInstant(schedule.timeMin, fmt)} to ${formatGanttInstant(schedule.timeMax, fmt)}.`;
4289
+ if (schedule.analysis && schedule.analysis.criticalPathTaskIds.length > 0) {
4290
+ s += ` Critical path: ${schedule.analysis.criticalPathTaskIds.join(" -> ")}.`;
4291
+ }
4292
+ } catch {
4293
+ }
4294
+ const sectionLabels = body.sections.map((sec) => sec.label).filter((l) => l !== void 0);
4295
+ if (sectionLabels.length > 0) s += ` Sections: ${sectionLabels.join(", ")}.`;
4296
+ const taskStr = tasks.map((t) => {
4297
+ const bits = [];
4298
+ const status = t.tags.find((tag) => tag === "done" || tag === "active" || tag === "crit");
4299
+ if (status) bits.push(status);
4300
+ if (t.tags.includes("milestone")) bits.push("milestone");
4301
+ if (t.tags.includes("vert")) bits.push("vert marker");
4302
+ if (t.start) bits.push(t.start);
4303
+ bits.push(t.end);
4304
+ return `${t.label} (${bits.join(", ")})`;
4305
+ });
4306
+ if (taskStr.length > 0) s += ` Tasks: ${taskStr.join("; ")}.`;
4307
+ return s;
4308
+ }
4309
+ function describeOpaque(kind, source) {
4310
+ const plugin = getFamily(kind);
4311
+ const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(source).map((label) => label.text);
4312
+ const unique2 = Array.from(new Set(labels)).filter(Boolean);
4313
+ const family = kind === "xychart" ? "XY chart" : kind;
4314
+ let s = `A ${family} diagram with a source-level body (structured editing not exposed).`;
4315
+ if (unique2.length > 0) s += ` Labels: ${unique2.join(", ")}.`;
4316
+ return s;
4317
+ }
4318
+
4319
+ // src/ascii/reverse.ts
4320
+ var TL = /* @__PURE__ */ new Set(["\u250C", "\u256D"]);
4321
+ var TR = /* @__PURE__ */ new Set(["\u2510", "\u256E"]);
4322
+ var ARROW_R = /* @__PURE__ */ new Set(["\u25BA", "\u25B6", ">"]);
4323
+ var ARROW_L = /* @__PURE__ */ new Set(["\u25C4", "\u25C0", "<"]);
4324
+ var ARROW_D = /* @__PURE__ */ new Set(["\u25BC", "v"]);
4325
+ var ARROW_U = /* @__PURE__ */ new Set(["\u25B2", "^"]);
4326
+ function detectBoxes(rows) {
4327
+ const boxes = [];
4328
+ let counter = 0;
4329
+ for (let y = 0; y < rows.length; y++) {
4330
+ const row = rows[y];
4331
+ for (let x = 0; x < row.length; x++) {
4332
+ const ch = row[x];
4333
+ const isCorner = TL.has(ch) || ch === "+" && isAsciiTopLeft(rows, x, y);
4334
+ if (!isCorner) continue;
4335
+ const box = traceBox(rows, x, y);
4336
+ if (box) {
4337
+ box.id = `N${counter++}`;
4338
+ boxes.push(box);
4339
+ }
4340
+ }
4341
+ }
4342
+ return boxes;
4343
+ }
4344
+ function isAsciiTopLeft(rows, x, y) {
4345
+ const right = rows[y]?.[x + 1];
4346
+ const below = rows[y + 1]?.[x];
4347
+ return right === "-" && below === "|";
4348
+ }
4349
+ function traceBox(rows, x0, y0) {
4350
+ const topRow = rows[y0];
4351
+ let x1 = -1;
4352
+ for (let x = x0 + 1; x < topRow.length; x++) {
4353
+ const ch = topRow[x];
4354
+ if (TR.has(ch) || ch === "+") {
4355
+ x1 = x;
4356
+ break;
4357
+ }
4358
+ if (ch !== "\u2500" && ch !== "-") break;
4359
+ }
4360
+ if (x1 < 0) return null;
4361
+ let y1 = -1;
4362
+ for (let y = y0 + 1; y < rows.length; y++) {
4363
+ const ch = rows[y]?.[x0];
4364
+ if (ch === "\u2514" || ch === "\u2570" || ch === "+") {
4365
+ y1 = y;
4366
+ break;
4367
+ }
4368
+ if (ch !== "\u2502" && ch !== "|") break;
4369
+ }
4370
+ if (y1 < 0) return null;
4371
+ const labelParts = [];
4372
+ for (let y = y0 + 1; y < y1; y++) {
4373
+ const seg = (rows[y] ?? "").slice(x0 + 1, x1).replace(/[│|]/g, " ").trim();
4374
+ if (seg) labelParts.push(seg);
4375
+ }
4376
+ return { id: "", label: labelParts.join(" ").trim(), top: y0, left: x0, bottom: y1, right: x1 };
4377
+ }
4378
+ function boxAt(boxes, x, y, slack = 1) {
4379
+ for (const b of boxes) {
4380
+ if (x >= b.left - slack && x <= b.right + slack && y >= b.top - slack && y <= b.bottom + slack) return b;
4381
+ }
4382
+ return null;
4383
+ }
4384
+ function detectEdges(rows, boxes) {
4385
+ const edges = [];
4386
+ const seen = /* @__PURE__ */ new Set();
4387
+ for (let y = 0; y < rows.length; y++) {
4388
+ const row = rows[y];
4389
+ for (let x = 0; x < row.length; x++) {
4390
+ const ch = row[x];
4391
+ let target = null;
4392
+ let source = null;
4393
+ if (ARROW_R.has(ch)) {
4394
+ target = boxAt(boxes, x + 1, y);
4395
+ source = walkLeft(rows, boxes, x, y);
4396
+ } else if (ARROW_L.has(ch)) {
4397
+ target = boxAt(boxes, x - 1, y);
4398
+ source = walkRight(rows, boxes, x, y);
4399
+ } else if (ARROW_D.has(ch)) {
4400
+ target = boxAt(boxes, x, y + 1);
4401
+ source = walkUp(rows, boxes, x, y);
4402
+ } else if (ARROW_U.has(ch)) {
4403
+ target = boxAt(boxes, x, y - 1);
4404
+ source = walkDown(rows, boxes, x, y);
4405
+ }
4406
+ if (source && target && source.id !== target.id) {
4407
+ const key = `${source.id}->${target.id}`;
4408
+ if (!seen.has(key)) {
4409
+ seen.add(key);
4410
+ edges.push([source.id, target.id]);
4411
+ }
4412
+ }
4413
+ }
4414
+ }
4415
+ return edges;
4416
+ }
4417
+ var HLINE = /* @__PURE__ */ new Set(["\u2500", "-", "\u252C", "\u2534", "\u253C", "\u251C", "\u2524", "+"]);
4418
+ var VLINE = /* @__PURE__ */ new Set(["\u2502", "|", "\u252C", "\u2534", "\u253C", "\u251C", "\u2524", "+"]);
4419
+ function walkLeft(rows, boxes, x, y) {
4420
+ for (let cx = x - 1; cx >= 0; cx--) {
4421
+ const b = boxAt(boxes, cx, y);
4422
+ if (b) return b;
4423
+ if (!HLINE.has(rows[y]?.[cx] ?? "")) {
4424
+ const up = boxAt(boxes, cx, y - 1), dn = boxAt(boxes, cx, y + 1);
4425
+ return up ?? dn ?? null;
4426
+ }
4427
+ }
4428
+ return null;
4429
+ }
4430
+ function walkRight(rows, boxes, x, y) {
4431
+ for (let cx = x + 1; cx < (rows[y]?.length ?? 0); cx++) {
4432
+ const b = boxAt(boxes, cx, y);
4433
+ if (b) return b;
4434
+ if (!HLINE.has(rows[y]?.[cx] ?? "")) break;
4435
+ }
4436
+ return null;
4437
+ }
4438
+ function walkUp(rows, boxes, x, y) {
4439
+ let cx = x;
4440
+ for (let cy = y - 1; cy >= 0; cy--) {
4441
+ const b = boxAt(boxes, cx, cy);
4442
+ if (b) return b;
4443
+ const ch = rows[cy]?.[cx] ?? "";
4444
+ const isJunction = "\u252C\u2534\u253C\u251C\u2524\u2510\u250C\u256E\u256D".includes(ch);
4445
+ if (isJunction && !VLINE.has(rows[cy - 1]?.[cx] ?? "")) {
4446
+ const hop = hopToTrunk(rows, cx, cy);
4447
+ if (hop === null) {
4448
+ const left2 = boxAt(boxes, cx - 1, cy), right2 = boxAt(boxes, cx + 1, cy);
4449
+ return left2 ?? right2 ?? null;
4450
+ }
4451
+ cx = hop;
4452
+ continue;
4453
+ }
4454
+ if (VLINE.has(ch)) continue;
4455
+ if (HLINE.has(ch)) {
4456
+ const hop = hopToTrunk(rows, cx, cy);
4457
+ if (hop !== null) {
4458
+ cx = hop;
4459
+ continue;
4460
+ }
4461
+ const left2 = boxAt(boxes, cx - 1, cy), right2 = boxAt(boxes, cx + 1, cy);
4462
+ return left2 ?? right2 ?? null;
4463
+ }
4464
+ const left = boxAt(boxes, cx - 1, cy), right = boxAt(boxes, cx + 1, cy);
4465
+ return left ?? right ?? null;
4466
+ }
4467
+ return null;
4468
+ }
4469
+ function hopToTrunk(rows, x, y) {
4470
+ const above = (cx) => rows[y - 1]?.[cx] ?? "";
4471
+ for (let d = 0; d < 200; d++) {
4472
+ for (const cx of [x - d, x + d]) {
4473
+ const here = rows[y]?.[cx] ?? "";
4474
+ if (!HLINE.has(here) && !"\u2510\u250C\u256E\u256D".includes(here)) continue;
4475
+ if (VLINE.has(above(cx))) return cx;
4476
+ }
4477
+ }
4478
+ return null;
4479
+ }
4480
+ function walkDown(rows, boxes, x, y) {
4481
+ for (let cy = y + 1; cy < rows.length; cy++) {
4482
+ const b = boxAt(boxes, x, cy);
4483
+ if (b) return b;
4484
+ if (!VLINE.has(rows[cy]?.[x] ?? "")) break;
4485
+ }
4486
+ return null;
4487
+ }
4488
+ function escapeLabel(label) {
4489
+ return /["[\]{}()]/.test(label) ? `["${label.replace(/"/g, "&quot;")}"]` : `[${label}]`;
4490
+ }
4491
+ function asciiToMermaid(ascii, opts = {}) {
4492
+ if (!ascii.trim()) return err([{ code: "EMPTY", message: "empty ASCII input" }]);
4493
+ const rows = ascii.replace(/\[[0-9;]*m/g, "").split(/\r?\n/);
4494
+ const boxes = detectBoxes(rows);
4495
+ if (boxes.length === 0) return err([{ code: "NO_BOXES", message: "no boxes detected in ASCII; not a recognized flowchart render" }]);
4496
+ const edges = detectEdges(rows, boxes);
4497
+ const dir = opts.direction ?? "LR";
4498
+ const lines = [`flowchart ${dir}`];
4499
+ for (const b of boxes) lines.push(` ${b.id}${escapeLabel(b.label || b.id)}`);
4500
+ for (const [from, to] of edges) lines.push(` ${from} --> ${to}`);
4501
+ return ok(lines.join("\n"));
4502
+ }
4503
+
4504
+ // src/agent/core.ts
4505
+ function renderMermaidSVG2(input, opts = {}) {
4506
+ return renderMermaidSVG(input, opts);
4507
+ }
4508
+ function renderMermaidSVGWithReceipt2(input, opts = {}) {
4509
+ return renderMermaidSVGWithReceipt(input, opts);
4510
+ }
4511
+ function renderMermaidASCII2(input, opts = {}) {
4512
+ return renderMermaidASCII(input, opts);
4513
+ }
4514
+ function renderMermaidASCIIWithReceipt2(input, opts = {}) {
4515
+ return renderMermaidASCIIWithReceipt(input, opts);
4516
+ }
4517
+ function layoutMermaidWithReceipt(input, opts = {}) {
4518
+ const { debug, regions, actions, ...renderOptions } = opts;
4519
+ const layoutOptions = { debug, regions, actions };
4520
+ const preparedInput = prepareRenderInput(input);
4521
+ const source = preparedInput.source;
4522
+ const request = resolveRenderRequestForExecution(source, renderOptions, "layout", layoutOptions, {
4523
+ expectedFamilyId: preparedInput.expectedFamilyId
4524
+ });
4525
+ emitResolvedConfigDiagnostics(request);
4526
+ let diagram;
4527
+ if (typeof input === "string") {
4528
+ const parsed = parseRegisteredMermaid(source);
4529
+ if (!parsed.ok) throw new Error(parsed.error.map((error) => error.message).join("; "));
4530
+ diagram = parsed.value;
4531
+ } else {
4532
+ diagram = input;
4533
+ }
4534
+ const familyLayout = layoutResolvedFamilyToRendered(diagram, request, { debug });
4535
+ if (!familyLayout) throw new Error(`No public layout projection registered for Mermaid family "${diagram.kind}"`);
4536
+ return {
4537
+ layout: enrichRenderedLayout(diagram, familyLayout, layoutOptions),
4538
+ receipt: receiptOf(request)
4539
+ };
4540
+ }
4541
+ function layoutMermaid(d, opts = {}) {
4542
+ if (d.body.kind === "preserved") {
4543
+ throw new MermaidFamilyDetectionError(familyDetectionDiagnosticFromPreservedBody(d.body));
4544
+ }
4545
+ return layoutMermaidWithReceipt(d, opts).layout;
4546
+ }
4547
+ function enrichRenderedLayout(d, layout, opts) {
4548
+ const wantRegions = opts.debug || opts.regions;
4549
+ const wantActions = opts.debug || opts.actions;
4550
+ if (!wantRegions && !wantActions) return layout;
4551
+ const next = { ...layout };
4552
+ if (wantRegions) next.regions = buildRenderedRegions(d, layout);
4553
+ if (wantActions) {
4554
+ const nodeIds = new Set(layout.nodes.map((n) => n.id));
4555
+ next.actions = (d.body.kind === "extension" || d.body.kind === "preserved" ? [] : collectActionRecords(d)).filter((a) => nodeIds.has(a.target)).map((a) => ({ ...a, regionId: a.regionId ?? `node:${a.target}` }));
4556
+ }
4557
+ return next;
4558
+ }
4559
+ function buildRenderedRegions(d, layout) {
4560
+ const regions = [{
4561
+ id: "canvas",
4562
+ kind: "canvas",
4563
+ bounds: { x: toFinite(0), y: toFinite(0), w: layout.bounds.w, h: layout.bounds.h }
4564
+ }];
4565
+ const groupByMember = /* @__PURE__ */ new Map();
4566
+ for (const group of layout.groups) for (const member of group.members) groupByMember.set(member, group.id);
4567
+ const sourceLines = sourceLineHints(d);
4568
+ for (const group of layout.groups) {
4569
+ regions.push({
4570
+ id: `group:${group.id}`,
4571
+ kind: group.regionKind ?? renderedGroupRegionKind(layout.kind),
4572
+ elementId: group.id,
4573
+ parentId: group.parentId ? `group:${group.parentId}` : "canvas",
4574
+ bounds: { x: group.x, y: group.y, w: group.w, h: group.h },
4575
+ sourceLine: d.source.groups.get(group.id)?.line ?? sourceLines.groups.get(group.id)
4576
+ });
4577
+ }
4578
+ for (const node of layout.nodes) {
4579
+ regions.push({
4580
+ id: `node:${node.id}`,
4581
+ kind: "node",
4582
+ elementId: node.id,
4583
+ parentId: groupByMember.has(node.id) ? `group:${groupByMember.get(node.id)}` : "canvas",
4584
+ bounds: { x: node.x, y: node.y, w: node.w, h: node.h },
4585
+ sourceLine: d.source.nodes.get(node.id)?.line ?? sourceLines.nodes.get(node.id)
4586
+ });
4587
+ }
4588
+ for (const edge of layout.edges) {
4589
+ const xs = edge.path.map((p) => p[0]);
4590
+ const ys = edge.path.map((p) => p[1]);
4591
+ if (xs.length > 0 && ys.length > 0) {
4592
+ const minX = Math.min(...xs), maxX = Math.max(...xs), minY = Math.min(...ys), maxY = Math.max(...ys);
4593
+ regions.push({
4594
+ id: `edge:${edge.id}`,
4595
+ kind: "edge",
4596
+ elementId: edge.id,
4597
+ parentId: "canvas",
4598
+ bounds: { x: toFinite(minX), y: toFinite(minY), w: toFinite(Math.max(1, maxX - minX)), h: toFinite(Math.max(1, maxY - minY)) }
4599
+ });
4600
+ }
4601
+ if (edge.label) {
4602
+ const w = Math.max(1, edge.label.text.length * 7);
4603
+ const h = 14;
4604
+ const labelX = d.kind === "sequence" && edge.from === edge.to ? edge.label.x : edge.label.x - w / 2;
4605
+ regions.push({
4606
+ id: `label:${edge.id}`,
4607
+ kind: "label",
4608
+ elementId: edge.id,
4609
+ parentId: `edge:${edge.id}`,
4610
+ bounds: { x: toFinite(labelX), y: toFinite(edge.label.y - h / 2), w: toFinite(w), h: toFinite(h) }
4611
+ });
4612
+ }
4613
+ }
4614
+ return regions;
4615
+ }
4616
+ function renderedGroupRegionKind(kind) {
4617
+ switch (kind) {
4618
+ case "flowchart":
4619
+ case "state":
4620
+ case "class":
4621
+ case "er":
4622
+ case "architecture":
4623
+ return "cluster";
4624
+ case "gitgraph":
4625
+ return "lane";
4626
+ case "timeline":
4627
+ case "journey":
4628
+ case "gantt":
4629
+ return "band";
4630
+ case "sequence":
4631
+ case "quadrant":
4632
+ return "compartment";
4633
+ case "xychart":
4634
+ return "plot";
4635
+ case "radar":
4636
+ return "ring";
4637
+ default:
4638
+ return "group";
4639
+ }
4640
+ }
4641
+ function sourceLineHints(d) {
4642
+ const nodes = /* @__PURE__ */ new Map();
4643
+ const groups = /* @__PURE__ */ new Map();
4644
+ const source = d.body.kind === "opaque" || d.body.kind === "extension" ? d.body.source : d.canonicalSource;
4645
+ const lines = source.split(/\r?\n/);
4646
+ for (let i = 0; i < lines.length; i++) {
4647
+ const text = lines[i];
4648
+ const group = text.match(/^\s*subgraph\s+([A-Za-z0-9_:-]+)/);
4649
+ if (group?.[1] && !groups.has(group[1])) groups.set(group[1], i + 1);
4650
+ for (const node of text.matchAll(/(?:^|[\s&;])([A-Za-z][A-Za-z0-9_:-]*)(?=\s*(?:\[|\(|\{|--|==|-.|$))/g)) {
4651
+ const id = node[1];
4652
+ if (id === "subgraph" || id === "end" || id === d.kind) continue;
4653
+ if (!nodes.has(id)) nodes.set(id, i + 1);
4654
+ }
4655
+ }
4656
+ return { nodes, groups };
4657
+ }
4658
+
4659
+ export {
4660
+ logToolInvocation,
4661
+ hasOpSchema,
4662
+ validateOp,
4663
+ opMenu,
4664
+ describeOps,
4665
+ opSignatures,
4666
+ mutate,
4667
+ mutateChecked,
4668
+ createMermaid,
4669
+ buildMermaid,
4670
+ verifyMermaid,
4671
+ configWarningsForMermaid,
4672
+ verifySummary,
4673
+ buildChecked,
4674
+ applyOps,
4675
+ DEFAULT_BOUNDS,
4676
+ BOUND_PROVENANCE,
4677
+ measureQuality,
4678
+ checkQuality,
4679
+ layoutCertificateProof,
4680
+ FamilyConformanceError,
4681
+ registerFamily,
4682
+ analyzeMermaid,
4683
+ analyzeMermaidSource,
4684
+ collectActionRecords,
4685
+ ASCII_ROUTE_PARITY_CONTRACT,
4686
+ renderMermaidASCIIWithMeta,
4687
+ describeMermaidFacts,
4688
+ describeMermaidFactsSource,
4689
+ checkMermaid,
4690
+ checkMermaidSource,
4691
+ describeMermaidSource,
4692
+ describeMermaid,
4693
+ describeMermaidTree,
4694
+ asciiToMermaid,
4695
+ renderMermaidSVG2 as renderMermaidSVG,
4696
+ renderMermaidSVGWithReceipt2 as renderMermaidSVGWithReceipt,
4697
+ renderMermaidASCII2 as renderMermaidASCII,
4698
+ renderMermaidASCIIWithReceipt2 as renderMermaidASCIIWithReceipt,
4699
+ layoutMermaidWithReceipt,
4700
+ layoutMermaid
4701
+ };
4702
+ //# sourceMappingURL=chunk-4QECACIA.js.map