Files
spaceman1412 fd4c3c2fc7 Add new skills and utilities for enhanced writing and testing
- Introduced graphviz conventions for visualizing process flows in writing skills.
- Added a comprehensive guide on persuasion principles to improve skill design effectiveness.
- Implemented a script to render graphviz diagrams from markdown files to SVG format.
- Created a detailed reference for testing skills with subagents, emphasizing TDD principles.
- Established a task tracker template for live task management.
- Developed a shell script to check the integrity of the antigravity profile and required files.
- Added test scripts to validate the initialization of agent projects.
- Created workflows for brainstorming, executing plans, and writing plans to streamline processes.
2026-02-26 15:16:32 +07:00

176 lines
4.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Render graphviz diagrams from a skill's SKILL.md to SVG files.
*
* Usage:
* ./render-graphs.js <skill-directory> # Render each diagram separately
* ./render-graphs.js <skill-directory> --combine # Combine all into one diagram
*
* Extracts all ```dot blocks from SKILL.md and renders to SVG.
* Useful for helping your human partner visualize the process flows.
*
* Requires: graphviz (dot) installed on system
*/
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
function extractDotBlocks(markdown) {
const blocks = [];
const regex = /```dot\n([\s\S]*?)```/g;
let match;
while ((match = regex.exec(markdown)) !== null) {
const content = match[1].trim();
// Extract digraph name
const nameMatch = content.match(/digraph\s+(\w+)/);
const name = nameMatch ? nameMatch[1] : `graph_${blocks.length + 1}`;
blocks.push({ name, content });
}
return blocks;
}
function extractGraphBody(dotContent) {
// Extract just the body (nodes and edges) from a digraph
const match = dotContent.match(/digraph\s+\w+\s*\{([\s\S]*)\}/);
if (!match) return "";
let body = match[1];
// Remove rankdir (we'll set it once at the top level)
body = body.replace(/^\s*rankdir\s*=\s*\w+\s*;?\s*$/gm, "");
return body.trim();
}
function combineGraphs(blocks, skillName) {
const bodies = blocks.map((block, i) => {
const body = extractGraphBody(block.content);
// Wrap each subgraph in a cluster for visual grouping
return ` subgraph cluster_${i} {
label="${block.name}";
${body
.split("\n")
.map((line) => " " + line)
.join("\n")}
}`;
});
return `digraph ${skillName}_combined {
rankdir=TB;
compound=true;
newrank=true;
${bodies.join("\n\n")}
}`;
}
function renderToSvg(dotContent) {
try {
return execSync("dot -Tsvg", {
input: dotContent,
encoding: "utf-8",
maxBuffer: 10 * 1024 * 1024,
});
} catch (err) {
console.error("Error running dot:", err.message);
if (err.stderr) console.error(err.stderr.toString());
return null;
}
}
function main() {
const args = process.argv.slice(2);
const combine = args.includes("--combine");
const skillDirArg = args.find((a) => !a.startsWith("--"));
if (!skillDirArg) {
console.error("Usage: render-graphs.js <skill-directory> [--combine]");
console.error("");
console.error("Options:");
console.error(" --combine Combine all diagrams into one SVG");
console.error("");
console.error("Example:");
console.error(" ./render-graphs.js ../single-flow-task-execution");
console.error(
" ./render-graphs.js ../single-flow-task-execution --combine",
);
process.exit(1);
}
const skillDir = path.resolve(skillDirArg);
const skillFile = path.join(skillDir, "SKILL.md");
const skillName = path.basename(skillDir).replace(/-/g, "_");
if (!fs.existsSync(skillFile)) {
console.error(`Error: ${skillFile} not found`);
process.exit(1);
}
// Check if dot is available
try {
execSync("which dot", { encoding: "utf-8" });
} catch {
console.error("Error: graphviz (dot) not found. Install with:");
console.error(" brew install graphviz # macOS");
console.error(" apt install graphviz # Linux");
process.exit(1);
}
const markdown = fs.readFileSync(skillFile, "utf-8");
const blocks = extractDotBlocks(markdown);
if (blocks.length === 0) {
console.log("No ```dot blocks found in", skillFile);
process.exit(0);
}
console.log(
`Found ${blocks.length} diagram(s) in ${path.basename(skillDir)}/SKILL.md`,
);
const outputDir = path.join(skillDir, "diagrams");
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
if (combine) {
// Combine all graphs into one
const combined = combineGraphs(blocks, skillName);
const svg = renderToSvg(combined);
if (svg) {
const outputPath = path.join(outputDir, `${skillName}_combined.svg`);
fs.writeFileSync(outputPath, svg);
console.log(` Rendered: ${skillName}_combined.svg`);
// Also write the dot source for debugging
const dotPath = path.join(outputDir, `${skillName}_combined.dot`);
fs.writeFileSync(dotPath, combined);
console.log(` Source: ${skillName}_combined.dot`);
} else {
console.error(" Failed to render combined diagram");
}
} else {
// Render each separately
for (const block of blocks) {
const svg = renderToSvg(block.content);
if (svg) {
const outputPath = path.join(outputDir, `${block.name}.svg`);
fs.writeFileSync(outputPath, svg);
console.log(` Rendered: ${block.name}.svg`);
} else {
console.error(` Failed: ${block.name}`);
}
}
}
console.log(`\nOutput: ${outputDir}/`);
}
main();