@drunkcoding/agents-and-skills 0.0.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 (54) hide show
  1. package/.claude-plugin/marketplace.json +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +50 -0
  4. package/package.json +33 -0
  5. package/plugins/tech-graph/.claude-plugin/plugin.json +17 -0
  6. package/plugins/tech-graph/README.md +66 -0
  7. package/plugins/tech-graph/agents/tech-graph.md +89 -0
  8. package/plugins/tech-graph/commands/tech-graph.md +13 -0
  9. package/plugins/tech-graph/scripts/check-deps.sh +55 -0
  10. package/plugins/tech-graph/skills/tech-graph/LICENSE +21 -0
  11. package/plugins/tech-graph/skills/tech-graph/README.md +555 -0
  12. package/plugins/tech-graph/skills/tech-graph/README.zh.md +514 -0
  13. package/plugins/tech-graph/skills/tech-graph/SKILL.md +559 -0
  14. package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +4 -0
  15. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
  16. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
  17. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  18. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
  19. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
  20. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
  21. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
  22. package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +181 -0
  23. package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +40 -0
  24. package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +297 -0
  25. package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +64 -0
  26. package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +45 -0
  27. package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +48 -0
  28. package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +182 -0
  29. package/plugins/tech-graph/skills/tech-graph/package.json +42 -0
  30. package/plugins/tech-graph/skills/tech-graph/references/icons.md +406 -0
  31. package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +108 -0
  32. package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +107 -0
  33. package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +113 -0
  34. package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +95 -0
  35. package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +125 -0
  36. package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +209 -0
  37. package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +215 -0
  38. package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +135 -0
  39. package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +100 -0
  40. package/plugins/tech-graph/skills/tech-graph/scripts/README.md +269 -0
  41. package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +181 -0
  42. package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +1587 -0
  43. package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +143 -0
  44. package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +306 -0
  45. package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +28 -0
  46. package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +23 -0
  47. package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +14 -0
  48. package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +28 -0
  49. package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +21 -0
  50. package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +21 -0
  51. package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +20 -0
  52. package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +20 -0
  53. package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +19 -0
  54. package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +21 -0
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # Batch Test Script
3
+ # Renders regression fixtures, validates SVGs, and exports PNGs
4
+
5
+ set -euo pipefail
6
+
7
+ # Colors
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ NC='\033[0m'
13
+
14
+ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
15
+ TEST_DIR="${SKILL_DIR}/test-output"
16
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
17
+
18
+ echo -e "${BLUE}=== Fireworks Tech Graph - Batch Test ===${NC}"
19
+ echo "Test directory: $TEST_DIR"
20
+ echo "Timestamp: $TIMESTAMP"
21
+ echo ""
22
+
23
+ # Create test directory
24
+ mkdir -p "$TEST_DIR"
25
+
26
+ # Test configuration
27
+ STYLES=(1 2 3 4 5 6 7)
28
+ STYLE_NAMES=("Flat Icon" "Dark Terminal" "Blueprint" "Notion Clean" "Glassmorphism" "Claude Official" "OpenAI Official")
29
+
30
+ # Summary counters
31
+ TOTAL=0
32
+ PASSED=0
33
+ FAILED=0
34
+
35
+ FIXTURES_DIR="${SKILL_DIR}/fixtures"
36
+
37
+ echo -e "${BLUE}Testing all styles...${NC}"
38
+ echo "----------------------------------------"
39
+
40
+ for i in "${!STYLES[@]}"; do
41
+ STYLE="${STYLES[$i]}"
42
+ STYLE_NAME="${STYLE_NAMES[$i]}"
43
+
44
+ echo -e "\n${YELLOW}Style $STYLE: $STYLE_NAME${NC}"
45
+
46
+ # Check if style reference exists
47
+ STYLE_FILE=$(find "${SKILL_DIR}/references" -maxdepth 1 -type f -name "style-${STYLE}-*.md" | head -n 1)
48
+ if [ -z "${STYLE_FILE:-}" ] || [ ! -f "$STYLE_FILE" ]; then
49
+ echo -e "${RED}✗ Style file not found: $STYLE_FILE${NC}"
50
+ FAILED=$((FAILED + 1))
51
+ TOTAL=$((TOTAL + 1))
52
+ continue
53
+ fi
54
+
55
+ echo -e "${GREEN}✓ Style file found${NC}"
56
+
57
+ if [ ! -d "$FIXTURES_DIR" ]; then
58
+ echo -e "${YELLOW}⚠ Fixtures directory not found: $FIXTURES_DIR${NC}"
59
+ continue
60
+ fi
61
+
62
+ FIXTURE_FILES=$(find "$FIXTURES_DIR" -maxdepth 1 -type f -name "*.json" | sort || true)
63
+ MATCHED_FIXTURES=()
64
+ for FIXTURE in $FIXTURE_FILES; do
65
+ FIXTURE_STYLE=$(python3 - "$FIXTURE" <<'PY'
66
+ import json
67
+ import sys
68
+ from pathlib import Path
69
+ data = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
70
+ print(data.get("style", ""))
71
+ PY
72
+ )
73
+ if [ "$FIXTURE_STYLE" = "$STYLE" ]; then
74
+ MATCHED_FIXTURES+=("$FIXTURE")
75
+ fi
76
+ done
77
+
78
+ if [ "${#MATCHED_FIXTURES[@]}" -eq 0 ]; then
79
+ echo -e "${YELLOW}⚠ No regression fixtures found for style $STYLE${NC}"
80
+ continue
81
+ fi
82
+
83
+ # Render, validate, and export each fixture
84
+ for FIXTURE in "${MATCHED_FIXTURES[@]}"; do
85
+ BASENAME=$(basename "$FIXTURE" .json)
86
+ SVG_FILE="${TEST_DIR}/${BASENAME}_${TIMESTAMP}.svg"
87
+ PNG_FILE="${TEST_DIR}/${BASENAME}_${TIMESTAMP}.png"
88
+ TEMPLATE_TYPE=$(python3 - "$FIXTURE" <<'PY'
89
+ import json
90
+ import sys
91
+ from pathlib import Path
92
+ data = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
93
+ print(data.get("template_type", "architecture"))
94
+ PY
95
+ )
96
+
97
+ echo -n " Rendering $BASENAME... "
98
+ TOTAL=$((TOTAL + 1))
99
+
100
+ if python3 "${SKILL_DIR}/scripts/generate-from-template.py" "$TEMPLATE_TYPE" "$SVG_FILE" "$(cat "$FIXTURE")" > /dev/null 2>&1 \
101
+ && "${SKILL_DIR}/scripts/validate-svg.sh" "$SVG_FILE" > /dev/null 2>&1; then
102
+ PNG_OK=false
103
+ # Prefer cairosvg (best CSS support); fall back to rsvg-convert
104
+ if python3 -c "import cairosvg" 2>/dev/null \
105
+ && python3 -c "import cairosvg; cairosvg.svg2png(url='${SVG_FILE}', write_to='${PNG_FILE}', scale=2)" 2>/dev/null; then
106
+ PNG_OK=true
107
+ elif command -v rsvg-convert &> /dev/null \
108
+ && rsvg-convert -w 1920 "$SVG_FILE" -o "$PNG_FILE" 2>/dev/null; then
109
+ PNG_OK=true
110
+ fi
111
+ if [ "$PNG_OK" = true ]; then
112
+ PNG_SIZE=$(du -h "$PNG_FILE" | cut -f1)
113
+ echo -e "${GREEN}✓ Pass${NC} (${PNG_SIZE})"
114
+ else
115
+ echo -e "${GREEN}✓ Pass${NC}"
116
+ fi
117
+ PASSED=$((PASSED + 1))
118
+ else
119
+ echo -e "${RED}✗ Fail${NC}"
120
+ FAILED=$((FAILED + 1))
121
+ if [ -f "$SVG_FILE" ]; then
122
+ "${SKILL_DIR}/scripts/validate-svg.sh" "$SVG_FILE" 2>&1 | grep -E "✗|Error" | sed 's/^/ /' || true
123
+ fi
124
+ fi
125
+ done
126
+ done
127
+
128
+ # Print summary
129
+ echo ""
130
+ echo "========================================"
131
+ echo -e "${BLUE}Test Summary${NC}"
132
+ echo "----------------------------------------"
133
+ echo "Total tests: $TOTAL"
134
+ echo -e "${GREEN}Passed: $PASSED${NC}"
135
+ echo -e "${RED}Failed: $FAILED${NC}"
136
+
137
+ if [ "$FAILED" -eq 0 ]; then
138
+ echo -e "\n${GREEN}✓ All tests passed!${NC}"
139
+ exit 0
140
+ else
141
+ echo -e "\n${RED}✗ Some tests failed${NC}"
142
+ exit 1
143
+ fi
@@ -0,0 +1,306 @@
1
+ #!/bin/bash
2
+ # SVG Validation Script
3
+ # Checks SVG syntax and reports detailed errors
4
+
5
+ set -euo pipefail
6
+
7
+ # Colors for output
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ NC='\033[0m' # No Color
12
+
13
+ if [ $# -eq 0 ]; then
14
+ echo "Usage: $0 <svg-file>"
15
+ exit 1
16
+ fi
17
+
18
+ SVG_FILE="$1"
19
+
20
+ if [ ! -f "$SVG_FILE" ]; then
21
+ echo -e "${RED}Error: File not found: $SVG_FILE${NC}"
22
+ exit 1
23
+ fi
24
+
25
+ echo "Validating SVG: $SVG_FILE"
26
+ echo "----------------------------------------"
27
+
28
+ FAILURES=0
29
+
30
+ # Check 0: XML syntax
31
+ echo -n "Checking XML syntax... "
32
+ if command -v xmllint &> /dev/null; then
33
+ if xmllint --noout "$SVG_FILE" 2>/dev/null; then
34
+ echo -e "${GREEN}✓ Pass${NC}"
35
+ else
36
+ echo -e "${RED}✗ Fail${NC}"
37
+ xmllint --noout "$SVG_FILE" 2>&1 || true
38
+ FAILURES=$((FAILURES + 1))
39
+ fi
40
+ else
41
+ echo -e "${YELLOW}⚠ Skipped${NC} (xmllint not found)"
42
+ fi
43
+
44
+ # Check 1: Tag balance
45
+ echo -n "Checking tag balance... "
46
+ OPEN_TAGS=$( { grep -o '<[A-Za-z][A-Za-z0-9:-]*' "$SVG_FILE" || true; } | { grep -v '</' || true; } | wc -l | tr -d ' ' )
47
+ SELF_CLOSING=$( { grep -o '/>' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
48
+ CLOSE_TAGS=$( { grep -o '</[A-Za-z][A-Za-z0-9:-]*>' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
49
+ TOTAL_CLOSE=$((SELF_CLOSING + CLOSE_TAGS))
50
+
51
+ if [ "$OPEN_TAGS" -eq "$TOTAL_CLOSE" ]; then
52
+ echo -e "${GREEN}✓ Pass${NC} (${OPEN_TAGS} tags)"
53
+ else
54
+ echo -e "${RED}✗ Fail${NC} (${OPEN_TAGS} open, ${TOTAL_CLOSE} close)"
55
+ FAILURES=$((FAILURES + 1))
56
+ fi
57
+
58
+ # Check 2: Quote check
59
+ echo -n "Checking attribute quotes... "
60
+ UNQUOTED=$( { grep -oE '[a-z-]+=[^"'\''> ]' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
61
+ if [ "$UNQUOTED" -eq 0 ]; then
62
+ echo -e "${GREEN}✓ Pass${NC}"
63
+ else
64
+ echo -e "${RED}✗ Fail${NC} (${UNQUOTED} unquoted attributes)"
65
+ grep -n -oE '[a-z-]+=[^"'\''> ]' "$SVG_FILE" | head -5 || true
66
+ FAILURES=$((FAILURES + 1))
67
+ fi
68
+
69
+ # Check 3: Unescaped entities in text
70
+ echo -n "Checking text entities... "
71
+ SPECIAL=$(python3 - "$SVG_FILE" <<'PY'
72
+ from pathlib import Path
73
+ import re
74
+ import sys
75
+
76
+ text = Path(sys.argv[1]).read_text(encoding='utf-8')
77
+ issues = 0
78
+ for chunk in re.findall(r'>([^<]*)<', text, flags=re.S):
79
+ cleaned = re.sub(r'&(amp|lt|gt|quot|apos);', '', chunk)
80
+ if '&' in cleaned:
81
+ issues += 1
82
+ print(issues)
83
+ PY
84
+ )
85
+ if [ "$SPECIAL" -eq 0 ]; then
86
+ echo -e "${GREEN}✓ Pass${NC}"
87
+ else
88
+ echo -e "${YELLOW}⚠ Warning${NC} (${SPECIAL} potential unescaped entities)"
89
+ fi
90
+
91
+ # Check 4: Marker references
92
+ echo -n "Checking marker references... "
93
+ MARKER_REFS=$( { grep -oE 'marker-end="url\(#[^)]+\)"' "$SVG_FILE" || true; } | { grep -oE '#[^)]+' || true; } | tr -d '#' | sort -u )
94
+ MARKER_DEFS=$( { grep -oE '<marker id="[^"]+"' "$SVG_FILE" || true; } | { grep -oE 'id="[^"]+"' || true; } | tr -d 'id="' | sort -u )
95
+
96
+ MISSING=0
97
+ for ref in $MARKER_REFS; do
98
+ if ! echo "$MARKER_DEFS" | grep -q "^${ref}$"; then
99
+ echo -e "${RED}✗ Missing marker: $ref${NC}"
100
+ MISSING=$((MISSING + 1))
101
+ fi
102
+ done
103
+
104
+ if [ "$MISSING" -eq 0 ]; then
105
+ echo -e "${GREEN}✓ Pass${NC}"
106
+ else
107
+ echo -e "${RED}✗ Fail${NC} (${MISSING} missing markers)"
108
+ FAILURES=$((FAILURES + 1))
109
+ fi
110
+
111
+ # Check 5: Arrow-component collision
112
+ echo -n "Checking arrow collisions... "
113
+ COLLISIONS=$(python3 - "$SVG_FILE" <<'PY'
114
+ from pathlib import Path
115
+ import re
116
+ import sys
117
+ import xml.etree.ElementTree as ET
118
+
119
+ SVG_NS = {'svg': 'http://www.w3.org/2000/svg'}
120
+
121
+ def strip(tag):
122
+ return tag.split('}', 1)[-1]
123
+
124
+ def to_float(value, default=0.0):
125
+ try:
126
+ return float(value)
127
+ except (TypeError, ValueError):
128
+ return default
129
+
130
+ def is_container_rect(el):
131
+ if el.get('stroke-dasharray'):
132
+ return True
133
+ width = to_float(el.get('width'))
134
+ height = to_float(el.get('height'))
135
+ if width > 700 or height > 500:
136
+ return True
137
+ if width < 70 or height < 30:
138
+ return True
139
+ return False
140
+
141
+ def shape_bounds(el):
142
+ tag = strip(el.tag)
143
+ if tag == 'rect':
144
+ if is_container_rect(el):
145
+ return None
146
+ x = to_float(el.get('x'))
147
+ y = to_float(el.get('y'))
148
+ w = to_float(el.get('width'))
149
+ h = to_float(el.get('height'))
150
+ return (x, y, x + w, y + h)
151
+ if tag == 'circle':
152
+ r = to_float(el.get('r'))
153
+ if r < 20:
154
+ return None
155
+ cx = to_float(el.get('cx'))
156
+ cy = to_float(el.get('cy'))
157
+ return (cx - r, cy - r, cx + r, cy + r)
158
+ if tag == 'ellipse':
159
+ rx = to_float(el.get('rx'))
160
+ ry = to_float(el.get('ry'))
161
+ if rx < 20 or ry < 20:
162
+ return None
163
+ cx = to_float(el.get('cx'))
164
+ cy = to_float(el.get('cy'))
165
+ return (cx - rx, cy - ry, cx + rx, cy + ry)
166
+ return None
167
+
168
+ def parse_path_points(d):
169
+ tokens = re.findall(r'[ML]|-?\d+(?:\.\d+)?', d or '')
170
+ if not tokens:
171
+ return []
172
+ points = []
173
+ command = None
174
+ index = 0
175
+ while index < len(tokens):
176
+ token = tokens[index]
177
+ if token in {'M', 'L'}:
178
+ command = token
179
+ index += 1
180
+ continue
181
+ if command not in {'M', 'L'} or index + 1 >= len(tokens):
182
+ return []
183
+ x = float(tokens[index])
184
+ y = float(tokens[index + 1])
185
+ points.append((x, y))
186
+ index += 2
187
+ return points
188
+
189
+ def segment_hits_bounds(p1, p2, bounds):
190
+ x1, y1 = p1
191
+ x2, y2 = p2
192
+ left, top, right, bottom = bounds
193
+ eps = 1e-6
194
+
195
+ if abs(y1 - y2) < eps:
196
+ y = y1
197
+ if not (top + eps < y < bottom - eps):
198
+ return False
199
+ seg_left = min(x1, x2)
200
+ seg_right = max(x1, x2)
201
+ overlap_left = max(seg_left, left)
202
+ overlap_right = min(seg_right, right)
203
+ if overlap_right - overlap_left <= eps:
204
+ return False
205
+ if abs(overlap_left - x1) < eps or abs(overlap_right - x2) < eps:
206
+ return False
207
+ if abs(overlap_left - x2) < eps or abs(overlap_right - x1) < eps:
208
+ return False
209
+ return True
210
+
211
+ if abs(x1 - x2) < eps:
212
+ x = x1
213
+ if not (left + eps < x < right - eps):
214
+ return False
215
+ seg_top = min(y1, y2)
216
+ seg_bottom = max(y1, y2)
217
+ overlap_top = max(seg_top, top)
218
+ overlap_bottom = min(seg_bottom, bottom)
219
+ if overlap_bottom - overlap_top <= eps:
220
+ return False
221
+ if abs(overlap_top - y1) < eps or abs(overlap_bottom - y2) < eps:
222
+ return False
223
+ if abs(overlap_top - y2) < eps or abs(overlap_bottom - y1) < eps:
224
+ return False
225
+ return True
226
+
227
+ return False
228
+
229
+ root = ET.fromstring(Path(sys.argv[1]).read_text(encoding='utf-8'))
230
+ obstacles = [bounds for element in root.iter() if (bounds := shape_bounds(element)) is not None]
231
+
232
+ collisions = 0
233
+ for element in root.iter():
234
+ tag = strip(element.tag)
235
+ if tag == 'line' and element.get('marker-end'):
236
+ points = [
237
+ (to_float(element.get('x1')), to_float(element.get('y1'))),
238
+ (to_float(element.get('x2')), to_float(element.get('y2'))),
239
+ ]
240
+ elif tag == 'path' and element.get('marker-end'):
241
+ points = parse_path_points(element.get('d'))
242
+ else:
243
+ continue
244
+
245
+ for p1, p2 in zip(points, points[1:]):
246
+ if any(segment_hits_bounds(p1, p2, bounds) for bounds in obstacles):
247
+ collisions += 1
248
+ break
249
+
250
+ print(collisions)
251
+ PY
252
+ )
253
+ if [ "$COLLISIONS" -eq 0 ]; then
254
+ echo -e "${GREEN}✓ Pass${NC}"
255
+ else
256
+ echo -e "${RED}✗ Fail${NC} (${COLLISIONS} arrow path collision(s))"
257
+ FAILURES=$((FAILURES + 1))
258
+ fi
259
+
260
+ # Check 6: Closing </svg> tag
261
+ echo -n "Checking closing tag... "
262
+ if grep -q '</svg>' "$SVG_FILE"; then
263
+ echo -e "${GREEN}✓ Pass${NC}"
264
+ else
265
+ echo -e "${RED}✗ Fail${NC} (missing </svg>)"
266
+ FAILURES=$((FAILURES + 1))
267
+ fi
268
+
269
+ # Check 7: render validation (cairosvg preferred, rsvg-convert fallback)
270
+ echo -n "Running render validation... "
271
+ RENDER_OK=false
272
+ RENDER_TOOL=""
273
+ RENDER_ERR=""
274
+
275
+ if python3 -c "import cairosvg" 2>/dev/null; then
276
+ RENDER_TOOL="cairosvg"
277
+ if RENDER_ERR=$(python3 -c "import cairosvg; cairosvg.svg2png(url='${SVG_FILE}', write_to='/tmp/test-output.png')" 2>&1); then
278
+ RENDER_OK=true
279
+ fi
280
+ elif command -v rsvg-convert &> /dev/null; then
281
+ RENDER_TOOL="rsvg-convert"
282
+ if RENDER_ERR=$(rsvg-convert "$SVG_FILE" -o /tmp/test-output.png 2>&1); then
283
+ RENDER_OK=true
284
+ fi
285
+ fi
286
+
287
+ if [ "$RENDER_OK" = true ]; then
288
+ echo -e "${GREEN}✓ Pass${NC} (via ${RENDER_TOOL})"
289
+ rm -f /tmp/test-output.png
290
+ elif [ -n "$RENDER_TOOL" ]; then
291
+ echo -e "${RED}✗ Fail${NC} (via ${RENDER_TOOL})"
292
+ echo "${RENDER_TOOL} error:"
293
+ echo "$RENDER_ERR"
294
+ FAILURES=$((FAILURES + 1))
295
+ else
296
+ echo -e "${YELLOW}⚠ Skipped${NC} (no renderer found — install cairosvg: pip install cairosvg)"
297
+ fi
298
+
299
+ echo "----------------------------------------"
300
+ if [ "$FAILURES" -eq 0 ]; then
301
+ echo "Validation complete"
302
+ exit 0
303
+ fi
304
+
305
+ echo -e "${RED}Validation failed (${FAILURES} error(s))${NC}"
306
+ exit 1
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ <marker id="arrowPurple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
7
+ <polygon points="0 0, 10 3.5, 0 7" fill="#7c3aed"/>
8
+ </marker>
9
+ <marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
10
+ <polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
11
+ </marker>
12
+ <marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
13
+ <polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
14
+ </marker>
15
+ </defs>
16
+ <style>
17
+ text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
18
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
19
+ .node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
20
+ .sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
21
+ .arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
22
+ </style>
23
+ <rect width="960" height="700" fill="#ffffff"/>
24
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
25
+ <!-- NODES -->
26
+ <!-- ARROWS -->
27
+ <!-- LEGEND -->
28
+ </svg>
@@ -0,0 +1,23 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ <marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
7
+ <polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
8
+ </marker>
9
+ </defs>
10
+ <style>
11
+ text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
12
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
13
+ .layer-label { font-size: 16px; font-weight: 600; fill: #475569; }
14
+ .node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
15
+ .sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
16
+ .arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
17
+ </style>
18
+ <rect width="960" height="700" fill="#ffffff"/>
19
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
20
+ <!-- NODES -->
21
+ <!-- ARROWS -->
22
+ <!-- LEGEND -->
23
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <style>
3
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
4
+ .title { font-size: 24px; font-weifill: #1e293b; }
5
+ .header { font-size: 14px; font-weight: 600; fill: #1e293b; }
6
+ .row-label { font-size: 13px; font-weight: 500; fill: #475569; }
7
+ .cell-text { font-size: 13px; font-weight: 400; fill: #1e293b; }
8
+ </style>
9
+ <rect width="960" height="700" fill="#ffffff"/>
10
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
11
+ <!-- HEADERS -->
12
+ <!-- ROWS -->
13
+ <!-- CELLS -->
14
+ </svg>
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ <marker id="arrowPurple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
7
+ <polygon points="0 0, 10 3.5, 0 7" fill="#7c3aed"/>
8
+ </marker>
9
+ <marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
10
+ <polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
11
+ </marker>
12
+ <marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
13
+ <polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
14
+ </marker>
15
+ </defs>
16
+ <style>
17
+ text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
18
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
19
+ .node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
20
+ .sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
21
+ .arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
22
+ </style>
23
+ <rect width="960" height="700" fill="#ffffff"/>
24
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
25
+ <!-- NODES -->
26
+ <!-- ARROWS -->
27
+ <!-- LEGEND -->
28
+ </svg>
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .entity-name { font-size: 15px; font-weight: 700; fill: #1e293b; }
11
+ .attribute { font-size: 12px; font-weight: 400; fill: #475569; }
12
+ .primary-key { font-size: 12px; font-weight: 600; fill: #1e293b; text-decoration: underline; }
13
+ .relationship { font-size: 12px; font-weight: 500; fill: #64748b; }
14
+ .cardinality { font-size: 11px; font-weight: 400; fill: #64748b; }
15
+ </style>
16
+ <rect width="960" height="700" fill="#ffffff"/>
17
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
18
+ <!-- ENTITIES -->
19
+ <!-- RELATIONSHIPS -->
20
+ <!-- CARDINALITY -->
21
+ </svg>
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .node-label { font-size: 14px; font-weight: 500; fill: #1e293b; }
11
+ .decision-label { font-size: 13px; font-weight: 500; fill: #1e293b; }
12
+ .arrow-label { font-size: 11px; font-weight: 400; fill: #475569; }
13
+ </style>
14
+ <rect width="960" height="700" fill="#ffffff"/>
15
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
16
+ <!-- START -->
17
+ <!-- PROCESSES -->
18
+ <!-- DECISIONS -->
19
+ <!-- ARROWS -->
20
+ <!-- END -->
21
+ </svg>
@@ -0,0 +1,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 800">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .participant { font-size: 14px; font-weight: 600; fill: #1e293b; }
11
+ .message { font-size: 12px; font-weight: 400; fill: #475569; }
12
+ .lifeline { stroke: #cbd5e1; stroke-width: 1.5; stroke-dasharray: 5,3; }
13
+ </style>
14
+ <rect width="960" height="800" fill="#ffffff"/>
15
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
16
+ <!-- PARTICIPANTS -->
17
+ <!-- LIFELINES -->
18
+ <!-- MESSAGES -->
19
+ <!-- ACTIVATIONS -->
20
+ </svg>
@@ -0,0 +1,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .state-name { font-size: 14px; font-weight: 600; fill: #1e293b; }
11
+ .state-activity { font-size: 11px; font-weight: 400; fill: #64748b; }
12
+ . { font-size: 11px; font-weight: 400; fill: #475569; }
13
+ </style>
14
+ <rect width="960" height="700" fill="#ffffff"/>
15
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
16
+ <!-- INITIAL_STATE -->
17
+ <!-- STATES -->
18
+ <!-- TRANSITIONS -->
19
+ <!-- FINAL_STATE -->
20
+ </svg>
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 400">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .period-label { font-size: 12px; font-weight: 500; fill: #475569; }
11
+ .task-label { font-size: 13px; font-weight: 500; fill: #1e293b; }
12
+ .milestone-label { font-size: 12px; font-weight: 600; fill: #1e293b; }
13
+ </style>
14
+ <rect width="1200" height="400" fill="#ffffff"/>
15
+ <text x="600" y="50" text-anchor="middle" class="title">{{TITLE}}</text>
16
+ <!-- TIME_AXIS -->
17
+ <!-- TASKS -->
18
+ <!-- MILESTONES -->
19
+ </svg>
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
2
+ <defs>
3
+ <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
4
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
9
+ .title { font-size: 24px; font-weight: 700; fill: #1e293b; }
10
+ .actor-label { font-size: 13px; font-weight: 600; fill: #1e293b; }
11
+ .usecaseel { font-size: 13px; font-weight: 500; fill: #1e293b; }
12
+ .system-label { font-size: 16px; font-weight: 600; fill: #475569; }
13
+ .relationship { font-size: 11px; font-weight: 400; fill: #64748b; font-style: italic; }
14
+ </style>
15
+ <rect width="960" height="700" fill="#ffffff"/>
16
+ <text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
17
+ <!-- SYSTEM_BOUNDARY -->
18
+ <!-- ACTORS -->
19
+ <!-- USECASES -->
20
+ <!-- RELATIONSHIPS -->
21
+ </svg>