feat: OpenCode runtime support - translation layer and plugin - #46
Conversation
Cherry-picked from docs/opencode-support-trd and docs/opencode-support-prd branches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OC-S1-PKG-001 through OC-S1-PKG-006: Create packages/opencode/ with package.json, plugin.json, tsconfig.json; add generator script stubs in scripts/generate-opencode/; add generate:opencode npm script. 15 structure tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OC-S1-SK-001 through SK-006, TEST-001: SkillCopier discovers SKILL.md and REFERENCE.md files, injects frontmatter, copies to OpenCode paths. 24 tests passing. OC-S1-CMD-001 through CMD-010, TEST-002, TEST-003: CommandTranslator parses YAML commands and generates OpenCode Markdown format with argument mapping, workflow sections, and JSON config entries. 62 tests + 6 snapshots passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OC-S3-MF-001, MF-003 through MF-008, TEST-009: ManifestGenerator reads plugin manifests and generates unified opencode.json with command, skills, plugin, instructions, and permission sections. 35 tests. OC-S3-CLI-001 through CLI-009, TEST-010, TEST-011: Generator CLI with --dry-run, --verbose, --validate, --output-dir, --force flags. Full pipeline orchestration with incremental hash-based caching, progress reporting, and error collection. 31 tests. 167 total tests passing across 5 suites. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OC-S4-CI-001 through CI-003: GitHub Actions workflow for OpenCode generation validation, regression checks, and staleness detection. OC-S4-CI-005: OpenCode plugin.json validation in validate.yml. OC-S4-PKG-007: Already covered by existing glob validation. OC-S4-PKG-008: ensemble-opencode added to marketplace.json. OC-S4-DOC-004: CLAUDE.md updated with OpenCode references. OC-S4-TEST-019: Performance tests for generation timing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OC-S3-DIST-001/002/004-007, TEST-012: Plugin entry point with ensemble-info tool, npm publishing config, build step, local file install support, and version sync. 34 tests. 203 total tests passing across 7 suites. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
test review |
OpenCode Research DocumentDate: 2026-02-26
1. What OpenCode IsOpenCode is an open-source AI coding agent built for the terminal. It provides a TUI ArchitectureOpenCode uses a client/server architecture:
Internal Module Structure (
|
| Module | Purpose |
|---|---|
agent/ |
Agent definitions, prompt templates, agent configuration |
cli/ |
CLI entry point using yargs |
config/ |
Configuration loading, merging, validation (JSONC support) |
command/ |
Custom command system (slash commands) |
lsp/ |
Language Server Protocol client integration |
mcp/ |
Model Context Protocol client for external tools |
permission/ |
Permission system with granular allow/ask/deny rules |
plugin/ |
Plugin loader and hook dispatch |
provider/ |
LLM provider integrations (via Vercel AI SDK) |
server/ |
Hono HTTP server, SSE event streaming |
session/ |
Conversation session management |
skill/ |
Skill discovery and loading |
tool/ |
Built-in tool definitions (bash, edit, glob, grep, etc.) |
snapshot/ |
File snapshot/undo system |
worktree/ |
Git worktree detection |
Key Technical Choices
- LLM Integration: Uses the Vercel AI SDK (
aipackage) with provider-specific
adapters (@ai-sdk/anthropic,@ai-sdk/openai,@ai-sdk/google, etc.) - TUI Framework: OpenTUI + Solid.js (reactive terminal UI)
- Config Format: JSONC (JSON with comments) via
jsonc-parser - Package Manager: Bun workspaces (monorepo with
packages/directory)
Monorepo Packages
| Package | Description |
|---|---|
packages/opencode |
Core CLI and server |
packages/plugin |
Plugin SDK (@opencode-ai/plugin) |
packages/sdk |
Client SDK (@opencode-ai/sdk) |
packages/app |
Desktop application |
packages/console |
Web console |
packages/docs |
Documentation site |
packages/extensions |
IDE extensions (Zed) |
packages/web |
Marketing website |
packages/enterprise |
Enterprise features |
packages/ui |
Shared UI components |
2. Plugin/Extension System
OpenCode has a TypeScript-based plugin system that allows extending the agent with
custom tools, hooks, authentication providers, and event handlers.
Installing Plugins
Plugins are specified in the configuration file as npm package references:
{
"plugin": [
"my-opencode-plugin@1.0.0",
"file:///path/to/local/plugin"
]
}Plugins are installed automatically via Bun.install() at startup. Built-in plugins
(like opencode-anthropic-auth) are loaded by default and can be disabled with the
OPENCODE_DISABLE_DEFAULT_PLUGINS flag.
Plugins can also be placed in .opencode/plugins/ directories (project or global level).
Plugin API (@opencode-ai/plugin)
A plugin is an async function that receives a PluginInput context and returns a
Hooks object:
import type { Plugin } from "@opencode-ai/plugin"
import { tool } from "@opencode-ai/plugin"
export const MyPlugin: Plugin = async (ctx) => {
// ctx.client - OpenCode SDK client
// ctx.project - Current project info
// ctx.directory - Current working directory
// ctx.worktree - Git worktree root
// ctx.serverUrl - Local server URL
// ctx.$ - Bun shell instance
return {
// Custom tools available to the AI
tool: {
mytool: tool({
description: "This is a custom tool",
args: {
foo: tool.schema.string().describe("foo"),
},
async execute(args, context) {
// context.sessionID, context.agent, context.directory, etc.
return `Hello ${args.foo}!`
},
}),
},
// Lifecycle hooks (see full list below)
"tool.execute.before": async (input, output) => { /* ... */ },
"tool.execute.after": async (input, output) => { /* ... */ },
}
}Available Plugin Hooks
| Hook | Description |
|---|---|
event |
Called on any bus event |
config |
Called with loaded configuration |
tool |
Register custom tools (object map of tool definitions) |
auth |
Authentication provider (OAuth or API key flows) |
chat.message |
Called when a new message is received |
chat.params |
Modify LLM parameters (temperature, topP, etc.) |
chat.headers |
Modify HTTP headers sent to LLM providers |
permission.ask |
Intercept permission prompts |
command.execute.before |
Before a slash command executes |
tool.execute.before |
Before a tool executes (modify args) |
tool.execute.after |
After a tool executes (modify output) |
tool.definition |
Modify tool descriptions/parameters sent to LLM |
shell.env |
Inject environment variables into shell commands |
experimental.chat.messages.transform |
Transform message history |
experimental.chat.system.transform |
Transform system prompt |
experimental.session.compacting |
Customize session compaction |
experimental.text.complete |
Modify completed text |
Custom Commands
Commands are Markdown files stored in specific directories:
- User commands (
user:prefix):~/.config/opencode/commands/*.md - Project commands (
project:prefix):<PROJECT>/.opencode/commands/*.md - Subdirectory organization:
commands/git/commit.mdbecomesuser:git:commit
Commands support named arguments with $PLACEHOLDER syntax:
# Fix Issue $ISSUE_NUMBER
RUN gh issue view $ISSUE_NUMBER --json title,body,comments
READ README.mdCommands can also be defined in configuration:
{
"command": {
"my-command": {
"description": "My custom command",
"template": "Do something with $1",
"agent": "build",
"subtask": false
}
}
}Skills (Agent Knowledge)
Skills are Markdown files with frontmatter that provide domain knowledge to agents.
They follow the SKILL.md convention:
---
name: my-skill
description: Knowledge about X
---
# Skill Content
Detailed instructions and knowledge the agent can reference...Skill Discovery Locations (in order of precedence):
.claude/skills/**/SKILL.md(Claude Code compatibility).agents/skills/**/SKILL.md(generic agent directory).opencode/skill/**/SKILL.mdor.opencode/skills/**/SKILL.md- Additional paths from
config.skills.paths - Remote URLs from
config.skills.urls(fetches index.json + files) - Global:
~/.claude/skills/and~/.agents/skills/
Skills are automatically exposed as invocable commands (if no command with the same
name exists).
Agents (Custom Agent Definitions)
OpenCode ships with built-in agents and allows custom agent configuration:
Built-in Agents:
| Agent | Mode | Description |
|---|---|---|
build |
primary | Default full-access agent for development |
plan |
primary | Read-only agent for analysis and code exploration |
general |
subagent | General-purpose for complex searches and multistep tasks |
explore |
subagent | Fast agent specialized for codebase exploration |
compaction |
primary (hidden) | Session compaction/summarization |
title |
primary (hidden) | Session title generation |
summary |
primary (hidden) | Session summary generation |
Custom Agent Configuration (in opencode.json):
{
"agent": {
"build": {
"model": "anthropic/claude-sonnet-4-20250514",
"temperature": 0.7,
"steps": 50,
"prompt": "You are a specialized backend developer..."
},
"my-custom-agent": {
"name": "my-custom-agent",
"description": "Specialized agent for database work",
"mode": "primary",
"model": { "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" },
"permission": {
"bash": "ask",
"edit": "allow"
}
}
}
}Custom agents can also be defined as Markdown files in .opencode/agents/ directories.
3. Configuration Format and File Structure
Configuration File Locations
OpenCode uses JSONC format and searches these locations (low to high precedence):
- Remote
.well-known/opencode(organization defaults) - Global config:
~/.config/opencode/opencode.jsonoropencode.jsonc - Custom config:
$OPENCODE_CONFIGenvironment variable - Project config:
./opencode.jsonor./opencode.jsonc .opencode/directories:.opencode/opencode.json- Inline config:
$OPENCODE_CONFIG_CONTENT - Enterprise managed:
/Library/Application Support/opencode/(highest priority)
Full Configuration Schema
{
"$schema": "https://opencode.ai/config.json",
"logLevel": "info",
"model": "anthropic/claude-sonnet-4-20250514",
"small_model": "anthropic/claude-3-5-haiku-20241022",
"default_agent": "build",
"username": "custom-name",
"snapshot": true,
"share": "manual",
"autoupdate": true,
"server": { },
"agent": {
"build": {
"model": { "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" },
"temperature": 0.7,
"topP": 0.9,
"steps": 50,
"prompt": "Custom system prompt addition"
},
"plan": { },
"general": { },
"explore": { }
},
"command": {
"deploy": {
"description": "Deploy the application",
"template": "Deploy to $1 environment",
"agent": "build",
"subtask": false
}
},
"skills": {
"paths": ["./custom-skills", "~/shared-skills"],
"urls": ["https://skills.example.com/"]
},
"plugin": [
"my-plugin@1.0.0",
"file:///local/plugin"
],
"instructions": [
"path/to/instructions.md",
"another/instructions.md"
],
"permission": {
"bash": "ask",
"edit": "allow",
"read": {
"*": "allow",
"*.env": "ask"
}
},
"disabled_providers": ["groq"],
"enabled_providers": ["anthropic", "openai"],
"mcpServers": {
"example-stdio": {
"type": "stdio",
"command": "path/to/mcp-server",
"args": ["--flag"],
"env": { "KEY": "value" }
},
"example-sse": {
"type": "sse",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer token" }
}
},
"lsp": {
"typescript": {
"disabled": false,
"command": "typescript-language-server",
"args": ["--stdio"]
},
"go": {
"disabled": false,
"command": "gopls"
}
},
"shell": {
"path": "/bin/zsh",
"args": ["-l"]
},
"watcher": {
"ignore": ["node_modules", ".git"]
},
"autoCompact": true
}Project Directory Structure
project/
|-- opencode.json # Project-level config
|-- .opencode/
| |-- opencode.json # Additional config (merged)
| |-- commands/
| | |-- deploy.md # project:deploy command
| | |-- git/
| | | +-- commit.md # project:git:commit command
| |-- agents/
| | +-- db-expert.md # Custom agent definition
| |-- plugins/
| | +-- my-plugin/ # Local plugin directory
| |-- skill/
| | +-- react/
| | +-- SKILL.md # React skill
| +-- plans/ # Plan mode output
+-- AGENTS.md # Project context file (like CLAUDE.md)
Global Configuration
~/.config/opencode/
|-- opencode.json # Global config
|-- commands/
| +-- prime-context.md # user:prime-context command
+-- skills/
+-- general/
+-- SKILL.md
4. Comparison with Claude Code Extensibility
| Feature | OpenCode | Claude Code (Ensemble) |
|---|---|---|
| Open Source | Yes (MIT) | Proprietary CLI, open source plugin ecosystem |
| Provider Lock-in | None - supports 15+ providers | Anthropic only |
| Plugin Format | TypeScript/npm packages | JSON manifests + YAML agents + Markdown commands |
| Plugin SDK | @opencode-ai/plugin (typed, async) |
No formal SDK; file-based conventions |
| Custom Tools | TypeScript functions via plugin API | Not directly supported (MCP servers instead) |
| Hook System | 15+ typed hook points (before/after) | PreToolUse / PostToolUse hooks (shell commands) |
| Agent Definition | JSON config or Markdown files | YAML files with frontmatter |
| Skill System | SKILL.md with frontmatter + remote URLs | SKILL.md and REFERENCE.md files |
| Custom Commands | Markdown files with $ARG placeholders |
YAML/Markdown command definitions |
| MCP Support | Yes (stdio + SSE) | Yes (via MCP servers in config) |
| LSP Integration | Built-in (diagnostics exposed to AI) | Not built-in |
| Permission System | Granular per-tool with glob patterns | Allowlist-based (.claude/settings.json) |
| Configuration | JSONC with 7-level precedence | JSON settings + YAML manifests |
| Agent Delegation | @general inline + agent tool |
Task tool with subagent_type |
| Session Management | SQLite-backed, auto-compact | Conversation-based |
| Desktop App | Yes (beta) | No |
| Web Interface | Yes (console) | No |
| TUI Framework | Solid.js + OpenTUI (custom) | Ink (React-based) |
| Runtime | Bun | Node.js |
| Architecture | Client/server (separable) | Monolithic CLI |
| Skill Discovery | Cross-compatible (.claude/, .agents/) | Own directories only |
Key Differences in Extensibility
-
Plugin Power: OpenCode plugins are full TypeScript programs that can intercept
and modify virtually every aspect of the agent lifecycle. Ensemble plugins are
declarative (YAML/JSON manifests) with shell-based hooks. -
Tool Creation: OpenCode allows defining custom tools directly in plugins with
Zod schemas. Claude Code relies on MCP servers for custom tool creation. -
Agent Customization: OpenCode allows deep agent customization (model, temperature,
permissions, prompts) via config. Ensemble defines agents as YAML files with
mission/behavior documentation. -
Hook Granularity: OpenCode has 15+ specific hook points (chat.params, shell.env,
permission.ask, etc.). Ensemble has 2 hook points (PreToolUse, PostToolUse). -
Cross-Compatibility: OpenCode explicitly supports
.claude/skills/directories,
making it compatible with Claude Code skill definitions.
5. SDK and API for Building Plugins/Extensions
Plugin SDK (@opencode-ai/plugin)
Published as @opencode-ai/plugin on npm. Provides:
Plugintype: The main plugin function signaturetool()helper: Creates typed tool definitions with Zod schemasPluginInput: Context object with client, project info, shell accessHooksinterface: All available hook points with typed signaturesToolContext: Runtime context for tool execution (sessionID, abort signal, etc.)AuthHook: OAuth and API key authentication provider interface
Client SDK (@opencode-ai/sdk)
Published as @opencode-ai/sdk. Provides a typed client for the OpenCode server API:
import { createOpencodeClient } from "@opencode-ai/sdk"
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
directory: "/path/to/project",
})The SDK is auto-generated from an OpenAPI spec (packages/sdk/openapi.json).
Server API
OpenCode runs a local HTTP server (default port 4096) with:
- REST API endpoints for sessions, messages, tools
- SSE (Server-Sent Events) for real-time streaming
- OpenAPI specification available at
https://opencode.ai/openapi.json
Building a Plugin (Step-by-Step)
- Create a new npm package:
mkdir my-opencode-plugin && cd my-opencode-plugin
bun init
bun add @opencode-ai/plugin- Define the plugin (
src/index.ts):
import type { Plugin } from "@opencode-ai/plugin"
import { tool } from "@opencode-ai/plugin"
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
"my-tool": tool({
description: "Does something useful",
args: {
input: tool.schema.string().describe("Input text"),
},
async execute(args, context) {
// context.sessionID - current session
// context.directory - project directory
// context.abort - AbortSignal for cancellation
// context.metadata() - set tool call metadata
// context.ask() - request permission
return `Processed: ${args.input}`
},
}),
},
"tool.execute.before": async (input, output) => {
// Intercept any tool call before execution
console.log(`Tool ${input.tool} called`)
},
"shell.env": async (input, output) => {
// Inject environment variables into shell commands
output.env["MY_CUSTOM_VAR"] = "value"
},
}
}- Register in project config (
opencode.json):
{
"plugin": ["file:///path/to/my-opencode-plugin"]
}Or publish to npm and reference by package name:
{
"plugin": ["my-opencode-plugin@1.0.0"]
}Tool Definition API
import { tool } from "@opencode-ai/plugin"
const myTool = tool({
description: "Human-readable description for the LLM",
args: {
// Uses Zod schemas
filePath: tool.schema.string().describe("Path to the file"),
lines: tool.schema.number().optional().describe("Number of lines"),
options: tool.schema.object({
recursive: tool.schema.boolean(),
}).optional(),
},
async execute(args, context) {
// args is fully typed from the schema
// Must return a string (the tool output shown to the LLM)
return "result"
},
})Authentication Plugin API
Plugins can provide authentication for custom LLM providers:
export const MyAuthPlugin: Plugin = async (ctx) => {
return {
auth: {
provider: "my-provider",
methods: [
{
type: "api",
label: "API Key",
prompts: [
{
type: "text",
key: "apiKey",
message: "Enter your API key",
placeholder: "sk-...",
},
],
async authorize(inputs) {
return {
type: "success",
key: inputs.apiKey,
provider: "my-provider",
}
},
},
{
type: "oauth",
label: "Login with MyProvider",
async authorize() {
return {
url: "https://my-provider.com/oauth",
instructions: "Complete login in your browser",
method: "auto",
async callback() {
// Exchange code for tokens
return {
type: "success",
refresh: "refresh-token",
access: "access-token",
expires: Date.now() + 3600000,
}
},
}
},
},
],
},
}
}6. Relevance to Ensemble
Opportunities
-
Cross-Compatibility: OpenCode already scans
.claude/skills/directories.
Ensemble skills defined asSKILL.mdfiles would be automatically discovered
by OpenCode users. -
Plugin Inspiration: OpenCode's typed hook system with 15+ hook points is
more granular than Ensemble's PreToolUse/PostToolUse. Consider expanding
Ensemble's hook points. -
Client/Server Pattern: OpenCode's separable client/server architecture
enables remote driving (mobile app, web). This could inspire Ensemble features. -
Skill URLs: OpenCode supports remote skill repositories via URL + index.json.
Ensemble could adopt a similar pattern for distributing skills. -
Permission Granularity: OpenCode's glob-pattern permissions per tool
(e.g.,read: { "*.env": "ask" }) are more granular than Ensemble's allowlist.
Competitive Positioning
- OpenCode is provider-agnostic; Ensemble is Claude-native (strength for Anthropic users)
- OpenCode has richer programmatic extensibility; Ensemble has richer declarative agent mesh
- OpenCode's 28-agent mesh equivalent would require custom agent configs in JSON
- Ensemble's multi-tier plugin architecture (core/workflow/framework/testing) has no
direct parallel in OpenCode
Sources
- GitHub Repository: https://github.com/anomalyco/opencode (dev branch, accessed 2026-02-26)
- Plugin SDK source:
packages/plugin/src/index.ts,tool.ts,example.ts - Core config:
packages/opencode/src/config/config.ts - Agent system:
packages/opencode/src/agent/agent.ts - Skill system:
packages/opencode/src/skill/skill.ts,discovery.ts - Command system:
packages/opencode/src/command/index.ts - Plugin loader:
packages/opencode/src/plugin/index.ts - Documentation site: https://opencode.ai/docs
- npm package:
opencode-ai(CLI),@opencode-ai/plugin(SDK),@opencode-ai/sdk(client)
|
PR Review: See detailed analysis in subsequent comment |
OC-S2-HK-001 through HK-007, TEST-007, TEST-008, DIST-003: HookBridgeGenerator discovers hooks.json files and maps PreToolUse/PostToolUse to OpenCode's tool.execute.before/after. Supports matcher patterns (exact, wildcard, regex), env var bridging, and blocking behavior. Plugin entry point updated with hook wiring. 62 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR Review: feat: OpenCode runtime support - translation layer and pluginGreat work on Sprint 1! The 203-test suite and clean pipeline architecture give confidence. Here is my detailed feedback. Architecture and DesignDual JS/TS file strategy is confusing The repo ships paired .js (runtime) and .ts (stub/type) files for the same modules without a build pipeline that compiles TS to JS:
Recommendation: Either commit to TypeScript (add a proper build step) or remove the .ts stubs and add them in Sprint 2. They currently add confusion without value. Custom YAML parser duplicates an existing dependency packages/opencode/lib/skill-copier.js implements parseFrontmatter/parseSimpleYaml/stringifyFrontmatter, yet packages/opencode/package.json already lists gray-matter@^4.0.3 as a dependency. This reinvents the wheel while paying the dependency cost. The custom parser has correctness gaps described below. Recommend using gray-matter directly. Potential BugsBug 1: parseFrontmatter fails on CRLF line endings In packages/opencode/lib/skill-copier.js, the startsWith check against the LF-terminated frontmatter delimiter fails on Windows CRLF files. Also, const trimmed = input; -- the variable is named trimmed but no trimming is done. A leading BOM will cause silent parse failure. gray-matter handles all these cases. Bug 2: stringifyFrontmatter produces invalid YAML for embedded double-quotes The function only wraps values containing colon, hash, braces in double-quotes. A description with embedded double-quotes will produce malformed YAML. gray-matter/js-yaml handle escaping correctly. Bug 3: usesArguments uses JSON serialization for text detection JSON.stringify serializes the entire parsed command object on every call and may escape content differently than raw YAML text. A targeted check of mission.summary, step descriptions, and action arrays is more reliable. Bug 4: AGENTS.md unconditionally added to instructions generateInstructionsSection in manifest-generator.js always appends AGENTS.md to the instructions array even when that file does not exist. This file is not created until a later pipeline stage (not Sprint 1). If OpenCode validates instruction paths eagerly, this will fail. Recommend checking file existence first. Bug 5: Incomplete ARGUMENT_PLACEHOLDERS map The PRD mentions 15+ commands but only 8 are mapped in command-translator.js. Unmapped commands silently fall through to a generic dollar-SLUG_INPUT fallback. For example, ensemble:playwright-test would produce dollar-PLAYWRIGHT_TEST_INPUT. The test suite does not exercise this fallback against the full real command set. CI/CD IssuesStaleness check assumes dist/opencode/ is committed In .github/workflows/opencode-generate.yml the staleness check uses git diff after regenerating output. If dist/opencode/ is in .gitignore, git will never report differences and the check is a no-op. Please confirm whether generated output is committed or gitignored and update accordingly. Also git checkout -- . resets the entire worktree -- scoping it to dist/ would be safer. PerformanceThe hash-based incremental caching is well-designed. One note: the skill cache uses a single aggregated hash (_skillHash), so any single skill change causes a full re-copy of all skills. Acceptable for 10 skills but worth revisiting at scale. Minor ItemsHardcoded model version strings will go stale: manifest-generator.js hardcodes anthropic/claude-sonnet-4-20250514 as the default model, and command-translator.js has the same in MODEL_MAP. Centralizing in a MODEL_DEFAULTS config file would make updates easier. Unused gray-matter dependency: packages/opencode/package.json lists both gray-matter and js-yaml but the JS implementation never calls gray-matter (the custom parser replaced it). Remove it or use it. marketplace.json category: The new ensemble-opencode entry uses category: core. Consider runtime or integration to distinguish it from core infrastructure packages. Unchecked PR checklist: The npm run validate passes item is unchecked. Please confirm before merge since validate.yml now includes OpenCode plugin.json validation. What Is Working Well
SummaryThe core translation logic is solid. Main issues to address:
Reviewed with Claude Code |
Implement OC-S2-AGT-001 through OC-S2-AGT-014 and OC-S2-TEST-006: - AgentTranslator class: YAML agents → OpenCode JSON config + Markdown - Tool permission mapping (Read/Write/Edit → file, Bash → shell, etc.) - Mode classification (plan, bypassPermissions, default) - Routing prompt generation with @agent-name references - Category metadata tags for agent organization - 5 representative agent fixtures (orchestrator, developer, quality, workflow, utility) - 81 unit tests + 10 snapshot tests Replaces TypeScript stub with full JS implementation and .d.ts declarations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Integrate AgentTranslator into generate-opencode CLI (step 3) - Agent config block now flows into opencode.json manifest (MF-002) - Update package-structure test to accept .js/.d.ts module variants - Update generator-cli test: AgentTranslator is no longer deferred - Mark all OC-S2-AGT-* and OC-S2-TEST-004/005/006 tasks as done in TRD - Mark OC-S3-MF-002 as done (agent block generation) All 346 tests pass across 9 test suites (16 snapshots). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR Review: feat: OpenCode runtime support - translation layer and pluginThis is an impressive, well-scoped PR with solid test coverage (203 tests, 6 snapshots). The architecture — a build-time translation layer producing OpenCode-compatible artifacts from Ensemble's existing YAML/JSON/Markdown sources — is sound. The deferred scope for Sprint 2 (agent mesh, hook bridge) is appropriate. Below are my findings, organized by severity. 🔴 Issues (Should Fix Before Merge)1. ReDoS / Regex injection in The const regex = new RegExp('^(?:' + matcher + ')$');A maliciously crafted Recommendation: Either use a safe regex escape library, or restrict matchers to an allow-list of patterns (exact name, 2.
// Current — blocks event loop despite async declaration
async function defaultExecutor(command, env) {
const stdout = execSync(command, { timeout: 30000, ... });
...
}Replace 3. Incorrect "types": "src/index.ts"The 4. Unused
🟡 Warnings (Strong Suggestions)5. Committed compiled JS alongside TypeScript source Both Options:
6. Hardcoded capability counts in const ENSEMBLE_META = { agents: 28, commands: 15, skills: 10, ... };These will silently go stale as the ecosystem grows. The 7. Code duplication: These helpers are duplicated verbatim in:
Extract to a shared 8. Silent error swallowing in } catch (_err) {
// Skip files that cannot be parsed
}And the outer: } catch (_err) {
// If discovery fails, return empty hooks
}Both swallow errors completely. Hook registration failures should at minimum log a warning so operators know hooks are not running. In a plugin context, silent failures are hard to debug. 9. CI staleness check logic is fragile ( The staleness check resets all changes with 10. Incremental 2-second performance test is environment-sensitive expect(result.elapsed).toBeLessThan(2000);This assertion is fragile on CI runners under load. The hash check is lightweight, but spawning a child process, loading 🟢 Observations / Minor Points11. The npm package is 12. The CLAUDE.md lists 13. Looking at the implementation, Positives Worth Calling Out
Overall this is close to merge-ready. The |
- Add ensemble-opencode to Architecture listing under Runtime Translation - Add OpenCode Support section with generator CLI usage, installation instructions, and output directory structure
PR Review: feat: OpenCode runtime support - translation layer and pluginGreat scope and well-structured PR! The translation layer concept is sound, the TRD coverage is thorough, and 203 passing tests with snapshots is a solid baseline. Below are the items that should be addressed before merging, roughly prioritized. 🔴 High Priority1. Dual
|
| Priority | Issue | Suggested Action |
|---|---|---|
| 🔴 | Dual .js/.ts source files |
Pick one canonical format |
| 🔴 | dist/opencode/ staleness commits |
Add to .gitignore or document strategy |
| 🔴 | Unsafe RegExp from matcher |
Escape/validate matcher values |
| 🔴 | execSync without path validation |
Validate command path within pluginRoot |
| 🟡 | README documents deferred features | Update to reflect current implementation state |
| 🟡 | Version 5.3.0 vs ecosystem 5.1.0 |
Align versioning |
| 🟡 | plugin.json missing standard fields |
Add or comment on omission |
| 🟡 | types → raw .ts file |
Use .d.ts or remove |
| 🟡 | --legacy-peer-deps in CI |
Address root dependency conflict |
Happy to discuss any of these—the overall direction is excellent and most of these are straightforward to fix.
🤖 Reviewed with Claude Code
Resolve 4 of 5 open questions in TRD v1.0.0: - br --json support confirmed, updated all br list calls to use --json - br create --json for bead ID capture - br comment add replaces removed comment functionality - NTM naming uses TRD_SLUG-track-N pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…wheel instructions Implement TRD for beads br/bv migration with 35 tasks across 4 phases: - Phase 1: Foundation (br/bv CLI setup, wheel-based installation) - Phase 2: Core Migration (command migration from bd to br/bv) - Phase 3: Integration (workflow integration and validation) - Phase 4: Cleanup (bd removal and documentation updates) Includes YAML command definition, generated markdown, and PRD. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 35 TRD tasks marked complete — tracked via br beads with full dependency graph (51 edges, 42 nodes). Implementation completed in commit e30f297. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… PRD - Add mandatory @code-reviewer step before task closure (max 2 review rounds) - Add --description to br create for epic/story/task beads with full TRD context - Add closing comments with structured work summaries from agents - Add @tech-lead-orchestrator to routing table for architecture/design tasks - Expand keyword routing for @documentation-specialist - Add PRD for team-based execution model (role-based multi-agent collaboration) - Bump implement-trd-beads to v2.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR #46 Code Review: feat: OpenCode runtime supportSummaryThis is a substantial PR (+15,873/-1,394 lines) adding OpenCode runtime support to the Ensemble ecosystem. The overall approach is well-architected and the test suite is thorough for individual components (346 tests, 16 snapshots across 9 suites). However, there are several concrete issues worth addressing before merge. 🔴 Critical Issues1. Staleness check in CI will always pass vacuously (
Either commit 2. Step 5 inlines a simplified manifest rather than calling
Tests validate the ManifestGenerator in isolation but do not catch that the pipeline bypasses it entirely. 3. Step 4 is marked "deferred - Sprint 2" and skipped, yet the PR includes a full 🟡 Bugs4. Unused variable
5. Model ID inconsistency between translators
These should be identical. This produces inconsistent OpenCode configs depending on whether the model hint originates from a command or agent YAML. 6. Hook
🟡 Design / Architecture Concerns7. Runtime artifacts committed to
8. Dual TypeScript stub + compiled JS pattern creates maintenance risk Multiple files exist in both
9.
10. Per CLAUDE.md's Plugin Manifest spec and the schema used elsewhere, 🟡 Code Quality11.
12.
13. Silent error swallowing in the plugin entry point ( } catch (_err) {
// Skip files that cannot be parsed
}If hook discovery fails for any reason (e.g., incorrect monorepo root path), the plugin loads with zero hooks and no diagnostic output. At minimum, add a 🟡 Test Coverage Gaps14. No integration test verifying Generator-CLI tests validate the CLI as a black box but don't assert on 15. Performance tests have no quality assertions
Nits
VerdictThe translation layer concept is well-architected and the per-component test suite is solid. Before merging, the following should be addressed:
Review generated by Claude Code (claude-sonnet-4-6) |
feat: OpenCode runtime support - translation layer and plugin
Summary
Adds OpenCode runtime support to the Ensemble plugin ecosystem via a translation layer that converts Ensemble YAML/JSON/Markdown artifacts to OpenCode-compatible formats.
What's Included
New Package:
packages/opencode/(ensemble-opencode)ensemble-infotool for capability discovery@opencode-ai/pluginpeer dependencyfile:///pathTranslation Layer:
scripts/generate-opencode/opencode.jsonfrom plugin manifestsnpm run generate:opencodewith --dry-run, --verbose, --validate, --forceCI/CD
.github/workflows/opencode-generate.yml- Validation, regression, staleness checksvalidate.ymlwith OpenCode plugin.json validationDocumentation
CLAUDE.mdwith OpenCode referencesmarketplace.jsonwith ensemble-opencode entryTest Results
Deferred (Sprint 2 - Agent Mesh & Hook Bridge)
The following are intentionally deferred as the agent mesh delegation system is not yet needed:
TRD Reference
docs/PRD/opencode-support.mddocs/TRD/opencode-support.mdTest plan
npm run validatepassesnpm run generate:opencode --dry-runproduces expected output🤖 Generated with Claude Code