unifold 0.3.6

The reference tool#

unifold is one Python file, unifold.py. It checks a project against the standard, generates the diagrams, and answers questions about the map. Its findings: rules.md. Its JSON: tool-json.md.

Running it#

It needs Python 3.9 or newer and PyYAML, and is published on PyPI as unifold:

uvx unifold check                                      # run it without installing
uv tool install unifold                                # or install the command (pipx install unifold works too)
uv run docs/skills/unifold/scripts/unifold.py check    # or run the copy that init --agents installs

The examples below write unifold. PATH is any folder inside the project (default: the current folder).

Commands#

Command Effect
init [DIR] [--agents] Starts a project in DIR: writes the root manifest and a README.md from the template where there is none, then the generated files. Changes no existing manifest.
check [PATH] Reports findings. Changes nothing, unless --fix is given.
generate [PATH] Writes diagrams, decision indexes and the AGENTS.md of every unit; writes the skill stubs where their folders exist.
show [PATH] One unit: responsibility, kind, secret, not, capabilities, needs, who finally provides and uses them, with status, and the scenarios that pass through it.
capability PATH NAME Both ends of every connection of one capability or need, with contracts and README sections.
impact PATH [CAPABILITY] Every unit that depends on the unit or capability, transitively, and the context items that cite its files.
tree [PATH] [--depth N] The unit tree with status, kind, capabilities and needs.
model [PATH] The resolved model of the project as JSON, with a stable schema (tool-json.md): every unit, connection, final provider, scenario and decision. For tools that build on the map.
stamp [PATH] Writes or refreshes the stamps of the context items at or below PATH, and prints each with file, line and the source it cites.
cited-by FILE Context items and links that cite FILE, with line and Says text. FILE may be deleted already.
skills [sync] [PATH] Lists all skills; sync writes their stubs into .agents/skills and .claude/skills, and the other generated files as generate does (agents.md).
explore [PATH] [-o FILE\|DIR] [--no-open] Writes the explorer, one HTML page with the map and every document, and opens it in a browser.
rules [ID] Prints the rule table, or one rule with its fix.

Every command accepts --json. With --json, a failure also prints JSON (tool-json.md).

Exit code Meaning
0 No error (with --strict: no warning either)
1 Findings; or generate refused to overwrite a hand-written file
2 Cannot run: wrong usage, no project, root manifest unusable for this command, file not writable
3 Internal error. UNIFOLD_DEBUG=1 shows the traceback.

check#

Option Effect
--fix First writes what generate would write, then checks. It writes no stamp.
--strict Warnings fail too.
--quiet, -q No info findings, no fix hints.
--format github Prints workflow commands; GitHub shows them on the changed lines.
--rule ID Reports only this rule. Repeatable.
--ignore ID Drops the findings of this rule from the output and the exit code, whatever their severity. Repeatable. The summary counts them: [C2×3 ignored]. For a process that fixes a finding at a later stage.

One line per finding, then a summary:

error W2 cloud/unifold.yaml:14: need 'alerts.push' has no provider
    fix: Add `alerts.push: <provider>` to the wiring of this manifest, or declare a need `push` here and pass it up (`alerts.push: push`).
12 unit(s): 1 error(s), 0 warning(s), 0 info  [W2×1, C2×3 ignored]

Generated files are built in memory and compared, so a stale diagram is an error; check --fix repairs it. The tool reads version 0.3 only: there is no migration command, because a tool that rewrites YAML drops its comments; a 0.2 project is rewritten by hand, and S1 lists what changed. --fix never adds or refreshes a stamp: only unifold stamp does, after you have compared Says with the source.

init#

unifold init starts a project. If DIR has no unifold.yaml, it writes the root manifest: the version of the standard, name from the folder name, and responsibility: TODO, which check reports until a person has replaced it. If DIR has no README.md, it writes one from the template of the unifold skill, with placeholders in angle brackets to replace; check reports only its two example headings. Then it runs generate. A hand-written AGENTS.md is in the way of the generated one: on a terminal init asks whether to replace it, otherwise it keeps the file and says so. A project of one unit conforms at once: check reports warnings and no error.

init reads no code and writes no other manifest. The units of an existing repository are a judgement about its architecture; a build file knows packages, not units. A person makes that judgement, or an agent with the skill unifold-convert (first map); check then says what is missing or inconsistent.

The root AGENTS.md always carries the unifold block: about 15 lines, plus one row per unit when the project has at most 40 units below the root. init --agents also installs the skills unifold and unifold-convert into docs/skills/ (text, templates and this tool) and runs skills sync; the block then names the installed tool and links the skills. It prints a hint if a CLAUDE.md at the root does not mention AGENTS.md, which hides it from Claude Code.

Generated files#

generate writes the files of spec.md, section 7: the diagrams, one sequence diagram per scenario with the kind of each part in its table, and the index docs/decisions/index.md of every unit that has decisions. It removes generated files it no longer writes, and it never overwrites a file that lacks the generated marker: a hand-written AGENTS.md is reported until its content has moved to README.md and docs/ and the file is deleted. Links to files that do not exist are left out.

The explorer#

unifold explore writes the explorer, one HTML page with the tree, all diagrams and tables and every document, and opens it in the browser of the system. The explorer is a view of the map, not a file of it: without -o the page goes to explorer.html under the temporary folder of the system, outside the repository, and nothing is committed; -o FILE or -o DIR writes it where a project publishes it, for example into a site build, and --no-open leaves the browser closed, for a build or an agent. The page leaves out the skills unifold and unifold-convert that init --agents installs at the root. Every scenario has a page of its own, at #<holder>@scenario:<name> and linked from each unit it passes through: the summary, source and expect, the sequence diagram with a legend of how the kind of a part draws a step, the steps with the kind of their part, their contracts and wiring lines, and the lifelines. The page of a contract lists its parts with their kind and the scenarios and steps that use each; a part no scenario uses says so. A second tab, map, shows the architecture or component diagram of the deepest unit whose diagram holds every unit the steps name (a plain folder the units share belongs to the unit above it) with the path of the scenario on it: the boxes and connections its steps use are lit and carry the numbers of the steps, a row above the map lists the steps, and a dot runs them in the direction of the data while the current step is marked. The page of a unit shows the first paragraph of its README.md under its name and, above its tables, the section ## Why and the entries of not as a list under Out of scope; every box and frame of its diagrams shows the responsibility of its unit in small text, and a diagram wider than the page opens fitted to its width, with a button for the full size. Every Markdown file of a unit (the README.md in full, contracts with the parts of a folder contract, decisions, explanation, tutorials, skills, AGENTS.md) has a page of its own at #<unit>@doc:<path in the unit>, listed by kind on the page of the unit and beside the tree. The tool turns the Markdown into HTML itself, without a library: headings, lists with tasks, tables, code, quotes and Mermaid blocks, drawn like the map; it escapes all HTML of the source, shows front matter as fields, a decision with its status, and the fields of a part (Kind, Pre, ...) one to a line. A link between documents opens the target in the page, a link to a README.md the page of its unit (with an anchor other than #why the page of the README), a link to a generated diagram file its tab or scenario; a link to any other file goes to the file, and a link with a scheme other than http, https and mailto is shown as text. The page loads Mermaid from a CDN and says so if it cannot; the tables work without it. --mermaid FILE embeds a local mermaid.min.js (the page then works offline and grows by about 5 MB); --mermaid URL loads it elsewhere. The page follows the light or dark setting of the system; a button at the top switches and keeps the choice in the browser, under the same key as unifold.md.

Hooks and continuous integration#

GitHub Actions: after actions/checkout, add the step - uses: <owner>/unifold@<tag> (this repository; inputs path, strict, args). Findings appear as annotations on the pull request.

pre-commit: this repository provides the hooks unifold (check) and unifold-fix (rewrite generated files, then check):

repos:
  - repo: <URL of this repository>
    rev: <tag>
    hooks: [{id: unifold-fix}]

A coding agent should check when it stops, not after every edit: a unit that is half written is broken on purpose. Claude Code, in .claude/settings.json:

{"hooks": {"Stop": [{"hooks": [{"type": "command", "timeout": 60,
  "command": "cd \"$CLAUDE_PROJECT_DIR\" && uvx unifold check --fix --quiet >&2 || exit 2"}]}]}}

What the tool does not read#

Testing another implementation#

conformance.py holds the test cases as data: small projects with the findings that check --json must report, as rule, severity and file, and scenarios for init, stamps, --fix, model, --ignore and JSON on failure. uv run tests/conformance.py --tool "CMD" runs them against another tool; --write DIR writes the cases as files. An implementation conforms when all cases pass. Cases for generated files run without the reference generator, so a second generator is tested too.

This page as plain Markdown, and its source on GitHub.