agentic-mermaid 0.1.0 → 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 (333) hide show
  1. package/CHANGELOG.md +149 -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/assets/fonts/FONT-LICENSES.md +9 -5
  6. package/assets/fonts/Inter-Bold.ttf +0 -0
  7. package/assets/fonts/Inter-Medium.ttf +0 -0
  8. package/assets/fonts/Inter-Regular.ttf +0 -0
  9. package/assets/fonts/Inter-SemiBold.ttf +0 -0
  10. package/dist/agent-core.d.ts +463 -0
  11. package/dist/agent-core.js +397 -0
  12. package/dist/agent.d.ts +81 -1822
  13. package/dist/agent.js +336 -36839
  14. package/dist/agentic-mermaid-mcp.js +7 -37908
  15. package/dist/am.js +33638 -36261
  16. package/dist/chunk-4QECACIA.js +4702 -0
  17. package/dist/chunk-CDPKJ2BQ.js +37 -0
  18. package/dist/chunk-QKDLMOJJ.js +3487 -0
  19. package/dist/chunk-WI35L7IR.js +615 -0
  20. package/dist/chunk-WN3YDOBG.js +149 -0
  21. package/dist/chunk-ZYMMKQ3I.js +62637 -0
  22. package/dist/index-D54dqSdC.d.ts +6777 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +310 -32836
  25. package/dist/mcp-cli-SBQSTJFC.js +17 -0
  26. package/docs/README.md +14 -6
  27. package/docs/schemas/style-spec.schema.json +1221 -30
  28. package/examples/styles/catalog.json +54 -0
  29. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  30. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  31. package/examples/styles/cupertino-prototype.mmd +10 -0
  32. package/examples/styles/cupertino-prototype.style.json +63 -0
  33. package/examples/styles/mid-century-report.style.json +2 -2
  34. package/examples/styles/star-chart-atlas.style.json +2 -1
  35. package/examples/styles/transit-route-map.style.json +2 -2
  36. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  37. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  38. package/package.json +70 -43
  39. package/server.json +41 -0
  40. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  41. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  42. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  43. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  44. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  45. package/AGENT_NATIVE.md +0 -602
  46. package/Instructions_for_agents.md +0 -83
  47. package/TODO.md +0 -662
  48. package/assets/hero.png +0 -0
  49. package/dist/index-x2Cj1dVv.d.ts +0 -1089
  50. package/docs/agent-api-cookbook.md +0 -308
  51. package/docs/agent-mutation-policy.md +0 -104
  52. package/docs/agent-workflow-examples.md +0 -46
  53. package/docs/api.md +0 -203
  54. package/docs/ascii.md +0 -103
  55. package/docs/assets/improvements/box-start-connector.png +0 -0
  56. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  57. package/docs/assets/improvements/er-content-loss.png +0 -0
  58. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  59. package/docs/assets/improvements/gantt-family.png +0 -0
  60. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  61. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  62. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  63. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  64. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  65. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  66. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  67. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  68. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  69. package/docs/comparison.md +0 -94
  70. package/docs/config.md +0 -105
  71. package/docs/contributing/adding-diagram-types.md +0 -144
  72. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  73. package/docs/contributing/diagram-family-citizenship.md +0 -82
  74. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  75. package/docs/contributing/lessons-learned.md +0 -276
  76. package/docs/contributing/releasing.md +0 -71
  77. package/docs/contributing/visual-review-evidence.md +0 -53
  78. package/docs/custom-style-cookbook.md +0 -154
  79. package/docs/design/families/README.md +0 -26
  80. package/docs/design/families/architecture-beta.md +0 -48
  81. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  82. package/docs/design/families/gantt-research.md +0 -137
  83. package/docs/design/families/gantt.md +0 -329
  84. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  85. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  86. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  87. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  88. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  89. package/docs/design/families/journey-migration-parity.md +0 -433
  90. package/docs/design/families/journey-usage-research.md +0 -423
  91. package/docs/design/families/journey.md +0 -125
  92. package/docs/design/families/xychart.md +0 -85
  93. package/docs/design/system/README.md +0 -45
  94. package/docs/design/system/abstraction-audit.md +0 -224
  95. package/docs/design/system/abstraction-recommendations.md +0 -655
  96. package/docs/design/system/architecture.mmd +0 -29
  97. package/docs/design/system/architecture.svg +0 -137
  98. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  99. package/docs/design/system/layout-rubric.md +0 -138
  100. package/docs/design/system/route-contracts.md +0 -848
  101. package/docs/design/system/source-preservation-ladder.md +0 -52
  102. package/docs/design/system/styles-rollout.md +0 -216
  103. package/docs/design/system/ugly-layouts.md +0 -119
  104. package/docs/diagram-families.md +0 -193
  105. package/docs/features.md +0 -147
  106. package/docs/fork-differences.md +0 -130
  107. package/docs/getting-started.md +0 -175
  108. package/docs/issue-derived-test-cases.md +0 -156
  109. package/docs/layout-characterization/README.md +0 -245
  110. package/docs/layout-characterization/contact-sheet-families.md +0 -496
  111. package/docs/layout-characterization/contact-sheet.md +0 -638
  112. package/docs/layout-characterization/properties.md +0 -281
  113. package/docs/layout-characterization/visual-quality.md +0 -160
  114. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  115. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  116. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  117. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  118. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  119. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -118
  120. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  121. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  122. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  123. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  124. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  125. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  126. package/docs/mcp-code-mode-rationale.md +0 -101
  127. package/docs/mcp-http-transport.md +0 -223
  128. package/docs/mermaid-layout-complaints.md +0 -837
  129. package/docs/mutation-testing.md +0 -206
  130. package/docs/project/divergences.md +0 -98
  131. package/docs/project/dogfooding-docs-strategy.md +0 -66
  132. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  133. package/docs/project/lessons-learned.md +0 -1019
  134. package/docs/project/remove-role-styling-plan.md +0 -176
  135. package/docs/quality.md +0 -212
  136. package/docs/react.md +0 -97
  137. package/docs/style-authoring.md +0 -133
  138. package/docs/testing-strategy.md +0 -335
  139. package/docs/theming.md +0 -162
  140. package/examples/agent-improve-auth-flow.ts +0 -206
  141. package/examples/agent-loop.ts +0 -53
  142. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  143. package/llms.txt +0 -104
  144. package/skill-evals/README.md +0 -77
  145. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  146. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  147. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  148. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  149. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  150. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  151. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  152. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  153. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  154. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  155. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  156. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  157. package/skill-evals/shared-benchmark.json +0 -510
  158. package/src/agent/analyze.ts +0 -157
  159. package/src/agent/apply.ts +0 -146
  160. package/src/agent/architecture-body.ts +0 -458
  161. package/src/agent/certificates.ts +0 -110
  162. package/src/agent/class-body.ts +0 -319
  163. package/src/agent/core.ts +0 -207
  164. package/src/agent/create.ts +0 -106
  165. package/src/agent/describe.ts +0 -364
  166. package/src/agent/er-body.ts +0 -224
  167. package/src/agent/facts.ts +0 -337
  168. package/src/agent/families-builtin.ts +0 -934
  169. package/src/agent/families.ts +0 -232
  170. package/src/agent/family-layouts.ts +0 -826
  171. package/src/agent/flowchart-body.ts +0 -372
  172. package/src/agent/flowchart-unsupported.ts +0 -243
  173. package/src/agent/gantt-body.ts +0 -495
  174. package/src/agent/index.ts +0 -10
  175. package/src/agent/journey-body.ts +0 -414
  176. package/src/agent/label-metrics.ts +0 -28
  177. package/src/agent/layout-to-rendered.ts +0 -63
  178. package/src/agent/mutate.ts +0 -97
  179. package/src/agent/mutation-ops.ts +0 -51
  180. package/src/agent/op-schema.ts +0 -439
  181. package/src/agent/parse.ts +0 -223
  182. package/src/agent/pie-body.ts +0 -270
  183. package/src/agent/png.ts +0 -117
  184. package/src/agent/quadrant-body.ts +0 -336
  185. package/src/agent/quality.ts +0 -363
  186. package/src/agent/readability-audit.ts +0 -92
  187. package/src/agent/rendered-route-audit.ts +0 -61
  188. package/src/agent/sequence-body.ts +0 -345
  189. package/src/agent/serialize.ts +0 -214
  190. package/src/agent/state-body.ts +0 -589
  191. package/src/agent/structural-count.ts +0 -150
  192. package/src/agent/timeline-body.ts +0 -280
  193. package/src/agent/trace-log.ts +0 -43
  194. package/src/agent/types.ts +0 -979
  195. package/src/agent/verify.ts +0 -571
  196. package/src/agent/xychart-body.ts +0 -472
  197. package/src/architecture/config.ts +0 -282
  198. package/src/architecture/layout.ts +0 -496
  199. package/src/architecture/parser.ts +0 -375
  200. package/src/architecture/renderer.ts +0 -611
  201. package/src/architecture/types.ts +0 -113
  202. package/src/ascii/ansi.ts +0 -423
  203. package/src/ascii/architecture.ts +0 -210
  204. package/src/ascii/canvas.ts +0 -443
  205. package/src/ascii/class-diagram.ts +0 -697
  206. package/src/ascii/converter.ts +0 -475
  207. package/src/ascii/draw.ts +0 -1693
  208. package/src/ascii/edge-bundling.ts +0 -328
  209. package/src/ascii/edge-routing.ts +0 -330
  210. package/src/ascii/er-diagram.ts +0 -439
  211. package/src/ascii/gantt.ts +0 -276
  212. package/src/ascii/grid.ts +0 -747
  213. package/src/ascii/index.ts +0 -196
  214. package/src/ascii/journey.ts +0 -189
  215. package/src/ascii/meta.ts +0 -244
  216. package/src/ascii/multiline-utils.ts +0 -80
  217. package/src/ascii/pathfinder.ts +0 -301
  218. package/src/ascii/pie.ts +0 -67
  219. package/src/ascii/quadrant.ts +0 -158
  220. package/src/ascii/reverse.ts +0 -216
  221. package/src/ascii/sequence.ts +0 -499
  222. package/src/ascii/shapes/circle.ts +0 -27
  223. package/src/ascii/shapes/corners.ts +0 -143
  224. package/src/ascii/shapes/diamond.ts +0 -27
  225. package/src/ascii/shapes/hexagon.ts +0 -27
  226. package/src/ascii/shapes/index.ts +0 -106
  227. package/src/ascii/shapes/rectangle.ts +0 -168
  228. package/src/ascii/shapes/rounded.ts +0 -27
  229. package/src/ascii/shapes/special.ts +0 -332
  230. package/src/ascii/shapes/stadium.ts +0 -110
  231. package/src/ascii/shapes/state.ts +0 -192
  232. package/src/ascii/shapes/types.ts +0 -73
  233. package/src/ascii/timeline.ts +0 -128
  234. package/src/ascii/types.ts +0 -312
  235. package/src/ascii/validate.ts +0 -120
  236. package/src/ascii/width.ts +0 -81
  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 -1117
  247. package/src/cli/init-agent.ts +0 -147
  248. package/src/color-resolver.ts +0 -152
  249. package/src/elk-instance.ts +0 -114
  250. package/src/er/layout.ts +0 -187
  251. package/src/er/parser.ts +0 -226
  252. package/src/er/renderer.ts +0 -792
  253. package/src/er/types.ts +0 -99
  254. package/src/font-manifest.ts +0 -27
  255. package/src/gantt/layout.ts +0 -365
  256. package/src/gantt/parser.ts +0 -282
  257. package/src/gantt/pipeline.ts +0 -27
  258. package/src/gantt/renderer.ts +0 -414
  259. package/src/gantt/schedule.ts +0 -513
  260. package/src/gantt/types.ts +0 -255
  261. package/src/index.ts +0 -368
  262. package/src/journey/layout.ts +0 -520
  263. package/src/journey/parser.ts +0 -218
  264. package/src/journey/renderer.ts +0 -811
  265. package/src/journey/types.ts +0 -144
  266. package/src/layout/geometry.ts +0 -182
  267. package/src/layout/pass.ts +0 -142
  268. package/src/layout/passes/index.ts +0 -3916
  269. package/src/layout-engine.ts +0 -1565
  270. package/src/layout-rubric.ts +0 -659
  271. package/src/mcp/artifacts.ts +0 -137
  272. package/src/mcp/dynamic-harness.ts +0 -31
  273. package/src/mcp/dynamic-modules.d.ts +0 -7
  274. package/src/mcp/facade.ts +0 -608
  275. package/src/mcp/harness-runtime.ts +0 -203
  276. package/src/mcp/hosted-server.ts +0 -468
  277. package/src/mcp/mcp-bin.ts +0 -3
  278. package/src/mcp/mcp-cli.ts +0 -82
  279. package/src/mcp/protocol.ts +0 -13
  280. package/src/mcp/sandbox.ts +0 -195
  281. package/src/mcp/sdk-decl.ts +0 -387
  282. package/src/mcp/server.ts +0 -362
  283. package/src/mcp/tool-surface.ts +0 -146
  284. package/src/mermaid-source.ts +0 -655
  285. package/src/multiline-utils.ts +0 -228
  286. package/src/parser.ts +0 -1031
  287. package/src/pie/layout.ts +0 -168
  288. package/src/pie/parser.ts +0 -136
  289. package/src/pie/renderer.ts +0 -234
  290. package/src/pie/types.ts +0 -82
  291. package/src/quadrant/layout.ts +0 -191
  292. package/src/quadrant/parser.ts +0 -183
  293. package/src/quadrant/renderer.ts +0 -322
  294. package/src/quadrant/types.ts +0 -113
  295. package/src/render-family-hooks.ts +0 -244
  296. package/src/renderer.ts +0 -1099
  297. package/src/route-contracts.ts +0 -2723
  298. package/src/scene/backend.ts +0 -76
  299. package/src/scene/fidelity.ts +0 -183
  300. package/src/scene/hybrid-backend.ts +0 -174
  301. package/src/scene/ir.ts +0 -181
  302. package/src/scene/marks.ts +0 -127
  303. package/src/scene/rough-backend.ts +0 -378
  304. package/src/scene/seed.ts +0 -46
  305. package/src/scene/style-registry.ts +0 -556
  306. package/src/sequence/layout.ts +0 -456
  307. package/src/sequence/parser.ts +0 -255
  308. package/src/sequence/renderer.ts +0 -604
  309. package/src/sequence/types.ts +0 -154
  310. package/src/shape-clipping.ts +0 -351
  311. package/src/shared/accessibility-directives.ts +0 -22
  312. package/src/shared/batched.ts +0 -61
  313. package/src/shared/color-math.ts +0 -72
  314. package/src/shared/svg-a11y.ts +0 -29
  315. package/src/shared/syntax-error.ts +0 -17
  316. package/src/shared/unicode-ranges.ts +0 -135
  317. package/src/styles.ts +0 -318
  318. package/src/svg-paths.ts +0 -28
  319. package/src/text-metrics.ts +0 -248
  320. package/src/theme.ts +0 -639
  321. package/src/timeline/layout.ts +0 -381
  322. package/src/timeline/parser.ts +0 -215
  323. package/src/timeline/renderer.ts +0 -538
  324. package/src/timeline/types.ts +0 -105
  325. package/src/types.ts +0 -454
  326. package/src/website-worker-modules.d.ts +0 -18
  327. package/src/xychart/axis-utils.ts +0 -109
  328. package/src/xychart/colors.ts +0 -124
  329. package/src/xychart/config.ts +0 -88
  330. package/src/xychart/layout.ts +0 -656
  331. package/src/xychart/parser.ts +0 -504
  332. package/src/xychart/renderer.ts +0 -640
  333. package/src/xychart/types.ts +0 -282
@@ -1,571 +0,0 @@
1
- // ============================================================================
2
- // verifyMermaid — Tier 1 (source/structure) + Tier 2 (geometric) + Tier 3 (lint).
3
- //
4
- // v4: no LayoutContext, no seed wrapper (ELK is deterministic on its own).
5
- // LABEL_OVERFLOW is a rendered-line char-count check (Tier 1): the cap applies
6
- // to the longest displayed line (entities decoded, <br> splits), not raw
7
- // source chars — see label-metrics.ts.
8
- // ============================================================================
9
-
10
- import { parseMermaid as parseValidDiagram } from './parse.ts'
11
- import { serializeMermaid } from './serialize.ts'
12
- import { logToolInvocation } from './trace-log.ts'
13
- import { countStructuralElements, faithfulnessWarning } from './structural-count.ts'
14
- import { layoutGraphSync } from '../layout-engine.ts'
15
- import { renderMermaidSVG } from '../index.ts'
16
- import { parseMermaid as parseFlowchartLegacy } from '../parser.ts'
17
- import { auditRouteContracts, findRouteHitches } from '../route-contracts.ts'
18
- import type {
19
- ValidDiagram, VerifyOptions, VerifyResult, LayoutWarning, RenderedLayout,
20
- WarningCode, SequenceBody,
21
- } from './types.ts'
22
- import { WARNING_SEVERITY, DEFAULT_LABEL_CHAR_CAP } from './types.ts'
23
- import { positionedToRenderedLayout, emptyRenderedLayout } from './layout-to-rendered.ts'
24
- import { layoutFamilyToRendered, ganttGeometryWarnings, ganttScheduleWarning, layoutGeometryWarnings } from './family-layouts.ts'
25
- import { getFamily, extractLabelsGeneric, builtinFamilyMetadata } from './families.ts'
26
- import { labelOverflowWarning } from './label-metrics.ts'
27
- import { stateBodyToGraph } from './state-body.ts'
28
- import { flowchartUnsupportedSyntaxWarnings } from './flowchart-unsupported.ts'
29
- import './families-builtin.ts' // registers built-in families at import time
30
-
31
- const KNOWN_SHAPES = new Set([
32
- 'rectangle', 'service', 'rounded', 'diamond', 'stadium', 'circle',
33
- 'subroutine', 'doublecircle', 'hexagon', 'cylinder', 'asymmetric',
34
- 'trapezoid', 'trapezoid-alt', 'lean-r', 'lean-l', 'state-start', 'state-end',
35
- ])
36
-
37
- function opaqueSourceHasOnlyHeader(kind: ValidDiagram['kind'], source: string): boolean {
38
- const statements = source
39
- .split(/[;\n]/)
40
- .map(part => part.trim())
41
- .filter(part => part && !part.startsWith('%%'))
42
- if (statements.length === 0) return true
43
- if (statements.length > 1) return false
44
- const header = statements[0]!.toLowerCase()
45
- // Header aliases come from the family metadata table — the single source of
46
- // truth — instead of a third hand-encoding of the same strings.
47
- const aliases = (builtinFamilyMetadata(kind)?.headers ?? [kind]).map(h => h.toLowerCase())
48
- return aliases.some(alias => header === alias || header.startsWith(`${alias} `))
49
- }
50
-
51
- /**
52
- * Tier-3 faithfulness lint: does the structured {nodes, edges, groups} tally
53
- * survive a parse → serialize → re-parse cycle? Round-trip *byte*-stability
54
- * (already gated elsewhere) only proves serialize∘parse is idempotent; this
55
- * proves canonical serialization is not silently dropping a node/edge/group —
56
- * the ER `}o` class of bug. Promoted from a corpus-only check + the LLM-judge
57
- * helper so it now runs on EVERY verify, for every family. Opaque bodies carry
58
- * no structured arrays (their faithfulness is byte-verbatim) and are skipped.
59
- */
60
- function roundtripFaithfulnessWarnings(d: ValidDiagram): LayoutWarning[] {
61
- // Thin I/O wrapper: do the parse → serialize → re-parse, then defer the
62
- // verdict to the pure (mutation-gated, unit-tested) faithfulnessWarning.
63
- const before = countStructuralElements(d)
64
- if (!before) return []
65
- try {
66
- const reparsed = parseValidDiagram(serializeMermaid(d))
67
- if (!reparsed.ok) return faithfulnessWarning(before, null) // total loss
68
- const after = countStructuralElements(reparsed.value)
69
- if (!after) return [] // reparsed to an opaque body — the round-trip gate owns that
70
- return faithfulnessWarning(before, after)
71
- } catch {
72
- // Serialization/parse threw — the round-trip-stability gate owns that
73
- // failure mode; don't double-report it as a faithfulness drop.
74
- return []
75
- }
76
- }
77
-
78
- export function verifyMermaid(input: ValidDiagram | string, opts: VerifyOptions = {}): VerifyResult {
79
- logToolInvocation('verify')
80
- return withRenderParity(input, verifyStructure(input, opts), opts)
81
- }
82
-
83
- /**
84
- * Render-parity gate — generalizes UNRESOLVABLE_SCHEDULE's seam-closing to
85
- * every family. The agent parser preserves unmodeled syntax verbatim, but the
86
- * render parser is strict, so a diagram could verify clean and still make
87
- * `am render` exit 4 (observed live: onboarding agents followed
88
- * verify-before-commit and shipped unrenderable quadrant/architecture
89
- * diagrams). A clean verify now proves the canonical source actually renders.
90
- * Skipped when verify already failed — the render error would only repeat a
91
- * diagnosis the caller already has to act on — and when the caller
92
- * suppressed ANY error-severity code: suppression means "I acknowledge this
93
- * failure class, proceed", and the gate must not resurrect the acknowledged
94
- * failure under a different name (e.g. suppress UNRESOLVABLE_SCHEDULE on a
95
- * gantt whose render still throws for exactly that reason).
96
- */
97
- function withRenderParity(input: ValidDiagram | string, result: VerifyResult, opts: VerifyOptions): VerifyResult {
98
- if (!result.ok || (opts.suppress ?? []).some(code => code === 'RENDER_FAILED' || WARNING_SEVERITY[code] === 'error')) return result
99
- const source = typeof input === 'string' ? input : serializeMermaid(input)
100
- try {
101
- renderMermaidSVG(source)
102
- return result
103
- } catch (e) {
104
- const reason = e instanceof Error ? e.message : String(e)
105
- return { ...result, ok: false, warnings: [...result.warnings, { code: 'RENDER_FAILED', reason }] }
106
- }
107
- }
108
-
109
- function verifyStructure(input: ValidDiagram | string, opts: VerifyOptions = {}): VerifyResult {
110
- const d = typeof input === 'string' ? unwrap(input) : input
111
- if (!d) return finalize([{ code: 'EMPTY_DIAGRAM' }], emptyRenderedLayout('flowchart'), opts)
112
-
113
- const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP
114
-
115
- // Family-plugin verify dispatcher pass: every registered family's `verify`
116
- // hook gets a chance to contribute warnings. Runs ahead of per-body branches
117
- // so plugins can hook into any body kind (structured or opaque). Closes the
118
- // dead-code gap where `FamilyPlugin.verify` was declared but never invoked.
119
- // 2C comment policy: in-body comments that structured serialization drops
120
- // (recorded at parse time) surface here as the Tier 3 COMMENT_DROPPED lint,
121
- // so the loss is announced rather than silent.
122
- const metaWarnings: LayoutWarning[] = d.meta.droppedComments?.length
123
- ? [{ code: 'COMMENT_DROPPED', count: d.meta.droppedComments.length, lines: d.meta.droppedComments.map(c => c.line) }]
124
- : []
125
- const sourceWarnings = d.kind === 'flowchart' ? flowchartUnsupportedSyntaxWarnings(d.canonicalSource) : []
126
- const faithfulnessWarnings = roundtripFaithfulnessWarnings(d)
127
- const pluginWarnings = dedupedConcat(dedupedConcat(dedupedConcat(metaWarnings, dispatchFamilyVerify(d, opts)), sourceWarnings), faithfulnessWarnings)
128
-
129
- if (d.body.kind === 'sequence') return mergeFinalize(verifySequence(d as ValidDiagram & { body: SequenceBody }, cap, opts), pluginWarnings, opts)
130
- if (d.body.kind === 'timeline') return mergeFinalize(verifyTimeline(d as ValidDiagram & { body: import('./types.ts').TimelineBody }, cap, opts), pluginWarnings, opts)
131
- // class + ER: the FamilyPlugin.verify hooks (registered in families-builtin.ts)
132
- // already produce the per-body warnings. Loop 9 M2 removes the duplicate
133
- // explicit branches; the dispatcher path + emptyRenderedLayout fall-through
134
- // does the work. Dedup is unnecessary now (single source of truth) so we
135
- // emit pluginWarnings directly.
136
- // class + ER + journey + architecture: the FamilyPlugin.verify hooks produce
137
- // the per-body warnings (journey added by BUILD-15, architecture by BUILD-17).
138
- // Gantt adds geometric tripwires (OFF_CANVAS / GROUP_BREACH) over its real
139
- // layout and surfaces unresolvable schedules (UNRESOLVABLE_SCHEDULE),
140
- // alongside the body-level plugin warnings — see docs/design/families/gantt.md
141
- // §Verification.
142
- if (d.body.kind === 'gantt') {
143
- const layout = layoutFamilyToRendered(d) ?? emptyRenderedLayout(d.kind)
144
- const schedFail = ganttScheduleWarning(d)
145
- const geometric = dedupedConcat(ganttGeometryWarnings(layout), schedFail ? [schedFail] : [])
146
- return finalize(dedupedConcat(pluginWarnings, geometric), layout, opts)
147
- }
148
-
149
- 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') {
150
- // QUAL-1: verify.layout is now truthful — the real positioned layout from
151
- // the family adapters (was emptyRenderedLayout). #33 adds zero-noise
152
- // class/ER semantic geometry tripwires: relationship endpoints must sit on
153
- // class/entity box boundaries and boxes must remain on-canvas/non-overlap.
154
- const layout = layoutFamilyToRendered(d) ?? emptyRenderedLayout(d.kind)
155
- const familyGeometry = (d.body.kind === 'class' || d.body.kind === 'er')
156
- ? layoutGeometryWarnings(layout, { edgeAnchors: true, nodeOverlaps: true })
157
- : layoutGeometryWarnings(layout, {
158
- nodeOverlaps: d.body.kind === 'journey',
159
- groupContainment: d.body.kind === 'xychart' || d.body.kind === 'quadrant',
160
- })
161
- return finalize(dedupedConcat(pluginWarnings, familyGeometry), layout, opts)
162
- }
163
-
164
- // State diagrams (BUILD-19): the StateBody projects to a MermaidGraph via the
165
- // legacy state parser — the exact graph the renderer lays out — so the full
166
- // flowchart Tier 1 + Tier 2 geometric path runs unchanged. pluginWarnings
167
- // (verifyState) add the body-level structural checks on the StateBody itself.
168
- if (d.body.kind === 'state') {
169
- const graph = stateBodyToGraph(d.body)
170
- if (graph.nodes.size === 0) return finalize(dedupedConcat([{ code: 'EMPTY_DIAGRAM' }], pluginWarnings), emptyRenderedLayout(d.kind), opts)
171
- const { warnings, layout } = verifyGraph(graph, d.kind, cap)
172
- return finalize(dedupedConcat(warnings, pluginWarnings), layout, opts)
173
- }
174
-
175
- if (d.body.kind === 'opaque') {
176
- const isEmpty = opaqueSourceHasOnlyHeader(d.kind, d.body.source)
177
- // Universal Tier 1 LABEL_OVERFLOW via family-specific (or generic) label
178
- // extraction. Closes the gap where opaque-body diagrams (class / ER /
179
- // journey / xychart / architecture / sequence-with-alt/etc.) never got
180
- // label-cap checking.
181
- const plugin = getFamily(d.kind)
182
- const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(d.body.source)
183
- const warnings: LayoutWarning[] = isEmpty ? [{ code: 'EMPTY_DIAGRAM' }] : []
184
- // Systemic silent-opaque signal: a non-empty opaque body means the parser
185
- // met syntax it does not model and preserved the diagram verbatim, so the
186
- // `as*` narrower returns null and typed mutation is unavailable. Flowchart
187
- // and quadrant already emit a MORE SPECIFIC UNSUPPORTED_SYNTAX naming the
188
- // exact construct (via sourceWarnings / the quadrant verify hook, both in
189
- // pluginWarnings) — do not double-flag those. This one line generalizes
190
- // that announcement to the other families (class/state/er/xychart/pie/
191
- // journey/timeline/architecture/sequence) that used to fall opaque silently.
192
- if (!isEmpty && !pluginWarnings.some(w => w.code === 'UNSUPPORTED_SYNTAX')) {
193
- warnings.push({
194
- code: 'UNSUPPORTED_SYNTAX',
195
- syntax: `${d.kind}_opaque`,
196
- 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 — edit the source directly, or verify/render it as-is.`,
197
- })
198
- }
199
- const seen = new Set<string>()
200
- for (const lbl of labels) {
201
- const w = labelOverflowWarning(lbl.target, lbl.text, cap)
202
- if (!w) continue
203
- const key = `${lbl.target}:${lbl.text}`
204
- if (seen.has(key)) continue
205
- seen.add(key)
206
- warnings.push(w)
207
- }
208
- // QUAL-1: opaque bodies of renderable families (pie/quadrant always, and
209
- // class/er/journey/architecture/xychart when unmodeled) still produce a
210
- // real positioned layout from canonicalSource. layoutFamilyToRendered
211
- // degrades to an empty layout on render-error, so this never throws.
212
- const opaqueLayout = d.kind === 'flowchart'
213
- ? layoutOpaqueFlowchart(d)
214
- : layoutFamilyToRendered(d) ?? emptyRenderedLayout(d.kind)
215
- // Opaque bodies are preserved-not-rendered: an empty local layout means
216
- // the syntax is unmodeled, not that the diagram is empty — the isEmpty
217
- // header-only check above owns genuine emptiness here.
218
- return finalize(dedupedConcat(warnings, pluginWarnings), opaqueLayout, opts, false)
219
- }
220
-
221
- const graph = d.body.graph
222
- if (graph.nodes.size === 0) return finalize([{ code: 'EMPTY_DIAGRAM' }], emptyRenderedLayout(d.kind), opts)
223
- const { warnings: graphWarnings, layout: graphLayout } = verifyGraph(graph, d.kind, cap)
224
- return finalize(dedupedConcat(graphWarnings, pluginWarnings), graphLayout, opts)
225
- }
226
-
227
- /**
228
- * Full Tier 1 (structural) + Tier 2 (geometric) + Tier 3 (lint) verify over a
229
- * MermaidGraph. Shared by flowchart bodies and state-diagram bodies (which
230
- * project to a graph via stateBodyToGraph). Returns warnings + the rendered
231
- * layout; the caller finalizes (suppress + ok flag).
232
- */
233
- function verifyGraph(graph: import('../types.ts').MermaidGraph, kind: ValidDiagram['kind'], cap: number): { warnings: LayoutWarning[]; layout: RenderedLayout } {
234
- const positioned = layoutGraphSync(graph, {})
235
- const layout = positionedToRenderedLayout(positioned, kind)
236
- const warnings: LayoutWarning[] = []
237
-
238
- // Tier 1 — structural
239
- for (const edge of graph.edges) {
240
- const hasSource = graph.nodes.has(edge.source) || Boolean(findSubgraphById(graph.subgraphs, edge.source))
241
- const hasTarget = graph.nodes.has(edge.target) || Boolean(findSubgraphById(graph.subgraphs, edge.target))
242
- if (!hasSource || !hasTarget) {
243
- warnings.push({
244
- code: 'EDGE_MISANCHORED', edge: `${edge.source}->${edge.target}`,
245
- from: hasSource ? edge.source : undefined,
246
- to: hasTarget ? edge.target : undefined,
247
- })
248
- }
249
- }
250
- for (const [id, node] of graph.nodes) {
251
- if (!KNOWN_SHAPES.has(node.shape)) warnings.push({ code: 'UNKNOWN_SHAPE', node: id, shape: String(node.shape) })
252
- const w = labelOverflowWarning(id, node.label, cap)
253
- if (w) warnings.push(w)
254
- }
255
- for (const edge of graph.edges) {
256
- const w = edge.label ? labelOverflowWarning(`${edge.source}->${edge.target}`, edge.label, cap) : null
257
- if (w) warnings.push(w)
258
- }
259
- for (const n of positioned.nodes) {
260
- // Report x and y independently so a node off-canvas on both axes surfaces
261
- // both, instead of masking the second behind an else-if.
262
- if (n.x < 0 || n.x + n.width > positioned.width + 1) warnings.push({ code: 'OFF_CANVAS', target: n.id, axis: 'x' })
263
- if (n.y < 0 || n.y + n.height > positioned.height + 1) warnings.push({ code: 'OFF_CANVAS', target: n.id, axis: 'y' })
264
- }
265
- for (const g of positioned.groups) {
266
- const visit = (group: typeof g) => {
267
- const sg = findSubgraphById(graph.subgraphs, group.id)
268
- if (sg) for (const n of positioned.nodes) {
269
- if (!sg.nodeIds.includes(n.id)) continue
270
- const inside = n.x >= group.x && n.y >= group.y &&
271
- n.x + n.width <= group.x + group.width + 0.5 && n.y + n.height <= group.y + group.height + 0.5
272
- if (!inside) warnings.push({ code: 'GROUP_BREACH', group: group.id, member: n.id })
273
- }
274
- for (const c of group.children) visit(c)
275
- }
276
- visit(g)
277
- }
278
-
279
- // Tier 2 — geometric
280
- for (let i = 0; i < positioned.nodes.length; i++) {
281
- for (let j = i + 1; j < positioned.nodes.length; j++) {
282
- const a = positioned.nodes[i]!, b = positioned.nodes[j]!
283
- const o = rectIntersection(a, b)
284
- if (o > 0) warnings.push({ code: 'NODE_OVERLAP', a: a.id, b: b.id, areaPx: Math.round(o) })
285
- }
286
- }
287
- for (const e of positioned.edges) {
288
- const c = countSelfCrossings(e.points)
289
- if (c > 0) warnings.push({ code: 'ROUTE_SELF_CROSS', edge: `${e.source}->${e.target}`, count: c })
290
- }
291
- // Route-contract tripwires over FINAL geometry: the layout pipeline already
292
- // upholds these invariants itself (straight clear lanes, border-anchored
293
- // containers, on-shape endpoints, labels on their own lines), so any hit
294
- // here means some pass mutated geometry after route certification.
295
- // See docs/design/system/route-contracts.md.
296
- for (const hitch of findRouteHitches(positioned, graph)) {
297
- warnings.push({ code: 'ROUTE_HITCH', edge: hitch.edge, deviationPx: hitch.deviationPx })
298
- }
299
- warnings.push(...auditRouteContracts(positioned, graph))
300
-
301
- // Tier 3 — advisory lint for common agent mistakes that still parse/render.
302
- warnings.push(...lintFlowchartGraph(graph))
303
-
304
- return { warnings, layout }
305
- }
306
-
307
- function dedupedConcat(a: LayoutWarning[], b: LayoutWarning[]): LayoutWarning[] {
308
- if (b.length === 0) return a
309
- const seen = new Set(a.map(warningKey))
310
- const novel = b.filter(w => !seen.has(warningKey(w)))
311
- return novel.length === 0 ? a : [...a, ...novel]
312
- }
313
-
314
- /**
315
- * Run the registered FamilyPlugin.verify hook for this diagram's kind.
316
- * Returns the warnings the plugin produced, or [] when no plugin / no hook.
317
- */
318
- function dispatchFamilyVerify(d: ValidDiagram, opts: VerifyOptions): LayoutWarning[] {
319
- const plugin = getFamily(d.kind)
320
- if (!plugin?.verify) return []
321
- try {
322
- return plugin.verify(d.body, opts)
323
- } catch {
324
- // A faulty plugin shouldn't blow up verifyMermaid. Silent skip is acceptable
325
- // for an optional hook; the test suite catches bugs in built-in plugins.
326
- return []
327
- }
328
- }
329
-
330
- /** finalize() variant that merges an already-finalized result with extra warnings.
331
- * Loop 9 M10: now delegates fully to dedupedConcat → finalize. Dedupes on
332
- * (code, target/edge/node) so a plugin verify hook returning a warning
333
- * identical to one the per-body verify already produced doesn't surface twice.
334
- * The dispatcher has been live since Loop 7 M1, so this hazard remains real. */
335
- function mergeFinalize(prev: VerifyResult, extra: LayoutWarning[], opts: VerifyOptions): VerifyResult {
336
- if (extra.length === 0) return prev
337
- const merged = dedupedConcat(prev.warnings, extra)
338
- if (merged === prev.warnings) return prev
339
- // prev already went through the caller's guardEmptyLayout choice — don't
340
- // second-guess it here.
341
- return finalize(merged, prev.layout, opts, false)
342
- }
343
-
344
- function warningKey(w: LayoutWarning): string {
345
- if (w.code === 'UNSUPPORTED_SYNTAX') return `${w.code}:${w.syntax}:${w.line ?? ''}`
346
- if ('target' in w) return `${w.code}:${w.target}`
347
- if ('edge' in w) return `${w.code}:${w.edge}`
348
- if ('node' in w) return `${w.code}:${w.node}`
349
- if ('group' in w) return `${w.code}:${w.group}`
350
- if ('a' in w && 'b' in w) return `${w.code}:${w.a}|${w.b}`
351
- return w.code
352
- }
353
-
354
- function lintFlowchartGraph(graph: import('../types.ts').MermaidGraph): LayoutWarning[] {
355
- const warnings: LayoutWarning[] = []
356
- const firstBySignature = new Map<string, { edge: string; from: string; to: string; label?: string }>()
357
- graph.edges.forEach((edge, index) => {
358
- const signature = JSON.stringify({
359
- source: edge.source,
360
- target: edge.target,
361
- label: edge.label ?? '',
362
- style: edge.style,
363
- hasArrowStart: edge.hasArrowStart,
364
- hasArrowEnd: edge.hasArrowEnd,
365
- startMarker: edge.startMarker ?? 'arrow',
366
- endMarker: edge.endMarker ?? 'arrow',
367
- })
368
- const id = `${edge.source}->${edge.target}#${index}`
369
- const first = firstBySignature.get(signature)
370
- if (first) {
371
- warnings.push({ code: 'DUPLICATE_EDGE', edge: id, duplicateOf: first.edge, from: edge.source, to: edge.target, label: edge.label })
372
- } else {
373
- firstBySignature.set(signature, { edge: id, from: edge.source, to: edge.target, label: edge.label })
374
- }
375
- })
376
-
377
- // ISO 5807 (10.3.1.2) / ANSI X3.5 (4.10.2): when a decision has several
378
- // exits, EACH exit shall be labeled with its condition value. A diamond
379
- // with two or more out-edges where any branch is unlabeled is ambiguous.
380
- const outEdges = new Map<string, { labeled: number; unlabeled: Array<string> }>()
381
- graph.edges.forEach((edge, index) => {
382
- const node = graph.nodes.get(edge.source)
383
- if (!node || node.shape !== 'diamond' || edge.source === edge.target) return
384
- const entry = outEdges.get(edge.source) ?? { labeled: 0, unlabeled: [] }
385
- if (edge.label && edge.label.trim().length > 0) entry.labeled++
386
- else entry.unlabeled.push(`${edge.source}->${edge.target}#${index}`)
387
- outEdges.set(edge.source, entry)
388
- })
389
- for (const [nodeId, entry] of outEdges) {
390
- if (entry.labeled + entry.unlabeled.length < 2) continue
391
- for (const edge of entry.unlabeled) {
392
- warnings.push({ code: 'DECISION_BRANCH_UNLABELED', node: nodeId, edge })
393
- }
394
- }
395
-
396
- const ids = Array.from(graph.nodes.keys())
397
- if (ids.length === 0 || graph.edges.length === 0) return warnings
398
- const incoming = new Map(ids.map(id => [id, 0]))
399
- const outgoing = new Map(ids.map(id => [id, [] as string[]]))
400
- for (const edge of graph.edges) {
401
- if (!graph.nodes.has(edge.source) || !graph.nodes.has(edge.target)) continue
402
- incoming.set(edge.target, (incoming.get(edge.target) ?? 0) + 1)
403
- outgoing.get(edge.source)!.push(edge.target)
404
- }
405
- const roots = ids.filter(id => (incoming.get(id) ?? 0) === 0)
406
- if (roots.length === 0) return warnings
407
- const seen = new Set<string>(roots)
408
- const queue = [...roots]
409
- for (let i = 0; i < queue.length; i++) {
410
- for (const next of outgoing.get(queue[i]!) ?? []) {
411
- if (seen.has(next)) continue
412
- seen.add(next)
413
- queue.push(next)
414
- }
415
- }
416
- for (const id of ids) {
417
- if (!seen.has(id)) warnings.push({ code: 'UNREACHABLE_NODE', node: id })
418
- }
419
- return warnings
420
- }
421
-
422
- function verifyTimeline(d: ValidDiagram & { body: import('./types.ts').TimelineBody }, cap: number, opts: VerifyOptions): VerifyResult {
423
- const body = d.body
424
- const layout = layoutFamilyToRendered(d) ?? emptyRenderedLayout(d.kind)
425
- const warnings: LayoutWarning[] = []
426
- const hasContent = body.sections.some(s => s.periods.length > 0) || body.title !== undefined
427
- if (!hasContent) return finalize([{ code: 'EMPTY_DIAGRAM' }], layout, opts)
428
- const overflow = (target: string, text: string | undefined) => {
429
- const w = text !== undefined ? labelOverflowWarning(target, text, cap) : null
430
- if (w) warnings.push(w)
431
- }
432
- overflow('title', body.title)
433
- for (const s of body.sections) {
434
- overflow(s.id, s.label)
435
- for (const p of s.periods) {
436
- overflow(p.id, p.label)
437
- for (const e of p.events) overflow(e.id, e.text)
438
- }
439
- }
440
- return finalize(dedupedConcat(warnings, layoutGeometryWarnings(layout, { nodeOverlaps: true, groupContainment: true })), layout, opts)
441
- }
442
-
443
- function verifySequence(d: ValidDiagram & { body: SequenceBody }, cap: number, opts: VerifyOptions): VerifyResult {
444
- const body = d.body
445
- const layout = layoutFamilyToRendered(d) ?? emptyRenderedLayout(d.kind)
446
- const warnings: LayoutWarning[] = []
447
- // BUILD-18: a segment-preserving body may carry content only in opaque-block
448
- // segments (e.g. activation-shorthand messages `A->>+B`, blocks). That is
449
- // not an empty diagram — it just isn't structurally modeled.
450
- const hasOpaqueContent = (body.statements ?? []).some(
451
- s => s.kind === 'opaque-block' && s.lines.some(l => l.trim().length > 0),
452
- )
453
- if (body.participants.length === 0 && body.messages.length === 0 && !hasOpaqueContent) {
454
- return finalize([{ code: 'EMPTY_DIAGRAM' }], layout, opts, false)
455
- }
456
- const ids = new Set(body.participants.map(p => p.id))
457
- body.messages.forEach((m, i) => {
458
- if (!ids.has(m.from) || !ids.has(m.to)) {
459
- warnings.push({
460
- code: 'EDGE_MISANCHORED', edge: `msg#${i}:${m.from}->${m.to}`,
461
- from: ids.has(m.from) ? m.from : undefined, to: ids.has(m.to) ? m.to : undefined,
462
- })
463
- }
464
- const w = labelOverflowWarning(`msg#${i}:${m.from}->${m.to}`, m.text, cap)
465
- if (w) warnings.push(w)
466
- })
467
- for (const p of body.participants) {
468
- const w = labelOverflowWarning(p.id, p.label, cap)
469
- if (w) warnings.push(w)
470
- }
471
- // BUILD-18: opaque-block segments (Note/alt/loop/par/title lines) still get
472
- // universal LABEL_OVERFLOW via the family's label extractor, so the safety
473
- // check survives the move from whole-body-opaque to structured-with-segments.
474
- const opaqueLines = (body.statements ?? [])
475
- .filter((s): s is Extract<typeof s, { kind: 'opaque-block' }> => s.kind === 'opaque-block')
476
- .flatMap(s => s.lines)
477
- if (opaqueLines.length > 0) {
478
- const plugin = getFamily(d.kind)
479
- const labels = (plugin?.extractLabels ?? extractLabelsGeneric)(opaqueLines.join('\n'))
480
- const seen = new Set<string>()
481
- for (const lbl of labels) {
482
- const w = labelOverflowWarning(lbl.target, lbl.text, cap)
483
- if (!w) continue
484
- const key = `${lbl.target}:${lbl.text}`
485
- if (seen.has(key)) continue
486
- seen.add(key)
487
- warnings.push(w)
488
- }
489
- }
490
- // The empty-layout tripwire only arms when there is no structured content:
491
- // opaque-only segments still have to lay out SOMETHING (a malformed message
492
- // like `Alice->>` renders a 0x0 canvas — announce it), whereas a sequence
493
- // WITH participants whose family layout degrades to empty (e.g.
494
- // semicolon-packed statements) is a renderer limitation, not emptiness.
495
- const guardEmpty = body.participants.length === 0 && body.messages.length === 0
496
- return finalize(dedupedConcat(warnings, layoutGeometryWarnings(layout, { nodeOverlaps: true })), layout, opts, guardEmpty)
497
- }
498
-
499
- // ---- helpers --------------------------------------------------------------
500
-
501
- function layoutOpaqueFlowchart(d: ValidDiagram): RenderedLayout {
502
- try {
503
- return positionedToRenderedLayout(layoutGraphSync(parseFlowchartLegacy(d.canonicalSource), {}), d.kind)
504
- } catch {
505
- return emptyRenderedLayout(d.kind)
506
- }
507
- }
508
-
509
- function unwrap(source: string): ValidDiagram | null {
510
- const r = parseValidDiagram(source)
511
- return r.ok ? r.value : null
512
- }
513
-
514
- function finalize(warnings: LayoutWarning[], layout: RenderedLayout, opts: VerifyOptions, guardEmptyLayout = true): VerifyResult {
515
- const suppress = new Set<WarningCode>(opts.suppress ?? [])
516
- const kept = warnings.filter(w => !suppress.has(w.code))
517
- const ok = !kept.some(w => WARNING_SEVERITY[w.code] === 'error')
518
- // Empty-layout tripwire over the FINAL layout: a 0x0 canvas with no
519
- // nodes/edges/groups renders as visually nothing, so it must never verify
520
- // clean (e.g. `sequenceDiagram\n Alice->>` — a malformed message that lays
521
- // out zero participants). Source that carries content the local layout
522
- // cannot express stays ok (the upstream-suite bench pins that), so the
523
- // announcement is UNSUPPORTED_SYNTAX — warning severity, consistent with
524
- // the ok verdict — never an appended EMPTY_DIAGRAM, whose declared severity
525
- // is error and would contradict ok:true for callers that gate on it. Truly
526
- // content-less diagrams keep the explicit, ok-flipping EMPTY_DIAGRAM their
527
- // verify paths already push. Callers whose empty layout means "unmodeled,
528
- // preserved" rather than "renders nothing" (opaque bodies) opt out via
529
- // guardEmptyLayout=false.
530
- if (guardEmptyLayout && !suppress.has('UNSUPPORTED_SYNTAX')
531
- && layout.nodes.length === 0 && layout.edges.length === 0 && layout.groups.length === 0
532
- && layout.bounds.w === 0 && layout.bounds.h === 0
533
- && !kept.some(w => w.code === 'EMPTY_DIAGRAM')
534
- && !kept.some(w => w.code === 'UNSUPPORTED_SYNTAX' && w.syntax === 'empty_layout')) {
535
- return {
536
- ok,
537
- 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).' }],
538
- layout,
539
- }
540
- }
541
- return { ok, warnings: kept, layout }
542
- }
543
-
544
- function rectIntersection(a: { x: number; y: number; width: number; height: number }, b: { x: number; y: number; width: number; height: number }): number {
545
- const xo = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x))
546
- const yo = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y))
547
- return xo * yo
548
- }
549
- function countSelfCrossings(points: { x: number; y: number }[]): number {
550
- if (points.length < 4) return 0
551
- let count = 0
552
- for (let i = 0; i < points.length - 1; i++)
553
- for (let j = i + 2; j < points.length - 1; j++) {
554
- if (i === 0 && j === points.length - 2) continue
555
- if (segInt(points[i]!, points[i + 1]!, points[j]!, points[j + 1]!)) count++
556
- }
557
- return count
558
- }
559
- function segInt(p1: { x: number; y: number }, p2: { x: number; y: number }, p3: { x: number; y: number }, p4: { x: number; y: number }): boolean {
560
- const d1 = cr(p4.x - p3.x, p4.y - p3.y, p1.x - p3.x, p1.y - p3.y)
561
- const d2 = cr(p4.x - p3.x, p4.y - p3.y, p2.x - p3.x, p2.y - p3.y)
562
- const d3 = cr(p2.x - p1.x, p2.y - p1.y, p3.x - p1.x, p3.y - p1.y)
563
- const d4 = cr(p2.x - p1.x, p2.y - p1.y, p4.x - p1.x, p4.y - p1.y)
564
- return ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))
565
- }
566
- function cr(x1: number, y1: number, x2: number, y2: number): number { return x1 * y2 - x2 * y1 }
567
-
568
- function findSubgraphById(list: import('../types.ts').MermaidSubgraph[], id: string): import('../types.ts').MermaidSubgraph | null {
569
- for (const sg of list) { if (sg.id === id) return sg; const c = findSubgraphById(sg.children, id); if (c) return c }
570
- return null
571
- }