PLATFORM_SPEC.md — OpenCode (sst/opencode)¶
New to gald3r on this platform? Start at Use gald3r with OpenCode for a short overview before the full breakdown below.
OpenCode is an open-source, terminal-first AI coding agent from the SST team (opencode binary,
repo sst/opencode, docs https://opencode.ai/docs). It runs as a TUI with multi-provider model
support and a JSON/JSONC config (opencode.json). As of mid-2026 OpenCode natively supports all
six gald3r-relevant extension primitives — custom commands, rules/instructions, agents
(primary + subagents), Agent Skills, lifecycle hooks (via plugins), and MCP. Critically for gald3r,
OpenCode reads AGENTS.md (with CLAUDE.md fallback) and natively discovers skills from
.claude/skills/ and .agents/skills/ in addition to .opencode/skills/, so gald3r's
Claude-Code SKILL.md packages, AGENTS.md, and command/agent assets are largely drop-in reusable
on OpenCode.
Authoring path: UPDATE. Verified 2026-06-02 against https://opencode.ai/docs (see
Verification Evidence). This supersedes the prior spec (last_doc_scan: 2026-05-26,
status: ⚠️) which conservatively marked every mechanism partial because it was doc-scan-only and
not install-tested — the crawl assessment confirms all six primitives are NATIVE in OpenCode.
Instruction-file convention: OpenCode reads
AGENTS.mdas its primary instruction file (the open agents standard), falling back toCLAUDE.md. If bothAGENTS.mdandCLAUDE.mdexist locally, onlyAGENTS.mdis used. It also reads the global~/.config/opencode/AGENTS.mdand the Claude Code file~/.claude/CLAUDE.md(unless disabled). This differs from Claude Code (which readsCLAUDE.md, notAGENTS.md).Hooks caveat: OpenCode's lifecycle hooks are in-process JS/TS plugins (event handlers), not drop-in shell scripts and not a JSON wiring file. Lifecycle coverage is broad, but there is no first-class git pre-commit / pre-push event and gald3r's Python
g-hk-*.pyhooks ( port; NOT PowerShell) must be shelled out from a JS/TS wrapper rather than registered directly. This is the single largest friction point (see §6 + §9).
1. Folder Hierarchy¶
OpenCode reads a project-root .opencode/ directory plus a root-level opencode.json config.
Subdirectory names use plural forms (singular still accepted for backwards compatibility):
<project-root>/
├── AGENTS.md / CLAUDE.md ← instruction files OpenCode reads (AGENTS.md wins if both exist)
├── opencode.json (or .jsonc) ← ROOT config — NOT inside .opencode/ (mcp + instructions + plugin)
└── .opencode/
├── commands/ *.md ← custom commands (markdown + YAML frontmatter)
├── agents/ *.md ← primary agents + subagents (markdown, or opencode.json inline)
├── skills/ <name>/SKILL.md ← Agent Skills (loaded on-demand via native `skill` tool)
├── plugins/ *.{js,ts} ← JS/TS plugins == OpenCode's hook mechanism
├── modes/ ← agent modes (opencode concept; no gald3r analog)
├── tools/ ← custom tool definitions (opencode concept)
└── themes/ ← TUI themes
OpenCode also discovers .claude/skills/<name>/SKILL.md and .agents/skills/<name>/SKILL.md
(workspace or ~/), reads ~/.claude/CLAUDE.md, and walks project-local paths up to the git worktree
root. gald3r's .claude/-style skill trees and AGENTS.md/CLAUDE.md therefore work on OpenCode
with no OpenCode-specific port for skills + rules.
Global equivalents live under ~/.config/opencode/ (opencode.json, AGENTS.md, commands/,
agents/, skills/, plugins/). The 2026 config update loads opencode.json from the opened
location upward.
gald3r writes: .opencode/commands/, .opencode/agents/, opencode.json, and (optionally)
.opencode/plugins/ for a hook shim; for maximum reuse, gald3r's .claude/skills/ tree +
AGENTS.md/CLAUDE.md are read as-is.
OpenCode owns: the .opencode/ namespace, the opencode.json schema, plugin loading, skill
discovery, and the TUI.
2. AI Instruction File¶
OpenCode's primary instruction file is AGENTS.md (project root), the open agents standard —
the equivalent of Cursor's rules-as-context. Load order (verified):
- Local files traversing up from the current directory —
AGENTS.md, thenCLAUDE.md. If both exist locally, onlyAGENTS.mdis used. - Global file at
~/.config/opencode/AGENTS.md(applies across all sessions). -
Claude Code file at
~/.claude/CLAUDE.md(unless disabled). -
Generated via: the OpenCode
/initcommand scans the repo and writesAGENTS.md. - Additional files: the
instructionsarray inopencode.jsonregisters extra instruction files (paths, glob patterns, and remote URLs):"instructions": ["CONTRIBUTING.md", "docs/guidelines.md"].
gald3r generates/merges AGENTS.md / CLAUDE.md via the setup + parity pipeline; these files
are personalized per user and gitignored (g-rl-02). Because OpenCode reads CLAUDE.md, gald3r's
existing CLAUDE.md already delivers rules content to OpenCode with no extra work.
Source: https://opencode.ai/docs/rules/
3. Agents Support — ✅ NATIVE¶
- Primary agents + subagents: markdown agent files (named after the agent, e.g.
review.md→ thereviewagent) in.opencode/agents/(or~/.config/opencode/agents/), or inline inopencode.json.opencode agent createscaffolds one interactively. - Invocation: subagents manually invoked via
@mention(e.g.@general); auto-invoked via the Task tool. Built-in primaries: Build, Plan. Built-in subagents: General, Explore, Scout. - gald3r
g-agnt-*definitions map directly to OpenCode agent files. - Source: https://opencode.ai/docs/agents/
4. Skills Support — ✅ NATIVE¶
- Agent Skills (
SKILL.md+ YAML frontmatter) loaded on-demand via the nativeskilltool — agents see available skills and load the full content only when needed. Required frontmatter:name+description(optionallicense,compatibility,metadata). - Discovery (multi-path):
.opencode/skills/<name>/SKILL.md,.claude/skills/<name>/SKILL.md,.agents/skills/<name>/SKILL.md(project), plus~/.config/opencode/skills/,~/.claude/skills/,~/.agents/skills/(global). Project-local paths are walked up to the git worktree root. - gald3r
g-skl-*/SKILL.mdload natively — including straight from.claude/skills/. gald3r's extra frontmatter (subsystem_memberships,token_budget) lands under the tolerated/metadataspace. No conversion required. - Source: https://opencode.ai/docs/skills/
5. Commands / Workflows — ✅ NATIVE¶
- Custom commands: markdown files in
.opencode/commands/(also~/.config/opencode/commands/), with YAML frontmatter. Prompts support the$ARGUMENTSplaceholder, positional$1/$2/$3, and!commandbash injection. Frontmatter fields:description,agent,model,subtask. - gald3r
@g-*//g-*commands map directly to.opencode/commands/. - Source: https://opencode.ai/docs/commands/
6. Hooks System — ✅ NATIVE (JS/TS plugins)¶
- Lifecycle hooks are implemented as plugins: JS/TS modules that export a function returning a hooks object and subscribe to lifecycle events. Auto-loaded at startup from the plugin directory or from npm. A plugin's context exposes project info, cwd, git worktree path, an SDK client, and Bun's shell API.
- Location:
.opencode/plugins/(project),~/.config/opencode/plugins/(global), or npm packages registered via thepluginoption inopencode.json. - Available events (20):
tool.execute.before,tool.execute.after,command.executed,file.edited,file.watcher.updated,session.created,session.idle,session.compacted,session.deleted,session.updated,permission.asked,permission.replied,shell.env,lsp.client.diagnostics,todo.updated,tui.prompt.append,tui.command.execute,tui.toast.show,server.connected,installation.updated. - Mapping:
tool.execute.before/aftergives pre-tool gating (PreToolUse-equivalent);file.edited/file.watcher.updatedcovers file-watch;session.createdcovers session-start. - Two dispatch surfaces, not one uniform key set (correction). Live-verified against
OpenCode's real, on-disk TypeScript source (
packages/plugin/src/index.ts'sexport interface Hooks {...}, https://github.com/sst/opencode/blob/dev/packages/plugin/src/index.ts): onlytool.execute.before/tool.execute.after(and a handful of chat/permission/experimental events not relevant to gald3r) are real top-levelHooksobject keys, each called with TWO positional arguments(input, output). The remaining events in the 20-event list above — includingsession.created/session.deleted/session.idle— are NOTHookskeys at all; they are variants of the generic busEventunion (packages/sdk/js/src/gen/types.gen.ts) delivered exclusively through a singleevent?: (input: {event: Event}) => Promise<void>hook, discriminated byevent.type. A plugin object key literally named"session.created"is never invoked by OpenCode's real plugin loader. - gald3r friction (largest gap): hooks are event-driven JS/TS plugins, not drop-in shell
scripts and not a JSON wiring file. gald3r ships hooks as Python
g-hk-*.pyscripts ( port), which do not run natively as OpenCode plugins. A thin JS/TS plugin ( /, corrected:gald3r-hooks-plugin.ts,spawnSync("python", …)) shells out to the six canonicalg-hk-on-<event>.pyentrypoints —tool.execute.before/tool.execute.afterregistered directly (realHookskeys),session.created/session.deleted/session.idlerouted through the single genericeventhook'sswitch (event.type)(they are notHookskeys). Additionally there is no first-class git pre-commit / pre-push event — commit-gate enforcement must be wired viacommand.executedor external git hooks. ✅ for native lifecycle coverage; gald3r's hook payload is not portable as-is (see §9). - Source: https://opencode.ai/docs/plugins/, https://github.com/sst/opencode/blob/dev/packages/plugin/src/index.ts
7. Rules / Memory — ✅ NATIVE¶
- Rules/memory == the
AGENTS.mdinstruction file (§2) plus theinstructionsconfig array. The whole ofAGENTS.md(and referencedinstructionsfiles) is injected into the LLM context at startup — effectively one always-apply document. There is no separatememoryfile distinct from rules: persistent instructions are the singleAGENTS.md/CLAUDE.mdconvention (good parity, but no auto-updating memory store). - There is no
.mdcper-file glob-scoped rule engine like Cursor's.cursor/rules/*.mdc. gald3r's manyg-rl-*rules consolidate intoAGENTS.md(or are referenced viainstructions); rule content transfers, Cursor's per-rule glob scoping does not. - CLAUDE.md fallback: because OpenCode reads
CLAUDE.md, gald3r's existingCLAUDE.mdalready delivers rules to OpenCode with no extra work. - Source: https://opencode.ai/docs/rules/
8. MCP Support — ✅ NATIVE¶
- MCP servers defined under the
mcpfield inopencode.json(root) or global~/.config/opencode/opencode.json. Supports typelocal(spawned command, stdio) and typeremote(URL). 2026 updates added MCP OAuth callback-port config and scoped client metadata. - Config supports
{env:VAR}and{file:path}substitution — inject API keys/secrets without inlining them. - gald3r's MCP server block drops into
opencode.json -> mcp. - Source: https://opencode.ai/docs/mcp-servers/
9. Other Extensibility + Known Gaps vs. Cursor Reference¶
Other extensibility (OpenCode bonuses, no Cursor analog):
- Custom Tools — functions the LLM can call during conversations, alongside built-in
read/write/bash tools (https://opencode.ai/docs/custom-tools/).
- Modes — built-in Plan mode (read-only / suggest) vs Build mode (full access) as
primary-agent permission profiles.
- SDK — official @opencode-ai/sdk + in-process HTTP server; sessions can store custom metadata
via API/SDK (2026 update).
Gaps / friction vs. Cursor reference:
1. Hooks are JS/TS plugins, not Python-native (✅ native, but not portable). gald3r g-hk-*.py
require a JS/TS plugin shim (: gald3r-hooks-plugin.ts) that shells
out via Node's child_process.spawnSync("python", …). No first-class git pre-commit / pre-push
event — wire via command.executed or external git hooks.
2. No .mdc glob-scoped rule engine (rule content transfers via AGENTS.md/CLAUDE.md; per-rule
alwaysApply/globs scoping does not).
3. No separately named memory store distinct from the AGENTS.md/CLAUDE.md instruction file
(good parity, but no auto-updating memory).
4. Decision-tree placement: OpenCode's plugin (JS/TS) hook format and opencode.json schema are
correctly classified platform-specific — they live in the OpenCode tree
(.gald3r_sys/platforms/.opencode/), not common .gald3r_sys/. The shared .claude/skills/ +
.agents/skills/ discovery paths and AGENTS.md/CLAUDE.md reads are where OpenCode reuses
common gald3r output directly.
Reuse note (important): because OpenCode reads AGENTS.md/CLAUDE.md and discovers .claude/
+ .agents/ skill trees, gald3r's Claude-Code SKILL.md packages, instruction files, and
command/agent assets are largely drop-in reusable — the cheapest high-parity path is to ship the
gald3r .claude/skills/ tree + AGENTS.md, then add a thin JS/TS plugin shim only for the hooks.
Hook System¶
- Type: native (JS/TS plugins, not a JSON wiring file, not
.ps1) - Config file / location:
.opencode/plugins/(project) +~/.config/opencode/plugins/(global), auto-loaded at startup; or npm packages viaopencode.jsonpluginoption - Events available (20):
tool.execute.before,tool.execute.after,command.executed,file.edited,file.watcher.updated,session.created,session.idle,session.compacted,session.deleted,session.updated,permission.asked,permission.replied,shell.env,lsp.client.diagnostics,todo.updated,tui.prompt.append,tui.command.execute,tui.toast.show,server.connected,installation.updated - Event payload format: JS/TS context object (project info, cwd, git worktree path, SDK client, Bun shell API); a plugin exports a function returning a hooks object
- Limitations: plugin language is JavaScript/TypeScript (npm supported) — gald3r's Python
g-hk-*.pyhooks (port; NOT PowerShell) must be shelled out from a JS/TS wrapper; no first-class git pre-commit / pre-push event (wire viacommand.executedor external git hooks) - gald3r hook files (corrected):
gald3r-hooks-plugin.tswires gald3r's five relevant native events to six canonicalg-hk-on-<event>.pyentrypoints viaspawnSync("python", …), perneutral_source/hooks/g_hk_core.py'sPLATFORM_EVENT_MAP["opencode"]— but across TWO real dispatch mechanisms, not one uniform key set:tool.execute.before/tool.execute.afterare registered directly as real top-levelHookskeys, each marshaling its actual(input, output)argument pair into a{tool_name, tool_input}dict;session.created/session.deleted/session.idleare NOTHookskeys (live-verified againstpackages/plugin/src/index.ts) and are instead routed through the single genericeventhook'sswitch (event.type), each case marshaling thatEventvariant's real fields (properties.info.idfor created/deleted,properties.sessionIDfor idle, perpackages/sdk/js/src/gen/types.gen.ts) into a{session_id}dict. Each entrypoint fans out to the full per-event concern chain (g_hk_core.dispatch(...)); no OpenCode event maps to canonicaluser-prompt-submittoday. - Tool-id/path-key normalization (fixed). OpenCode's real builtin tool ids are
lowercase (
edit,write,apply_patch,read, live-verified againstpackages/opencode/src/tool/*.ts'sTool.define(...)calls) and its tool-argument path field is camelCase (filePath), neither of which matched gald3r's Claude/Cursor-centricWRITE_TOOLS(Edit,Write, ...) /PATH_KEYS(file_path, ...) conventions used byg-hk-pre-tool-call-gald3r-guard.pyand its siblingtool-startconcern hooks (g-hk-pre-tool-call-prd-freeze.py,g-hk-pre-tool-call-member-gald3r-guard.py) — so even with the corrected dict payload shape, those guard hooks did not fire on a real OpenCode install. Fixed by anormalizeToolPayloadmarshaling step insidegald3r-hooks-plugin.tsitself (_OPENCODE_WRITE_TOOL_ID_MAPingenerate.py):edit/write/apply_patchare renamed toEdit/Write/ApplyPatchand gain an additionalfile_pathkey (alongside the originalfilePath, never removed) when their args carry one — keeping the shared, platform-agnostic Python concern hooks free of OpenCode-specific vocabulary. Live/executable end-to-end regression:tests/platform/test_opencode_plugin_hooks_shape.py'stest_opencode_gald3r_guard_blocks_real_generated_plugin_write_end_to_end/test_opencode_prd_freeze_blocks_real_generated_plugin_write_end_to_end.apply_patchgap closed (fixed).apply_patch's sole real argument ispatchText(a unified-diff body with per-hunk target paths embedded in*** {Add,Delete,Update} File:/*** Move to:header lines, live-verified againstpackages/opencode/src/patch/index.ts's realparsePatchHeader()) — it has no top-level path field of its own to copy the wayedit/write'sfilePathis copied.normalizeToolPayloadnow also extracts every recognized header path out ofpatchText; when a patch touches multiple files, whichever extracted path resolves under.gald3r/is surfaced asfile_path(never just the first one), and a malformed/unrecognizedpatchTextstill catches a literal.gald3r/-containing path token elsewhere in the body via a narrower fallback scan, so an unparseable patch can never silently allow a real.gald3r/target. Live/executable end-to-end regression:test_opencode_gald3r_guard_blocks_real_generated_apply_patch_write_end_to_end(blocks a.gald3r/-targetingapply_patchcall, including one where the.gald3r/header is not the first in a multi-file patch, and the fail-safe malformed-patchText case; allows an ordinary-path call and a patchText with no.gald3r/mention at all).
Atypical Handling¶
- Instruction file is
AGENTS.md(CLAUDE.md fallback); if both exist locally, only AGENTS.md is used — unlike Claude Code, which readsCLAUDE.md. - Skills are discovered from
.opencode/skills/,.claude/skills/, and.agents/skills/(shared Agent-Skills locations), loaded on-demand via the nativeskilltool. - Hooks are JS/TS plugins exporting a function, not a JSON wiring file and not bare
.pyscripts — a format mismatch with gald3r's Python hooks, bridged by a generatedgald3r-hooks-plugin.tsshim (corrected); no first-class git pre-commit event. - Config is
opencode.json(root, NOT inside.opencode/); MCP lives under itsmcpfield.
gald3r Integration Notes¶
- Cheapest high-parity install: ship gald3r's
.claude/skills/tree +AGENTS.md/CLAUDE.md— OpenCode loads them natively. Put commands in.opencode/commands/, agents in.opencode/agents/. - gald3r
g-hk-*.pyhooks do NOT run natively —gald3r platform install opencode( /, corrected ) authors a thingald3r-hooks-plugin.tsin.opencode/plugins/that shells out tospawnSync("python", …):tool.execute.before/tool.execute.afterregistered directly (realHookskeys),session.created/session.deleted/session.idlerouted through the genericeventhook (they are busEventvariants, notHookskeys). Re-verify the plugin context fields +Hooksinterface +Eventunion before changing the shim. - Re-verify on the next
@g-platform-scan-docs opencode(crawl_max_age_days: 7).
Capability Summary¶
| Hooks | Rules | Skills | Commands | MCP | Docs Fresh |
|---|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Legend: ✅ verified working · ⚠️ partial / Cursor-generic · ❌ not supported · ❓ untested.
- Hooks ✅: native lifecycle hooks via JS/TS plugins (20 events); gald3r's Python
g-hk-*.pyhooks need a JS/TS shim (:gald3r-hooks-plugin.ts) and there is no first-class git pre-commit event. - Rules ✅:
AGENTS.md(CLAUDE.md fallback) +instructionsarray; no.mdcglob scoping. - Skills ✅: native
skilltool; discovered in.opencode/skills/+.claude/skills/+.agents/skills/→ gald3r SKILL.md drop-in. - Commands ✅:
.opencode/commands/*.mdwith$ARGUMENTS/$1+!bash; frontmatterdescription/agent/model/subtask. - MCP ✅:
opencode.json -> mcp(local + remote),{env:}/{file:}substitution. - Docs Fresh ✅: crawl assessment of https://opencode.ai/docs completed 2026-06-02.
Verification Evidence (docs crawl 2026-06-02, https://opencode.ai/docs)¶
| Capability | How verified |
|---|---|
| Commands | /docs/commands/ — .opencode/commands/*.md with YAML frontmatter; $ARGUMENTS + $1/$2/$3 + !command bash; fields description/agent/model/subtask |
| Rules | /docs/rules/ — AGENTS.md primary (CLAUDE.md fallback; AGENTS.md wins if both local) + global ~/.config/opencode/AGENTS.md + ~/.claude/CLAUDE.md; instructions array; /init |
| Agents | /docs/agents/ — primary (Build/Plan) + subagents (General/Explore/Scout); markdown agent files or opencode.json; @mention + Task tool; opencode agent create |
| Skills | /docs/skills/ — SKILL.md loaded on-demand via native skill tool; discovered in .opencode/skills/, .claude/skills/, .agents/skills/ (+ home); name+description frontmatter |
| Hooks | /docs/plugins/ — JS/TS plugins in .opencode/plugins/ (+ npm via opencode.json plugin); 20 lifecycle events; no first-class git pre-commit; gald3r's Python g-hk-*.py hooks need a JS/TS shim |
| MCP | /docs/mcp-servers/ — mcp field in opencode.json; type local (command) + remote (URL); 2026 OAuth callback-port + scoped client metadata; {env:}/{file:} substitution |
| Other | /docs/custom-tools/ — Custom Tools; built-in Plan/Build modes; @opencode-ai/sdk + in-process HTTP server with session metadata |
| Cross-compat | OpenCode reads AGENTS.md/CLAUDE.md + discovers .claude/ + .agents/ skills → gald3r Claude-Code SKILL.md/instruction artifacts reusable; hooks need a JS/TS shim (gald3r-hooks-plugin.ts) over Python g-hk-*.py, not .ps1 |
Reference page — canonical source: g-skl-platform-opencode/PLATFORM_SPEC.md in the engine repo, generated by scripts/generate_platform_support_docs.py. Pages marked curated are hand-assessed and not yet live-verified.