cmf Command Reference
cmf is a read-only consumer of a structured intent atlas. Production atlas
records are maintained outside this project; the small
reference-atlas belongs to this project
and exercises the cmf-to-cmv contract. cmf scans an atlas to assemble and
install agent-facing guidance.
Run commands from the atlas root, or pass --root <path>. Intent
records live below intents/. Named profiles resolve below profiles/, while
an explicit profile path can live elsewhere.
Commands
| Command | Description |
|---|---|
cmf assemble <profile> | Write an assembled agent or SKILL.md document to stdout |
cmf install <profile> | Preview platform-aware installation through cmx-core |
cmf install <profile> --apply | Apply the displayed installation plan |
cmf install <profile> --platform codex | Preview installation for Codex only |
cmf status | Count structured intents, materialization profiles, and declared sensors |
cmf validate [--json] | Check every atlas record, sensor declaration, profile, selection, budget, and declared validator path without writing files |
validate checks the whole atlas before installation. It parses intent records
and ecosystem sensors, loads every TOML profile under profiles/, and assembles
each profile using the same selection and token-budget rules as install.
Every executable static-check must point to an executable file within the
atlas; descriptive entries with neither language nor run remain valid.
Validation never invokes a validator and does not write to the atlas, project,
or home directory.
cmf --root ./reference-atlas validate
cmf --root ./reference-atlas validate --json
Exit status 0 means valid, 2 means invalid atlas input (and also clap
command-line usage errors). Human output names the source file and field for
each failure. JSON output has stable valid, intents, profiles, and
diagnostics fields; each diagnostic has file, field, and message.
For malformed intent TOML, file is the scanned record path, even when the
parser includes a location in its error message. A missing required field uses
that field's name (for example, title); syntax errors without a field use
record.
Validation continues after an invalid record or profile and reports each
failure it can inspect in one run. A profile that selects an invalid record
may also report a missing selection. Repair the record first, then rerun.
Records and profiles are visited in sorted path order, so repeated runs over
the same atlas produce the same JSON bytes. The counts include successfully
scanned intents and successfully assembled profiles.
assemble --explain writes selected intent keys, graph traversals, and the
estimated token count to stderr, leaving stdout safe for redirection.
--surface agent|skill can override a profile's delivery surface.
--manifest <path> additionally writes the compile manifest (below) to that
path.
install is global by default. Use --local for project scope and --force
to replace drifted or newer installed guidance. Target platforms come from
cmx configuration and existing lock state; cmf does not duplicate their path
or format rules. Pass --platform codex to target only Codex, including when
Codex is not yet in the managed set. Codex agents are transformed from the
assembled Markdown into .codex/agents/<name>.toml; skills are installed as
.agents/skills/<name>/SKILL.md. Neither target replaces a project's
hand-authored AGENTS.md. Omitting --platform preserves the existing
cmx target resolution. A local install also records the compile manifest at
.context-mixer/cmf-manifest.json, beside the local lock file: the preview
says so, and --apply writes it. Each successful local artifact write
updates only that artifact’s compilation record; a preview or refused install
leaves the manifest unchanged. Global installs write no manifest.
A local install (preview and apply alike) also compares the profile's declared
[select] ecosystems with what the atlas's sensors detect in the
current directory, and warns on stderr when a declared ecosystem is missing —
a profile compiled for rust against a Python project is almost certainly the
wrong profile:
warning: profile rust-shipping targets rust but this project shows python
The line ends … but the atlas declares no sensors when the atlas has no
sensor file, and … but nothing was detected when its sensors found nothing.
A profile that declares no ecosystems filters nothing and never warns.
assemble has no project and does no detection.
cmf status adds a Sensors: N ecosystems line (Sensors: none declared
when the atlas has no sensor file).
Compile manifest
The manifest is cmf's second output: a JSON record of what was compiled, so a
verifier can later hold the project to exactly those intents. It carries a
schema version (1 for a standalone assemble --manifest record; local
installs use a schema-2 artifacts array of these records), the compiled_at instant, the atlas (its
path, plus its cmx source name and git revision when the root is a
registered source or a git checkout — both omitted otherwise), the profile
id, version, and declared ecosystems (always present, empty when the profile
declared none), the delivered artifact (name, surface, and sha256:
checksum of its content), one intents entry per retained record (id,
catalog key, and the sha256: checksum of the record file) in the order
they were retained, and a dropped list that is currently always empty
because assembly fails rather than drops on budget overrun.
Profile schema
id = "rust-dependency-change"
version = "0.1.0"
description = "Use when adding, removing, or upgrading Rust dependencies."
surface = "skill"
budget_tokens = 2800
[select]
keys = ["craftsperson/audit-dependency-risk"]
categories = ["dependencies", "quality"]
tags = ["security", "cargo"]
ecosystems = ["rust"]
[graph]
follow = ["specializes", "related-to"]
max_related_depth = 1
prefer_specializations = true
[content]
include = ["guidance", "rationale", "evidence"]
A profile must name exact keys or combine category and tag filters. This guard
prevents accidental whole-catalogue exports. Graph expansion is bounded, and
generation fails instead of truncating when the shaped artifact exceeds its
declared context budget. prefer_specializations defaults to true whether or
not a [graph] table is present: a selected specialization drops the general
record it specializes, and, when ecosystems is declared, the declared
ecosystems' specializations of every selected record are pulled in first (see
Downward expansion).
Ecosystems
The atlas carries a record's language and tooling as the directory it
sits in — the directory hierarchy is the realization hierarchy, so
craftsperson/python/uv/ specializes Python guidance for uv-based projects. A
record's ecosystem qualifiers are the segments of its catalog key between
the collection root and the slug: craftsperson/python/uv/pin-interpreter has
qualifiers python and uv, craftsperson/rust/use-structured-tracing has
rust, and craftsperson/isolate-functional-core-from-effects has none.
[select] ecosystems names the ecosystems the artifact targets. A record is
eligible when it has no qualifiers or every qualifier it has is declared;
so ecosystems = ["python"] admits the general records and the python/
specializations but not python/uv/, and ["python", "uv"] admits all three.
A profile that declares no ecosystems applies no filter. Eligibility applies in
three places:
- Category/tag selection never picks an ineligible record. Without a
filter,
categories = ["quality"]withtags = ["testing"]pulls every ecosystem's testing records into one artifact; with one, only the declared ecosystems' records (and the general ones) remain. - Graph expansion skips an edge to an ineligible target instead of
following it, and records the skip in
--explain's traversal as<key> --<relation>--> <target> (skipped: ecosystem). - Explicit keys must be eligible: naming a key outside the declared ecosystems is an error that names the key and the qualifier that excluded it, because a profile should not contradict itself.
--explain prints the declared ecosystems and how many category/tag matches
the filter excluded. The compile manifest records the declared list as
profile.ecosystems (empty when none) so a verifier can compare it with the
ecosystems it detects (cmv reports the difference as profile_mismatch).
Downward expansion
Graph expansion along follow edges walks upward: from a specialization
along its specializes edge to the general record. A profile that selects
general advice by category and tag would therefore never find the ecosystem's
own version of it. Once the ecosystems are declared it is safe to walk
downward too, because only the project's own specializations qualify.
After selection and follow expansion, cmf pulls in every eligible record that
specializes a selected record, transitively to a fixpoint: a general record
pulls python/…, which pulls python/uv/… when uv is declared. The existing
shadow removal then drops the general parent, so the ecosystem's version
replaces the general advice rather than duplicating it. Downward expansion
runs only when both gates hold; otherwise it is a no-op:
[graph] prefer_specializationsistrue(its default). Its meaning — prefer an ecosystem's specialization over the general advice — now covers both finding specializations and dropping the parents they shadow.[select] ecosystemsis non-empty. Without a declared list, downward expansion would pull every language's version of every general record, which is exactly the failure the filter exists to prevent.
Assembly order is: initial selection → follow expansion → downward expansion
→ shadowed-parent removal → budget check. Records added by downward expansion
do not have their own follow edges expanded; that keeps the pass bounded
and deterministic, and their specializes edge already points at a selected
parent. Each pull appears in --explain's traversal as
<general-key> <--specializes-- <specialization-key>, and --explain prints
the total as specialized downward: N beside the ecosystem lines.
Selected intents render as compact, ordered blocks rather than separate
guidance, rationale, and evidence sections. Within each block, rationale
contributes the capability, threat, expectation, and accepted trade-off;
guidance contributes the preferred strategy; and evidence contributes
required or optional verification. Intent titles and evidence-type metadata
stay in --explain provenance instead of consuming delivered context.
Sensors
The atlas declares the ecosystems it supports, and how to recognize each in a
project, in ecosystems.toml at its root. cmf and cmv read it — cmf for the
install --local warning above, cmv to decide which validators run — and
neither carries a built-in table of build files.
[python]
signatures = [
{ file = "pyproject.toml" },
{ file = "setup.py" },
{ glob = "requirements*.txt" },
]
[uv]
implies = ["python"]
signatures = [
{ file = "uv.lock" },
{ file = "pyproject.toml", contains = "[tool.uv]" },
]
One table per ecosystem. signatures is a non-empty list of predicates; the
ecosystem is detected when any one holds. implies (optional) names the
ecosystems detected whenever this one is, transitively — uv implies
python. The predicate kinds are:
| Predicate | Holds when |
|---|---|
{ file = "<path>" } | the root-relative path exists, as a file or a directory |
{ file = "<path>", contains = "<literal>" } | the file exists and holds the literal |
{ glob = "<pattern>" } | at least one root-level entry, file or directory, has a name matching the pattern |
Every predicate reads only the project root, so detection is deterministic
and, at a pinned revision, pinned with everything else. A predicate with an
unknown key, no recognized kind, a contains without file, a glob
combined with file, or an empty value is a parse error naming the ecosystem
and the signature; a file path may not be absolute or contain ...
The names are the atlas's own vocabulary: an ecosystem's name must be a
directory the realization hierarchy below intents/ uses (python for
intents/craftsperson/python/, uv for the nested
intents/craftsperson/python/uv/). cmf and cmv validate the file against
the scanned records and reject the atlas, naming the ecosystem and the record
that exposed the problem, when a declared name is not such a directory, when
an implies target is not declared, or when a declared nested ecosystem does
not imply its parent. An ecosystem the records use but the file does not
declare is simply undetectable.
An atlas with no sensor file detects nothing. cmf then cannot compare a profile's ecosystems (the warning says so) and cmv reports every validator-bearing intent unchecked with a reason (see the cmv reference).
Validator evidence
An evidence entry of type static-check declares an executable validator for
the intent. It carries two extra fields: language, the source language the
validator reads (rust, python, …), and run, the path of the validator
executable relative to the atlas root. A record may declare one
static-check entry per language it can be checked in.
evidence = [
{ type = "architecture_review", description = "Pure modules import no gateway.", required = true },
{ type = "static-check", language = "rust", run = "checks/rust/isolate_functional_core.py", description = "No gateway trait is referenced from a pure module.", required = true },
]
Only the description renders — under Require or Observe when useful,
exactly like any other evidence — so the agent learns what will be checked
while language and run never reach the delivered artifact. A static-check
entry with neither language nor run is ordinary descriptive evidence — an
expectation a validator may later make executable — and declares no validator.
cmf rejects an atlas at scan time, naming the record and entry, when a
static-check entry carries only one of language and run (both are needed
to declare a validator), when either field appears on another evidence type,
or when run is absolute or contains ... Executing validators is the
job of the verifier, cmv (see CMV.md); cmf only records and renders
them.