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.
- package/CHANGELOG.md +149 -4
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +25 -32
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/fonts/FONT-LICENSES.md +9 -5
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +81 -1822
- package/dist/agent.js +336 -36839
- package/dist/agentic-mermaid-mcp.js +7 -37908
- package/dist/am.js +33638 -36261
- package/dist/chunk-4QECACIA.js +4702 -0
- package/dist/chunk-CDPKJ2BQ.js +37 -0
- package/dist/chunk-QKDLMOJJ.js +3487 -0
- package/dist/chunk-WI35L7IR.js +615 -0
- package/dist/chunk-WN3YDOBG.js +149 -0
- package/dist/chunk-ZYMMKQ3I.js +62637 -0
- package/dist/index-D54dqSdC.d.ts +6777 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -32836
- package/dist/mcp-cli-SBQSTJFC.js +17 -0
- package/docs/README.md +14 -6
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +70 -43
- package/server.json +41 -0
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/AGENT_NATIVE.md +0 -602
- package/Instructions_for_agents.md +0 -83
- package/TODO.md +0 -662
- package/assets/hero.png +0 -0
- package/dist/index-x2Cj1dVv.d.ts +0 -1089
- package/docs/agent-api-cookbook.md +0 -308
- package/docs/agent-mutation-policy.md +0 -104
- package/docs/agent-workflow-examples.md +0 -46
- package/docs/api.md +0 -203
- package/docs/ascii.md +0 -103
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/comparison.md +0 -94
- package/docs/config.md +0 -105
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -71
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/custom-style-cookbook.md +0 -154
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -433
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -125
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/diagram-families.md +0 -193
- package/docs/features.md +0 -147
- package/docs/fork-differences.md +0 -130
- package/docs/getting-started.md +0 -175
- package/docs/issue-derived-test-cases.md +0 -156
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -496
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -118
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/mcp-code-mode-rationale.md +0 -101
- package/docs/mcp-http-transport.md +0 -223
- package/docs/mermaid-layout-complaints.md +0 -837
- package/docs/mutation-testing.md +0 -206
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/docs/quality.md +0 -212
- package/docs/react.md +0 -97
- package/docs/style-authoring.md +0 -133
- package/docs/testing-strategy.md +0 -335
- package/docs/theming.md +0 -162
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -364
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -337
- package/src/agent/families-builtin.ts +0 -934
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -826
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -414
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -439
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -117
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -979
- package/src/agent/verify.ts +0 -571
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -189
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -128
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1117
- package/src/cli/init-agent.ts +0 -147
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/font-manifest.ts +0 -27
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -520
- package/src/journey/parser.ts +0 -218
- package/src/journey/renderer.ts +0 -811
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -387
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -72
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -639
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -454
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
package/src/agent/pie-body.ts
DELETED
|
@@ -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
|
-
}
|