@haposoft/cafekit 0.7.23 → 0.7.24
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.
- package/README.md +81 -862
- package/bin/install.js +4 -3
- package/package.json +2 -3
- package/src/claude/agents/code-auditor.md +25 -1
- package/src/claude/agents/spec-maker.md +17 -2
- package/src/claude/agents/test-runner.md +22 -3
- package/src/claude/hooks/spec-state.cjs +4 -4
- package/src/claude/migration-manifest.json +1 -1
- package/src/claude/rules/state-sync.md +7 -5
- package/src/claude/skills/code-review/references/spec-compliance-review.md +8 -1
- package/src/claude/skills/develop/SKILL.md +25 -4
- package/src/claude/skills/develop/references/quality-gate.md +23 -13
- package/src/claude/skills/generate-graph/LICENSE +21 -0
- package/src/claude/skills/generate-graph/README.md +523 -0
- package/src/claude/skills/generate-graph/SKILL.md +427 -0
- package/src/claude/skills/generate-graph/agentloop-core.svg +101 -0
- package/src/claude/skills/generate-graph/agents/openai.yaml +4 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style1-flat.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style2-dark.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style3-blueprint.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style4-notion.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style5-glass.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style6-claude.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style7-openai.png +0 -0
- package/src/claude/skills/generate-graph/fixtures/agent-memory-types-style4.json +181 -0
- package/src/claude/skills/generate-graph/fixtures/api-flow-style7.json +40 -0
- package/src/claude/skills/generate-graph/fixtures/mem0-style1.json +297 -0
- package/src/claude/skills/generate-graph/fixtures/microservices-style3.json +64 -0
- package/src/claude/skills/generate-graph/fixtures/multi-agent-style5.json +45 -0
- package/src/claude/skills/generate-graph/fixtures/system-architecture-style6.json +48 -0
- package/src/claude/skills/generate-graph/fixtures/tool-call-style2.json +182 -0
- package/src/claude/skills/generate-graph/package.json +42 -0
- package/src/claude/skills/generate-graph/references/icons.md +281 -0
- package/src/claude/skills/generate-graph/references/style-1-flat-icon.md +108 -0
- package/src/claude/skills/generate-graph/references/style-2-dark-terminal.md +107 -0
- package/src/claude/skills/generate-graph/references/style-3-blueprint.md +113 -0
- package/src/claude/skills/generate-graph/references/style-4-notion-clean.md +94 -0
- package/src/claude/skills/generate-graph/references/style-5-glassmorphism.md +125 -0
- package/src/claude/skills/generate-graph/references/style-6-claude-official.md +209 -0
- package/src/claude/skills/generate-graph/references/style-7-openai.md +215 -0
- package/src/claude/skills/generate-graph/references/style-diagram-matrix.md +135 -0
- package/src/claude/skills/generate-graph/references/svg-layout-best-practices.md +100 -0
- package/src/claude/skills/generate-graph/scripts/generate-diagram.sh +157 -0
- package/src/claude/skills/generate-graph/scripts/generate-from-template.py +1556 -0
- package/src/claude/skills/generate-graph/scripts/test-all-styles.sh +135 -0
- package/src/claude/skills/generate-graph/scripts/validate-svg.sh +292 -0
- package/src/claude/skills/generate-graph/templates/agent-architecture.svg +28 -0
- package/src/claude/skills/generate-graph/templates/architecture.svg +23 -0
- package/src/claude/skills/generate-graph/templates/comparison-matrix.svg +14 -0
- package/src/claude/skills/generate-graph/templates/data-flow.svg +28 -0
- package/src/claude/skills/generate-graph/templates/er-diagram.svg +21 -0
- package/src/claude/skills/generate-graph/templates/flowchart.svg +21 -0
- package/src/claude/skills/generate-graph/templates/sequence.svg +20 -0
- package/src/claude/skills/generate-graph/templates/state-machine.svg +20 -0
- package/src/claude/skills/generate-graph/templates/timeline.svg +19 -0
- package/src/claude/skills/generate-graph/templates/use-case.svg +21 -0
- package/src/claude/skills/specs/SKILL.md +35 -5
- package/src/claude/skills/specs/references/review.md +1 -1
- package/src/claude/skills/specs/rules/tasks-generation.md +17 -0
- package/src/claude/skills/specs/templates/design.md +13 -0
- package/src/claude/skills/specs/templates/init.json +4 -1
- package/src/claude/skills/specs/templates/requirements.md +21 -8
- package/src/claude/skills/specs/templates/task.md +16 -3
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
if command -v rsvg-convert &> /dev/null \
|
|
103
|
+
&& rsvg-convert -w 1920 "$SVG_FILE" -o "$PNG_FILE" 2>/dev/null; then
|
|
104
|
+
PNG_SIZE=$(du -h "$PNG_FILE" | cut -f1)
|
|
105
|
+
echo -e "${GREEN}✓ Pass${NC} (${PNG_SIZE})"
|
|
106
|
+
else
|
|
107
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
108
|
+
fi
|
|
109
|
+
PASSED=$((PASSED + 1))
|
|
110
|
+
else
|
|
111
|
+
echo -e "${RED}✗ Fail${NC}"
|
|
112
|
+
FAILED=$((FAILED + 1))
|
|
113
|
+
if [ -f "$SVG_FILE" ]; then
|
|
114
|
+
"${SKILL_DIR}/scripts/validate-svg.sh" "$SVG_FILE" 2>&1 | grep -E "✗|Error" | sed 's/^/ /' || true
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
done
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
# Print summary
|
|
121
|
+
echo ""
|
|
122
|
+
echo "========================================"
|
|
123
|
+
echo -e "${BLUE}Test Summary${NC}"
|
|
124
|
+
echo "----------------------------------------"
|
|
125
|
+
echo "Total tests: $TOTAL"
|
|
126
|
+
echo -e "${GREEN}Passed: $PASSED${NC}"
|
|
127
|
+
echo -e "${RED}Failed: $FAILED${NC}"
|
|
128
|
+
|
|
129
|
+
if [ "$FAILED" -eq 0 ]; then
|
|
130
|
+
echo -e "\n${GREEN}✓ All tests passed!${NC}"
|
|
131
|
+
exit 0
|
|
132
|
+
else
|
|
133
|
+
echo -e "\n${RED}✗ Some tests failed${NC}"
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
@@ -0,0 +1,292 @@
|
|
|
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: rsvg-convert validation
|
|
270
|
+
echo -n "Running rsvg-convert validation... "
|
|
271
|
+
if command -v rsvg-convert &> /dev/null; then
|
|
272
|
+
if rsvg-convert "$SVG_FILE" -o /tmp/test-output.png 2>/dev/null; then
|
|
273
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
274
|
+
rm -f /tmp/test-output.png
|
|
275
|
+
else
|
|
276
|
+
echo -e "${RED}✗ Fail${NC}"
|
|
277
|
+
echo "rsvg-convert error:"
|
|
278
|
+
rsvg-convert "$SVG_FILE" -o /tmp/test-output.png 2>&1 || true
|
|
279
|
+
FAILURES=$((FAILURES + 1))
|
|
280
|
+
fi
|
|
281
|
+
else
|
|
282
|
+
echo -e "${YELLOW}⚠ Skipped${NC} (rsvg-convert not found)"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
echo "----------------------------------------"
|
|
286
|
+
if [ "$FAILURES" -eq 0 ]; then
|
|
287
|
+
echo "Validation complete"
|
|
288
|
+
exit 0
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
echo -e "${RED}Validation failed (${FAILURES} error(s))${NC}"
|
|
292
|
+
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>
|