Capstone
Capstone scaffolds a batteries-included Elixir application and then keeps it upgradable. It is the whole stack, wired together and generated in one go:
capstone = cache(valkey) + authn + postgres + svelixir + tailwind
+ openbao + nodejs + elixir
This is one package, one namespace. It carries both the project
generator (mix capstone.new, Capstone.New.*) and the engine that keeps a
generated project upgradable for the rest of its life (Capstone.*) —
installed either as a global mix archive or as an ordinary dependency of a
project it generated, from the same capstone-x.y.z.ez/hex release. Nothing
here
resolves to a runtime dependency in :prod — everything the engine needs at
runtime (Sourceror, TypedStruct, Vex, simple_enum) is vendored under
lib/capstone/vendor/ instead of declared in mix.exs, and every declared
dependency is only::dev/:test. CredoNoRuntimeDepsTest and
Capstone.Credo.Check.Design.NoRuntimeDeps enforce that mechanically on every
commit — see goals.md for why it matters.
Getting started
mix archive.install hex capstone
mix capstone.new --path target.exs
target.exs is a small Elixir literal describing the project to generate:
%{
schema_version: 1,
base: :api,
project: [name: "my_app", github_org: "acme"],
plugins: []
}
base is :api, :web, or :both; project.module and project.app are
optional and derived from name when omitted. plugins is a list of plugin
type atoms — this package ships :cache, :openapi and :prod_image_api
— resolved and applied during generation, before dependencies are fetched.
See Building a plugin and
Applying a plugin for how that works.
Once a project exists, adding a plugin to target.exs and running
mix capstone.update applies it without regenerating anything — see
Upgrading the installation.
A project generated by Capstone already carries capstone as a dependency —
it is what keeps that project upgradable for the rest of its life. Add it by
hand only when adopting Capstone into a project that was not generated by it:
def deps do
[
{:capstone, "~> 0.1"}
]
end
A version-skew guard (Capstone.VersionGuard) catches the one case where a
globally-installed archive and a project's own pinned dependency on it
disagree.
Status
Pre-1.0 and under active development. The API is not yet stable — see
goals.md for the design record.
Versioning
A single .version file holds one bare x.y.z line, read by mix.exs at
compile time.
mix devops.bump_version bumps it based on HEAD's Conventional Commit
type (feat → minor, fix → patch, chore/test/build/ci → no
bump — see config :devops, :commit_types in config/devops.exs for the
full table) and amends that bump into the commit it describes. Run it by
hand, or activate it as a post-commit hook once per checkout:
git config core.hooksPath scripts/hooks
Changelog
CHANGELOG.md is generated, not hand-written: mix devops.release runs
git cliff (configured in cliff.toml) against full history, consolidating
every tagged release into one section each. Commit groups mirror
config/devops.exs's :commit_types table — a commit type that doesn't
bump the version (test, chore, build, ci) doesn't appear in the
changelog either.
Gates
Every commit is expected to satisfy:
mix format --check-formatted
mix credo --strict
mix coveralls # the suite plus the 100% line-coverage gate
mix dialyzer
mix doctor
mix credo --strict fails while a TODO(capstone) marker exists — see
goals.md for why that is a feature,
not friction.
Documentation
goals.md— the design record: why Capstone exists, the architecture decisions, and the numbered goals it's held to.- Building a plugin — the
Capstone.Plugin.Behaviorcontract, and how a first-party plugin is derived and packaged into the shipped registry. - Applying a plugin — what happens when a plugin is resolved and installed, and how to retire a bad archive.
- Upgrading the installation —
mix capstone.update, and what it deliberately does not do.
Generated API reference: https://hexdocs.pm/capstone
Licence
MIT — see LICENSE.