Internals and conventions for hacking on the library itself, for both human contributors and AI assistants. End-user documentation lives in the README.
Orca is implemented in Scala 3 on top of Ox for structured concurrency, tapir for JSON Schema derivation, and jsoniter-scala for codec generation. sbt 1.12+ is needed in addition to the runtime requirements listed in the README.
orca/
├── build.sbt / project/
├── tools/ # tool interfaces + os-backed impls + structured I/O + event bus
├── flow/ # FlowContext, stage/fail; orca.review (ReviewTypes, ReviewLoop, Reviewers); orca.bug; orca.plan
├── claude/ # Claude Code backend + DefaultClaudeTool + DefaultLlmCall
├── codex/ # Codex backend (codex exec --json over stdio)
└── runner/ # flow() entry + DefaultFlowContext + terminal layer
Dependency graph:
tools (standalone)
├── flow → tools
├── claude → tools
├── codex → tools
└── runner → tools + flow + claude + codex
The runner module owns the flow entry point (package orca) and wires
defaults via DefaultFlowContext (package orca.runner). Its terminal UI
lives in its own sub-package, orca.runner.terminal, so swapping it for a
Slack or HTTP equivalent is a matter of substituting one Interaction at
the call site rather than rewiring modules.
Only the user-facing surface lives in package orca (the flow entry
point, the tool traits, the accessors, JsonData, OrcaArgs).
Implementations live in focused subpackages: orca.tools.fs /
orca.tools.git / orca.tools.github (os-backed tool impls),
orca.tools.claude / orca.tools.codex (LLM backends), orca.subprocess
(subprocess shim), orca.io (structured-I/O plumbing), orca.runner /
orca.runner.terminal (wiring and terminal UI).
sbt compile # build every module
sbt test # unit tests across all modules
sbt "flow/test" # scope to one module
sbt "flow/testOnly orca.FixLoopTest" # scope to one suiteExtra Scala 3 warnings are enabled (-Wunused:all, -Wvalue-discard,
-Wnonunit-statement). They aren't fatal — fix them before committing
rather than relying on the compiler to block.
sbt scalafmtAll # reformat every source in place
sbt scalafmtCheckAll # fail if anything would reformatSome tests shell out to real external tools and skip by default:
ORCA_INTEGRATION=1 sbt test
ORCA_INTEGRATION=1 sbt "claude/testOnly orca.claude.ClaudeIntegrationTest"
ORCA_INTEGRATION=1 sbt "tools/testOnly orca.tools.OsGitHubIntegrationTest"
ORCA_INTEGRATION=1 sbt "runner/testOnly orca.runner.terminal.ScalaCliSmokeTest"| Suite | Needs |
|---|---|
ClaudeIntegrationTest |
claude authenticated |
OsGitHubIntegrationTest |
gh authenticated |
ScalaCliSmokeTest |
scala-cli; runs sbt publishLocal internally |
Unit tests use in-memory fakes (StubCliRunner, FakeLlmTool,
FakeCliProcess, TestFlowContext) — no network, no real filesystem
outside of os.temp.dir().
sbt --client <cmd>talks to a persistent sbt server; round-trips drop from ~20s to ~2s.sbt ~testre-runs tests on save.- Metals MCP is configured (
.metals/mcp.json), so AI-assisted tooling can query real type info across modules.
- Braceless syntax; explicit return types on every public member.
- No class-level
vars; mutable state stays in method bodies orAtomicReference-guarded test helpers. - Opaque-type aliases for domain string labels (e.g.
Title,SessionId). - Recoverable failures return
Either[E, T]whereE <: OrcaFlowException; system failures throw. Use Ox's.orThrowat the call site when the failure case is genuinely unexpected.
- Use proper packaging — related functionality lives in one package.
- Scaladoc describes contract and intent; implementation notes go in inline
//comments alongside the code. - Tests target exactly one scenario each.
- Tool event sinks take
OrcaListener(defaultOrcaListener.noop). - Domain helpers that bundle an LLM brief follow
ADR 0010: sibling
XxxPromptsobject +instructions: String = …parameter. - Subprocesses launched from a tool must capture stderr — go through
subprocess.QuietProc.callor aCliRunner. os-lib defaultsos.proc(...).call(...)'sstderrtoInherit, which lets subprocess output bypass the renderer's StatusBar and tear the spinner row.
The direct-style-scala plugin codifies the Scala-style bullets; re-reading
its chapters before a non-trivial change is recommended.
sbt publishLocalInstalls org.virtuslab::orca:0.0.2 plus its transitive modules
(orca-tools, orca-flow, orca-claude, orca-codex) into
~/.ivy2/local so a flow script with //> using repository ivy2Local can
resolve them.
For an iteration loop while hacking on Orca itself, run sbt in one
terminal with a ~ watch-and-publish:
sbt "~publishLocal"Every save rebuilds the affected module and refreshes ~/.ivy2/local.