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:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user