Context for agents (and humans) working on modelith itself. For using the
tool, start with the README and docs/.
modelith is a single static Go binary (cobra CLI: lint, render, schema)
that validates and renders *.modelith.yaml domain models. Module path
github.com/stacklok/modelith.
modelith/
├── cmd/modelith/ # CLI entrypoint (cobra)
├── internal/
│ ├── model/ # Go structs + YAML (un)marshalling
│ ├── lint/ # structural (schema) + semantic + completeness
│ ├── render/
│ │ ├── markdown/ # YAML → Markdown (embeds the Mermaid)
│ │ └── mermaid/ # YAML → Mermaid (erDiagram)
│ └── schema/ # version registry + compile/dispatch
│ ├── schema.go
│ └── v1/modelith.schema.json # canonical v1 schema (to be served at modelith.sh)
├── examples/ # worked example: *.modelith.yaml + committed *.md (golden)
├── docs/ # Docusaurus-importable docs (published at modelith.sh)
├── plugin/ # Claude Code plugin (skills/)
├── project-docs/ # durable project records (not published)
│ ├── adr/ # forward-looking decision records
│ └── audits/ # retrospective multi-agent audit snapshots
├── .claude/rules/ # path-triggered coding/process conventions
├── action.yml · Taskfile.yml · .goreleaser.yaml · .github/workflows/
internal/schema/is the source of truth for the format. The Go code embeds each version's JSON Schema viago:embed; the canonical copy is destined for a stable URL (https://modelith.sh/schema/domain-model/v1.json) that editors will fetch via the# yaml-language-server: $schema=header once it's served (a roadmap item — not live yet; the CLI/CI embed the schema, so they don't depend on it). Living underinternal/keeps the Go API private (internal/is a Go-compiler rule) without affecting URL reachability. Versions are directories (v1/,v2/, …) so the repo layout mirrors the URL layout and adding one is additive.internal/notpkg/— this is a tool, not a library. The CLI and the published schema are the contract; the Go API is private. Promote to a public API later only if there's demand.docs/is self-contained Docusaurus content built and served by thewebsite/directory at modelith.sh.plugin/ships the agent tooling next to the binary it drives, so skills and the CLI version stay in lockstep.
The repo uses Task. The one that matters:
task check # run before pushing — CI parity plus local plugin validationtask check runs vet, staticcheck, test, lint-models, render-check,
and (locally only) validate-plugin. Run task with no arguments to list every
target. The full target table, build/install steps, and how to develop the
Claude Code plugin with --plugin-dir live in
docs/09-local-development.md.
- The example is a golden fixture.
examples/example.modelith.yamland its committedexamples/example.modelith.mdmust stay in sync. After any change to the renderer or the example, runtask render(ormodelith render examples/example.modelith.yaml) to regenerate the.md.task render-check/ CI fails on drift;internal/render/markdownhas a golden test against it. The example must also lint clean undertask lint-models(strict: completeness gaps are errors). - Schema ↔ structs stay in sync.
internal/schema/v1/modelith.schema.jsonandinternal/model/model.goare guarded byTestSchemaStructSync(every schema property has a matching struct json field and vice versa). Every object isadditionalProperties: false. - The canonical schema URL appears in three places — the schema's
$id, the GoURLFor/URLininternal/schema/schema.go, and the example header — andTestURLConsistencyfails if they drift. Don't hardcode the URL elsewhere. - The binary, not the schema, owns supported versions.
internal/schemaholds aregistry(version → embedded bytes);lintreads the declaredversionand gives a friendly error before schema validation. Adding a format version = newvN/schema + a registry entry; never mutate a shipped version. - The
docs/follow publishing conventions. They are built bywebsite/and served at modelith.sh. Page files are numberedNN-name.md(landing is plainindex.md), carrytitle:, and cross-link with relative, prefix-included paths. Thedocs/05-parking-garage/example is lint/render-checked by CI, globbed by path inTaskfile.ymland.github/workflows/ci.yml— renumber that dir and you must update both. Full rules:docs/_docs-conventions.md.
- Format evolution requires the new structured forms; no legacy string forms.
Invariants are
{id, statement}referenced byid; enums are first-class (top-levelenums, referenced from an attributetype); a top-levelglossarydefines non-entity vocabulary; actions are a bare string or{name, actor?, preserves?, description?}; attributes can bederived(with a requiredderivation). Seedocs/06-schema-reference.md. - Stay on schema
v1while pre-release — there's no external*.modelith.yamlcorpus to preserve, so the format evolves in place rather than bumping to v2.
Push a vX.Y.Z tag on main — release.yml builds, signs, generates SBOMs,
publishes the GitHub Release, and pushes the Homebrew formula to
stacklok/homebrew-tap. After it succeeds:
- Bump
action.yml'sversioninput default to the new tag and commit.action.ymldownloads a specific pinned release rather than building from source (seedocs/08-github-action.md) — skip this step and the action keeps installing an old release indefinitely, with no error to flag it. - Update
docs/08-github-action.mdfor the new tag. Change its example action reference and documentedversiondefault to the release tag, then revise any feature-availability notes that depended on the former default. The Action page is a copy-paste entry point; leaving it on an old tag makes its instructions disagree withaction.yml. - Bump
plugin/.claude-plugin/plugin.json'sversionto match, if the plugin/skills changed. The plugin ships next to the binary it drives so the two stay in lockstep (see "Repository layout" above) — this doesn't enforce itself. - If the plugin is listed on the Claude plugin marketplace
(
anthropics/claude-plugins-community), check whether a meaningfulplugin/change needs to be re-submitted there too. Marketplace entries pin a specific commit SHA (confirmed by inspecting that repo'smarketplace.json) — listing it once does not mean it tracksmainafterward. The exact re-submission mechanism wasn't determined as of this writing (submission is via https://clau.de/plugin-directory-submission, not a PR — direct PRs against that repo are auto-closed).
Durable project records live under project-docs/, kept off
the published site to keep the root clean:
project-docs/adr/— forward-looking decision records. When you make a hard-to-reverse call with real trade-offs, record it as an ADR so intentions stay current. Shape and bar:.claude/rules/adr.md.project-docs/audits/— retrospective multi-agent audit snapshots (rationale for the choices above) and the process for running new ones.
- Every commit must be signed off (DCO). Commit with
git commit -sso aSigned-off-by:trailer is added, certifying you have the right to submit the change under the project's license..github/workflows/dco.ymlenforces this on every commit in a PR and fails the check if any commit lacks the trailer. Details:dco.md. - Coding and process rules are path-triggered.
.claude/rules/holdsgo-style.md,testing.md,adr.md, andagent-workflow.md; each declares the paths it governs and loads when you edit a matching file. Where a rule file and this file disagree on a point it covers, the rule file wins. - Non-trivial or risky code changes follow the subagent review-loop and
model-discipline protocol in
.claude/rules/agent-workflow.md. It is available discipline for changes that put a correctness-critical surface at risk, not a mandate on every commit. - Imported skills record where they came from.
.claude/skills/holds skills for working on modelith (distinct fromplugin/skills/, which ships to users). When a skill is copied in from elsewhere, add its upstream source, commit, licence, and local changes to.claude/skills/SOURCES.md. - Scratch work (spikes, smoke tests, throwaway fixtures, review
round-records) goes in the repo-local, gitignored
.scratch/— not/tmpor a session temp dir. It stays browsable in the checkout; clean it manually when stale. HANDOFF.md(repo root, gitignored, local-only) holds running state: current state, decisions in flight, ordered next steps. Update it before ending a significant session. Never commit it or reference it from tracked files.