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 Voyager-Org/voyager or its cross-repository contracts.

ℹ️Default rule

Prefer Voyager-owned seams over broad changes to shared VoyagerCode 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 Voyager CLI behaviorpackages/voyagercode/src/voyager/Keeps Voyager-only behavior out of upstream-owned files
Voyager CLI test for additive behaviorpackages/voyagercode/test/voyager/Avoids shared tests that encode only Voyager behavior
Required shared VoyagerCode editSmall import, route, or injection seam in shared file plus voyager_change markerKeeps upstream diff narrow and merge review obvious
VS Code, JetBrains, docs, indexing, UI, gateway, or telemetry changeExisting Voyager-owned packageThese packages are Voyager-owned; do not add voyager_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
Voyager-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

Voyager-owned boundaries

Voyager CLI forks upstream VoyagerCode. Prefer Voyager-owned directories and packages for additive behavior:

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

Shared VoyagerCode files

Use voyager_change markers when Voyager-specific code must modify shared upstream files.

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

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

GuardWhen to run
bun run script/check-voyagercode-annotations.tsPR touches packages/voyagercode/; verifies shared VoyagerCode Voyager edits are annotated
bun run script/check-voyagercode-promise-facades.tsService adapter changes; prevents new runtime-backed Promise facades in shared Effect services
bun run check-voyager-change from packages/voyager-vscode/VS Code or Voyager UI changes; markers must not appear in fully Voyager-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/voyagercode/src/server/routes/instance/httpapi/Keeps route contract close to runtime handlers
Normalize public spec in packages/voyagercode/src/server/routes/instance/httpapi/public.tsPreserves legacy-compatible request and response shapes during Effect migration
Put additive Voyager groups and handlers under packages/voyagercode/src/voyager/server/httpapi/Reduces edits in shared upstream-owned files
Inject Voyager 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 Voyager-only config key requires CLI Effect Schema change in Voyager-Org/voyager and JSON Schema overlay change in Voyager-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/voyagercode/src/session/session.ts
export const create = fn(CreateSchema, async (input) => {
  // ...
})

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

// packages/voyagercode/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 Voyager-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/voyagercode/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 Voyager-Org/voyager.

ConcernSource path
Tool definition APIpackages/voyagercode/src/tool/tool.ts
Tool examplepackages/voyagercode/src/tool/read.ts
Server APIspackages/voyagercode/src/server/routes/instance/httpapi/
Public OpenAPI normalizationpackages/voyagercode/src/server/routes/instance/httpapi/public.ts
Voyager route seampackages/voyagercode/src/voyager/server/httpapi/
JavaScript SDK generationpackages/sdk/js/script/build.ts
script/generate.ts
JetBrains client generationpackages/voyager-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 Voyager-specific logic extracted, shared seams narrow, markers accurate, and CI guards green before upstream merge work lands.