Skip to content

feat: OpenCode feature parity — plugin + skills + hooks#616

Open
ZeR020 wants to merge 1 commit into
DeusData:mainfrom
ZeR020:feature/opencode-parity
Open

feat: OpenCode feature parity — plugin + skills + hooks#616
ZeR020 wants to merge 1 commit into
DeusData:mainfrom
ZeR020:feature/opencode-parity

Conversation

@ZeR020

@ZeR020 ZeR020 commented Jun 24, 2026

Copy link
Copy Markdown

Summary

Closes #585. Adds full OpenCode feature parity with Claude Code:

  1. TypeScript plugin (~/.config/opencode/plugins/cbm-augment.ts) — auto-discovered, no opencode.json entry needed
  2. Consolidated codebase-memory skill (~/.config/opencode/skills/codebase-memory/SKILL.md) — same skill content as Claude Code
  3. Uninstall path — removes plugin, skills, MCP config, and AGENTS.md

Design Decision: tool.execute.after (not .before)

Issue #585 proposed tool.execute.before for context injection. However, source verification against anomalyco/opencode proved that .before is non-functional for this purpose:

tool.execute.before (packages/opencode/src/session/tools.ts:82):

yield* plugin.trigger("tool.execute.before",
  { tool: item.id, sessionID, callID },  // input — NO args field
  { args },                                // output — fresh wrapper object
)
const result = yield* item.execute(args, ctx)  // uses ORIGINAL args
  • Input does NOT include args. Output { args } is a fresh object; mutation has zero effect — the tool uses the original args variable, not the mutated output.
  • Runs BEFORE the tool produces a result — there's nothing to append to.

tool.execute.after (packages/opencode/src/session/tools.ts:92):

const output = { ...result, attachments: [...] }
yield* plugin.trigger("tool.execute.after",
  { tool, sessionID, callID, args },  // input — INCLUDES args
  output,                               // output — actual tool result
)
return output  // model sees this
  • output.output (type: string) is model-visible tool result text.
  • Mutating output.output in place IS model-visible.
  • input.args available (has pattern for grep/glob).

This PR uses .after so that graph context is appended to the tool result the model actually sees.

How It Works

Plugin (cbm-augment.ts)

tool.execute.after hook:

  • Checks if input.tool is "grep" or "glob" (OpenCode uses lowercase tool names)
  • Maps to capitalized "Grep"/"Glob" for the agent-agnostic hook-augment binary
  • Spawns <binary> hook-augment, writes {"tool_name":"Grep","tool_input":{"pattern":"..."}} to stdin
  • Parses hookSpecificOutput.additionalContext from stdout
  • Appends to output.output with \n\n separator
  • All failures silently swallowed — never blocks or throws (consistent with Claude Code's cbm-code-discovery-gate)

experimental.chat.system.transform hook:

  • Pushes the session reminder string to output.system (rebuilt fresh each turn, no accumulation)
  • Message consistent with CMM_SESSION_REMINDER_CMD used by Codex/Gemini/Antigravity

Install/Uninstall

Install path (install_cli_agent_configs):

  • Calls cbm_install_skills(skills_dir, true, dry_run) — reuses existing function
  • Calls cbm_upsert_opencode_plugin(home, binary_path, dry_run) — writes plugin with embedded binary path
  • Plan mode (--plan) records skills + plugin entries without mutating

Uninstall path (uninstall_cli_agents):

  • Calls cbm_remove_skills(skills_dir, dry_run)
  • Calls cbm_remove_opencode_plugin(home, dry_run) — unlinks plugin file (unlike Claude Code which leaves inert scripts, OpenCode auto-discovers plugins so the file must be deleted)

Security

Tests

5 new tests in tests/test_cli.c:

  • cli_upsert_opencode_plugin_fresh — verifies plugin file written with correct binary path, hook names, and satisfies Plugin
  • cli_upsert_opencode_plugin_idempotent — verifies re-install overwrites cleanly with new path
  • cli_upsert_opencode_plugin_rejects_quote — verifies security: binary paths with " are rejected
  • cli_remove_opencode_plugin — verifies install → remove → file gone, and idempotent remove
  • cli_opencode_skills_installed — verifies SKILL.md written to correct path with correct content

All 5 pass. 5698 total tests pass. 1 pre-existing failure (incr_full_index in test_incremental.c) is unrelated — an RSS memory threshold assertion that's environment-dependent.

Files Changed

  • src/cli/cli.c (+149 lines): opencode_plugin_content static string, cbm_upsert_opencode_plugin(), cbm_remove_opencode_plugin(), install/uninstall path modifications
  • src/cli/cli.h (+9 lines): function declarations
  • tests/test_cli.c (+130 lines): 5 new tests + RUN_TEST registrations

Add full OpenCode integration matching Claude Code parity:
- TypeScript plugin (tool.execute.after for grep/glob graph augmentation,
  experimental.chat.system.transform for session reminder)
- Consolidated codebase-memory skill installation
- Plugin auto-discovered from ~/.config/opencode/plugins/ (no config entry needed)
- Skill auto-discovered from ~/.config/opencode/skills/

Design note: uses tool.execute.after (NOT .before as proposed in DeusData#585)
because source verification against anomalyco/opencode proved .before output
mutation has no effect — the tool uses original args, not output.args.
The .after hook mutates output.output which is model-visible.

Non-blocking: all plugin failures silently swallowed (consistent with
Claude Code hook gate script behavior). TOCTOU-safe fchmod pattern.
Defensive binary-path quote rejection (security).

5 new tests: plugin install/idempotent/quote-reject/remove, skills install.

Closes DeusData#585
Copilot AI review requested due to automatic review settings June 24, 2026 20:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Full OpenCode feature parity with Claude Code — pre-tool hook, session-start context, and skill installation

2 participants