> For the complete documentation index, see [llms.txt](https://ascribed-dev.com/docs/llms.txt). This documentation describes Ascribe 0.4.4. Anything marked in main, not yet released isn't in that release yet.

# `ascribe check`, `build`, `preview`, and `fmt`

> Reference · Check a project's sources, build its outputs, preview its pages, and format its Markdown: options, output, and exit codes.

The commands that check, build, preview, and format a project. The options every command accepts are in the [command reference](https://ascribed-dev.com/docs/command.md#options-every-command-accepts).

## `ascribe check`

Checks every source file for problems, without building anything: every file-level diagnostic, then the page-level ones for **every build** in `ascribe.toml`. The editor and `ascribe build` run the same checks, so they report the same diagnostics.

- `[PATHS]...`: Files and directories to report on, relative to the current directory. The whole project is still checked, since links, includes, and ids need it, and only the problems that count for these paths are shown: those in a file under one of them, or with a related place in one (a problem a [fragment](https://ascribed-dev.com/docs/glossary.md#fragment) causes on the page that includes it). Such a problem in a fragment is shown once, at its first include. Without `--config`, the project is the nearest `ascribe.toml` at or above the first path, and every path must be in it. By default, the whole project.
- `--stdin`: Check standard input as the text of the file `--path` names. The text is laid over the project on disk, which isn't changed; the file doesn't have to exist. Only the problems that count for it are shown.
- `--path <PATH>`: The file standard input is checked as, relative to the current directory. Only with `--stdin`.
- `--build <NAME>`: Check only this build. Repeat it for several. By default, every build in `ascribe.toml`. An unknown build name is a usage error, and the message lists the builds.
- `--editor-build`: Run the page-level checks of the editor's build only, as the editor does as you type. The editor's build is `[editor] build` in `ascribe.toml`, or the first build. With paths that name files, only those files and the pages that include them are checked, which is quick: for a check after every edit. A full `ascribe check` still covers every build.
- `--code <CODE>`: Show only the problems with this code (`ASC036`) or name (`link-target-missing`), as `ascribe explain` takes it. Repeat it for several. The whole project is still checked, and the rest is left out of the report, as with paths: the counts, the exit code, a prompt, and the fixes `--fix` applies are about what's left. An unknown code is a usage error, and the message names the closest.
- `--summary`: Show how many problems each code and each file has, most first, instead of listing them, then the command that lists the first code's problems.
- `--format <FORMAT>`: How to show the results.
  - `text` (the default): Problems with source snippets, for people.
  - `concise`: One line per problem, `file:line: [code] message`, grouped by file: for an agent. At most 50, or as many as `--limit` says, then how many more and the command that lists them all.
  - `json`: One JSON document, for tools.
  - `github`: A GitHub Actions workflow command per problem, which shows it on the pull request's lines, with the path from the repository's root, then the summary line: for CI.
  - `prompt`: A prompt for an agent that fixes the problems: about the file, when the paths name one file, else about the project or the paths. Nothing when there are no problems.
- `--limit <N>`: List at most this many problems with `--format concise`, or `all` of them. By default, 50. When some are left out, the list ends with how many, and the command that lists them all. Only with `--format concise`, and not with `--summary`, which lists none.
- `--exit-code[=<LEVEL>]`: Exit with 1 on problems at this level or above: `error`, the default, or `warning`, for CI. Errors fail the command without it. Advice never fails it.
  - `error`: An error.
  - `warning`: A warning or an error.
- `--deny-warnings`: Make warnings fail the command too: `--exit-code=warning`.
- `--base <REV>`: The revision whose addresses the check compares the project's with, when it has `ascribe.addresses`: anything git accepts (a branch, a tag, a commit). An address the base published that no build publishes now, and that no entry of `ascribe.addresses` accounts for, is `address-lost`. By default, the repository's default branch, the first of `origin/HEAD`, `origin/main`, `origin/master`, `main`, and `master` that exists, from its merge base with `HEAD`, as `ascribe diff` chooses it. A base that can't be read is `addresses-base-unreadable`, and the rest of the check is the same. Only when the check covers the whole project: with paths, `--stdin`, or `--editor-build`, the base isn't read. A project without `ascribe.addresses` reads the base's only when this names the base: `addresses-file-deleted` when it has one.
- `--base-exact`: Compare the addresses with the base revision itself instead of its merge base with `HEAD`: in CI on a push, the commit before it.
- `--fix`: Apply the fixes marked safe, then report what's left. A fix is safe when applying it can't change what the page says and leaves nothing to decide, such as linking to a page's file instead of its route. Only the fixes of the diagnostics reported are applied, so with paths, those that count for them. Unsafe fixes are never applied. The report, and the exit code, are the check after the fixes.
- `--dry-run`: Say what it would change, and change nothing.
- `--force`: Change files even when git couldn't put them back as they are: ones with changes not committed, ones git doesn't track, or ones in no repository.
- `--vale`: Check the prose with Vale too, as `[checks.vale]` in `ascribe.toml` sets it up. Vale runs once, on the files reported on. Its alerts are `prose` diagnostics, at their place in the source. A Vale that can't be run is one `prose-not-checked` advice, and the rest of the check is the same. `[checks.vale] in-check = true` checks the prose without this option. Without `[checks.vale]`, it's a usage error.
- `--no-baseline`: Report the advice `ascribe.baseline` holds back too, without reading the file. By default, a problem the [baseline](https://ascribed-dev.com/docs/glossary.md#baseline) lists isn't reported while it's advice, and is counted instead, and an entry that matches nothing is reported, as `baseline-entry-unmatched`. A baseline that can't be read stops the check, unless this is given.

How diagnostics are reported:

- **Each problem is reported once**, however many builds it appears in. One that isn't in every build names the builds it's in, at the end of its message (`only in build cloud`).
- **A problem in included content is reported at the include**, with its place in the fragment as related information.
- **Content that no build publishes is checked too**, so a problem in an arm no build selects isn't missed. Its diagnostics say "in content that no build publishes". Not with `--build`, which is about one build.

Every diagnostic, with its fix, is in the [diagnostics reference](https://ascribed-dev.com/docs/checks/diagnostics.md).

### Checking as of another day

Two checks depend on the day they run: `recheck-overdue` reports a page whose [recheck date](https://ascribed-dev.com/docs/glossary.md#recheck-date) has passed, and `release-line-ended` a live [release line](https://ascribed-dev.com/docs/glossary.md#release-line) whose `ends` date has passed, each as of today's date in UTC. Set `ASCRIBE_TODAY=YYYY-MM-DD` to check as of another day, such as in a test that mustn't change with the calendar. Every command that checks, the editor and the hook's check server included, reads it.

### Checking some files

`ascribe check docs/guides/install.md` reports on that file alone, from a repository's root as well as from inside the project. A fragment's problem is reported at every page that includes it, so checking the fragment shows it once, and its `repeats` says at how many other includes it appears too. The problems shown are the whole check's, in the same order.

### Checking text before it's saved

`--stdin` checks text that isn't on disk yet, such as an editor's unsaved buffer or an agent's draft. The file `--path` names has to be where a source file could be: a `.md` file in the content root. A link from another page that the new text breaks isn't shown, since it doesn't count for that file.

```sh
npx ascribe check --stdin --path docs/guides/new.md < draft.md
```

### Checking the prose

`--vale` reports Vale's `suggestion`, `warning`, and `error` alerts as advice, warnings, and errors, up to [`[checks.vale] max-level`](https://ascribed-dev.com/docs/content-model/checks.md#checksvale). Vale is given the prose alone, and a Vale that takes more than ten minutes is given up, as `prose-not-checked`. See [Prose, through Vale](https://ascribed-dev.com/docs/checks/vale.md).

### A quick check after each edit

`--editor-build` leaves out what the editor doesn't report: the pass over content no build publishes, the image checks (`image-unused` and `image-large`), and, with paths that name files, the [content checks](https://ascribed-dev.com/docs/checks/diagnostics/content-checks.md) across the project. It's quick enough to run after every edit; the timings are in [`tests/corpora/RESULTS.md`](https://github.com/ascribed-dev/ascribe/blob/main/tests/corpora/RESULTS.md#one-file). It can't be combined with `--build`. The summary line, and `builds_checked` in the JSON, name the build, so a clean result says what it covers.

### Output for agents

`--format concise` writes a line for each problem, grouped by file in file order, and by line within a file, and ends with the summary line. A problem with a fix ends its line with `→` and what the fix does, the help the full output gives:

```text
docs/keys.md:8: [ASC041] this looks like the published route of `install-agent.md`; link to the file instead: `/install-agent.md` → Link to the page's file instead of its route
checked 8 files for every build, reported on 1 file in the paths, 1 with problems: 0 errors, 1 warning, 0 advice
```

Tested with ascribe 0.4.4 on Linux.

It lists at most 50 problems. When there are more, the line before the summary says how many it left out, and the command that lists them all: the same check with `--limit all` (`and 11 more: ascribe check docs --code ASC212 --format concise --limit all`). `--limit <n>` sets how many it lists, and `--limit all` lists every one. `--limit` is only for `--format concise`; with another format, or with `--summary`, it's a usage error.

`--format prompt` writes the prompt [Prompt your agent](https://ascribed-dev.com/docs/agents/prompts.md) describes. It lists at most 20 problems, or 20 files, then names the command that lists the rest. With `--stdin`, it says the file has unsaved changes.

`--summary` works in any format. The text lists at most 20 files, then the command whose JSON lists them all. On a project with hundreds of warnings, it shows which rule or file to work through first.

### Output for GitHub Actions

Available: Ascribe (in main, not yet released, 0.4.5)

`--format github` writes a GitHub Actions [workflow command](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands) for each problem, in the same order as `--format concise`, and ends with the summary line. The workflow run shows each one on the pull request's line: an error as an error, a warning as a warning, and advice as a notice. The path is from the repository's root, where GitHub looks for it, even when the project is in a folder of the repository, and the title is the diagnostic's name and code. The message is one line, without the help or the source snippet:

```text
::warning file=handbook/docs/keys.md,line=8,col=15,endLine=8,endColumn=30,title=link-route (ASC041)::this looks like the published route of `install-agent.md`; link to the file instead: `/install-agent.md`
checked 8 files for every build, 1 with problems: 0 errors, 1 warning, 0 advice
```

Tested with ascribe 0.4.4 on Linux.

A problem that spans lines has its first and last line, and no columns, which GitHub takes only on one line. It needs no permissions and no upload step, and [Checks in CI](https://ascribed-dev.com/docs/checks/ci.md) uses it. Without `--format`, the output is the text one, in GitHub Actions too.

### Checking one rule at a time

`--code` lets an agent, or a person, fix one rule across the project instead of working file by file. The summary line names the codes (``3 with problems for code `ASC037` ``), and the commands a report names keep them: the concise list's `and N more:`, and the JSON's `next_command`.

```sh
npx ascribe check --code link-id-missing --format concise
```

### Applying the safe fixes

An `unsafe` fix, such as the nearest spelling of a misspelled name, is never applied by `--fix`: it stays in the report for you to decide. `ascribe fmt` is the other command that rewrites what it can.

```sh
npx ascribe check --fix
```

The output starts with a line for each file changed (`fixed 2 problems in docs/guides/install.md`). It changes only files git could put back, as every [command that writes](https://ascribed-dev.com/docs/command.md#commands-that-write) does: a page with changes not committed is refused, and named, unless you pass `--force`. With `--dry-run`, it says what it would fix (`would fix 2 problems in docs/guides/install.md`), changes nothing, and reports the check of the files as they are. Two fixes that would edit the same text are one too many: the first is applied, and the other's problem is reported again, for the next run. `--fix` can't be combined with `--stdin`, whose text isn't on disk.

In VS Code, **Source Action… → Apply the safe fixes in this page** applies a page's safe fixes the same way, to the text in the editor, saved or not, for the problems the editor shows, and can run each time you save. See [Apply the safe fixes](https://ascribed-dev.com/docs/editor/problems.md#apply-the-safe-fixes).

### Failing on warnings

Available: Ascribe (in main, not yet released, 0.4.5)

`--exit-code=<level>` says the least problem that fails the command: `error`, which `--exit-code` alone means and the command does without it, or `warning`, for CI. `--deny-warnings` is the same as `--exit-code=warning`, and when both are given, the last one counts. The level follows `=`, so `ascribe check --exit-code docs/guides` checks the folder. Advice never fails the command, so `advice` isn't a level.

```console
$ ascribe check --exit-code=warning
```

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | No errors. Warnings don't fail the command unless you pass `--exit-code=warning` or `--deny-warnings`, and advice never does. |
| `1` | There are errors, or warnings with `--exit-code=warning` or `--deny-warnings`. |
| `2` | The project couldn't be checked: a usage error, a `--code` no diagnostic has, no `ascribe.toml`, a [content model](https://ascribed-dev.com/docs/glossary.md#content-model) with errors (they're shown, and nothing else is checked), a path that doesn't exist or isn't in the project, an `ascribe.baseline` with a line that doesn't parse (the error names it; `--no-baseline` checks without the file), or, with `--fix`, a file it fixed that can't be written. A source file that can't be read, or isn't UTF-8, is a `source-unreadable` error in the list instead, and the rest of the project is still checked. |

### Text output

Each diagnostic shows its code, message, and source, and its next step as `Help:`: the title of its first fix, or, with none, the fix advice the JSON gives as `help`. Help that's the same for every diagnostic with a code is printed in full once; later diagnostics with that code point back to it, as `Help: as for ASC252 above (ascribe explain ASC252)`. The output ends with a summary in one form: the files checked, the builds (`for every build`, or with `--build`, `` for build `cloud` ``), with paths, how many files they name (`reported on 2 files in the paths`), how many files have a problem reported, and the counts of errors, warnings, and advice (`checked 12 files for every build, 1 with problems: 1 error, 0 warnings, 0 advice`). The JSON's `files_reported` counts the files reported on, all of them when no paths are given:

```text
[ASC041] Warning: this looks like the published route of `install-agent.md`; link to the file instead: `/install-agent.md`
   ╭─[ docs/keys.md:8:15 ]
   │
 8 │ See [Install](/install-agent/).
   │               ───────┬───────
   │                      ╰───────── link-route
   │
   │ Help: Link to the page's file instead of its route
───╯
checked 8 files for every build, 1 with problems: 0 errors, 1 warning, 0 advice
```

Tested with ascribe 0.4.4 on Linux.

A project with no pages yet, and nothing to report, says so in place of the summary, with the content root: `No pages in docs/ yet.` It exits with 0. The JSON is the same as for any check, with `files_checked` 0.

Diagnostics go to standard output. A failure that stops the command (exit code 2) goes to standard error.

Advice, a diagnostic below a warning, is listed after the errors and warnings, in every format, and the summary counts it apart: `0 errors, 2 warnings, 5 advice`. It never fails the command, even with `--deny-warnings`. A project sets how loudly a check about its content's quality speaks in [`[checks]`](https://ascribed-dev.com/docs/content-model/checks.md#22-checks).

A problem [acknowledged as intended](https://ascribed-dev.com/docs/writing/directives.md#intended) isn't listed, and doesn't fail the command; the summary counts it when there is one: `0 errors, 2 warnings, 0 advice, 1 acknowledged`. `ascribe build` counts it the same way.

Advice the project's [baseline](https://ascribed-dev.com/docs/checks/baseline.md), `ascribe.baseline`, holds back isn't listed either, and the summary counts it: `0 errors, 0 warnings, 2 advice, 3412 in the baseline`, and the JSON's `summary` as `baselined`. An entry of the baseline that matches nothing is reported, as `baseline-entry-unmatched`, when the check covers the whole project and every build. `--no-baseline` lists everything, without reading the file.

### Quieting a problem

There are four ways to quiet a problem, each with its own rules:

| Way | What it quiets | For how long | Reason | Read by |
| --- | --- | --- | --- | --- |
| [`@intended`](https://ascribed-dev.com/docs/writing/directives.md#intended), the `intended` frontmatter key, or [`[[intended]]`](https://ascribed-dev.com/docs/content-model/checks.md#intended-acknowledgements) | One problem of a review check, at its place | For good | Required | Everything |
| [`[checks]`](https://ascribed-dev.com/docs/content-model/checks.md#22-checks) `<check> = "off"` | A content check, in the whole project | Until it's turned back on | None | Everything |
| `[checks]` `<check> = "advice"` or `"warning"` | Lowers a content check, in the whole project | Until it's set again | None | Everything |
| [`ascribe.baseline`](https://ascribed-dev.com/docs/checks/baseline.md) | Many advice, each by its problem | Until it's fixed and pruned | None | `ascribe check`, the editor, the hooks, and the MCP server; not `ascribe build` or `ascribe report`, for now |

- **An error about whether the project is valid is never quieted,** by any of the four: a broken link, an unclosed container, or an unknown attribute makes a page wrong, and is fixed instead. These are the specification's diagnostics ([SPEC §8.2](https://github.com/ascribed-dev/ascribe/blob/main/SPEC.md#82-diagnostics)), and none is configurable. The four are for the [content checks](https://ascribed-dev.com/docs/checks/diagnostics.md#setting-a-checks-level), Ascribe's own checks of the content's quality.
- **A content check raised to an error isn't the same.** `[checks]` can raise a content check, such as `page-orphan = "error"`: its problems then fail `ascribe check` and stop `ascribe build`, as an error does, but the page is still valid by the specification. A review check raised this way can still be acknowledged with `@intended`, at whatever level the project gives it.
- **The baseline holds advice only.** A check raised to a warning or an error is reported, whatever the baseline lists. To put a check's warnings off, lower it to `"advice"`, then run `ascribe baseline write` again.

Which to use: acknowledge a problem the team has looked at and means to keep, with why, beside it. Turn off or lower a check the project doesn't need, which a reviewer sees in `ascribe.toml`. Put a backlog of advice in the baseline, to fix at the team's own pace, so `ascribe check` reports only what's new.

### JSON output

`--format json` writes one JSON document to standard output, whatever the outcome, so a tool can always parse it. Its [schema](https://ascribed-dev.com/docs/contracts/json-reports.md#ascribe-check-and-ascribe-build) has every field, under the rules the [JSON report contract](https://ascribed-dev.com/docs/contracts/json-reports.md) sets for every document, such as ignoring fields you don't know:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "error": null,
  "files_checked": 8,
  "files_reported": 8,
  "builds_checked": [
    "site",
    "site@3.4",
    "cloud"
  ],
  "diagnostics": [
    {
      "code": "ASC041",
      "slug": "link-route",
      "severity": "warning",
      "next": "choose",
      "message": "this looks like the published route of `install-agent.md`; link to the file instead: `/install-agent.md`",
      "file": "docs/keys.md",
      "range": {
        "start": {
          "line": 8,
          "column": 15,
          "offset": 132
        },
        "end": {
          "line": 8,
          "column": 30,
          "offset": 147
        }
      },
      "related": [],
      "fixes": [
        {
          "title": "Link to the page's file instead of its route",
          "file": "docs/keys.md",
          "edits": [
            {
              "range": {
                "start": {
                  "line": 8,
                  "column": 15,
                  "offset": 132
                },
                "end": {
                  "line": 8,
                  "column": 30,
                  "offset": 147
                }
              },
              "new_text": "/install-agent.md"
            }
          ],
          "applicability": "safe"
        }
      ],
      "builds": [],
      "unpublished": false,
      "help": "Link to the page's file, not its URL: Ascribe writes each output's URLs. The editor's quick fix makes the change.",
      "docs": "https://ascribed-dev.com/go/ASC041/",
      "repeats": 0
    }
  ],
  "truncated": false,
  "shown": 1,
  "total": 1,
  "next_command": null,
  "summary": {
    "errors": 0,
    "warnings": 1,
    "advice": 0
  }
}
```

Tested with ascribe 0.4.4 on Linux.

## `ascribe build`

Checks the project, then writes each build's outputs. The checks run first, exactly as `ascribe check` runs them. **If any check finds an error, nothing is written, for any build.**

- `--build <NAME>`: Build only this build. Repeat it for several. By default, every build in `ascribe.toml`.
- `--emit <OUTPUTS>`: Which outputs to write, separated by commas. All of them by default.
  - `site`: Markdown plus web components, for an Astro site.
  - `plain`: Fully resolved CommonMark with no HTML.
  - `json`: The resolved tree as JSON.
- `--format <FORMAT>`: How to show the checks' results, as for `ascribe check`.
  - `text` (the default): Diagnostics with source snippets, for people.
  - `json`: One JSON document, for tools.
- `--anchors`: Mark each block of the site output with the source file and lines it came from, for review. Each Markdown block gets an `<!--ascribe-anchor …-->` comment before it, and each element Ascribe writes gets `data-ascribe-source` (with `data-ascribe-via` for a block from a fragment). The site output's manifest records `"anchors": true`. Without it, the output has no anchors. The other outputs are the same either way.
- `--no-baseline`: Report the advice `ascribe.baseline` holds back too, without reading the file. By default, the checks hold back what the baseline lists while it's advice, and count it, as `ascribe check` does. A baseline that can't be read stops the build, unless this is given.

The Astro integration turns the source anchors that `--anchors` writes into attributes on every block's element ([site-render contract](https://ascribed-dev.com/docs/contracts/site-render.md#7-source-anchors)).

The build prints each error in full, since errors stop it. Warnings and advice are only counted, in the check's summary line, with the command that lists each: `checked 8 files in every build: 0 errors, 1 warning, 3 advice: ascribe check for each`, or `checked 8 files in every build: no problems`. The advice the [baseline](https://ascribed-dev.com/docs/checks/baseline.md) holds back isn't counted with the rest, but after it, as `ascribe check` counts it: `no problems, 3412 in the baseline`. `--format json` prints exactly what `ascribe check --format json` does.

Then it prints one line for each build it wrote, with the outputs it wrote, on standard error, with its warnings:

```text
built site: 6 pages, 2 assets (site, plain, json)
built cloud: 3 pages, 2 assets (site, plain, json)
```

### Outputs

Each build writes each output to `<output-dir>/<build>/<output>/`, `.ascribe/build/` being the default output directory, with a manifest beside it (`<output>.manifest.json`) listing every file it wrote. Every output is self-contained: images and other files that pages use are copied into it.

| Output | What it is | For |
| --- | --- | --- |
| `site` | Markdown with web components (`<ascribe-note>`, `<ascribe-tabs>`, and the rest), heading ids and image attributes as markers the Astro integration applies, and the generated Zod schema in `_ascribe/schema.ts` | An Astro site, through `@ascribed/astro` ([Astro](https://ascribed-dev.com/docs/astro.md)) |
| `plain` | Fully resolved CommonMark with no HTML: notes as quotes, [variants](https://ascribed-dev.com/docs/glossary.md#variant) as labeled sections, [availability](https://ascribed-dev.com/docs/glossary.md#availability) as text, and links as absolute URLs | Search indexes, LLMs, and export |
| `json` | The resolved tree of each page | Your own tools |

Without `[consumer] site` in `ascribe.toml`, plain-Markdown links are root-relative instead of absolute, and the build says so.

A rebuild replaces its previous output. It removes only files its own manifest listed, and never overwrites a file it didn't write: a file in the way is an error. Two builds can't write to one output directory at once.

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Built |
| `1` | The checks found errors; nothing was written |
| `2` | The build couldn't run: a usage error, no `ascribe.toml`, a content model with errors, an `ascribe.baseline` with a line that doesn't parse (`--no-baseline` builds without the file), another build writing to the output directory, a file in the output directory that Ascribe didn't write and would have to overwrite, or two pages of the site output with the same route |

## `ascribe preview`

Available: Ascribe (in main, not yet released, 0.4.5)

Shows a build's pages as readers see them, with nothing but the `ascribe` command: no Node, no editor, and no Astro. It renders them into `.ascribe/preview/`, serves the folder on this computer, and renders it again as you edit.

It checks the build as `ascribe check --build` does, and prints the problems as that prints them. Then it renders every page into `.ascribe/preview/` with the renderer [`ascribe versions export`](https://ascribed-dev.com/docs/versions/release.md#ascribe-versions-export) uses: each page is one HTML file with the element library's styles, a sidebar of the build's navigation, the page's contents, the previous and next pages, and a search box. A page with errors is rendered as far as it goes: a link that leads nowhere is written as its text, and an image that isn't there as its description.

Then it serves the folder at `http://127.0.0.1:4880/`, and prints the address to open:

```text
checked 1 file for every build, 0 with problems: 0 errors, 0 warnings, 0 advice
rendered build `site`: 1 page, 0 assets, in .ascribe/preview
previewing build `site` at http://127.0.0.1:4880/
it renders again as the project's files change; Ctrl-C stops it
```

When a page, a fragment, `ascribe.toml`, or a file a snippet takes code from changes, it checks and renders the build again, and prints what it found; reload the page in the browser to see it. It runs until you stop it.

- `--build <NAME>`: The build to preview. Needed when `ascribe.toml` has more than one build. Of a versioned build, its current line.
- `--port <PORT>`: The port to serve the preview on, at 127.0.0.1. By default 4880, or a free port the system picks when 4880 is taken.

The preview isn't the published site, which looks the way its Astro project styles it ([Astro](https://ascribed-dev.com/docs/astro.md)), and isn't the editor's [page preview](https://ascribed-dev.com/docs/glossary.md#page-preview) or [site preview](https://ascribed-dev.com/docs/glossary.md#site-preview), two of the four [previews](https://ascribed-dev.com/docs/glossary.md#preview). Only this computer can open its address: it listens at `127.0.0.1` alone, answers only a request addressed to `127.0.0.1` or `localhost` with its port, so a web page elsewhere can't read it, and connects to nothing. `.ascribe/` is the folder `.gitignore` already leaves out.

| Code | Meaning |
| --- | --- |
| `2` | It couldn't start: no `ascribe.toml`, a content model with errors, a build that isn't the model's, several builds and no `--build`, a preview that can't be written, or a port that can't be listened on |

Once it's serving, problems in the pages never stop it, and a content model that stops loading leaves the last preview served until it loads again.

## `ascribe fmt`

Rewrites Ascribe constructs into canonical form: the spacing of directive lines and attribute blocks, attribute order, quoting, and blank lines between directives and their blocks. It changes nothing else, never how a page renders, and leaves alone a construct that has an error. See [Canonical form](https://ascribed-dev.com/docs/writing/syntax.md#canonical-form).

It lists each file it changed. It changes only files git could put back, as every [command that writes](https://ascribed-dev.com/docs/command.md#commands-that-write) does: a page with changes not committed is refused, and named, unless you pass `--force`.

With `--dry-run`, it changes nothing and lists each change it would make: the file and line, then the lines as they are, marked `-`, and as they'd be, marked `+`:

```text
docs/install.md:5
- @note{ type = tip }:Run it from the project root.
+ @note {type=tip}: Run it from the project root.
```

`--check` changes nothing too, writes the changes as a unified diff, which `git apply` applies, and exits with `1` when a file would change: for CI. `--diff` is its old spelling, kept for a release.

- `--check`: Change nothing, and show what would change as a unified diff, which `git apply` applies. Exits with 1 if any file would change. Use it in CI.
- `--dry-run`: Say what it would change, and change nothing.
- `--force`: Change files even when git couldn't put them back as they are: ones with changes not committed, ones git doesn't track, or ones in no repository.
- `[PATHS]...`: Files and directories to format: the `.md` files among them. By default, every `.md` file under the content root. Directories whose names start with `.`, `node_modules`, and directories inside the searched ones that hold an `ascribe.toml` other than the project's own (another project, formatted under its own model) are skipped. So is a file in the content root reached through a symbolic link that leads to a file that isn't a source file of the content root, and it's reported.
- `--format <FORMAT>`: How to show what changed. `json` lists each file with the edits that format it; with `--check`, that's the edits without writing them. `github`, only with `--check`, is for GitHub Actions.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.
  - `github`: A GitHub Actions workflow command per file that would change, which shows it on the pull request at its first change, with the path from the repository's root: for CI.

With `--format json` and `--check`, it writes the edits it would make and no file: what a tool that applies edits itself needs, such as the MCP server's `ascribe_fmt`. An edit has the shape of a fix's edit in [`ascribe check`'s JSON](https://ascribed-dev.com/docs/checks/check.md#json-output), and the [schema](https://ascribed-dev.com/docs/contracts/json-reports.md#ascribe-fmt) has every field.

With `--format github` and `--check`, it writes a GitHub Actions workflow command for each file that would change, at the line of its first change, with the path from the repository's root, as [`ascribe check`'s](https://ascribed-dev.com/docs/checks/check.md#output-for-github-actions) are. `--format github` without `--check` is a usage error.

```text
::error file=handbook/docs/keys.md,line=28,title=ascribe fmt::`ascribe fmt` would change this file in 1 place; run `ascribe fmt` to format it
```

Tested with ascribe 0.4.4 on Linux.

A symbolic link it leaves alone is reported on standard error, as `check` reports it ([`source-unreadable`](https://ascribed-dev.com/docs/checks/diagnostics/sources.md#asc123-source-unreadable)), and the other files are still formatted.

| Code | Meaning |
| --- | --- |
| `0` | Every file was formatted, or, with `--check`, none needed it; with `--dry-run`, whatever would change |
| `1` | With `--check`: a file would change |
| `2` | A problem: no `ascribe.toml`, a content model with errors, a path or file that can't be read, a symbolic link that leads out of the content root, or a file git couldn't put back, without `--force` |
