Maintainer and Agent Guide
This document defines how automated and human-assisted agents operate within the Project Governance. It is process guidance for agents, not product semantics. The product source of truth remains architecture.md.
1. Purpose
Agent governance exists to keep repository changes reviewable, locally verifiable, and aligned with Modelable's core contract guarantees and Product Principles:
- Published model and projection versions are immutable.
- Incompatible changes require new versions.
- Lineage, compatibility, governance findings, and generated artifacts must be deterministic.
- Adapter-specific details must not leak into platform-neutral model and projection definitions.
- PII, restricted fields, and unauthorized fields must not be exposed through projections, generated artifacts, logs, or dead-letter payloads.
These rules apply to documentation, samples, future source code, tests, generated artifacts, CI configuration, and PR preparation.
2. Agent Operating Rules
Agents must:
- Consult AGENTS.md for the current project state and ROADMAP.md for planned work before starting.
- Read the relevant specification before editing.
- Keep changes small enough for meaningful review.
- Add or update tests with any code change that affects parser behavior, validation, compatibility checks, lineage, planning, runtime execution, governance, security, or generated artifacts.
- Prefer behavior and contract tests over implementation-shape tests. A test should normally exercise the public command, exported function, generated artifact, policy contract, or workflow behavior. Avoid assertions that only prove a private helper, literal string, control-flow shape, or file layout is present unless that detail is itself the reviewed contract.
- Add Docker-backed compile smoke tests for any change that adds or modifies a generated-language backend or generated artifact format, using the latest official compiler/runtime image for each affected language.
- Run the OpenMetadata Testcontainers smoke for any change that can affect the
OpenMetadata export format, including
openmetadataemitter code, shared emitter metadata helpers, IR field/governance metadata shape, projection lineage resolution, or OpenMetadata CLI/documentation contracts. - Run the Marquez/OpenLineage Testcontainers smoke for any change that can
affect OpenLineage event export or live lineage synchronization, including
openlineageemitter code, the OpenLineage registry client, sync command wiring, shared emitter metadata helpers, projection lineage resolution, or OpenLineage CLI/documentation contracts. - Run the Data Contract CLI lint smoke for any change that can affect the ODCS
export format, including
odcsemitter code, shared emitter metadata helpers, IR field/governance metadata shape, or ODCS CLI/documentation contracts. - Run the HL7 FHIR Validator smoke for any change that can affect the FHIR R4
profile export format, including
fhir-profileemitter code, shared emitter metadata helpers, IR field/governance metadata shape, or FHIR CLI/documentation contracts. - Do not use hard-coded line numbers to locate language elements in test fixtures or sample files. Derive line positions dynamically.
- Validate current latest stable framework, library, CLI, build-tool, and scaffolding choices with a web search against official documentation, package registries, or release pages before adding or changing them.
- Use the latest stable framework and tool versions by default, unless the specification, compatibility constraints, existing manifests, or explicit user direction require a different version.
- Record any deliberate use of an older framework or tool version in the final handoff or PR body.
- Use
uvexclusively for Python version management, project setup, dependency management, lockfile generation, and tool execution. Keep packages up to date with the latest stable versions. - The project requires Python >= 3.14 (declared in
cli/pyproject.tomlrequires-pythonand[tool.mypy] python_version). All agents and CI must run under Python 3.14+ for Pydantic v2 validation and modern typing behavior. Strict mypy is enforced as a CI baseline ratchet: existing errors are tracked incli/mypy-baseline.txt, new errors fail the Validate workflow, and resolved baseline lines should be removed as modules are cleaned up. - Preserve the existing domain language: domain-owned canonical models, immutable versions, projections, subscriptions, adapter bindings, planner/runtime/materializer, compatibility, lineage, and governance.
- Maintain backward compatibility within major versions for the
.mdllanguage and CLI. - Prefer explicit derivation and traceability over implicit behavior.
- Identify whether a change is MVP, deferred, non-goal, or open decision when adding requirements.
- Avoid broad architecture or tooling churn unless it directly supports the requested change.
- Record verification evidence in the final handoff or PR body.
- Local CI Requirement: Run the full local gate and ensure all tests pass before reporting a task as complete or creating a PR.
- GitHub Verification: Verify that GitHub Actions CI passes for any pushed changes.
- Strategic Re-evaluation: If a fix fails more than 3 times, stop and re-evaluate assumptions. Propose an alternative architectural approach rather than continuing to patch a failing one.
Agents must not:
- Weaken published-contract semantics.
- Reclassify governance findings as non-blocking implementation details without documenting the policy decision.
- Collapse registry, compiler/planner, runtime, materializer, and adapter boundaries for convenience.
- Commit transient local artifacts such as dependency folders, build outputs, caches, test result directories, local environment files, or fetched Helm dependencies.
3. Local Gate
Every completed change must pass a local gate before it is reported as complete.
Minimum local gate:
git status --short
review changed-file diff
run relevant tests or checks
report commands run and skipped checks
Documentation-only local gate:
git status --short
review Markdown diff
confirm links and document references are coherent
confirm terminology matches the system specification
CLI implementation local gate:
git status --short
uv sync --extra dev
uv run ruff check . --fix
uv run ruff format .
uv run ruff check .
uv run ruff format --check .
uv run pytest tests/ -v
uv run modelable validate tests/fixtures/customer.mdl
Run these commands from cli/. The final non-mutating Ruff checks are required
after any auto-fix or formatting step; they mirror the first CLI gates in
GitHub Actions and prevent unformatted changes from skipping the test suite.
If the formatter or static-analysis commands change, update this section,
AGENTS.md, and
CONTRIBUTING.md
together.
If a milestone is completed, update the status table in
AGENTS.md.
For LSP or VS Code extension changes, also run:
cd vscode
npm ci
npm run build
npm test
For changes to the .mdl grammar or language documentation, regenerate the
derived artifacts and confirm their drift tests pass. The Lark grammar at
cli/src/modelable/grammar/modelable.lark is the single source of truth for
the language:
docs/grammar.mdis rendered bycli/scripts/render_language_grammar.py(cli/tests/test_grammar_doc_sync.pyenforces it is up to date).- The TextMate keyword/type lists in
vscode/syntaxes/modelable.tmLanguage.jsonand the Monarch lists inweb/src/language/monaco-providers.tsare rendered bycli/scripts/render_editor_grammars.py(cli/tests/test_editor_grammar_sync.pyenforces they are up to date).
Regenerate after grammar edits with:
cd cli
uv run python scripts/render_language_grammar.py
uv run python scripts/render_editor_grammars.py
A grammar change that adds a keyword or type word shows up in the editor grammars automatically; a parser change that only rewrites existing rules should leave the editor lists unchanged.
On Windows, close any running desktop VS Code windows before npm test; the smoke runner fails fast if the desktop app is still holding the update mutex.
For conversational compilation changes, exercise both application-service
callers. Direct modelable compile must preserve its public targets, options,
console behavior, output bytes, and errors. A chat acceptance case must use a
fake provider to preview compile this workspace to Rust, confirm that no
workspace bytes changed, capture the exact staged bytes and affected
definitions, then issue the literal /apply and compare every written byte to
staging. Also verify that:
- changing a source, generated destination, registry input, parent path, or staged file makes apply stale;
- dirty generated destinations block VS Code apply while unrelated dirty files do not;
- an injected promotion or audit failure restores prior files and removes new transaction paths;
- discard, replacement, expiry, reset, quit, and exceptional close remove private staging; and
.modelable/audit/compilations/<action-id>.jsoncontains hashes, sizes, canonical plan, affected references, and confirmation provenance, but no prompt, response, source/artifact content, credentials, tokens, environment values, or unrelated paths.
Keep conversational planning local-only. CLI, VS Code, and the playground use the same typed Python conversation engine with filesystem and in-memory adapters. Browser tests use the semantic simulator; real-model checks are opt-in:
cd cli
$env:MODELABLE_OLLAMA_TESTS='1'
$env:MODELABLE_OLLAMA_MODEL='qwen2.5-coder:14b'
uv run pytest tests/test_ollama_conversation_conformance.py -v -n 0
The suite uses MODELABLE_LLM_BASE_URL or http://127.0.0.1:11434, never
downloads models, and does not make Ollama a playground provider. Registry
synchronization, publishing, and external actions remain outside the
conversation plan vocabulary. Preview text over 2 MiB must continue to fail
with guidance to use direct modelable compile.
For release pipeline or packaging metadata changes, also run:
cd cli
uv run pytest tests/test_release_metadata.py tests/test_release_workflow.py -v
For browser compiler or playground changes, run the complete playground gate and compose the same combined Pages artifact used by CI from the repository root:
uv run python .github/scripts/run_browser_playground.py
uvx --from mkdocs==1.6.1 --with mkdocs-material==9.7.6 mkdocs build --strict
uv run --project cli python .github/scripts/assemble_pages.py --site site --web-dist web/dist
Pull requests only build and test the browser playground. Only pushes to main
deploy the combined documentation and playground artifact.
Manual real-model WebLLM chat conformance
tests/ai-actions.spec.ts exercises the assistant panel against the
deterministic ?ai=simulator provider and always runs in CI. Real-model
checks against WebLLM/WebGPU are opt-in, like the CLI's Ollama conformance
suite above, and never run automatically:
cd web
npm run build
npm run test:e2e:manual
This drives every model in the curated allowlist
(web/src/ai/curated-models.ts, the same list ai.worker.ts filters the
WebLLM catalog down to) through every chat use case, per model:
- create an entity from scratch and accept it;
- apply a natural-language update to an existing entity and confirm prior fields survive alongside the new one (not a wholesale replacement);
- suggest a projection for an existing entity, accept it, and confirm a real
projectiondefinition was written, not just an explanation; - explain the workspace; and
- generate an entity and discard it, confirming the source is untouched.
The update and projection steps also assert the workspace has zero
diagnostics after the AI-applied change, i.e. the generated .mdl actually
validates, not just that the chat turn avoided an AI_ERROR. Restrict to a
subset for a faster loop:
MODELABLE_WEBLLM_TEST_MODELS=Qwen2.5-0.5B-Instruct-q4f16_1-MLC npm run test:e2e:manual
Run this suite whenever you touch what it exercises — it is opt-in precisely because it needs real WebGPU hardware and bandwidth, not because it is optional to keep passing. That includes changes to:
web/src/ai/**(provider, worker, curated model list,ChatPanel.tsx);App.tsx's conversation wiring (runConversation,handleAiAccept,handleAiDownload,handleAiFetchModels);- the shared Python conversation engine or plan schema
(
cli/src/modelable/llm/conversation_engine.py,conversation_planner.py,conversation_plan.py,cli/browser/conversation.py) — these decide what a chat turn is allowed to change and how updates/projections get validated before preview; or - the
@mlc-ai/web-llmdependency version.
A model-download-only change (e.g. adding a curated model id) only needs
that one model exercised via MODELABLE_WEBLLM_TEST_MODELS; changes to the
conversation engine or plan schema affect every provider and warrant the
full default run across all curated models.
It needs a real WebGPU adapter, so it runs headed by default (set
PLAYWRIGHT_HEADLESS=1 only on a machine with confirmed working headless
WebGPU) and skips itself with a clear reason if no adapter is available.
Downloaded model weights persist in a browser profile under
web/output/webllm-profile/ (gitignored) so repeat runs do not
re-download; delete that directory to force a clean fetch after bumping
@mlc-ai/web-llm or changing the curated model list.
Browser playground troubleshooting
- Bumping
searchable(or any package listed incli/browser/browser-lock.json'sexternalWheels):cli/uv.lockandcli/browser/browser-lock.jsonare two separate lockfiles.uv lockonly updates the former, which the CLI and LSP read; the Playground builds its own browser wheel from the latter and ignoresuv.lockentirely. A dependency fix that only updatesuv.lockships correctly to the CLI/LSP while the deployed Playground silently keeps running the old, unfixed version — no error, no failing test, nothing to notice until a user reports the old behavior (this exact thing happened once already; seegit log --oneline -- cli/browser/browser-lock.json). Afteruv lock, updatebrowser-lock.json'sversion/fileName/url/sha256for that package to the matching published wheel (https://pypi.org/pypi/<name>/<version>/jsonlists the wheel's exact URL anddigests.sha256), then runnpm run prepare:pythonfromweb/to checksum-verify it.cli/tests/test_repository_release.py::test_browser_lock_matches_uv_lock_searchable_versionsenforces thatsearchablestays in sync between the two lockfiles specifically; it does not cover otherexternalWheelsentries (currentlylark,pyodide-http). - Checksum or manifest failure during
prepare:python: do not patch generated files underweb/public/python. Confirmcli/browser/browser-lock.jsoncontains the intended pinned identities, then runnpm run prepare:pythonfromweb/. The wheel builder writes its SHA-256 tobrowser-manifest.json. The vendor step validates its complete plan before mutation and preserves the generated Modelable wheel and browser manifest during cleanup. Each downloaded archive is checksum-verified before that archive is written. A checksum failure stops the build but may leave generated staging incomplete; correct the lock or source and rerun the build. INITIALIZATION_FAILEDor an unavailable editor: inspect the browser console for Monaco worker startup failures and the network panel for the same-originpyodide/,python/runtime-manifest.json, and two manifest wheel requests, then runnpm run buildandnpm run test:e2efromweb/. Monaco editor and JSON workers are bundled as same-origin assets, just like the pinned Pyodide runtime and Python wheels. The production UI deliberately sanitizes Python and worker exceptions, so use the development console and failed request status rather than expecting a traceback in the page.- Native/browser conformance mismatch: run
uv run python .github/scripts/run_browser_playground.py. Review the fixture and normalized result difference before changing snapshots. Regeneratecli/tests/conformance/browser/snapshotswithuv run --project cli python cli/scripts/write_browser_conformance.py --output cli/tests/conformance/browser/snapshotsonly for an intentional, reviewed semantic change. - Size or timing budget failure: run
npm run build, thennpm run check:budgetsandnpm run test:e2efromweb/. The JSON output identifies the wheel, application, additional-Python, and Monaco size categories. Monaco is reported separately without a limit; the existing compiler application, wheel, Python dependency, and timing budgets remain enforced. Playwright prints initialization, validation, and generation medians. Keep the enforced budgets fixed unless an approved design change explicitly revises them. - GitHub Pages base-path failure: keep Vite's base at
/modelable/playground/, build both surfaces, and runuv run --project cli python .github/scripts/assemble_pages.py --site site --web-dist web/distfrom the repository root. Confirmsite/playground/index.htmlexists and generated HTML contains no origin-root/assets/URLs. The assembler must compose the proof into the MkDocs output; deployingweb/distby itself drops the documentation.
4. Test Gates
Test gates are selected by risk and touched surface.
| Touched surface | Required gate |
|---|---|
| Documentation only | Markdown diff review, link/reference coherence check, terminology check against the system spec |
.mdl samples or fixtures |
uv run modelable validate <path> from cli/ when the touched file is expected to be supported by the current parser/compiler; otherwise manual grammar and semantic review with the unsupported construct stated in the handoff |
| Parser, IR, or semantic validation | Focused parser/validation tests plus the full local compiler gate |
| Planner, lineage, compatibility, or governance | Focused tests for changed behavior plus representative projection and governance fixtures |
| Emitters or generated artifacts | Focused emitter tests, deterministic output comparison, fixture regeneration review, and Docker-backed compile smoke tests for every affected language backend |
| OpenMetadata export format | uv run pytest tests/test_emit_openmetadata.py -q plus MODELABLE_OPENMETADATA_TESTCONTAINERS=1 uv run pytest tests/test_openmetadata_testcontainers.py -q from cli/ |
| OpenLineage export or sync format | uv run pytest tests/test_emit_openlineage.py tests/test_openlineage_sync.py -q plus MODELABLE_OPENLINEAGE_TESTCONTAINERS=1 uv run pytest tests/test_openlineage_testcontainers.py -q from cli/ |
| ODCS export format | uv run pytest tests/test_emit_odcs.py -q plus MODELABLE_DATACONTRACT_CLI=1 uv run --with datacontract-cli pytest tests/test_emit_odcs.py --tb=short -q from cli/ |
| Protobuf export format | uv run pytest tests/test_emit_protobuf.py tests/test_codegen_targets.py -q from cli/ |
| Scalable gRPC export format | uv run pytest tests/test_emit_grpc.py tests/test_emit_protobuf.py tests/test_codegen_targets.py -q from cli/ |
| FHIR R4 profile export format | uv run pytest tests/test_emit_fhir.py tests/test_fhir_validator.py -q plus MODELABLE_FHIR_VALIDATOR=1 MODELABLE_FHIR_VALIDATOR_JAR=<path-to-validator_cli.jar> uv run pytest tests/test_fhir_validator.py --tb=short -q from cli/ when the HL7 validator jar is available |
| LSP, VS Code extension, or editor integration | Focused LSP tests plus cd vscode && npm ci && npm run check && npm run build && npm test && npm run package |
| Conversational compilation | Focused conversation, compilation service, transaction, audit, protocol, and VS Code tests; fake-provider preview/apply acceptance; exact staged-byte and audit-privacy checks; then the complete CLI and VS Code gates |
| Browser compiler or playground | uv run python .github/scripts/run_browser_playground.py plus strict MkDocs build and combined Pages assembly |
| Release pipeline or packaging metadata | Focused release metadata/workflow tests plus the full local CLI gate |
| Runtime, subscriptions, adapters, or materializers | Unit tests, integration or smoke tests for the adapter boundary, and failure-mode coverage |
| Security, permissions, PII, or restricted fields | Negative tests proving unauthorized exposure is rejected or reported as a governance finding |
Compatibility, lineage, and governance tests must include negative cases when behavior can fail unsafely.
Workflow and policy tests should exercise reusable scripts or parse structured workflow data where feasible. Avoid broad "contains this exact text" assertions for CI behavior when the same requirement can be checked by running the routing logic, parsing the workflow jobs, or inspecting generated outputs.
5. Pull Request Handling
PRs should be narrow and explicit.
Every PR should include:
- Intent: what product or repository behavior changes.
- Scope: documents, modules, samples, or generated artifacts touched.
- Verification: exact local commands or checks run.
- Risk: compatibility, lineage, governance, PII, generated artifact, or runtime risks.
- Deferred work: any intentionally skipped follow-up.
PRs that change published contract semantics, compatibility rules, governance findings, lineage resolution, access policy, or generated artifacts should remain draft until the local gate passes and the relevant risks are documented.
Review feedback is blocking when it identifies:
- A possible weakening of immutable published-contract semantics.
- Missing or incorrect lineage.
- Lowered, omitted, or incorrect classification metadata.
- PII or restricted-field exposure.
- Non-deterministic generated artifacts or registry output.
- Missing tests for compiler, planner, compatibility, lineage, governance, or security behavior.
6. CI and Remote Gate Expectations
Remote CI mirrors the local Ruff, test, VS Code, dependency-audit, and external-smoke gates for the changed surface. It does not replace local verification for ordinary development.
Recommended CI gate sequence as implementation expands:
format check
mypy baseline ratchet
unit tests
dependency audit
fixture-based compiler tests
lineage, compatibility, and governance regression tests
emitter determinism tests
component smoke tests where applicable
CI failures must be investigated from the first failing gate. Agents should not rerun failed CI repeatedly without first reading the failure context.
The Validate workflow starts with a cheap changed-surface detector. Pull request
and push runs execute only the jobs relevant to the files changed in that run;
manual workflow_dispatch runs execute every validation job. Changes to the
Validate workflow or its workflow regression tests force every validation job so
CI edits are self-tested.
The CLI job runs strict mypy through
.github/scripts/check_mypy_baseline.py. The script compares current mypy
output with cli/mypy-baseline.txt: new error lines fail CI, while resolved
lines are reported so the baseline can shrink in the same PR that fixes them.
The OpenMetadata live-smoke CI job must run with
MODELABLE_OPENMETADATA_TESTCONTAINERS=1 when changes affect the OpenMetadata
export format so the generated artifact is checked against a live OpenMetadata
server stack.
The OpenLineage live-smoke CI job must run with
MODELABLE_OPENLINEAGE_TESTCONTAINERS=1 when changes affect OpenLineage export
or sync behavior so generated events are posted to a live Marquez-compatible
lineage backend.
The ODCS lint-smoke CI job must run with MODELABLE_DATACONTRACT_CLI=1 and
datacontract-cli available when changes affect the ODCS export format so
generated ODCS artifacts are checked against the upstream validator.
The FHIR Validator CI job must run with
MODELABLE_FHIR_VALIDATOR=1 and MODELABLE_FHIR_VALIDATOR_JAR pointing at the
HL7-maintained validator_cli.jar when changes affect the FHIR R4 profile
export format so representative generated R4 StructureDefinition profiles are
checked against the upstream validator.
Release changes must also verify package metadata, archive contents, clean-wheel
installation, version agreement, and the manual release dry run. Tag-triggered
publishing uses the protected pypi environment and trusted publishing; agents
must not add long-lived package-index credentials to repository secrets.
7. Open Decisions
- Whether governance findings become blocking CI failures is an open policy decision. Phase 1 treats them as visibility and process-support findings unless a policy wrapper promotes them to failures.
- The PR template location and required status checks are open until repository hosting configuration is added.
8. Release Process
Releases are built from version tags. The tag, Python package, VS Code extension, changelog, wheel, sdist, VSIX, checksums, and release manifest must agree on the version.
There are two ways to prepare a release: the automated one-click flow (preferred,
below) or the manual command sequence (further down). Both converge on the same
things — a focused release PR that bumps the version and freezes the changelog,
then pushing the v<version> tag, which triggers the publish workflow.
Automated one-click release
.github/workflows/release-prep.yml and .github/workflows/release-tag.yml
automate the mechanical part of a release. From the GitHub Actions UI, run the
Prepare release workflow manually with a version input (e.g. 1.5.0);
One-time setup: release-tag.yml pushes the v<version> tag using a
RELEASE_TAG_TOKEN repository secret, not the default GITHUB_TOKEN. This is
required, not optional: GitHub blocks workflow runs from cascading off events
triggered by the default token (an anti-recursion safeguard), so a tag pushed
with GITHUB_TOKEN would succeed and appear in the repo, but
release.yml's push: tags: v* trigger would silently never fire and
nothing would publish. Add a fine-grained PAT scoped to contents: write on
this repo only (or a GitHub App installation token) as the RELEASE_TAG_TOKEN
secret under Settings → Secrets and variables → Actions before running
this flow; release-tag.yml fails with an explicit error if it's missing.
- Visit Actions → Prepare release → Run workflow, set the
version(e.g.1.5.0) and, if desired, enableauto_merge. - The workflow bumps
cli/pyproject.toml,cli/browser/pyproject.toml,vscode/package.json, and the two top-levelversionfields ofvscode/package-lock.json, moves the## [Unreleased]changelog entries into a dated## [<version>] - <date>section (leaving a fresh emptyUnreleasedsection), and regeneratescli/uv.lock. It then opens aRelease <version>PR. - Review the PR diff (especially
CHANGELOG.mdand the English of the entries) and merge it. Ifauto_mergewas enabled, the PR merges itself once CI is green. The automation does not touchROADMAP.md's "The latest published release is ..." baseline line — update it in this PR or a follow-up so the roadmap does not trail the release. - Once the release PR merges,
release-tag.ymldetects the merge, derives the version from theRelease <version>title, and pushes the annotatedv<version>tag. That tag triggers.github/workflows/release.yml, which re-verifies and publishes (see the tag behavior below).
Because the bump is scripted, the version input must be a clean X.Y.Z that is
not already tagged, and it should match the changelog's substance (minor = new
backward-compatible feature/flag, patch = fixes only — see
§9). Always inspect the generated PR: the script
is mechanical and does not judge the changelog content.
Manual command sequence
- Move user-facing changelog entries from
Unreleasedinto a dated release, and updateROADMAP.md's "The latest published release is ..." baseline line (the automated flow does not cover it either). - Set the same version in
cli/pyproject.toml,cli/browser/pyproject.toml, andvscode/package.json.cli/browser/pyproject.tomlmust matchcli/pyproject.tomlexactly —build_browser_wheel.pyrefuses to build a browser wheel when the two disagree. - Run the complete local gates in this document and
CONTRIBUTING.md. - Run the release workflow manually; this validates artifacts without publishing.
- Merge the focused release pull request.
- Create and push an annotated version tag.
- Verify PyPI, the GitHub release, checksums, manifest, and VSIX.
- Install the published wheel in a clean environment and run
modelable --versionplus strict sample validation.
The concrete commands for the manual path are given below.
PyPI publishing uses trusted publishing through the protected pypi
environment. Do not add long-lived package-index credentials. Do not blindly
rerun a failed publication; inspect the first failure and publish a new version
if an immutable artifact already reached the index.
Concrete command sequence (manual path)
Determine the version bump from the changelog-worthy commits since the last
tag (git log v<last>..HEAD --oneline): a new backward-compatible feature or
flag is a minor bump, a fix-only set of commits is a patch bump, per
§9.
git checkout main && git pull --ff-only
git checkout -b release-<version>
# Edit CHANGELOG.md: rename `## [Unreleased]` content into a new
# `## [<version>] - <date>` section (keep an empty `## [Unreleased]` above it).
# Bump the version in cli/pyproject.toml, cli/browser/pyproject.toml, and
# vscode/package.json to match.
cd cli && uv lock # regenerates cli/uv.lock's modelable entry
# vscode/package-lock.json needs the same version at its top two "version"
# fields only (root package + the "" entry) — do not touch dependency
# entries that happen to share the same version number.
uv run ruff format --check .
uv run ruff check .
uv run python ../.github/scripts/check_mypy_baseline.py --baseline mypy-baseline.txt -- uv run mypy src/modelable --no-error-summary --show-error-codes
uv run pytest tests/ -q --deselect tests/test_llm_provider_integration.py --deselect tests/test_codegen_docker_smoke.py
cd .. && git add CHANGELOG.md cli/pyproject.toml cli/browser/pyproject.toml cli/uv.lock vscode/package.json vscode/package-lock.json
git commit -m "Release <version>"
git push -u origin release-<version>
gh pr create --title "Release <version>" --body "..."
Before opening the release PR, confirm the full test suite is green on a
fresh checkout of main — test_repository_release.py::test_local_markdown_links_resolve
and the other repository-hygiene checks in that file are release gates but
are not path-gated the same way as CLI code changes, so an unrelated
docs/archival change on main can silently break the release pipeline
between releases. Fix any such break in its own PR before the version-bump
PR, so the release commit stays focused on the version bump.
After the release PR merges (manual path only — the automated flow tags for you):
git checkout main && git pull --ff-only
git tag -a v<version> -m "Release <version>"
git push origin v<version>
Pushing the tag triggers .github/workflows/release.yml: it re-runs the full
gate, builds the wheel/sdist/VSIX, verifies a clean install, then (on the tag
push, not workflow_dispatch) publishes to PyPI via trusted publishing and
creates the GitHub release. Watch the workflow run to completion before
telling anyone the release shipped — a failure after the build job but before
publish means nothing reached PyPI even though the tag exists.
1.0 release outcome
Modelable 1.0 is tagged and published. The PyPI publish job uses trusted
publishing through the protected pypi environment, repository documentation
uses the 1.0 stable-surface language, and SECURITY.md defines the current
1.0.x support policy. The VS Code Marketplace publish job remains disabled
because Marketplace distribution is deferred from the 1.0 surface.
9. 1.x Compatibility Policy
Modelable follows semantic versioning within the stable surface defined in
README.md and ROADMAP.md.
Additive changes (1.x minor releases):
- New CLI flags, commands, or output fields that do not change existing behavior when not used.
- New generated artifact formats or new emitter options.
- New
.mdlsyntax that is backward-compatible with existing files. - New diagnostic codes (new errors do not break files that previously passed, unless the file was relying on a missing check).
Breaking changes require a major version bump:
- Removing or renaming a CLI command, flag, or stable output field.
- Changing the shape of a generated artifact in a way that breaks existing consumers (e.g., renaming a Rust struct field, changing a TypeScript import path).
- Changing
.mdlparsing rules that cause previously valid files to fail validation. - Changing compatibility, lineage, or governance report output in a way that silently changes findings for existing models.
Not covered by the stable surface (may change in 1.x):
- Internal IR types, resolver internals, and private module APIs.
- Language server protocol message shapes (LSP compatibility is maintained with VS Code, not with direct LSP clients).
- Experimental or beta-labeled commands and flags.
- Generated artifacts for formats labeled "preview" in the CLI reference.
Security fixes are backported to the latest stable minor release. Older minor versions are not actively patched; upgrade to the latest 1.x.
Observable conformance remains the external runtime evidence for significant emitter or compiler changes. For contributor-accessible conformance, use the public fixture tracked in issue #107 and documented in conformance.md.