OpenWriterControle/integrations/claude-code/converter/src/convert-agents.js
Darren Hinde bf02ad0d5c
refactor: Rename repository to OpenAgents Control (#100)
* refactor: Rename repository to OpenAgents Control

Update all project references from "OpenAgents" to "OpenAgents Control":
- Updated core config files (package.json, registry.json)
- Updated documentation (README.md, docs/)
- Updated scripts (install.sh, update.sh, test scripts)
- Updated agent and command files
- Updated integrations and packages
- Updated all GitHub URLs to use OpenAgentsControl

Naming convention:
- Display name: "OpenAgents Control"
- Repository URL: OpenAgentsControl

* feat: Enable subagent-to-subagent delegation (ContextScout integration)

Allow coding-related subagents to call ContextScout for context discovery:
- CoderAgent: Can now discover coding standards on-demand
- TestEngineer: Can now discover test coverage and TDD guidelines
- CodeReviewer: Can now discover code review and security patterns
- BuildAgent: Can now discover build validation standards
- DocWriter: Can now discover documentation formatting guidelines

Changes per subagent:
- Added `task: true` to tools section
- Added permission allowlist for ContextScout only (deny all other delegation)
- Added Context Discovery workflow section with when-to-call guidance

This enables subagents to be self-sufficient for finding relevant standards
instead of relying on orchestrator to provide all context files.

Benefits:
- Subagents can discover what they need when context isn't explicit
- Maintains security (only ContextScout allowed via task tool)
- Follows existing patterns (uses task tool like main agents)

* fix: Set OPENCODE_BRANCH in CI tests to use PR branch for downloads

- Add OPENCODE_BRANCH environment variable to E2E tests
- Add OPENCODE_BRANCH environment variable to profile smoke tests
- Ensures tests download files from the PR branch instead of main
- Fixes E2E test failures after repository rename to OpenAgentsControl
2026-01-18 09:31:35 +00:00

233 lines
6.9 KiB
JavaScript

#!/usr/bin/env node
/**
* convert-agents.js
* Converts OpenAgents Control to Claude Code format
*
* Usage: node convert-agents.js [--watch]
*/
const fs = require('fs');
const path = require('path');
// Configuration - Use absolute paths from the script location
const SCRIPT_DIR = __dirname;
const REPO_ROOT = path.resolve(path.join(SCRIPT_DIR, '../../../../'));
const SOURCE_DIR = path.join(REPO_ROOT, '.opencode/agent');
const OUTPUT_DIR = path.join(SCRIPT_DIR, '../generated');
const CLAUDE_AGENTS_DIR = path.join(OUTPUT_DIR, 'agents');
const CLAUDE_SKILLS_DIR = path.join(OUTPUT_DIR, 'skills');
// Claude frontmatter fields (subset of OpenCode)
const CLAUDE_FIELDS = ['name', 'description', 'tools', 'model', 'permissionMode', 'skills', 'hooks'];
console.log('🚀 OpenAgents Control → Claude Code Converter');
console.log(` Source: ${SOURCE_DIR}`);
console.log(` Output: ${OUTPUT_DIR}\n`);
/**
* Parses YAML frontmatter from markdown
*/
function parseFrontmatter(content) {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match) return { frontmatter: null, content };
const yaml = match[1];
const body = match[2];
const frontmatter = {};
yaml.split('\n').forEach(line => {
const colonIndex = line.indexOf(':');
if (colonIndex > -1) {
const key = line.slice(0, colonIndex).trim();
let value = line.slice(colonIndex + 1).trim();
// Parse arrays
if (value.startsWith('[') && value.endsWith(']')) {
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/"/g, ''));
}
frontmatter[key] = value;
}
});
return { frontmatter, body };
}
/**
* Converts OpenCode frontmatter to Claude format
*/
function convertFrontmatter(ocFrontmatter) {
const claude = {};
// Map OpenCode fields to Claude fields
claude.name = ocFrontmatter.id || ocFrontmatter.name;
claude.description = ocFrontmatter.description;
// Map tools from OpenCode permissions to Claude
if (ocFrontmatter.tools) {
claude.tools = ocFrontmatter.tools;
} else if (ocFrontmatter.permissions) {
// Extract allowed tools from permissions block
const tools = [];
if (ocFrontmatter.permissions.read) tools.push('Read');
if (ocFrontmatter.permissions.grep) tools.push('Grep');
if (ocFrontmatter.permissions.glob) tools.push('Glob');
if (ocFrontmatter.permissions.edit) tools.push('Edit');
if (ocFrontmatter.permissions.write) tools.push('Write');
if (ocFrontmatter.permissions.bash) tools.push('Bash');
claude.tools = tools.join(', ');
}
// Map model
claude.model = mapModel(ocFrontmatter.model);
// Map permissionMode (default to 'default' if not specified)
claude.permissionMode = ocFrontmatter.mode === 'subagent' ? 'plan' : 'default';
return claude;
}
/**
* Maps OpenCode model names to Claude model aliases
*/
function mapModel(model) {
const modelMap = {
'opencode/grok-code': 'sonnet',
'opencode/grok': 'opus',
'gpt-4': 'sonnet',
'gpt-4o': 'sonnet',
'haiku': 'haiku',
};
return modelMap[model] || 'sonnet';
}
/**
* Generates Claude markdown from converted data
*/
function generateClaudeMarkdown(claudeFrontmatter, body) {
const fm = Object.entries(claudeFrontmatter)
.map(([key, value]) => {
if (Array.isArray(value)) {
return `${key}: [${value.map(v => `"${v}"`).join(', ')}]`;
}
return `${key}: "${value}"`;
})
.join('\n');
return `---\n${fm}\n---\n\n${body}`;
}
/**
* Recursively finds all .md files in a directory
*/
function findMarkdownFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
findMarkdownFiles(fullPath, files);
} else if (entry.name.endsWith('.md')) {
files.push(fullPath);
}
}
return files;
}
/**
* Processes a single agent file
*/
function processAgent(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const { frontmatter, body } = parseFrontmatter(content);
if (!frontmatter) {
console.log(`⚠️ Skipping ${filePath} (no frontmatter)`);
return;
}
const claudeFrontmatter = convertFrontmatter(frontmatter);
const claudeMarkdown = generateClaudeMarkdown(claudeFrontmatter, body);
// Determine output path
const relativePath = path.relative(SOURCE_DIR, filePath);
const outputPath = path.join(CLAUDE_AGENTS_DIR, relativePath);
// Ensure output directory exists
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, claudeMarkdown);
console.log(`✅ Converted: ${relativePath}`);
}
/**
* Main conversion function
*/
function convert() {
// Clean output directories
if (fs.existsSync(CLAUDE_AGENTS_DIR)) fs.rmSync(CLAUDE_AGENTS_DIR, { recursive: true });
if (fs.existsSync(CLAUDE_SKILLS_DIR)) fs.rmSync(CLAUDE_SKILLS_DIR, { recursive: true });
fs.mkdirSync(CLAUDE_AGENTS_DIR, { recursive: true });
fs.mkdirSync(path.join(CLAUDE_SKILLS_DIR, 'openagents-control-standards'), { recursive: true });
console.log('📦 Converting agents...\n');
// Process category agents
const agentFiles = findMarkdownFiles(SOURCE_DIR);
agentFiles.forEach(processAgent);
// Create default context-scout subagent
const contextScoutContent = `---
name: context-scout
description: Discovers and recommends OpenAgents Control context files using glob, read, and grep tools. Use when you need to find OpenAgents Control standards, guides, or domain knowledge in the .opencode/context directory.
tools: Read, Grep, Glob
model: haiku
permissionMode: plan
---
# ContextScout
You discover and recommend relevant OpenAgents Control context files from \`.opencode/context/\` based on the user's request.
## Your Process
1. Use \`Glob\` to find files in \`.opencode/context/\`.
2. Use \`Read\` or \`Grep\` to verify relevance.
3. Return file paths with brief descriptions.
`;
fs.writeFileSync(
path.join(CLAUDE_AGENTS_DIR, 'context-scout.md'),
contextScoutContent
);
// Create openagents-control-standards skill
const skillContent = `---
name: openagents-control-standards
description: Automatically triggers before any task to ensure OpenAgents Control standards and context are loaded. Use when the user asks to create, modify, or analyze anything in this repository.
---
# OpenAgents Control Standards Loader
Before proceeding with the user's request:
1. Call the \`context-scout\` subagent with the user's request to find relevant OpenAgents Control context files.
2. Read the returned "Critical" and "High" priority files.
3. Apply the OpenAgents Control standards found to your work.
`;
fs.writeFileSync(
path.join(CLAUDE_SKILLS_DIR, 'openagents-control-standards/SKILL.md'),
skillContent
);
console.log('\n✨ Conversion complete!');
console.log(` Output: ${OUTPUT_DIR}`);
}
// Run conversion
convert();