@orchestrator-claude/definitions 3.37.3 → 3.39.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 (74) hide show
  1. package/agents/agentic-architect.md +174 -0
  2. package/agents/architecture-mediator.md +215 -0
  3. package/agents/azure-devops-engineer.md +2 -2
  4. package/agents/backend-architect.md +198 -0
  5. package/agents/cicd-platform-engineer.md +3 -2
  6. package/agents/contract-architect.md +210 -0
  7. package/agents/datamodel-architect.md +202 -0
  8. package/agents/frontend-architect.md +166 -0
  9. package/agents/frontend-engineer.md +10 -7
  10. package/agents/harness-architect.md +51 -32
  11. package/agents/harness-engineer.md +175 -0
  12. package/agents/implementer.md +10 -1
  13. package/agents/infra-architect.md +160 -0
  14. package/agents/operational-artifact-builder.md +1 -1
  15. package/agents/python-data-engineer.md +5 -4
  16. package/agents/software-architect.md +29 -147
  17. package/hooks/hooks.json +2 -2
  18. package/hooks/pre-agent-dispatch.mjs +3 -1
  19. package/hooks/pre-agent-dispatch.ts +2 -0
  20. package/hooks/subagent-start.ts +3 -1
  21. package/manifest.json +101 -13
  22. package/package.json +1 -1
  23. package/skills/archify/LICENSE +22 -0
  24. package/skills/archify/SKILL.md +278 -0
  25. package/skills/archify/VENDOR.txt +70 -0
  26. package/skills/archify/assets/template.html +1261 -0
  27. package/skills/archify/bin/archify.mjs +271 -0
  28. package/skills/archify/examples/agent-run.lifecycle.json +64 -0
  29. package/skills/archify/examples/agent-tool-call.workflow.json +88 -0
  30. package/skills/archify/examples/cache-miss-request.sequence.json +75 -0
  31. package/skills/archify/examples/product-analytics.dataflow.json +70 -0
  32. package/skills/archify/examples/web-app.architecture.json +41 -0
  33. package/skills/archify/package-lock.json +80 -0
  34. package/skills/archify/package.json +23 -0
  35. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  36. package/skills/archify/renderers/architecture/render-architecture.mjs +371 -0
  37. package/skills/archify/renderers/dataflow/README.md +82 -0
  38. package/skills/archify/renderers/dataflow/render-dataflow.mjs +295 -0
  39. package/skills/archify/renderers/lifecycle/README.md +91 -0
  40. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +384 -0
  41. package/skills/archify/renderers/sequence/README.md +84 -0
  42. package/skills/archify/renderers/sequence/render-sequence.mjs +257 -0
  43. package/skills/archify/renderers/shared/cli.mjs +44 -0
  44. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  45. package/skills/archify/renderers/shared/geometry.mjs +230 -0
  46. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  47. package/skills/archify/renderers/shared/utils.mjs +88 -0
  48. package/skills/archify/renderers/shared/validator.mjs +40 -0
  49. package/skills/archify/renderers/workflow/README.md +103 -0
  50. package/skills/archify/renderers/workflow/render-workflow.mjs +481 -0
  51. package/skills/archify/schemas/README.md +72 -0
  52. package/skills/archify/schemas/architecture.schema.json +121 -0
  53. package/skills/archify/schemas/common.schema.json +43 -0
  54. package/skills/archify/schemas/dataflow.schema.json +221 -0
  55. package/skills/archify/schemas/lifecycle.schema.json +241 -0
  56. package/skills/archify/schemas/sequence.schema.json +186 -0
  57. package/skills/archify/schemas/workflow.schema.json +332 -0
  58. package/skills/archify/scripts/check-render-output.mjs +297 -0
  59. package/skills/archify/scripts/generate-validators.mjs +66 -0
  60. package/skills/archify/scripts/render-examples.mjs +26 -0
  61. package/skills/archify/test/animation.test.mjs +53 -0
  62. package/skills/archify/test/cli.test.mjs +206 -0
  63. package/skills/archify/test/degraded.test.mjs +157 -0
  64. package/skills/archify/test/generate-validators.test.mjs +40 -0
  65. package/skills/archify/test/geometry.test.mjs +186 -0
  66. package/skills/archify/test/golden.mjs +167 -0
  67. package/skills/archify/test/grid.test.mjs +47 -0
  68. package/skills/archify/test/layout-rules.test.mjs +193 -0
  69. package/skills/archify/test/render-output-checks.test.mjs +75 -0
  70. package/skills/arquitetura-lane-dispatch/SKILL.md +205 -0
  71. package/skills/arquitetura-mediada-arquiteto/SKILL.md +261 -0
  72. package/skills/checkpoint-protocol/SKILL.md +1 -1
  73. package/skills/orchestrator/SKILL.md +1 -1
  74. package/skills/release/release.sh +63 -6
@@ -0,0 +1,332 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/tt-a1i/archify/schemas/workflow.schema.json",
4
+ "title": "Archify Workflow Diagram",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version",
9
+ "diagram_type",
10
+ "meta",
11
+ "lanes",
12
+ "nodes",
13
+ "edges"
14
+ ],
15
+ "properties": {
16
+ "schema_version": {
17
+ "const": 1
18
+ },
19
+ "diagram_type": {
20
+ "const": "workflow"
21
+ },
22
+ "meta": {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "required": [
26
+ "title"
27
+ ],
28
+ "properties": {
29
+ "title": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ },
33
+ "subtitle": {
34
+ "type": "string"
35
+ },
36
+ "output": {
37
+ "type": "string"
38
+ },
39
+ "animation": {
40
+ "enum": [
41
+ "trace",
42
+ "none"
43
+ ]
44
+ },
45
+ "viewBox": {
46
+ "type": "array",
47
+ "prefixItems": [
48
+ {
49
+ "type": "number",
50
+ "minimum": 700
51
+ },
52
+ {
53
+ "type": "number",
54
+ "minimum": 240
55
+ }
56
+ ],
57
+ "items": false,
58
+ "minItems": 2,
59
+ "maxItems": 2
60
+ }
61
+ }
62
+ },
63
+ "lanes": {
64
+ "type": "array",
65
+ "minItems": 1,
66
+ "items": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "required": [
70
+ "id",
71
+ "label"
72
+ ],
73
+ "properties": {
74
+ "id": {
75
+ "$ref": "common.schema.json#/$defs/id"
76
+ },
77
+ "label": {
78
+ "type": "string",
79
+ "minLength": 1
80
+ },
81
+ "variant": {
82
+ "enum": [
83
+ "normal",
84
+ "exception"
85
+ ]
86
+ }
87
+ }
88
+ }
89
+ },
90
+ "phases": {
91
+ "type": "array",
92
+ "items": {
93
+ "type": "object",
94
+ "additionalProperties": false,
95
+ "required": [
96
+ "id",
97
+ "label",
98
+ "fromCol",
99
+ "toCol"
100
+ ],
101
+ "properties": {
102
+ "id": {
103
+ "$ref": "common.schema.json#/$defs/id"
104
+ },
105
+ "label": {
106
+ "type": "string",
107
+ "minLength": 1
108
+ },
109
+ "fromCol": {
110
+ "type": "integer",
111
+ "minimum": 0,
112
+ "maximum": 5
113
+ },
114
+ "toCol": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "maximum": 5
118
+ },
119
+ "variant": {
120
+ "enum": [
121
+ "default",
122
+ "emphasis",
123
+ "security",
124
+ "dashed"
125
+ ]
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "groups": {
131
+ "type": "array",
132
+ "items": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "required": [
136
+ "id",
137
+ "label",
138
+ "lane",
139
+ "fromCol",
140
+ "toCol"
141
+ ],
142
+ "properties": {
143
+ "id": {
144
+ "$ref": "common.schema.json#/$defs/id"
145
+ },
146
+ "label": {
147
+ "type": "string",
148
+ "minLength": 1
149
+ },
150
+ "lane": {
151
+ "$ref": "common.schema.json#/$defs/id"
152
+ },
153
+ "fromCol": {
154
+ "type": "integer",
155
+ "minimum": 0,
156
+ "maximum": 5
157
+ },
158
+ "toCol": {
159
+ "type": "integer",
160
+ "minimum": 0,
161
+ "maximum": 5
162
+ },
163
+ "variant": {
164
+ "enum": [
165
+ "default",
166
+ "emphasis",
167
+ "security",
168
+ "dashed"
169
+ ]
170
+ }
171
+ }
172
+ }
173
+ },
174
+ "mainPath": {
175
+ "type": "array",
176
+ "minItems": 2,
177
+ "items": {
178
+ "$ref": "common.schema.json#/$defs/id"
179
+ }
180
+ },
181
+ "nodes": {
182
+ "type": "array",
183
+ "minItems": 1,
184
+ "items": {
185
+ "type": "object",
186
+ "additionalProperties": false,
187
+ "required": [
188
+ "id",
189
+ "lane",
190
+ "col",
191
+ "type",
192
+ "label"
193
+ ],
194
+ "properties": {
195
+ "id": {
196
+ "$ref": "common.schema.json#/$defs/id"
197
+ },
198
+ "lane": {
199
+ "$ref": "common.schema.json#/$defs/id"
200
+ },
201
+ "col": {
202
+ "type": "integer",
203
+ "minimum": 0,
204
+ "maximum": 5
205
+ },
206
+ "type": {
207
+ "$ref": "common.schema.json#/$defs/componentType"
208
+ },
209
+ "label": {
210
+ "type": "string",
211
+ "minLength": 1
212
+ },
213
+ "sublabel": {
214
+ "type": "string"
215
+ },
216
+ "tag": {
217
+ "type": "string"
218
+ },
219
+ "width": {
220
+ "type": "number",
221
+ "minimum": 32
222
+ },
223
+ "height": {
224
+ "type": "number",
225
+ "minimum": 32
226
+ },
227
+ "yOffset": {
228
+ "type": "number"
229
+ }
230
+ }
231
+ }
232
+ },
233
+ "edges": {
234
+ "type": "array",
235
+ "items": {
236
+ "type": "object",
237
+ "additionalProperties": false,
238
+ "required": [
239
+ "from",
240
+ "to"
241
+ ],
242
+ "properties": {
243
+ "from": {
244
+ "$ref": "common.schema.json#/$defs/id"
245
+ },
246
+ "to": {
247
+ "$ref": "common.schema.json#/$defs/id"
248
+ },
249
+ "label": {
250
+ "type": "string"
251
+ },
252
+ "variant": {
253
+ "$ref": "common.schema.json#/$defs/variant"
254
+ },
255
+ "role": {
256
+ "enum": [
257
+ "main",
258
+ "branch",
259
+ "async",
260
+ "return",
261
+ "error"
262
+ ]
263
+ },
264
+ "fromSide": {
265
+ "$ref": "#/$defs/side"
266
+ },
267
+ "toSide": {
268
+ "$ref": "#/$defs/side"
269
+ },
270
+ "route": {
271
+ "enum": [
272
+ "auto",
273
+ "straight",
274
+ "drop",
275
+ "outside-right",
276
+ "return-left",
277
+ "bottom-channel",
278
+ "up-channel"
279
+ ]
280
+ },
281
+ "via": {
282
+ "type": "array",
283
+ "items": {
284
+ "$ref": "common.schema.json#/$defs/point"
285
+ }
286
+ },
287
+ "labelAt": {
288
+ "$ref": "common.schema.json#/$defs/point"
289
+ },
290
+ "labelDx": {
291
+ "type": "number"
292
+ },
293
+ "labelDy": {
294
+ "type": "number"
295
+ },
296
+ "labelSegment": {
297
+ "type": "integer",
298
+ "minimum": 0
299
+ },
300
+ "channelX": {
301
+ "type": "number"
302
+ },
303
+ "channelY": {
304
+ "type": "number"
305
+ },
306
+ "bias": {
307
+ "type": "number",
308
+ "minimum": 0,
309
+ "maximum": 1
310
+ },
311
+ "width": {
312
+ "type": "number",
313
+ "minimum": 0.5
314
+ }
315
+ }
316
+ }
317
+ },
318
+ "cards": {
319
+ "$ref": "common.schema.json#/$defs/cards"
320
+ }
321
+ },
322
+ "$defs": {
323
+ "side": {
324
+ "enum": [
325
+ "left",
326
+ "right",
327
+ "top",
328
+ "bottom"
329
+ ]
330
+ }
331
+ }
332
+ }
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const input = process.argv[2];
7
+
8
+ if (!input || input === '-h' || input === '--help') {
9
+ console.error('Usage: node scripts/check-render-output.mjs <diagram.html>');
10
+ process.exit(input ? 0 : 2);
11
+ }
12
+
13
+ const htmlPath = path.resolve(input);
14
+ let html;
15
+ try {
16
+ html = fs.readFileSync(htmlPath, 'utf8');
17
+ } catch (err) {
18
+ console.error(JSON.stringify({
19
+ ok: false,
20
+ file: htmlPath,
21
+ checks: [{ name: 'file_readable', ok: false, details: [err.message] }],
22
+ }, null, 2));
23
+ process.exit(1);
24
+ }
25
+
26
+ const checks = [];
27
+
28
+ function addCheck(name, ok, details = []) {
29
+ checks.push({ name, ok, details });
30
+ }
31
+
32
+ const svgMatches = [...html.matchAll(/<svg\b[\s\S]*?<\/svg>/gi)];
33
+ addCheck('single_svg', svgMatches.length === 1, [`found ${svgMatches.length} <svg> block(s)`]);
34
+
35
+ if (svgMatches.length === 1) {
36
+ const svg = svgMatches[0][0];
37
+ addCheck('finite_svg', !/\b(?:NaN|undefined|Infinity|-Infinity)\b/.test(svg));
38
+ const legendStart = svg.indexOf('<!-- Legend -->');
39
+ const beforeLegend = legendStart >= 0 ? svg.slice(0, legendStart) : svg;
40
+ const arrows = collectArrows(beforeLegend);
41
+ const diagonal = arrows.filter((arrow) => isTwoPointDiagonal(arrow));
42
+ addCheck(
43
+ 'orthogonal_arrows',
44
+ diagonal.length === 0,
45
+ diagonal.map((arrow) => `${arrow.kind} ${arrow.index}: ${arrow.raw}`),
46
+ );
47
+
48
+ if (legendStart >= 0) {
49
+ const legendFragment = svg.slice(legendStart);
50
+ const legendBoxes = collectLegendBoxes(legendFragment);
51
+ const collisions = collectLegendCollisions(arrows, legendBoxes);
52
+ addCheck(
53
+ 'legend_clearance',
54
+ collisions.length === 0,
55
+ collisions.map((hit) => `${hit.arrow.kind} ${hit.arrow.index} crosses legend ${hit.box.label}`),
56
+ );
57
+ } else {
58
+ addCheck('legend_clearance', true, ['no legend marker found']);
59
+ }
60
+ }
61
+
62
+ const ok = checks.every((check) => check.ok);
63
+ console.log(JSON.stringify({ ok, file: htmlPath, checks }, null, 2));
64
+ process.exit(ok ? 0 : 1);
65
+
66
+ function collectArrows(fragment) {
67
+ const arrows = [];
68
+ let index = 0;
69
+
70
+ for (const tag of fragment.matchAll(/<(path|line)\b[^>]*>/gi)) {
71
+ const raw = tag[0];
72
+ if (!/\bclass="[^"]*\ba-(?:default|emphasis|security|dashed)\b/.test(raw)) continue;
73
+ if (!/\bmarker-end=/.test(raw)) continue;
74
+ const attrs = parseAttrs(raw);
75
+ const segments = tag[1].toLowerCase() === 'line'
76
+ ? lineSegments(attrs)
77
+ : pathSegments(attrs.d || '');
78
+ arrows.push({ kind: tag[1].toLowerCase(), index: index += 1, raw, segments });
79
+ }
80
+
81
+ return arrows;
82
+ }
83
+
84
+ function lineSegments(attrs) {
85
+ const start = [numberAttr(attrs, 'x1'), numberAttr(attrs, 'y1')];
86
+ const end = [numberAttr(attrs, 'x2'), numberAttr(attrs, 'y2')];
87
+ if (!isPoint(start) || !isPoint(end)) return [];
88
+ return [{ start, end }];
89
+ }
90
+
91
+ function pathSegments(d) {
92
+ const points = pointsFromPath(d);
93
+ const segments = [];
94
+ for (let i = 1; i < points.length; i += 1) {
95
+ segments.push({ start: points[i - 1], end: points[i] });
96
+ }
97
+ return segments;
98
+ }
99
+
100
+ function isTwoPointDiagonal(arrow) {
101
+ if (arrow.segments.length !== 1) return false;
102
+ const { start, end } = arrow.segments[0];
103
+ return Math.abs(start[0] - end[0]) > 0.01 && Math.abs(start[1] - end[1]) > 0.01;
104
+ }
105
+
106
+ function collectLegendBoxes(fragment) {
107
+ const boxes = [];
108
+
109
+ for (const match of fragment.matchAll(/<rect\b[^>]*>/gi)) {
110
+ const attrs = parseAttrs(match[0]);
111
+ const x = numberAttr(attrs, 'x');
112
+ const y = numberAttr(attrs, 'y');
113
+ const width = numberAttr(attrs, 'width');
114
+ const height = numberAttr(attrs, 'height');
115
+ if ([x, y, width, height].every(Number.isFinite)) {
116
+ boxes.push({ x1: x, y1: y, x2: x + width, y2: y + height, label: `rect@${x},${y}` });
117
+ }
118
+ }
119
+
120
+ for (const match of fragment.matchAll(/<text\b([^>]*)>([\s\S]*?)<\/text>/gi)) {
121
+ const attrs = parseAttrs(match[1]);
122
+ const box = textBox(attrs, stripTags(match[2]).trim());
123
+ if (box) boxes.push(box);
124
+ }
125
+
126
+ return boxes;
127
+ }
128
+
129
+ function collectLegendCollisions(arrows, boxes) {
130
+ const collisions = [];
131
+ for (const arrow of arrows) {
132
+ for (const segment of arrow.segments) {
133
+ for (const box of boxes) {
134
+ if (segmentIntersectsBox(segment, padBox(box, 2))) {
135
+ collisions.push({ arrow, box });
136
+ }
137
+ }
138
+ }
139
+ }
140
+ return collisions;
141
+ }
142
+
143
+ function textBox(attrs, text) {
144
+ const x = numberAttr(attrs, 'x');
145
+ const y = numberAttr(attrs, 'y');
146
+ const fontSize = Number.parseFloat(attrs['font-size'] || '10');
147
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(fontSize)) return null;
148
+ const width = estimatedTextWidth(text, fontSize);
149
+ const anchor = attrs['text-anchor'] || 'start';
150
+ let x1 = x;
151
+ if (anchor === 'middle') x1 = x - width / 2;
152
+ if (anchor === 'end') x1 = x - width;
153
+ return {
154
+ x1,
155
+ y1: y - fontSize,
156
+ x2: x1 + width,
157
+ y2: y + fontSize * 0.25,
158
+ label: text || `text@${x},${y}`,
159
+ };
160
+ }
161
+
162
+ function estimatedTextWidth(text, fontSize) {
163
+ let units = 0;
164
+ for (const char of text) units += char.charCodeAt(0) > 255 ? 1.8 : 0.62;
165
+ return Math.max(fontSize, units * fontSize);
166
+ }
167
+
168
+ function pointsFromPath(d) {
169
+ const tokens = d.match(/[MLHVZmlhvz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || [];
170
+ const points = [];
171
+ let i = 0;
172
+ let command = '';
173
+ let current = [0, 0];
174
+ let start = null;
175
+
176
+ while (i < tokens.length) {
177
+ if (isCommand(tokens[i])) command = tokens[i++];
178
+ if (!command) break;
179
+
180
+ const absolute = command === command.toUpperCase();
181
+ switch (command.toUpperCase()) {
182
+ case 'M':
183
+ case 'L': {
184
+ while (i + 1 < tokens.length && !isCommand(tokens[i])) {
185
+ const x = Number.parseFloat(tokens[i++]);
186
+ const y = Number.parseFloat(tokens[i++]);
187
+ if (!Number.isFinite(x) || !Number.isFinite(y)) break;
188
+ current = absolute ? [x, y] : [current[0] + x, current[1] + y];
189
+ points.push(current);
190
+ if (!start) start = current;
191
+ }
192
+ break;
193
+ }
194
+ case 'H': {
195
+ while (i < tokens.length && !isCommand(tokens[i])) {
196
+ const x = Number.parseFloat(tokens[i++]);
197
+ if (!Number.isFinite(x)) break;
198
+ current = absolute ? [x, current[1]] : [current[0] + x, current[1]];
199
+ points.push(current);
200
+ }
201
+ break;
202
+ }
203
+ case 'V': {
204
+ while (i < tokens.length && !isCommand(tokens[i])) {
205
+ const y = Number.parseFloat(tokens[i++]);
206
+ if (!Number.isFinite(y)) break;
207
+ current = absolute ? [current[0], y] : [current[0], current[1] + y];
208
+ points.push(current);
209
+ }
210
+ break;
211
+ }
212
+ case 'Z': {
213
+ if (start) points.push(start);
214
+ break;
215
+ }
216
+ default:
217
+ return [];
218
+ }
219
+ }
220
+
221
+ return points.filter(isPoint);
222
+ }
223
+
224
+ function segmentIntersectsBox(segment, box) {
225
+ const { start, end } = segment;
226
+ if (pointInsideBox(start, box) || pointInsideBox(end, box)) return true;
227
+ const edges = [
228
+ [[box.x1, box.y1], [box.x2, box.y1]],
229
+ [[box.x2, box.y1], [box.x2, box.y2]],
230
+ [[box.x2, box.y2], [box.x1, box.y2]],
231
+ [[box.x1, box.y2], [box.x1, box.y1]],
232
+ ];
233
+ return edges.some(([a, b]) => segmentsIntersect(start, end, a, b));
234
+ }
235
+
236
+ function segmentsIntersect(a, b, c, d) {
237
+ const o1 = orientation(a, b, c);
238
+ const o2 = orientation(a, b, d);
239
+ const o3 = orientation(c, d, a);
240
+ const o4 = orientation(c, d, b);
241
+
242
+ if (o1 !== o2 && o3 !== o4) return true;
243
+ if (o1 === 0 && onSegment(a, c, b)) return true;
244
+ if (o2 === 0 && onSegment(a, d, b)) return true;
245
+ if (o3 === 0 && onSegment(c, a, d)) return true;
246
+ if (o4 === 0 && onSegment(c, b, d)) return true;
247
+ return false;
248
+ }
249
+
250
+ function orientation(a, b, c) {
251
+ const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]);
252
+ if (Math.abs(value) < 1e-9) return 0;
253
+ return value > 0 ? 1 : 2;
254
+ }
255
+
256
+ function onSegment(a, b, c) {
257
+ return b[0] <= Math.max(a[0], c[0]) + 1e-9
258
+ && b[0] + 1e-9 >= Math.min(a[0], c[0])
259
+ && b[1] <= Math.max(a[1], c[1]) + 1e-9
260
+ && b[1] + 1e-9 >= Math.min(a[1], c[1]);
261
+ }
262
+
263
+ function pointInsideBox(point, box) {
264
+ return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2;
265
+ }
266
+
267
+ function padBox(box, padding) {
268
+ return {
269
+ ...box,
270
+ x1: box.x1 - padding,
271
+ y1: box.y1 - padding,
272
+ x2: box.x2 + padding,
273
+ y2: box.y2 + padding,
274
+ };
275
+ }
276
+
277
+ function parseAttrs(tag) {
278
+ const attrs = {};
279
+ for (const match of tag.matchAll(/([\w:-]+)\s*=\s*"([^"]*)"/g)) attrs[match[1]] = match[2];
280
+ return attrs;
281
+ }
282
+
283
+ function numberAttr(attrs, name) {
284
+ return Number.parseFloat(attrs[name]);
285
+ }
286
+
287
+ function isCommand(token) {
288
+ return /^[A-Za-z]$/.test(token);
289
+ }
290
+
291
+ function isPoint(point) {
292
+ return Array.isArray(point) && point.length === 2 && point.every(Number.isFinite);
293
+ }
294
+
295
+ function stripTags(value) {
296
+ return value.replace(/<[^>]*>/g, '');
297
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import Ajv2020 from 'ajv/dist/2020.js';
7
+ import standaloneCode from 'ajv/dist/standalone/index.js';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const root = path.resolve(__dirname, '..');
11
+ const schemasDir = path.join(root, 'schemas');
12
+ const output = path.join(root, 'renderers/shared/generated-validators.mjs');
13
+ const diagramTypes = ['workflow', 'sequence', 'dataflow', 'lifecycle', 'architecture'];
14
+
15
+ const ajv = new Ajv2020({
16
+ allErrors: true,
17
+ strict: true,
18
+ code: { source: true, esm: true },
19
+ });
20
+ ajv.addSchema(JSON.parse(fs.readFileSync(path.join(schemasDir, 'common.schema.json'), 'utf8')));
21
+
22
+ const schemaIds = {};
23
+ for (const type of diagramTypes) {
24
+ const schema = JSON.parse(fs.readFileSync(path.join(schemasDir, `${type}.schema.json`), 'utf8'));
25
+ ajv.addSchema(schema);
26
+ schemaIds[type] = schema.$id;
27
+ }
28
+
29
+ const banner = '// Generated by scripts/generate-validators.mjs. Do not edit by hand.\n';
30
+ const ajvUcs2Import = 'require("ajv/dist/runtime/ucs2length").default';
31
+ const inlineUcs2Length = `function ucs2length(str) {
32
+ const len = str.length;
33
+ let length = 0;
34
+ let pos = 0;
35
+ while (pos < len) {
36
+ length += 1;
37
+ const value = str.charCodeAt(pos++);
38
+ if (value >= 0xd800 && value <= 0xdbff && pos < len
39
+ && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1;
40
+ }
41
+ return length;
42
+ }`;
43
+ let validatorCode = standaloneCode(ajv, schemaIds);
44
+ if (!validatorCode.includes(ajvUcs2Import)) {
45
+ throw new Error('AJV standalone output no longer contains the expected ucs2length helper');
46
+ }
47
+ validatorCode = validatorCode.replaceAll(ajvUcs2Import, inlineUcs2Length);
48
+ if (validatorCode.includes('require(')) {
49
+ throw new Error('AJV standalone output contains an unexpected runtime dependency');
50
+ }
51
+ const generated = `${banner}${validatorCode}\n`;
52
+
53
+ if (process.argv.includes('--check')) {
54
+ const current = fs.existsSync(output)
55
+ ? fs.readFileSync(output, 'utf8').replace(/\r\n?/g, '\n')
56
+ : '';
57
+ if (current !== generated) {
58
+ console.error('generated validators are stale — run npm run generate:validators');
59
+ process.exit(1);
60
+ }
61
+ } else {
62
+ const temporary = `${output}.${process.pid}.tmp`;
63
+ fs.writeFileSync(temporary, generated);
64
+ fs.renameSync(temporary, output);
65
+ console.log(`generated ${path.relative(root, output)}`);
66
+ }