Thanks for your interest in contributing. This document explains how the codebase is organized and how to land a good PR.
Please read and follow our Code of Conduct. Be kind, be specific, assume good intent.
git clone https://github.com/your-org/stemstudio.git
cd stemstudio
git submodule update --init --recursive
bun install
bun run devOpen http://localhost:5173 and follow the first-time bootstrap.
For AI features, copy .env.example to .env and fill in whichever provider keys you want — or skip them all and the editor will prompt you when needed.
client/
packages/
editor-oss/ ← Editor, player, runtime, behaviors, lambdas,
physics, render, multiplayer client, Monaco,
AI/persistence/asset interfaces.
shared/ ← Thin re-export shims plus app boot.
network/ ← HTTP client adapters (api/scene, api/asset, etc.).
stemstudio-multiplayer/ ← Colyseus server. Runs as a sidecar in dev
(bun run dev:mp); deploy standalone for production.
server/
cmd/ai-server/ ← Go AI proxy entry point. Forwards calls to
Anthropic, OpenAI, Meshy, ElevenLabs, AnythingWorld
using env keys or BYOK keys forwarded by the editor.
server/controllers/tools/ai/ ← AI handler implementations.
server/controllers/tools/ai/byok/ ← BYOK key resolution.
scripts/ ← Dev scripts.
docs/ ← Contributor and user documentation.
A few practical constraints keep the codebase consistent:
- AI calls go through
AIBackend(client/packages/editor-oss/src/ai/AIBackend.ts). The default implementation talks to the local AI server. BYOK keys are stored client-side viaBYOKKeyStore(IndexedDB-backed, optional passphrase encryption). - Auth goes through
IAuthProvider(client/packages/editor-oss/src/auth/IAuthProvider.ts). The defaultNullAuthProviderreturns a dummy local user so backend requests can carry a stable token. - Analytics goes through
IAnalyticsRecorder(default: no-op). - Remote docs go through
IRemoteDocStore(default: no-op). - Project save/load goes through
ProjectStore(client/packages/editor-oss/src/persistence/ProjectStore.ts). Implementations:IndexedDBProjectStore,FileSystemProjectStore. - Copilot goes through
ICopilotProvider. Wire in an ACP-compatible bridge to enable it.
If you need a capability these interfaces don't expose, extend the interface — don't bypass it.
If you add a behavior that uses multiplayer or AI, it must degrade gracefully when the sidecar isn't running or no AI key is configured. Log a clear message and no-op — don't crash, don't pop modals on every frame.
bun run lint # full repo lint
bun run lint:oss-boundary # narrow gate on packages/editor-oss/lint:oss-boundary runs client/eslint.boundary.cjs and rejects imports from packages that aren't part of this repo. Keep editor-oss/ self-contained.
- Branch from
main. Use a descriptive name:feat/...,fix/...,docs/...,refactor/.... - One topic per PR. Smaller is better.
- Tests: add or update tests for the change. Bun's test runner is the default; Vitest is also supported.
- Verification before submitting:
All five must pass.
bun run typecheck bun run lint bun run lint:oss-boundary bun run test bun run build - PR description template:
- What: one-paragraph summary.
- Why: the user problem or technical motivation.
- How: brief design notes if the change is non-trivial.
- Test plan: what you did to verify, including OSS-boundary checks.
Reviewers will look for:
- No new imports in
editor-oss/from packages outside this repo. - AI / persistence / asset code paths go through the interfaces.
-
bun run devstill boots cleanly end-to-end. - First-time bootstrap modal still works on a fresh IndexedDB.
- If the change touches behaviors: behavior lifecycle is respected, Three.js resources are disposed, no leaked timers/listeners.
- If the change touches multiplayer: graceful degradation when sidecar is unavailable.
- If the change touches AI: graceful degradation when no key is configured.
- Tests added or updated.
The fastest way to learn the codebase is to add a behavior. Short version:
- Create
client/packages/editor-oss/src/behaviors/packs/<category>/myBehavior.ts. - Extend
Behavior. Overrideinit(game),update(dt), and any event handlers you need. - Register the behavior type in the appropriate pack
index.ts. - Add a test under
client/packages/editor-oss/src/behaviors/packs/<category>/__tests__/myBehavior.test.ts. - Document any attributes in JSDoc on the class.
Read client/packages/editor-oss/src/behaviors/Behavior.ts first — that's the base class plus the lifecycle contract.
The AI server registers providers by environment variable. To add a new one:
- Add a provider client under
server/server/controllers/tools/ai/helpers/(mirror the existingclaude.go/openai.goshape). - Register the provider in
server/server/controllers/tools/ai/byok/resolve.go'sProviderEnvVarsmap so BYOK lookup knows how to find its env key. - Wire it into
helpers.NewLLMProvider/NewLLMProviderWithKeyif it's an LLM-class provider. - Add the env var to
.env.exampleand document the key shape indocs/byok.md.
Open a GitHub Discussion or an Issue. For security disclosures, see SECURITY.md.
Thanks for contributing.