You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
opencode-rules@2.0.0: a clean-break major that retargets the plugin to the opencode v2 plugin API (no v1+v2 compatibility). Works on the current opencode v2 beta; fix forward rather than gating on GA. Published to a new npm dist-tag v2 (not beta/latest, so v1 users tracking beta don't get v2 builds). Sidebar included (ported to the v2 CLI-plugin slot API).
Notes
All work happens on the v2 branch (already exists; we are on it). main stays untouched for v1 users; no parallel v1 plugin line, no v1 maintenance branch.
Deps: devDeps track the npm beta dist-tag (@opencode-ai/plugin@beta, @opencode-ai/sdk@beta / @opencode-ai/client@beta, plus new transitive peers effect, zod); peer range targets the beta line, tightened at GA.
Skills to consult: crafting-rules (dogfood rule files), verification-before-completion before any "done" claim.
Module contract: default export { id, setup(ctx) }. Only the default export is loaded.
tool.execute.before/after → ctx.tool.hook(...); after: { status, result?/error? } with mutable result, reject via Tool.Error.
session.synthetic = durable inbox admission ("schedule execution unless resume is false"); delivery: "steer" (default) runs at the next step boundary before queued prompts; id must satisfy Session.Message.ID (msg_ prefix); metadata rides message-level. Synthetic messages do NOT run prompt hooks.
ctx.session.hook("context") fires per model dispatch (tool continuations, generation, compaction; not title requests), mutable { system, messages, tools, generation, providerOptions }, never persisted.
ctx.session.hook("prompt") fires once during admission ({ sessionID, messageID, prompt: { text, files, agents, skills }, metadata, delivery }); model resolution happens after admission, so provider scoping is unavailable there.
session.prompt({ id, text, metadata, resume: false }) = durable admission without agent-loop execution (v1 noReply); idempotent by id (409 ConflictError otherwise).
SessionContext.model is a Model.Ref{ providerID, id, variant? } — there is no modelID key (see Port model/agent capture ticket).
TUI = "CLI plugins": default export { id, setup(ctx) } at ./tui; ctx.ui.slot(claim) with fixed slot tree; our sidebar_content maps to sidebar.content.
Config key plugin → plugins; local discovery accepts .opencode/plugin/ and .opencode/plugins/.
Dogfooding: main + v1 opencode dogfooding untouched. V2 validation is deliberate: run opencode2 against v2-branch builds. This repo's own .opencode/rules/*.md are the v2 test corpus.
Decisions so far
Port server entry + orchestrator to v2 plugin contract: entry default-exports {id, setup(ctx)} (type Plugin.Plugin), __testOnly replaced by the createRuntime factory seam; tool hooks → ctx.tool.hook (Tool.Error blockers), durable delivery → session prompt hook + ctx.session.synthetic, admission → session.prompt({resume:false}), transient → session context hook mutation, history → session.context, tool IDs → context tools + mcp.list, events → session.revert.*/compaction events (projection rides next dispatch, placeholder for Redesign post-compaction ledger rebuild without a compaction hook #73); plugin devDep pinned 0.0.0-beta-18999 (exact; caret resolves wrong prerelease), schema/effect/zod still transitive for Update deps, versioning, and release workflow for v2 dist-tag #75.
Port delivery engine to session.synthetic + context hook: durable = session.synthetic({ delivery: "steer" }) (next step boundary, before queued prompts — closest to v1 append-before-dispatch; divergence: separate persisted synthetic message instead of a part on the user message); transient per-dispatch context hook OK — tool-continuation re-injection suppressed by turn-dedup keyed on latest real user message id; admission = session.prompt({ resume: false }) (= v1 noReply, idempotent by id); codec/history port kept, decodeRawHistory extended for message-level metadata on synthetic history messages; fixed synthetic id schema violation (prt_rules_ → msg_-prefixed id via syntheticMessageId()).
Port model/agent capture to v2 Model.Ref shape: capture reads Model.Ref.id ({ providerID, id, variant? } — no modelID key), internal field stays modelID; V2SessionContextInput.model retyped, hook-ref + history-assistant fixtures fixed (model: { id }); variant handled for free (Model.Ref.id is variant-free by construction, model#variant parses into its own key) — model: rules match the bare model id, no variant logic added; flat modelID message-info fields are v2 history shape, not matching inputs.
Update deps, versioning, and release workflow for v2 dist-tag: version 2.0.0-beta.1; oc-plugin field dropped (v2 discovers via exports: . + ./tui); @opencode-ai/sdk removed (unused; plugin context is the client — no @opencode-ai/client migration needed); effect/zod/@opencode-ai/ai left as plugin transitive deps (no direct imports); plugin peer range >=0.0.0-beta-18999 <0.0.1 (literal 0.0.0-beta-* is an invalid semver range — satisfies nothing); devDep stays exact-pinned beta-18999; release-beta.yml publishes --tag v2 with containment dev|v2; stable release job also excludes -rc; README/AGENTS install + release docs updated to v2 dist-tag and the v2 plugins config key; files/exports unchanged (pack dry-run clean).
Port TUI sidebar to v2 CLI-plugin slot API: entry default-exports { id, setup(ctx) } typed Plugin.Definition (Plugin is a namespace export); claim = ctx.ui.slot({ append: 'sidebar.content', render }), input { sessionID }; matched-rule state still read by direct fs (no data/storage second source of truth); project dir via ctx.data.session.root(sessionID); theme is flat ResolvedTheme (text.default/text.subdued/text.feedback.success.default); refresh on ctx.data.on('session.message.content.updated' | 'session.status') (data.sessionID payloads); @opencode-ai/client + @opencode-ai/theme added as exact-pinned beta-18999 devDeps (real v2 types need them) and the vendored v1 type shim deleted; build emits { id, setup } shape.
Not yet specified
Post-GA cleanup: when opencode v2 stabilizes, tighten peer ranges and port any API drift that landed between now and GA (compaction behavior parity is now settled in Redesign post-compaction ledger rebuild without a compaction hook #73; only the degraded-then-healed window — tool-only continuations before the next durable turn see the deduped ledger — remains by design).
Out of scope
v1 + v2 dual support — clean break per destination; v1 users stay on 1.0.0 (main).
Behavioral parity chase for post-compaction re-injection — accepted degraded-then-healed posture (decided in charting); refinement deferred to post-GA.
v1 maintenance branch / parallel v1 plugin line — no v1 support beyond 1.0.0; main serves as passive reference.
Stable 2.0.0 release now — ship betas on v2 dist-tag; the stable flip waits for opencode v2 GA.
Destination
opencode-rules@2.0.0: a clean-break major that retargets the plugin to the opencode v2 plugin API (no v1+v2 compatibility). Works on the current opencode v2 beta; fix forward rather than gating on GA. Published to a new npm dist-tagv2(notbeta/latest, so v1 users trackingbetadon't get v2 builds). Sidebar included (ported to the v2 CLI-plugin slot API).Notes
v2branch (already exists; we are on it).mainstays untouched for v1 users; no parallel v1 plugin line, no v1 maintenance branch.betadist-tag (@opencode-ai/plugin@beta,@opencode-ai/sdk@beta/@opencode-ai/client@beta, plus new transitive peerseffect,zod); peer range targets the beta line, tightened at GA.crafting-rules(dogfood rule files),verification-before-completionbefore any "done" claim.0.0.0-beta-18999+ https://opencode.ai/v2/docs/build/plugins + v2 API reference):{ id, setup(ctx) }. Only the default export is loaded.tool.execute.before/after→ctx.tool.hook(...); after:{ status, result?/error? }with mutableresult, reject via Tool.Error.session.synthetic= durable inbox admission ("schedule execution unless resume is false");delivery: "steer"(default) runs at the next step boundary before queued prompts;idmust satisfySession.Message.ID(msg_prefix); metadata rides message-level. Synthetic messages do NOT run prompt hooks.ctx.session.hook("context")fires per model dispatch (tool continuations, generation, compaction; not title requests), mutable{ system, messages, tools, generation, providerOptions }, never persisted.ctx.session.hook("prompt")fires once during admission ({ sessionID, messageID, prompt: { text, files, agents, skills }, metadata, delivery }); model resolution happens after admission, so provider scoping is unavailable there.session.prompt({ id, text, metadata, resume: false })= durable admission without agent-loop execution (v1 noReply); idempotent byid(409 ConflictError otherwise).session.compaction.started/ended,session.revert.staged/cleared/committedevents (payloaddata.sessionID) +ctx.session.context({ sessionID })reads (returnsSessionMessageInfo[]incl.type: "synthetic"messages with message-level metadata).SessionContext.modelis aModel.Ref{ providerID, id, variant? }— there is nomodelIDkey (see Port model/agent capture ticket).{ id, setup(ctx) }at./tui;ctx.ui.slot(claim)with fixed slot tree; oursidebar_contentmaps tosidebar.content.plugin→plugins; local discovery accepts.opencode/plugin/and.opencode/plugins/.main+ v1 opencode dogfooding untouched. V2 validation is deliberate: runopencode2againstv2-branch builds. This repo's own.opencode/rules/*.mdare the v2 test corpus.Decisions so far
{id, setup(ctx)}(typePlugin.Plugin),__testOnlyreplaced by thecreateRuntimefactory seam; tool hooks →ctx.tool.hook(Tool.Error blockers), durable delivery → sessionprompthook +ctx.session.synthetic, admission →session.prompt({resume:false}), transient → sessioncontexthook mutation, history →session.context, tool IDs → contexttools+mcp.list, events →session.revert.*/compaction events (projection rides next dispatch, placeholder for Redesign post-compaction ledger rebuild without a compaction hook #73); plugin devDep pinned0.0.0-beta-18999(exact; caret resolves wrong prerelease), schema/effect/zod still transitive for Update deps, versioning, and release workflow for v2 dist-tag #75.session.synthetic({ delivery: "steer" })(next step boundary, before queued prompts — closest to v1 append-before-dispatch; divergence: separate persisted synthetic message instead of a part on the user message); transient per-dispatchcontexthook OK — tool-continuation re-injection suppressed by turn-dedup keyed on latest real user message id; admission =session.prompt({ resume: false })(= v1 noReply, idempotent by id); codec/history port kept,decodeRawHistoryextended for message-level metadata on synthetic history messages; fixed synthetic id schema violation (prt_rules_→msg_-prefixed id viasyntheticMessageId()).Model.Ref.id({ providerID, id, variant? }— nomodelIDkey), internal field staysmodelID;V2SessionContextInput.modelretyped, hook-ref + history-assistant fixtures fixed (model: { id }); variant handled for free (Model.Ref.idis variant-free by construction,model#variantparses into its own key) —model:rules match the bare model id, no variant logic added; flatmodelIDmessage-info fields are v2 history shape, not matching inputs.session.compaction.started/endedcarrydata.sessionIDon beta-18999;session.compactedis a different event) for invalidation + lazy revision-guarded ledger re-decode at the first post-compaction durable turn (pendingCompactionHealflag; durable rules dropped by the summary re-append exactly once); Working-context projection is event-driven one-shot via acompactedflag on SessionState, riding the next context dispatch; v2 message parsing needed no further work (already landed via Port server entry + orchestrator to v2 plugin contract #71/Port delivery engine to session.synthetic + context hook #72).2.0.0-beta.1;oc-pluginfield dropped (v2 discovers viaexports:.+./tui);@opencode-ai/sdkremoved (unused; plugin context is the client — no@opencode-ai/clientmigration needed);effect/zod/@opencode-ai/aileft as plugin transitive deps (no direct imports); plugin peer range>=0.0.0-beta-18999 <0.0.1(literal0.0.0-beta-*is an invalid semver range — satisfies nothing); devDep stays exact-pinned beta-18999;release-beta.ymlpublishes--tag v2with containmentdev|v2; stable release job also excludes-rc; README/AGENTS install + release docs updated tov2dist-tag and the v2pluginsconfig key; files/exports unchanged (pack dry-run clean).{ id, setup(ctx) }typedPlugin.Definition(Pluginis a namespace export); claim =ctx.ui.slot({ append: 'sidebar.content', render }), input{ sessionID }; matched-rule state still read by direct fs (nodata/storagesecond source of truth); project dir viactx.data.session.root(sessionID); theme is flatResolvedTheme(text.default/text.subdued/text.feedback.success.default); refresh onctx.data.on('session.message.content.updated' | 'session.status')(data.sessionIDpayloads);@opencode-ai/client+@opencode-ai/themeadded as exact-pinned beta-18999 devDeps (real v2 types need them) and the vendored v1 type shim deleted; build emits{ id, setup }shape.Not yet specified
Out of scope
main).mainserves as passive reference.v2dist-tag; the stable flip waits for opencode v2 GA.