Development
Building, testing, and extending yottacode.
Build
go build -o yottacode ./cmd/yottacodeRequirements: Go 1.25+. The module is pure Go, so cross-compilation is straightforward:
GOOS=darwin GOARCH=arm64 go build -o yottacode-darwin-arm64 ./cmd/yottacode
GOOS=linux GOARCH=amd64 go build -o yottacode-linux-amd64 ./cmd/yottacode
GOOS=windows GOARCH=amd64 go build -o yottacode-windows.exe ./cmd/yottacodeTest
go test ./... # unit tests, fast, no live deps
go test -tags=integration ./... # adds live-provider tests
go test -race ./...
go test -cover ./...Standing rules:
- Unit tests must not require network, GPU, or external services.
- Integration tests belong behind
//go:build integration. - Bug fixes should ship with a regression test.
- A change is not done until
go test ./...is green.
Adding A Built-In Tool
Implement agent.Tool and register the tool in
both entry points:
Conventions:
- Resolve relative paths against the session working directory.
- Require approval for mutating behavior.
- Cap output so tool results do not explode transcript size.
- Return errors as normal errors, not panics.
- Add tests for schema, happy path, edge cases, and approval behavior.
Adding A Slash Command
Add a slashCommand entry in
internal/tui/commands.go and implement the
handler with the usual (Model, tea.Cmd) signature.
If the command takes arguments, set the Args field so the palette can insert
the command prefix instead of executing it immediately.
Refreshing The Embedded Model Catalog
The cloud provider model lists for Anthropic, OpenAI, and Gemini live
in internal/catalog/catalog.gen.json,
embedded into the binary at build time. When a provider ships new
models (or deprecates old ones), regenerate the file:
ANTHROPIC_API_KEY=โฆ OPENAI_API_KEY=โฆ GEMINI_API_KEY=โฆ \
go run ./cmd/yotta-models refreshOptional flags:
go run ./cmd/yotta-models refresh --output internal/catalog/catalog.gen.jsonA missing key skips that provider with a warning; existing entries for that provider in the output file are preserved untouched, so you can refresh one provider at a time.
The script:
- Calls each provider’s list-models endpoint (Anthropic
/v1/models, OpenAI/v1/models, Gemini/v1beta/models). - Filters OpenAI to chat-completions models via prefix regex
(
fetch_openai.go) โ drop embeddings/tts/audio/realtime/image variants. Widening for new families (e.g.gpt-6,o5) is a one-line PR. - Filters Gemini to entries whose
supportedGenerationMethodscontains"generateContent"โ drops embedding-only and countTokens-only models. - Maps each row onto the common
catalog.Modelschema with tristate (*bool) capability flags so renderers can distinguish “supported” / “not supported” / “unknown.” - Sorts the result by (provider, ID) so diffs across runs are minimal.
Commit the refreshed catalog.gen.json like any other source file.
There is no runtime equivalent โ users can’t refresh locally because
they don’t necessarily have keys for all three providers, and the
embedded catalog keeps offline-first ergonomics. Anything else
(Ollama, openai-compatible endpoints) is fetched live at picker-open
time via catalog.Live.
Adding Or Expanding A Model Adapter
Today the adapter layer supports OpenAI-compatible chat-completions endpoints
plus automatic OpenAI Responses routing for supported reasoning models. The
agent depends only on the streaming interface, so provider-specific expansion
belongs in internal/adapter rather than internal/agent.
Provider Profiles, Diagnostics, And Probes
Provider-specific setup and validation now have two layers:
- Static diagnostics in
internal/adapter/provider.goandinternal/adapter/diagnostics.go - Active probes in
internal/adapter/diagnostics.go
The split is intentional:
ProviderProfileis the resolved static view of a config:- detected provider
- routing choice (
chat-completionsvs Responses) - supported native capabilities
- enabled built-in tools
- static issues and warnings
Probe(ctx, cfg)performs a lightweight live check against the configured endpoint, currently via/models, and reports:- endpoint reachability
- auth status
- selected-model visibility
- the same resolved profile and static diagnostics
Keep these rules when extending provider support:
- Put provider-specific validation in
internal/adapter, not intui,oneshot, orcmd. - Treat
/provider,/doctor, andyottacode doctoras consumers of the diagnostics API, not as the implementation. - Prefer cheap, broadly-supported probes first.
/modelsis useful because it distinguishes network, auth, and model-visibility failures without spending prompt tokens. - Keep probes side-effect free. They should validate configuration, not mutate provider state.
- Return structured data first; format human-readable output at the edge.
When adding a new provider capability check:
- Extend
adapter.ConfigandProviderProfileif the capability is part of the resolved static surface. - Add static validation in
providerDiagnostics(...). - Add live validation in
Probe(...)only if it can be done cheaply and reliably across that provider’s endpoint shape. - Cover both layers with tests:
internal/adapter/*_test.gofor static and probe behavior- consumer tests only for rendering / command wiring
CLI And TUI Consumers
The current diagnostics consumers are:
- TUI startup/background probing in
internal/tui/model.go - static
/provideroutput ininternal/tui/commands.go - active
/doctoroutput ininternal/tui/commands.go - shell/CI command
yottacode doctorincmd/yottacode/main.go - oneshot preflight fail-fast in
internal/oneshot/oneshot.go
If you add a new diagnostics field, update the adapter tests first, then only the consumers that need to display it.
Project Layout Reminder
cmd/yottacode/ cobra root
internal/cli/ option resolution
internal/adapter/ provider streaming layer
internal/agent/ turn loop, tools, approvals
internal/session/ saved conversations
internal/memory/ prompt memory composer and agent-managed memory store
internal/recall/ FTS5 session search
internal/tui/ interactive terminal UI
internal/oneshot/ one-shot runner
internal/version/ version stringRelease Versioning
The release number lives in internal/version/version.go:
const Current = "0.1.0"Use semantic versioning: MAJOR.MINOR.PATCH.
- Bump
PATCHfor backward-compatible bug fixes. - Bump
MINORfor backward-compatible features. - Bump
MAJORfor breaking changes.
Examples:
0.1.0->0.1.1: bug-fix release0.1.0->0.2.0: new feature release without a stable-API commitment yet0.1.0->1.0.0: first stable public release1.2.3->2.0.0: breaking change
Before 1.0.0, treat 0.x as pre-stable. That means breaking changes may
still happen before the first stable release, even if they land in a minor
bump.