- Add nemotron-3-nano:30b to Model Format table in KILO_SPEC.md - Update Pipeline Agents table to match current agent definitions - Restore sync-agents.cjs script (uses .kilo/agents/*.md as source of truth) - Script does NOT require kilo-meta.json in root (keeps Kilo Code working) Models in use: - ollama-cloud/nemotron-3-super (9 agents) ✓ available - ollama-cloud/glm-5 (4 agents) ✓ available - ollama-cloud/qwen3-coder:480b (3 agents) ✓ available - qwen/qwen3.6-plus:free (2 agents) ✓ available - ollama-cloud/minimax-m2.5 (2 agents) ✓ available - ollama-cloud/gpt-oss:120b (2 agents) ✓ available - ollama-cloud/nemotron-3-nano:30b (1 agent) ✓ available (added)
130 lines
5.9 KiB
JavaScript
130 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Sync Agent Models - Source of truth: .kilo/agents/*.md frontmatter
|
|
* Run: node scripts/sync-agents.cjs [--check | --fix]
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const AGENTS_DIR = path.join(ROOT, '.kilo', 'agents');
|
|
const KILO_SPEC = path.join(ROOT, '.kilo', 'KILO_SPEC.md');
|
|
const AGENTS_MD = path.join(ROOT, 'AGENTS.md');
|
|
|
|
function parseFrontmatter(content) {
|
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (!match) return {};
|
|
const frontmatter = {};
|
|
for (const line of match[1].split('\n')) {
|
|
const idx = line.indexOf(':');
|
|
if (idx > 0) {
|
|
const key = line.slice(0, idx).trim();
|
|
let val = line.slice(idx + 1).trim();
|
|
if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
|
|
frontmatter[key] = val;
|
|
}
|
|
}
|
|
return frontmatter;
|
|
}
|
|
|
|
function getAllAgents() {
|
|
const agents = {};
|
|
for (const file of fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'))) {
|
|
const content = fs.readFileSync(path.join(AGENTS_DIR, file), 'utf-8');
|
|
const fm = parseFrontmatter(content);
|
|
const name = file.replace('.md', '');
|
|
agents[name] = {
|
|
description: fm.description || '',
|
|
model: fm.model || '',
|
|
mode: fm.mode || 'all',
|
|
color: fm.color || ''
|
|
};
|
|
}
|
|
return agents;
|
|
}
|
|
|
|
function categorizeAgent(name) {
|
|
const cats = {
|
|
core: ['requirement-refiner', 'history-miner', 'system-analyst', 'sdet-engineer', 'lead-developer', 'frontend-developer', 'backend-developer', 'go-developer', 'devops-engineer'],
|
|
quality: ['code-skeptic', 'the-fixer', 'performance-engineer', 'security-auditor', 'visual-tester'],
|
|
meta: ['orchestrator', 'release-manager', 'evaluator', 'prompt-optimizer', 'product-owner', 'agent-architect', 'capability-analyst', 'workflow-architect', 'markdown-validator'],
|
|
testing: ['browser-automation'],
|
|
cognitive: ['planner', 'reflector', 'memory-manager']
|
|
};
|
|
for (const [cat, list] of Object.entries(cats)) {
|
|
if (list.includes(name)) return cat;
|
|
}
|
|
return 'meta';
|
|
}
|
|
|
|
function updateKiloSpec(agents) {
|
|
let content = fs.readFileSync(KILO_SPEC, 'utf-8');
|
|
const rows = Object.entries(agents)
|
|
.filter(([_, a]) => a.model)
|
|
.map(([name, a]) => {
|
|
const dn = name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
return `| \`@${dn}\` | ${a.description.split('.')[0]}. | ${a.model} |`;
|
|
}).join('\n');
|
|
const table = `### Pipeline Agents\n\n| Agent | Role | Model |\n|-------|------|-------|\n${rows}`;
|
|
content = content.replace(/### Pipeline Agents\n\n\| Agent \| Role \| Model \|[\s\S]*?(?=\n\n\*\*Note)/, table + '\n\n');
|
|
fs.writeFileSync(KILO_SPEC, content);
|
|
}
|
|
|
|
function updateAgentsMd(agents) {
|
|
let content = fs.readFileSync(AGENTS_MD, 'utf-8');
|
|
const catNames = { core: '### Core Development', quality: '### Quality Assurance', meta: '### Meta & Process', testing: '### Testing', cognitive: '### Cognitive Enhancement (New)' };
|
|
const triggers = { 'requirement-refiner': 'Issue status: new', 'history-miner': 'Status: planned', 'system-analyst': 'Status: researching', 'sdet-engineer': 'Status: designed', 'lead-developer': 'Status: testing', 'frontend-developer': 'When UI work needed', 'backend-developer': 'When backend needed', 'go-developer': 'When Go backend needed', 'devops-engineer': 'When deployment/infra needed', 'code-skeptic': 'Status: implementing', 'the-fixer': 'When review fails', 'performance-engineer': 'After code-skeptic', 'security-auditor': 'After performance', 'visual-tester': 'When UI changes', 'orchestrator': 'Manages all agent routing', 'release-manager': 'Status: releasing', 'evaluator': 'Status: evaluated', 'prompt-optimizer': 'When score < 7', 'product-owner': 'Manages issues', 'agent-architect': 'When gaps identified', 'capability-analyst': 'When starting new task', 'workflow-architect': 'New workflow needed', 'markdown-validator': 'Before issue creation', 'browser-automation': 'E2E testing needed', 'planner': 'Complex tasks', 'reflector': 'After each agent', 'memory-manager': 'Context management' };
|
|
|
|
const byCat = {};
|
|
for (const [name, a] of Object.entries(agents)) {
|
|
const cat = categorizeAgent(name);
|
|
(byCat[cat] = byCat[cat] || []).push([name, a]);
|
|
}
|
|
|
|
for (const [cat, heading] of Object.entries(catNames)) {
|
|
const list = byCat[cat] || [];
|
|
if (!list.length) continue;
|
|
const rows = list.map(([name, a]) => {
|
|
const dn = name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
return `| \`@${dn}\` | ${a.description.split('.')[0]} | ${triggers[name] || 'Manual invocation'} |`;
|
|
}).join('\n');
|
|
const table = `${heading}\n| Agent | Role | When Invoked |\n|-------|------|--------------|\n${rows}`;
|
|
const regex = new RegExp(`${heading}[\s\S]*?(?=###|$)`);
|
|
if (regex.test(content)) content = content.replace(regex, table + '\n\n');
|
|
}
|
|
fs.writeFileSync(AGENTS_MD, content);
|
|
}
|
|
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
const fix = args.includes('--fix');
|
|
const check = args.includes('--check');
|
|
|
|
console.log('=== Agent Sync Tool ===\n');
|
|
console.log('Source of truth: .kilo/agents/*.md frontmatter\n');
|
|
|
|
const agents = getAllAgents();
|
|
console.log(`Found ${Object.keys(agents).length} agents\n`);
|
|
|
|
const issues = Object.entries(agents).filter(([_, a]) => !a.model || !a.description);
|
|
if (issues.length) {
|
|
console.log('Issues found:');
|
|
issues.forEach(([n, a]) => console.log(` ${n}: ${!a.model ? 'missing model' : ''} ${!a.description ? 'missing description' : ''}`));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (fix) {
|
|
console.log('Updating KILO_SPEC.md...');
|
|
updateKiloSpec(agents);
|
|
console.log('Updating AGENTS.md...');
|
|
updateAgentsMd(agents);
|
|
console.log('✅ Done!');
|
|
} else {
|
|
console.log('✅ All agents have model and description');
|
|
if (check) console.log('\nRun with --fix to update documentation.');
|
|
}
|
|
}
|
|
|
|
main();
|