dsh-model-everything 0.4.1 → 0.5.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 (114) hide show
  1. package/README.en-US.md +75 -19
  2. package/README.md +377 -345
  3. package/cordis.patch.yml +9 -0
  4. package/diagram/package.json +5 -0
  5. package/lib/client.js +3494 -0
  6. package/lib/diagram-diff-tool.js +90 -0
  7. package/lib/diagram-render-tool.js +102 -0
  8. package/lib/diagram-review-tool.js +99 -0
  9. package/lib/diagram.js +43 -0
  10. package/lib/gate.js +14 -12
  11. package/lib/presentation.js +10 -0
  12. package/lib/render/budget.js +99 -0
  13. package/lib/render/channels.js +243 -0
  14. package/lib/render/collapse.js +155 -0
  15. package/lib/render/components.js +99 -0
  16. package/lib/render/coords.js +288 -0
  17. package/lib/render/cycle-break.js +147 -0
  18. package/lib/render/diff.js +138 -0
  19. package/lib/render/dummy.js +43 -0
  20. package/lib/render/from-mermaid.js +235 -0
  21. package/lib/render/from-plantuml.js +430 -0
  22. package/lib/render/index.js +42 -0
  23. package/lib/render/layer.js +91 -0
  24. package/lib/render/layout.js +68 -0
  25. package/lib/render/model.js +17 -0
  26. package/lib/render/network-simplex.js +272 -0
  27. package/lib/render/order.js +122 -0
  28. package/lib/render/paint.js +891 -0
  29. package/lib/render/palette.js +127 -0
  30. package/lib/render/place.js +482 -0
  31. package/lib/render/route.js +225 -0
  32. package/lib/render/svg.js +229 -0
  33. package/lib/render/symbol.js +108 -0
  34. package/lib/render/text.js +74 -0
  35. package/lib/render/viewmodel.js +126 -0
  36. package/lib/render/wrap.js +87 -0
  37. package/lib/render/write.js +14 -0
  38. package/lib/scan.js +248 -8
  39. package/lib/structure-tool.js +2 -2
  40. package/lib/types/client.d.ts +83 -0
  41. package/lib/types/diagram-diff-tool.d.ts +8 -0
  42. package/lib/types/diagram-render-tool.d.ts +8 -0
  43. package/lib/types/diagram-review-tool.d.ts +8 -0
  44. package/lib/types/diagram.d.ts +34 -0
  45. package/lib/types/presentation.d.ts +7 -0
  46. package/lib/types/render/budget.d.ts +55 -0
  47. package/lib/types/render/channels.d.ts +63 -0
  48. package/lib/types/render/collapse.d.ts +23 -0
  49. package/lib/types/render/components.d.ts +57 -0
  50. package/lib/types/render/coords.d.ts +64 -0
  51. package/lib/types/render/cycle-break.d.ts +45 -0
  52. package/lib/types/render/diff.d.ts +44 -0
  53. package/lib/types/render/dummy.d.ts +32 -0
  54. package/lib/types/render/from-mermaid.d.ts +27 -0
  55. package/lib/types/render/from-plantuml.d.ts +33 -0
  56. package/lib/types/render/index.d.ts +38 -0
  57. package/lib/types/render/layer.d.ts +28 -0
  58. package/lib/types/render/layout.d.ts +54 -0
  59. package/lib/types/render/model.d.ts +73 -0
  60. package/lib/types/render/network-simplex.d.ts +54 -0
  61. package/lib/types/render/order.d.ts +27 -0
  62. package/lib/types/render/paint.d.ts +127 -0
  63. package/lib/types/render/palette.d.ts +41 -0
  64. package/lib/types/render/place.d.ts +55 -0
  65. package/lib/types/render/route.d.ts +77 -0
  66. package/lib/types/render/svg.d.ts +19 -0
  67. package/lib/types/render/symbol.d.ts +73 -0
  68. package/lib/types/render/text.d.ts +38 -0
  69. package/lib/types/render/viewmodel.d.ts +20 -0
  70. package/lib/types/render/wrap.d.ts +27 -0
  71. package/lib/types/render/write.d.ts +14 -0
  72. package/lib/types/scan.d.ts +10 -2
  73. package/lib/types/structure.d.ts +1 -0
  74. package/package.json +148 -132
  75. package/skills/model-everything/SKILL.md +454 -454
  76. package/skills/model-everything/references/architecture-domains.md +137 -0
  77. package/skills/model-everything/references/architecture-views.md +119 -0
  78. package/skills/model-everything-diagram/SKILL.md +84 -0
  79. package/src/client.ts +307 -0
  80. package/src/diagram-diff-tool.ts +94 -0
  81. package/src/diagram-render-tool.ts +108 -0
  82. package/src/diagram-review-tool.ts +109 -0
  83. package/src/diagram.ts +61 -0
  84. package/src/gate.ts +14 -12
  85. package/src/presentation.ts +12 -0
  86. package/src/render/budget.ts +135 -0
  87. package/src/render/channels.ts +309 -0
  88. package/src/render/collapse.ts +150 -0
  89. package/src/render/components.ts +126 -0
  90. package/src/render/coords.ts +328 -0
  91. package/src/render/cycle-break.ts +164 -0
  92. package/src/render/diff.ts +162 -0
  93. package/src/render/dummy.ts +65 -0
  94. package/src/render/from-mermaid.ts +261 -0
  95. package/src/render/from-plantuml.ts +469 -0
  96. package/src/render/index.ts +73 -0
  97. package/src/render/layer.ts +98 -0
  98. package/src/render/layout.ts +101 -0
  99. package/src/render/model.ts +86 -0
  100. package/src/render/network-simplex.ts +290 -0
  101. package/src/render/order.ts +139 -0
  102. package/src/render/paint.ts +1006 -0
  103. package/src/render/palette.ts +164 -0
  104. package/src/render/place.ts +527 -0
  105. package/src/render/route.ts +269 -0
  106. package/src/render/svg.ts +270 -0
  107. package/src/render/symbol.ts +137 -0
  108. package/src/render/text.ts +86 -0
  109. package/src/render/viewmodel.ts +136 -0
  110. package/src/render/wrap.ts +103 -0
  111. package/src/render/write.ts +17 -0
  112. package/src/scan.ts +807 -564
  113. package/src/structure-tool.ts +2 -2
  114. package/src/structure.ts +1 -1
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Dummy nodes: the placeholders a long edge stands on.
3
+ *
4
+ * An edge from layer 0 to layer 3 has to cross layers 1 and 2. The router can go around whatever is in the way, but a
5
+ * drawing reads better if the edge owns a lane through those layers: then ordering can keep other edges off it, and
6
+ * coordinate assignment can give the whole lane one x. A dummy node is that lane: one per layer the edge crosses.
7
+ *
8
+ * A lane is one variable, not one variable per layer. The instances keep their own id (`#d<edge>_<layer>`) so the
9
+ * ordering stage can sort them inside their layer, and they all point at one lane id (`#e<edge>`) so coordinate
10
+ * assignment cannot give the same edge three different x values.
11
+ *
12
+ * Pure: the layers, the chains, and the map between the two.
13
+ */
14
+
15
+ import type { LayoutEdge } from './layout.js'
16
+ import type { Layering } from './layer.js'
17
+
18
+ /** One edge, as the id it passes through in each layer it crosses. */
19
+ export interface Chain {
20
+ /** The diagram edge's index. */
21
+ index: number
22
+ /** The ids from the layout source to the layout target: the endpoints plus one dummy per crossed layer. */
23
+ path: string[]
24
+ }
25
+
26
+ export interface Augmented {
27
+ /** The layers with the dummies inserted; each dummy sits in the layer it crosses. */
28
+ layers: string[][]
29
+ chains: Chain[]
30
+ /** A dummy instance's lane id: the variable coordinate assignment shares between a long edge's dummies. */
31
+ chainOf: Map<string, string>
32
+ /** The lane id of every edge that needed dummies. */
33
+ laneOf: Map<number, string>
34
+ }
35
+
36
+ export function addDummies(layering: Layering, edges: LayoutEdge[]): Augmented {
37
+ const layers = layering.layers.map((layer) => [...layer])
38
+ const chains: Chain[] = []
39
+ const chainOf = new Map<string, string>()
40
+ const laneOf = new Map<number, string>()
41
+ for (const edge of edges) {
42
+ if (edge.from === edge.to) {
43
+ chains.push({ index: edge.index, path: [edge.from, edge.to] })
44
+ continue
45
+ }
46
+ const from = layering.layerOf.get(edge.from)
47
+ const to = layering.layerOf.get(edge.to)
48
+ if (from === undefined || to === undefined || to <= from) {
49
+ chains.push({ index: edge.index, path: [edge.from, edge.to] })
50
+ continue
51
+ }
52
+ const path = [edge.from]
53
+ for (let cursor = from + 1; cursor < to; cursor += 1) {
54
+ const instance = '#d' + String(edge.index) + '_' + String(cursor)
55
+ const lane = '#e' + String(edge.index)
56
+ chainOf.set(instance, lane)
57
+ laneOf.set(edge.index, lane)
58
+ layers[cursor].push(instance)
59
+ path.push(instance)
60
+ }
61
+ path.push(edge.to)
62
+ chains.push({ index: edge.index, path })
63
+ }
64
+ return { layers, chains, chainOf, laneOf }
65
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Mermaid text to a diagram, for the subset this renderer can actually draw.
3
+ *
4
+ * The subset is chosen by what a reader of an architecture document writes: a flowchart of boxes and
5
+ * arrows, a state diagram with a start marker, and the group syntax that turns a flat graph into a layered
6
+ * one. Everything outside it is refused **with its line number** rather than half-drawn: a picture that
7
+ * silently drops a lifeline or an interaction is worse than no picture, because it looks complete.
8
+ *
9
+ * Supported:
10
+ *
11
+ * - `graph TD|TB|LR` and `flowchart TD|TB|LR`, with direction carried into the layout as a hint;
12
+ * - node declarations `A[text]`, `A(text)`, `A{text}`, `A((text))`, and a bare `A`;
13
+ * - edges `-->`, `---`, `-.->`, `==>`, with a label as `-->|text|` or `-- text -->`;
14
+ * - `stateDiagram` / `stateDiagram-v2`: `[*] --> A`, `A --> B: event`, `state "text" as id`;
15
+ * - `subgraph name ... end`, which becomes a group;
16
+ * - `%%` comments and blank lines.
17
+ *
18
+ * Refused, by name: `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `journey`, `mindmap`,
19
+ * `timeline`, `quadrantChart`, `gitGraph`, `C4Context`, and `click`/`style`/`classDef` directives.
20
+ */
21
+
22
+ import { emptyDiagram, type Diagram, type DiagramEdge, type DiagramGroup, type DiagramNode } from './model.js'
23
+
24
+ export interface Parsed {
25
+ diagram: Diagram
26
+ /** Lines the parser refused to interpret, with the reason. Empty when the whole source was understood. */
27
+ refused: string[]
28
+ }
29
+
30
+ const CURVED_EDGE = /^(.+?)\s*(-->|---|-\.->|==>)\s*(?:\|([^|]*)\|\s*)?(.+)$/
31
+ const LABELLED_EDGE = /^(.+?)\s*--\s*([^->]+?)\s*-->\s*(.+)$/
32
+ const NODE = /^([A-Za-z_][\w-]*)\s*(?:\[\((.*)\)\]|\[\[(.*)\]\]|\[(.*)\]|\((.*)\)|\{(.*)\}|<<(.*)>>)?$/
33
+ const TRANSITION = /^([A-Za-z_][\w-]*|\[\*\])\s*-->\s*([A-Za-z_][\w-]*|\[\*\])\s*(?::\s*(.*))?$/
34
+ const STATE_ALIAS = /^state\s+["'](.+?)["']\s+as\s+([A-Za-z_][\w-]*)$/
35
+ const DIRECTIVE = /^(click|style|classDef|class|linkStyle|accTitle|accDescr)\b/
36
+ const UNSUPPORTED = new Set([
37
+ 'sequencediagram',
38
+ 'classdiagram',
39
+ 'erdiagram',
40
+ 'gantt',
41
+ 'pie',
42
+ 'journey',
43
+ 'mindmap',
44
+ 'timeline',
45
+ 'quadrantchart',
46
+ 'gitgraph',
47
+ 'c4context',
48
+ 'requirementdiagram',
49
+ 'sankey-beta',
50
+ ])
51
+
52
+ interface Builder {
53
+ nodes: Map<string, DiagramNode>
54
+ edges: DiagramEdge[]
55
+ groups: DiagramGroup[]
56
+ /** The boundaries currently open, outermost first. */
57
+ open: DiagramGroup[]
58
+ }
59
+
60
+ function ensure(builder: Builder, id: string, label?: string, shape?: DiagramNode['shape']): void {
61
+ const existing = builder.nodes.get(id)
62
+ if (existing === undefined) {
63
+ builder.nodes.set(id, {
64
+ id,
65
+ role: 'normal',
66
+ ...(label === undefined ? {} : { label }),
67
+ ...(shape === undefined ? {} : { shape }),
68
+ })
69
+ // Membership is decided where the node is first written: the innermost open boundary. Naming it again
70
+ // later from an outer boundary does not move it - the outer boundary holds it through `parent`.
71
+ const innermost = builder.open[builder.open.length - 1]
72
+ if (innermost !== undefined && !innermost.members.includes(id)) innermost.members.push(id)
73
+ return
74
+ }
75
+ if (label !== undefined && (existing.label === undefined || existing.label === '')) existing.label = label
76
+ }
77
+
78
+ function nodeFrom(token: string): { id: string; label?: string; shape?: DiagramNode['shape'] } | undefined {
79
+ const match = NODE.exec(token.trim())
80
+ if (match === null) return undefined
81
+ const id = match[1]
82
+ const label = [match[2], match[3], match[4], match[5], match[6], match[7]].find((value) => value !== undefined && value.trim() !== '')
83
+ // Group 2 `[(x)]` is a datastore, 3 `[[x]]` a subroutine, 4 `[x]` a rectangle, 5 `(x)` a rounded box, 6 `{x}` a
84
+ // decision, 7 `<<x>>` a hexagon. Mermaid's own mapping; a shape the notation did not state stays undefined.
85
+ const shape: DiagramNode['shape'] | undefined =
86
+ match[2] !== undefined
87
+ ? 'cylinder'
88
+ : match[3] !== undefined
89
+ ? 'subroutine'
90
+ : match[4] !== undefined
91
+ ? 'rect'
92
+ : match[5] !== undefined
93
+ ? 'stadium'
94
+ : match[6] !== undefined
95
+ ? 'diamond'
96
+ : match[7] !== undefined
97
+ ? 'hexagon'
98
+ : undefined
99
+ return {
100
+ id,
101
+ ...(label === undefined ? {} : { label: label.trim().replace(/^["']|["']$/g, '') }),
102
+ ...(shape === undefined ? {} : { shape }),
103
+ }
104
+ }
105
+
106
+ export function fromMermaid(source: string, title = 'mermaid'): Parsed {
107
+ const lines = source.split(/\r?\n/)
108
+ const refused: string[] = []
109
+ const builder: Builder = { nodes: new Map(), edges: [], groups: [], open: [] }
110
+ let mode: 'none' | 'graph' | 'state' = 'none'
111
+ let startId: string | undefined
112
+ let endCount = 0
113
+
114
+ const push = (edge: DiagramEdge): void => {
115
+ builder.edges.push(edge)
116
+ }
117
+
118
+ lines.forEach((raw, index) => {
119
+ const line = raw.replace(/%%(.*)$/, '').trim()
120
+ if (line === '') return
121
+ const where = 'line ' + String(index + 1) + ': '
122
+ const lower = line.toLowerCase()
123
+ const keyword = lower.split(/[\s{}]/)[0]
124
+
125
+ if (UNSUPPORTED.has(keyword)) {
126
+ refused.push(where + '`' + keyword + '` is not a diagram this renderer draws')
127
+ return
128
+ }
129
+ if (DIRECTIVE.test(line)) {
130
+ refused.push(where + '`' + line.split(/\s/)[0] + '` is a directive this renderer ignores')
131
+ return
132
+ }
133
+ if (/^(graph|flowchart)\b/.test(lower)) {
134
+ mode = 'graph'
135
+ return
136
+ }
137
+ if (/^statediagram(-v2)?\b/.test(lower)) {
138
+ mode = 'state'
139
+ return
140
+ }
141
+ if (/^subgraph\b/.test(lower)) {
142
+ const label = line.replace(/^subgraph\b/i, '').trim().replace(/^["']|["']$/g, '')
143
+ const id = 'subgraph-' + String(++endCount)
144
+ builder.open.push({ id, label: label === '' ? id : label, members: [] })
145
+ return
146
+ }
147
+ if (lower === 'end') {
148
+ const closed = builder.open.pop()
149
+ if (closed !== undefined) {
150
+ const container = builder.open[builder.open.length - 1]
151
+ if (container !== undefined) closed.parent = container.id
152
+ builder.groups.push(closed)
153
+ }
154
+ return
155
+ }
156
+
157
+ if (mode === 'state') {
158
+ const alias = STATE_ALIAS.exec(line)
159
+ if (alias !== null) {
160
+ ensure(builder, alias[2], alias[1])
161
+ return
162
+ }
163
+ const transition = TRANSITION.exec(line)
164
+ if (transition === null) {
165
+ // A bare `state X` declaration, or a state named on its own line.
166
+ const bare = /^state\s+([A-Za-z_][\w-]*)$/.exec(line)
167
+ if (bare !== null) {
168
+ ensure(builder, bare[1])
169
+ return
170
+ }
171
+ if (/^[A-Za-z_][\w-]*$/.test(line)) {
172
+ ensure(builder, line)
173
+ return
174
+ }
175
+ refused.push(where + '`' + line + '` is not a state transition this renderer draws')
176
+ return
177
+ }
178
+ const from = transition[1]
179
+ const to = transition[2]
180
+ const label = (transition[3] ?? '').trim()
181
+ if (from === '[*]') {
182
+ // A start marker names the state it enters, not a box of its own.
183
+ ensure(builder, to)
184
+ if (startId === undefined) startId = to
185
+ return
186
+ }
187
+ if (to === '[*]') {
188
+ ensure(builder, from)
189
+ push({ from, to: from, label: label === '' ? 'end' : label, dashed: true })
190
+ return
191
+ }
192
+ ensure(builder, from)
193
+ ensure(builder, to)
194
+ push({ from, to, label })
195
+ return
196
+ }
197
+
198
+ const labelled = LABELLED_EDGE.exec(line)
199
+ if (labelled !== null) {
200
+ const left = nodeFrom(labelled[1])
201
+ const right = nodeFrom(labelled[3])
202
+ if (left === undefined || right === undefined) {
203
+ refused.push(where + '`' + line + '` does not name two nodes')
204
+ return
205
+ }
206
+ ensure(builder, left.id, left.label, left.shape)
207
+ ensure(builder, right.id, right.label, right.shape)
208
+ push({ from: left.id, to: right.id, label: labelled[2].trim() })
209
+ return
210
+ }
211
+
212
+ const edge = CURVED_EDGE.exec(line)
213
+ if (edge !== null) {
214
+ const left = nodeFrom(edge[1])
215
+ const right = nodeFrom(edge[4])
216
+ if (left === undefined || right === undefined) {
217
+ refused.push(where + '`' + line + '` does not name two nodes')
218
+ return
219
+ }
220
+ ensure(builder, left.id, left.label, left.shape)
221
+ ensure(builder, right.id, right.label, right.shape)
222
+ push({
223
+ from: left.id,
224
+ to: right.id,
225
+ label: (edge[3] ?? '').trim(),
226
+ ...(edge[2] === '-.->' || edge[2] === '==>' ? { dashed: true } : {}),
227
+ })
228
+ return
229
+ }
230
+
231
+ const single = nodeFrom(line)
232
+ if (single !== undefined) {
233
+ ensure(builder, single.id, single.label, single.shape)
234
+ return
235
+ }
236
+
237
+ refused.push(where + '`' + line + '` is not something this renderer draws')
238
+ })
239
+
240
+ if (mode === 'none') {
241
+ return { diagram: emptyDiagram(title, 'no mermaid diagram found'), refused }
242
+ }
243
+
244
+ if (builder.nodes.size === 0) {
245
+ return { diagram: emptyDiagram(title, 'no nodes found'), refused }
246
+ }
247
+
248
+ const diagram: Diagram = {
249
+ kind: mode === 'state' ? 'machine' : 'graph',
250
+ title,
251
+ ...(startId === undefined ? {} : { init: startId }),
252
+ nodes: [...builder.nodes.values()],
253
+ edges: builder.edges,
254
+ ...(builder.groups.length === 0 ? {} : { groups: builder.groups }),
255
+ caption:
256
+ mode + ', ' + String(builder.nodes.size) + ' box' + (builder.nodes.size === 1 ? '' : 'es') + ', ' +
257
+ String(builder.edges.length) + ' arrow' + (builder.edges.length === 1 ? '' : 's') +
258
+ (refused.length === 0 ? '' : ', ' + String(refused.length) + ' line(s) refused'),
259
+ }
260
+ return { diagram, refused }
261
+ }