Restructure to skill-manager conventions

- Move the TypeScript project under scripts/ (src, tsconfig,
  package.json, pnpm lockfile/workspace, canonical .gitignore);
  drop the npm package-lock
- Add scripts/Taskfile.yml aggregator plus .scripts modules
  (loggers, base, cli) with build, run, and validate tasks
- Move the six SKILL-*.md docs into references/ with kebab names
  and extract the connector-routing sections from SKILL.md into
  references/routing-best-practices.md (SKILL.md 666 -> ~310 lines)
- Add license/metadata/compatibility frontmatter, an Available
  scripts section, and update all CLI paths in README and references

skill-manager validate: 13/13 passed, 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 15:20:26 +03:00
co-authored by Claude Fable 5
parent 99f1fd07e3
commit 77655d1d97
72 changed files with 1089 additions and 1828 deletions
-3
View File
@@ -1,3 +0,0 @@
# NodeJs
dist
node_modules
+7 -5
View File
@@ -18,23 +18,25 @@ Built with TypeScript using [`@maxgraph/core`](https://github.com/maxGraph/maxGr
## Install ## Install
```bash ```bash
cd scripts
pnpm install pnpm install
``` ```
## Build ## Build
```bash ```bash
pnpm run build cd scripts
# compiled output → dist/ task build # or: pnpm run build
# compiled output → scripts/dist/
``` ```
## Usage ## Usage
```bash ```bash
cd scripts
task run -- --file="diagram.drawio" --action=<action> [--page <index>]
# or directly:
node dist/cli/commands.js --file diagram.drawio --action <action> [--page <index>] node dist/cli/commands.js --file diagram.drawio --action <action> [--page <index>]
# or via tsx (no build step)
pnpm dev --file diagram.drawio --action <action>
``` ```
Output is always YAML to stdout. Output is always YAML to stdout.
+47 -393
View File
@@ -1,6 +1,14 @@
--- ---
name: drawio-main name: drawio-main
description: Always use when user asks to create, generate, draw, or design a diagram, flowchart, architecture diagram, ER diagram, sequence diagram, class diagram, network diagram, mockup, wireframe, or UI sketch, or mentions draw.io, drawio, drawoi, .drawio files, or diagram export to PNG/SVG/PDF. description: Always use when user asks to create, generate, draw, or design a diagram, flowchart, architecture diagram, ER diagram, sequence diagram, class diagram, network diagram, mockup, wireframe, or UI sketch, or mentions draw.io, drawio, drawoi, .drawio files, or diagram export to PNG/SVG/PDF.
license: Proprietary
metadata:
author: workspace-swiss-knife
version: "2.0"
spec: agentskills.io/specification
origin-repository: git@github.ibm.com:CTOTools-skills-code-agent/drawio-main.git
origin-path: $HOME/projects-ibm/cognitive-architect/workspace-skills-code-agent/drawio-main
compatibility: Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
--- ---
# Draw.io Diagram Skill # Draw.io Diagram Skill
@@ -10,12 +18,37 @@ This skill covers two capabilities:
1. **Diagram generation** — create `.drawio` files (and optionally export to PNG/SVG/PDF) from a description or requirements 1. **Diagram generation** — create `.drawio` files (and optionally export to PNG/SVG/PDF) from a description or requirements
2. **Diagram analysis** — run the `drawio-tools` CLI to analyse an existing `.drawio` file: inventory shapes and connectors, validate layout quality, detect overlaps/orphans, and recommend page sizes 2. **Diagram analysis** — run the `drawio-tools` CLI to analyse an existing `.drawio` file: inventory shapes and connectors, validate layout quality, detect overlaps/orphans, and recommend page sizes
**Skill files** (read these when using this skill): **Reference files** (read these when using this skill):
- [SKILL-CAPABILITIES.md](./SKILL-CAPABILITIES.md) — full list of capabilities and all CLI analysis actions an agent can execute - [references/capabilities.md](./references/capabilities.md) — full list of capabilities and all CLI analysis actions an agent can execute
- [SKILL-RULES-LAYOUT.md](./SKILL-RULES-LAYOUT.md) — mandatory layer model, connector-crossing avoidance, waypoint patterns, overlap verification, layout checklist - [references/rules-layout.md](./references/rules-layout.md) — mandatory layer model, connector-crossing avoidance, waypoint patterns, overlap verification, layout checklist
- [SKILL-RULES-STYLE.md](./SKILL-RULES-STYLE.md) — mandatory visual appearance and sizing rules (colors, dimensions, connector dash styles, shape sizing) - [references/rules-style.md](./references/rules-style.md) — mandatory visual appearance and sizing rules (colors, dimensions, connector dash styles, shape sizing)
- [SKILL-XML-REFERENCES.md](./SKILL-XML-REFERENCES.md) — complete draw.io XML reference (styles, routing, containers, layers, dark mode, well-formedness) - [references/xml-references.md](./references/xml-references.md) — complete draw.io XML reference (styles, routing, containers, layers, dark mode, well-formedness)
- [SKILL-NEGATIVE-SPACE-DIAGRAM.md](./SKILL-NEGATIVE-SPACE-DIAGRAM.md) — rules for generating negative space companion diagrams from `page-negative-space-summary` output - [references/negative-space-diagram.md](./references/negative-space-diagram.md) — rules for generating negative space companion diagrams from `page-negative-space-summary` output
- [references/routing-best-practices.md](./references/routing-best-practices.md) — corridor planning, routing patterns, overlap verification, swimlane routing, validation workflow
- [references/maintenance.md](./references/maintenance.md) — maintaining and rebuilding the skill itself
## Available scripts
This skill ships the `drawio-tools` TypeScript CLI under `scripts/`. All commands run via `task` from the `scripts/` directory (one-time setup: `pnpm install` + `task build`):
```bash
cd <skill>/scripts
# Run any analysis action against a .drawio file
task run -- --file="/path/to/diagram.drawio" --action=page-connectors-validation
# Actions: summary, page-summary, page-hierarchy, page-connectors-summary,
# page-connectors-validation, page-labels-validation, page-shape-bbox-validation,
# page-orphans, page-recommendations, page-hierarchy-full,
# page-negative-space-summary, validate
# Validate a .drawio file (mandatory final gate)
task validate -- --file="/path/to/diagram.drawio"
# Build TypeScript to dist/
task build
```
See [references/capabilities.md](./references/capabilities.md) for what every action outputs.
--- ---
@@ -277,390 +310,11 @@ Where:
--- ---
## Connector Routing Best Practices (Zero-Overlap Guarantee) ## Connector routing
Complex architecture diagrams with many connectors require deliberate routing. Follow this process to achieve zero connector-shape overlaps. For complex diagrams (many connectors, swimlane zones), read
[references/routing-best-practices.md](./references/routing-best-practices.md) — corridor
### 1. Plan corridors before drawing planning, absolute-coordinate calculation, Python overlap verification, routing patterns
(top highway, stacked columns, narrow corridors, bypasses, swimlane fan-out/fan-in),
Before placing any connectors, identify **clear corridors** — horizontal or vertical bands on the canvas that are free of all shapes. Route every connector through these corridors. waypoint XML format, the port-pin prohibition, and the validation workflow. The mandatory
layer model and layout checklist live in [references/rules-layout.md](./references/rules-layout.md).
#### Vertical corridors (x-axis lanes between columns)
Between each column of shapes, choose a single x-coordinate that lies in the gap:
```
GAP = (right_edge_of_left_column + left_edge_of_right_column) / 2
```
Examples from a typical IBM Cloud deployment diagram:
| Corridor name | x value | Description |
|---------------|---------|-------------|
| GAP_AB | 370 | Between Internet zone and IBM Cloud boundary |
| GAP_BC | 720 | Between IBM edge services and VPC |
| GAP_NS | 1510 | Between namespace columns inside VPC |
| GAP_CD | 2450 | Between VPC right edge and external service group |
| GAP_DE | 2760 | Between two external service columns |
#### Horizontal corridors (y-axis lanes between rows)
Between each row of shapes, choose a y-coordinate in the clear gap:
| Corridor name | y value | Description |
|---------------|---------|-------------|
| Y_TOP | 110 | Above all shapes (highway for cross-diagram connectors) |
| Y_ROKS | 350 | Between ingress row and pod row |
| Y_GW_CORE | 640 | Between gateway namespace and core namespace |
| Y_CICD | 875 | Between ops pods and CI/CD row |
| Y_BETWEEN | 985 | Between CI/CD and database row |
| Y_BELOW | 1200 | Below all shapes |
**Rule:** When two rows are only 20–40 px apart, the corridor between them is still usable — just ensure the chosen y does not touch any shape's bounding box (see tolerance rule below).
### 2. Calculate absolute canvas coordinates
draw.io uses **relative coordinates** for nested containers: a child's `x`/`y` is relative to its parent container's top-left corner, *not* to the canvas. To verify that a connector waypoint (which uses canvas-absolute coordinates) clears a shape, compute:
```
abs_x = shape.x + parent.x + grandparent.x + ... (sum all ancestor x offsets)
abs_y = shape.y + parent.y + grandparent.y + ... (sum all ancestor y offsets)
```
For swimlane containers, child coordinates are relative to the swimlane's **top-left origin** (not to the content area below the header). `startSize` only affects the visual header height — ignore it when computing absolute coordinates.
Build a flat list of all shapes with absolute bounding boxes before routing:
```python
shapes = [
# (id, abs_x1, abs_y1, abs_x2, abs_y2)
("pod-api", 985, 416, 1145, 574),
("pod-svc", 810, 416, 967, 574),
...
]
```
### 3. Verify routes with Python before writing XML
Run an overlap-detection simulation against all shapes before finalising the XML. This prevents invisible bugs that only become apparent when the file is opened.
```python
TOL = 2 # pixels — allow connectors to touch but not overlap interiors
def segment_overlaps_shape(seg, shape):
sx1, sy1, sx2, sy2 = shape
if seg['type'] == 'H': # horizontal segment at y=Y from xA to xB
Y, xA, xB = seg['y'], min(seg['x1'], seg['x2']), max(seg['x1'], seg['x2'])
if sy1 + TOL < Y < sy2 - TOL:
if max(xA, sx1 + TOL) < min(xB, sx2 - TOL):
return True
elif seg['type'] == 'V': # vertical segment at x=X from yA to yB
X, yA, yB = seg['x'], min(seg['y1'], seg['y2']), max(seg['y1'], seg['y2'])
if sx1 + TOL < X < sx2 - TOL:
if max(yA, sy1 + TOL) < min(yB, sy2 - TOL):
return True
return False
def route_overlaps(segments, shapes):
for seg in segments:
for shape in shapes:
if segment_overlaps_shape(seg, shape[1:]): # skip id
return shape[0], seg
return None
```
Convert every connector's waypoint list into a series of H/V segments, then call `route_overlaps` for each connector. Fix any reported overlap before writing the XML.
**Endpoint-touch rule:** A connector that terminates *at* a target shape will appear to overlap that shape in the algorithm because the last segment enters the shape's bounding box. This is expected and correct — exclude the *terminal shape* (source or target) when checking a connector's first and last segments.
### 4. Common routing patterns
#### Pattern A — Top highway for long cross-diagram connectors
Route connectors that must span the full diagram width through `Y_TOP` (well above all shapes):
```
source_mid_y → vertical up to Y_TOP → horizontal across to target_col_x → vertical down to target_mid_y
```
Waypoints example (mxGraphModel format):
```xml
<Array as="points">
<mxPoint x="200" y="110"/>
<mxPoint x="2600" y="110"/>
</Array>
```
#### Pattern B — Stacked services (approach from the left)
When multiple services are stacked vertically in a column (e.g., Watson/AI services), never route a vertical connector *through* the column. Instead, approach each service individually from a horizontal corridor to its left:
```
source → vertical to Y_TOP → horizontal to GAP_CD (just left of the column) → horizontal at service_mid_y → connect to service left edge
```
Each service in the stack gets its own connector that stops at the gap corridor. draw.io completes the final short horizontal stub automatically.
```python
for svc_id, svc_abs_y in stacked_services:
svc_mid_y = svc_abs_y + svc_height / 2
waypoints = [
(source_mid_x, Y_TOP), # exit source upward
(GAP_CD, Y_TOP), # travel across at top highway
(GAP_CD, svc_mid_y), # descend to service's row
# last waypoint stops at corridor; draw.io connects to service edge
]
```
#### Pattern C — Narrow inter-pod corridors
When adjacent pods in a row leave only a small gap (e.g., pod_A right=967, pod_B left=985 → 18 px gap), that gap is still a usable vertical corridor:
```
corridor_x = (pod_A_right + pod_B_left) / 2 → e.g., 976
```
Use this narrow corridor to route a vertical segment that exits a pod row and connects to shapes above or below:
```
pod_interior → horizontal to corridor_x → vertical through gap to target_y → horizontal to target
```
#### Pattern D — Right bypass for connections below a dense pod grid
When many pods span horizontally across the diagram, route connectors that must reach shapes below by going around the right edge of the pod grid:
```
source_mid → horizontal right to RIGHT_BYPASS (just right of all pods) → vertical to target_y → horizontal left to target
```
Set `RIGHT_BYPASS` to `(rightmost_pod_right + left_edge_of_next_column) / 2`.
### 5. Waypoint XML format
Always use the `Array as="points"` form inside `mxGeometry`:
```xml
<mxCell id="conn-1" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;" edge="1" source="A" target="B" parent="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="450" y="110"/>
<mxPoint x="2450" y="110"/>
<mxPoint x="2450" y="520"/>
</Array>
</mxGeometry>
</mxCell>
```
Rules:
- All waypoint coordinates are **canvas-absolute** (not relative to any container).
- Edges/connectors must be children of the **root layer** (`parent="1"`), never children of a container shape.
- The first waypoint is where draw.io exits the source shape's auto-routing; the last waypoint is where it enters the target.
- Stop the last waypoint at the corridor boundary — do not pass a waypoint into the interior of the target shape. draw.io will complete the stub automatically.
### CRITICAL — Never use port pin constraints on edges
**DO NOT** add `exitX`, `exitY`, `exitDx`, `exitDy`, `entryX`, `entryY`, `entryDx`, `entryDy` attributes to edge `mxCell` elements.
| Indicator | Cause | Meaning |
|-----------|-------|---------|
| 🔵 Blue circle at connector endpoint | `source`/`target` IDs set, no port pins | **Correct** — properly connected to shape |
| 🟢 Green circle with white X at connector endpoint | Port pin constraints (`exitX/Y`, `entryX/Y`) present | **Wrong** — draw.io treats endpoint as floating/unlinked |
Port pin attributes force draw.io to attach the connector to a precise computed point on the shape boundary. When the waypoints don't exactly match that computed point, draw.io renders the endpoint as floating (green X). This breaks visual connectivity even though the `source`/`target` IDs are set.
**Correct pattern — source/target IDs + waypoints only, NO port pins:**
```xml
<!-- ✅ CORRECT — blue circle, properly connected -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;"
edge="1" source="shape-A" target="shape-B" parent="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="200" y="420"/>
<mxPoint x="200" y="200"/>
</Array>
</mxGeometry>
</mxCell>
<!-- ❌ WRONG — green X, floating endpoint -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;
exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;"
edge="1" source="shape-A" target="shape-B" parent="1">
...
</mxCell>
```
draw.io auto-computes the connection point from `source`/`target` shape IDs and the last waypoint direction. Waypoints guide the routing corridor; the actual attachment to the shape is handled automatically.
**For «include» / «extend» labels:** use real UTF-8 characters `«include»` / `«extend»` in the edge `value` attribute — not XML entities (`&#171;include&#187;`).
**Single-port rule:** When multiple edges enter the same target shape, all their last waypoints must approach from the **same axis direction** (all from left, all from right, all from below, etc.). Mixed directions cause `singlePortViolation` in `page-connectors-validation`.
### 6. Corridor-first layout checklist
Before placing shapes, plan the layout so corridors are naturally available:
- [ ] Leave ≥ 40 px horizontal gaps between columns of shapes
- [ ] Leave ≥ 30 px vertical gaps between rows of shapes
- [ ] Reserve at least one wide horizontal corridor above all shapes (`Y_TOP`)
- [ ] Reserve at least one wide horizontal corridor below all shapes (`Y_BELOW`)
- [ ] Do not place shapes in the corridor bands — treat corridors as sacred routing lanes
- [ ] For stacked service columns, leave a vertical corridor to their left at `GAP_CD`
- [ ] Run Python overlap verification before finalising; fix every reported overlap
- [ ] Apply endpoint-touch exclusion when the last segment enters the target shape
---
## Swimlane / Multi-Zone Diagram Routing
When a diagram uses **swimlane zones** (horizontal bands, each a `swimlane` container), apply the following rules in addition to the general patterns above.
### Zone layout reference
For a typical layered architecture with `startSize=40` swimlanes and 40pt spacing:
```
zone.y ← swimlane top edge
zone.y + 40 ← header bottom (label occupies y..y+40)
zone.y + 40 + 40 = zone.y+80 ← first child row top (rel y=80 inside container)
zone.y + 80 + child_h ← first child row bottom
...
zone.y + height ← swimlane bottom edge
```
Inter-zone gaps (between adjacent swimlanes) are clean horizontal corridors — use their midpoint as the H-travel y-coordinate for connectors crossing zone boundaries.
### Bypass corridors
Always reserve **two vertical bypass corridors** outside all zones:
| Corridor | x value | Rule |
|---|---|---|
| LEFT bypass | `zone.x - 20` (e.g. x=60 when zones start at x=80) | All leftward cross-zone connectors |
| RIGHT bypass | `zone.x + zone.width + 20` (e.g. x=1260 when zones end at x=1240) | All rightward cross-zone connectors |
These bypass corridors run the full canvas height and are free of all shapes. **Every connector that must travel between zones should route through one of these corridors.**
### Multi-row swimlane: horizontal segment placement
When a swimlane has **two rows of shapes** (row1 and row2), each row has a y-range. Never place a connector H segment at a y-value that falls inside a row's y-range — that will overlap sibling shapes.
Use only these safe H corridors inside a multi-row services swimlane:
| Corridor | y value | When to use |
|---|---|---|
| Inter-row gap | `row1_bottom + (row2_top - row1_bottom)/2` | H travel between row1 and row2 shapes |
| Services-bottom gap | `row2_bottom + (zone_bottom - row2_bottom)/2` | H travel below row2, still inside zone |
| Inter-zone gaps | midpoint of gap between adjacent zones | H travel outside zones |
**Example** — services-zone: startSize=40, zone.y=500, row1 abs y=580..620, row2 abs y=660..700, zone bottom=740:
- Inter-row gap corridor: **y=640** (midpoint of y=620..660)
- Services-bottom corridor: **y=720** (midpoint of y=700..740)
### Connector routing rules for multi-row swimlanes
**Rule 1 — Never route H segments through a row's y-range.**
If a connector must travel horizontally through the services zone, use y=640 (inter-row) or y=720 (services-bottom), never y=580..620 or y=660..700.
**Rule 2 — When exiting a shape in row2 that has sibling shapes to its right in the same row:**
Do NOT exit from the bottom of the shape and then travel H at y=720 (services-bottom) to the right — this H will cross the V stubs of right-side siblings that exit to the same corridor.
**Fix options:**
- a) Exit from the **right side** of the shape → immediately go to RIGHT bypass x=1260 → V down/up to target (no H inside zone)
- b) If right-side exit H would cross a sibling shape body: exit right → first waypoint in the **column gap** to the right (e.g. x=1060 for col5/col6 gap) → V down to services-bottom corridor y=720 → H right to RIGHT bypass → V to target
**Rule 3 — Left bypass for connectors going downward to lower zones.**
When a row2 shape must connect to a shape in a lower zone (messaging, data), route:
```
shape_bottom → inter-row corridor y=640 → H LEFT to x=LEFT_BYPASS → V down to target zone corridor → H right to target
```
This ensures the H segment travels in the clear inter-row gap corridor and never crosses sibling shapes.
**Rule 4 — connectorShapeOverlaps with zone containers are structural and unavoidable.**
Any connector crossing a swimlane zone boundary will be flagged as `connector_shape_overlap` with the zone container. This is expected and acceptable — focus on eliminating `connectorCrossings` (two connectors intersecting each other) and overlaps with **non-container leaf shapes**.
### Pattern F — Multi-row swimlane fan-out (api-gateway → many services)
When a shape in an upper zone connects to N shapes spread across multiple columns of a multi-row services swimlane:
```
api-gateway_bottom → H to LEFT_BYPASS at inter-zone gap y → V down LEFT_BYPASS →
branch per target:
- col1 (leftmost): H right from LEFT_BYPASS to target_center_x at inter-row gap y
- col2: same, longer H
- col3..colN: same pattern, extending H further right
- for row2 targets: V from inter-row corridor down to row2 top, enter from top
```
All H branches travel at the same y (inter-row gap) — they are **parallel**, not crossing.
### Pattern G — Event bus → services (right bypass fan-in)
When an event bus (bottom of diagram) connects back up to multiple services:
```
event-bus_right → H right to RIGHT_BYPASS → V up →
branch per target:
- service in row1: H left from RIGHT_BYPASS at row1_center_y to target
- service in row2: stop at services-bottom corridor y=720 → H left → V up 20pt to target bottom
- stagger y values slightly (e.g. y=720 for one, y=730 for another) to avoid parallel confusion
```
**Critical:** The H segments going LEFT from RIGHT_BYPASS at y=720/730 will cross V stubs of row2 services that exit their bottoms and route DOWN and RIGHT to the right bypass. To avoid these crossings:
- Route those downward connectors via the **LEFT bypass** instead (Pattern F inverse)
- OR ensure the rightward services exit via their **right side** (not bottom), so no V stub exists at y=720..730
### Validation workflow for swimlane diagrams
1. Run `page-connectors-validation` after every edit
2. Fix `connectorCrossings` first — these are always avoidable
3. Fix `connectorShapeOverlaps` with **leaf shapes** (non-containers) — these are overlaps with actual service boxes
4. Accept `connectorShapeOverlaps` with zone containers as structural (unavoidable)
5. Fix `cornerPortViolations`, `headerEdgeViolations`, `singlePortViolations` — all should reach 0
6. Target: `connectorCrossings=0`, `cornerPortViolations=0`, `headerEdgeViolations=0`, `singlePortViolations=0`
---
## Section 8 — Text-width estimation for negative space calculation
When computing negative space manually (e.g. generating a negative-space diagram), **shape bounding boxes alone overestimate occupied space**. Text labels only occupy a sub-region within the shape bbox. Use the following formula:
```
charWidth = fontSize × 0.6 // avg glyph width for proportional fonts
rawWidth = longestLineCharCount × charWidth
padding = fontSize × 1.0 // horizontal padding (~0.5em each side)
textWidth = rawWidth + padding
fontStyle modifiers (draw.io bitmask):
bit 0 (value 1) = bold → charWidth × 1.10
bit 1 (value 2) = italic → charWidth × 1.05
textXMin = clamp(shapeCenterX − textWidth/2, shape.xMin, shape.xMax)
textXMax = clamp(shapeCenterX + textWidth/2, shape.xMin, shape.xMax)
textFlankLeft = textXMin − shape.xMin // free space left of text inside bbox
textFlankRight = shape.xMax − textXMax // free space right of text inside bbox
```
**When to apply:**
- **Swimlane headers** (`startSize=40` band): header text is centered in the full zone width → use text region for occupied X, flanks are free negative space
- Large container shapes whose children don't fill the full width
- Any shape where `textWidth < shapeWidth` — the flanks inside the bbox are free
**Example — Marketplace diagram zone headers, fontSize=12 bold (fontStyle=1), center x=660, zone x=80..1240:**
| Label | longestLine chars | textWidth | textXMin | textXMax | flank each side |
|---|---|---|---|---|---|
| "Client Layer" | 12 | 107 | 607 | 713 | ~527pt |
| "API Gateway / Edge Layer" | 24 | 202 | 559 | 761 | ~479pt |
| "Core Microservices" | 18 | 155 | 583 | 737 | ~503pt |
| "Messaging / Event Bus" | 21 | 178 | 571 | 749 | ~491pt |
| "Data Layer" | 10 | 91 | 615 | 705 | ~535pt |
**`page-negative-space-summary` action outputs (per shape):**
- `textXMin`, `textXMax`, `textWidth` — estimated text rendering region
- `textFlankLeft`, `textFlankRight` — free flanks inside bbox
- `rows[].textAwareFreeCorridors` — corridors computed using text regions (wider than bbox-based)
**When drawing a negative-space diagram:** see [SKILL-NEGATIVE-SPACE-DIAGRAM.md](./SKILL-NEGATIVE-SPACE-DIAGRAM.md) for all rules, construction steps, XML pattern, file naming, and validation.
-1401
View File
File diff suppressed because it is too large Load Diff
@@ -9,18 +9,19 @@ This file lists all capabilities an agent can use from this skill.
Create `.drawio` files (and optionally export to PNG/SVG/PDF) from a description or requirements. Create `.drawio` files (and optionally export to PNG/SVG/PDF) from a description or requirements.
See [SKILL.md](./SKILL.md) for the generation workflow, XML format, page sizes, element shapes, and well-formedness rules. See [SKILL.md](./SKILL.md) for the generation workflow, XML format, page sizes, element shapes, and well-formedness rules.
See [SKILL-RULES-LAYOUT.md](./SKILL-RULES-LAYOUT.md) for mandatory connector and layout rules. See [rules-layout.md](./rules-layout.md) for mandatory connector and layout rules.
--- ---
## Capability 2 — Diagram analysis (drawio-tools CLI) ## Capability 2 — Diagram analysis (drawio-tools CLI)
A **TypeScript / Node.js** CLI tool for programmatic analysis of `.drawio` files. A **TypeScript / Node.js** CLI tool for programmatic analysis of `.drawio` files.
Entry point: `node dist/cli/commands.js` (run from `.skills/drawio-main/`). Entry point: `node dist/cli/commands.js` (run from the skill's `scripts/` directory), or `task run -- --file=… --action=…`.
### Usage ### Usage
```bash ```bash
cd <skill>/scripts
node dist/cli/commands.js --file <path-to-file.drawio> --action <action> [--page <index>] node dist/cli/commands.js --file <path-to-file.drawio> --action <action> [--page <index>]
# short flags: -f, -a, -p, -h # short flags: -f, -a, -p, -h
``` ```
@@ -56,4 +57,4 @@ Always prints YAML to stdout. Exit code `0` on success, `1` on error.
|---|---|---| |---|---|---|
| `page-recommendations` | Page 0 | Smallest standard page size (A4→A3→A2→A1→custom) that fits content with 80 px margin | | `page-recommendations` | Page 0 | Smallest standard page size (A4→A3→A2→A1→custom) that fits content with 80 px margin |
> For install/build instructions, source structure, and how to add new actions, see [SKILL-MAINTENANCE.md](./SKILL-MAINTENANCE.md). > For install/build instructions, source structure, and how to add new actions, see [maintenance.md](./maintenance.md).
@@ -1,7 +1,10 @@
# drawio-main — Maintenance Guide # drawio-main — Maintenance Guide
This document is for **developers** maintaining or extending the `drawio-tools` CLI. This document is for **developers** maintaining or extending the `drawio-tools` CLI.
For agent usage instructions, see [SKILL.md](./SKILL.md). For agent usage instructions, see [SKILL.md](../SKILL.md).
The CLI lives under the skill's `scripts/` directory (`scripts/src/` → `scripts/dist/`),
per the skill-manager structure conventions. All commands below run from `scripts/`.
--- ---
@@ -27,45 +30,45 @@ allowBuilds:
--- ---
## Install to `.agents/skills` ## Install and deploy
Always remove the previous installation first, then do a fresh copy and install: The git repository is the source of truth. Install dependencies and build inside `scripts/`:
```bash ```bash
# 1. Remove previous installation cd <skill>/scripts
rm -rf .agents/skills/drawio-main pnpm install
task build # or: pnpm run build
```
# 2. Copy the full skill directory (source of truth) Deploy with skill-manager (creates absolute-path symlinks in `$HOME/.agents/skills`
cp -r CogArch-drawio-2-cogarch/.skills/drawio-main .agents/skills/drawio-main and `$HOME/.claude/skills`, so the repo working copy stays the single source of truth):
# 3. Install dependencies in the new location ```bash
cd .agents/skills/drawio-main && pnpm install cd <skill-manager>/scripts
task deploy -- --skill-dir="/absolute/path/to/drawio-main"
``` ```
Verify the CLI works after installation: Verify the CLI works after installation:
```bash ```bash
node .agents/skills/drawio-main/dist/cli/commands.js -f <diagram.drawio> -a summary cd <skill>/scripts
node dist/cli/commands.js -f <diagram.drawio> -a summary
``` ```
--- ---
## Run the CLI ## Run the CLI
Single entry point — always builds first, then runs:
```bash ```bash
cd CogArch-drawio-2-cogarch/.skills/drawio-main cd <skill>/scripts
task run -- --file="<diagram.drawio>" --action=<action>
# or directly:
node dist/cli/commands.js -f <diagram.drawio> -a <action>
# or build-and-run during development:
pnpm run cli -f <diagram.drawio> -a <action> pnpm run cli -f <diagram.drawio> -a <action>
``` ```
Example: > Note: with `pnpm run cli`, pass arguments directly after `cli` — do **not** use `--` separator.
```bash
pnpm run cli -f ../../000-input/01-marketplace-system-context.drawio -a page-recommendations
```
> Note: pass arguments directly after `cli` — do **not** use `--` separator.
--- ---
@@ -74,8 +77,8 @@ pnpm run cli -f ../../000-input/01-marketplace-system-context.drawio -a page-rec
Build only (no run): Build only (no run):
```bash ```bash
cd CogArch-drawio-2-cogarch/.skills/drawio-main cd <skill>/scripts
pnpm run build # tsc → dist/ task build # tsc → dist/
``` ```
--- ---
@@ -90,7 +90,7 @@ artifacts/
Validate with the standard `validate` action before finishing: Validate with the standard `validate` action before finishing:
```bash ```bash
node dist/cli/commands.js --action validate --file <path-to-negative-space.drawio> cd <skill>/scripts && task validate -- --file="<path-to-negative-space.drawio>"
``` ```
Expected: `valid: true`. The diagram will have 0 edges (only vertex rectangles). Expected: `valid: true`. The diagram will have 0 edges (only vertex rectangles).
+389
View File
@@ -0,0 +1,389 @@
# drawio-main — Connector Routing Best Practices
## Connector Routing Best Practices (Zero-Overlap Guarantee)
Complex architecture diagrams with many connectors require deliberate routing. Follow this process to achieve zero connector-shape overlaps.
### 1. Plan corridors before drawing
Before placing any connectors, identify **clear corridors** — horizontal or vertical bands on the canvas that are free of all shapes. Route every connector through these corridors.
#### Vertical corridors (x-axis lanes between columns)
Between each column of shapes, choose a single x-coordinate that lies in the gap:
```
GAP = (right_edge_of_left_column + left_edge_of_right_column) / 2
```
Examples from a typical IBM Cloud deployment diagram:
| Corridor name | x value | Description |
|---------------|---------|-------------|
| GAP_AB | 370 | Between Internet zone and IBM Cloud boundary |
| GAP_BC | 720 | Between IBM edge services and VPC |
| GAP_NS | 1510 | Between namespace columns inside VPC |
| GAP_CD | 2450 | Between VPC right edge and external service group |
| GAP_DE | 2760 | Between two external service columns |
#### Horizontal corridors (y-axis lanes between rows)
Between each row of shapes, choose a y-coordinate in the clear gap:
| Corridor name | y value | Description |
|---------------|---------|-------------|
| Y_TOP | 110 | Above all shapes (highway for cross-diagram connectors) |
| Y_ROKS | 350 | Between ingress row and pod row |
| Y_GW_CORE | 640 | Between gateway namespace and core namespace |
| Y_CICD | 875 | Between ops pods and CI/CD row |
| Y_BETWEEN | 985 | Between CI/CD and database row |
| Y_BELOW | 1200 | Below all shapes |
**Rule:** When two rows are only 20–40 px apart, the corridor between them is still usable — just ensure the chosen y does not touch any shape's bounding box (see tolerance rule below).
### 2. Calculate absolute canvas coordinates
draw.io uses **relative coordinates** for nested containers: a child's `x`/`y` is relative to its parent container's top-left corner, *not* to the canvas. To verify that a connector waypoint (which uses canvas-absolute coordinates) clears a shape, compute:
```
abs_x = shape.x + parent.x + grandparent.x + ... (sum all ancestor x offsets)
abs_y = shape.y + parent.y + grandparent.y + ... (sum all ancestor y offsets)
```
For swimlane containers, child coordinates are relative to the swimlane's **top-left origin** (not to the content area below the header). `startSize` only affects the visual header height — ignore it when computing absolute coordinates.
Build a flat list of all shapes with absolute bounding boxes before routing:
```python
shapes = [
# (id, abs_x1, abs_y1, abs_x2, abs_y2)
("pod-api", 985, 416, 1145, 574),
("pod-svc", 810, 416, 967, 574),
...
]
```
### 3. Verify routes with Python before writing XML
Run an overlap-detection simulation against all shapes before finalising the XML. This prevents invisible bugs that only become apparent when the file is opened.
```python
TOL = 2 # pixels — allow connectors to touch but not overlap interiors
def segment_overlaps_shape(seg, shape):
sx1, sy1, sx2, sy2 = shape
if seg['type'] == 'H': # horizontal segment at y=Y from xA to xB
Y, xA, xB = seg['y'], min(seg['x1'], seg['x2']), max(seg['x1'], seg['x2'])
if sy1 + TOL < Y < sy2 - TOL:
if max(xA, sx1 + TOL) < min(xB, sx2 - TOL):
return True
elif seg['type'] == 'V': # vertical segment at x=X from yA to yB
X, yA, yB = seg['x'], min(seg['y1'], seg['y2']), max(seg['y1'], seg['y2'])
if sx1 + TOL < X < sx2 - TOL:
if max(yA, sy1 + TOL) < min(yB, sy2 - TOL):
return True
return False
def route_overlaps(segments, shapes):
for seg in segments:
for shape in shapes:
if segment_overlaps_shape(seg, shape[1:]): # skip id
return shape[0], seg
return None
```
Convert every connector's waypoint list into a series of H/V segments, then call `route_overlaps` for each connector. Fix any reported overlap before writing the XML.
**Endpoint-touch rule:** A connector that terminates *at* a target shape will appear to overlap that shape in the algorithm because the last segment enters the shape's bounding box. This is expected and correct — exclude the *terminal shape* (source or target) when checking a connector's first and last segments.
### 4. Common routing patterns
#### Pattern A — Top highway for long cross-diagram connectors
Route connectors that must span the full diagram width through `Y_TOP` (well above all shapes):
```
source_mid_y → vertical up to Y_TOP → horizontal across to target_col_x → vertical down to target_mid_y
```
Waypoints example (mxGraphModel format):
```xml
<Array as="points">
<mxPoint x="200" y="110"/>
<mxPoint x="2600" y="110"/>
</Array>
```
#### Pattern B — Stacked services (approach from the left)
When multiple services are stacked vertically in a column (e.g., Watson/AI services), never route a vertical connector *through* the column. Instead, approach each service individually from a horizontal corridor to its left:
```
source → vertical to Y_TOP → horizontal to GAP_CD (just left of the column) → horizontal at service_mid_y → connect to service left edge
```
Each service in the stack gets its own connector that stops at the gap corridor. draw.io completes the final short horizontal stub automatically.
```python
for svc_id, svc_abs_y in stacked_services:
svc_mid_y = svc_abs_y + svc_height / 2
waypoints = [
(source_mid_x, Y_TOP), # exit source upward
(GAP_CD, Y_TOP), # travel across at top highway
(GAP_CD, svc_mid_y), # descend to service's row
# last waypoint stops at corridor; draw.io connects to service edge
]
```
#### Pattern C — Narrow inter-pod corridors
When adjacent pods in a row leave only a small gap (e.g., pod_A right=967, pod_B left=985 → 18 px gap), that gap is still a usable vertical corridor:
```
corridor_x = (pod_A_right + pod_B_left) / 2 → e.g., 976
```
Use this narrow corridor to route a vertical segment that exits a pod row and connects to shapes above or below:
```
pod_interior → horizontal to corridor_x → vertical through gap to target_y → horizontal to target
```
#### Pattern D — Right bypass for connections below a dense pod grid
When many pods span horizontally across the diagram, route connectors that must reach shapes below by going around the right edge of the pod grid:
```
source_mid → horizontal right to RIGHT_BYPASS (just right of all pods) → vertical to target_y → horizontal left to target
```
Set `RIGHT_BYPASS` to `(rightmost_pod_right + left_edge_of_next_column) / 2`.
### 5. Waypoint XML format
Always use the `Array as="points"` form inside `mxGeometry`:
```xml
<mxCell id="conn-1" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;" edge="1" source="A" target="B" parent="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="450" y="110"/>
<mxPoint x="2450" y="110"/>
<mxPoint x="2450" y="520"/>
</Array>
</mxGeometry>
</mxCell>
```
Rules:
- All waypoint coordinates are **canvas-absolute** (not relative to any container).
- Edges/connectors must be children of the **root layer** (`parent="1"`), never children of a container shape.
- The first waypoint is where draw.io exits the source shape's auto-routing; the last waypoint is where it enters the target.
- Stop the last waypoint at the corridor boundary — do not pass a waypoint into the interior of the target shape. draw.io will complete the stub automatically.
### CRITICAL — Never use port pin constraints on edges
**DO NOT** add `exitX`, `exitY`, `exitDx`, `exitDy`, `entryX`, `entryY`, `entryDx`, `entryDy` attributes to edge `mxCell` elements.
| Indicator | Cause | Meaning |
|-----------|-------|---------|
| 🔵 Blue circle at connector endpoint | `source`/`target` IDs set, no port pins | **Correct** — properly connected to shape |
| 🟢 Green circle with white X at connector endpoint | Port pin constraints (`exitX/Y`, `entryX/Y`) present | **Wrong** — draw.io treats endpoint as floating/unlinked |
Port pin attributes force draw.io to attach the connector to a precise computed point on the shape boundary. When the waypoints don't exactly match that computed point, draw.io renders the endpoint as floating (green X). This breaks visual connectivity even though the `source`/`target` IDs are set.
**Correct pattern — source/target IDs + waypoints only, NO port pins:**
```xml
<!-- ✅ CORRECT — blue circle, properly connected -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;"
edge="1" source="shape-A" target="shape-B" parent="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="200" y="420"/>
<mxPoint x="200" y="200"/>
</Array>
</mxGeometry>
</mxCell>
<!-- ❌ WRONG — green X, floating endpoint -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;
exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;"
edge="1" source="shape-A" target="shape-B" parent="1">
...
</mxCell>
```
draw.io auto-computes the connection point from `source`/`target` shape IDs and the last waypoint direction. Waypoints guide the routing corridor; the actual attachment to the shape is handled automatically.
**For «include» / «extend» labels:** use real UTF-8 characters `«include»` / `«extend»` in the edge `value` attribute — not XML entities (`&#171;include&#187;`).
**Single-port rule:** When multiple edges enter the same target shape, all their last waypoints must approach from the **same axis direction** (all from left, all from right, all from below, etc.). Mixed directions cause `singlePortViolation` in `page-connectors-validation`.
### 6. Corridor-first layout checklist
Before placing shapes, plan the layout so corridors are naturally available:
- [ ] Leave ≥ 40 px horizontal gaps between columns of shapes
- [ ] Leave ≥ 30 px vertical gaps between rows of shapes
- [ ] Reserve at least one wide horizontal corridor above all shapes (`Y_TOP`)
- [ ] Reserve at least one wide horizontal corridor below all shapes (`Y_BELOW`)
- [ ] Do not place shapes in the corridor bands — treat corridors as sacred routing lanes
- [ ] For stacked service columns, leave a vertical corridor to their left at `GAP_CD`
- [ ] Run Python overlap verification before finalising; fix every reported overlap
- [ ] Apply endpoint-touch exclusion when the last segment enters the target shape
---
## Swimlane / Multi-Zone Diagram Routing
When a diagram uses **swimlane zones** (horizontal bands, each a `swimlane` container), apply the following rules in addition to the general patterns above.
### Zone layout reference
For a typical layered architecture with `startSize=40` swimlanes and 40pt spacing:
```
zone.y ← swimlane top edge
zone.y + 40 ← header bottom (label occupies y..y+40)
zone.y + 40 + 40 = zone.y+80 ← first child row top (rel y=80 inside container)
zone.y + 80 + child_h ← first child row bottom
...
zone.y + height ← swimlane bottom edge
```
Inter-zone gaps (between adjacent swimlanes) are clean horizontal corridors — use their midpoint as the H-travel y-coordinate for connectors crossing zone boundaries.
### Bypass corridors
Always reserve **two vertical bypass corridors** outside all zones:
| Corridor | x value | Rule |
|---|---|---|
| LEFT bypass | `zone.x - 20` (e.g. x=60 when zones start at x=80) | All leftward cross-zone connectors |
| RIGHT bypass | `zone.x + zone.width + 20` (e.g. x=1260 when zones end at x=1240) | All rightward cross-zone connectors |
These bypass corridors run the full canvas height and are free of all shapes. **Every connector that must travel between zones should route through one of these corridors.**
### Multi-row swimlane: horizontal segment placement
When a swimlane has **two rows of shapes** (row1 and row2), each row has a y-range. Never place a connector H segment at a y-value that falls inside a row's y-range — that will overlap sibling shapes.
Use only these safe H corridors inside a multi-row services swimlane:
| Corridor | y value | When to use |
|---|---|---|
| Inter-row gap | `row1_bottom + (row2_top - row1_bottom)/2` | H travel between row1 and row2 shapes |
| Services-bottom gap | `row2_bottom + (zone_bottom - row2_bottom)/2` | H travel below row2, still inside zone |
| Inter-zone gaps | midpoint of gap between adjacent zones | H travel outside zones |
**Example** — services-zone: startSize=40, zone.y=500, row1 abs y=580..620, row2 abs y=660..700, zone bottom=740:
- Inter-row gap corridor: **y=640** (midpoint of y=620..660)
- Services-bottom corridor: **y=720** (midpoint of y=700..740)
### Connector routing rules for multi-row swimlanes
**Rule 1 — Never route H segments through a row's y-range.**
If a connector must travel horizontally through the services zone, use y=640 (inter-row) or y=720 (services-bottom), never y=580..620 or y=660..700.
**Rule 2 — When exiting a shape in row2 that has sibling shapes to its right in the same row:**
Do NOT exit from the bottom of the shape and then travel H at y=720 (services-bottom) to the right — this H will cross the V stubs of right-side siblings that exit to the same corridor.
**Fix options:**
- a) Exit from the **right side** of the shape → immediately go to RIGHT bypass x=1260 → V down/up to target (no H inside zone)
- b) If right-side exit H would cross a sibling shape body: exit right → first waypoint in the **column gap** to the right (e.g. x=1060 for col5/col6 gap) → V down to services-bottom corridor y=720 → H right to RIGHT bypass → V to target
**Rule 3 — Left bypass for connectors going downward to lower zones.**
When a row2 shape must connect to a shape in a lower zone (messaging, data), route:
```
shape_bottom → inter-row corridor y=640 → H LEFT to x=LEFT_BYPASS → V down to target zone corridor → H right to target
```
This ensures the H segment travels in the clear inter-row gap corridor and never crosses sibling shapes.
**Rule 4 — connectorShapeOverlaps with zone containers are structural and unavoidable.**
Any connector crossing a swimlane zone boundary will be flagged as `connector_shape_overlap` with the zone container. This is expected and acceptable — focus on eliminating `connectorCrossings` (two connectors intersecting each other) and overlaps with **non-container leaf shapes**.
### Pattern F — Multi-row swimlane fan-out (api-gateway → many services)
When a shape in an upper zone connects to N shapes spread across multiple columns of a multi-row services swimlane:
```
api-gateway_bottom → H to LEFT_BYPASS at inter-zone gap y → V down LEFT_BYPASS →
branch per target:
- col1 (leftmost): H right from LEFT_BYPASS to target_center_x at inter-row gap y
- col2: same, longer H
- col3..colN: same pattern, extending H further right
- for row2 targets: V from inter-row corridor down to row2 top, enter from top
```
All H branches travel at the same y (inter-row gap) — they are **parallel**, not crossing.
### Pattern G — Event bus → services (right bypass fan-in)
When an event bus (bottom of diagram) connects back up to multiple services:
```
event-bus_right → H right to RIGHT_BYPASS → V up →
branch per target:
- service in row1: H left from RIGHT_BYPASS at row1_center_y to target
- service in row2: stop at services-bottom corridor y=720 → H left → V up 20pt to target bottom
- stagger y values slightly (e.g. y=720 for one, y=730 for another) to avoid parallel confusion
```
**Critical:** The H segments going LEFT from RIGHT_BYPASS at y=720/730 will cross V stubs of row2 services that exit their bottoms and route DOWN and RIGHT to the right bypass. To avoid these crossings:
- Route those downward connectors via the **LEFT bypass** instead (Pattern F inverse)
- OR ensure the rightward services exit via their **right side** (not bottom), so no V stub exists at y=720..730
### Validation workflow for swimlane diagrams
1. Run `page-connectors-validation` after every edit
2. Fix `connectorCrossings` first — these are always avoidable
3. Fix `connectorShapeOverlaps` with **leaf shapes** (non-containers) — these are overlaps with actual service boxes
4. Accept `connectorShapeOverlaps` with zone containers as structural (unavoidable)
5. Fix `cornerPortViolations`, `headerEdgeViolations`, `singlePortViolations` — all should reach 0
6. Target: `connectorCrossings=0`, `cornerPortViolations=0`, `headerEdgeViolations=0`, `singlePortViolations=0`
---
## Section 8 — Text-width estimation for negative space calculation
When computing negative space manually (e.g. generating a negative-space diagram), **shape bounding boxes alone overestimate occupied space**. Text labels only occupy a sub-region within the shape bbox. Use the following formula:
```
charWidth = fontSize × 0.6 // avg glyph width for proportional fonts
rawWidth = longestLineCharCount × charWidth
padding = fontSize × 1.0 // horizontal padding (~0.5em each side)
textWidth = rawWidth + padding
fontStyle modifiers (draw.io bitmask):
bit 0 (value 1) = bold → charWidth × 1.10
bit 1 (value 2) = italic → charWidth × 1.05
textXMin = clamp(shapeCenterX − textWidth/2, shape.xMin, shape.xMax)
textXMax = clamp(shapeCenterX + textWidth/2, shape.xMin, shape.xMax)
textFlankLeft = textXMin − shape.xMin // free space left of text inside bbox
textFlankRight = shape.xMax − textXMax // free space right of text inside bbox
```
**When to apply:**
- **Swimlane headers** (`startSize=40` band): header text is centered in the full zone width → use text region for occupied X, flanks are free negative space
- Large container shapes whose children don't fill the full width
- Any shape where `textWidth < shapeWidth` — the flanks inside the bbox are free
**Example — Marketplace diagram zone headers, fontSize=12 bold (fontStyle=1), center x=660, zone x=80..1240:**
| Label | longestLine chars | textWidth | textXMin | textXMax | flank each side |
|---|---|---|---|---|---|
| "Client Layer" | 12 | 107 | 607 | 713 | ~527pt |
| "API Gateway / Edge Layer" | 24 | 202 | 559 | 761 | ~479pt |
| "Core Microservices" | 18 | 155 | 583 | 737 | ~503pt |
| "Messaging / Event Bus" | 21 | 178 | 571 | 749 | ~491pt |
| "Data Layer" | 10 | 91 | 615 | 705 | ~535pt |
**`page-negative-space-summary` action outputs (per shape):**
- `textXMin`, `textXMax`, `textWidth` — estimated text rendering region
- `textFlankLeft`, `textFlankRight` — free flanks inside bbox
- `rows[].textAwareFreeCorridors` — corridors computed using text regions (wider than bbox-based)
**When drawing a negative-space diagram:** see [negative-space-diagram.md](./negative-space-diagram.md) for all rules, construction steps, XML pattern, file naming, and validation.
+12
View File
@@ -0,0 +1,12 @@
# Node / pnpm
node_modules/
dist/
.pnpm-store/
# Environment
.env
.env.local
.venv/
# OS
.DS_Store
+11
View File
@@ -0,0 +1,11 @@
# https://taskfile.dev
version: "3"
tasks:
environment:show:
desc: "Show environment vars"
cmds:
- |
./.scripts/base/api/environment-show.sh {{ .CLI_ARGS }}
silent: true
@@ -0,0 +1,9 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/--index.sh
_base_environment_show "$@"
@@ -0,0 +1,28 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
export LOCAL_HOME_DIR=$(pwd)
export LOCAL_HOME_DIR_NAME=${PWD##*/}
export LOCAL_GIT_ROOT_DIR=$(dirname "${LOCAL_HOME_DIR}")
TMP_BASE_FILE_DOTENV=".env"
if
[ -e "${TMP_BASE_FILE_DOTENV}" ]
then
export $(
grep -v '^#' "${TMP_BASE_FILE_DOTENV}" | xargs
) >/dev/null 2>&1
fi
ALL_ARGS=("$@")
while [[ "$#" -gt 0 ]]; do
case $1 in
*) ;;
esac
shift
done
set -- "${ALL_ARGS[@]}"
@@ -0,0 +1,10 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/--index-api.sh
# No required environment variables for skill-manager base
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/base/lib/-ensure-environment-variable.sh
. ./.scripts/base/lib/-environment-show.sh
. ./.scripts/base/lib/-mask.sh
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index.sh
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/base/lib/--env-vars-reader.sh
#
# --> required environment variables are validated for existence
#
. ./.scripts/base/lib/--env-vars-validator.sh
#
# --> available functions are imported/exported
#
. ./.scripts/base/lib/--index-api.sh
# -------------------------------------------------------------------------------------
@@ -0,0 +1,18 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index-api.sh
_base_ensureEnvironmentVariable() {
local FUNCTION_NAME="_base_ensureEnvironmentVariable"
local ENV_VAR_NAME="$1"
_loggers_info "${FUNCTION_NAME}" "ENV_VAR_NAME: ${ENV_VAR_NAME}"
if [ -z "${!ENV_VAR_NAME}" ]; then
_loggers_error "${FUNCTION_NAME}" "Missing required environment variable: ${ENV_VAR_NAME}. Check .env or .env-* files!"
exit 1
fi
}
@@ -0,0 +1,16 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index-api.sh
_base_environment_show() {
local FUNCTION_NAME="_base_environment_show"
_loggers_info "${FUNCTION_NAME}" "LOCAL_HOME_DIR: ${LOCAL_HOME_DIR}"
_loggers_info "${FUNCTION_NAME}" "LOCAL_HOME_DIR_NAME: ${LOCAL_HOME_DIR_NAME}"
}
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_base_mask() {
local original_value="$1"
local RET_VAL
if
[[ -n "$original_value" && ${#original_value} -gt 2 ]]
then
RET_VAL="${original_value:0:1}$(printf '%*s' $((${#original_value} - 2)) '' | tr ' ' '*')${original_value: -1}"
else
RET_VAL="$(printf '%*s' ${#original_value} '' | tr ' ' '*')"
fi
echo "${RET_VAL}"
}
+32
View File
@@ -0,0 +1,32 @@
# https://taskfile.dev
version: "3"
tasks:
build:
desc: Compile TypeScript source to dist/
cmds:
- |
./.scripts/cli/api/build.sh {{ .CLI_ARGS }}
silent: true
run:
desc: |
Run any drawio-tools action against a .drawio file.
Usage: task cli:run -- --file="/path/to/diagram.drawio" --action=<action> [--page <index>]
Actions: summary, page-summary, page-hierarchy, page-connectors-summary, page-connectors-validation,
page-labels-validation, page-shape-bbox-validation, page-orphans, page-recommendations,
page-hierarchy-full, page-negative-space-summary, validate
cmds:
- |
./.scripts/cli/api/run.sh {{ .CLI_ARGS }}
silent: true
validate:
desc: |
Validate a .drawio file (XML well-formedness + required structure).
Usage: task cli:validate -- --file="/path/to/diagram.drawio"
cmds:
- |
./.scripts/cli/api/validate.sh {{ .CLI_ARGS }}
silent: true
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__build "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__run "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
set -e
. ./.scripts/cli/lib/--index.sh
_cli__validate "$@"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
ALL_ARGS=("$@")
while [[ "$#" -gt 0 ]]; do
case $1 in
*) ;;
esac
shift
done
set -- "${ALL_ARGS[@]}"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/cli/lib/--index-api.sh
# No required environment variables for cli module
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/cli/lib/-build.sh
. ./.scripts/cli/lib/-run.sh
. ./.scripts/cli/lib/-validate.sh
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--index.sh
. ./.scripts/base/lib/--index.sh
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/cli/lib/--env-vars-reader.sh
#
# --> required environment variables are validated for existence
#
. ./.scripts/cli/lib/--env-vars-validator.sh
#
# --> available functions are imported/exported
#
. ./.scripts/cli/lib/--index-api.sh
# -------------------------------------------------------------------------------------
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__build() {
local FUNCTION_NAME="_cli__build"
_loggers_info "${FUNCTION_NAME}" "Compiling TypeScript source to dist/"
pnpm run build
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__run() {
local FUNCTION_NAME="_cli__run"
_loggers_info "${FUNCTION_NAME}" "Running drawio-tools CLI"
node dist/cli/commands.js "$@"
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_cli__validate() {
local FUNCTION_NAME="_cli__validate"
_loggers_info "${FUNCTION_NAME}" "Validating .drawio file via drawio-tools CLI"
node dist/cli/commands.js --action validate "$@"
}
@@ -0,0 +1,18 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
export LOGGER_TRAILING_NEW_LINE_ENABLED="TRUE"
export LOGGER_IS_ENABLED_ERROR=true
export LOGGER_IS_ENABLED_INFO=true
export LOGGER_IS_ENABLED_WARN=true
export LOGGER_IS_ENABLED_DEBUG=true
export LOGGER_IS_ENABLED_TRACE=false
export LOCAL_STRING_005_SPACES=" "
export LOCAL_STRING_010_SPACES="${LOCAL_STRING_005_SPACES}${LOCAL_STRING_005_SPACES}"
export LOCAL_STRING_050_SPACES="${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}"
export LOCAL_STRING_070_SPACES="${LOCAL_STRING_050_SPACES}${LOCAL_STRING_010_SPACES}${LOCAL_STRING_010_SPACES}"
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/--env-vars-reader.sh
. ./.scripts/loggers/lib/--index.sh
@@ -0,0 +1,22 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
. ./.scripts/loggers/lib/-info.sh
. ./.scripts/loggers/lib/-trace.sh
. ./.scripts/loggers/lib/-debug.sh
. ./.scripts/loggers/lib/-error.sh
. ./.scripts/loggers/lib/-warn.sh
. ./.scripts/loggers/lib/-printf--debug.sh
. ./.scripts/loggers/lib/-printf--info.sh
. ./.scripts/loggers/lib/-printf--trace.sh
. ./.scripts/loggers/lib/-printf--warn.sh
. ./.scripts/loggers/lib/-empty-line.sh
. ./.scripts/loggers/lib/-waiting-dot.sh
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
#
# --> passed parameters are read & exported environment variables
#
. ./.scripts/loggers/lib/--env-vars-reader.sh
#
# --> available functions are imported/exported
#
. ./.scripts/loggers/lib/--index-api.sh
# -------------------------------------------------------------------------------------
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_debug() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_DEBUG}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# DEBUG # ${TMP_1ST_PARAM} # ${2}"
echo -e "\033[1;36m${TMP_LINE}\033[0m" >&2
fi
}
@@ -0,0 +1,13 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_emptyLine() {
_loggers_enableLoggerTrailingNewLine
echo -e "" >&2
}
@@ -0,0 +1,15 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
_loggers_enableLoggerTrailingNewLine() {
if
[ -z "$LOGGER_TRAILING_NEW_LINE_DISABLED" ]
then
echo "" >&2
export LOGGER_TRAILING_NEW_LINE_DISABLED="TRUE"
fi
}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_error() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_ERROR}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# ERROR # ${TMP_1ST_PARAM} # $2"
echo -e "\033[1;31m${TMP_LINE}\033[0m" >&2
fi
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_info() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_INFO}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# INFO # ${TMP_1ST_PARAM} # ${2}"
echo -e "${TMP_LINE}" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__debug() {
if
[ "${LOGGER_IS_ENABLED_DEBUG}" = true ]
then
printf '\033[1;36m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__info() {
if
[ "${LOGGER_IS_ENABLED_INFO}" = true ]
then
printf '\033[1;32m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__trace() {
if
[ "${LOGGER_IS_ENABLED_TRACE}" = true ]
then
printf '\033[0;90m%s\033[0m\n' "$*" >&2
fi
}
@@ -0,0 +1,14 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
#
#
# -------------------------------------------------------------------------------------
_loggers_printf__warn() {
if
[ "${LOGGER_IS_ENABLED_WARN}" = true ]
then
printf '\033[1;33m%s\033[0m\n' "$*" >&2
fi
}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_trace() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_TRACE}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# TRACE # ${TMP_1ST_PARAM} # $2"
echo -e "\033[0;94m${TMP_LINE}\033[0m" >&2
fi
}
@@ -0,0 +1,13 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_waitingDot() {
echo -n "." >&2
export LOGGER_TRAILING_NEW_LINE_DISABLED=""
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# -------------------------------------------------------------------------------------
#
# TOBE ADDED - Licence & Copyright Disclaimer
#
# -------------------------------------------------------------------------------------
. ./.scripts/loggers/lib/-enable-trailing-new-line.sh
_loggers_warn() {
_loggers_enableLoggerTrailingNewLine
if
[ "${LOGGER_IS_ENABLED_WARN}" = true ]
then
local TMP_1ST_PARAM="${1}${LOCAL_STRING_070_SPACES}"
TMP_1ST_PARAM="${TMP_1ST_PARAM:0:70}"
local TMP_LINE="# WARN # ${TMP_1ST_PARAM} # $2"
echo -e "\033[1;33m${TMP_LINE}\033[0m" >&2
fi
}
+39
View File
@@ -0,0 +1,39 @@
# https://taskfile.dev
version: "3"
includes:
base: ./.scripts/base/Taskfile.yml
cli: ./.scripts/cli/Taskfile.yml
tasks:
default:
cmds:
- task --list-all
silent: true
build:
desc: Compile TypeScript source to dist/
cmds:
- task: cli:build
silent: true
run:
desc: |
Run any drawio-tools action against a .drawio file.
Usage: task run -- --file="/path/to/diagram.drawio" --action=<action> [--page <index>]
cmds:
- task: cli:run
vars:
CLI_ARGS: "{{ .CLI_ARGS }}"
silent: true
validate:
desc: |
Validate a .drawio file (XML well-formedness + required structure).
Usage: task validate -- --file="/path/to/diagram.drawio"
cmds:
- task: cli:validate
vars:
CLI_ARGS: "{{ .CLI_ARGS }}"
silent: true
View File