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,81 @@
/**
* Action: page-shape-bbox-validation
*
* Detects shapes whose bounding boxes overlap each other.
* Parent-child containment is intentional and is NOT flagged.
*
* Two shapes overlap when their axis-aligned bounding boxes intersect
* with more than TOL pixels of penetration on both axes.
*/
import { parseDiagram, Shape } from "../../services/drawio-parser/parser.js";
const TOL = 2; // px — minimum penetration depth to flag as an overlap
function bboxOverlaps(a: Shape, b: Shape): boolean {
// Axis-aligned overlap with tolerance
return (
a.x + TOL < b.x2 &&
a.x2 - TOL > b.x &&
a.y + TOL < b.y2 &&
a.y2 - TOL > b.y
);
}
export function run(filePath: string): Record<string, unknown> {
const { shapes } = parseDiagram(filePath);
// Build ancestor set for a shape (to skip parent-child pairs)
function getAncestors(id: string): Set<string> {
const ancestors = new Set<string>();
let current = shapes.get(id);
while (current && current.parentId && current.parentId !== "1") {
ancestors.add(current.parentId);
current = shapes.get(current.parentId);
}
return ancestors;
}
const shapeList = Array.from(shapes.values());
const issues: Record<string, unknown>[] = [];
const seenPairs = new Set<string>();
for (let i = 0; i < shapeList.length; i++) {
const a = shapeList[i];
const aAncestors = getAncestors(a.id);
for (let j = i + 1; j < shapeList.length; j++) {
const b = shapeList[j];
// Skip parent-child containment in either direction
if (aAncestors.has(b.id)) continue;
if (b.parentId === a.id || a.parentId === b.id) continue;
const pairKey = a.id < b.id ? `${a.id}|${b.id}` : `${b.id}|${a.id}`;
if (seenPairs.has(pairKey)) continue;
if (bboxOverlaps(a, b)) {
seenPairs.add(pairKey);
issues.push({
type: "bbox_overlap",
shapeAId: a.id,
shapeALabel: a.label,
shapeBId: b.id,
shapeBLabel: b.label,
overlapX: Math.round((Math.min(a.x2, b.x2) - Math.max(a.x, b.x)) * 10) / 10,
overlapY: Math.round((Math.min(a.y2, b.y2) - Math.max(a.y, b.y)) * 10) / 10,
});
}
}
}
return {
action: "page-shape-bbox-validation",
file: filePath,
summary: {
totalShapes: shapes.size,
overlappingPairs: issues.length,
},
issues,
};
}