Problem
The portable core is fully prepared for OpenCode, but the TypeScript bridge package that connects OpenCode's plugin API to the core does not exist yet. This issue implements the bridge; the goal is that OpenCode works exactly like the existing hosts (Claude Code / Cursor / Codex) on the same core — the bridge translates, it never decides.
What already exists (do NOT re-implement or refactor)
Core readiness landed on dev (see .archcore/plugin/host-expansion-core-readiness.plan.md):
plugins/archcore/bin/lib/normalize-stdin.sh — explicit opencode case (env-only host: ARCHCORE_HOST=opencode from the bridge is never clobbered — pinned by a test that fails on the pre-change code); info helpers emit plain text for opencode; block stays exit 2 + reason on stderr. The full bridge contract is documented in the file header.
plugins/archcore/bin/session-start — plain-text arm covers opencode; passes opencode as the host arg to archcore hooks.
test/fixtures/stdin/opencode/ — Claude-shaped snake_case fixtures; these ARE the bridge's stdin contract (write-archcore / write-regular / mcp-update).
test/helpers/common.bash — run_with_fixture_env helper for env-forced hosts.
- Tests: block/allow/info pins for opencode across
normalize-stdin.bats, check-archcore-write.bats, session-start.bats, output-helpers-matrix.bats.
Decisions are final and recorded in .archcore/plugin/ — do not re-litigate:
opencode-adapter-packaging.adr.md — TypeScript npm package @archcore/opencode-plugin, in THIS repo under plugins/opencode/; bun test scoped to that directory only; hook bridge is shell-out only — no decision logic in TS.
opencode-host-support.rnd.md — verified OpenCode plugin API facts: tool.execute.before/after hooks; deny = throw new Error(...) (model sees the message); config hook mutates live config (cfg.mcp, cfg.skills.paths); MCP config key is environment, not env; skills ship inside the package and register via cfg.skills.paths.
stack-and-tooling.rule.md — the TS + bun test exception is scoped to plugins/opencode/ only.
host-adapter-contract.spec.md — adapter obligations and the deny-semantics translation table.
Expected result
An OpenCode session with the plugin installed behaves like the shipped hosts: session-start injects .archcore context, direct writes to .archcore/*.md are blocked with the redirect-to-MCP message (bridge throws Error(stderr) on exit 2), MCP document tools work, post-tool validation/cascade context is appended.
Proposed approach
Acceptance criteria
- Live OpenCode session: write blocked with redirect message, MCP tools work, session-start context arrives, skills available.
- Zero changes under
plugins/archcore/ (enforced by host-logic-locality.bats and the exact-output pins — all stay green).
- No TypeScript outside
plugins/opencode/ (stack rule enforcement); make test lint check-json green; bun test green in plugins/opencode/.
Dependencies
needs-cli: the Go CLI must accept archcore hooks opencode session-start. Today an unknown host degrades silently (guard still works, context load does not) — needs a companion issue in the CLI repo.
References
.archcore/plugin/opencode-adapter-packaging.adr.md, opencode-host-support.rnd.md, host-adapter-contract.spec.md, stack-and-tooling.rule.md, host-expansion-core-readiness.plan.md.
Problem
The portable core is fully prepared for OpenCode, but the TypeScript bridge package that connects OpenCode's plugin API to the core does not exist yet. This issue implements the bridge; the goal is that OpenCode works exactly like the existing hosts (Claude Code / Cursor / Codex) on the same core — the bridge translates, it never decides.
What already exists (do NOT re-implement or refactor)
Core readiness landed on
dev(see.archcore/plugin/host-expansion-core-readiness.plan.md):plugins/archcore/bin/lib/normalize-stdin.sh— explicitopencodecase (env-only host:ARCHCORE_HOST=opencodefrom the bridge is never clobbered — pinned by a test that fails on the pre-change code); info helpers emit plain text for opencode; block stays exit 2 + reason on stderr. The full bridge contract is documented in the file header.plugins/archcore/bin/session-start— plain-text arm covers opencode; passesopencodeas the host arg toarchcore hooks.test/fixtures/stdin/opencode/— Claude-shaped snake_case fixtures; these ARE the bridge's stdin contract (write-archcore / write-regular / mcp-update).test/helpers/common.bash—run_with_fixture_envhelper for env-forced hosts.normalize-stdin.bats,check-archcore-write.bats,session-start.bats,output-helpers-matrix.bats.Decisions are final and recorded in
.archcore/plugin/— do not re-litigate:opencode-adapter-packaging.adr.md— TypeScript npm package@archcore/opencode-plugin, in THIS repo underplugins/opencode/;bun testscoped to that directory only; hook bridge is shell-out only — no decision logic in TS.opencode-host-support.rnd.md— verified OpenCode plugin API facts:tool.execute.before/afterhooks; deny =throw new Error(...)(model sees the message);confighook mutates live config (cfg.mcp,cfg.skills.paths); MCP config key isenvironment, notenv; skills ship inside the package and register viacfg.skills.paths.stack-and-tooling.rule.md— the TS +bun testexception is scoped toplugins/opencode/only.host-adapter-contract.spec.md— adapter obligations and the deny-semantics translation table.Expected result
An OpenCode session with the plugin installed behaves like the shipped hosts: session-start injects
.archcorecontext, direct writes to.archcore/*.mdare blocked with the redirect-to-MCP message (bridge throwsError(stderr)on exit 2), MCP document tools work, post-tool validation/cascade context is appended.Proposed approach
plugins/opencode/as npm package@archcore/opencode-plugin(Bun runtime).normalize-stdin.shheader): spawnplugins/archcore/bin/scripts withARCHCORE_HOST=opencodein env and a Claude-shaped snake_case JSON payload on stdin — exactly the shape intest/fixtures/stdin/opencode/.tool.execute.before→bin/check-archcore-write+bin/check-code-alignment; exit 2 →throw new Error(stderr); stdout (plain text) → context.tool.execute.after→bin/validate-archcore,bin/check-cascade,bin/check-precision; append plain stdout verbatim.bin/session-start(plain-text output, appended verbatim).confighook: register the archcore MCP server (keyenvironment) and add the bundled skills path tocfg.skills.paths.bun testinsideplugins/opencode/only, driven by the shared fixtures intest/fixtures/stdin/opencode/so the TS side and the shell side pin the same contract.@archcore/opencode-plugin(install viaplugin: []array).Acceptance criteria
plugins/archcore/(enforced byhost-logic-locality.batsand the exact-output pins — all stay green).plugins/opencode/(stack rule enforcement);make test lint check-jsongreen;bun testgreen inplugins/opencode/.Dependencies
needs-cli: the Go CLI must acceptarchcore hooks opencode session-start. Today an unknown host degrades silently (guard still works, context load does not) — needs a companion issue in the CLI repo.References
.archcore/plugin/opencode-adapter-packaging.adr.md,opencode-host-support.rnd.md,host-adapter-contract.spec.md,stack-and-tooling.rule.md,host-expansion-core-readiness.plan.md.