Contributing
Rocky lives in one repository. It holds the Rust engine and its browser UI, two Python packages, a VS Code extension, a sample pipeline, and this docs site. One pull request can change several of them at once, and one CI run covers them all.
This page answers three questions. Where does my change belong? How do I build and test that part? What will CI check before it merges?
Share a first attempt
Section titled “Share a first attempt”You can contribute without writing code. Use the first-run feedback form to describe a completed workflow or where you stopped. Include your goal, Rocky version, installation method, and the step that helped or blocked you. A feature proposal is optional. A small example with synthetic data is enough.
The form creates a public issue only when you submit it. Remove credentials, private data, and internal addresses before posting. Use private security reporting for vulnerabilities.
Where to start
Section titled “Where to start”The right entry point depends on what you want to change.
| You want to change | Start here |
|---|---|
| How a warehouse executes SQL | engine/crates/rocky-<name>/ — the adapter crate. Implement WarehouseAdapter / SqlDialect from rocky-core::traits. An out-of-tree process adapter uses rocky-adapter-sdk instead. |
| How SQL is generated from the IR | engine/crates/rocky-core/src/sql_gen.rs |
| What the IR looks like (model shape) | engine/crates/rocky-ir/src/ir.rs |
| The type checker or semantic graph | engine/crates/rocky-compiler/src/ |
| A diagnostic code (error/warning) | engine/crates/rocky-compiler/src/diagnostic.rs |
| Incremental / watermark logic | engine/crates/rocky-core/src/state.rs + sql_gen.rs |
The rocky run execution loop |
engine/crates/rocky-cli/src/commands/run.rs |
| DAG / dependency resolution | engine/crates/rocky-ir/src/dag.rs |
| Data contracts enforcement | engine/crates/rocky-core/src/contracts.rs |
| Schema drift detection | engine/crates/rocky-core/src/drift.rs |
| Masking strategies | engine/crates/rocky-core/src/masking.rs |
| Permission / role graph | engine/crates/rocky-core/src/role_graph.rs |
| SCD-2 snapshot logic | engine/crates/rocky-core/src/snapshots.rs |
| Quality checks | engine/crates/rocky-core/src/checks.rs |
| Hooks / webhooks | engine/crates/rocky-core/src/hooks/ |
| Column lineage extraction | engine/crates/rocky-sql/src/lineage.rs |
| Skip-unchanged gate / hashing | engine/crates/rocky-sql/src/determinism.rs + rocky-ir/src/ir.rs::skip_hash() |
| CLI JSON output shape | engine/crates/rocky-cli/src/output.rs — then run just codegen |
| A new CLI subcommand | See the rocky-new-cli-command skill in .agents/skills/ (mirrored in .claude/skills/) |
The Rocky DSL (.rocky files) |
engine/crates/rocky-lang/src/ — then update VS Code grammar too |
The browser UI (rocky serve --ui) |
engine/ui/ (React + Vite). Run just ui-dev for the dev loop. |
| The LSP server | engine/crates/rocky-server/src/lsp.rs |
| The Dagster integration | integrations/dagster/src/dagster_rocky/ |
| The Python SDK | sdk/python/src/rocky_sdk/client.py |
| The VS Code extension | editors/vscode/src/ |
The IR named above is the intermediate representation: the typed blueprint Rocky builds from your SQL before it emits any warehouse dialect.
Key invariants before you commit
Section titled “Key invariants before you commit”- Never commit a Rust
*Outputchange without runningjust codegen—codegen-drift.ymlCI will fail. - Never
format!()untrusted input into SQL — userocky-sql/validation.rsto validate identifiers first. - Never hand-edit files under
*/types_generated/or*/types/generated/— those are codegen outputs. - Never skip hooks (
--no-verify). Fix the underlying issue instead.
What lives where
Section titled “What lives where”Rocky is a monorepo with five subprojects:
rocky/├── engine/ # Rust CLI + engine (Cargo workspace)│ └── ui/ # Browser UI that `rocky serve --ui` embeds├── sdk/python/ # rocky-sdk Python client├── integrations/dagster/ # dagster-rocky Python package├── editors/vscode/ # VS Code extension (LSP client)├── examples/playground/ # POC catalog + benchmarks├── docs/ # Documentation site (Astro + Starlight)├── justfile # Cross-project build orchestration└── AGENTS.md # Monorepo conventionsFor the crate-level breakdown of engine/, see Architecture.
Set up a development environment
Section titled “Set up a development environment”Each subproject builds with its own native tool. You do not need the others installed to work on one.
Rocky engine (Rust)
Section titled “Rocky engine (Rust)”git clone https://github.com/rocky-data/rocky.gitcd rocky/engine
# Buildcargo buildcargo build --release
# Run testscargo test
# Lintcargo clippy -- -D warningscargo fmt -- --checkrocky-sdk (Python)
Section titled “rocky-sdk (Python)”cd rocky/sdk/python
# Install with dev dependenciesuv sync --dev
# Run testsuv run pytest
# Lintuv run ruff checkuv run ruff format --checkdagster-rocky (Python)
Section titled “dagster-rocky (Python)”cd rocky/integrations/dagster
# Install with dev dependenciesuv sync --dev
# Run testsuv run pytest -v
# Lintuv run ruff checkuv run ruff format --checkBrowser UI (TypeScript)
Section titled “Browser UI (TypeScript)”cd rocky/engine/ui
npm cinpm run typechecknpm test
# From the monorepo root: `rocky serve` plus the Vite dev serverjust ui-dev
# Build the UI and a release binary that embeds itjust build-engine-uiA plain cargo build does not need the UI.
VS Code extension (TypeScript)
Section titled “VS Code extension (TypeScript)”cd rocky/editors/vscode
# Install dependenciesnpm install
# Compilenpm run compile
# Run unit tests (vitest)npm run test:unit
# Run the integration tests (downloads a full Electron, about 344 MB)npm testRun every subproject at once
Section titled “Run every subproject at once”The top-level justfile fans one command out across all subprojects:
just build # cargo build --release + uv build --wheel + npm compilejust test # cargo test + pytest + vitest (VS Code and browser UI)just lint # cargo clippy/fmt + ruff + eslintjust codegen # Export JSON schemas + regenerate Pydantic/TS bindingsjust --list # All recipesCoding standards
Section titled “Coding standards”- Edition: 2024 (MSRV 1.88)
- Error handling:
thiserrorfor library errors,anyhowfor binary/CLI errors - Logging:
tracingcrate (notprintln!) - SQL safety: All identifiers validated via
rocky-sql/validation.rsbefore interpolation - Tests: In the same file (
#[cfg(test)] mod tests) - Public types: Must derive
Debug,Clone,Serialize,Deserializewhere applicable
Python
Section titled “Python”- Target Python 3.11+
- Type annotations required (use modern syntax:
list[str],X | None) - Use
rufffor linting and formatting
TypeScript
Section titled “TypeScript”- Strict mode enabled
- ESLint + Prettier formatting
How to write a commit message
Section titled “How to write a commit message”- Use conventional commits:
feat:,fix:,refactor:,test:,docs:,chore: - Scope by subproject or crate:
feat(engine/rocky-databricks): add OAuth M2M auth,fix(dagster): handle partial-success exit codes,chore(vscode): bump vscode-languageclient - Never include
Co-Authored-Bytrailers
Changes that cross subprojects
Section titled “Changes that cross subprojects”Two kinds of change must land in several subprojects at once. Do each as a single pull request, so CI checks the whole cascade together.
Changing the CLI’s JSON output schema:
- Edit the relevant
*Outputstruct inengine/crates/rocky-cli/src/output.rs - Run
just codegenfrom the monorepo root to regenerate bindings - Add a new field by hand to the matching model in
sdk/python/src/rocky_sdk/types.py - Run
just regen-fixtureswhen the output shape changes - Commit the schema, the regenerated bindings and the fixtures together with the Rust change
The codegen-drift CI workflow fails any PR whose committed bindings differ from what just codegen produces locally. The full cascade is documented in the JSON contract.
Changing Rocky DSL syntax (.rocky files):
engine/crates/rocky-lang/(parser + lexer)engine/crates/rocky-compiler/(type checking)editors/vscode/syntaxes/rocky.tmLanguage.json(TextMate grammar)editors/vscode/snippets/rocky.json(snippets)docs/src/content/docs/concepts/rocky-dsl.mdanddocs/rocky-lang-spec.md(DSL page and full spec)
Run the tests
Section titled “Run the tests”The engine’s end-to-end tests run against DuckDB, so they need no credentials.
# Engine — all testscargo test
# Engine — single cratecargo test -p rocky-core
# Engine — E2E integration tests (DuckDB, no credentials)cargo test -p rocky-core --test e2e
# Engine — with outputcargo test -- --nocapture
# Dagster integrationcd integrations/dagster && uv run pytest -v
# Browser UIcd engine/ui && npm test
# VS Code extension (unit tests)cd editors/vscode && npm run test:unitWhat CI runs
Section titled “What CI runs”The workflows in .github/workflows/ are path-filtered. A PR that touches only engine/** runs the engine workflows and nothing else.
| Workflow | Trigger | What it does |
|---|---|---|
engine-ci.yml |
engine/** changes |
Tests, clippy, fmt |
engine-weekly.yml |
Monday schedule + manual | Coverage (tarpaulin) + security audit |
engine-release.yml |
engine-v* tag |
Full 5-target matrix (macOS, Linux, Windows) |
engine-bench.yml |
PRs labeled perf |
criterion benchmarks; raw bencher output uploaded as an artifact (no baseline comparison) |
sdk-ci.yml |
sdk/python/** changes |
pytest + ruff |
sdk-release.yml |
sdk-v* tag |
PyPI publish via OIDC |
dagster-ci.yml |
integrations/dagster/** changes |
pytest + ruff |
dagster-release.yml |
dagster-v* tag |
PyPI publish via OIDC |
vscode-ci.yml |
editors/vscode/** changes |
npm test + eslint |
vscode-release.yml |
vscode-v* tag |
VS Code Marketplace publish |
codegen-drift.yml |
engine/**, schemas/**, generated bindings, fixtures, POCs |
Validates committed bindings and fixtures match just codegen / just regen-fixtures output |
How a release ships
Section titled “How a release ships”Releases are tag-namespaced, so each artifact ships on its own schedule. All four are CI-driven. Land a release PR with the version bump and the CHANGELOG entry, tag the merged commit, then push the tag.
| Artifact | Tag | Workflow |
|---|---|---|
| Rocky CLI binary | engine-v* |
engine-release.yml — 5-target matrix (macOS ARM64/Intel, Linux x86_64/ARM64, Windows) |
| rocky-sdk wheel | sdk-v* |
sdk-release.yml — PyPI publish via OIDC |
| dagster-rocky wheel | dagster-v* |
dagster-release.yml — PyPI publish via OIDC |
| Rocky VSIX | vscode-v* |
vscode-release.yml — VS Code Marketplace publish |
One ordering rule applies. Release rocky-sdk before any dagster-rocky release that raises its rocky-sdk>=… floor. The published dagster-rocky wheel resolves the SDK from PyPI, not from the monorepo path source.
git tag engine-v<version>git push origin engine-v<version> # CI builds + publishesThe scripts/release.sh helper stays as a local-build fallback for a hotfix. These recipes wrap it: just release-engine <version>, just release-sdk <version> [--publish], just release-dagster <version> [--publish], and just release-vscode <version> [--publish].