Skip to main content

Local Development

For people working on modelith itself — the binary, the docs, or the Claude Code plugin. For using the tool, start with the overview; for the format, see the schema reference. Agents working on the repo should also read CLAUDE.md, which holds the repo layout and the CI-breaking conventions summarized at the bottom of this page.

Prerequisites

  • Go (a recent stable release) — the binary and tooling are pure Go.
  • Task — the task runner (brew install go-task).
  • jq — used by the CI plugin check (brew install jq).
  • The claude CLI — only needed to validate/develop the plugin locally.

Building the binary

task build # → ./bin/modelith
task install # go install into GOBIN (on your PATH)

During development you can also skip the build and run from source — go run ./cmd/modelith lint <file> — which is what the lint-models and render tasks do.

The Task workflow

The repo uses Task. The one that matters before pushing:

task check

It runs the CI checks plus a local-only plugin validation. Run task with no arguments to list every target.

CommandWhat it does
task buildBuild the binary into ./bin/modelith.
task installgo install modelith into GOBIN.
task testRun unit tests with cross-package coverage (writes coverage.out).
task coverShow the per-function coverage report (run task test first).
task vetRun go vet.
task staticcheckRun staticcheck (pinned version).
task lint-modelsLint the example models (strict: completeness gaps are errors).
task renderRe-render the example models to Markdown.
task render-checkVerify the committed Markdown is up to date.
task validate-pluginValidate the plugin with claude plugin validate --strict (needs the claude CLI).
task checkCI parity (vet, staticcheck, test, lint-models, render-check) plus validate-plugin.
CI runs a lighter plugin check

task check runs the full claude plugin validate ./plugin --strict locally, where you already have the claude CLI. CI instead does an equivalent jq-based structural check (valid JSON, required fields, skill frontmatter present) so the Go pipeline doesn't depend on the Claude Code CLI. Both gate the same thing.

Developing the plugin locally

modelith ships a Claude Code plugin under plugin/ (skills that author, lint, and load domain models). The plugin files live here, next to the binary they drive, so the skills and CLI version stay in lockstep.

To iterate on the plugin without publishing, point Claude Code straight at the plugin directory:

claude --plugin-dir /path/to/modelith/plugin

You can pass --plugin-dir more than once to load several plugins at once. The plugin's skills are namespaced under its name — invoke them as /modelith:domain-model-author, /modelith:domain-model-lint, etc. Changes to the plugin files are picked up on the next Claude Code session.

Already running the installed plugin? Uninstall it before testing locally

If modelith@claude-community is already installed, both copies load with identical skill names and you can't tell which is active. Uninstall the managed one before testing locally, and reinstall when you're done:

claude plugin uninstall modelith@claude-community
claude --plugin-dir /path/to/modelith/plugin # test
claude plugin install modelith@claude-community

This same --plugin-dir trick is the cleanest way to try the authoring skills in a consuming repo (one that holds a *.modelith.yaml) before publishing — launch the session there with --plugin-dir pointing at your modelith checkout, rather than copying skill files around.

Validate after any change to a manifest or skill:

task validate-plugin # or: claude plugin validate ./plugin --strict

Conventions that break CI (summary)

The full contract lives in CLAUDE.md; the load-bearing ones:

  • The example is a golden fixture. After any change to the renderer or examples/example.modelith.yaml, run task render to regenerate the committed .md. task render-check / CI fails on drift, and there's a golden test in internal/render/markdown. The example must also lint clean under task lint-models (strict).
  • Schema ↔ structs stay in sync. internal/schema/v1/modelith.schema.json and internal/model/model.go are guarded by TestSchemaStructSync. Every object is additionalProperties: false.
  • The canonical schema URL appears in three places (schema $id, the Go URLFor/URL, the example header); TestURLConsistency fails on drift.
  • The binary, not the schema, owns supported versions. Adding a format version = a new vN/ schema + a registry entry in internal/schema; never mutate a shipped version.