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