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,106 @@
/**
* Action: page-connectors-summary
*
* Returns a summary of all connectors (edges) on a page:
* - Total connector count
* - Per-connector: id, label, source, target, waypoint count, type (directed/undirected/floating),
* routed exit point (first routed coordinate), routed entry point (last routed coordinate)
* - Aggregated stats: labelled vs unlabelled, with waypoints vs straight, floating (missing endpoint)
* - Per-shape port summary: for each shape that is a source or target of 2+ connectors,
* lists the actual exit/entry coordinates to help verify single-port rule compliance.
*/
import { parseDiagram } from "../../services/drawio-parser/parser.js";
import { routeAllEdges } from "../../services/connector-router/connector-router.js";
export function run(filePath: string): Record<string, unknown> {
const { edges, shapes, graphModelXml } = parseDiagram(filePath);
// Get routed paths for all edges
const routeMap = routeAllEdges(graphModelXml);
const connectors = edges.map((e) => {
const hasSource = e.sourceId !== null && shapes.has(e.sourceId);
const hasTarget = e.targetId !== null && shapes.has(e.targetId);
let connectorType: string;
if (hasSource && hasTarget) {
connectorType = "directed";
} else if (!hasSource && !hasTarget) {
connectorType = "floating";
} else {
connectorType = "partial"; // one endpoint missing
}
const pts = routeMap.get(e.id) ?? [];
const exitPt = pts.length >= 1 ? { x: Math.round(pts[0].x * 10) / 10, y: Math.round(pts[0].y * 10) / 10 } : null;
const entryPt = pts.length >= 1 ? { x: Math.round(pts[pts.length - 1].x * 10) / 10, y: Math.round(pts[pts.length - 1].y * 10) / 10 } : null;
return {
id: e.id,
label: e.label,
sourceId: e.sourceId,
sourceLabel: e.sourceId ? (shapes.get(e.sourceId)?.label ?? null) : null,
targetId: e.targetId,
targetLabel: e.targetId ? (shapes.get(e.targetId)?.label ?? null) : null,
waypointCount: e.waypoints.length,
type: connectorType,
exitPoint: exitPt,
entryPoint: entryPt,
};
});
// Build per-shape port map — only for shapes with 2+ connectors on a side
const exitsByShape = new Map<string, Array<{ edgeId: string; label: string; pt: { x: number; y: number } }>>();
const entriesByShape = new Map<string, Array<{ edgeId: string; label: string; pt: { x: number; y: number } }>>();
for (const c of connectors) {
if (c.sourceId && c.exitPoint) {
const list = exitsByShape.get(c.sourceId) ?? [];
list.push({ edgeId: c.id, label: c.label, pt: c.exitPoint });
exitsByShape.set(c.sourceId, list);
}
if (c.targetId && c.entryPoint) {
const list = entriesByShape.get(c.targetId) ?? [];
list.push({ edgeId: c.id, label: c.label, pt: c.entryPoint });
entriesByShape.set(c.targetId, list);
}
}
const shapePorts: Record<string, unknown>[] = [];
const allShapeIds = new Set([...exitsByShape.keys(), ...entriesByShape.keys()]);
for (const shapeId of allShapeIds) {
const exits = exitsByShape.get(shapeId) ?? [];
const entries = entriesByShape.get(shapeId) ?? [];
if (exits.length < 2 && entries.length < 2) continue; // only shapes with multiple connectors on a side
shapePorts.push({
shapeId,
shapeLabel: shapes.get(shapeId)?.label ?? shapeId,
exits,
entries,
});
}
const labelled = connectors.filter((c) => c.label.trim() !== "").length;
const withWaypts = connectors.filter((c) => c.waypointCount > 0).length;
const floating = connectors.filter((c) => c.type === "floating").length;
const partial = connectors.filter((c) => c.type === "partial").length;
const directed = connectors.filter((c) => c.type === "directed").length;
return {
action: "page-connectors-summary",
file: filePath,
summary: {
total: edges.length,
directed,
partial,
floating,
labelled,
unlabelled: edges.length - labelled,
withWaypoints: withWaypts,
straight: edges.length - withWaypts,
},
connectors,
shapePorts,
};
}