@diagrammo/dgmo 0.47.0 → 0.48.1

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 (345) hide show
  1. package/dist/advanced.js +11 -11
  2. package/dist/auto.js +16 -16
  3. package/dist/auto.mjs +41 -12
  4. package/dist/block.js +4 -4
  5. package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
  6. package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
  7. package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
  8. package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
  9. package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
  10. package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
  11. package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
  12. package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
  13. package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
  14. package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
  15. package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
  16. package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
  17. package/dist/cli.cjs +15 -15
  18. package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
  19. package/dist/element.js +16 -16
  20. package/dist/element.mjs +41 -12
  21. package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
  22. package/dist/index.d.ts +4 -2
  23. package/dist/index.js +4 -4
  24. package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
  25. package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
  26. package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
  27. package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
  28. package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
  29. package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
  30. package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
  31. package/package.json +23 -71
  32. package/dist/advanced.cjs +0 -71648
  33. package/dist/advanced.d.cts +0 -5719
  34. package/dist/auto.cjs +0 -70648
  35. package/dist/auto.d.cts +0 -54
  36. package/dist/block.cjs +0 -70208
  37. package/dist/block.d.cts +0 -115
  38. package/dist/editor.cjs +0 -526
  39. package/dist/editor.d.cts +0 -26
  40. package/dist/element.cjs +0 -70530
  41. package/dist/element.d.cts +0 -62
  42. package/dist/highlight.cjs +0 -852
  43. package/dist/highlight.d.cts +0 -49
  44. package/dist/index.cjs +0 -70491
  45. package/dist/index.d.cts +0 -160
  46. package/dist/pert.cjs +0 -325
  47. package/dist/pert.d.cts +0 -559
  48. package/dist/themes-1-CuKpeH.d.cts +0 -908
  49. package/src/advanced.ts +0 -873
  50. package/src/arc/parser.ts +0 -15
  51. package/src/arc/renderer.ts +0 -633
  52. package/src/auto/index.ts +0 -621
  53. package/src/auto/shared.ts +0 -370
  54. package/src/auto/styles.ts +0 -73
  55. package/src/block/layout.ts +0 -198
  56. package/src/block/parser.ts +0 -427
  57. package/src/block/renderer.ts +0 -484
  58. package/src/block/types.ts +0 -67
  59. package/src/boxes-and-lines/collapse.ts +0 -96
  60. package/src/boxes-and-lines/focus.ts +0 -257
  61. package/src/boxes-and-lines/label-placement.ts +0 -339
  62. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  63. package/src/boxes-and-lines/layout-layered.ts +0 -722
  64. package/src/boxes-and-lines/layout-search.ts +0 -1678
  65. package/src/boxes-and-lines/layout.ts +0 -398
  66. package/src/boxes-and-lines/parser.ts +0 -1181
  67. package/src/boxes-and-lines/renderer.ts +0 -1465
  68. package/src/boxes-and-lines/types.ts +0 -66
  69. package/src/c4/layout.ts +0 -2441
  70. package/src/c4/parser.ts +0 -1158
  71. package/src/c4/renderer.ts +0 -2008
  72. package/src/c4/types.ts +0 -89
  73. package/src/chart-type-registry.ts +0 -529
  74. package/src/chart-types.ts +0 -225
  75. package/src/chart.ts +0 -685
  76. package/src/charts-d3/bar.ts +0 -338
  77. package/src/charts-d3/chord.ts +0 -140
  78. package/src/charts-d3/function.ts +0 -147
  79. package/src/charts-d3/funnel.ts +0 -104
  80. package/src/charts-d3/heatmap.ts +0 -145
  81. package/src/charts-d3/index.ts +0 -337
  82. package/src/charts-d3/interactions.ts +0 -477
  83. package/src/charts-d3/line.ts +0 -344
  84. package/src/charts-d3/mount.ts +0 -151
  85. package/src/charts-d3/pie.ts +0 -117
  86. package/src/charts-d3/polar.ts +0 -89
  87. package/src/charts-d3/radar.ts +0 -0
  88. package/src/charts-d3/sankey.ts +0 -0
  89. package/src/charts-d3/scatter.ts +0 -185
  90. package/src/charts-d3/shared.ts +0 -208
  91. package/src/class/layout.ts +0 -326
  92. package/src/class/parser.ts +0 -635
  93. package/src/class/renderer.ts +0 -793
  94. package/src/class/types.ts +0 -58
  95. package/src/cli-banner.ts +0 -106
  96. package/src/cli.ts +0 -1080
  97. package/src/colors-diagnostics.ts +0 -35
  98. package/src/colors.ts +0 -379
  99. package/src/completion-types.ts +0 -27
  100. package/src/completion.ts +0 -2668
  101. package/src/cycle/layout.ts +0 -1026
  102. package/src/cycle/parser.ts +0 -387
  103. package/src/cycle/renderer.ts +0 -653
  104. package/src/cycle/types.ts +0 -101
  105. package/src/d3.ts +0 -1434
  106. package/src/data-chart-parser.ts +0 -1007
  107. package/src/dgmo-router.ts +0 -296
  108. package/src/diagnostics-registry.ts +0 -108
  109. package/src/diagnostics.ts +0 -0
  110. package/src/dimensions.ts +0 -20
  111. package/src/directives-registry.ts +0 -396
  112. package/src/editor/dgmo.grammar +0 -76
  113. package/src/editor/dgmo.grammar.d.ts +0 -2
  114. package/src/editor/dgmo.grammar.js +0 -18
  115. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  116. package/src/editor/dgmo.grammar.terms.js +0 -35
  117. package/src/editor/highlight-api.ts +0 -596
  118. package/src/editor/highlight.ts +0 -36
  119. package/src/editor/index.ts +0 -27
  120. package/src/editor/keywords.ts +0 -128
  121. package/src/editor/tokens.ts +0 -30
  122. package/src/element/index.ts +0 -432
  123. package/src/embed/css.ts +0 -312
  124. package/src/embed/escape.ts +0 -15
  125. package/src/embed/index.ts +0 -280
  126. package/src/er/classify.ts +0 -228
  127. package/src/er/layout.ts +0 -530
  128. package/src/er/parser.ts +0 -750
  129. package/src/er/renderer.ts +0 -725
  130. package/src/er/types.ts +0 -52
  131. package/src/error-card.ts +0 -269
  132. package/src/event-line/diagnostics.ts +0 -117
  133. package/src/event-line/parser.ts +0 -643
  134. package/src/event-line/renderer.ts +0 -1742
  135. package/src/event-line/types.ts +0 -74
  136. package/src/fonts.ts +0 -3
  137. package/src/gantt/calculator.ts +0 -979
  138. package/src/gantt/parser.ts +0 -1641
  139. package/src/gantt/renderer.ts +0 -3787
  140. package/src/gantt/resolver.ts +0 -172
  141. package/src/gantt/types.ts +0 -206
  142. package/src/graph/diagnostics.ts +0 -64
  143. package/src/graph/edge-spline.ts +0 -29
  144. package/src/graph/flowchart-parser.ts +0 -759
  145. package/src/graph/flowchart-renderer.ts +0 -817
  146. package/src/graph/layout.ts +0 -472
  147. package/src/graph/notes.ts +0 -21
  148. package/src/graph/state-collapse.ts +0 -78
  149. package/src/graph/state-parser.ts +0 -574
  150. package/src/graph/state-renderer.ts +0 -653
  151. package/src/graph/types.ts +0 -65
  152. package/src/index.ts +0 -261
  153. package/src/infra/compute.ts +0 -1385
  154. package/src/infra/layout.ts +0 -888
  155. package/src/infra/parser.ts +0 -1234
  156. package/src/infra/renderer.ts +0 -2534
  157. package/src/infra/roles.ts +0 -75
  158. package/src/infra/types.ts +0 -216
  159. package/src/infra/validation.ts +0 -201
  160. package/src/journey-map/layout.ts +0 -490
  161. package/src/journey-map/parser.ts +0 -557
  162. package/src/journey-map/renderer.ts +0 -1580
  163. package/src/journey-map/types.ts +0 -47
  164. package/src/kanban/mutations.ts +0 -175
  165. package/src/kanban/parser.ts +0 -509
  166. package/src/kanban/renderer.ts +0 -1449
  167. package/src/kanban/types.ts +0 -39
  168. package/src/label-layout.ts +0 -368
  169. package/src/map/colorize.ts +0 -54
  170. package/src/map/completion.ts +0 -316
  171. package/src/map/context-labels.ts +0 -609
  172. package/src/map/data/PROVENANCE.json +0 -1
  173. package/src/map/data/README.md +0 -40
  174. package/src/map/data/airport-collisions.json +0 -1
  175. package/src/map/data/airports.json +0 -1
  176. package/src/map/data/gazetteer.json +0 -1
  177. package/src/map/data/lakes.json +0 -1
  178. package/src/map/data/mountain-ranges.json +0 -1
  179. package/src/map/data/na-lakes.json +0 -1
  180. package/src/map/data/na-land.json +0 -1
  181. package/src/map/data/region-names.json +0 -1
  182. package/src/map/data/rivers.json +0 -1
  183. package/src/map/data/types.ts +0 -121
  184. package/src/map/data/us-states.json +0 -1
  185. package/src/map/data/water-bodies.json +0 -1
  186. package/src/map/data/world-coarse.json +0 -1
  187. package/src/map/data/world-detail.json +0 -1
  188. package/src/map/diagnostics.ts +0 -134
  189. package/src/map/dimensions.ts +0 -150
  190. package/src/map/geo-query.ts +0 -295
  191. package/src/map/geo.ts +0 -438
  192. package/src/map/invert.ts +0 -111
  193. package/src/map/layout.ts +0 -5069
  194. package/src/map/legend-band.ts +0 -127
  195. package/src/map/load-data.ts +0 -186
  196. package/src/map/parser.ts +0 -783
  197. package/src/map/renderer.ts +0 -1445
  198. package/src/map/resolved-types.ts +0 -160
  199. package/src/map/resolver.ts +0 -1146
  200. package/src/map/types.ts +0 -194
  201. package/src/mindmap/collapse.ts +0 -67
  202. package/src/mindmap/layout.ts +0 -649
  203. package/src/mindmap/parser.ts +0 -430
  204. package/src/mindmap/renderer.ts +0 -637
  205. package/src/mindmap/text-wrap.ts +0 -218
  206. package/src/mindmap/types.ts +0 -56
  207. package/src/org/collapse.ts +0 -151
  208. package/src/org/layout.ts +0 -1262
  209. package/src/org/parser.ts +0 -519
  210. package/src/org/renderer.ts +0 -866
  211. package/src/org/resolver.ts +0 -636
  212. package/src/palettes/atlas.ts +0 -77
  213. package/src/palettes/blueprint.ts +0 -73
  214. package/src/palettes/catppuccin.ts +0 -84
  215. package/src/palettes/color-utils.ts +0 -443
  216. package/src/palettes/index.ts +0 -67
  217. package/src/palettes/nord.ts +0 -67
  218. package/src/palettes/registry.ts +0 -114
  219. package/src/palettes/slate.ts +0 -73
  220. package/src/palettes/tidewater.ts +0 -73
  221. package/src/palettes/tokyo-night.ts +0 -86
  222. package/src/palettes/types.ts +0 -88
  223. package/src/pert/analyzer.ts +0 -1183
  224. package/src/pert/diagnostics.ts +0 -110
  225. package/src/pert/index.ts +0 -43
  226. package/src/pert/internal.ts +0 -252
  227. package/src/pert/layout.ts +0 -971
  228. package/src/pert/monte-carlo.ts +0 -446
  229. package/src/pert/parser.ts +0 -1606
  230. package/src/pert/renderer.ts +0 -4009
  231. package/src/pert/share-normalize.ts +0 -62
  232. package/src/pert/types.ts +0 -604
  233. package/src/pyramid/parser.ts +0 -185
  234. package/src/pyramid/renderer.ts +0 -768
  235. package/src/pyramid/types.ts +0 -28
  236. package/src/quadrant/parser.ts +0 -18
  237. package/src/quadrant/renderer.ts +0 -638
  238. package/src/raci/diagnostics.ts +0 -213
  239. package/src/raci/index.ts +0 -23
  240. package/src/raci/mutations.ts +0 -321
  241. package/src/raci/parser.ts +0 -699
  242. package/src/raci/renderer.ts +0 -1627
  243. package/src/raci/types.ts +0 -82
  244. package/src/raci/variants.ts +0 -320
  245. package/src/render.ts +0 -268
  246. package/src/ring/parser.ts +0 -212
  247. package/src/ring/renderer.ts +0 -490
  248. package/src/ring/types.ts +0 -27
  249. package/src/sequence/collapse.ts +0 -174
  250. package/src/sequence/parser.ts +0 -1596
  251. package/src/sequence/participant-inference.ts +0 -152
  252. package/src/sequence/renderer.ts +0 -3168
  253. package/src/sequence/tag-resolution.ts +0 -164
  254. package/src/sharing.ts +0 -211
  255. package/src/sitemap/collapse.ts +0 -100
  256. package/src/sitemap/layout.ts +0 -925
  257. package/src/sitemap/parser.ts +0 -755
  258. package/src/sitemap/renderer.ts +0 -828
  259. package/src/sitemap/types.ts +0 -42
  260. package/src/slope/parser.ts +0 -18
  261. package/src/slope/renderer.ts +0 -378
  262. package/src/swimlane/diagnostics.ts +0 -94
  263. package/src/swimlane/layout.ts +0 -597
  264. package/src/swimlane/parser.ts +0 -613
  265. package/src/swimlane/renderer.ts +0 -681
  266. package/src/swimlane/types.ts +0 -151
  267. package/src/tech-radar/interactive.ts +0 -1129
  268. package/src/tech-radar/layout.ts +0 -197
  269. package/src/tech-radar/parser.ts +0 -436
  270. package/src/tech-radar/renderer.ts +0 -1239
  271. package/src/tech-radar/shared.ts +0 -151
  272. package/src/tech-radar/types.ts +0 -86
  273. package/src/themes.ts +0 -22
  274. package/src/timeline/parser.ts +0 -472
  275. package/src/timeline/renderer.ts +0 -3024
  276. package/src/timeline/types.ts +0 -33
  277. package/src/timeline/viz-parser.ts +0 -21
  278. package/src/treemap/diagnostics.ts +0 -62
  279. package/src/treemap/layout-radial.ts +0 -233
  280. package/src/treemap/layout.ts +0 -189
  281. package/src/treemap/parser.ts +0 -463
  282. package/src/treemap/renderer-radial.ts +0 -438
  283. package/src/treemap/renderer.ts +0 -572
  284. package/src/treemap/treemap-shared.ts +0 -309
  285. package/src/treemap/types.ts +0 -60
  286. package/src/utils/arrow-markers.ts +0 -51
  287. package/src/utils/arrows-diagnostics.ts +0 -62
  288. package/src/utils/arrows.ts +0 -227
  289. package/src/utils/brand.ts +0 -61
  290. package/src/utils/card.ts +0 -183
  291. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  292. package/src/utils/collapse-engine/tree.ts +0 -117
  293. package/src/utils/d3-helpers.ts +0 -219
  294. package/src/utils/d3-types.ts +0 -9
  295. package/src/utils/description-helpers.ts +0 -38
  296. package/src/utils/duration.ts +0 -312
  297. package/src/utils/export-container.ts +0 -41
  298. package/src/utils/expr-eval.ts +0 -27
  299. package/src/utils/fit-canvas.ts +0 -64
  300. package/src/utils/hover-styles.ts +0 -673
  301. package/src/utils/inline-markdown.ts +0 -96
  302. package/src/utils/legend-constants.ts +0 -48
  303. package/src/utils/legend-d3.ts +0 -618
  304. package/src/utils/legend-integration.ts +0 -107
  305. package/src/utils/legend-layout.ts +0 -776
  306. package/src/utils/legend-svg.ts +0 -214
  307. package/src/utils/legend-types.ts +0 -272
  308. package/src/utils/name-normalize.ts +0 -163
  309. package/src/utils/note-box/constants.ts +0 -25
  310. package/src/utils/note-box/index.ts +0 -11
  311. package/src/utils/note-box/metrics.ts +0 -90
  312. package/src/utils/note-box/svg.ts +0 -331
  313. package/src/utils/notes/bounds.ts +0 -30
  314. package/src/utils/notes/build.ts +0 -131
  315. package/src/utils/notes/index.ts +0 -18
  316. package/src/utils/notes/model.ts +0 -19
  317. package/src/utils/notes/parse.ts +0 -131
  318. package/src/utils/notes/place.ts +0 -177
  319. package/src/utils/notes/resolve.ts +0 -88
  320. package/src/utils/number-format.ts +0 -36
  321. package/src/utils/parsing.ts +0 -953
  322. package/src/utils/reserved-key-registry.ts +0 -239
  323. package/src/utils/safe-href.ts +0 -38
  324. package/src/utils/scaling.ts +0 -97
  325. package/src/utils/svg-embed.ts +0 -251
  326. package/src/utils/tag-groups.ts +0 -607
  327. package/src/utils/text-measure.ts +0 -122
  328. package/src/utils/time-ticks.ts +0 -253
  329. package/src/utils/title-constants.ts +0 -24
  330. package/src/utils/visual-conventions.ts +0 -61
  331. package/src/utils/wrapped-desc.ts +0 -87
  332. package/src/venn/parser.ts +0 -18
  333. package/src/venn/renderer.ts +0 -1047
  334. package/src/version-control/diagnostics.ts +0 -67
  335. package/src/version-control/parser.ts +0 -544
  336. package/src/version-control/renderer.ts +0 -724
  337. package/src/version-control/types.ts +0 -100
  338. package/src/visualizations/parse.ts +0 -1560
  339. package/src/visualizations/types.ts +0 -262
  340. package/src/wireframe/layout.ts +0 -479
  341. package/src/wireframe/parser.ts +0 -1114
  342. package/src/wireframe/renderer.ts +0 -1318
  343. package/src/wireframe/types.ts +0 -110
  344. package/src/wordcloud/parser.ts +0 -18
  345. package/src/wordcloud/renderer.ts +0 -357
@@ -1,699 +0,0 @@
1
- // ============================================================
2
- // RACI / RASCI / DACI parser
3
- // ============================================================
4
- //
5
- // Three-level indented hierarchy under the chart-type header:
6
- // raci [title]
7
- // <directives> // e.g. `no-rule-enforcement`
8
- // [Phase] // optional, one level deep, bracketed
9
- // Task name
10
- // Optional description line(s)
11
- // Role: <markers>
12
- //
13
- // Patterns followed:
14
- // - Stack-based indent parse (org/c4 precedent)
15
- // - measureIndent() from utils/parsing
16
- // - Forgiving identity via normalizeName() — first-seen casing wins
17
- // - All diagnostics accumulated in result.diagnostics; never throws
18
- //
19
- // See `docs/dgmo-language-spec.md` § "RACI Matrix".
20
-
21
- import { emit, makeDgmoError, makeFail, suggest } from '../diagnostics';
22
- import { RACI_DX } from './diagnostics';
23
- import { resolveColorWithDiagnostic } from '../colors';
24
- import { RACI_REGISTRY } from '../utils/reserved-key-registry';
25
- import {
26
- measureIndent,
27
- parseFirstLine,
28
- splitNameAndMeta,
29
- OPTION_NOCOLON_RE,
30
- tryParseSharedOption,
31
- warnUnknownMetaKeys,
32
- } from '../utils/parsing';
33
- import {
34
- normalizeName,
35
- displayName as displayNameOf,
36
- } from '../utils/name-normalize';
37
- import { NAME_DIAGNOSTIC_CODES, nameMergedMessage } from '../diagnostics';
38
- import type { PaletteColors } from '../palettes';
39
- import { VARIANTS, ALL_MARKERS, inferVariant } from './variants';
40
- import type {
41
- ParsedRaci,
42
- RaciMarker,
43
- RaciPhase,
44
- RaciRoleAssignment,
45
- RaciTask,
46
- } from './types';
47
- import type { Writable } from '../utils/brand';
48
-
49
- /** Header options that take a value (`key value`). */
50
- const KNOWN_OPTIONS = new Set(['roles', 'palette', 'theme', 'active-tag']);
51
- /** Header options that are bare booleans (presence = on). */
52
- const KNOWN_BOOLEANS = new Set<string>(['no-title']);
53
- const REMOVED_BOOLEANS: Record<string, string> = {
54
- 'no-rule-enforcement':
55
- '"no-rule-enforcement" has been removed — RACI validation is always active.',
56
- };
57
-
58
- // Allow optional trailing color shortcut and/or pipe metadata after the
59
- // bracket: `[Voyage] blue | desc: …` (per §1.5 universal trailing-token
60
- // rule + the modern cycle/pyramid/ring/journey-map/b&l idiom).
61
- // Captures: 1=label, 2=trailing content (parsed below for color + metadata).
62
- const PHASE_RE = /^\[(.+?)\]\s*(.*)$/;
63
- const PHASE_PALETTE_COLOR_RE =
64
- /^(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\b/;
65
- const ROLE_ASSIGNMENT_RE = /^([^:]+):\s*(.*)$/;
66
-
67
- /**
68
- * Decide whether `trimmed` is a role-assignment line and split it into
69
- * `rawRole` + `markersBlob`. Two accepted forms:
70
- * - Explicit colon: `Bos: A R` / `Bos:` (markers may be empty).
71
- * - Colon-less: `Bos A R` / `Bos AR` — the role name is everything
72
- * before the first whitespace-separated token whose
73
- * every character is a marker letter from the union
74
- * of all variant alphabets. If no such token exists,
75
- * this isn't an assignment (likely a description line).
76
- *
77
- * The colon-less form uses the widest alphabet (R/A/S/C/I/D) so the
78
- * tokenizer doesn't depend on knowing the variant up front — variant
79
- * resolution happens AFTER parsing in a post-pass.
80
- *
81
- * Returns null when the line does not look like an assignment. The
82
- * caller then falls through to description / unexpected-line handling.
83
- */
84
- function parseRoleAssignmentLine(
85
- trimmed: string
86
- ): { rawRole: string; markersBlob: string } | null {
87
- const colonMatch = trimmed.match(ROLE_ASSIGNMENT_RE);
88
- if (colonMatch) {
89
- return {
90
- // Capture groups [1] and [2] guaranteed by ROLE_ASSIGNMENT_RE shape.
91
- rawRole: colonMatch[1]!.trim(),
92
- markersBlob: colonMatch[2]!.trim(),
93
- };
94
- }
95
- const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0);
96
- if (tokens.length < 2) return null;
97
- const isAllMarkers = (tok: string): boolean => {
98
- if (tok.length === 0) return false;
99
- for (const ch of tok) {
100
- if (!ALL_MARKERS.has(ch as RaciMarker)) return false;
101
- }
102
- return true;
103
- };
104
- let firstMarkerIdx = -1;
105
- for (let j = 0; j < tokens.length; j++) {
106
- // In-bounds by loop guard (j < tokens.length).
107
- if (isAllMarkers(tokens[j]!)) {
108
- firstMarkerIdx = j;
109
- break;
110
- }
111
- }
112
- // No marker token found, OR the entire line is markers (no role
113
- // prefix) — not a role-assignment line.
114
- if (firstMarkerIdx <= 0) return null;
115
- return {
116
- rawRole: tokens.slice(0, firstMarkerIdx).join(' '),
117
- markersBlob: tokens.slice(firstMarkerIdx).join(' '),
118
- };
119
- }
120
-
121
- /**
122
- * Parse RACI/RASCI/DACI source. The leading chart-type id (if a
123
- * recognized variant id) acts as a hint for default variant when the
124
- * `variant` directive is absent.
125
- */
126
- export function parseRaci(
127
- content: string,
128
- palette?: PaletteColors
129
- ): ParsedRaci {
130
- const options: Record<string, string> = {};
131
- const result: Writable<ParsedRaci> = {
132
- type: 'raci',
133
- variant: 'raci',
134
- roles: [],
135
- roleDisplayNames: [],
136
- roleColors: [],
137
- phases: [],
138
- tasksWithoutPhase: [],
139
- options,
140
- diagnostics: [],
141
- error: null,
142
- };
143
-
144
- const fail = makeFail(result);
145
-
146
- const warn = (line: number, message: string, code?: string): void => {
147
- result.diagnostics.push(makeDgmoError(line, message, 'warning', code));
148
- };
149
-
150
- const errorAt = (line: number, message: string, code?: string): void => {
151
- result.diagnostics.push(makeDgmoError(line, message, 'error', code));
152
- };
153
-
154
- if (!content?.trim()) {
155
- return fail(0, 'No content provided');
156
- }
157
-
158
- const lines = content.split('\n');
159
-
160
- // ── Header phase ─────────────────────────────────────────────
161
-
162
- let i = 0;
163
-
164
- // Variant is resolved after parse purely from the markers used
165
- // (D → daci, S → rasci, else raci). Both D and S → E_RACI_MIXED_VARIANTS.
166
- // First non-empty / non-comment line: chart type + optional title.
167
- for (; i < lines.length; i++) {
168
- // In-bounds by loop guard (i < lines.length).
169
- const trimmed = lines[i]!.trim();
170
- if (!trimmed || trimmed.startsWith('//')) continue;
171
- const firstLine = parseFirstLine(trimmed);
172
- if (firstLine?.chartType !== 'raci') {
173
- return fail(i + 1, 'Expected chart type "raci" on the first line.');
174
- }
175
- if (firstLine.title) {
176
- result.title = firstLine.title;
177
- result.titleLineNumber = i + 1;
178
- }
179
- i++;
180
- break;
181
- }
182
-
183
- // ── Directives + body, single pass ───────────────────────────
184
-
185
- // Tracks header-vs-body phase. Once we see a phase or task, the
186
- // header window closes — directives after that are content errors.
187
- let bodyStarted = false;
188
-
189
- // Role identity store — single source of truth for role display names.
190
- // role normalized key → { displayName, declaredLine }
191
- const roleStore = new Map<
192
- string,
193
- { displayName: string; declaredLine: number }
194
- >();
195
-
196
- // Task identity store — only used for `I_NAME_MERGED` detection on
197
- // tasks. Mutations look up tasks by `task.id`, which is the
198
- // normalized key set during parse.
199
- const taskStore = new Map<
200
- string,
201
- { displayName: string; declaredLine: number }
202
- >();
203
-
204
- // Explicit `roles:` directive → strict column ordering + unknown-role diagnostics.
205
- let rolesExplicit = false;
206
-
207
- // Stack-based indent tracking: which phase / task is currently
208
- // accepting children?
209
- let currentPhase: Writable<RaciPhase> | null = null;
210
- let currentTask: Writable<RaciTask> | null = null;
211
- /** Indent of the task line itself (children must be deeper than this). */
212
- let taskIndent = 0;
213
- /** True once we've parsed at least one role assignment under `currentTask`.
214
- * After that, free-text lines under the task are an error. */
215
- let taskHasRoleAssignment = false;
216
-
217
- /**
218
- * Helper: register a role and return its normalized id. The optional
219
- * `color` (resolved hex) overrides any prior color for that role —
220
- * declared roles win over usage-only roles.
221
- */
222
- const getOrAddRole = (
223
- label: string,
224
- line: number,
225
- color?: string
226
- ): string | null => {
227
- const display = displayNameOf(label);
228
- if (!display) {
229
- errorAt(line, 'Role name is empty.');
230
- return null;
231
- }
232
- const key = normalizeName(display);
233
- const existing = roleStore.get(key);
234
- if (existing) {
235
- if (existing.displayName !== display) {
236
- warn(
237
- line,
238
- nameMergedMessage({
239
- incomingDisplay: display,
240
- incomingLine: line,
241
- existingDisplay: existing.displayName,
242
- existingLine: existing.declaredLine,
243
- }),
244
- NAME_DIAGNOSTIC_CODES.NAME_MERGED
245
- );
246
- }
247
- // Late color upgrade — if a previously-undeclared role gets
248
- // color via a later declaration, accept it.
249
- if (color !== undefined) {
250
- const idx = result.roles.indexOf(key);
251
- if (idx >= 0) result.roleColors[idx] = color;
252
- }
253
- return key;
254
- }
255
- roleStore.set(key, { displayName: display, declaredLine: line });
256
- result.roles.push(key);
257
- result.roleDisplayNames.push(display);
258
- result.roleColors.push(color);
259
- return key;
260
- };
261
-
262
- /** Helper: register a task identity (returns normalized id). */
263
- const registerTask = (display: string, line: number): string => {
264
- const key = normalizeName(display);
265
- const existing = taskStore.get(key);
266
- if (existing) {
267
- if (existing.displayName !== display) {
268
- warn(
269
- line,
270
- nameMergedMessage({
271
- incomingDisplay: display,
272
- incomingLine: line,
273
- existingDisplay: existing.displayName,
274
- existingLine: existing.declaredLine,
275
- }),
276
- NAME_DIAGNOSTIC_CODES.NAME_MERGED
277
- );
278
- }
279
- } else {
280
- taskStore.set(key, { displayName: display, declaredLine: line });
281
- }
282
- return key;
283
- };
284
-
285
- /** Finalize the current task — set endLineNumber to last contentful line. */
286
- const finalizeTask = (uptoLineExclusive: number): void => {
287
- if (!currentTask) return;
288
- let end = uptoLineExclusive - 1;
289
- // In-bounds: end > currentTask.lineNumber >= 1, so end-1 >= 1 and < lines.length.
290
- while (end > currentTask.lineNumber && !lines[end - 1]!.trim()) end--;
291
- currentTask.endLineNumber = end;
292
- if (currentPhase) currentPhase.endLineNumber = end;
293
- currentTask = null;
294
- taskHasRoleAssignment = false;
295
- };
296
-
297
- /** Finalize the current phase (also finalizes its tail task). */
298
- const finalizePhase = (uptoLineExclusive: number): void => {
299
- if (currentTask) finalizeTask(uptoLineExclusive);
300
- if (currentPhase) {
301
- let end = uptoLineExclusive - 1;
302
- // In-bounds: end > currentPhase.lineNumber >= 1, so end-1 >= 1 and < lines.length.
303
- while (end > currentPhase.lineNumber && !lines[end - 1]!.trim()) end--;
304
- currentPhase.endLineNumber = Math.max(currentPhase.endLineNumber, end);
305
- currentPhase = null;
306
- }
307
- };
308
-
309
- for (; i < lines.length; i++) {
310
- // In-bounds by loop guard (i < lines.length).
311
- const raw = lines[i]!;
312
- const lineNumber = i + 1;
313
- const trimmed = raw.trim();
314
- const indent = measureIndent(raw);
315
-
316
- if (!trimmed) continue;
317
- if (trimmed.startsWith('//')) continue;
318
-
319
- // ── Directives (header window only, indent === 0) ──────────
320
- if (!bodyStarted && indent === 0 && !PHASE_RE.test(trimmed)) {
321
- // Bare `roles` followed by indented role names (block form):
322
- // roles
323
- // Cap
324
- // QM
325
- // Bos
326
- // Mirrors the kanban-tag-block / `series:` precedents. Both
327
- // this and the inline `roles Cap, QM, Bos` form are accepted.
328
- if (trimmed.toLowerCase() === 'roles') {
329
- rolesExplicit = true;
330
- let j = i + 1;
331
- for (; j < lines.length; j++) {
332
- // In-bounds by loop guard (j < lines.length).
333
- const next = lines[j]!;
334
- const nextTrim = next.trim();
335
- if (!nextTrim) continue;
336
- if (nextTrim.startsWith('//')) continue;
337
- // First non-indented line ends the block.
338
- if (next.length > 0 && next[0] !== ' ' && next[0] !== '\t') break;
339
- // Strip a possible trailing comma (user habit tolerance,
340
- // matches `collectIndentedValues`).
341
- const stripped = nextTrim.replace(/,\s*$/, '');
342
- // §1.4 unified metadata grammar — same-line cut.
343
- const split = splitNameAndMeta(
344
- stripped,
345
- RACI_REGISTRY,
346
- new Map(),
347
- undefined,
348
- result.diagnostics,
349
- j + 1
350
- );
351
- warnUnknownMetaKeys(
352
- split.meta,
353
- RACI_REGISTRY,
354
- (msg) => warn(j + 1, msg),
355
- split.name
356
- );
357
- const roleLabel = split.name;
358
- let roleColor: string | undefined;
359
- const colorRaw = split.color ?? split.meta['color'];
360
- if (colorRaw) {
361
- roleColor = resolveColorWithDiagnostic(
362
- colorRaw,
363
- j + 1,
364
- result.diagnostics,
365
- palette
366
- );
367
- }
368
- if (roleLabel) getOrAddRole(roleLabel, j + 1, roleColor);
369
- }
370
- i = j - 1; // outer loop's i++ lands on the first non-block line
371
- continue;
372
- }
373
-
374
- const lower = trimmed.toLowerCase();
375
- const removedMsg = REMOVED_BOOLEANS[lower];
376
- if (removedMsg) {
377
- warn(lineNumber, removedMsg);
378
- continue;
379
- }
380
- if (KNOWN_BOOLEANS.has(lower)) {
381
- options[lower] = 'on';
382
- continue;
383
- }
384
- if (tryParseSharedOption(trimmed, options)) continue;
385
-
386
- const optMatch = trimmed.match(OPTION_NOCOLON_RE);
387
- if (optMatch) {
388
- // Capture groups [1] and [2] guaranteed by OPTION_NOCOLON_RE shape.
389
- const key = optMatch[1]!.trim().toLowerCase();
390
- if (KNOWN_OPTIONS.has(key)) {
391
- const value = optMatch[2]!.trim();
392
- if (key === 'roles') {
393
- rolesExplicit = true;
394
- // Inline form is name-only. For per-role color use the
395
- // block form: each role on its own line with `| color: ...`.
396
- const declared = value
397
- .split(',')
398
- .map((s) => s.trim())
399
- .filter((s) => s.length > 0);
400
- for (const rawRole of declared) {
401
- getOrAddRole(rawRole, lineNumber);
402
- }
403
- } else {
404
- options[key] = value;
405
- }
406
- continue;
407
- }
408
- }
409
- // Falls through → treated as a task line below.
410
- }
411
-
412
- // ── Phase header: `[Label]` at indent 0 ────────────────────
413
- if (indent === 0) {
414
- const phaseMatch = trimmed.match(PHASE_RE);
415
- if (phaseMatch) {
416
- bodyStarted = true;
417
- finalizePhase(lineNumber);
418
- // Capture group [1] guaranteed by PHASE_RE shape.
419
- const label = phaseMatch[1]!.trim();
420
- const display = displayNameOf(label);
421
- if (!display) {
422
- errorAt(lineNumber, 'Phase label is empty.');
423
- continue;
424
- }
425
- // Parse tail: optional trailing-token color.
426
- let phaseColor: string | undefined;
427
- const tail = (phaseMatch[2] ?? '').trim();
428
- const colorMatch = tail.match(PHASE_PALETTE_COLOR_RE);
429
- if (colorMatch) {
430
- phaseColor = resolveColorWithDiagnostic(
431
- colorMatch[1]!,
432
- lineNumber,
433
- result.diagnostics,
434
- palette
435
- );
436
- }
437
- currentPhase = {
438
- id: normalizeName(display),
439
- displayName: display,
440
- ...(phaseColor !== undefined && { color: phaseColor }),
441
- tasks: [],
442
- lineNumber,
443
- endLineNumber: lineNumber,
444
- };
445
- result.phases.push(currentPhase);
446
- continue;
447
- }
448
- }
449
-
450
- // Helper to start a fresh task. Called from each line-shape that
451
- // resolves to a task declaration.
452
- const startTask = (): void => {
453
- const display = displayNameOf(trimmed);
454
- if (!display) {
455
- errorAt(lineNumber, 'Task name is empty.');
456
- return;
457
- }
458
- const taskId = registerTask(display, lineNumber);
459
-
460
- const task: Writable<RaciTask> = {
461
- id: taskId,
462
- displayName: display,
463
- description: '',
464
- roleAssignments: [],
465
- lineNumber,
466
- endLineNumber: lineNumber,
467
- };
468
-
469
- if (currentPhase) {
470
- currentPhase.tasks.push(task);
471
- } else {
472
- result.tasksWithoutPhase.push(task);
473
- }
474
- currentTask = task;
475
- taskIndent = indent;
476
- taskHasRoleAssignment = false;
477
- };
478
-
479
- // ── Top-level task at indent 0 (no current phase) ──────────
480
- if (indent === 0 && !currentPhase) {
481
- bodyStarted = true;
482
- finalizeTask(lineNumber);
483
- startTask();
484
- continue;
485
- }
486
-
487
- // ── Top-level task at indent 0 that closes an open phase ───
488
- if (indent === 0 && currentPhase) {
489
- bodyStarted = true;
490
- finalizePhase(lineNumber);
491
- startTask();
492
- continue;
493
- }
494
-
495
- // ── Task under an open phase: indent > 0 and either no
496
- // current task yet, or this line is at-or-shallower than
497
- // the current task's indent (a sibling task closing it).
498
- if (currentPhase && indent > 0 && (!currentTask || indent <= taskIndent)) {
499
- bodyStarted = true;
500
- finalizeTask(lineNumber);
501
- startTask();
502
- continue;
503
- }
504
-
505
- // ── Lines under an open task (indent > taskIndent) ──────────
506
- if (currentTask && indent > taskIndent) {
507
- // Snapshot — `let`-binding narrowing is lost across nested
508
- // closures (warn/errorAt/getOrAddRole), so capture the
509
- // narrowed reference once and use it for the rest of the block.
510
- const task: Writable<RaciTask> = currentTask;
511
- // Resolve to (rawRole, markersBlob) for either the colon form
512
- // (`Bos: A R`) or the colon-less form (`Bos A R`, `Bos AR`).
513
- const parsedAssign = parseRoleAssignmentLine(trimmed);
514
- if (parsedAssign) {
515
- const { rawRole, markersBlob } = parsedAssign;
516
- const markers: RaciMarker[] = [];
517
- // Tokens may be space-separated (`A R`), concatenated (`AR`),
518
- // or any mix (`A RC I`). Split by whitespace, then by character.
519
- // Validation against the resolved variant's alphabet happens in
520
- // a post-pass after variant resolution; here we accept any token
521
- // from the union of alphabets and reject unknown letters outright.
522
- const tokens: string[] = [];
523
- for (const tok of markersBlob.split(/\s+/)) {
524
- if (!tok) continue;
525
- for (const ch of tok) tokens.push(ch);
526
- }
527
- for (const tok of tokens) {
528
- if (ALL_MARKERS.has(tok as RaciMarker)) {
529
- markers.push(tok as RaciMarker);
530
- } else {
531
- result.diagnostics.push(
532
- emit(RACI_DX.INVALID_MARKER, lineNumber, { marker: tok })
533
- );
534
- }
535
- }
536
-
537
- const roleId = getOrAddRole(rawRole, lineNumber);
538
- if (roleId === null) continue;
539
-
540
- // unknown-role diagnostic only when `roles:` is declared.
541
- if (rolesExplicit) {
542
- // The role is "unknown" if it wasn't pre-declared at the
543
- // header — but `getOrAddRole` adds it on first sight. So
544
- // detect by whether it was declared before `bodyStarted`.
545
- // We piggyback the `declaredLine` we recorded.
546
- const entry = roleStore.get(roleId);
547
- if (entry?.declaredLine === lineNumber) {
548
- const candidates = result.roleDisplayNames.filter(
549
- (n) => n !== entry.displayName
550
- );
551
- const hint = suggest(rawRole, candidates);
552
- result.diagnostics.push(
553
- emit(RACI_DX.UNKNOWN_ROLE, lineNumber, {
554
- role: rawRole,
555
- hint: hint ?? '',
556
- })
557
- );
558
- }
559
- }
560
-
561
- const assignment: RaciRoleAssignment = {
562
- id: roleId,
563
- displayName: roleStore.get(roleId)!.displayName,
564
- markers,
565
- lineNumber,
566
- endLineNumber: lineNumber,
567
- };
568
- task.roleAssignments.push(assignment);
569
- task.endLineNumber = lineNumber;
570
- taskHasRoleAssignment = true;
571
- continue;
572
- }
573
-
574
- // Description line — only valid BEFORE the first role assignment.
575
- if (!taskHasRoleAssignment) {
576
- task.description = task.description
577
- ? task.description + '\n' + trimmed
578
- : trimmed;
579
- task.endLineNumber = lineNumber;
580
- continue;
581
- }
582
-
583
- // Free-text line AFTER a role assignment is unexpected.
584
- result.diagnostics.push(
585
- emit(RACI_DX.UNEXPECTED_LINE, lineNumber, { task: task.displayName })
586
- );
587
- continue;
588
- }
589
-
590
- // ── Catch-all: stray content line in body ────────────────────
591
- if (bodyStarted) {
592
- errorAt(
593
- lineNumber,
594
- `Unexpected line: '${trimmed}'. In the body expect a '[Phase]' header, an indented task name, or an assignment 'Role: A R C I' under a task. (§24A)`
595
- );
596
- } else {
597
- // Pre-body unrecognized line — likely a typo'd directive. Tolerate
598
- // by emitting a warning rather than failing the whole parse.
599
- warn(lineNumber, `Unrecognized directive: '${trimmed}'.`);
600
- }
601
- }
602
-
603
- finalizePhase(lines.length + 1);
604
-
605
- // ── Variant resolution ───────────────────────────────────────
606
- //
607
- // Inferred purely from the markers actually used. Mixed `D` + `S` is
608
- // unresolvable → MIXED_VARIANTS. Falls back to RASCI for downstream rule
609
- // application so cells still render, but the chart-level error makes the
610
- // conflict loud.
611
-
612
- const usedMarkers: RaciMarker[] = [];
613
- for (const task of allTasks(result)) {
614
- for (const a of task.roleAssignments) {
615
- for (const m of a.markers) usedMarkers.push(m);
616
- }
617
- }
618
-
619
- const inferred = inferVariant(usedMarkers);
620
- if (inferred === null) {
621
- const titleLine = result.titleLineNumber ?? 1;
622
- result.diagnostics.push(emit(RACI_DX.MIXED_VARIANTS, titleLine));
623
- result.variant = 'rasci';
624
- } else {
625
- result.variant = inferred;
626
- }
627
-
628
- // ── Marker validation against the resolved variant ───────────
629
- //
630
- // The first pass accepted any letter from the union alphabet; now
631
- // that we know the variant, drop markers that fall outside it and
632
- // surface INVALID_MARKER for each.
633
-
634
- const resolvedAlphabet = VARIANTS[result.variant].alphabet;
635
- for (const task of allTasks(result)) {
636
- for (const a of task.roleAssignments) {
637
- const kept: RaciMarker[] = [];
638
- for (const m of a.markers) {
639
- if (resolvedAlphabet.includes(m)) {
640
- kept.push(m);
641
- } else {
642
- result.diagnostics.push(
643
- emit(RACI_DX.INVALID_MARKER, a.lineNumber, {
644
- marker: m,
645
- variant: result.variant.toUpperCase(),
646
- alphabet: resolvedAlphabet.join(' '),
647
- })
648
- );
649
- }
650
- }
651
- (a as Writable<RaciRoleAssignment>).markers = kept;
652
- }
653
- }
654
-
655
- // ── Constraint linting ───────────────────────────────────────
656
-
657
- const variantRules = VARIANTS[result.variant];
658
-
659
- for (const task of allTasks(result)) {
660
- for (const rule of variantRules.errorRules) {
661
- result.diagnostics.push(...rule(task));
662
- }
663
- for (const rule of variantRules.warningRules) {
664
- result.diagnostics.push(...rule(task));
665
- }
666
- }
667
-
668
- // Orphan-role check: any role declared (or first seen via assignment)
669
- // but never *actually* assigned a marker is dead weight on the matrix.
670
- const usedRoleIds = new Set<string>();
671
- for (const task of allTasks(result)) {
672
- for (const a of task.roleAssignments) {
673
- if (a.markers.length > 0) usedRoleIds.add(a.id);
674
- }
675
- }
676
- for (let i = 0; i < result.roles.length; i++) {
677
- const roleId = result.roles[i]!;
678
- if (usedRoleIds.has(roleId)) continue;
679
- const declared = roleStore.get(roleId);
680
- const displayName = result.roleDisplayNames[i] ?? roleId;
681
- result.diagnostics.push(
682
- emit(RACI_DX.ORPHAN_ROLE, declared?.declaredLine ?? 1, {
683
- role: displayName,
684
- })
685
- );
686
- }
687
-
688
- return result;
689
- }
690
-
691
- // ── Helpers ────────────────────────────────────────────────────
692
-
693
- /** Iterate every task — phased and unphased, in source order. */
694
- export function* allTasks(parsed: ParsedRaci): Iterable<RaciTask> {
695
- for (const t of parsed.tasksWithoutPhase) yield t;
696
- for (const phase of parsed.phases) {
697
- for (const t of phase.tasks) yield t;
698
- }
699
- }