Documentation
BloqrAI Versioning Strategy
Status: Active standard, org-wide.
Scope: Every independently-published @bloqr JSR package, in this repo and any future repo that publishes one.
The core rule
Code version and package version always match, and there is exactly one place you edit to change either.
Concretely, for any package that follows this standard:
- A
VERSIONconstant in the package's own source (e.g.src/version.ts) is the single source of writable truth. - A
version:syncscript propagates that constant into the package manifest (deno.json's"version"field, andpackage.json/wrangler.toml/anything else that carries a duplicate copy, if the package has those). - Nobody hand-edits
deno.json's version field directly — it's always derived.
This isn't a new idea invented for this repo: bloqr-compiler (the private commercial monorepo) already runs exactly this pattern (src/version.ts → scripts/sync-version.ts → deno.json/package.json/wrangler.toml) and has for a while. This document makes it the explicit, written-down, org-wide standard — both because bloqr-core is about to need it for real (its first JSR package, @bloqr/compiler-core, just went live), and because bloqr-core is going to decompose into several independently-versioned JSR packages as that epic proceeds, which is a meaningfully different shape than bloqr-compiler's single-package case.
Why "one repo, one version" doesn't work here
bloqr-compiler has one deployable artifact, so one VERSION constant and one tag prefix (v* / compiler-v*) is sufficient. bloqr-core is different: it already contains multiple independent things (four rules-compiler implementations, a validation library, wrapper CLIs, a docs website), and the plan is to decompose the TypeScript/Deno side specifically into multiple standalone @bloqr/* JSR packages over time. Each of those needs to be versioned, tagged, and released independently — bumping @bloqr/compiler-core from 1.0.0 to 1.1.0 must not force (or even suggest) a version bump on some future @bloqr/rules-schema or @bloqr/filter-utils package that happens to live in the same repo and didn't change.
So the standard is per-package, not per-repo:
- Each package gets its own
VERSIONsource-of-truth file, scoped to that package's own directory. - Each package gets its own git tag prefix:
<package-slug>-v<semver>(e.g.compiler-core-v1.2.3). No barev*tags in this repo once there's more than one package — barev*is ambiguous the moment a second package exists. - Each package gets its own version-bump automation, scoped by path filter to only that package's directory, with its own bump-commit marker string (so
git log --grepfor "when did we last bump package X" doesn't accidentally match package Y's bump commits). - Each package's publish workflow triggers off changes to its own path (already true for
publish-jsr.yml— see below) and/or its own tag.
Reference implementation: @bloqr/compiler-core
This is the first package on the new standard, and it's meant to be the copy-paste template for every future one. Four pieces, all scoped to src/compilers/typescript/:
| File | Role |
|---|---|
src/compilers/typescript/src/version.ts |
export const VERSION = '1.0.0' — hand-edited only by the bump automation (or a human doing a manual override), never by feature PRs. |
src/compilers/typescript/scripts/sync-version.ts |
deno task version:sync — reads VERSION from version.ts, writes it into deno.json's "version" field. No-ops if already in sync. |
.github/workflows/compiler-core-version-bump.yml |
Runs on every push to main that touches src/compilers/typescript/**. Walks Conventional Commits since the last chore: bump compiler-core version commit, determines the bump type (see below), bumps version.ts, runs the sync script, updates CHANGELOG.md, and opens a PR (auto-version-bump-compiler-core-<version> branch). |
.github/workflows/compiler-core-create-version-tag.yml |
Runs when a auto-version-bump-compiler-core-* PR merges. Reads the now-updated deno.json version and pushes the compiler-core-v<version> tag. |
.github/workflows/publish-jsr.yml |
Unchanged by this doc's introduction — it already triggers on any push to src/compilers/typescript/** on main, and deno publish is idempotent (no-ops if the current version is already published). The version-bump PR's merge commit is what actually causes the next real publish; the tag above exists for traceability, not to gate the publish. |
Conventional Commits → bump type
Same rule bloqr-compiler already uses, scoped to commits that touched the package's own directory:
| Commit prefix | Bump |
|---|---|
fix:, perf: |
patch |
feat: |
minor |
feat!:, fix!:, or a BREAKING CHANGE: footer |
major |
anything else (chore:, docs:, test:, ci:, ...) |
no bump |
A push with no bump-worthy commits since the last bump is a silent no-op (logged to the workflow's job summary, no PR opened).
Onboarding a new decomposed package
When a piece of bloqr-core (or a brand-new repo) becomes its own @bloqr/* JSR package, copy the compiler-core pattern with these substitutions:
<package-dir>/src/version.ts(or equivalent) — new file,VERSION = '0.1.0'(or wherever the extracted code's version actually starts — if it's an extraction of existing code that already has a version, keep continuity rather than resetting to 0.1.0).<package-dir>/scripts/sync-version.ts— copycompiler-core's, change the relative import path if the directory depth differs. If the new package also ships apackage.json/wrangler.toml(unlikely for a pure JSR library, but possible), add sync steps for those too — seebloqr-compiler's fullersync-version.tsfor that shape..github/workflows/<package-slug>-version-bump.yml— copycompiler-core-version-bump.yml, replace:- the
paths:filter (src/compilers/typescript/**→ the new package's path) - the bump-commit grep marker (
chore: bump compiler-core version→chore: bump <package-slug> version) - the branch prefix (
auto-version-bump-compiler-core-→auto-version-bump-<package-slug>-) - the
working-directorydefault
- the
.github/workflows/<package-slug>-create-version-tag.yml— copycompiler-core-create-version-tag.yml, same substitutions, tag prefix<package-slug>-v.- That package's own
publish-jsr.yml(or a shared one filtered by path — either is fine,bloqr-corecurrently has one workflow per publishable package) — path-filtered trigger,deno publish --token ${{ secrets.JSR_WORKFLOW_TOKEN }}perdocs/jsr-token-authentication.md. - Add the new package to
docs/jsr-org-standards.md's package table.
Reference implementation: bloqr-validator-core (crates.io)
The core rule — independent version per publishable package identity, one place to edit it — applies to crates.io exactly as it does to JSR, with two Cargo-specific differences:
- No separate
version.ts/sync-script indirection is needed. Cargo's manifest (Cargo.toml's[package] versionfield) already is the single source of writable truth — there's no JSONC-vs-JSON split to work around likedeno.json. So the "sync" step from the JSR pattern simply doesn't exist for Rust crates. version.workspace = true(inheriting[workspace.package] version) must not be used for any independently-published or independently-cadenced crate. Workspace-inherited versioning is the crates.io equivalent of the "one repo, one version" anti-pattern this doc's introduction already rejects for JSR — it would forcebloqr-validator-core,bloqr-validator-core-cli, andbloqr-compilerto bump in lockstep with each other, even when only one of them changed. All three now declare an explicit, independentversionfield instead:bloqr-validator-coreandbloqr-validator-core-cli(src/validation/) as of #365's follow-up, andbloqr-compiler(src/compilers/rust/) as of its own crates.io publish decision (reversing #365's original "unpublished, stays workspace-internal" call for this crate specifically).
The FOSS/commercial × library/CLI version-independence matrix
Per the Rust package-naming standard (docs/jsr-org-standards.md), a single product surface can have up to four crates: FOSS library, FOSS CLI, commercial library, commercial CLI. All four version independently — none of them share a version number by convention, even where one embeds another as a dependency:
| Crate | Versions independently because |
|---|---|
bloqr-validator-core (FOSS lib) |
Its cadence is driven by validation-logic changes; this is the published, externally-consumed artifact. |
bloqr-validator-core-cli (FOSS CLI) |
Its cadence is driven by flag/UX/output-format changes, which don't always coincide with a library-logic change (and vice versa — a library patch doesn't always need a CLI release). |
bloqr-validator (commercial lib, reserved) |
A different product surface with its own release cadence and its own team ownership boundary, even when the same engineers work both sides. May depend on bloqr-validator-core as a Cargo dependency, but that's a dependency version pin, not a lockstep release version. |
bloqr-validator-cli (commercial CLI, reserved) |
Same reasoning as the FOSS CLI, applied to the commercial surface. |
This mirrors the crates.io convention itself (any crate can depend on any version-compatible range of any other crate) and avoids the classic monorepo trap where an unrelated CLI flag tweak forces a version bump — and a fresh audit/compliance review — of the validation library it happens to wrap.
crates.io publishing is independent of the repo-wide release, like JSR
Both bloqr-validator-core (library) and bloqr-validator-core-cli (the bloqr-validate binary — installable via cargo install, the standard crates.io distribution mechanism for Rust CLI tools) publish via the same publish-crates.yml, triggered on every push to main touching either crate's directory (or manually via workflow_dispatch) — the same path-filtered-push pattern as publish-jsr.yml, gated by an idempotency check against the crates.io API rather than a cargo publish --skip-duplicate flag (which doesn't exist). The CLI's job runs after the library's (needs: publish-core), since bloqr-validator-core-cli's dependency on bloqr-validator-core is rewritten from a path dependency to a registry one at publish time (version = "1" alongside path in its Cargo.toml) — a brand-new library version needs to exist on the registry before the CLI can resolve against it.
This is deliberately not part of release.yml's repo-wide v*-tag-triggered binary release: bloqr-compiler's release binary still bundles into that coordinated multi-language GitHub Release (it isn't published to crates.io at all — no external consumer as a library, and the binary already ships there), but neither crates.io publish needs to wait for (or force) a full binary release cut just because a validation-logic or CLI-only change landed. docs/architecture/nuget-distribution-strategy.md's publish-nuget.yml got the identical treatment for the same reason.
What's still genuinely manual: there's no automated Conventional-Commits bump/tag workflow pair (the compiler-core-version-bump.yml/compiler-core-create-version-tag.yml equivalents) for either crate yet — today their versions are bumped by hand in Cargo.toml. Building that automation is tracked as follow-up under #372, not this doc.
What this doc does not cover yet
- The non-JSR, non-Rust wrapper projects in this repo (.NET, Python, PowerShell) have their own version fields (
.csproj,pyproject.toml) and are not yet wired into an equivalent automated bump/tag/release pattern for their own registries (NuGet, PyPI).docs/release-guide.mddescribes their current (manual, repo-wide-tag) release process, which predates this standard and still reflects the pre-split repo shape in places — it needs its own pass to either adopt an equivalent per-package pattern or explicitly document why it stays manual. Tracked as follow-up, not blocking this doc. - Full crates.io (and NuGet) Conventional-Commits bump/tag automation — see the crates.io publishing note above.
- Cross-repo propagation: once packages are actually split out of
bloqr-coreinto their own repos, this document (or a copy of it) needs to travel with them. For now, seedocs/org-documentation-strategy.mdfor how org-wide docs are being tracked during this transitional period (.github-privatefor internal standards while things are still moving; this doc lives inbloqr-coreitself since it's currently the one place with a working reference implementation).
Related
docs/jsr-token-authentication.md— howpublish-jsr.ymlauthenticates to JSR.docs/jsr-org-standards.md— JSR scope/package conventions this versioning strategy sits alongside.bloqr-compiler'ssrc/version.ts/scripts/sync-version.ts/.github/workflows/version-bump.yml/.github/workflows/create-version-tag.yml— the pre-existing, independently-arrived-at prior art this standard formalizes and extends to the multi-package case.