Thanks for helping out! This is a pnpm workspace of OpenCode plugins (the
@vymalo scope) plus a companion browser extension. This guide gets you from clone to PR.
This repository follows the ADORSYS-GIS AI Governance discipline: AI may accelerate the work, but humans own intent, verification, and consequences. AI output is not truth — review AI-generated code as untrusted, and never submit work you cannot explain.
- Open issues with the structured forms — Epic, User Story, or Development Ticket. Blank issues are disabled on purpose.
- Open pull requests with the pull request template and fill in
every section — including the AI Usage Declaration, a source-of-truth link, and verification
evidence. The advisory
governance.ymlcheck flags a PR body missing any of the three (pinned to an immutable governance release SHA). - AI code review is advisory, never a merge gate. Only deterministic checks (governance, lint, tests) may block. Treat every AI-review finding as a claim to verify against the cited lines, not a verdict. See the AI Working Agreement and the Doctrine.
- Node ≥ 22 (runtime packages set this in
engines). - pnpm 11 (
packageManagerpins the exact version). - For the browser extension: a Chromium browser and/or Firefox to load it unpacked.
pnpm install # install the whole workspace
pnpm -r build # oxc emits dist/ for library packages (no type check); wxt build for the extension
pnpm -r test # vitest in each package that has testsRun all five before opening a PR — CI runs the same:
pnpm -r build && pnpm -r typecheck && pnpm -r test && pnpm lint && pnpm format:checkOrder matters.
@vymalo/opencode-browser-mcptypechecks against@vymalo/opencode-browser's built./libexport, so build before typecheck/test (CI does).
pnpm buildnever type-checks. Library packages build withnode ../../scripts/build-package.mjs, which uses oxc to emit JS and.d.tsin one process with zero type analysis.pnpm typecheck(tsc --noEmit) is the only type-safety gate — a green build says nothing about types. See ADR-0010.
pnpm --filter @vymalo/opencode-oauth2 test
pnpm --filter @vymalo/opencode-oauth2 build
pnpm --filter @vymalo/opencode-oauth2 exec vitest run path/to/file.test.ts
pnpm --filter @vymalo/opencode-oauth2 exec vitest run -t "ensureAccessToken" # by name
pnpm --filter @vymalo/opencode-oauth2 exec vitest # watch modeA reusable compose stack lives in test-env/. Integration suites skip
themselves when their env var is unset, so the default pnpm test stays hermetic.
pnpm test:env:up # compose up (waits for health)
pnpm --filter @vymalo/opencode-models-info test:integration
pnpm test:env:down
# or one-shot:
pnpm test:integration- Biome, not ESLint/Prettier. Config in
biome.json: double quotes, 100-col, no trailing commas, semicolons always. Keep 0 lint warnings —noNonNullAssertionis a warning the codebase stays clean of; don't introduce!. - Strict TypeScript (
tsconfig.base.json:ES2022+NodeNext+strict+isolatedDeclarations). The last one is a real authoring rule, not just a compiler flag: every exported symbol needs a type the compiler can read off its own file, becausebuildderives.d.tsfiles with oxc'sisolatedDeclaration, which never consults the type checker. In practice, add an explicit annotation on things likeexport const FooPlugin: Plugin = createFoo()and an explicit return type on every exported function — inference that reaches into another file is exactly what this rejects. A violation is caught bypnpm typecheck, with a pointer to the offending export. See ADR-0010. Usenode:prefixes for built-ins. - kebab-case filenames for
.ts/.tsx/.css/.md/.json/.sh;camelCasevars/functions;PascalCasetypes/components;SCREAMING_SNAKE_CASEtrue constants. - Tests live in
test/, not co-located. Vitest everywhere. - Structured logging — emit
snake_caseevents through the existing logger (console + host log stream), never ad-hocconsole.log. Redact secrets. - Lint/format scripts pass explicit paths (
packages apps test-env *.json) becausebiome.jsonexcludes**/.claude; if you add a top-level lintable dir, add it to those scripts.
Each plugin follows the same shape:
packages/<plugin>/src/
├── index.ts # tiny re-export — OpenCode discovers this (rejects non-Plugin exports)
├── opencode.ts # plugin factory + default export
├── plugin.ts # core runtime (split out so it stays testable)
├── lib.ts # public library API (the "./lib" export subpath)
└── …
Two entry points per published package: "." → dist/index.js (kept intentionally tiny);
"./lib" → dist/lib.js (the embedder API — new utilities go here, not in index.ts).
- Conventional-commit style subjects (
feat(browser): …,fix(oauth2): …,docs: …). - Keep one concern per PR; open an issue first for substantial changes so we can align on scope.
- Make sure the pre-push gate is green and docs are updated alongside behavior changes.
- Use the pull request template and complete the AI Usage Declaration, source-of-truth link, and verification evidence (see AI Governance).
Versions are bumped manually — no changesets. All seventeen workspace packages sit on one
version line and are bumped together in a single PR that also adds the CHANGELOG.md entry: the
thirteen published ones (opencode-auth-core, opencode-core-otel, opencode-provider-sync,
opencode-oauth2, opencode-models-info, opencode-ratelimit, opencode-browser,
opencode-browser-mcp, opencode-devtools, opencode-devtools-mcp, opencode-otel,
opencode-repo-auth, opencode-lightbridge) plus four private (workspace root, plugin-bundle,
browser-extension, opencode-code-index).
After that PR merges, a maintainer publishes by dispatching the workflow:
gh workflow run publish.yml -f dry_run=falseIt runs the gate, publishes the thirteen npm packages with provenance (shared libraries — auth-core,
core-otel, provider-sync — before the plugins that depend on them), attaches the extension zips,
and wxt submits to the Chrome Web Store + Firefox AMO (each gated on its own store secrets, so a
store with no credentials is skipped rather than failing). -f dry_run=true validates everything,
store credentials included, without publishing.
Important
Do not tag the commit or cut a GitHub Release. This repo deliberately has neither — a release
is the workflow run, not a tag. Changelog entries are anchored by chore(release) commit
boundaries instead.
See the Releasing section in CLAUDE.md for the full mechanics.
docs/README.md— the documentation index.CLAUDE.md— the live architectural map of the repo.