agentic-mermaid 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/AGENT_NATIVE.md +155 -91
  2. package/CHANGELOG.md +163 -4
  3. package/Instructions_for_agents.md +10 -10
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +31 -38
  6. package/THIRD_PARTY_NOTICES.md +29 -0
  7. package/assets/hero.png +0 -0
  8. package/dist/agent-core.d.ts +463 -0
  9. package/dist/agent-core.js +397 -0
  10. package/dist/agent.d.ts +73 -1868
  11. package/dist/agent.js +336 -37325
  12. package/dist/agentic-mermaid-mcp.js +7 -38399
  13. package/dist/am.js +33626 -40446
  14. package/dist/chunk-2P5FKUQC.js +3485 -0
  15. package/dist/chunk-2S3VSS6I.js +149 -0
  16. package/dist/chunk-2SILJDW2.js +37 -0
  17. package/dist/chunk-BJYMBT4I.js +61963 -0
  18. package/dist/chunk-FDM43XU7.js +615 -0
  19. package/dist/chunk-MYPDVZCO.js +4702 -0
  20. package/dist/index-DWPa2MJs.d.ts +6780 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +310 -33014
  23. package/dist/mcp-cli-5J47WEY3.js +17 -0
  24. package/docs/README.md +39 -31
  25. package/docs/agent-api-cookbook.md +44 -35
  26. package/docs/agent-mutation-policy.md +1 -1
  27. package/docs/agent-workflow-examples.md +2 -2
  28. package/docs/api.md +284 -77
  29. package/docs/ascii.md +26 -16
  30. package/docs/code-mode-impact.md +214 -0
  31. package/docs/comparison.md +23 -11
  32. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  33. package/docs/config.md +26 -10
  34. package/docs/custom-fonts.md +265 -0
  35. package/docs/custom-style-cookbook.md +130 -9
  36. package/docs/diagram-families.md +48 -23
  37. package/docs/features.md +59 -52
  38. package/docs/fork-differences.md +6 -3
  39. package/docs/getting-started.md +23 -16
  40. package/docs/issue-derived-test-cases.md +29 -22
  41. package/docs/mcp-code-mode-rationale.md +13 -6
  42. package/docs/mcp-http-transport.md +20 -13
  43. package/docs/mermaid-layout-complaints.md +66 -52
  44. package/docs/mutation-testing.md +145 -66
  45. package/docs/quality.md +23 -14
  46. package/docs/react.md +2 -2
  47. package/docs/schemas/style-spec.schema.json +1221 -30
  48. package/docs/style-authoring.md +241 -38
  49. package/docs/svg-semantic-contract.md +49 -0
  50. package/docs/testing-strategy.md +166 -58
  51. package/docs/theming.md +19 -32
  52. package/examples/styles/catalog.json +54 -0
  53. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  54. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  55. package/examples/styles/cupertino-prototype.mmd +10 -0
  56. package/examples/styles/cupertino-prototype.style.json +63 -0
  57. package/examples/styles/mid-century-report.style.json +2 -2
  58. package/examples/styles/star-chart-atlas.style.json +2 -1
  59. package/examples/styles/transit-route-map.style.json +2 -2
  60. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  61. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  62. package/package.json +85 -45
  63. package/server.json +2 -2
  64. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  65. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  66. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  67. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  68. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  69. package/TODO.md +0 -684
  70. package/dist/index-wjRjbsP-.d.ts +0 -1102
  71. package/docs/assets/improvements/box-start-connector.png +0 -0
  72. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  73. package/docs/assets/improvements/er-content-loss.png +0 -0
  74. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  75. package/docs/assets/improvements/gantt-family.png +0 -0
  76. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  77. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  78. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  79. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  80. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  81. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  82. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  83. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  84. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  85. package/docs/contributing/adding-diagram-types.md +0 -144
  86. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  87. package/docs/contributing/diagram-family-citizenship.md +0 -82
  88. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  89. package/docs/contributing/lessons-learned.md +0 -276
  90. package/docs/contributing/releasing.md +0 -82
  91. package/docs/contributing/visual-review-evidence.md +0 -53
  92. package/docs/design/families/README.md +0 -26
  93. package/docs/design/families/architecture-beta.md +0 -48
  94. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  95. package/docs/design/families/gantt-research.md +0 -137
  96. package/docs/design/families/gantt.md +0 -329
  97. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  98. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  99. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  100. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  101. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  102. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  103. package/docs/design/families/journey-migration-parity.md +0 -439
  104. package/docs/design/families/journey-section-overlap-after.png +0 -0
  105. package/docs/design/families/journey-section-overlap-before.png +0 -0
  106. package/docs/design/families/journey-usage-research.md +0 -423
  107. package/docs/design/families/journey.md +0 -153
  108. package/docs/design/families/xychart.md +0 -85
  109. package/docs/design/system/README.md +0 -45
  110. package/docs/design/system/abstraction-audit.md +0 -224
  111. package/docs/design/system/abstraction-recommendations.md +0 -655
  112. package/docs/design/system/architecture.mmd +0 -29
  113. package/docs/design/system/architecture.svg +0 -137
  114. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  115. package/docs/design/system/layout-rubric.md +0 -138
  116. package/docs/design/system/route-contracts.md +0 -848
  117. package/docs/design/system/source-preservation-ladder.md +0 -52
  118. package/docs/design/system/styles-rollout.md +0 -216
  119. package/docs/design/system/ugly-layouts.md +0 -119
  120. package/docs/layout-characterization/README.md +0 -245
  121. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  122. package/docs/layout-characterization/contact-sheet.md +0 -638
  123. package/docs/layout-characterization/properties.md +0 -281
  124. package/docs/layout-characterization/visual-quality.md +0 -160
  125. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  126. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  127. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  128. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  129. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  130. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  131. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  132. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  133. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  134. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  135. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  136. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  137. package/docs/project/divergences.md +0 -98
  138. package/docs/project/dogfooding-docs-strategy.md +0 -66
  139. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  140. package/docs/project/lessons-learned.md +0 -1019
  141. package/docs/project/remove-role-styling-plan.md +0 -176
  142. package/examples/agent-improve-auth-flow.ts +0 -206
  143. package/examples/agent-loop.ts +0 -53
  144. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  145. package/llms.txt +0 -104
  146. package/skill-evals/README.md +0 -77
  147. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  148. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  149. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  150. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  151. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  152. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  153. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  154. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  155. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  156. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  157. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  158. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  159. package/skill-evals/shared-benchmark.json +0 -510
  160. package/src/agent/analyze.ts +0 -157
  161. package/src/agent/apply.ts +0 -146
  162. package/src/agent/architecture-body.ts +0 -458
  163. package/src/agent/certificates.ts +0 -110
  164. package/src/agent/class-body.ts +0 -319
  165. package/src/agent/core.ts +0 -207
  166. package/src/agent/create.ts +0 -106
  167. package/src/agent/describe.ts +0 -368
  168. package/src/agent/er-body.ts +0 -224
  169. package/src/agent/facts.ts +0 -360
  170. package/src/agent/families-builtin.ts +0 -920
  171. package/src/agent/families.ts +0 -232
  172. package/src/agent/family-layouts.ts +0 -832
  173. package/src/agent/flowchart-body.ts +0 -372
  174. package/src/agent/flowchart-unsupported.ts +0 -243
  175. package/src/agent/font-coverage.ts +0 -288
  176. package/src/agent/gantt-body.ts +0 -495
  177. package/src/agent/index.ts +0 -10
  178. package/src/agent/journey-body.ts +0 -374
  179. package/src/agent/label-metrics.ts +0 -28
  180. package/src/agent/layout-to-rendered.ts +0 -63
  181. package/src/agent/mutate.ts +0 -97
  182. package/src/agent/mutation-ops.ts +0 -51
  183. package/src/agent/op-schema.ts +0 -443
  184. package/src/agent/parse.ts +0 -223
  185. package/src/agent/pie-body.ts +0 -270
  186. package/src/agent/png.ts +0 -171
  187. package/src/agent/quadrant-body.ts +0 -336
  188. package/src/agent/quality.ts +0 -363
  189. package/src/agent/readability-audit.ts +0 -92
  190. package/src/agent/rendered-route-audit.ts +0 -61
  191. package/src/agent/sequence-body.ts +0 -345
  192. package/src/agent/serialize.ts +0 -214
  193. package/src/agent/state-body.ts +0 -589
  194. package/src/agent/structural-count.ts +0 -150
  195. package/src/agent/timeline-body.ts +0 -280
  196. package/src/agent/trace-log.ts +0 -43
  197. package/src/agent/types.ts +0 -995
  198. package/src/agent/verify.ts +0 -603
  199. package/src/agent/xychart-body.ts +0 -472
  200. package/src/architecture/config.ts +0 -282
  201. package/src/architecture/layout.ts +0 -496
  202. package/src/architecture/parser.ts +0 -375
  203. package/src/architecture/renderer.ts +0 -611
  204. package/src/architecture/types.ts +0 -113
  205. package/src/ascii/ansi.ts +0 -423
  206. package/src/ascii/architecture.ts +0 -210
  207. package/src/ascii/canvas.ts +0 -443
  208. package/src/ascii/class-diagram.ts +0 -697
  209. package/src/ascii/converter.ts +0 -475
  210. package/src/ascii/draw.ts +0 -1693
  211. package/src/ascii/edge-bundling.ts +0 -328
  212. package/src/ascii/edge-routing.ts +0 -330
  213. package/src/ascii/er-diagram.ts +0 -439
  214. package/src/ascii/gantt.ts +0 -276
  215. package/src/ascii/grid.ts +0 -747
  216. package/src/ascii/index.ts +0 -196
  217. package/src/ascii/journey.ts +0 -169
  218. package/src/ascii/meta.ts +0 -244
  219. package/src/ascii/multiline-utils.ts +0 -80
  220. package/src/ascii/pathfinder.ts +0 -301
  221. package/src/ascii/pie.ts +0 -67
  222. package/src/ascii/quadrant.ts +0 -158
  223. package/src/ascii/reverse.ts +0 -216
  224. package/src/ascii/sequence.ts +0 -499
  225. package/src/ascii/shapes/circle.ts +0 -27
  226. package/src/ascii/shapes/corners.ts +0 -143
  227. package/src/ascii/shapes/diamond.ts +0 -27
  228. package/src/ascii/shapes/hexagon.ts +0 -27
  229. package/src/ascii/shapes/index.ts +0 -106
  230. package/src/ascii/shapes/rectangle.ts +0 -168
  231. package/src/ascii/shapes/rounded.ts +0 -27
  232. package/src/ascii/shapes/special.ts +0 -332
  233. package/src/ascii/shapes/stadium.ts +0 -110
  234. package/src/ascii/shapes/state.ts +0 -192
  235. package/src/ascii/shapes/types.ts +0 -73
  236. package/src/ascii/timeline.ts +0 -141
  237. package/src/ascii/types.ts +0 -312
  238. package/src/ascii/validate.ts +0 -120
  239. package/src/ascii/width.ts +0 -81
  240. package/src/ascii/wrap.ts +0 -131
  241. package/src/ascii/xychart.ts +0 -919
  242. package/src/browser.ts +0 -29
  243. package/src/class/layout.ts +0 -240
  244. package/src/class/parser.ts +0 -332
  245. package/src/class/renderer.ts +0 -638
  246. package/src/class/types.ts +0 -129
  247. package/src/cli/agent-instructions.ts +0 -1
  248. package/src/cli/am-bin.ts +0 -6
  249. package/src/cli/exit-codes.ts +0 -18
  250. package/src/cli/index.ts +0 -1132
  251. package/src/cli/init-agent.ts +0 -147
  252. package/src/cli/run-entrypoint.ts +0 -10
  253. package/src/color-resolver.ts +0 -152
  254. package/src/elk-instance.ts +0 -114
  255. package/src/er/layout.ts +0 -187
  256. package/src/er/parser.ts +0 -226
  257. package/src/er/renderer.ts +0 -792
  258. package/src/er/types.ts +0 -99
  259. package/src/family-rubric.ts +0 -425
  260. package/src/font-manifest.ts +0 -31
  261. package/src/gantt/layout.ts +0 -365
  262. package/src/gantt/parser.ts +0 -282
  263. package/src/gantt/pipeline.ts +0 -27
  264. package/src/gantt/renderer.ts +0 -414
  265. package/src/gantt/schedule.ts +0 -513
  266. package/src/gantt/types.ts +0 -255
  267. package/src/index.ts +0 -368
  268. package/src/journey/layout.ts +0 -554
  269. package/src/journey/parse-core.ts +0 -251
  270. package/src/journey/parser.ts +0 -101
  271. package/src/journey/renderer.ts +0 -917
  272. package/src/journey/types.ts +0 -144
  273. package/src/layout/geometry.ts +0 -182
  274. package/src/layout/pass.ts +0 -142
  275. package/src/layout/passes/index.ts +0 -3916
  276. package/src/layout-engine.ts +0 -1565
  277. package/src/layout-rubric.ts +0 -659
  278. package/src/mcp/artifacts.ts +0 -137
  279. package/src/mcp/dynamic-harness.ts +0 -31
  280. package/src/mcp/dynamic-modules.d.ts +0 -7
  281. package/src/mcp/facade.ts +0 -608
  282. package/src/mcp/harness-runtime.ts +0 -203
  283. package/src/mcp/hosted-server.ts +0 -468
  284. package/src/mcp/mcp-bin.ts +0 -3
  285. package/src/mcp/mcp-cli.ts +0 -82
  286. package/src/mcp/protocol.ts +0 -13
  287. package/src/mcp/sandbox.ts +0 -195
  288. package/src/mcp/sdk-decl.ts +0 -391
  289. package/src/mcp/server.ts +0 -362
  290. package/src/mcp/tool-surface.ts +0 -146
  291. package/src/mermaid-source.ts +0 -655
  292. package/src/multiline-utils.ts +0 -228
  293. package/src/parser.ts +0 -1031
  294. package/src/pie/layout.ts +0 -168
  295. package/src/pie/parser.ts +0 -136
  296. package/src/pie/renderer.ts +0 -234
  297. package/src/pie/types.ts +0 -82
  298. package/src/quadrant/layout.ts +0 -191
  299. package/src/quadrant/parser.ts +0 -183
  300. package/src/quadrant/renderer.ts +0 -322
  301. package/src/quadrant/types.ts +0 -113
  302. package/src/render-family-hooks.ts +0 -244
  303. package/src/renderer.ts +0 -1099
  304. package/src/route-contracts.ts +0 -2723
  305. package/src/scene/backend.ts +0 -76
  306. package/src/scene/fidelity.ts +0 -183
  307. package/src/scene/hybrid-backend.ts +0 -174
  308. package/src/scene/ir.ts +0 -181
  309. package/src/scene/marks.ts +0 -127
  310. package/src/scene/rough-backend.ts +0 -378
  311. package/src/scene/seed.ts +0 -46
  312. package/src/scene/style-registry.ts +0 -556
  313. package/src/sequence/layout.ts +0 -456
  314. package/src/sequence/parser.ts +0 -255
  315. package/src/sequence/renderer.ts +0 -604
  316. package/src/sequence/types.ts +0 -154
  317. package/src/shape-clipping.ts +0 -351
  318. package/src/shared/accessibility-directives.ts +0 -22
  319. package/src/shared/batched.ts +0 -61
  320. package/src/shared/color-math.ts +0 -93
  321. package/src/shared/svg-a11y.ts +0 -29
  322. package/src/shared/syntax-error.ts +0 -17
  323. package/src/shared/unicode-ranges.ts +0 -135
  324. package/src/styles.ts +0 -318
  325. package/src/svg-paths.ts +0 -28
  326. package/src/text-metrics.ts +0 -248
  327. package/src/theme.ts +0 -710
  328. package/src/timeline/layout.ts +0 -381
  329. package/src/timeline/parser.ts +0 -215
  330. package/src/timeline/renderer.ts +0 -538
  331. package/src/timeline/types.ts +0 -105
  332. package/src/types.ts +0 -462
  333. package/src/website-worker-modules.d.ts +0 -18
  334. package/src/xychart/axis-utils.ts +0 -109
  335. package/src/xychart/colors.ts +0 -124
  336. package/src/xychart/config.ts +0 -88
  337. package/src/xychart/layout.ts +0 -656
  338. package/src/xychart/parser.ts +0 -504
  339. package/src/xychart/renderer.ts +0 -640
  340. package/src/xychart/types.ts +0 -282
@@ -1,150 +0,0 @@
1
- // ============================================================================
2
- // countStructuralElements — a family-agnostic faithfulness oracle.
3
- //
4
- // "100% parse success is not faithfulness" (lessons-learned, Loop 17): a parser
5
- // can accept a source and the serializer can round-trip byte-stably while a
6
- // relationship is silently dropped (the ER `}o` bug shipped inside a fully
7
- // gated corpus for exactly this reason). Byte-stability proves serialize∘parse
8
- // is idempotent; it does NOT prove parse preserved the source's content.
9
- //
10
- // This counter projects every renderable family onto a small {nodes, edges,
11
- // groups} tally drawn from the structured IR. Comparing the count of a source
12
- // against the count of its re-parsed serialization turns "did we drop a node or
13
- // edge on round-trip?" into a deterministic assertion across all twelve
14
- // families — without a golden image and without a human. It backs both the
15
- // corpus count-oracle (eval) and the CONTENT_DROPPED_ON_ROUNDTRIP verify lint.
16
- //
17
- // Opaque bodies have no structured arrays to count, so they return null and the
18
- // caller skips them (their faithfulness contract is byte-verbatim preservation,
19
- // which the round-trip-stability gate already covers).
20
- // ============================================================================
21
-
22
- import type { ValidDiagram, StateNode, LayoutWarning } from './types.ts'
23
- import type { MermaidSubgraph } from '../types.ts'
24
-
25
- export interface StructuralCount {
26
- /** Primary entities: nodes, participants, states, classes, entities, slices… */
27
- nodes: number
28
- /** Relations: edges, messages, transitions, relations. 0 for relationless families. */
29
- edges: number
30
- /** Containers: subgraphs, sections, groups. 0 when the family has none. */
31
- groups: number
32
- }
33
-
34
- function countStates(states: StateNode[]): { nodes: number; edges: number } {
35
- let nodes = 0, edges = 0
36
- for (const s of states) {
37
- nodes++
38
- if (s.states && s.states.length) {
39
- const inner = countStates(s.states)
40
- nodes += inner.nodes
41
- edges += inner.edges
42
- }
43
- if (s.transitions) edges += s.transitions.length
44
- }
45
- return { nodes, edges }
46
- }
47
-
48
- function countSubgraphs(subgraphs: MermaidSubgraph[]): number {
49
- let groups = 0
50
- for (const sg of subgraphs) {
51
- groups++
52
- groups += countSubgraphs(sg.children)
53
- }
54
- return groups
55
- }
56
-
57
- /**
58
- * Count the structured elements of a diagram, or null for opaque bodies (which
59
- * carry no structured arrays — their faithfulness is byte-verbatim, covered by
60
- * the round-trip gate).
61
- */
62
- export function countStructuralElements(d: ValidDiagram): StructuralCount | null {
63
- const body = d.body
64
- switch (body.kind) {
65
- case 'flowchart': {
66
- const g = body.graph
67
- return { nodes: g.nodes.size, edges: g.edges.length, groups: countSubgraphs(g.subgraphs) }
68
- }
69
- case 'sequence':
70
- return { nodes: body.participants.length, edges: body.messages.length, groups: 0 }
71
- case 'state': {
72
- const top = countStates(body.states)
73
- return { nodes: top.nodes, edges: top.edges + body.transitions.length, groups: 0 }
74
- }
75
- case 'class':
76
- return { nodes: body.classes.length, edges: body.relations.length, groups: 0 }
77
- case 'er':
78
- return { nodes: body.entities.length, edges: body.relations.length, groups: 0 }
79
- case 'timeline': {
80
- let nodes = 0
81
- for (const sec of body.sections) for (const p of sec.periods) nodes += 1 + p.events.length
82
- return { nodes, edges: 0, groups: body.sections.length }
83
- }
84
- case 'journey': {
85
- let nodes = 0
86
- for (const sec of body.sections) nodes += sec.tasks.length
87
- return { nodes, edges: 0, groups: body.sections.length }
88
- }
89
- case 'architecture':
90
- return {
91
- nodes: body.services.length + body.junctions.length,
92
- edges: body.edges.length,
93
- groups: body.groups.length,
94
- }
95
- case 'xychart':
96
- return { nodes: body.series.length, edges: 0, groups: 0 }
97
- case 'pie':
98
- return { nodes: body.slices.length, edges: 0, groups: 0 }
99
- case 'quadrant':
100
- return { nodes: body.points.length, edges: 0, groups: 0 }
101
- case 'gantt': {
102
- let nodes = 0
103
- for (const sec of body.sections) nodes += sec.tasks.length
104
- return { nodes, edges: 0, groups: body.sections.length }
105
- }
106
- case 'opaque':
107
- return null
108
- default: {
109
- // Exhaustiveness guard: a new family must declare its count here.
110
- const _never: never = body
111
- void _never
112
- return null
113
- }
114
- }
115
- }
116
-
117
- export function countsEqual(a: StructuralCount, b: StructuralCount): boolean {
118
- return a.nodes === b.nodes && a.edges === b.edges && a.groups === b.groups
119
- }
120
-
121
- /**
122
- * Pure faithfulness verdict over the before/after counts of a round-trip
123
- * (Move 3): the decision logic of the CONTENT_DROPPED_ON_ROUNDTRIP verify lint,
124
- * separated from the parse/serialize I/O so it lives in the mutation-gated
125
- * counter module and is directly unit-testable with constructed counts.
126
- *
127
- * before = null → opaque body, nothing to check (no warning).
128
- * after = null → the serialization did not re-parse: total loss.
129
- * counts differ → a node/edge/group was dropped or duplicated.
130
- * counts equal → faithful (no warning).
131
- */
132
- export function faithfulnessWarning(
133
- before: StructuralCount | null,
134
- after: StructuralCount | null,
135
- ): LayoutWarning[] {
136
- if (!before) return []
137
- if (!after) return [{ code: 'CONTENT_DROPPED_ON_ROUNDTRIP', before, after: { nodes: 0, edges: 0, groups: 0 } }]
138
- if (!countsEqual(before, after)) return [{ code: 'CONTENT_DROPPED_ON_ROUNDTRIP', before, after }]
139
- return []
140
- }
141
-
142
- /**
143
- * Boolean view of {@link faithfulnessWarning} for the differential gates
144
- * (corpus / seqbench / upstream), which only need "did content drop?" — reads
145
- * better than `faithfulnessWarning(...).length > 0` at the call sites and is
146
- * independently testable.
147
- */
148
- export function isDrop(before: StructuralCount | null, after: StructuralCount | null): boolean {
149
- return faithfulnessWarning(before, after).length > 0
150
- }
@@ -1,280 +0,0 @@
1
- // ============================================================================
2
- // Timeline structured body: parse / serialize / mutate (FamilyPlugin hooks).
3
- //
4
- // Structured-or-null parse, same pattern as sequence: any unmodeled line
5
- // makes the caller fall back to a lossless opaque body.
6
- // ============================================================================
7
-
8
- import { unknownOpMessage } from './mutation-ops.ts'
9
- import type {
10
- TimelineBody, TimelineSection, TimelinePeriod, TimelineEvent,
11
- TimelineMutationOp, MutationError, Result,
12
- } from './types.ts'
13
- import { ok, err } from './types.ts'
14
-
15
- // ---- Parser -----------------------------------------------------------------
16
-
17
- const TL_TITLE_RE = /^title\s+(.+)$/i
18
- const TL_SECTION_RE = /^section\s+(.+)$/i
19
- const TL_PERIOD_RE = /^([^:]+?)\s*:\s*(.*)$/
20
- const TL_CONT_RE = /^:\s*(.+)$/ // continuation of previous period
21
-
22
- /**
23
- * Parse the body lines of a timeline diagram. Returns a structured body only
24
- * if EVERY non-blank, non-comment line is one of: title, section, period
25
- * (`<label> : <event>` and `: <event>` continuations), or a multi-event line
26
- * with extra `:` separators. Otherwise returns null so caller falls back to
27
- * a lossless opaque body.
28
- *
29
- * Mirrors the legacy parser's accepted syntax (src/timeline/parser.ts).
30
- */
31
- function normalizeTimelineText(value: string): string {
32
- return value.split(/\r?\n/).map(part => part.trim()).filter(Boolean).join(' ')
33
- }
34
-
35
- function validTimelineText(value: string, opts: { allowColon: boolean }): boolean {
36
- return value.length > 0 && (opts.allowColon || !value.includes(':'))
37
- }
38
-
39
- function parseTimelineEventSegments(raw: string): string[] | null {
40
- if (raw.trim().length === 0) return []
41
- const segments = raw.split(':').map(normalizeTimelineText)
42
- if (segments.some(segment => !validTimelineText(segment, { allowColon: false }))) return null
43
- return segments
44
- }
45
-
46
- export function parseTimelineBody(lines: string[]): TimelineBody | null {
47
- const body: TimelineBody = { kind: 'timeline', sections: [] }
48
- let currentSection: TimelineSection | undefined
49
- let currentPeriod: TimelinePeriod | undefined
50
- let sIdx = 0, pIdx = 0, eIdx = 0
51
-
52
- const implicitSection = (): TimelineSection => {
53
- if (!currentSection) {
54
- currentSection = { id: `section-${sIdx++}`, periods: [] }
55
- body.sections.push(currentSection)
56
- }
57
- return currentSection
58
- }
59
-
60
- for (const raw of lines) {
61
- const line = raw.trim()
62
- if (!line) continue
63
- if (line.startsWith('%%')) continue
64
-
65
- const tm = line.match(TL_TITLE_RE)
66
- if (tm) {
67
- const title = normalizeTimelineText(tm[1]!)
68
- if (!validTimelineText(title, { allowColon: true })) return null
69
- body.title = title
70
- continue
71
- }
72
-
73
- const sm = line.match(TL_SECTION_RE)
74
- if (sm) {
75
- const label = normalizeTimelineText(sm[1]!)
76
- if (!validTimelineText(label, { allowColon: true })) return null
77
- currentSection = { id: `section-${sIdx++}`, label, periods: [] }
78
- body.sections.push(currentSection)
79
- currentPeriod = undefined
80
- continue
81
- }
82
-
83
- // `: <continuation>` — extra event on the previous period.
84
- const cont = line.match(TL_CONT_RE)
85
- if (cont && !line.match(TL_PERIOD_RE)) {
86
- if (!currentPeriod) return null
87
- const text = normalizeTimelineText(cont[1]!)
88
- if (!validTimelineText(text, { allowColon: false })) return null
89
- currentPeriod.events.push({ id: `event-${eIdx++}`, text })
90
- continue
91
- }
92
-
93
- // `<label> : <event> [ : <event2> : <event3> …]`
94
- const pm = line.match(TL_PERIOD_RE)
95
- if (pm) {
96
- const label = normalizeTimelineText(pm[1]!)
97
- if (!validTimelineText(label, { allowColon: false })) return null
98
- const restRaw = pm[2]!
99
- // Multi-event lines allow `: extra` segments to add more events to the same period.
100
- const eventTexts = parseTimelineEventSegments(restRaw)
101
- if (!eventTexts) return null
102
- const events: TimelineEvent[] = eventTexts.map(text => ({
103
- id: `event-${eIdx++}`, text,
104
- }))
105
- const period: TimelinePeriod = { id: `period-${pIdx++}`, label, events }
106
- implicitSection().periods.push(period)
107
- currentPeriod = period
108
- continue
109
- }
110
-
111
- // Unmodeled line → opaque fallback.
112
- return null
113
- }
114
-
115
- return body
116
- }
117
-
118
- // ---- Serializer -------------------------------------------------------------
119
-
120
- export function renderTimeline(body: TimelineBody): string {
121
- const lines: string[] = ['timeline']
122
- if (body.title) lines.push(` title ${body.title}`)
123
- for (const section of body.sections) {
124
- if (section.label !== undefined) lines.push(` section ${section.label}`)
125
- for (const period of section.periods) {
126
- // First event on the same line as the period label; extra events on
127
- // continuation lines (`: text`). Matches Mermaid timeline syntax.
128
- if (period.events.length === 0) {
129
- lines.push(` ${period.label} :`)
130
- continue
131
- }
132
- lines.push(` ${period.label} : ${period.events[0]!.text}`)
133
- for (const e of period.events.slice(1)) lines.push(` : ${e.text}`)
134
- }
135
- }
136
- return lines.join('\n') + '\n'
137
- }
138
-
139
- // ---- Mutator ----------------------------------------------------------------
140
-
141
- function cloneTimeline(b: TimelineBody): TimelineBody {
142
- return {
143
- kind: 'timeline',
144
- title: b.title,
145
- sections: b.sections.map(s => ({
146
- id: s.id, label: s.label,
147
- periods: s.periods.map(p => ({ id: p.id, label: p.label, events: p.events.map(e => ({ id: e.id, text: e.text })) })),
148
- })),
149
- }
150
- }
151
-
152
- function makeTimelineIdAllocator(body: TimelineBody): (prefix: 'section' | 'period' | 'event') => string {
153
- const seen = new Set<string>()
154
- for (const s of body.sections) {
155
- seen.add(s.id)
156
- for (const p of s.periods) { seen.add(p.id); for (const e of p.events) seen.add(e.id) }
157
- }
158
- return prefix => {
159
- let n = 0
160
- while (seen.has(`${prefix}-${n}`)) n++
161
- const id = `${prefix}-${n}`
162
- seen.add(id)
163
- return id
164
- }
165
- }
166
-
167
- function normalizeTimelineOpText(value: string, opts: { field: string; allowColon?: boolean }): Result<string, MutationError> {
168
- if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Timeline ${opts.field} must be a string` })
169
- const normalized = normalizeTimelineText(value)
170
- if (!validTimelineText(normalized, { allowColon: opts.allowColon ?? true })) {
171
- return err({ code: 'INVALID_OP', message: `Timeline ${opts.field} must be non-empty${opts.allowColon === false ? ' and must not contain :' : ''}` })
172
- }
173
- return ok(normalized)
174
- }
175
-
176
- export function mutateTimeline(input: TimelineBody, op: TimelineMutationOp): Result<TimelineBody, MutationError> {
177
- const body = cloneTimeline(input)
178
- const nextTimelineId = makeTimelineIdAllocator(body)
179
-
180
- const getSection = (i: number): TimelineSection | undefined => body.sections[i]
181
- const getPeriod = (si: number, pi: number): TimelinePeriod | undefined => getSection(si)?.periods[pi]
182
-
183
- switch (op.kind) {
184
- case 'set_title': {
185
- if (op.title === null) delete body.title
186
- else {
187
- const title = normalizeTimelineOpText(op.title, { field: 'title' })
188
- if (!title.ok) return title
189
- body.title = title.value
190
- }
191
- break
192
- }
193
- case 'add_section': {
194
- const label = normalizeTimelineOpText(op.label, { field: 'section label' })
195
- if (!label.ok) return label
196
- body.sections.push({ id: nextTimelineId('section'), label: label.value, periods: [] })
197
- break
198
- }
199
- case 'remove_section': {
200
- if (!getSection(op.index)) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
201
- body.sections.splice(op.index, 1)
202
- break
203
- }
204
- case 'set_section_label': {
205
- const s = getSection(op.index)
206
- if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
207
- const label = normalizeTimelineOpText(op.label, { field: 'section label' })
208
- if (!label.ok) return label
209
- s.label = label.value
210
- break
211
- }
212
- case 'add_period': {
213
- const s = getSection(op.sectionIndex)
214
- if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
215
- const label = normalizeTimelineOpText(op.label, { field: 'period label', allowColon: false })
216
- if (!label.ok) return label
217
- const events: TimelinePeriod['events'] = []
218
- for (const raw of op.events ?? []) {
219
- const text = normalizeTimelineOpText(raw, { field: 'event text', allowColon: false })
220
- if (!text.ok) return text
221
- events.push({ id: nextTimelineId('event'), text: text.value })
222
- }
223
- const period: TimelinePeriod = {
224
- id: nextTimelineId('period'),
225
- label: label.value,
226
- events,
227
- }
228
- s.periods.push(period)
229
- break
230
- }
231
- case 'remove_period': {
232
- const s = getSection(op.sectionIndex)
233
- if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
234
- if (!s.periods[op.periodIndex]) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at index ${op.periodIndex}` })
235
- s.periods.splice(op.periodIndex, 1)
236
- if (s.label === undefined && s.periods.length === 0) body.sections.splice(op.sectionIndex, 1)
237
- break
238
- }
239
- case 'set_period_label': {
240
- const p = getPeriod(op.sectionIndex, op.periodIndex)
241
- if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
242
- const label = normalizeTimelineOpText(op.label, { field: 'period label', allowColon: false })
243
- if (!label.ok) return label
244
- p.label = label.value
245
- break
246
- }
247
- case 'add_event': {
248
- const p = getPeriod(op.sectionIndex, op.periodIndex)
249
- if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
250
- const text = normalizeTimelineOpText(op.text, { field: 'event text', allowColon: false })
251
- if (!text.ok) return text
252
- p.events.push({ id: nextTimelineId('event'), text: text.value })
253
- break
254
- }
255
- case 'remove_event': {
256
- const p = getPeriod(op.sectionIndex, op.periodIndex)
257
- if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
258
- if (!p.events[op.eventIndex]) return err({ code: 'EVENT_NOT_FOUND', message: `No event at index ${op.eventIndex}` })
259
- p.events.splice(op.eventIndex, 1)
260
- const section = getSection(op.sectionIndex)
261
- if (section && section.label === undefined && section.periods.length === 0) body.sections.splice(op.sectionIndex, 1)
262
- break
263
- }
264
- case 'set_event_text': {
265
- const p = getPeriod(op.sectionIndex, op.periodIndex)
266
- if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
267
- const e = p.events[op.eventIndex]
268
- if (!e) return err({ code: 'EVENT_NOT_FOUND', message: `No event at index ${op.eventIndex}` })
269
- const text = normalizeTimelineOpText(op.text, { field: 'event text', allowColon: false })
270
- if (!text.ok) return text
271
- e.text = text.value
272
- break
273
- }
274
- default: {
275
- const _x: never = op
276
- return err({ code: 'INVALID_OP', message: unknownOpMessage('timeline', _x) })
277
- }
278
- }
279
- return ok(body)
280
- }
@@ -1,43 +0,0 @@
1
- // Shared OBSERVED tool-use sink. When the AM_TRACE_LOG env var names a file,
2
- // every agent-facing entry point — the `am` CLI, the library functions
3
- // (verifyMermaid / mutate / buildMermaid / …), and the hosted MCP verify/mutate/
4
- // build tools — appends one `{verb}` JSON line here. The agent-usage eval reads
5
- // this to grade traceOk from the calls an agent ACTUALLY made, regardless of
6
- // which channel it drove, instead of inferring tool use from its Trace prose
7
- // (see eval/agent-usage/RUNBOOK.md).
8
- //
9
- // Portability: this module is bundled for the browser (the website editor runs
10
- // the library), so it must NOT statically import `node:fs`. It reaches fs at
11
- // runtime via `process.getBuiltinModule` — absent in browsers/Workers, so it
12
- // degrades to a no-op there. Logging is best-effort and never throws into the
13
- // caller: a diagram operation must never fail because its trace line couldn't
14
- // be written.
15
-
16
- type AppendFn = (path: string, data: string) => void
17
-
18
- let appendFn: AppendFn | null | undefined // undefined = not yet probed
19
-
20
- function resolveAppend(): AppendFn | null {
21
- if (appendFn !== undefined) return appendFn
22
- appendFn = null
23
- try {
24
- const proc = (globalThis as { process?: { getBuiltinModule?: (m: string) => unknown } }).process
25
- const fs = proc?.getBuiltinModule?.('node:fs') as { appendFileSync?: AppendFn } | undefined
26
- if (typeof fs?.appendFileSync === 'function') appendFn = fs.appendFileSync
27
- } catch { /* no node fs available (browser / Worker) */ }
28
- return appendFn
29
- }
30
-
31
- /**
32
- * Append `{verb}` (or `{verb, ok}`) to $AM_TRACE_LOG if set. No-op when unset or
33
- * off-node. Pass `ok` for verbs whose outcome is meaningful (mutate/build): an
34
- * `{ok:false}` line records a FAILED op attempt, so the eval can measure a run's
35
- * error rate from observed failures rather than inferring retries from call
36
- * counts. Verbs with no natural pass/fail (verify, capabilities) omit it.
37
- */
38
- export function logToolInvocation(verb: string, ok?: boolean): void {
39
- const path = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.AM_TRACE_LOG
40
- if (!path) return
41
- const record = ok === undefined ? { verb } : { verb, ok }
42
- try { resolveAppend()?.(path, JSON.stringify(record) + '\n') } catch { /* best-effort */ }
43
- }