automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
223 lines (202 loc) • 9.64 kB
JavaScript
const fs = require('fs');
const path = require('path');
function scanAgents(root) {
const genieRoot = path.join(root, '.genie');
const out = { code_agents: [], create_agents: [], code_workflows: [], orchestrators: [], git_workflows: [] };
function push(filePath, bucket) {
out[bucket].push({
name: path.basename(filePath, path.extname(filePath)),
path: path.relative(root, filePath),
full_path: filePath,
});
}
// Scan code agents
const codeAgentsDir = path.join(genieRoot, 'code', 'agents');
if (fs.existsSync(codeAgentsDir)) {
for (const f of fs.readdirSync(codeAgentsDir)) {
if (f.endsWith('.md')) {
push(path.join(codeAgentsDir, f), 'code_agents');
} else if (fs.statSync(path.join(codeAgentsDir, f)).isDirectory()) {
// Check for git/ or wish/ subdirectories
const agentFile = path.join(codeAgentsDir, f, `${f}.md`);
if (fs.existsSync(agentFile)) {
push(agentFile, 'code_agents');
// Check for workflows subdirectory
const wfDir = path.join(codeAgentsDir, f, 'workflows');
if (fs.existsSync(wfDir)) {
for (const wf of fs.readdirSync(wfDir)) {
if (wf.endsWith('.md')) push(path.join(wfDir, wf), 'git_workflows');
}
}
}
}
}
}
// Scan code workflows
const codeWorkflowsDir = path.join(genieRoot, 'code', 'workflows');
if (fs.existsSync(codeWorkflowsDir)) {
for (const f of fs.readdirSync(codeWorkflowsDir)) {
if (f.endsWith('.md')) push(path.join(codeWorkflowsDir, f), 'code_workflows');
}
}
// Scan code collective
const codeDir = path.join(genieRoot, 'code');
if (fs.existsSync(codeDir)) {
const codeOrch = path.join(codeDir, 'code.md');
if (fs.existsSync(codeOrch)) out.orchestrators.push({ name: 'code', path: path.relative(root, codeOrch), full_path: codeOrch });
}
// Scan create agents
const createAgentsDir = path.join(genieRoot, 'create', 'agents');
if (fs.existsSync(createAgentsDir)) {
for (const f of fs.readdirSync(createAgentsDir)) {
if (f.endsWith('.md')) push(path.join(createAgentsDir, f), 'create_agents');
}
}
// Scan create collective
const createDir = path.join(genieRoot, 'create');
if (fs.existsSync(createDir)) {
const createOrch = path.join(createDir, 'create.md');
if (fs.existsSync(createOrch)) out.orchestrators.push({ name: 'create', path: path.relative(root, createOrch), full_path: createOrch });
}
return out;
}
function extractDelegations(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const re = /mcp__genie__run.*?agent=["']([^"']+)["']/gs;
const set = new Set();
let m; while ((m = re.exec(content))) set.add(m[1]);
return Array.from(set);
} catch { return []; }
}
function generateMermaid(agents) {
const lines = [];
lines.push('```mermaid');
lines.push('graph TB');
lines.push(' %% Genie Agent Tree');
lines.push('');
lines.push(' %% Code Collective');
lines.push(' CODE[Code Collective]:::orchestrator');
const codeAgents = [...agents.code_agents].sort((a,b)=>a.name.localeCompare(b.name));
codeAgents.slice(0,6).forEach((a)=>{ lines.push(` code_${a.name}[${a.name}]:::code_agent`); lines.push(` CODE --> code_${a.name}`); });
if (codeAgents.length > 6) { lines.push(` more_code[...${codeAgents.length-6} more]:::more`); lines.push(' CODE --> more_code'); }
if (agents.git_workflows.length) {
lines.push('');
lines.push(' %% Git Workflows');
const git = codeAgents.find((a)=>a.name==='git');
if (git) { lines.push(' code_git --> git_issue[issue]:::workflow'); lines.push(' code_git --> git_pr[pr]:::workflow'); lines.push(' code_git --> git_report[report]:::workflow'); }
}
lines.push('');
if (agents.code_workflows.length) {
lines.push(' %% Code Workflows');
const workflows = [...agents.code_workflows].sort((a,b)=>a.name.localeCompare(b.name));
workflows.slice(0,4).forEach((a)=>{ lines.push(` workflow_${a.name}[${a.name}]:::workflow`); lines.push(` CODE --> workflow_${a.name}`); });
}
if (agents.create_agents.length || agents.orchestrators.find(o=>o.name==='create')) {
lines.push('');
lines.push(' %% Create Collective');
lines.push(' CREATE[Create Collective]:::orchestrator');
const createAgents = [...agents.create_agents].sort((a,b)=>a.name.localeCompare(b.name));
createAgents.forEach((a)=>{ lines.push(` create_${a.name}[${a.name}]:::create_agent`); lines.push(` CREATE --> create_${a.name}`); });
}
lines.push('');
lines.push(' %% Styling');
lines.push(' classDef orchestrator fill:#fff3e0,stroke:#f57c00,stroke-width:3px');
lines.push(' classDef code_agent fill:#e8f5e9,stroke:#388e3c,stroke-width:2px');
lines.push(' classDef create_agent fill:#fce4ec,stroke:#c2185b,stroke-width:2px');
lines.push(' classDef workflow fill:#fff9c4,stroke:#fbc02d,stroke-width:1px');
lines.push(' classDef more fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5');
lines.push('```');
return lines.join('\n');
}
function generateMarkdownTree(agents) {
const lines = [];
lines.push('## Agent Tree');
lines.push('');
lines.push('**Auto-generated** from `.genie/` folder structure');
lines.push('');
const total = agents.code_agents.length + agents.git_workflows.length + agents.create_agents.length + agents.code_workflows.length + agents.orchestrators.length;
lines.push('**Summary:**');
lines.push(`- Code agents: ${agents.code_agents.length}`);
lines.push(`- Code workflows: ${agents.code_workflows.length}`);
lines.push(`- Git workflows: ${agents.git_workflows.length}`);
lines.push(`- Create agents: ${agents.create_agents.length}`);
lines.push(`- Orchestrators: ${agents.orchestrators.length}`);
lines.push(`- **Total: ${total} agents**`);
lines.push('');
lines.push('### Code Collective');
lines.push('');
lines.push('**Orchestrator:** `code`');
lines.push('');
lines.push('**Agents:**');
for (const a of [...agents.code_agents].sort((x,y)=>x.name.localeCompare(y.name))) {
const dels = extractDelegations(a.full_path);
lines.push(dels.length ? `- **${a.name}** → ${dels.map((d)=>'`'+d+'`').join(', ')}` : `- **${a.name}**`);
}
if (agents.code_workflows.length) {
lines.push('');
lines.push('**Workflows:**');
for (const a of [...agents.code_workflows].sort((x,y)=>x.name.localeCompare(y.name))) {
lines.push(`- **${a.name}**`);
}
}
if (agents.git_workflows.length) {
lines.push('');
lines.push('**Git workflows:** `issue`, `pr`, `report`');
}
if (agents.create_agents.length) {
lines.push('');
lines.push('### Create Collective');
lines.push('');
lines.push('**Orchestrator:** `create`');
lines.push('');
lines.push('**Agents:**');
for (const a of [...agents.create_agents].sort((x,y)=>x.name.localeCompare(y.name))) {
lines.push(`- **${a.name}**`);
}
}
return lines.join('\n');
}
function updateBetweenMarkers(filePath, startMarker, endMarker, newContent) {
if (!fs.existsSync(filePath)) { console.warn(`⚠️ File not found: ${filePath}`); return false; }
const content = fs.readFileSync(filePath, 'utf8');
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
console.warn(`⚠️ Markers not found in ${path.relative(process.cwd(), filePath)}`);
return false;
}
const before = content.slice(0, startIdx + startMarker.length) + '\n';
const after = '\n' + content.slice(endIdx);
fs.writeFileSync(filePath, before + newContent + after);
return true;
}
function main() {
const repoRoot = path.join(__dirname, '..', '..');
console.log('🔍 Scanning .genie/ structure...');
const agents = scanAgents(repoRoot);
const total = agents.code_agents.length + agents.code_workflows.length + agents.git_workflows.length + agents.create_agents.length + agents.orchestrators.length;
console.log(` Found ${total} agents total`);
console.log(` - Code agents: ${agents.code_agents.length}`);
console.log(` - Code workflows: ${agents.code_workflows.length}`);
console.log(` - Git workflows: ${agents.git_workflows.length}`);
console.log(` - Create agents: ${agents.create_agents.length}`);
console.log(` - Orchestrators: ${agents.orchestrators.length}`);
console.log('\n🌲 Generating Mermaid diagram...');
const mermaid = generateMermaid(agents);
console.log('📝 Generating markdown tree...');
const mdTree = generateMarkdownTree(agents);
const readme = path.join(repoRoot, 'README.md');
console.log(`\n📄 Updating ${path.relative(repoRoot, readme)}...`);
const ok1 = updateBetweenMarkers(readme, '<!-- AGENT_TREE_START -->', '<!-- AGENT_TREE_END -->', mermaid);
console.log(ok1 ? ' ✅ Mermaid chart updated' : ' ⚠️ Could not update Mermaid chart (markers missing)');
const genieReadme = path.join(repoRoot, '.genie', 'README.md');
console.log(`\n📄 Updating ${path.relative(repoRoot, genieReadme)}...`);
const ok2 = updateBetweenMarkers(genieReadme, '<!-- NEURAL_TREE_START -->', '<!-- NEURAL_TREE_END -->', mdTree);
console.log(ok2 ? ' ✅ Markdown tree updated' : ' ⚠️ Could not update markdown tree (markers missing)');
console.log('\n✨ Agent agent tree generation complete!');
console.log(' - README.md: Mermaid flowchart');
console.log(' - .genie/README.md: Detailed markdown tree');
}
main();