Instructions for AI coding agents working with this codebase.
Source code for dependencies is available in opensrc/ for deeper understanding of implementation details.
See opensrc/sources.json for the list of available packages and their versions.
Use this source code when you need to understand how a package works internally, not just its types/interface.
To fetch source code for a package or repository you need to understand, run:
npx opensrc <package> # npm package (e.g., npx opensrc zod)
npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)These repos are pre-fetched for deeper debugging and implementation reference:
| Source | Path | Purpose |
|---|---|---|
gitbutlerapp/gitbutler |
opensrc/repos/github.com/gitbutlerapp/gitbutler/ |
GitButler CLI/action crates (but cursor, but rub, but reword, but-action) |
anomalyco/opencode |
opensrc/repos/github.com/anomalyco/opencode/ |
OpenCode host runtime and plugin SDK internals |
Key GitButler paths for debugging plugin behavior:
opensrc/.../gitbutler/crates/but-cursor/src/lib.rs--but cursor after-edit/stophandlersopensrc/.../gitbutler/crates/but-action/src/-- shared commit, reword, rename logicopensrc/.../gitbutler/crates/but/src/command/-- CLI command implementations (rub,reword,status)
Runtime: Bun (not Node). All commands use bun.
# Install dependencies
bun install
# Type-check (no emit)
bunx tsc --noEmit
# Build (ESM bundle + declaration files)
bun run build
# Run all tests
bun test
# Run a single test file
bun test src/__tests__/config.test.ts
# Run tests matching a pattern
bun test --grep "stripJsonComments"The build produces dist/index.js (ESM) and dist/index.d.ts (declarations).
Publishing is automated via GitHub Actions (.github/workflows/publish.yml). Do NOT run npm publish locally.
# 1. Bump version
npm version patch --no-git-tag-version # or minor/major
# 2. Commit and push
git add package.json
git commit -m "chore: bump to $(jq -r .version package.json)"
git push origin main
# 3. Create and push tag — this triggers the publish workflow
git tag "v$(jq -r .version package.json)"
git push origin "v$(jq -r .version package.json)"The workflow runs test → typecheck → build → npm publish (via OIDC, no token needed) → GitHub Release with auto-changelog.
src/
index.ts -- Entry point, public exports, command loader, plugin wrapper
plugin.ts -- Core plugin: hooks, branch mgmt, reword, cleanup, context injection
config.ts -- Config loading from .opencode/gitbutler.json (JSONC)
auto-update.ts -- npm registry version check (best-effort, never throws)
__tests__/ -- bun:test test files
skill/ -- GitButler skill files bundled with the package
command/ -- Slash-command markdown templates (b-branch, b-branch-commit, b-branch-pr)
docs/ -- Architecture docs (gitbutler-integration.md)
These exports from src/index.ts are the public surface -- do not rename, remove, or change signatures:
export default GitButlerPlugin; // Plugin function (default export)
export { GitButlerPlugin }; // Named re-export
// Re-exported from config.ts:
export { DEFAULT_CONFIG, loadConfig, stripJsonComments };
export type { GitButlerPluginConfig };- Target: ES2020, module: ESNext, strict mode enabled
- Module resolution:
bundler-- use.jsextensions in imports ("./config.js") - No
as any,@ts-ignore, or@ts-expect-error-- fix the type properly - Prefer
typeimports for type-only usage (import type { ... }) - Empty catch blocks must have a comment explaining why (e.g.,
// best-effort, ignore failure)
- 2-space indentation
- Double quotes for strings
- Semicolons required
- Trailing commas in multiline constructs
camelCasefor variables, functions, parametersPascalCasefor types, interfaces, classesUPPER_SNAKE_CASEfor constants and config keys in codesnake_casefor config field names inGitButlerPluginConfig(matches JSON)- Prefix private/internal helpers with descriptive verbs:
findFileBranch,toRelativePath,extractEdits
- Functions that call external processes (
Bun.spawnSync,Bun.spawn) must wrap in try/catch - Return
nullorfalseon failure -- never throw from helper functions - The plugin must never crash the host. All hooks must be fault-tolerant.
- Log errors via the structured logger (
log.error(category, data)) - Use bounded retries with exponential backoff for transient failures (see
butCursor)
- All runtime events go through the structured NDJSON logger (
createLogger) - Each log entry is
{ ts, level, cat, ...data }-- one JSON object per line - Use standardized category names:
cursor-ok,cursor-error,rub-ok,reword,lock-acquired, etc. - Never log secrets, tokens, or full file contents
- Test framework:
bun:test(built into Bun) - Tests live in
src/__tests__/*.test.ts - Use
describe/testblocks,expectassertions - Mock external dependencies (e.g.,
globalThis.fetch) in tests, restore inafterEach - Use temp directories (
mkdtemp) for filesystem tests, clean up inafterEach
- Do not add runtime dependencies beyond
@opencode-ai/plugin @types/bunandtypescriptare dev-only- No version bumps in
package.jsonwithout explicit request
This plugin bridges OpenCode to GitButler by impersonating Cursor via but cursor CLI.
See docs/gitbutler-integration.md for the full architecture, feature comparison, and known issues.
Key flows:
- Edit --
tool.execute.after-> lock check ->findFileBranch->but cursor after-editorbut rub - Stop --
session.idleevent ->but cursor stop->postStopProcessing(reword + rename + cleanup) - Context --
experimental.chat.messages.transform-> inject<system-reminder>with accumulated notifications