Development Patterns

This page turns architecture boundaries into contributor decisions. Read Architecture Overview and relevant subsystem page first, then use this guide before editing architecture-facing code in Kilo-Org/kilocode or its cross-repository contracts.

ℹ️Default rule

Prefer Kilo-owned seams over broad changes to shared OpenCode files. Follow neighboring style when changing existing modules.

How to use this page

  1. Identify owning subsystem in architecture docs.
  2. Choose narrowest source boundary that can hold change.
  3. Update generated or cross-repository contracts when public surface changes.
  4. Run smallest relevant checks plus affected repository guards.

Where should change live?

Change shapePreferred location or actionReason
Additive Kilo CLI behaviorpackages/opencode/src/kilocode/Keeps Kilo-only behavior out of upstream-owned files
Kilo CLI test for additive behaviorpackages/opencode/test/kilocode/Avoids shared tests that encode only Kilo behavior
Required shared OpenCode editSmall import, route, or injection seam in shared file plus kilocode_change markerKeeps upstream diff narrow and merge review obvious
VS Code, JetBrains, docs, indexing, UI, gateway, or telemetry changeExisting Kilo-owned packageThese packages are Kilo-owned; do not add kilocode_change markers
CLI server endpoint changeEffect HttpApi route plus handler; then run root SDK generatorKeeps server contract and generated JavaScript SDK aligned
JetBrains API contract changeShared CLI OpenAPI change; let Gradle regenerate build-local Kotlin clientKotlin client is generated during JetBrains build
Kilo-only config-key changeUpdate CLI Effect Schema and cloud JSON Schema overlayRuntime acceptance and editor validation are separate cross-repository paths
Docs page move or removalUpdate nav and add permanent redirectPreserves external links and bookmarks

Kilo-owned boundaries

Kilo CLI forks upstream OpenCode. Prefer Kilo-owned directories and packages for additive behavior:

PreferAvoid unless necessary
packages/opencode/src/kilocode/Broad edits to shared packages/opencode/src/ files
packages/opencode/test/kilocode/Shared tests that encode only Kilo behavior
packages/kilo-vscode/, packages/kilo-jetbrains/, packages/kilo-docs/, packages/kilo-indexing/Moving Kilo-only behavior into upstream-owned modules
Narrow import or route seams in shared filesRefactors that enlarge upstream merge conflicts

Shared OpenCode files

Use kilocode_change markers when Kilo-specific code must modify shared upstream files.

Change shapeMarker
One lineTrailing // kilocode_change
Multi-line block// kilocode_change start and // kilocode_change end
New file in shared pathTop-level // kilocode_change - new file
JSX or TSXJSX comment equivalents

Marker exemptions apply to paths already owned by Kilo, including paths whose names contain kilocode and Kilo packages such as packages/kilo-vscode/ or packages/kilo-ui/. Do not add markers there.

GuardWhen to run
bun run script/check-opencode-annotations.tsPR touches packages/opencode/; verifies shared OpenCode Kilo edits are annotated
bun run script/check-opencode-promise-facades.tsService adapter changes; prevents new runtime-backed Promise facades in shared Effect services
bun run check-kilocode-change from packages/kilo-vscode/VS Code or Kilo UI changes; markers must not appear in fully Kilo-owned packages
bun run script/check-workflows.tsWorkflow add or remove changes; keeps workflow allowlist explicit

CLI server API

CLI server uses Effect HttpApi and publishes OpenAPI-compatible HTTP + SSE surfaces consumed by JavaScript SDK and JetBrains build-local Kotlin client.

RuleReason
Define shared routes under packages/opencode/src/server/routes/instance/httpapi/Keeps route contract close to runtime handlers
Normalize public spec in packages/opencode/src/server/routes/instance/httpapi/public.tsPreserves legacy-compatible request and response shapes during Effect migration
Put additive Kilo groups and handlers under packages/opencode/src/kilocode/server/httpapi/Reduces edits in shared upstream-owned files
Inject Kilo APIs through narrow shared seamKeeps upstream diff small and marker placement obvious
Preserve route spans and stable attributesKeeps diagnostics and telemetry understandable

SDK generation

CLI Runtime SDK contract owns generation pipeline detail. Contributor rules are short:

ChangeAction
Add or change CLI server endpointRun root ./script/generate.ts after route and handler edits
JavaScript SDK generated files under packages/sdk/js/src/v2/gen/Do not edit by hand
JavaScript SDK wrapper behaviorEdit handwritten packages/sdk/js/src/v2/client.ts
JetBrains generated Kotlin clientLet Gradle regenerate build-local client from normalized OpenAPI

CLI config schema

Runtime config loading and editor validation are separate paths. New Kilo-only config key requires CLI Effect Schema change in Kilo-Org/kilocode and JSON Schema overlay change in Kilo-Org/cloud. Follow CLI Config Schema for exact workflow.

Module export pattern

For new public APIs, prefer flat ESM exports inside module, then namespace re-exports from index files when grouped access helps callers.

// packages/opencode/src/session/session.ts
export const create = fn(CreateSchema, async (input) => {
  // ...
})

export const list = fn(ListSchema, async (input) => {
  // ...
})

// packages/opencode/src/session/index.ts
export * as Session from "./session"

Import specific export when practical. Use namespace shape (Session.create) when preserving existing API or grouped module access improves clarity. Existing Kilo-owned namespaces remain valid; do not refactor them solely for style.

Tool implementation

Tools use Tool.define("id", Effect.gen(...)) with Effect Schema validation and typed execution.

export const ExampleTool = Tool.define(
  "example",
  Effect.gen(function* () {
    return {
      description: "Example tool",
      parameters: Schema.Struct({
        value: Schema.String,
      }),
      execute(args) {
        return Effect.succeed({
          title: args.value,
          metadata: {},
          output: args.value,
        })
      },
    }
  }),
)

Reuse tool helpers, permission gates, and telemetry conventions before adding abstractions. Tests should exercise implementation behavior rather than duplicating logic in mocks.

Build system

AreaTooling
Package managerBun workspaces
Task orchestrationTurborepo
CLI executableBun compile build in packages/opencode/script/build.ts
VS Code extension and webviewsesbuild
JetBrains pluginGradle, Kotlin JVM toolchain 21, build-local OpenAPI generation
Type checkingtsgo through bun turbo typecheck; Gradle compile checks for JetBrains
TestsPackage-level Bun test, Vitest, or Gradle test depending on package
DocsNext.js, Markdoc, Mermaid, and custom Markdoc components

Documentation changes

When adding or moving docs pages:

  • Create page under pages/.
  • Update matching navigation file in lib/nav/.
  • Add redirects when removing or moving routes.
  • Use compact markdown tables with unpadded cells.
  • Use /docs prefix for docs image paths.

Source map

Paths below are relative to Kilo-Org/kilocode.

ConcernSource path
Tool definition APIpackages/opencode/src/tool/tool.ts
Tool examplepackages/opencode/src/tool/read.ts
Server APIspackages/opencode/src/server/routes/instance/httpapi/
Public OpenAPI normalizationpackages/opencode/src/server/routes/instance/httpapi/public.ts
Kilo route seampackages/opencode/src/kilocode/server/httpapi/
JavaScript SDK generationpackages/sdk/js/script/build.ts
script/generate.ts
JetBrains client generationpackages/kilo-jetbrains/backend/build.gradle.kts
Upstream merge automationscript/upstream/

Upstream merge workflow

bun install runs script/setup-git.ts, which sets repo-local merge conflict style to zdiff3. Base-aware markers make manual resolution and syntax-aware tooling more useful. Upstream automation under script/upstream/ applies transforms before merge, forces zdiff3 for merge operation, and runs mergiraf against remaining textual conflicts. mergiraf is required by merge script.

From script/upstream/, use:

bun run analyze.ts --version <tag>
bun run merge.ts --version <tag> --dry-run
bun run merge.ts --version <tag>

Keep Kilo-specific logic extracted, shared seams narrow, markers accurate, and CI guards green before upstream merge work lands.