* 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>