- Use Aube and install with
aube install --frozen-lockfile. Run checks in this order:aube run lint->aubx tsc --noEmit->aube run test:run. - There is no
typecheckscript; typecheck withaubx tsc --noEmit. - Run one colocated Vitest file with
aube run test:run src/<domain>/<name>.test.ts(or a path undertui/).tsconfig.jsonexcludes test/spec files, sotscdoes not typecheck them. docs/silent-message-implementation.mddescribes a superseded design; current delivery is synthetic parts viachat.message.
- One package, two default plugin exports: server entry
src/index.tsreturns{ id, server }; TUI entrytui/index.tsxreturns{ id, tui }. The package root imports builtdist/src/index.js, and"./tui"imports builtdist/tui/index.js(its types come fromdist/tui/index.d.ts). - OpenCode's plugin loader calls every named export as a plugin initializer; any added named export must be callable (see the
__testOnlypattern insrc/index.ts). - Each injection event is one
<system-message>block with one preamble and<rule name="...">blocks using frontmatternameor the filename stem. Session-durable rules (unconditional, globs, fileContains, keywords, command, project, os, ci) are injected as one synthetic text part appended to the user message viachat.message, not into the system prompt. A durable rule first matched by a live File observation is instead admitted at the earliest applicable dispatch through an awaited no-replysession.prompt, with transient fallback and retry when persistence fails. Ephemeral rules (agent, model, branch, tools) are delivered as one transient synthetic message per matching turn viaexperimental.chat.messages.transformand never persisted. Livetool.execute.afterobservations are the sole matching source for globs/fileContains; history rebuilds Working-context paths (compaction projection) and delivery-ledger identity only, never File observations. Path-derived identity keys prevent durable rules still present in history from being re-appended after content edits or resume; message removal invalidates the ledger so reverted delivery is retried, and after compaction the ledger is rebuilt so missing durable rules are re-appended. Hook guidance uses the same framing. - Depends on experimental OpenCode hooks (
experimental.chat.messages.transform,experimental.session.compacting); re-verify against@opencode-ai/pluginwhen upgrading. - Per-session matched-rule state is written atomically to
~/.opencode/state/opencode-rules/{sessionID}.json, which the TUI sidebar reads.
- ESM with NodeNext resolution: relative imports need
.jsextensions even in.ts/.tsxsource. - The
"./tui"package export must point to./dist/tui/index.js, not raw./tui/index.tsx: OpenCode/Bun does not reliably remap.jsrelative imports when loading raw TSX, while those targets exist only after the TypeScript build. - OpenCode caches npm plugin specs by their literal specifier; an existing
~/.cache/opencode/packages/opencode-rules@latestwrapper pins the version resolved when it was created and does not refresh whenlatestchanges. Clear that cache or use an explicit new version when validating a release. - tsconfig is strict-plus (
exactOptionalPropertyTypes,noUnusedLocals/noUnusedParameters,verbatimModuleSyntax), so type-only imports and unused symbols will fail typecheck even though lint passes. - Server source is grouped by domain:
src/rules/(discovery, metadata, filter, hooks),src/delivery/(delivery engine composed behindcreateRuleDeliveryplus codec and history port),src/session/(session/matched-rule state, file observations, message extraction),src/runtime/(orchestrator, client adapter, tool-hook flow, match context, chat capture),src/detection/(git-branch, project-fingerprint, mcp-tools),src/shared/(debug, bounded-session-map).src/api-surface.typecheck.tsenforces intentionally private exports duringtsc. - Do not edit generated
dist/;tscbuilds it fromsrc/andtui/. - This repo dogfoods its own plugin:
.opencode/rules/*.mdare injected into sessions and contain additional scoped guardrails. - When adding/removing/renaming production modules, update the README "Project Structure" section in the same change (
.opencode/rules/11-readme-and-doc-sync.md).
- Stable publishing triggers on non-alpha/non-beta
v*tags (the workflow does not verify branch containment). Beta tags must be contained bydevand publish with the npmbetadist-tag.
Issues are tracked in GitHub Issues for frap129/opencode-rules, using the gh CLI. See docs/agents/issue-tracker.md.
Default five-role vocabulary: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See docs/agents/triage-labels.md.
Single-context: CONTEXT.md at the repo root plus docs/adr/ for decisions. See docs/agents/domain.md.