agentic-mermaid 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/CHANGELOG.md +144 -4
  2. package/LICENSES/Apache-2.0.txt +202 -0
  3. package/README.md +25 -32
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/dist/agent-core.d.ts +463 -0
  6. package/dist/agent-core.js +397 -0
  7. package/dist/agent.d.ts +73 -1868
  8. package/dist/agent.js +336 -37325
  9. package/dist/agentic-mermaid-mcp.js +7 -38399
  10. package/dist/am.js +33632 -40445
  11. package/dist/chunk-4QECACIA.js +4702 -0
  12. package/dist/chunk-CDPKJ2BQ.js +37 -0
  13. package/dist/chunk-QKDLMOJJ.js +3487 -0
  14. package/dist/chunk-WI35L7IR.js +615 -0
  15. package/dist/chunk-WN3YDOBG.js +149 -0
  16. package/dist/chunk-ZYMMKQ3I.js +62637 -0
  17. package/dist/index-D54dqSdC.d.ts +6777 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +310 -33014
  20. package/dist/mcp-cli-SBQSTJFC.js +17 -0
  21. package/docs/README.md +14 -6
  22. package/docs/schemas/style-spec.schema.json +1221 -30
  23. package/examples/styles/catalog.json +54 -0
  24. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  25. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  26. package/examples/styles/cupertino-prototype.mmd +10 -0
  27. package/examples/styles/cupertino-prototype.style.json +63 -0
  28. package/examples/styles/mid-century-report.style.json +2 -2
  29. package/examples/styles/star-chart-atlas.style.json +2 -1
  30. package/examples/styles/transit-route-map.style.json +2 -2
  31. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  32. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  33. package/package.json +68 -43
  34. package/server.json +2 -2
  35. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  36. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  37. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  38. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  39. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  40. package/AGENT_NATIVE.md +0 -607
  41. package/Instructions_for_agents.md +0 -89
  42. package/TODO.md +0 -684
  43. package/assets/hero.png +0 -0
  44. package/dist/index-wjRjbsP-.d.ts +0 -1102
  45. package/docs/agent-api-cookbook.md +0 -308
  46. package/docs/agent-mutation-policy.md +0 -104
  47. package/docs/agent-workflow-examples.md +0 -46
  48. package/docs/api.md +0 -211
  49. package/docs/ascii.md +0 -103
  50. package/docs/assets/improvements/box-start-connector.png +0 -0
  51. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  52. package/docs/assets/improvements/er-content-loss.png +0 -0
  53. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  54. package/docs/assets/improvements/gantt-family.png +0 -0
  55. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  56. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  57. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  58. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  59. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  60. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  61. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  62. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  63. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  64. package/docs/comparison.md +0 -94
  65. package/docs/config.md +0 -105
  66. package/docs/contributing/adding-diagram-types.md +0 -144
  67. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  68. package/docs/contributing/diagram-family-citizenship.md +0 -82
  69. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  70. package/docs/contributing/lessons-learned.md +0 -276
  71. package/docs/contributing/releasing.md +0 -82
  72. package/docs/contributing/visual-review-evidence.md +0 -53
  73. package/docs/custom-style-cookbook.md +0 -154
  74. package/docs/design/families/README.md +0 -26
  75. package/docs/design/families/architecture-beta.md +0 -48
  76. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  77. package/docs/design/families/gantt-research.md +0 -137
  78. package/docs/design/families/gantt.md +0 -329
  79. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  80. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  81. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  82. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  83. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  84. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  85. package/docs/design/families/journey-migration-parity.md +0 -439
  86. package/docs/design/families/journey-section-overlap-after.png +0 -0
  87. package/docs/design/families/journey-section-overlap-before.png +0 -0
  88. package/docs/design/families/journey-usage-research.md +0 -423
  89. package/docs/design/families/journey.md +0 -153
  90. package/docs/design/families/xychart.md +0 -85
  91. package/docs/design/system/README.md +0 -45
  92. package/docs/design/system/abstraction-audit.md +0 -224
  93. package/docs/design/system/abstraction-recommendations.md +0 -655
  94. package/docs/design/system/architecture.mmd +0 -29
  95. package/docs/design/system/architecture.svg +0 -137
  96. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  97. package/docs/design/system/layout-rubric.md +0 -138
  98. package/docs/design/system/route-contracts.md +0 -848
  99. package/docs/design/system/source-preservation-ladder.md +0 -52
  100. package/docs/design/system/styles-rollout.md +0 -216
  101. package/docs/design/system/ugly-layouts.md +0 -119
  102. package/docs/diagram-families.md +0 -193
  103. package/docs/features.md +0 -147
  104. package/docs/fork-differences.md +0 -130
  105. package/docs/getting-started.md +0 -175
  106. package/docs/issue-derived-test-cases.md +0 -156
  107. package/docs/layout-characterization/README.md +0 -245
  108. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  109. package/docs/layout-characterization/contact-sheet.md +0 -638
  110. package/docs/layout-characterization/properties.md +0 -281
  111. package/docs/layout-characterization/visual-quality.md +0 -160
  112. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  113. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  114. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  115. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  116. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  117. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  118. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  119. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  120. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  121. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  122. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  123. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  124. package/docs/mcp-code-mode-rationale.md +0 -101
  125. package/docs/mcp-http-transport.md +0 -223
  126. package/docs/mermaid-layout-complaints.md +0 -837
  127. package/docs/mutation-testing.md +0 -206
  128. package/docs/project/divergences.md +0 -98
  129. package/docs/project/dogfooding-docs-strategy.md +0 -66
  130. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  131. package/docs/project/lessons-learned.md +0 -1019
  132. package/docs/project/remove-role-styling-plan.md +0 -176
  133. package/docs/quality.md +0 -215
  134. package/docs/react.md +0 -97
  135. package/docs/style-authoring.md +0 -133
  136. package/docs/testing-strategy.md +0 -335
  137. package/docs/theming.md +0 -162
  138. package/examples/agent-improve-auth-flow.ts +0 -206
  139. package/examples/agent-loop.ts +0 -53
  140. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  141. package/llms.txt +0 -104
  142. package/skill-evals/README.md +0 -77
  143. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  144. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  145. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  146. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  147. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  148. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  149. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  150. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  151. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  152. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  153. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  154. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  155. package/skill-evals/shared-benchmark.json +0 -510
  156. package/src/agent/analyze.ts +0 -157
  157. package/src/agent/apply.ts +0 -146
  158. package/src/agent/architecture-body.ts +0 -458
  159. package/src/agent/certificates.ts +0 -110
  160. package/src/agent/class-body.ts +0 -319
  161. package/src/agent/core.ts +0 -207
  162. package/src/agent/create.ts +0 -106
  163. package/src/agent/describe.ts +0 -368
  164. package/src/agent/er-body.ts +0 -224
  165. package/src/agent/facts.ts +0 -360
  166. package/src/agent/families-builtin.ts +0 -920
  167. package/src/agent/families.ts +0 -232
  168. package/src/agent/family-layouts.ts +0 -832
  169. package/src/agent/flowchart-body.ts +0 -372
  170. package/src/agent/flowchart-unsupported.ts +0 -243
  171. package/src/agent/font-coverage.ts +0 -288
  172. package/src/agent/gantt-body.ts +0 -495
  173. package/src/agent/index.ts +0 -10
  174. package/src/agent/journey-body.ts +0 -374
  175. package/src/agent/label-metrics.ts +0 -28
  176. package/src/agent/layout-to-rendered.ts +0 -63
  177. package/src/agent/mutate.ts +0 -97
  178. package/src/agent/mutation-ops.ts +0 -51
  179. package/src/agent/op-schema.ts +0 -443
  180. package/src/agent/parse.ts +0 -223
  181. package/src/agent/pie-body.ts +0 -270
  182. package/src/agent/png.ts +0 -171
  183. package/src/agent/quadrant-body.ts +0 -336
  184. package/src/agent/quality.ts +0 -363
  185. package/src/agent/readability-audit.ts +0 -92
  186. package/src/agent/rendered-route-audit.ts +0 -61
  187. package/src/agent/sequence-body.ts +0 -345
  188. package/src/agent/serialize.ts +0 -214
  189. package/src/agent/state-body.ts +0 -589
  190. package/src/agent/structural-count.ts +0 -150
  191. package/src/agent/timeline-body.ts +0 -280
  192. package/src/agent/trace-log.ts +0 -43
  193. package/src/agent/types.ts +0 -995
  194. package/src/agent/verify.ts +0 -603
  195. package/src/agent/xychart-body.ts +0 -472
  196. package/src/architecture/config.ts +0 -282
  197. package/src/architecture/layout.ts +0 -496
  198. package/src/architecture/parser.ts +0 -375
  199. package/src/architecture/renderer.ts +0 -611
  200. package/src/architecture/types.ts +0 -113
  201. package/src/ascii/ansi.ts +0 -423
  202. package/src/ascii/architecture.ts +0 -210
  203. package/src/ascii/canvas.ts +0 -443
  204. package/src/ascii/class-diagram.ts +0 -697
  205. package/src/ascii/converter.ts +0 -475
  206. package/src/ascii/draw.ts +0 -1693
  207. package/src/ascii/edge-bundling.ts +0 -328
  208. package/src/ascii/edge-routing.ts +0 -330
  209. package/src/ascii/er-diagram.ts +0 -439
  210. package/src/ascii/gantt.ts +0 -276
  211. package/src/ascii/grid.ts +0 -747
  212. package/src/ascii/index.ts +0 -196
  213. package/src/ascii/journey.ts +0 -169
  214. package/src/ascii/meta.ts +0 -244
  215. package/src/ascii/multiline-utils.ts +0 -80
  216. package/src/ascii/pathfinder.ts +0 -301
  217. package/src/ascii/pie.ts +0 -67
  218. package/src/ascii/quadrant.ts +0 -158
  219. package/src/ascii/reverse.ts +0 -216
  220. package/src/ascii/sequence.ts +0 -499
  221. package/src/ascii/shapes/circle.ts +0 -27
  222. package/src/ascii/shapes/corners.ts +0 -143
  223. package/src/ascii/shapes/diamond.ts +0 -27
  224. package/src/ascii/shapes/hexagon.ts +0 -27
  225. package/src/ascii/shapes/index.ts +0 -106
  226. package/src/ascii/shapes/rectangle.ts +0 -168
  227. package/src/ascii/shapes/rounded.ts +0 -27
  228. package/src/ascii/shapes/special.ts +0 -332
  229. package/src/ascii/shapes/stadium.ts +0 -110
  230. package/src/ascii/shapes/state.ts +0 -192
  231. package/src/ascii/shapes/types.ts +0 -73
  232. package/src/ascii/timeline.ts +0 -141
  233. package/src/ascii/types.ts +0 -312
  234. package/src/ascii/validate.ts +0 -120
  235. package/src/ascii/width.ts +0 -81
  236. package/src/ascii/wrap.ts +0 -131
  237. package/src/ascii/xychart.ts +0 -919
  238. package/src/browser.ts +0 -29
  239. package/src/class/layout.ts +0 -240
  240. package/src/class/parser.ts +0 -332
  241. package/src/class/renderer.ts +0 -638
  242. package/src/class/types.ts +0 -129
  243. package/src/cli/agent-instructions.ts +0 -1
  244. package/src/cli/am-bin.ts +0 -6
  245. package/src/cli/exit-codes.ts +0 -18
  246. package/src/cli/index.ts +0 -1132
  247. package/src/cli/init-agent.ts +0 -147
  248. package/src/cli/run-entrypoint.ts +0 -10
  249. package/src/color-resolver.ts +0 -152
  250. package/src/elk-instance.ts +0 -114
  251. package/src/er/layout.ts +0 -187
  252. package/src/er/parser.ts +0 -226
  253. package/src/er/renderer.ts +0 -792
  254. package/src/er/types.ts +0 -99
  255. package/src/family-rubric.ts +0 -425
  256. package/src/font-manifest.ts +0 -31
  257. package/src/gantt/layout.ts +0 -365
  258. package/src/gantt/parser.ts +0 -282
  259. package/src/gantt/pipeline.ts +0 -27
  260. package/src/gantt/renderer.ts +0 -414
  261. package/src/gantt/schedule.ts +0 -513
  262. package/src/gantt/types.ts +0 -255
  263. package/src/index.ts +0 -368
  264. package/src/journey/layout.ts +0 -554
  265. package/src/journey/parse-core.ts +0 -251
  266. package/src/journey/parser.ts +0 -101
  267. package/src/journey/renderer.ts +0 -917
  268. package/src/journey/types.ts +0 -144
  269. package/src/layout/geometry.ts +0 -182
  270. package/src/layout/pass.ts +0 -142
  271. package/src/layout/passes/index.ts +0 -3916
  272. package/src/layout-engine.ts +0 -1565
  273. package/src/layout-rubric.ts +0 -659
  274. package/src/mcp/artifacts.ts +0 -137
  275. package/src/mcp/dynamic-harness.ts +0 -31
  276. package/src/mcp/dynamic-modules.d.ts +0 -7
  277. package/src/mcp/facade.ts +0 -608
  278. package/src/mcp/harness-runtime.ts +0 -203
  279. package/src/mcp/hosted-server.ts +0 -468
  280. package/src/mcp/mcp-bin.ts +0 -3
  281. package/src/mcp/mcp-cli.ts +0 -82
  282. package/src/mcp/protocol.ts +0 -13
  283. package/src/mcp/sandbox.ts +0 -195
  284. package/src/mcp/sdk-decl.ts +0 -391
  285. package/src/mcp/server.ts +0 -362
  286. package/src/mcp/tool-surface.ts +0 -146
  287. package/src/mermaid-source.ts +0 -655
  288. package/src/multiline-utils.ts +0 -228
  289. package/src/parser.ts +0 -1031
  290. package/src/pie/layout.ts +0 -168
  291. package/src/pie/parser.ts +0 -136
  292. package/src/pie/renderer.ts +0 -234
  293. package/src/pie/types.ts +0 -82
  294. package/src/quadrant/layout.ts +0 -191
  295. package/src/quadrant/parser.ts +0 -183
  296. package/src/quadrant/renderer.ts +0 -322
  297. package/src/quadrant/types.ts +0 -113
  298. package/src/render-family-hooks.ts +0 -244
  299. package/src/renderer.ts +0 -1099
  300. package/src/route-contracts.ts +0 -2723
  301. package/src/scene/backend.ts +0 -76
  302. package/src/scene/fidelity.ts +0 -183
  303. package/src/scene/hybrid-backend.ts +0 -174
  304. package/src/scene/ir.ts +0 -181
  305. package/src/scene/marks.ts +0 -127
  306. package/src/scene/rough-backend.ts +0 -378
  307. package/src/scene/seed.ts +0 -46
  308. package/src/scene/style-registry.ts +0 -556
  309. package/src/sequence/layout.ts +0 -456
  310. package/src/sequence/parser.ts +0 -255
  311. package/src/sequence/renderer.ts +0 -604
  312. package/src/sequence/types.ts +0 -154
  313. package/src/shape-clipping.ts +0 -351
  314. package/src/shared/accessibility-directives.ts +0 -22
  315. package/src/shared/batched.ts +0 -61
  316. package/src/shared/color-math.ts +0 -93
  317. package/src/shared/svg-a11y.ts +0 -29
  318. package/src/shared/syntax-error.ts +0 -17
  319. package/src/shared/unicode-ranges.ts +0 -135
  320. package/src/styles.ts +0 -318
  321. package/src/svg-paths.ts +0 -28
  322. package/src/text-metrics.ts +0 -248
  323. package/src/theme.ts +0 -710
  324. package/src/timeline/layout.ts +0 -381
  325. package/src/timeline/parser.ts +0 -215
  326. package/src/timeline/renderer.ts +0 -538
  327. package/src/timeline/types.ts +0 -105
  328. package/src/types.ts +0 -462
  329. package/src/website-worker-modules.d.ts +0 -18
  330. package/src/xychart/axis-utils.ts +0 -109
  331. package/src/xychart/colors.ts +0 -124
  332. package/src/xychart/config.ts +0 -88
  333. package/src/xychart/layout.ts +0 -656
  334. package/src/xychart/parser.ts +0 -504
  335. package/src/xychart/renderer.ts +0 -640
  336. package/src/xychart/types.ts +0 -282
@@ -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
- }