Contributing to Konstruct
Thank you for helping. Bug reports with a reproducible case are the most valuable contribution: a SQL statement whose lineage is wrong, a DAG or Dagster file that is misread, or a project where a command fails.
Reporting a Problem
Open an issue with the bug report form. The most useful reports include:
the smallest SQL, DAG or dbt model that shows the problem (rename anything confidential);
what Konstruct said (
konstruct trace,explainordiffoutput, or the warning inREPORT.md) and what it should have said;konstruct --version, the SQL dialect and, for dbt, the adapter.
For a security problem, follow SECURITY.md instead.
Proposing a Change
For anything larger than a fix, open an issue first, so the approach can be agreed before you write it. New inputs (an orchestrator, a warehouse) and new commands especially benefit from this.
Contributing an Ontology Pack
An ontology pack describes the entities, keys, measures and relationships of a schema that many teams load, such as a SaaS tool’s tables as Fivetran or a Singer tap lands them, so every project that has the schema gets them without writing them. Most packs are generated, in konstruct/semantics/ontologies/generated/, by .kodex/scripts/generate_packs.py from permissively licensed connector schemas: Fivetran’s dbt packages, Singer and Meltano taps under MIT, Apache-2.0 or BSD licences, and dlt’s verified sources. Do not edit those files; improve the generator or regenerate them from newer checkouts, which also rewrites SOURCES.json (each repository’s URL, licence and commit), the NOTICE beside it and licenses/ontology-packs.txt. Never add a source under a licence that is not permissive, such as Airbyte’s connectors (Elastic License 2.0, whatever a connector’s metadata says) or AGPL taps, and never one that describes one organisation’s own tables. To add terms by hand, such as a measure with its unit, write konstruct/semantics/ontologies/<name>.yml: a definitions.yml with a name, a title, a description, schemas patterns for the schema names the source lands in, a kind for each term, and a match block (or a list of them, one per loader) naming the tables and at least two columns only that source has (see The Ontology). Its terms win over the generated pack of the same name. Then run uv run python .kodex/scripts/generate_packs.py --index to rebuild the index and the pack list, add a test to tests/test_registry.py with a warehouse that has the schema, and take column names from the tool’s published schema, not from one project’s copy of it.
Adding or Improving a Translation
The graph viewer’s messages are in viewer/src/locales/, one file per locale, with en-GB.ts as the source. Each message is in ICU MessageFormat: {name} inserts a value, and {count, plural, one {# model} other {# models}} chooses a form by the language’s plural rules (zero, one, two, few, many, other; =0 matches a number exactly). Keep every key, keep the arguments the British English message uses, and leave command names, file names and selector syntax such as +orders+ untranslated.
To improve a translation, edit its file. The catalogues other than British English were translated with machine assistance, and the header of each says so; corrections from native speakers are welcome.
To add a locale, copy
en-GB.tsto<locale>.ts(a BCP 47 tag such assvorpt-PT), type it asCatalogue, translate it, and add it tolocales/index.tsand to the list inviewer/tests/catalogue.test.ts. A right-to-left script is laid out right to left by its language code.To check, run
pnpm check && pnpm testat the repository root: a missing or extra key fails the type check, and the catalogue test fails on a message that does not parse or that uses an argument the source message does not have. Thenpnpm build, and commit the rebuiltkonstruct/outputs/viewer.htmlwith the catalogue.
Making a Change
The Development section of the README covers setting up, the test suite and the commands. You need uv and a stable Rust toolchain (1.95 or newer): uv sync builds Konstruct’s Rust core with maturin. In short:
uv sync
uv run pre-commit install
eval "$(.kodex/scripts/services.sh up)"
uv run pytest --cov
The Makefile at the root runs the same commands by name: make install sets up, make check runs the formatters’ checks, the linters and the Python, Rust and viewer tests, and make alone lists every target.
A pull request is ready when:
It has tests. A lineage fix comes with a test that fails without it, preferably as a small SQL case in
tests/test_sql.py. A change to what Konstruct claims about lineage should also hold underkonstruct verify(tests/test_verify.py). Coverage must stay at or above 90%.The checks pass.
uv run ruff check,uv run ruff format --checkanduv run mypy(strict) are clean, and the pre-push hook has run the tests. For a change to the Rust core (src/),cargo fmt --check,cargo clippy --all-targets --all-features -- -D warningsandcargo testpass there too.Output changes are reviewed. If the CLI’s text or
REPORT.mdchanges, regenerate the golden files withKONSTRUCT_UPDATE_GOLDEN=1 uv run pytest tests/test_golden.pyand check the diff is what you meant.Performance is no worse. For changes to the build, SQL analysis or formulas, run
uv run .kodex/scripts/benchmark.py 500before and after.The viewer is rebuilt. Changes under
viewer/passpnpm checkandpnpm test, and the rebuiltkonstruct/outputs/viewer.htmlis committed with them (the pre-commit hook does the rebuild).The README says what changed, if a user would notice it: a new command, option, input or limitation.
Commits
Commits follow Conventional Commits with the scopes listed in the README. A hook checks each message. Releases are cut automatically from them: fix gives a patch release, feat a minor one. Write the subject in sentence case and the body in plain British English, explaining why the change is needed.
Code Style
Typed Python 3.11+, checked by mypy in strict mode; the Rust core formatted by rustfmt and clean under Clippy; the viewer in strict TypeScript and Svelte 5, formatted by Prettier.
Code lives in one language: what moves to Rust is removed from Python, with no second implementation to fall back on.
Prefer small, plain functions to frameworks. Konstruct has few dependencies, and new ones need a good reason.
Never import or execute user code (DAGs, Dagster definitions, Python models). Parse it.
Comments explain why, not what.
Licence
By contributing you agree that your contribution is licensed under the MIT Licence.