Route OpenCode (and its Kilo sibling) through the public Host-Integration Interface and
land two Context7-verified capability upgrades. Byte-identical install output for all 16
runtimes (golden parity asserted).
Through the interface (AC2):
- OpenCode/Kilo's bespoke commands+skills+plugin install (the inline
`else if (isOpencode || isKilo)` block) moves into the engine
(installOpencodeFamilyCommands/Artifacts in src/install-engine.cts), dispatched by
installRuntimeArtifacts when the descriptor declares hostBehaviors.combinedFamilyInstall.
opencode/kilo now flow CLI -> _runtimeAdapter -> installRuntimeArtifacts like the skills
runtimes. _isSkillsRuntime no longer excludes them; the bespoke block + dead
copyFlattenedCommands are removed.
- Every hardcoded `runtime === 'opencode'`/`isOpencode` branch is folded into
descriptor-driven runtime.hostBehaviors. ZERO `runtime === 'opencode'`/`'kilo'`
string-equality remain in bin/install.js / install-engine.cts / runtime-artifact-conversion.cts.
Upgrades (AC4):
- Background dispatch: OpenCode shipped experimental background subagents in v1.15 and
made them default-on in v1.17 -> dispatch.background/backgroundDispatch flip to true;
shouldFlattenDispatch(opencode) now returns false (behavioral change; type: Changed).
- Expanded event surface: the OpenCode plugin subscribes permission.asked/replied +
session.error.
Tests: opencode-imperative-reference (adapter/profile, shouldFlattenDispatch pin,
fail-closed negotiate, hostBehaviors, AC2 source-guard) + extended plugin surface test.
Docs: capability matrix v1.15/v1.17 citations. Changeset (Changed). gitignore .memdb//.memtrace/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1682): OpenCode session.idle + opencode-subset dialect + Claude parity — Slice 1b/c
- Plugin (.opencode/plugins/gsd-core.js): handle session.idle (↔ Claude Stop
lifecycle point; no-op sentinel — state already persisted to .planning/).
Completes the compaction/idle pair (#1914 shipped compaction).
- Declare hookEvents: 'opencode-subset' in the OpenCode descriptor — the
reserved dialect now has a real consumer (no longer zero-consumer).
- host-integration.cts: add HOOK_EVENT_SURFACES + hookEventSurfaceFor() — the
pure consumer that resolves a dialect to its host-fireable event surface.
opencode-subset = session/tool/file subset with NO workflow-phase events
(engine owns phase sequencing; ADR-1239 §OpenCode binding).
- Tests: hookEventSurfaceFor unit tests (claude/gemini/opencode-subset/null);
plugin session.idle no-throw; compaction breadcrumb; opencode-subset surface
parity vs the plugin's handlers (Claude parity).
* fix(#1682): don't declare hookEvents on opencode (hooksSurface:none invariant)
The repo invariant couples runtime.hookEvents to the managed settings.json hook
surface: hooksSurface:'none' runtimes (opencode — plugin owns hooks) must NOT
declare hookEvents. Declaring 'opencode-subset' there violated 3 capability-
registry invariants + 2 install-plan golden masters + opencode golden parity.
The opencode-subset dialect is still IMPLEMENTED — just not via the legacy
descriptor field: hookEventSurfaceFor() (host-integration.cts) is its consumer,
and the OpenCode plugin consumes the subset events at runtime (session.idle
added here; compaction shipped in #1914). Declaring it on the descriptor would
require weakening the hooksSurface:none ⇔ no-hookEvents invariant (flagged for
decision).
* fix(#1682): refresh opencode golden parity for plugins/gsd-core.js (session.idle)
* docs(changeset): OpenCode session.idle + opencode-subset dialect (#1682)
* docs(changeset): backfill PR #1930
* feat(#1914): OpenCode native plugin integration (Option 1 file-copy)
Ship a native OpenCode plugin (.opencode/plugins/gsd-core.js) plus the
installer step that delivers it, so GSD's lifecycle hooks run on OpenCode.
OpenCode declares hooksSurface:'none', so GSD's hook scripts already ship to
<configDir>/hooks/ but nothing invokes them; the plugin bridges OpenCode's
event bus onto those scripts as subprocesses (prompt/read/worktree/workflow
guards, injection scanner, context monitor).
Distribution is Option 1 (file copy) per the #1914 triage decision: no
scripts.build rename, no prepare/prepack removal. package.json gains
main + .opencode in files[] for discovery.
Corrected against OpenCode's docs + loader source (not the reference branch):
- Auto-discovery globs {plugin,plugins}/*.{ts,js} — .cjs is never matched, so
the installed adapter must be .js (config dir carries {"type":"commonjs"}).
- No opencode.json plugin-array patch — that array is npm-only; local files
are auto-discovered.
- REPO_ROOT is resolved by walking up to the dir holding hooks/ + gsd-core/,
correct for package tree, global install, and local install.
- Config-hook registration is gated (IS_PACKAGE_TREE) so it never
double-registers commands/agents/skills already delivered by native copy.
Also fixes an incidental .gitignore drift: 8 ADR-1239 .cts-generated .cjs
artifacts were untracked-and-not-ignored (leak risk) — now ignored.
Tests: tests/opencode-plugin-adapter.test.cjs (14, pure helpers + real
subprocess bridge against stub hooks); golden-install-parity regenerated.
Green on Mac + Linux (gsd-test): 0 failures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1914): harden OpenCode plugin export shape + advisory accumulation (adversarial findings)
Address Codex adversarial-review findings:
- HIGH: export `{ id, server }` could trip OpenCode's loader
(`for (entry of Object.values(mod)) getServerPlugin(entry)` throws on a
non-extractable value). Make `id` NON-ENUMERABLE and assign module.exports
from a variable (not a literal) so no string `id` is ever iterated — verified
loader-safe under real import(pathToFileURL) (default + module.exports alias,
both objects with .server; no bare id string).
- MEDIUM: sequential advisory hooks clobbered output.metadata._gsdAdvisory;
now accumulate into an array.
- LOW: resolveRepoRoot fallback returned ".." while the comment said "../.." —
aligned to "../.." (package-tree depth).
Tests: added a faithful loader-loop emulation (raw CJS + ESM namespace views),
advisory-accumulation, and a real bin/install.js copy→manifest→uninstall
integration test. golden regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1914): add changeset fragment for OpenCode plugin integration (PR #1923)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1914): Windows — assert rewritten path with string include, not path-regex
The Read content-rewrite test built a RegExp from `path.join(root,'gsd-core')`.
On Windows the backslashes in the path are interpreted as regex escapes, so the
assertion never matched and `test (windows-latest, 24)` failed — even though the
adapter rewrote the path correctly. Replace the RegExp with a separator-agnostic
`String.includes` check (the repo's no-path-literal-in-assert concern).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>