Skip to content

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?

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.

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.

  • Never commit a Rust *Output change without running just codegen — codegen-drift.yml CI will fail.
  • Never format!() untrusted input into SQL — use rocky-sql/validation.rs to 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.

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 conventions

For the crate-level breakdown of engine/, see Architecture.

Each subproject builds with its own native tool. You do not need the others installed to work on one.

Terminal window
git clone https://github.com/rocky-data/rocky.git
cd rocky/engine
# Build
cargo build
cargo build --release
# Run tests
cargo test
# Lint
cargo clippy -- -D warnings
cargo fmt -- --check
Terminal window
cd rocky/sdk/python
# Install with dev dependencies
uv sync --dev
# Run tests
uv run pytest
# Lint
uv run ruff check
uv run ruff format --check
Terminal window
cd rocky/integrations/dagster
# Install with dev dependencies
uv sync --dev
# Run tests
uv run pytest -v
# Lint
uv run ruff check
uv run ruff format --check
Terminal window
cd rocky/engine/ui
npm ci
npm run typecheck
npm test
# From the monorepo root: `rocky serve` plus the Vite dev server
just ui-dev
# Build the UI and a release binary that embeds it
just build-engine-ui

A plain cargo build does not need the UI.

Terminal window
cd rocky/editors/vscode
# Install dependencies
npm install
# Compile
npm run compile
# Run unit tests (vitest)
npm run test:unit
# Run the integration tests (downloads a full Electron, about 344 MB)
npm test

The top-level justfile fans one command out across all subprojects:

Terminal window
just build # cargo build --release + uv build --wheel + npm compile
just test # cargo test + pytest + vitest (VS Code and browser UI)
just lint # cargo clippy/fmt + ruff + eslint
just codegen # Export JSON schemas + regenerate Pydantic/TS bindings
just --list # All recipes
  • Edition: 2024 (MSRV 1.88)
  • Error handling: thiserror for library errors, anyhow for binary/CLI errors
  • Logging: tracing crate (not println!)
  • SQL safety: All identifiers validated via rocky-sql/validation.rs before interpolation
  • Tests: In the same file (#[cfg(test)] mod tests)
  • Public types: Must derive Debug, Clone, Serialize, Deserialize where applicable
  • Target Python 3.11+
  • Type annotations required (use modern syntax: list[str], X | None)
  • Use ruff for linting and formatting
  • Strict mode enabled
  • ESLint + Prettier formatting
  • 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-By trailers

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:

  1. Edit the relevant *Output struct in engine/crates/rocky-cli/src/output.rs
  2. Run just codegen from the monorepo root to regenerate bindings
  3. Add a new field by hand to the matching model in sdk/python/src/rocky_sdk/types.py
  4. Run just regen-fixtures when the output shape changes
  5. 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):

  1. engine/crates/rocky-lang/ (parser + lexer)
  2. engine/crates/rocky-compiler/ (type checking)
  3. editors/vscode/syntaxes/rocky.tmLanguage.json (TextMate grammar)
  4. editors/vscode/snippets/rocky.json (snippets)
  5. docs/src/content/docs/concepts/rocky-dsl.md and docs/rocky-lang-spec.md (DSL page and full spec)

The engine’s end-to-end tests run against DuckDB, so they need no credentials.

Terminal window
# Engine — all tests
cargo test
# Engine — single crate
cargo test -p rocky-core
# Engine — E2E integration tests (DuckDB, no credentials)
cargo test -p rocky-core --test e2e
# Engine — with output
cargo test -- --nocapture
# Dagster integration
cd integrations/dagster && uv run pytest -v
# Browser UI
cd engine/ui && npm test
# VS Code extension (unit tests)
cd editors/vscode && npm run test:unit

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

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.

Terminal window
git tag engine-v<version>
git push origin engine-v<version> # CI builds + publishes

The 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].