# Catalog Policy How entries get into the catalog, how they are kept honest, and when they leave. Automation enforces most of this; this page is the reference it links to. ## Admission An entry must: - work with OpenCode; - point at a public, non-archived repository root (`path` for a monorepo folder); - be the only entry for that repository and path; - not be a cryptocurrency, token or trading project (zero tolerance); - pass `npm run validate` (schema, field limits, kind/category fit). ### OpenCode V2 requirement for new plugins New entries in `data/plugins/` must use the V2 plugin API: depend on an `@opencode/*` package (normally `@opencode/plugin`) and export `Plugin.define({ id, setup(ctx) })`. Pre-release V2 builds published under the old name (`@opencode-ai/plugin@0.0.0-beta-*`, `@next`, `@beta`, or `@opencode-ai/client`) also count. Packages that ship both V1 and V2 entry points count as V2. The `validate` check detects this from, in order: the entry's `npm` package, `package.json` files in the repository (at `path`, the root and `packages/*` / `plugins/*`), then the source code. Results: | Detected | New plugin PR | Meaning | | :--- | :--- | :--- | | `v2` | passes | `@opencode/*` dependency or `Plugin.define` found | | `v1` | blocked, labeled `needs-v2` | Only `@opencode-ai/plugin`/`sdk` 1.x, or a legacy V1 plugin function without `Plugin.define` | | `unknown` | passes with `needs-human` | Nothing conclusive found; a maintainer verifies by hand. Adding `npm` or `path` usually resolves it | PRs labeled `needs-v2` are re-checked every night. When the target migrates, the bot relabels the PR, fills in any missing fields on same-repo branches, re-runs `validate` and tells the author. **After 60 days still on V1, the PR is closed**; reopen it or resubmit through the issue form after migrating. Themes, agents, projects and resources are not blocked on V2. Their `compat` is informational (`v2`/`v1` when they use the SDK, otherwise `n/a`). ## Existing V1 plugins: sunset on 2027-01-01 Plugins already in the catalog that are V1-only carry a `V1 only` badge in the README and on the site. The nightly health job re-detects every plugin not marked `v2`; when one is detected as V2 it opens a PR (`bot/health-compat`) marking the entry `compat: v2`. **After 2027-01-01 V1-only plugins are removed** by the health job's removal PR, but only when that night's re-check still detects V1: a V2 or inconclusive result keeps the entry, and inconclusive ones are listed in the health report for a maintainer. Plugins with `unknown` compatibility are listed there too for manual verification. The bot keeps the list in the V1 sunset tracking issue (label `v1-sunset`) current, posts one reminder there 31 days before the sunset, and closes the issue once no V1-only plugins remain. ## Health checks and removal A nightly job checks every entry and maintains the **Catalog health report** issue. | Finding | Action | | :--- | :--- | | Repository renamed or transferred | Bot PR updates the `repo` URL | | Repository archived, deleted (404), or npm package deprecated | Listed in the report; **removal PR after 30 days** if it doesn't recover | | Plugin not marked `v2` now detected as V2 | Bot PR sets `compat: v2` | | V1-only plugin after 2027-01-01, still detected as V1 | Removal PR | | No push for 6–12 months / 12+ months | `quiet` / `stale` badge on the site. **Never removed for inactivity alone**: small, finished tools are fine | Removal PRs are reviewed like any other PR; closing one keeps the entries until the next run proposes them again. Git history is the archive; there is no separate retired list. ## Discovery A weekly scout searches npm for packages published since V2 shipped that depend on `@opencode/*`, keeps public, licensed, non-archived, non-fork repositories that are not already listed or in an open PR, and ranks them by weekly downloads, then stars. It posts a **Scout** issue; a maintainer ticking a candidate's box makes the bot open a PR for it. Add a package or repo to [`discovery/declined.txt`](../discovery/declined.txt) to stop suggesting it. ## AI review An OpenCode V2 agent reviews the judgment calls the deterministic checks leave open. It is advisory: **it never merges or closes anything.** | What | When | Result | | :--- | :--- | :--- | | Open PRs labeled `needs-human` or `scout` | daily; a PR is reviewed again only after it gets a new commit | One "AI review" comment (verdict, reasons, suggested placement, and for `fix` a corrected entry checked against the schema) and one label: `ai-approve`, `ai-fix`, `ai-close` or `ai-unsure` | | Unticked candidates on the open Scout issue | weekly, after the scout run | Confident, policy-clean accepts get a PR opened by the bot, with the AI's kind, topics and tagline; everything else gets an "AI: …" note on its line | A maintainer still merges every PR, including the ones the AI opened, and the `validate` check still applies. Delete the comment or remove the label to discard a verdict; dispatching the workflow with `force` re-reviews. Maintainers can apply a fix verdict in one click by commenting /apply-ai-fix on the PR. The bot validates the sender, checks the suggestion is fresh and schema-valid, commits it to the PR branch (same-repo PRs only), and validate re-runs. How it is contained: - It runs only on a schedule or manual dispatch, with base-branch code; PR code is never checked out. - The model runs in a job whose GitHub token has no permissions and is never in its environment. Its agent ([`.opencode/agents/catalog-reviewer.md`](../.opencode/agents/catalog-reviewer.md)) may only read, glob and grep inside the repository; a self-test before every run proves shell, edit and web access are unavailable. - It sees a pre-built evidence bundle (the PR's entries, check results, the target's README and `package.json`, similar entries), treats it as untrusted data, and must reply with one JSON verdict. - A separate job validates that verdict against a strict schema (known verdicts, categories, kinds and topics; matching PR number and commit) and renders all comment text itself, escaping HTML and breaking @mentions. Anything else is dropped. The model comes from the `OPENCODE_MODEL` repository variable; credentials from the `OPENCODE_GO_API_KEY` (or `OPENCODE_API_KEY`) secret. ## Branch protection `main` has a ruleset: changes land only through pull requests with a passing `validate` check (GitHub rulesets do not accept direct pushes while a status check is required), and force-pushes and deletion are blocked. Bots open PRs too; GitHub holds workflow runs on bot-opened PRs for approval, so each bot approves the `validate` run on its own PR. Repository admins can merge a PR whose check is failing or pending (for a broken CI). ## Automation map | Workflow | When | What | | :--- | :--- | :--- | | `validate-pr.yml` | every PR | Required `validate` check | | `triage.yml` | PR opened/updated | Labels and a results comment (base-branch code only). `needs-human` is only ever removed by a maintainer | | `generate-readme.yml` | push to main | README.md and dist/registry.json; fails without writing on any invalid entry. The regenerated commit goes through a bot PR (`bot/readme-*`) that merges itself once `validate` passes | | `site.yml` | push to main, daily | Search site on GitHub Pages | | `health.yml` | daily | Health report and V1 sunset issues; rename, V2-migration and removal PRs | | `reprobe.yml` | daily | Re-checks `needs-v2` PRs | | `scout.yml` | weekly | Scout issue with V2 plugin candidates | | `issues.yml` | issue opened/edited | Issue-form submissions and scout acceptances become PRs | | `ai-review.yml` | daily (PRs), weekly (scout) | Advisory AI review of `needs-human` and `scout` PRs; opens PRs for scout candidates it accepts |