C3 — one model, three acts
C3 is your architecture's own vocabulary, frozen into shared truth, that work edits only through reviewed change-units.
- Shape the model, freeze the facts. Descend the domain top-down — draft the facts the work needs and wire them as you go; the lean canvas (sections + typed columns each entity carries here) is already in place, taking a custom fact-type only where the domain needs one. Then flip the gate and the facts freeze: shared truth, never hand-edited again.
- Change-units drive progress. Work advances by change-units — an ADR plus its patch folder, one atomic saga: declare intent, the tool keeps the result integral (membership, citations, gates), flip it all-or-nothing. Frozen facts change only through this merge.
- The canvas grows — and evolves — with the need. When work outgrows the model, raise the canvas (climb a rung, additive) or morph it (reshape a mis-modeled type non-additively) and migrate every fact to fit — in one gated, atomic unit; completeness is never relaxed.
Build the model → freeze the facts → change-units drive the work → the canvas grows with the need.
CLI invocation
The packaged runtime is c3x, exposed through the skill-local wrapper at <skill-dir>/bin/c3x.sh. There is no c3 executable. Invoke the wrapper explicitly for every command:
# Template: resolve <skill-dir> to this skill's directory, then replace the command and args.
C3X_MODE=agent bash "<skill-dir>/bin/c3x.sh" check
For several commands in one shell, this helper is allowed, but it is only a shell function and does not install a c3 command:
c3local() { C3X_MODE=agent bash "<skill-dir>/bin/c3x.sh" "$@"; }
C3X_MODE=agent → TOON output (~40% fewer tokens) and help[] hints appended to each result — follow them. The packaged CLI is the single source; this skill is the router: classify the intent, load the reference, invoke the wrapper, follow the output. It names every gate and teaches no procedure — procedure lives in the references.
Intent Classification
| Keywords | Op | Reference |
|----------|----|-----------|
| adopt, init, bootstrap, onboard, "create .c3" | onboard | references/onboard.md |
| where, explain, how, diagram, trace, "show me", "what is", "what handles" | query | references/query.md |
| audit, validate, "check docs", "is the doc intact" | audit | references/audit.md |
| add, change, fix, implement, refactor, remove, design | change | references/change.md |
| pattern, convention, "create ref", "update ref", standardize | ref | references/ref.md |
| "coding rule", "coding standard", "split ref into rule" | rule | references/rule.md |
| "edit the canvas", "change the shape", "what sections does X have", "add a doc type", "raise the bar", "add prd/user-story" | canvas | references/canvas.md |
| impact, "what breaks", assess, sweep, "is this safe" | sweep | references/sweep.md |
| "does the code match", conformance, "is the doc still true", "check against code", drift-vs-external | eval | references/eval.md |
Dispatch
- Classify the op (ambiguous →
AskUserQuestionwith options). - Load
references/<op>.md. - Run the CLI (Task tool for parallelism), follow
help[].
Precondition — read-only fast path. Concept → wrapper search; file/glob → lookup; id/section → read --section. After a hit, use one graph <id> --depth 1 for route context, then stop when owner, governing refs, one causal link, and the next action have evidence. Read route.facts, route.graph, route.anchors, route.lanes, route.drift, and route.hash as inspection clues, not proof or apply gates. For ownership/query sessions, cap proof at four C3 calls: search/lookup, one graph, the owner section, and one governing-rule section; do not read every cited rule. Prefer compact reads; do not repeat successful reads. Use list, full reads, source scans, or tests only for a miss, drift, explicit audit, or change. For open-ended impact work, keep 2–3 independent routes, redirect duplicates, block theorem-strength gaps, and reopen only with a new mechanism; require concrete ids, commands, and evidence. Never Read/Glob/Edit .c3/ instances (canvas definitions are the user-owned exception); missing .c3/ → onboard.
The shared contract
These rules are stated once here; every reference cites them.
Frozen facts. A fact is any entity whose canvas declares no status: set — system, container, component, ref, rule, pm-requirement, user-story. The moment a fact carries a body it is frozen: the wrapper's write, set, and delete operations on it are refused (the guard keys on the first arg; an unknown type is treated as frozen). The refusal names the only legal path: "<id> is a fact — facts are frozen and change only through a change-unit." A fact changes only by authoring patches in a change-unit and running the wrapper's change apply operation.
Exempt from the freeze: the wrapper's add operation (creating a new fact is unguarded), the first write that authors a never-bodied fact, editing a change-doc (adr/prd/atomic-design-change — they declare status:), and editing a canvas definition (user-owned). The fact→code binding lives outside the freeze entirely: it sits in .c3/eval/<fact>.yaml (a code: field) — an ordinary editable file, re-aimed freely as code moves, never a frozen fact, so it needs no exemption (references/eval.md).
Membership is by construction. A parent's membership table is synthesized by the tool from its children's parent: links, on every path that changes parentage (add, change apply, or check --fix through the wrapper). Leave the row to the tool — never hand-author or hand-edit it. (Set parent:; the row appears.) Parentage is not a graph edge: parent: sets the child's parent_id and the synthesized membership row — a separate axis from wiring edges (uses, citations, edge<> columns), which the wrapper's graph operation shows. A reparent updates parent_id + both parents' membership; it does not touch wiring.
A fact is always complete to its rung. A canvas is a rung — a complete contract for one complexity level, not a target to fill in over time. A fresh init's canvas is deliberately lean (rung-1); the deeper sections a complex project needs are a higher rung, not a hole. Completeness is never relaxed. To grow, climb a rung: raise the canvas, then migrate every affected fact up to the new contract, completely — integrity forbids a fact straddling two rungs. When the shape itself is wrong — mis-modeled, not merely lean — morph it: a non-additive reshape of the canvas (the evolve-unit: a canvas-scope patch) that migrates every instance to the new shape in one gated, atomic unit, on the same no-straddle rule (references/change.md §Morphing the model).
ADR status set: [open, accepted, done, superseded]. (The wrapper's add adr operation stamps proposed — the legacy synonym for open; accepted auto-latches to done when its After-cites resolve fresh.) Terminal change-docs (done/superseded) are content-frozen historical records, exempt from the wrapper's check operation. list/check exclude ADRs by default; --include-adr to include.
Command table
The packaged CLI is the catalog — invoke the local wrapper with <cmd> --help; its output is authoritative. The change-unit gate stack (the load-bearing flow) lives in references/change.md.
| Command | Purpose |
|---------|---------|
| list | Topology with counts + coverage (--flat, --compact) |
| check | Validate facts against their canvas + consistency (--fix, --only, --include-adr). Fact-vs-code conformance is not here — run the wrapper's separate, off-switch eval operation |
| eval | Check a frozen fact's claim against the uncontrolled external it governs (the code: binding in .c3/eval/<fact>.yaml). A one-off CI-cadence verdict, never a gate (references/eval.md) |
| repair | Rebuild the disposable cache from canonical .c3/ and reseal (after a branch switch / selective merge) |
| search <query> | Concept → entities by semantic + keyword + graph signal, with route clues when available |
| lookup <file-or-glob> | File/glob → component(s) + refs |
| read <id> | Entity content (--full; --section <name> --cite emits the patch base anchor) |
| graph <id> | Relationship graph plus route facets (facts, graph, anchors, lanes, drift, hash); --depth, --direction forward\|reverse, --format mermaid, --unit <adr-id> previews staged patches |
| add <type> <slug> | Create a fact (body via stdin or --file; --container, --feature). The unguarded create path |
| canvas <list\|read\|add\|write> | Manage canvas definitions (user-owned shape, at .c3/canvases/) |
| schema <type> | Render a canvas's sections/columns/REJECT-IF (leads with the rejection contract) |
| write <id> / set <id> <field> <val> / delete <id> | Direct edits — refused on a frozen fact (see the contract). For change-docs and canvas bodies only |
| change <new\|view\|status\|accept\|apply\|rebase\|scaffold> | The change-unit saga — the only way to mutate a fact. apply runs the gate stack atomically; rebase emits the drift bundle for drifted patches; scaffold stages a rung-climb. See references/change.md |
Missing a packaged operation → STOP, tell the user. No file-tool workarounds.