name: rocky-codegen
description: Rocky CLI JSON-output schema cascade. Use when editing any *Output struct in engine/crates/rocky-cli/src/output.rs (or commands/doctor.rs), adding a new CLI command schema, or when a change could affect schemas/*.schema.json, sdk/python/src/rocky_sdk/types_generated/, or editors/vscode/src/types/generated/. Also use when CI reports codegen-drift.
Rocky codegen cascade
Rocky's CLI --output json is the interface contract with every consumer (Dagster, VS Code, shell scripts). Every command is backed by a typed Rust struct that derives JsonSchema, and the Pydantic + TypeScript bindings are autogenerated from those schemas. Drift is enforced in CI.
If you edit a Rust output struct and forget to regenerate, the codegen-drift workflow will fail the PR.
When to use this skill
- Editing a
*Outputstruct inengine/crates/rocky-cli/src/output.rsorengine/crates/rocky-cli/src/commands/doctor.rs - Adding a new field to any type that appears inside a CLI output
- Adding a brand new CLI command that needs a JSON schema
- Investigating a
codegen-drift.ymlCI failure
The three-step pipeline
engine/crates/rocky-cli/src/output.rs (Rust source of truth)
│
▼ cargo run -- export-schemas schemas/
schemas/*.schema.json (60 JSON Schemas, committed)
│
├──▶ sdk/python/src/rocky_sdk/types_generated/ (Pydantic v2)
└──▶ editors/vscode/src/types/generated/ (TypeScript)
Canonical workflow
From the monorepo root:
# 1. Edit the Rust struct
$EDITOR engine/crates/rocky-cli/src/output.rs
# 2. Run the full codegen pipeline
just codegen
# 3. Review the diff — you should see changes in 3 places:
git status
# engine/crates/rocky-cli/src/output.rs ← your edit
# schemas/<command>.schema.json ← regenerated
# integrations/dagster/.../types_generated/… ← regenerated
# editors/vscode/src/types/generated/… ← regenerated
# 4. Commit all of it in ONE commit
git add engine/ schemas/ integrations/ editors/
git commit -m "feat(engine): add <field> to <command>Output"
Never commit a Rust output change without the regenerated bindings. The codegen-drift CI workflow (.github/workflows/codegen-drift.yml) runs just codegen and fails if git diff is non-empty.
What just codegen actually does
Three recipes in justfile, runnable individually:
| Recipe | What it does |
|---|---|
just codegen-rust |
cargo run --release --bin rocky -- export-schemas schemas/ — rebuilds engine in release mode (shared with regen-fixtures), then writes the JSON schemas to schemas/ (currently 60; run `ls schemas/*.schema.json |
just codegen-sdk |
Runs datamodel-codegen over schemas/*.schema.json into sdk/python/src/rocky_sdk/types_generated/. Self-heals the curated __init__.py barrel via git checkout. |
just codegen-vscode |
Runs json2ts per schema into editors/vscode/src/types/generated/. Self-heals the curated index.ts barrel via git checkout. |
Both codegen-sdk and codegen-vscode intentionally overwrite the output directory and then git checkout HEAD -- <barrel> to restore the curated re-export files. Do not hand-edit files under types_generated/ or types/generated/ other than those two barrels — the next codegen run nukes them.
Adding a new CLI command schema
- Add (or edit) the typed struct in
engine/crates/rocky-cli/src/output.rs(orcommands/<name>.rs) derivingJsonSchema:#[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct MyCommandOutput { ... } - Register it in
engine/crates/rocky-cli/src/commands/export_schemas.rs::schemas(). - Run
just codegen. - For the dagster consumer: re-export the new type from
integrations/dagster/src/dagster_rocky/types.py(in the round 9 bridge section near the bottom), then add a dispatch entry inparse_rocky_output(), add aRockyResourcemethod inresource.py, and a fixture + test (seeintegrations/dagster/CLAUDE.mdfor the 9-step checklist). - For the vscode consumer: no extra wiring —
src/types/rockyJson.tsis already a 100% type-alias shim oversrc/types/generated/index.ts.
Fixture drift (related but distinct)
scripts/regen_fixtures.sh (aka just regen-fixtures) captures live rocky --output json output against the playground POCs into integrations/dagster/tests/fixtures_generated/. This is a drift-detection corpus — test_generated_fixtures.py re-validates the captured JSON against the current Pydantic models. The dagster parsing tests themselves load hand-crafted Python dicts from integrations/dagster/tests/scenarios.py, exposed as *_json pytest fixtures via conftest.py.
When to regen fixtures:
- After a schema change that affects the shape of an output (new fields, renamed fields, changed types).
- Before committing, run
just regen-fixturesand check thattest_generated_fixtures.pystill passes. If it doesn't, the captured shape no longer matches the Pydantic models — usually means the codegen step was skipped.
Prerequisite: the release binary at engine/target/release/rocky must exist (just codegen already builds it).
Common pitfalls
- Editing a generated file by hand — will be wiped on next codegen. Always edit the Rust source.
- Forgetting the
JsonSchemaderive —cargo buildpasses butexport-schemassilently skips the type. Check theschemas/diff. - Adding a new type but not registering it in
export_schemas.rs::schemas()— the schema never gets emitted. - Committing partial regeneration — always run full
just codegen, not just one of the sub-recipes. - CI drift failure on a PR that did NOT touch output.rs — usually means someone bumped
datamodel-code-generatororjson2ts; re-runjust codegenlocally and commit the deterministic diff.
Local belt-and-braces: the pre-commit hook
There's a repo-root git hook at .git-hooks/pre-commit that runs the codegen check locally, so drift is caught before you push instead of after a failed CI run. Enable once:
just install-hooks
# or manually:
git config core.hooksPath .git-hooks
The hook only fires when the staged change includes one of:
engine/crates/rocky-cli/src/output.rsengine/crates/rocky-cli/src/commands/doctor.rsengine/crates/rocky-cli/src/commands/export_schemas.rs
When triggered, it runs just codegen and fails the commit if the regenerated bindings produce a non-empty git diff. Skip with ROCKY_SKIP_CODEGEN_HOOK=1 git commit … in emergencies — but the CI workflow will still catch drift on the PR.
Note on core.hooksPath: it's a repo-wide setting (stored in .git/config, so it doesn't get shared across clones). If you've previously enabled hooks inside integrations/dagster/ via git config core.hooksPath .git-hooks, that points at the same root-level path (hooksPath is repo-relative) — running just install-hooks at the root is the same operation.
Reference files
engine/CLAUDE.md— "JSON Output Schema" section lists all command output structs.integrations/dagster/CLAUDE.md— "Adding support for a new Rocky CLI command" 9-step checklist..github/workflows/codegen-drift.yml— the CI check that enforces this..git-hooks/pre-commit— the local mirror of the CI check.justfile— thecodegen*recipes (lines ~63-126) +install-hooks.