How to contribute: layer ownership, testing strategy, test inventory, review checklist, and debugging.
This page covers everything you need to make changes to vm-system safely: where code belongs, how to test it, what reviewers look for, and how to debug when things break.
If you haven't run vm-system yet, start with vm-system help getting-started
and come back here when you're ready to change code.
vm-system has clean layer boundaries, and respecting them is the most important thing you can do for code quality. Before writing code, figure out which layer owns the behavior you're changing:
pkg/vmtransport/http (server*.go). This is where JSON is decoded,
fields are validated, and domain errors are mapped to HTTP status codes.pkg/vmcontrol (*_service.go, core.go). Business rules like "what
defaults does a new template get?" and "is this file path safe?" live here.pkg/vmsession and pkg/vmexec. This is where JavaScript actually runs,
where the console shim captures output, and where execution locks are
managed.pkg/vmstore (vmstore*.go). All database queries and schema definitions.cmd/vm-system (cmd_*.go). Flag parsing, output formatting, and help
text.pkg/vmclient (*_client.go). The typed HTTP client used by CLI commands.internal/web (spa.go, publicfs_*.go, generate.go). API/static
composition, embed loading, and frontend asset generation.ui/ (Vite + React + RTK Query). Browser UI and API client behavior.The most common mistake is leaking HTTP-specific logic into vmcontrol, or putting domain policy in a handler. When in doubt, ask yourself: "Does this code need to know about HTTP requests?" If no, it belongs in vmcontrol or below.
For most changes, this is all you need:
GOWORK=off go test ./pkg/vmtransport/http -count=1 # integration tests
GOWORK=off go test ./... -count=1 # all Go tests
bash ./test/scripts/smoke-test.sh # daemon sanity (~10s)
pnpm -C ui check # frontend type-check
Before opening a PR, run the full suite:
bash ./test/scripts/test-all.sh
pnpm -C ui run build
go generate ./internal/web
GOWORK=off go build -tags embed -o vm-system ./cmd/vm-system
Always prefix Go test commands with GOWORK=off — without it, workspace mode
can cause confusing import errors.
The integration tests in pkg/vmtransport/http are the most important tests
in the codebase. Each test spins up a complete in-memory stack — a real SQLite
store, the full vmcontrol core, and an httptest.Server with the actual HTTP
handler. No mocks anywhere. This means they exercise the entire request path
from HTTP decoding through domain logic to database persistence.
Here's what each test file covers:
after_seq cursor-based paginationMODULE_NOT_ALLOWED response for JavaScript built-insSeveral packages have focused unit tests that don't need the full stack:
vmpath (path normalization), vmmodels (ID parsing, JSON helpers),
vmdaemon/config (configuration defaults), libloader (loading files from
cache), vmclient (URL construction, error parsing), vmcontrol/template_service
(template creation defaults), and vmexec (executor behavior and persistence
failure handling).
The shell scripts test the system from the outside, the way a real user would:
All scripts use temporary databases, temporary worktrees, and dynamically allocated ports. They're safe to run in parallel with other work.
The test suite has strong coverage of the core operational path: template/session/execution endpoints, error contracts for all status codes, path traversal rejection, output limit enforcement, module allowlist behavior, and the full CLI happy path.
Known gaps that could use attention: daemon restart recovery (what happens to
sessions that are "ready" in the DB but have no runtime), load and concurrency
testing beyond the basic SESSION_BUSY contract, library cache filename
consistency between the downloader and the loader, and process crash durability
testing.
Frontend validation is currently lightweight (pnpm -C ui check and
pnpm -C ui run build). There is no broad UI integration/e2e suite yet.
This is the most common type of change. Follow the existing pattern:
server.go (look at how existing routes are registered)pkg/vmcontrol if the endpoint needs new
business logicvmclient wrapper so the CLI can call iterror.code field in the JSON envelopeWhen the system needs to distinguish a new failure case:
pkg/vmmodels/models.gowriteCoreError in server_errors.go — this is the single
place where domain errors become HTTP responsesSession state transitions are the most sensitive part of the system:
SessionManager in pkg/vmsessionFollow the pattern established by existing tests:
func TestMyBehavior(t *testing.T) {
store := vmstore.NewMemoryStore(t)
core := vmcontrol.NewCore(store)
server := httptest.NewServer(vmhttp.NewHandler(core))
defer server.Close()
template := createTestTemplate(t, server.URL)
session := createTestSession(t, server.URL, template.ID)
resp, err := http.Post(server.URL+"/api/v1/executions/repl", ...)
assert.Equal(t, 201, resp.StatusCode)
// parse response body and assert on specific fields...
}
The test helpers (createTestTemplate, createTestSession) handle the
boilerplate of creating prerequisite resources.
Before requesting review, check these:
Use scope prefixes in commit messages so the history is scannable:
feat(api): add execution-not-found 404 contract
test(integration): add execution get not-found test
docs(help): update api-reference for new endpoint
Stage intentionally. Check git diff --staged before committing — it's easy
to accidentally include debug prints or scratch files.
If you're looking for something bounded and meaningful for a first PR, here are five options that are practical, well-scoped, and directly improve the system:
API issues: Always reproduce with curl first. This isolates CLI formatting from actual server behavior:
curl -i -sS http://127.0.0.1:3210/api/v1/templates/does-not-exist
Compare the status code and error envelope against the writeCoreError
mapping in server_errors.go. If the error code doesn't match what you
expect, that's where to look.
Session and runtime issues: Check what the daemon thinks is alive:
vm-system session list --status ready
vm-system ops runtime-summary
If session list shows sessions as "ready" but runtime-summary shows zero
active sessions, the daemon was restarted and the runtimes are gone.
Execution issues: Run a minimal REPL snippet and inspect every event:
vm-system exec repl <session-id> '1+1'
vm-system exec events <execution-id> --after-seq 0
Look at the event types and ordering. If you're getting unexpected exceptions, the stack trace in the exception event usually points to the problem.
Persistence issues: Always use throwaway database paths when debugging. Stale databases from previous runs cause confusing behavior — you'll see old templates, old sessions, and wonder why your changes aren't taking effect.
Test failures: For integration tests, read the assertion message first —
it usually shows expected vs actual status codes or response bodies. Then check
whether writeCoreError maps the error correctly. Re-run with -v for full
output. For shell scripts, find the last line of successful output and try
the failing command manually.
vm-system help architecture — how the layers fit together and whyvm-system help api-reference — endpoint contracts and error codesvm-system help getting-started — first-run walkthrough