JSON output of the reference tool#
Every command of the tool accepts --json and then prints one JSON value on standard output. Paths are relative to the project root, with forward slashes; the root itself is ".". Later versions may add keys. The output of model has a schema of its own, versioned with the standard, and a key of it is never removed within a version.
Failure#
When a command cannot run, it prints this object, a message on standard error, and exits with 2 (3 for an internal error):
{"ok": false, "error": {"code": "no-project", "message": "no unifold project found: ..."}}
code |
Meaning |
|---|---|
usage |
Wrong command line |
no-project |
No unifold.yaml with the key unifold here or above |
bad-root |
The root manifest does not parse or states another version than 0.3; check reports it as a finding instead |
no-such-path, not-a-folder, not-a-root |
The path given does not fit the command |
unknown-capability, unknown-rule, nested-project |
The thing asked for does not exist here |
not-writable, io, no-mermaid |
A file could not be read or written |
internal |
A bug in the tool |
For check, the object also has summary and an empty findings list, so that a reader of findings needs no second code path.
check#
{"ok": false,
"summary": {"units": 12, "error": 1, "warn": 0, "info": 0, "rules": {"W2": 1},
"ignored": {"C2": 3}, "root": "/work/shop", "tool": "0.3.6"},
"findings": [
{"rule": "W2", "severity": "error", "file": "cloud/unifold.yaml", "line": 14,
"message": "need 'alerts.push' has no provider",
"fix": "Add `alerts.push: <provider>` to the wiring of this manifest, or declare a need `push` here to pass it up.",
"url": "https://unifold.md/rules#w2"}]}
okis true when the exit code is 0: no error, and with--strictno warning.severityiserror,warnorinfo.lineis a number ornull.fileis the file to change; for a missingREADME.mdorSKILL.mdit is the folder.fixsays what to do, specific to the finding where the tool can tell.ignoredcounts the findings that--ignoredropped, per rule; they are not infindingsand not in the counts.- With
--fix,summary.fixedhaswritten,removedandrefused(lists of paths).--fixwrites generated files only, never a stamp. --format githubprints the same findings as lines::error file=cloud/unifold.yaml,line=14,title=unifold W2::message Fix: ..., with paths relative to the current folder, then the summary line.
show#
{"name": "serial", "path": "transport/serial",
"responsibility": "Moves frames through the serial port with deadlines",
"kind": "adapter", "status": "planned", "owner": null,
"secret": {"what": "opening the port, writing to it and reading it with deadlines",
"decision": "transport/docs/decisions/0001-the-line-is-one-unit.md"},
"not": [{"text": "frame layout", "need": "frames"}, {"text": "retries", "need": null}],
"manifest": "transport/serial/unifold.yaml",
"parent": {"path": "transport", "name": "transport", "status": "planned", "responsibility": "..."},
"children": [], "nested-projects": [],
"capabilities": {"transport": {
"kind": null, "summary": "...", "contract": "docs/references/transport.md", "parts": null,
"used-by": [{"unit": "session", "status": "planned", "need": "transport", "config": null}]}},
"needs": {"frames": {
"kind": null, "summary": "...", "contract": "docs/references/frames.md", "parts": ["encode"],
"optional": false, "platform": false,
"provided-by": [{"unit": "codec", "status": "planned", "capability": "frames", "config": null}]}},
"scenarios": [{"unit": ".", "name": "open", "summary": "The host opens a session"}],
"skills": [{"name": "add-a-quantity", "path": "...", "description": "..."}],
"decisions": [{"path": "...", "title": "...", "status": "proposed"}],
"documents": {"tutorials": [], "references": ["..."], "explanation": ["..."]},
"diagrams": ["transport/serial/docs/diagrams/context.md"],
"files": ["transport/serial/README.md", "..."]}
used-byandprovided-byare resolved through the whole tree: they name the units whose own code is on the other side. A unit above can be such a user, when it wires a need of its own to this capability; a need that a unit meets inside is provided by a unit below it, never from outside.- The other form of an entry:
{"outside": true, "root-capability": "api"}and{"outside": true, "root-need": "clock", ...}for the world outside the project. configis the name of the alternative, ornull.partsis the list undercontract, ornullfor the whole contract.contractis a path relative to the root. Both sides of a connection name the same contract, so an entry underused-byorprovided-byrepeats none.scenarioslists the scenarios whose hops name the unit or use a wiring line of it, with the unit that holds each.- A
statusother thanactive, and a decision status other thanacceptedorderived, means: not in force. The text output marks these in brackets.
capability#
{"unit": "db", "name": "store", "side": "capabilities",
"ends": [
{"unit": "db", "status": "planned", "side": "capabilities", "name": "store", "kind": null,
"summary": "Keeps things", "contract": "docs/references/store.md", "parts": null,
"optional": null, "readme": "text of the README section, or null"},
{"unit": "web/inner", "status": "active", "side": "needs", "name": "store", "...": "...",
"optional": false, "config": null}]}
The first end is the capability or need asked for; the others are every final user or provider. An end outside the project has "outside": true.
impact#
{"unit": "db", "status": "planned", "capability": null, "file": null,
"dependents": [{"unit": "web/inner", "status": "active", "distance": 1,
"via": "need 'store' is met by capability 'store' of db"}],
"exposed-as": [{"unit": ".", "capability": "store"}],
"outside": ["store"], "root-capabilities-affected": ["api", "store"],
"citations": [{"file": "web/docs/explanation/context.md", "line": 12, "cites": "db/README.md",
"kind": "context-item", "title": "db: storage rules",
"says": "...", "stamp": "current"}]}
distance 1 is a direct user, which can be the parent when its own need is wired to the unit; higher numbers depend on a dependent. exposed-as lists the units above that provide the capability as their own; outside the capabilities of the root through which the world uses it directly; root-capabilities-affected every capability of the root that depends on it. stamp is current, stale or missing.
tree, cited-by, skills, rules#
tree prints a list, the root first:
[{"path": "cloud", "name": "cloud", "depth": 1, "status": "planned", "kind": "system",
"responsibility": "...", "parent": ".", "capabilities": ["public-api"],
"needs": ["push-service"], "children": ["cloud/platform"], "nested-projects": []}]
cited-by prints the citations rows of impact; a row of kind link has no title, says or stamp. skills prints [{"name", "unit", "path", "description"}]. rules prints [{"id", "severity", "what", "fix", "url"}].
model#
unifold model prints the resolved map of the whole project. Its schema is unifold-model 0.3 and changes only with a decision; within 0.3 keys are added, never removed or changed in meaning. Keys that start with x- in a manifest, a capability, a need or a scenario are passed through untouched under x. A process tool reads this and nothing else.
{"unifold-model": "0.3", "tool": "0.3.6", "project": "serial-line", "root": "/work/serial-line",
"units": [
{"path": "transport", "name": "transport", "parent": ".", "children": ["transport/framing", "transport/serial"],
"responsibility": "Sends and receives frames over the serial line", "kind": "adapter",
"status": "planned", "owner": null, "readme": "transport/README.md",
"secret": {"what": "how frames travel over the line", "decision": "transport/docs/decisions/0001-the-line-is-one-unit.md"},
"not": [{"text": "frame layout", "need": "frames"}, {"text": "retries after a failed write", "need": null}],
"capabilities": [
{"name": "transport", "summary": "...", "kind": null, "contract": "docs/references/transport.md", "parts": null,
"contract-parts": [{"name": "send", "kind": "command"}, {"name": "receive", "kind": "query"}], "fake": "described",
"final-users": [{"unit": "session", "need": "transport", "config": null}], "x": {}}],
"needs": [
{"name": "uart", "summary": "...", "kind": null, "contract": "docs/references/uart.md", "parts": null,
"contract-parts": [{"name": "write", "kind": "command"}], "fake": "described", "optional": false, "platform": false,
"final-providers": [{"outside": true, "root-need": "uart", "config": null}], "x": {}}],
"wiring": [
{"key": "transport", "value": "serial.transport", "alternative": null, "form": "implemented-by",
"user": null, "need": null, "provider": "transport/serial", "capability": "transport", "to-need": null},
{"key": "serial.uart", "value": "uart", "alternative": null, "form": "passed-up",
"user": "transport/serial", "need": "uart", "provider": null, "capability": null, "to-need": "uart"}],
"scenarios": [
{"name": "reassemble", "summary": "Two frames arrive in one read", "source": "proto ยง4.3",
"expect": "receive returns the first payload", "hops": [
{"caller": "transport", "target": "serial", "part": "receive", "caller-unit": null, "target-unit": "transport/serial",
"contract": "docs/references/transport.md", "via": [{"unit": "transport", "key": "transport"}]}],
"x": {}}],
"decisions": [{"path": "transport/docs/decisions/0001-the-line-is-one-unit.md", "title": "The line is one unit",
"status": "proposed", "date": "2026-10-27", "derived-from": null, "answer": null}],
"documents": {"tutorials": [], "references": ["transport/docs/references/framing.md"], "explanation": []},
"x": {}}],
"root-needs": [{"name": "uart", "platform": false, "reached-by": ["transport/serial"]},
{"name": "clock", "platform": true, "reached-by": ["session"]}]}
unitsholds every unit, the root first, then by path.parentisnullfor the root.formof a wiring row isconnection(a need of a child met by a sibling),passed-up(toto-needof this unit),met-inside(a need of this unit,useris this unit) orimplemented-by(capabilityis the own capability named bykey). One row per alternative;alternativeis its name ornull.final-usersandfinal-providersare theused-byandprovided-byofshow, without status.contract-partslists the parts the contract defines, with their kind, or isnullwhen the entry names no contract.fakeisrealwhen the contract's## Fakesection saysthe real implementation,describedwhen it says anything else, andnullwithout the section or the contract.- A hop's
callerandtargetare the names as written;caller-unitandtarget-unitare the paths of the units they name, ornullfor a capability or need of the holding unit.vialists the wiring lines the hop uses, from the top down, as the unit that holds the line and the key of the line; it is empty for a hop between a capability and a need of a unit without children.contractis the document in whichpartwas looked up. An unresolved hop hasunresolved, the hop as written, anderror. answerof a decision isnullwhen it has no## Answersection, else{"empty": true}or{"empty": false}: an open question of a process is a decision whose## Answeris empty.reached-bynames the units whose own code finally uses a need of the root.
Commands that write#
init: {"ok": true, "root", "written": [paths], "notes": [text], "units": 1}; written holds the root manifest, if init wrote it, and the generated files. generate and skills sync: {"ok", "written", "removed", "refused"}. explore: {"ok": true, "written": "/abs/explorer.html", "opened": true}; opened is false with --no-open or when no browser could be started. stamp names every stamp it wrote; action is added or refreshed:
{"ok": true, "stamped": 1, "items": [
{"file": "web/docs/explanation/context.md", "line": 12, "cites": "db/README.md",
"title": "db: storage rules", "action": "refreshed"}]}
This page as plain Markdown, and its source on GitHub.