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
@@ -0,0 +1,74 @@
/**
* connector-router — computes the actual routed path for draw.io connectors.
*
* Strategy: load the diagram XML into a real maxGraph Graph+GraphView instance
* via graph-loader, which calls GraphView.validate() to compute all cell states
* exactly as draw.io does at render time. Edge absolutePoints are taken directly
* from those states — no synthetic geometry approximation.
*
* This is the only correct approach: draw.io's routing algorithms (especially
* OrthConnector for edgeStyle=orthogonalEdgeStyle) depend on the full graph
* state including parent container bounds, so they cannot be reproduced
* accurately without loading the full model.
*/
import { loadGraphStates } from "../maxgraph-loader/graph-loader.js";
import type { Edge } from "../drawio-parser/parser.js";
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export interface RoutedPoint {
x: number;
y: number;
}
/**
* Build a map of edgeId → routed points for ALL edges in the diagram.
*
* Call this once per diagram page and reuse the result for all edges.
* Using `routeAllEdges` is significantly more efficient than calling
* `routeEdge` in a loop because it runs `GraphView.validate()` only once.
*
* @param graphModelXml - Raw <mxGraphModel> XML for the page
* @returns Map of edge id → array of canvas-absolute routed points
*/
export function routeAllEdges(graphModelXml: string): Map<string, RoutedPoint[]> {
const { edgeRoutes } = loadGraphStates(graphModelXml);
const result = new Map<string, RoutedPoint[]>();
for (const [id, route] of edgeRoutes) {
result.set(id, route.points);
}
return result;
}
/**
* Get routed points for a single edge from a pre-computed route map.
*
* For efficiency, pre-compute the map once using `routeAllEdges` and pass it here.
* Falls back to a straight line between shape centers if the edge is not found.
*
* @param edge - The edge to route
* @param routeMap - Pre-computed map from `routeAllEdges`
* @param shapeMidpoints - Optional fallback: map of shapeId → {midX, midY}
*/
export function getEdgeRoute(
edge: Edge,
routeMap: Map<string, RoutedPoint[]>,
shapeMidpoints?: Map<string, { midX: number; midY: number }>
): RoutedPoint[] {
const pts = routeMap.get(edge.id);
if (pts && pts.length >= 2) return pts;
// Fallback: straight line using shape midpoints
if (shapeMidpoints && edge.sourceId && edge.targetId) {
const src = shapeMidpoints.get(edge.sourceId);
const tgt = shapeMidpoints.get(edge.targetId);
if (src && tgt) {
return [{ x: src.midX, y: src.midY }, { x: tgt.midX, y: tgt.midY }];
}
}
return [];
}