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,270 +0,0 @@
1
- // ============================================================================
2
- // Pie structured body (promotes pie from opaque-only fallback semantics to
3
- // structured mutation, following the journey/xychart/architecture pilots).
4
- //
5
- // Modeled grammar (mirrors the legacy renderer parser, src/pie/parser.ts):
6
- // pie [showData] [title <text>]
7
- // title <text> — standalone title directive
8
- // showData — standalone show-data directive
9
- // "<label>" : <positive number>
10
- //
11
- // Structured-or-opaque: any other non-blank, non-comment line (accTitle,
12
- // accDescr, unmodeled syntax) returns null so the caller falls back to a
13
- // lossless opaque body. We deliberately re-emit a canonical header
14
- // (`pie showData`) plus standalone `title` / entry lines; the legacy parser
15
- // re-parses that canonical output identically (differential-tested). Render
16
- // support is unchanged — the legacy renderer keeps parsing canonical source.
17
- //
18
- // The legacy parser ERRORS LOUDLY on malformed entries; here a structured body
19
- // is only produced when every line is modeled and every entry is a quoted
20
- // label with a positive numeric value — anything else falls back to opaque
21
- // (which preserves it verbatim), and the legacy renderer still surfaces the
22
- // loud error at render time.
23
- // ============================================================================
24
-
25
- import { unknownOpMessage } from './mutation-ops.ts'
26
- import type {
27
- PieBody, PieMutationOp,
28
- MutationError, Result, LayoutWarning, VerifyOptions,
29
- } from './types.ts'
30
- import { ok, err, DEFAULT_LABEL_CHAR_CAP } from './types.ts'
31
- import { labelOverflowWarning } from './label-metrics.ts'
32
-
33
- // ---- Number format ----------------------------------------------------------
34
- //
35
- // Pie values are positive numbers (Mermaid supports up to two decimal places).
36
- // Canonical format is `String(n)` for finite n > 0; `parseFloat(String(n)) === n`
37
- // for every finite n, and the legacy parser reads values with parseFloat.
38
-
39
- function formatNumber(n: number): string {
40
- return String(n)
41
- }
42
-
43
- function isPositiveFinite(n: unknown): n is number {
44
- return typeof n === 'number' && Number.isFinite(n) && n > 0
45
- }
46
-
47
- // ---- Parser -----------------------------------------------------------------
48
-
49
- /** Entry line: a quoted label, a colon, and a value (shape mirrors legacy). */
50
- const ENTRY_RE = /^"((?:[^"\\]|\\.)*)"\s*:\s*(.+)$/
51
- /** Mermaid pie values: positive numbers, up to two decimal places. */
52
- const NUMBER_RE = /^\+?(?:\d+(?:\.\d+)?|\.\d+)$/
53
-
54
- function decodeEscapes(raw: string): string {
55
- return raw.replace(/\\(["\\])/g, '$1')
56
- }
57
-
58
- /** Re-encode a label for emission inside `"..."` (inverse of decodeEscapes). */
59
- function encodeLabel(label: string): string {
60
- return label.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
61
- }
62
-
63
- /**
64
- * Parse pie body lines (header excluded). The header tail (showData / inline
65
- * title) is parsed separately by the family hook and passed in. Returns a
66
- * structured body only if EVERY non-blank, non-comment line is a modeled
67
- * title / showData / entry directive with a positive numeric value and a
68
- * quoted label that round-trips. Otherwise returns null (opaque fallback).
69
- *
70
- * A structured body must contain at least one slice (the legacy renderer needs
71
- * data to render).
72
- */
73
- export function parsePieBody(lines: string[], header: { showData: boolean; title?: string }): PieBody | null {
74
- const body: PieBody = { kind: 'pie', showData: header.showData, slices: [] }
75
- if (header.title !== undefined) body.title = header.title
76
- let sIdx = 0
77
-
78
- for (const raw of lines) {
79
- const line = raw.trim()
80
- if (!line) continue
81
- if (line.startsWith('%%')) continue
82
-
83
- if (/^showData\s*$/i.test(line)) {
84
- body.showData = true
85
- continue
86
- }
87
-
88
- const titleMatch = line.match(/^title\s+(.+)$/i)
89
- if (titleMatch) {
90
- const title = titleMatch[1]!.trim()
91
- if (!title) return null
92
- body.title = title
93
- continue
94
- }
95
-
96
- const entryMatch = line.match(ENTRY_RE)
97
- if (entryMatch) {
98
- const label = decodeEscapes(entryMatch[1]!)
99
- const rawValue = entryMatch[2]!.trim()
100
- if (!NUMBER_RE.test(rawValue)) return null
101
- const value = Number.parseFloat(rawValue)
102
- if (!isPositiveFinite(value)) return null
103
- // A label that wouldn't round-trip (newlines, etc.) falls back to opaque.
104
- if (label.includes('\n')) return null
105
- body.slices.push({ id: `slice-${sIdx++}`, label, value })
106
- continue
107
- }
108
-
109
- // Unmodeled line (accTitle/accDescr/malformed entry/anything else) → opaque.
110
- return null
111
- }
112
-
113
- if (body.slices.length === 0) return null
114
-
115
- return body
116
- }
117
-
118
- // ---- Serializer -------------------------------------------------------------
119
-
120
- export function renderPie(body: PieBody): string {
121
- const header = body.showData ? 'pie showData' : 'pie'
122
- const lines: string[] = [header]
123
- if (body.title !== undefined) lines.push(` title ${body.title}`)
124
- for (const s of body.slices) {
125
- lines.push(` "${encodeLabel(s.label)}" : ${formatNumber(s.value)}`)
126
- }
127
- return lines.join('\n') + '\n'
128
- }
129
-
130
- // ---- Mutator ----------------------------------------------------------------
131
-
132
- function clonePie(b: PieBody): PieBody {
133
- return {
134
- kind: 'pie',
135
- title: b.title,
136
- showData: b.showData,
137
- slices: b.slices.map(s => ({ id: s.id, label: s.label, value: s.value })),
138
- }
139
- }
140
-
141
- function makeSliceIdAllocator(body: PieBody): () => string {
142
- const seen = new Set(body.slices.map(s => s.id))
143
- return () => {
144
- let n = 0
145
- while (seen.has(`slice-${n}`)) n++
146
- const id = `slice-${n}`
147
- seen.add(id)
148
- return id
149
- }
150
- }
151
-
152
- function validLabel(value: unknown, field: string): Result<string, MutationError> {
153
- if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Pie ${field} must be a string` })
154
- const trimmed = value.trim()
155
- if (!trimmed || trimmed.includes('\n')) {
156
- return err({ code: 'INVALID_OP', message: `Pie ${field} must be non-empty single-line text` })
157
- }
158
- return ok(trimmed)
159
- }
160
-
161
- function validValue(value: unknown): Result<number, MutationError> {
162
- if (!isPositiveFinite(value)) {
163
- return err({ code: 'INVALID_OP', message: `Pie slice value must be a positive finite number, got ${JSON.stringify(value)}` })
164
- }
165
- return ok(value)
166
- }
167
-
168
- function findSlice(body: PieBody, label: string): number {
169
- return body.slices.findIndex(s => s.label === label)
170
- }
171
-
172
- export function mutatePie(body: PieBody, op: PieMutationOp): Result<PieBody, MutationError> {
173
- const next = clonePie(body)
174
- const nextId = makeSliceIdAllocator(next)
175
-
176
- switch (op.kind) {
177
- case 'set_title': {
178
- if (op.title === null) { delete next.title; break }
179
- const t = validLabel(op.title, 'title')
180
- if (!t.ok) return t
181
- next.title = t.value
182
- break
183
- }
184
- case 'set_show_data': {
185
- if (typeof op.showData !== 'boolean') {
186
- return err({ code: 'INVALID_OP', message: 'Pie set_show_data requires a boolean showData' })
187
- }
188
- next.showData = op.showData
189
- break
190
- }
191
- case 'add_slice': {
192
- const label = validLabel(op.label, 'slice label')
193
- if (!label.ok) return label
194
- if (findSlice(next, label.value) >= 0) {
195
- return err({ code: 'INVALID_OP', message: `Pie slice "${label.value}" already exists` })
196
- }
197
- const value = validValue(op.value)
198
- if (!value.ok) return value
199
- next.slices.push({ id: nextId(), label: label.value, value: value.value })
200
- break
201
- }
202
- case 'remove_slice': {
203
- const label = validLabel(op.label, 'slice label')
204
- if (!label.ok) return label
205
- const idx = findSlice(next, label.value)
206
- if (idx < 0) return err({ code: 'SLICE_NOT_FOUND', message: `Pie slice "${label.value}" not found` })
207
- next.slices.splice(idx, 1)
208
- break
209
- }
210
- case 'rename_slice': {
211
- const from = validLabel(op.from, 'slice label')
212
- if (!from.ok) return from
213
- const to = validLabel(op.to, 'slice label')
214
- if (!to.ok) return to
215
- const idx = findSlice(next, from.value)
216
- if (idx < 0) return err({ code: 'SLICE_NOT_FOUND', message: `Pie slice "${from.value}" not found` })
217
- if (from.value !== to.value && findSlice(next, to.value) >= 0) {
218
- return err({ code: 'INVALID_OP', message: `Pie slice "${to.value}" already exists` })
219
- }
220
- next.slices[idx]!.label = to.value
221
- break
222
- }
223
- case 'set_slice_value': {
224
- const label = validLabel(op.label, 'slice label')
225
- if (!label.ok) return label
226
- const idx = findSlice(next, label.value)
227
- if (idx < 0) return err({ code: 'SLICE_NOT_FOUND', message: `Pie slice "${label.value}" not found` })
228
- const value = validValue(op.value)
229
- if (!value.ok) return value
230
- next.slices[idx]!.value = value.value
231
- break
232
- }
233
- case 'reorder_slice': {
234
- const { from, to } = op
235
- if (!Number.isInteger(from) || !Number.isInteger(to) || !next.slices[from] || to < 0 || to >= next.slices.length) {
236
- return err({ code: 'SLICE_NOT_FOUND', message: `reorder_slice indices out of range (from=${from}, to=${to})` })
237
- }
238
- const [moved] = next.slices.splice(from, 1)
239
- next.slices.splice(to, 0, moved!)
240
- break
241
- }
242
- default: {
243
- const _x: never = op
244
- return err({ code: 'INVALID_OP', message: unknownOpMessage('pie', _x) })
245
- }
246
- }
247
-
248
- // Preserve the structured floor: a pie must keep at least one slice so it
249
- // always renders.
250
- if (next.slices.length === 0) {
251
- return err({ code: 'INVALID_OP', message: 'Pie must keep at least one slice' })
252
- }
253
-
254
- return ok(next)
255
- }
256
-
257
- // ---- Verifier (FamilyPlugin.verify hook) ------------------------------------
258
-
259
- export function verifyPie(body: PieBody, opts: VerifyOptions): LayoutWarning[] {
260
- const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP
261
- const warnings: LayoutWarning[] = []
262
- const overflow = (target: string, text: string) => {
263
- const w = labelOverflowWarning(target, text, cap)
264
- if (w) warnings.push(w)
265
- }
266
- if (body.slices.length === 0) warnings.push({ code: 'EMPTY_DIAGRAM' })
267
- if (body.title !== undefined) overflow('title', body.title)
268
- for (const s of body.slices) overflow(s.id, s.label)
269
- return warnings
270
- }
package/src/agent/png.ts DELETED
@@ -1,117 +0,0 @@
1
- // ============================================================================
2
- // renderMermaidPNG — rasterize a Mermaid diagram to a PNG byte array.
3
- //
4
- // Uses @resvg/resvg-js (napi-rs build, pinned 2.6.2) for deterministic
5
- // rasterization. Critical choices from the Loop 8 5-critic plan-hardening:
6
- //
7
- // - napi-rs build (NOT WASM) — same prebuilt .node binary under Bun and
8
- // Node via N-API compat, eliminates WASM-init differences.
9
- // - loadSystemFonts: false + bundled DejaVu Sans fonts in assets/fonts/ —
10
- // without this, fontconfig differences between OSes and CI images would
11
- // collapse cross-runtime parity.
12
- // - SVG input rendered with embedFontImport: false so resvg doesn't fetch
13
- // from Google Fonts at rasterization time (offline / CSP / sandbox safe).
14
- // - PNG bytes returned as Uint8Array (runtime-neutral, Code Mode friendly).
15
- //
16
- // What's tested: in-process determinism (5x same-input SHA-256 stable),
17
- // cross-runtime determinism (bun ≡ node on same-machine x86_64/ARM64 when
18
- // Node + built dist are present), basic PNG validity.
19
- // What's NOT tested: direct x86_64-vs-ARM64 byte equality, resvg version drift
20
- // across npm install runs. See docs/quality.md "PNG determinism".
21
- // ============================================================================
22
-
23
- import { fileURLToPath } from 'node:url'
24
- import { join, dirname } from 'node:path'
25
- import { existsSync } from 'node:fs'
26
- import { Resvg } from '@resvg/resvg-js'
27
- import type { ValidDiagram } from './types.ts'
28
- import type { StyleInput } from '../scene/style-registry.ts'
29
- import { serializeMermaid } from './serialize.ts'
30
- import { renderMermaidSVG } from '../index.ts'
31
- import { inlineFontVarForRaster } from '../theme.ts'
32
-
33
- export interface PngOptions {
34
- /** Output scale multiplier (default 2 — retina). */
35
- scale?: number
36
- /** Background color, any CSS color string (default 'white'). */
37
- background?: string
38
- /** Constrain output dimensions; otherwise honors scale on the SVG bounds. */
39
- fitTo?: { width?: number; height?: number }
40
- /** Style name | spec | stack, same as RenderOptions.style. Faces referenced
41
- * by the built-in looks are bundled in assets/fonts/; other families fall
42
- * back to DejaVu Sans unless supplied via fontDirs. */
43
- style?: StyleInput | StyleInput[]
44
- /** Ink-wobble seed for styled looks, same as RenderOptions.seed. */
45
- seed?: number
46
- /** Extra font directories searched in addition to the bundled ones —
47
- * the escape hatch for custom styles that reference unbundled families. */
48
- fontDirs?: string[]
49
- }
50
-
51
- /**
52
- * Resolve the bundled-fonts directory. Walks up from this module path until
53
- * it finds `assets/fonts/`. Caches the result so it's only resolved once.
54
- */
55
- let cachedFontDir: string | null = null
56
- function resolveFontDir(): string | null {
57
- if (cachedFontDir !== null) return cachedFontDir
58
- // import.meta.url works under Bun + Node ESM
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- const here = typeof (import.meta as any).url === 'string' ? fileURLToPath((import.meta as any).url) : __filename
61
- let dir = dirname(here)
62
- for (let i = 0; i < 6; i++) {
63
- const candidate = join(dir, 'assets', 'fonts')
64
- if (existsSync(candidate)) { cachedFontDir = candidate; return candidate }
65
- const parent = dirname(dir)
66
- if (parent === dir) break
67
- dir = parent
68
- }
69
- // Don't crash if not found — resvg will fall back to its default fonts.
70
- // Cross-runtime determinism may weaken in that case; documented gap.
71
- cachedFontDir = ''
72
- return null
73
- }
74
-
75
- /**
76
- * Render a Mermaid diagram (source string or ValidDiagram) to a PNG byte array.
77
- * Deterministic within one runtime; cross-runtime parity is guarded for
78
- * same-machine Bun ≡ Node on x86_64/ARM64 when Node + built dist are present.
79
- *
80
- * Synchronous: resvg's `.render()` is native-sync, and static import keeps
81
- * the CLI/MCP integration straightforward. Library consumers can wrap in
82
- * `Promise.resolve()` if they want async semantics.
83
- */
84
- export function renderMermaidPNG(input: ValidDiagram | string, opts: PngOptions = {}): Uint8Array {
85
- // SVG input: embedFontImport=false so resvg doesn't try to fetch from
86
- // Google Fonts during rasterization. CSS-variable fonts (Loop 8 M2) means
87
- // the SVG still declares its font-family preference via --font.
88
- const source = typeof input === 'string' ? input : serializeMermaid(input)
89
- const svg = inlineFontVarForRaster(
90
- renderMermaidSVG(source, { embedFontImport: false, style: opts.style, seed: opts.seed }),
91
- )
92
-
93
- const scale = opts.scale ?? 2
94
- const fontDir = resolveFontDir()
95
-
96
- const resvgOpts: ConstructorParameters<typeof Resvg>[1] = {
97
- background: opts.background ?? 'white',
98
- fitTo: opts.fitTo?.width
99
- ? { mode: 'width' as const, value: opts.fitTo.width }
100
- : opts.fitTo?.height
101
- ? { mode: 'height' as const, value: opts.fitTo.height }
102
- : { mode: 'zoom' as const, value: scale },
103
- font: {
104
- loadSystemFonts: false,
105
- // Bundled fonts (DejaVu Sans + the faces built-in styles reference)
106
- // for cross-runtime determinism, plus caller-supplied directories.
107
- // Falls back to resvg's built-in fonts if nothing is found.
108
- fontDirs: [...(fontDir ? [fontDir] : []), ...(opts.fontDirs ?? [])],
109
- defaultFontFamily: 'DejaVu Sans',
110
- },
111
- }
112
-
113
- const resvg = new Resvg(svg, resvgOpts)
114
- const png = resvg.render().asPng()
115
- // resvg returns Buffer in Node; ensure we surface Uint8Array consistently.
116
- return new Uint8Array(png.buffer, png.byteOffset, png.byteLength)
117
- }