Rocky is a monorepo. The five subprojects share one repository, one issue tracker, and one pull-request flow, but each has its own build system.
| Subproject | Path | Language | Build |
|---|---|---|---|
| Rocky CLI engine | engine/ |
Rust (23-crate Cargo workspace) | cargo |
| Python SDK | sdk/python/ |
Python | uv |
| Dagster integration | integrations/dagster/ |
Python | uv |
| VS Code extension | editors/vscode/ |
TypeScript | npm |
| Sample project | examples/playground/ |
TOML / SQL config | none |
Clone once, work everywhere:
git clone https://github.com/rocky-data/rocky.git
cd rockyEach subproject is built and tested independently. From the repo root, the top-level justfile orchestrates common tasks across all of them. Install just and then:
just build # build engine + sdk + dagster wheels + vscode extension
just test # run all test suites
just lint # cargo clippy/fmt + ruff + eslint
just --list # see all available recipesYou can also build a single subproject directly without just; see the per-subproject sections below.
cd engine
cargo build --release
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --checkThe engine is a Cargo workspace with 25 members: 23 library crates plus the rocky and rocky-lsp binaries (Rust edition 2024, MSRV 1.88). Run a single crate's tests with cargo test -p rocky-core. End-to-end tests in crates/rocky-core/tests/e2e.rs use DuckDB and need no credentials.
cd sdk/python
uv sync --dev
uv run pytest -v
uv run ruff check src/ tests/ examples/ && uv run ruff format --check src/ tests/ examples/rocky-sdk is the standalone typed client (RockyClient) that dagster-rocky is built on. Unit tests mock the binary and need no credentials; examples/quickstart.py runs against a real rocky (the sdk-ci smoke job installs one). The generated Pydantic models in src/rocky_sdk/types_generated/ come from just codegen; don't hand-edit them.
cd integrations/dagster
uv sync --dev
uv run pytest -v
uv run ruff check && uv run ruff format --checkAll tests run without the Rocky binary or credentials; they use JSON fixtures in tests/fixtures/.
cd editors/vscode
npm install
npm run compile
npm run test:unit # vitest unit tests (fast)
npm test # full electron integration tests (downloads ~344 MB)For interactive development, open editors/vscode/ in VS Code and press F5 to launch the Extension Development Host.
A self-contained DuckDB pipeline used as a smoke test for the engine and as a fixture source for the dagster integration. No build step.
The single biggest reason Rocky is a monorepo: changes to the engine's CLI JSON output schema or DSL syntax can be made atomically across all consumers in one PR.
When modifying CLI JSON output (codegen-driven as of Phase 2):
- Edit the typed
*Outputstruct inengine/crates/rocky-cli/src/output.rs(orengine/crates/rocky-cli/src/commands/doctor.rsfor the doctor types). - From the repo root, run
just codegen. This regenerates:schemas/<command>.schema.json(canonical JSON Schema)sdk/python/src/rocky_sdk/types_generated/(Pydantic v2 models)editors/vscode/src/types/generated/(TypeScript interfaces)
- Commit the regenerated bindings together with your Rust change.
- The
codegen-driftCI workflow will fail any PR where the committed bindings don't match what the engine produces, so this is enforced.
The generated Pydantic models live in the rocky-sdk package. dagster_rocky.types (and dagster_rocky.types_generated) re-export them from rocky_sdk, and editors/vscode/src/types/rockyJson.ts is a re-export shim over the generated TypeScript, both providing backward-compat aliases for the renamed class names.
When modifying Rocky DSL syntax (.rocky files), update in lockstep:
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)
Each artifact is released independently using a tag-namespaced scheme. Releases are CI-driven, so land a release PR (version bump + CHANGELOG entry), tag the merged commit, push the tag, and the matching release workflow does the rest:
| Artifact | Tag pattern | CI workflow (on tag push) |
|---|---|---|
| Rocky CLI binary | engine-v* |
engine-release.yml — full 5-target matrix (macOS ARM64/Intel, Linux x86_64/ARM64, Windows x86_64), all attached to the GitHub Release |
| rocky-sdk wheel | sdk-v* |
sdk-release.yml — build + PyPI publish via OIDC |
| dagster-rocky wheel | dagster-v* |
dagster-release.yml — build + PyPI publish via OIDC |
| Rocky VSIX | vscode-v* |
vscode-release.yml — build + VS Code Marketplace publish |
dagster-rocky depends on rocky-sdk, so when a dagster-v* release raises its rocky-sdk floor, publish the sdk-v* tag first; the published dagster wheel resolves the SDK from PyPI, not the monorepo path source.
Canonical flow for each artifact:
# 1. Bump the version + update the CHANGELOG in a release PR; merge it.
# 2. Tag the merged commit and push:
git tag engine-v1.7.0
git push origin engine-v1.7.0For convenience, the monorepo exposes just release-engine <version>, just release-dagster <version> [--publish], and just release-vscode <version> [--publish]; these wrap the local-build path below. The rocky-release Claude skill in .claude/skills/rocky-release/SKILL.md walks the full checklist.
scripts/release.sh engine|dagster|vscode <version> remains as a local-build fallback for hotfix scenarios where CI is unavailable. It builds what it can locally (macOS natively, Linux via Docker) and attaches those artifacts to the GitHub Release. Prefer the CI-driven flow for normal releases.
- Branch from
main. - One logical change per PR. Cross-project PRs are encouraged when they ship a coordinated change (schema or DSL); otherwise keep changes scoped to one subproject.
- Conventional commits required:
feat:,fix:,refactor:,test:,docs:,chore:. Scope by subproject or crate when relevant:feat(engine/rocky-databricks): add OAuth M2M auth,fix(dagster): handle partial-success exit codes,docs(vscode): update README screenshots. - Never include
Co-Authored-Bytrailers in commit messages. - CI runs path-filtered workflows for the subprojects you touch. All required checks must pass before merge. Note: benchmarks only run on PRs labeled
perf; coverage and audit run weekly viaengine-weekly.yml.
GitHub lets you pick a merge strategy per-PR via the dropdown on the merge button. Rocky's default is squash and merge, but choose deliberately; the right choice depends on the PR's shape:
| PR shape | Strategy | Why |
|---|---|---|
| Single commit | Squash | Mechanically equivalent to rebase for a single-commit PR; you get the (#N) suffix on main for PR traceability |
| Docs, chore, tooling, small fixes | Squash | No git bisect value from intra-PR granularity; one atomic revert point is easier |
| Multi-commit feature crossing subprojects (DSL cascade, new adapter, new CLI command) | Rebase | Preserves per-subproject conventional-commit scopes in git log (e.g. feat(engine/rocky-lang): → feat(engine/rocky-compiler): → feat(vscode):) so git log --grep by crate still works |
| Refactor series where intermediate states build meaningfully | Rebase | Preserves the step-by-step narrative and keeps git bisect granular for debugging future regressions |
| WIP-heavy branches with "fix typo" / "oops" commits | Squash (or clean up via interactive rebase before opening the PR) | Collapses noise into one coherent commit |
Heuristic: if every commit in your PR has a distinct meaningful conventional-commit scope, rebase: squashing would collapse those scopes into one megacommit and you'd lose the grep-by-crate affordance. Otherwise squash.
Avoid the third option (create a merge commit) for normal PRs; it adds a branching structure to trunk-based linear history for no practical benefit in a solo-scale project.
Each subproject follows its language's idioms; the linter is the source of truth.
- Rust (
engine/): edition 2024,cargo fmt,cargo clippy --all-targets -- -D warnings. Usetracingfor logs. Usethiserrorfor library errors andanyhowfor binary/CLI errors. SQL identifiers must be validated viarocky-sql/validation.rsbefore interpolation. - Python (
sdk/python/,integrations/dagster/): Python 3.11+,from __future__ import annotations, Pydantic for all data structures. Line length 100. Ruff rules: E, F, I, N, UP, B, SIM. - TypeScript (
editors/vscode/): ES2022 target, strict mode,cp.execFile()(nevercp.exec()), escape HTML in webview content.
File issues against rocky-data/rocky with a label naming the subproject (engine, dagster, vscode, playground). Include the subproject's version, your platform, and minimal repro steps.