brane 0.0.2 → 0.3.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 (338) hide show
  1. package/AGENTS.md +362 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +91 -0
  4. package/DESIGN.md +402 -0
  5. package/Dockerfile +61 -0
  6. package/LICENSE +656 -17
  7. package/README.md +174 -2
  8. package/SECURITY.md +205 -0
  9. package/dist/absorb/brane-id.js +11 -0
  10. package/dist/absorb/chatgpt.js +161 -0
  11. package/dist/absorb/claude-code.js +504 -0
  12. package/dist/absorb/index.js +543 -0
  13. package/dist/absorb/organelle.js +345 -0
  14. package/dist/absorb/run.js +520 -0
  15. package/dist/absorb/sources.js +168 -0
  16. package/dist/absorb/types.js +3 -0
  17. package/dist/agent-env.js +45 -0
  18. package/dist/agents.js +1168 -0
  19. package/dist/claude-live-session.js +124 -0
  20. package/dist/cli.js +50 -0
  21. package/dist/codex-tool-log.js +144 -0
  22. package/dist/commands/absorb.js +291 -0
  23. package/dist/commands/attach.js +31 -0
  24. package/dist/commands/certify.js +62 -0
  25. package/dist/commands/config.js +116 -0
  26. package/dist/commands/cost.js +126 -0
  27. package/dist/commands/daemon.js +350 -0
  28. package/dist/commands/doctor.js +262 -0
  29. package/dist/commands/fold.js +95 -0
  30. package/dist/commands/list.js +89 -0
  31. package/dist/commands/mcp.js +84 -0
  32. package/dist/commands/meld.js +332 -0
  33. package/dist/commands/memory.js +215 -0
  34. package/dist/commands/revive.js +74 -0
  35. package/dist/commands/run.js +85 -0
  36. package/dist/commands/spawn.js +1094 -0
  37. package/dist/commands/token.js +153 -0
  38. package/dist/copilot-tool-log.js +145 -0
  39. package/dist/cost/pricing.js +116 -0
  40. package/dist/cost/reprice.js +136 -0
  41. package/dist/crush-tool-log.js +293 -0
  42. package/dist/daemon/agent-scope.js +116 -0
  43. package/dist/daemon/awaiting.js +54 -0
  44. package/dist/daemon/cert-manager.js +349 -0
  45. package/dist/daemon/client.js +262 -0
  46. package/dist/daemon/gated-routes.js +112 -0
  47. package/dist/daemon/keep-awake.js +103 -0
  48. package/dist/daemon/local-socket.js +145 -0
  49. package/dist/daemon/presence-gate.js +206 -0
  50. package/dist/daemon/server.js +6349 -0
  51. package/dist/daemon/signal-source.js +35 -0
  52. package/dist/daemon/tailscale-rebind.js +58 -0
  53. package/dist/daemon/token-ring.js +263 -0
  54. package/dist/daemon/token-routes.js +190 -0
  55. package/dist/db/events.js +152 -0
  56. package/dist/db/mentions.js +179 -0
  57. package/dist/db/schema.js +633 -0
  58. package/dist/db/tokens.js +583 -0
  59. package/dist/db/tool-uses.js +647 -0
  60. package/dist/diagram/index.js +85 -0
  61. package/dist/diagram/label.js +455 -0
  62. package/dist/diagram/layout.js +476 -0
  63. package/dist/diagram/links.js +50 -0
  64. package/dist/diagram/metrics.js +152 -0
  65. package/dist/diagram/parse.js +487 -0
  66. package/dist/diagram/svg.js +389 -0
  67. package/dist/diagram/types.js +9 -0
  68. package/dist/export.js +629 -0
  69. package/dist/files/archive-types.js +2 -0
  70. package/dist/files/archive.js +182 -0
  71. package/dist/files/browse.js +327 -0
  72. package/dist/files/bytes.js +162 -0
  73. package/dist/files/codecs.js +33 -0
  74. package/dist/files/find.js +212 -0
  75. package/dist/files/tar.js +223 -0
  76. package/dist/files/zip.js +209 -0
  77. package/dist/folds.js +72 -0
  78. package/dist/grammars/ebnf.js +92 -0
  79. package/dist/highlight.js +250 -0
  80. package/dist/ids.js +24 -0
  81. package/dist/markdown-diagnostics.js +233 -0
  82. package/dist/math.js +168 -0
  83. package/dist/mcp/server.js +886 -0
  84. package/dist/memory/embedder.js +145 -0
  85. package/dist/memory/embedding-worker.js +71 -0
  86. package/dist/memory/embeddings.js +91 -0
  87. package/dist/memory/index.js +232 -0
  88. package/dist/memory/storage.js +587 -0
  89. package/dist/memory/wiki.js +688 -0
  90. package/dist/memory/wordpiece.js +247 -0
  91. package/dist/mentions-scan.js +40 -0
  92. package/dist/presence.js +259 -0
  93. package/dist/session-affinity.js +185 -0
  94. package/dist/shared/api-request.js +124 -0
  95. package/dist/shared/block-glyphs.js +114 -0
  96. package/dist/shared/diagram-css.js +17 -0
  97. package/dist/shared/export-selection.js +101 -0
  98. package/dist/shared/find-in-text.js +110 -0
  99. package/dist/shared/glow-hues.js +225 -0
  100. package/dist/shared/hex-dump.js +62 -0
  101. package/dist/shared/hex-pages.js +52 -0
  102. package/dist/shared/hit-drum.js +79 -0
  103. package/dist/shared/known-nids.js +58 -0
  104. package/dist/shared/list-scroll-keys.js +56 -0
  105. package/dist/shared/markdown-lists.js +72 -0
  106. package/dist/shared/markdown-math.js +485 -0
  107. package/dist/shared/markdown-mentions.js +79 -0
  108. package/dist/shared/markdown-nids.js +41 -0
  109. package/dist/shared/markdown-protocols.js +53 -0
  110. package/dist/shared/markdown-refs.js +123 -0
  111. package/dist/shared/membrane-invasion.js +342 -0
  112. package/dist/shared/membrane-motion.js +83 -0
  113. package/dist/shared/membrane-palette.js +253 -0
  114. package/dist/shared/membrane-terrain.js +212 -0
  115. package/dist/shared/message-selection.js +123 -0
  116. package/dist/shared/mo-glyph.js +57 -0
  117. package/dist/shared/narrative-format.js +237 -0
  118. package/dist/shared/narrative-menu.js +82 -0
  119. package/dist/shared/narrative-sort.js +47 -0
  120. package/dist/shared/narrative-status.js +52 -0
  121. package/dist/shared/no-pan.js +85 -0
  122. package/dist/shared/pane-layout.js +50 -0
  123. package/dist/shared/payload-inline.js +83 -0
  124. package/dist/shared/speaker-color.js +61 -0
  125. package/dist/shared/token-rotation.js +14 -0
  126. package/dist/shared/view-url.js +197 -0
  127. package/dist/shared/wiki-links.js +70 -0
  128. package/dist/shared/wiki-window.js +4 -0
  129. package/dist/tailscale.js +55 -0
  130. package/dist/test/absorb-claude.test.js +892 -0
  131. package/dist/test/agent-scope.test.js +193 -0
  132. package/dist/test/agents.test.js +55 -0
  133. package/dist/test/api-request.test.js +122 -0
  134. package/dist/test/awaiting-glow.test.js +54 -0
  135. package/dist/test/awaiting.test.js +122 -0
  136. package/dist/test/bind-token.test.js +88 -0
  137. package/dist/test/block-glyphs.test.js +68 -0
  138. package/dist/test/cert-manager.test.js +262 -0
  139. package/dist/test/claude-live-session.test.js +110 -0
  140. package/dist/test/codex-tool-log.test.js +568 -0
  141. package/dist/test/config.test.js +71 -0
  142. package/dist/test/copilot-tool-log.test.js +93 -0
  143. package/dist/test/cors-preflight.test.js +49 -0
  144. package/dist/test/cost-linger.test.js +31 -0
  145. package/dist/test/cost-pricing.test.js +72 -0
  146. package/dist/test/cost-reprice.test.js +139 -0
  147. package/dist/test/cost-sessions.test.js +301 -0
  148. package/dist/test/crush-tool-log.test.js +289 -0
  149. package/dist/test/daemon-client.test.js +109 -0
  150. package/dist/test/daemon-socket.test.js +293 -0
  151. package/dist/test/diagram-arrows.test.js +205 -0
  152. package/dist/test/diagram-export.test.js +210 -0
  153. package/dist/test/diagram-labels.test.js +313 -0
  154. package/dist/test/diagram-parse.test.js +191 -0
  155. package/dist/test/diagram-render.test.js +175 -0
  156. package/dist/test/diagram-review.test.js +288 -0
  157. package/dist/test/diagram-self-loop-labels.test.js +303 -0
  158. package/dist/test/embedder.test.js +84 -0
  159. package/dist/test/events.test.js +123 -0
  160. package/dist/test/export-selection.test.js +102 -0
  161. package/dist/test/export.test.js +505 -0
  162. package/dist/test/files-browse.test.js +393 -0
  163. package/dist/test/files-find.test.js +272 -0
  164. package/dist/test/find-in-text.test.js +86 -0
  165. package/dist/test/first-paint-viewport.test.js +59 -0
  166. package/dist/test/fixtures/embedding-golden.js +23 -0
  167. package/dist/test/fixtures/wordpiece-golden.js +167 -0
  168. package/dist/test/glow-hues.test.js +202 -0
  169. package/dist/test/harness-cost.test.js +423 -0
  170. package/dist/test/helpers.js +21 -0
  171. package/dist/test/hex-pages.test.js +68 -0
  172. package/dist/test/highlight.test.js +69 -0
  173. package/dist/test/hit-drum.test.js +71 -0
  174. package/dist/test/hook-config.test.js +262 -0
  175. package/dist/test/json-gzip.test.js +70 -0
  176. package/dist/test/key-routing.test.js +58 -0
  177. package/dist/test/known-nids.test.js +106 -0
  178. package/dist/test/link-preview.test.js +67 -0
  179. package/dist/test/list-scroll-keys.test.js +66 -0
  180. package/dist/test/listen-own-words.test.js +147 -0
  181. package/dist/test/listen-timeout.test.js +33 -0
  182. package/dist/test/markdown-diagnostics.test.js +121 -0
  183. package/dist/test/markdown-lists.test.js +182 -0
  184. package/dist/test/markdown-math.test.js +256 -0
  185. package/dist/test/markdown-nids.test.js +146 -0
  186. package/dist/test/markdown-protocols.test.js +139 -0
  187. package/dist/test/mcp-bridge-cancel.test.js +98 -0
  188. package/dist/test/mcp-bridge-reconnect.test.js +121 -0
  189. package/dist/test/mcp-narrative.test.js +193 -0
  190. package/dist/test/mcp.test.js +548 -0
  191. package/dist/test/meld.test.js +85 -0
  192. package/dist/test/membrane-invasion.test.js +482 -0
  193. package/dist/test/membrane-motion.test.js +101 -0
  194. package/dist/test/membrane-palette.test.js +166 -0
  195. package/dist/test/membrane-terrain.test.js +170 -0
  196. package/dist/test/memory-log.test.js +202 -0
  197. package/dist/test/mentions-e2e.test.js +261 -0
  198. package/dist/test/mentions-ledger.test.js +265 -0
  199. package/dist/test/mentions-listen.test.js +265 -0
  200. package/dist/test/mentions.test.js +238 -0
  201. package/dist/test/message-selection.test.js +114 -0
  202. package/dist/test/mo-glyph.test.js +22 -0
  203. package/dist/test/model-resolution.test.js +166 -0
  204. package/dist/test/narrate-validate.test.js +77 -0
  205. package/dist/test/narrate-wait.test.js +160 -0
  206. package/dist/test/narrative-format.test.js +219 -0
  207. package/dist/test/narrative-menu.test.js +137 -0
  208. package/dist/test/narrative-sort.test.js +75 -0
  209. package/dist/test/no-pan.test.js +60 -0
  210. package/dist/test/pane-layout.test.js +153 -0
  211. package/dist/test/paths.test.js +37 -0
  212. package/dist/test/payload-inline.test.js +91 -0
  213. package/dist/test/precache-fonts.test.js +72 -0
  214. package/dist/test/presence-gate.test.js +440 -0
  215. package/dist/test/presence.test.js +160 -0
  216. package/dist/test/read-events.test.js +220 -0
  217. package/dist/test/remote-meld.test.js +115 -0
  218. package/dist/test/revive-cli.test.js +93 -0
  219. package/dist/test/search-floor.test.js +206 -0
  220. package/dist/test/search-fusion.test.js +168 -0
  221. package/dist/test/search.test.js +205 -0
  222. package/dist/test/security.test.js +48 -0
  223. package/dist/test/selection-wiring.test.js +61 -0
  224. package/dist/test/session-affinity.test.js +295 -0
  225. package/dist/test/signal-source.test.js +32 -0
  226. package/dist/test/socket-account.test.js +268 -0
  227. package/dist/test/spawn-toml.test.js +72 -0
  228. package/dist/test/speaker-color.test.js +90 -0
  229. package/dist/test/tailscale-rebind.test.js +79 -0
  230. package/dist/test/terminal-cursor.test.js +61 -0
  231. package/dist/test/terminal-resync.test.js +23 -0
  232. package/dist/test/thin-cli.test.js +382 -0
  233. package/dist/test/token-bootstrap.test.js +272 -0
  234. package/dist/test/token-devices.test.js +587 -0
  235. package/dist/test/token-lapse.test.js +94 -0
  236. package/dist/test/token-ring.test.js +188 -0
  237. package/dist/test/token-rotate-cli.test.js +58 -0
  238. package/dist/test/token-routes.test.js +95 -0
  239. package/dist/test/tool-uses.test.js +1006 -0
  240. package/dist/test/usage-ledger.test.js +126 -0
  241. package/dist/test/view-url.test.js +581 -0
  242. package/dist/test/wiki-links.test.js +57 -0
  243. package/dist/test/wiki-sources.test.js +85 -0
  244. package/dist/test/wiki-window.test.js +31 -0
  245. package/dist/test/wiki.test.js +399 -0
  246. package/dist/test/wordpiece.test.js +68 -0
  247. package/dist/tls.js +148 -0
  248. package/dist/tmux.js +202 -0
  249. package/dist/user-agent.js +46 -0
  250. package/docker-entrypoint.sh +141 -0
  251. package/docs/README.md +3 -0
  252. package/docs/absorb.md +231 -0
  253. package/docs/cost-ledger.md +125 -0
  254. package/docs/daemon-api.md +189 -0
  255. package/docs/diagrams.md +163 -0
  256. package/docs/doctor.md +36 -0
  257. package/docs/export-scope.md +69 -0
  258. package/docs/files-view.md +133 -0
  259. package/docs/link-previews.md +38 -0
  260. package/docs/math.md +32 -0
  261. package/docs/mcp.md +69 -0
  262. package/docs/memory-search.md +75 -0
  263. package/docs/mentions.md +101 -0
  264. package/docs/multi-brane.md +14 -0
  265. package/docs/passkeys.md +165 -0
  266. package/docs/presence.md +202 -0
  267. package/docs/releasing.md +53 -0
  268. package/docs/rendering-warnings.md +22 -0
  269. package/docs/request-layer.md +72 -0
  270. package/docs/selection-store.md +50 -0
  271. package/docs/session-affinity.md +61 -0
  272. package/docs/terminal-mirroring.md +211 -0
  273. package/docs/token-rotation.md +413 -0
  274. package/docs/view-urls.md +114 -0
  275. package/docs/web-app.md +23 -0
  276. package/docs/wiki.md +43 -0
  277. package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
  278. package/expo/dist/apple-touch-icon.png +0 -0
  279. package/expo/dist/favicon.svg +3 -0
  280. package/expo/dist/index.html +177 -0
  281. package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  282. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  283. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  284. package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  285. package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  286. package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  287. package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  288. package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  289. package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  290. package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  291. package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  292. package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  293. package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  294. package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  295. package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  296. package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  297. package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  298. package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  299. package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  300. package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  301. package/expo/dist/katex/katex.min.css +1 -0
  302. package/expo/dist/manifest.json +16 -0
  303. package/expo/dist/metadata.json +1 -0
  304. package/expo/dist/og/membrane.v1.png +0 -0
  305. package/expo/dist/pdf.min.mjs +28 -0
  306. package/expo/dist/pdf.worker.min.mjs +28 -0
  307. package/expo/dist/sw.js +154 -0
  308. package/package.json +81 -23
  309. package/protocols/LIVE_loop.md +407 -0
  310. package/protocols/bebrief.md +41 -0
  311. package/protocols/digest.md +231 -0
  312. package/protocols/fly.md +266 -0
  313. package/protocols/idle.md +81 -0
  314. package/protocols/keeper.md +829 -0
  315. package/protocols/markdown.md +80 -0
  316. package/protocols/memory.md +419 -0
  317. package/protocols/mentions.md +93 -0
  318. package/protocols/payloads.md +53 -0
  319. package/protocols/prelude.md +91 -0
  320. package/protocols/ramify.md +105 -0
  321. package/protocols/review.md +356 -0
  322. package/protocols/rrr.md +232 -0
  323. package/protocols/scribe.md +429 -0
  324. package/protocols/summarize.md +203 -0
  325. package/protocols/twin.md +174 -0
  326. package/protocols/wiki.md +438 -0
  327. package/protocols/worker.md +38 -0
  328. package/wiki/shared/append-only-event-log.md +148 -0
  329. package/wiki/shared/index.md +48 -0
  330. package/wiki/shared/owning-your-narratives.md +161 -0
  331. package/wiki/shared/premature-ontologies.md +435 -0
  332. package/wiki/shared/sedimentary-superintelligence.md +128 -0
  333. package/wiki/shared/sequestering-the-narrative.md +143 -0
  334. package/wiki/shared/why-wikis-work.md +216 -0
  335. package/.npmignore +0 -2
  336. package/.travis.yml +0 -5
  337. package/index.js +0 -8
  338. package/index.js~ +0 -12
@@ -0,0 +1,487 @@
1
+ /** Parser for the flowchart subset brane renders.
2
+ *
3
+ * Scope is empirical. Across every ```mermaid fence in the archive — 21
4
+ * diagrams, 316 lines, home brane plus the absorbed ChatGPT and organelle
5
+ * branes — the census was:
6
+ *
7
+ * headers flowchart TD 9 · graph TD 8 · flowchart LR 3 · stateDiagram-v2 1
8
+ * arrows --> 131 · <--> 9 · zero -.->, ==>, ---, ~~~
9
+ * shapes [rect] 125 · (round) 9 · [(cyl)] 5 · {diamond} 4 · >asym] 4 · [[sub]] 1
10
+ * also subgraph 10 · & fan-out 13 · <br/> 15 · classDef/class 3+3 · style 2 · %%
11
+ * absent click, linkStyle, and every diagram type but flowchart
12
+ *
13
+ * So: flowcharts, and everything else refuses. A refusal is not a failure —
14
+ * it renders as the highlighted code block a mermaid fence already produces,
15
+ * and `narrate` reports it so the author learns the diagram did not draw.
16
+ *
17
+ * The dotted and thick link forms are parsed although the census counted
18
+ * none: they cost one alternation here and one dash array in the renderer,
19
+ * and refusing them would be a worse first impression than drawing them.
20
+ * That turned out to be the right call for a reason better than taste — run
21
+ * against the archive, this parser found a `-. "…" .->` the census's
22
+ * arrow-shape grep had missed, because it only looked for bare arrows.
23
+ *
24
+ * Current standing against the archive: 20 of 21 draw. The one refusal is
25
+ * the `stateDiagram-v2`, which is a later increment rather than a defect.
26
+ */
27
+ import { parseLabel, plainLabel, textLines } from "./label.js";
28
+ /** A header, with two tolerances mermaid has and the first draft did not:
29
+ * a trailing `;` (statements strip one, but the header was tested raw, so
30
+ * `graph TD;` — a common habit in the absorbed ChatGPT history — was
31
+ * refused), and an absent direction, which mermaid defaults to TB. */
32
+ const HEADER = /^\s*(flowchart|graph)(?:\s+(TB|TD|LR|RL|BT))?\s*;?\s*$/i;
33
+ /** Diagram types brane recognises by name, so the refusal can say which. */
34
+ const OTHER_TYPES = /^\s*(sequenceDiagram|stateDiagram(?:-v2)?|classDiagram|erDiagram|gantt|pie|gitGraph|journey|timeline|mindmap|quadrantChart|xychart(?:-beta)?|requirementDiagram|C4Context|sankey(?:-beta)?|block(?:-beta)?)\b/i;
35
+ /** Openers longest-first: `[[` must beat `[`, `((` must beat `(`. */
36
+ const SHAPES = [
37
+ ["([", "])", "stadium"],
38
+ ["[[", "]]", "subroutine"],
39
+ ["[(", ")]", "cylinder"],
40
+ ["((", "))", "circle"],
41
+ ["{{", "}}", "hexagon"],
42
+ ["[", "]", "rect"],
43
+ ["(", ")", "round"],
44
+ ["{", "}", "diamond"],
45
+ [">", "]", "asymmetric"],
46
+ ];
47
+ /** The inline-label link forms: `A -- text --> B`, `A -. text .-> B`,
48
+ * `A == text ==> B`. Tried before the bare forms, and safe there because each
49
+ * requires whitespace where the bare form requires more dashes.
50
+ *
51
+ * The census found no `-- text -->`, which is why the spec left these out —
52
+ * but running the parser over the archive turned up a `-. "…" .->` in a
53
+ * lattice diagram, so the census undercounted rather than the form being
54
+ * unused.
55
+ *
56
+ * Two properties of the label group are load-bearing, and the obvious
57
+ * `\s+(.+?)\s+` has neither.
58
+ *
59
+ * **It cannot match whitespace at either end.** `\s+(.+?)\s+` makes the
60
+ * boundary between the padding and the label ambiguous at every position, and
61
+ * with three such quantifiers the cost is cubic in the run length: on
62
+ * `A -- <N spaces> x` this measured 17 ms at N=250, 30 ms at 500, 195 ms at
63
+ * 1000 and 1542 ms at 2000 — a clean 8× per doubling. `MAX_SOURCE_BYTES`
64
+ * is no defence, since it bounds *n* while the cost is n³. It mattered
65
+ * because `diagnoseMarkdown` runs on **every narration containing a mermaid
66
+ * fence**, so this was a daemon-wide stall reachable by narrating.
67
+ * `[^\s](?:[^]*?[^\s])?` anchors both ends on a non-space and removes the
68
+ * ambiguity outright.
69
+ *
70
+ * **It cannot contain the link's own punctuation.** `(.+?)` is lazy but will
71
+ * still cross a whole link to reach a later terminator, so `A -.-> B -.-> C`
72
+ * matched one edge `A → C` labelled `-> B -` and dropped B — a plausible,
73
+ * wrong diagram drawn in silence, which is worse than a refusal.
74
+ *
75
+ * What each form excludes differs, and the archive is why. A solid label
76
+ * cannot contain `>`, since that is what stops `A -- x --> B -- y --> C`
77
+ * spanning two links. A *dotted* label may — the lattice diagram's
78
+ * `-. "All<String, true> ⇒ ⊥" .->` needs it — so it excludes `.` instead,
79
+ * which is the dotted closer's own distinguishing character, and additionally
80
+ * forbids a leading `-` or `>`, which is what declines the `-.->` in
81
+ * `A -.-> B -.-> C` before the label ever begins.
82
+ *
83
+ * The cost is that a dotted label containing a literal `.` is not read. The
84
+ * archive has none, and the failure is a refusal — source plus a warning —
85
+ * rather than a wrong picture, which is the trade this whole module makes.
86
+ *
87
+ * **The opener is exactly two dashes**, as in mermaid, not `-{2,}`. Three is
88
+ * an open link, so a greedy opener read `A --- B --> C` as one edge `A → C`
89
+ * labelled `B`, losing a node in silence the same way. With `--`, the `-` in
90
+ * `--- B` cannot be the required whitespace and the form declines, leaving
91
+ * the bare `---` to match as the open link it is.
92
+ */
93
+ const LABELLED_ARROWS = [
94
+ [/^<--[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+-{2,}>/, "biarrow", "solid"],
95
+ [/^-\.[ \t]*([^\s.>-][^.]*[^\s.]|[^\s.>-])[ \t]*\.-*->/, "arrow", "dotted"],
96
+ [/^==[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+={2,}>/, "arrow", "thick"],
97
+ [/^--[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+-{2,}>/, "arrow", "solid"],
98
+ ];
99
+ /** A line longer than this is refused unparsed.
100
+ *
101
+ * Belt to the braces above: the patterns are linear now, but a parser on the
102
+ * `narrate` path should not depend on every future regex staying that way,
103
+ * and no diagram anyone reads has a 2,000-character line. */
104
+ const MAX_LINE_LENGTH = 2000;
105
+ const ARROWS = [
106
+ [/^<-{2,}>/, "biarrow", "solid"],
107
+ [/^<=+>/, "biarrow", "thick"],
108
+ [/^-\.-*->/, "arrow", "dotted"],
109
+ [/^={2,}>/, "arrow", "thick"],
110
+ [/^-{2,}>/, "arrow", "solid"],
111
+ [/^-{3,}/, "open", "solid"],
112
+ ];
113
+ /** A node id. Hyphens are legal in one — `claude-0qjv4nny` is the shape brane
114
+ * cares about most — but a naive `[\w-]*` eats the arrow in `A-->B`, which
115
+ * agents write without spaces. Requiring a word character *after* each hyphen
116
+ * stops the id at the first `--` while still admitting the hyphenated form. */
117
+ const ID = /^[A-Za-z_]\w*(?:-[A-Za-z0-9_]+)*/;
118
+ /** Style properties worth honouring, and the only ones let through. An
119
+ * arbitrary CSS property from a fence is markup we did not write. */
120
+ const SAFE_STYLE = new Set([
121
+ "fill", "stroke", "stroke-width", "stroke-dasharray", "color",
122
+ // The weight and slant of a node's whole label. Dropped in silence before,
123
+ // which is how `classDef key font-weight:bold` drew nothing and said nothing
124
+ // (claude-cdouqlgt:24).
125
+ "font-weight", "font-style",
126
+ ]);
127
+ /** Values are colours, lengths and keywords — never `url(...)`, never a
128
+ * semicolon that could close the declaration and open another. */
129
+ const SAFE_VALUE = /^[#\w\s.,%()-]{1,64}$/;
130
+ function parseStyleBody(body) {
131
+ const out = {};
132
+ for (const decl of body.split(/[,;]/)) {
133
+ const i = decl.indexOf(":");
134
+ if (i < 0)
135
+ continue;
136
+ const prop = decl.slice(0, i).trim().toLowerCase();
137
+ const value = decl.slice(i + 1).trim();
138
+ if (!SAFE_STYLE.has(prop))
139
+ continue;
140
+ if (!SAFE_VALUE.test(value) || /url\s*\(/i.test(value))
141
+ continue;
142
+ if (FONT_VALUES[prop] && !FONT_VALUES[prop].test(value))
143
+ continue;
144
+ out[prop] = value;
145
+ }
146
+ return out;
147
+ }
148
+ /** Values each font property may take. `SAFE_VALUE` alone would pass any
149
+ * word, and a word the SVG doesn't know is one the browser ignores — better
150
+ * dropped here, where the drop is the documented behaviour. */
151
+ const FONT_VALUES = {
152
+ "font-weight": /^(normal|bold|bolder|lighter|[1-9]00)$/i,
153
+ "font-style": /^(normal|italic|oblique)$/i,
154
+ };
155
+ const peek = (c) => c.s.slice(c.i);
156
+ function skipWs(c) { while (c.i < c.s.length && /\s/.test(c.s[c.i]))
157
+ c.i++; }
158
+ /** The end of a label that starts at `from`, given its closing delimiter.
159
+ *
160
+ * Quoting is why this is not `indexOf`. A quoted label may contain the
161
+ * delimiter that would otherwise end it, and the corpus does it twice: a node
162
+ * `ARR["Array[…]"]` whose text holds a `]`, and an edge label
163
+ * `-->|"P(C|A,B)"|` whose text holds a `|` — a conditional probability, where
164
+ * the bar is the notation. Scanning to the first delimiter truncates both and
165
+ * then fails on the remainder.
166
+ *
167
+ * Returns the index of the closer, or -1. */
168
+ function findClose(s, from, close) {
169
+ const q = s[from];
170
+ if (q === '"' || q === "'" || q === "`") {
171
+ const endQuote = s.indexOf(q, from + 1);
172
+ if (endQuote >= 0) {
173
+ const after = s.indexOf(close, endQuote + 1);
174
+ // Only the run between the quote and the closer may be whitespace; a
175
+ // quote that isn't actually wrapping the whole label falls through to
176
+ // the plain scan below.
177
+ if (after >= 0 && !s.slice(endQuote + 1, after).trim())
178
+ return after;
179
+ }
180
+ }
181
+ return s.indexOf(close, from);
182
+ }
183
+ function readNodeRef(c) {
184
+ skipWs(c);
185
+ const m = ID.exec(peek(c));
186
+ if (!m)
187
+ return null;
188
+ const id = m[0];
189
+ c.i += id.length;
190
+ let label;
191
+ let shape;
192
+ for (const [open, close, kind] of SHAPES) {
193
+ if (!peek(c).startsWith(open))
194
+ continue;
195
+ const end = findClose(c.s, c.i + open.length, close);
196
+ if (end < 0)
197
+ continue;
198
+ // Tables are a node's alone; a subgraph title read through here has them
199
+ // flattened below.
200
+ label = parseLabel(c.s.slice(c.i + open.length, end), { tables: true });
201
+ shape = kind;
202
+ c.i = end + close.length;
203
+ break;
204
+ }
205
+ // `A:::className` — the inline form of `class A className`.
206
+ const classes = [];
207
+ while (peek(c).startsWith(":::")) {
208
+ c.i += 3;
209
+ const cm = ID.exec(peek(c));
210
+ if (!cm)
211
+ break;
212
+ classes.push(cm[0]);
213
+ c.i += cm[0].length;
214
+ }
215
+ return { id, label, shape, classes: classes.length ? classes : undefined };
216
+ }
217
+ function readArrow(c) {
218
+ skipWs(c);
219
+ for (const [re, arrow, line] of LABELLED_ARROWS) {
220
+ const m = re.exec(peek(c));
221
+ if (!m)
222
+ continue;
223
+ c.i += m[0].length;
224
+ return { arrow, line, label: parseLabel(m[1]) };
225
+ }
226
+ for (const [re, arrow, line] of ARROWS) {
227
+ const m = re.exec(peek(c));
228
+ if (!m)
229
+ continue;
230
+ c.i += m[0].length;
231
+ let label;
232
+ if (peek(c).startsWith("|")) {
233
+ const end = findClose(c.s, c.i + 1, "|");
234
+ if (end < 0)
235
+ return null;
236
+ label = parseLabel(c.s.slice(c.i + 1, end));
237
+ c.i = end + 1;
238
+ }
239
+ return { arrow, line, label };
240
+ }
241
+ return null;
242
+ }
243
+ export function parseDiagram(source) {
244
+ const rawLines = source.replace(/\r\n?/g, "\n").split("\n");
245
+ let direction = null;
246
+ let headerAt = -1;
247
+ for (let i = 0; i < rawLines.length; i++) {
248
+ const t = rawLines[i].trim();
249
+ if (!t || t.startsWith("%%"))
250
+ continue;
251
+ const h = HEADER.exec(t);
252
+ if (h) {
253
+ const d = (h[2] ?? "TB").toUpperCase();
254
+ direction = (d === "TD" ? "TB" : d);
255
+ headerAt = i;
256
+ break;
257
+ }
258
+ const other = OTHER_TYPES.exec(t);
259
+ return {
260
+ ok: false,
261
+ refusal: other
262
+ ? { kind: "unsupported", message: `${other[1]} diagrams are not rendered yet`, line: i + 1 }
263
+ : { kind: "parse_error", message: `expected a \`flowchart\` or \`graph\` header, found \`${t.slice(0, 40)}\``, line: i + 1 },
264
+ };
265
+ }
266
+ if (direction === null) {
267
+ return { ok: false, refusal: { kind: "parse_error", message: "no diagram header" } };
268
+ }
269
+ const nodes = new Map();
270
+ const edges = [];
271
+ const subgraphs = [];
272
+ const classDefs = {};
273
+ const stack = [];
274
+ const literal = [];
275
+ let lineNo = 0;
276
+ /** A label's literal-markup notes, attributed to the statement it is in. */
277
+ const noted = (label) => {
278
+ for (const message of label?.literal ?? [])
279
+ literal.push({ message, line: lineNo });
280
+ return label;
281
+ };
282
+ // A node belongs to the subgraph it is *mentioned inside*, whether or not
283
+ // that is where it first appeared. Claiming only on creation meant the
284
+ // common `subgraph S` / bare member-list form — where the nodes were already
285
+ // introduced by an edge above — produced an empty cluster, which dagre then
286
+ // sized as nothing and the renderer drew as a floating title.
287
+ const claimed = new Set();
288
+ const joinCurrent = (id) => {
289
+ if (!stack.length || claimed.has(id))
290
+ return;
291
+ stack[stack.length - 1].nodes.push(id);
292
+ claimed.add(id);
293
+ };
294
+ const touch = (id) => {
295
+ let n = nodes.get(id);
296
+ if (!n) {
297
+ const l = plainLabel(id);
298
+ n = { id, lines: l.lines, runs: l.runs, shape: "rect", classes: [] };
299
+ nodes.set(id, n);
300
+ }
301
+ joinCurrent(id);
302
+ return n;
303
+ };
304
+ const apply = (ref) => {
305
+ const n = touch(ref.id);
306
+ if (noted(ref.label)) {
307
+ n.lines = ref.label.lines;
308
+ n.runs = ref.label.runs;
309
+ }
310
+ if (ref.shape)
311
+ n.shape = ref.shape;
312
+ if (ref.classes)
313
+ n.classes.push(...ref.classes);
314
+ return n;
315
+ };
316
+ for (let i = headerAt + 1; i < rawLines.length; i++) {
317
+ lineNo = i + 1;
318
+ // A trailing `;` is optional statement punctuation in mermaid.
319
+ // `%%` opens a comment only at the start of a line — mermaid's own rule.
320
+ // Stripping it anywhere broke any label containing one, e.g. `A[100%% done]`.
321
+ let line = rawLines[i];
322
+ if (line.length > MAX_LINE_LENGTH) {
323
+ return { ok: false, refusal: { kind: "unsupported", message: `line is over ${MAX_LINE_LENGTH} characters`, line: lineNo } };
324
+ }
325
+ // A markdown string may span lines — its newlines are its line breaks —
326
+ // so a statement runs on until every ``"` `` it opened is closed. The
327
+ // joined statement is held to the same length bound as a line, which is
328
+ // what keeps the patterns below linear-bounded on it.
329
+ if (!/^\s*%%/.test(line)) {
330
+ while (opensMarkdownString(line)) {
331
+ if (++i >= rawLines.length) {
332
+ return { ok: false, refusal: { kind: "parse_error", message: "a markdown string (`\"`…`\"`) is never closed", line: lineNo } };
333
+ }
334
+ line += "\n" + rawLines[i];
335
+ if (line.length > MAX_LINE_LENGTH) {
336
+ return { ok: false, refusal: { kind: "unsupported", message: `statement is over ${MAX_LINE_LENGTH} characters`, line: lineNo } };
337
+ }
338
+ }
339
+ }
340
+ const text = (/^\s*%%/.test(line) ? "" : line).replace(/;\s*$/, "").trim();
341
+ if (!text)
342
+ continue;
343
+ if (/^end$/i.test(text)) {
344
+ if (!stack.length) {
345
+ return { ok: false, refusal: { kind: "parse_error", message: "`end` without `subgraph`", line: lineNo } };
346
+ }
347
+ subgraphs.push(stack.pop());
348
+ continue;
349
+ }
350
+ let m;
351
+ if ((m = /^subgraph\s+([\s\S]*)$/i.exec(text))) {
352
+ const rest = m[1].trim();
353
+ const ref = readNodeRef({ s: rest, i: 0 });
354
+ // `subgraph id[Title]`, `subgraph id`, or `subgraph "Free text"`.
355
+ const isIdForm = ref && ref.id.length > 0 && /^[A-Za-z_][\w-]*(\[|\(|\{|$)/.test(rest);
356
+ const id = isIdForm ? ref.id : `sg${subgraphs.length + stack.length}`;
357
+ // A title is drawn on one line, so a multi-line label joins with spaces.
358
+ const label = noted(isIdForm ? (ref.label ?? plainLabel(ref.id)) : parseLabel(rest));
359
+ const titleRuns = textLines(label.runs).flatMap((l, k) => (k ? [{ text: " " }, ...l] : l));
360
+ stack.push({ id, title: label.lines.join(" "), titleRuns, nodes: [] });
361
+ continue;
362
+ }
363
+ if (/^direction\s+/i.test(text))
364
+ continue; // per-subgraph direction: ignored, not refused
365
+ if ((m = /^classDef\s+([\w,-]+)\s+(.*)$/i.exec(text))) {
366
+ const decl = parseStyleBody(m[2]);
367
+ for (const name of m[1].split(","))
368
+ classDefs[name.trim()] = decl;
369
+ continue;
370
+ }
371
+ if ((m = /^class\s+([\w,\s-]+)\s+([\w-]+)$/i.exec(text))) {
372
+ for (const id of m[1].split(",")) {
373
+ const t = id.trim();
374
+ if (t)
375
+ touch(t).classes.push(m[2]);
376
+ }
377
+ continue;
378
+ }
379
+ if ((m = /^style\s+([\w-]+)\s+(.*)$/i.exec(text))) {
380
+ const n = touch(m[1]);
381
+ n.style = { ...(n.style ?? {}), ...parseStyleBody(m[2]) };
382
+ continue;
383
+ }
384
+ if (/^(click|linkStyle|linkstyle)\b/i.test(text)) {
385
+ return {
386
+ ok: false,
387
+ refusal: { kind: "unsupported", message: `\`${text.split(/\s/)[0]}\` is not supported`, line: lineNo },
388
+ };
389
+ }
390
+ // Statement: a group of nodes, then zero or more (arrow, group) pairs.
391
+ const c = { s: text, i: 0 };
392
+ let group = [];
393
+ let ref = readNodeRef(c);
394
+ if (!ref) {
395
+ return { ok: false, refusal: { kind: "parse_error", message: `cannot read \`${text.slice(0, 40)}\``, line: lineNo } };
396
+ }
397
+ group.push(apply(ref));
398
+ skipWs(c);
399
+ while (peek(c).startsWith("&")) {
400
+ c.i += 1;
401
+ const r = readNodeRef(c);
402
+ if (!r) {
403
+ return { ok: false, refusal: { kind: "parse_error", message: "`&` with no node after it", line: lineNo } };
404
+ }
405
+ group.push(apply(r));
406
+ skipWs(c);
407
+ }
408
+ while (c.i < text.length) {
409
+ const arr = readArrow(c);
410
+ noted(arr?.label);
411
+ if (!arr) {
412
+ return { ok: false, refusal: { kind: "parse_error", message: `cannot read \`${peek(c).slice(0, 24)}\``, line: lineNo } };
413
+ }
414
+ const next = [];
415
+ const r0 = readNodeRef(c);
416
+ if (!r0) {
417
+ return { ok: false, refusal: { kind: "parse_error", message: "an arrow with no node after it", line: lineNo } };
418
+ }
419
+ next.push(apply(r0));
420
+ skipWs(c);
421
+ while (peek(c).startsWith("&")) {
422
+ c.i += 1;
423
+ const r = readNodeRef(c);
424
+ if (!r) {
425
+ return { ok: false, refusal: { kind: "parse_error", message: "`&` with no node after it", line: lineNo } };
426
+ }
427
+ next.push(apply(r));
428
+ skipWs(c);
429
+ }
430
+ // `A & B --> C & D` is the cross product, which is what mermaid does.
431
+ for (const a of group) {
432
+ for (const b of next) {
433
+ edges.push({
434
+ from: a.id, to: b.id,
435
+ label: arr.label?.lines.join(" "), labelRuns: arr.label && textLines(arr.label.runs),
436
+ arrow: arr.arrow, line: arr.line,
437
+ });
438
+ }
439
+ }
440
+ group = next;
441
+ skipWs(c);
442
+ }
443
+ }
444
+ if (stack.length) {
445
+ return { ok: false, refusal: { kind: "parse_error", message: "`subgraph` with no `end`" } };
446
+ }
447
+ // An edge to or from a subgraph id is legal mermaid and dagre cannot rank it
448
+ // — it throws, which `renderDiagram` caught and turned into a silent
449
+ // fallback: source shown, no warning, `strict: true` not refusing. Refusing
450
+ // here instead is what keeps "warns iff the operator sees source" true.
451
+ const clusterIds = new Set(subgraphs.map((sg) => sg.id));
452
+ for (const e of edges) {
453
+ for (const end of [e.from, e.to]) {
454
+ if (clusterIds.has(end)) {
455
+ return {
456
+ ok: false,
457
+ refusal: {
458
+ kind: "unsupported",
459
+ message: `\`${end}\` is a subgraph, and an edge to a subgraph is not drawn yet`,
460
+ },
461
+ };
462
+ }
463
+ }
464
+ }
465
+ if (nodes.size === 0) {
466
+ return { ok: false, refusal: { kind: "parse_error", message: "no nodes" } };
467
+ }
468
+ return { ok: true, ir: { direction, nodes: [...nodes.values()], edges, subgraphs, classDefs, literal } };
469
+ }
470
+ /** Whether `s` leaves a markdown string open: a ``"` `` with no ``` `" ```
471
+ * after it. One linear pass; a markdown string cannot contain either quote
472
+ * character, so the first closer is its closer. */
473
+ export function opensMarkdownString(s) {
474
+ let open = false;
475
+ for (let i = 0; i < s.length - 1; i++) {
476
+ if (!open && s[i] === '"' && s[i + 1] === "`") {
477
+ open = true;
478
+ i++;
479
+ }
480
+ else if (open && s[i] === "`" && s[i + 1] === '"') {
481
+ open = false;
482
+ i++;
483
+ }
484
+ }
485
+ return open;
486
+ }
487
+ //# sourceMappingURL=parse.js.map