> 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 versions`

> Reference · List a project's release lines, say what differs between two, release a line in one command, export one into a folder of HTML, and retire it from the source.

The commands for a project's [release lines](https://ascribed-dev.com/docs/versions/lines.md): what they are, what differs between two, the release that makes a planned line live, the export that keeps a copy of a line once it leaves, and the retirement that takes it out of the source. [Publish a site for each version](https://ascribed-dev.com/docs/versions.md) and [Export and retire a line](https://ascribed-dev.com/docs/versions/archive.md) are the tasks they're used in. The options every command accepts are in the [command reference](https://ascribed-dev.com/docs/command.md#options-every-command-accepts), and the JSON each writes with `--format json` is in [JSON reports: `ascribe versions`](https://ascribed-dev.com/docs/contracts/json-reports/versions.md).

## `ascribe versions`

Lists a project's [release lines](https://ascribed-dev.com/docs/glossary.md#release-line), says what differs between two, releases one, archives one, and retires one. Each reads the lines from `ascribe.toml`'s [`[releases]`](https://ascribed-dev.com/docs/content-model/variation.md#121-releasestarget), and a line is named as a mark names it: the target, then the version, `"self-managed 3.4"`.

### `ascribe versions list`

Shows each target's lines, newest first: whether each is current, live, planned, or archived, the output and base path of each live line on each build versioned by the target, with how many pages it publishes, the last day of support from `ends`, and an archived line's address and the day it ended. It reads the project and builds nothing. In `examples/quill`:

```text
self-managed, versioning `site`
  3.5  current   site at /, 6 pages
  3.4  live      site@3.4 at /3.4/, 5 pages
```

Tested with ascribe 0.4.4 on Linux.

- `--format <FORMAT>`: How to show the lines.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

With `--format json`, it writes the same in the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/versions.md#ascribe-versions-list) describes. It exits with `0`, and `2` when the project doesn't load.

**The JSON for this example**

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "targets": [
    {
      "target": "self-managed",
      "builds": [
        "site"
      ],
      "lines": [
        {
          "line": "3.5",
          "status": "current",
          "outputs": [
            {
              "build": "site",
              "base": "/",
              "pages": 6
            }
          ]
        },
        {
          "line": "3.4",
          "status": "live",
          "outputs": [
            {
              "build": "site@3.4",
              "base": "/3.4/",
              "pages": 5
            }
          ]
        }
      ]
    }
  ]
}
```

Tested with ascribe 0.4.4 on Linux.

### `ascribe versions changes`

Says what differs between two lines of a target, from the marks alone, with nothing built: the pages, sections, blocks, and table rows that are in the second line and not the first (added), in the first and not the second (removed), and in both with a different state, such as deprecated in one (changed). Each is listed with its file and line, the page, the heading it's under, and its first line. A page whose own mark adds it is one entry, not an entry for each part of it. A page added in place of one removed, which `replaces` links it with either way, is one entry too: it's listed as added, and names the page it's in place of, which isn't listed as removed. `ascribe versions changes "self-managed 3.4" 3.5` in `examples/quill`:

```text
In 3.5 and not 3.4:
  docs/configure.md:5: the page "Configure the agent", in place of "Configure the agent (3.4)" at docs/configure-legacy.md:5
  docs/keys.md:25: the section "Sign webhook events"
  docs/webhooks.md:5: the page "Webhooks"
```

Tested with ascribe 0.4.4 on Linux.

The second line is a version of the first's target, and either may come first. Each must be declared in the list, whether live, planned, or archived. A line holds what's marked for a version in it, not what's marked for a later one, so a line is compared as it was released, whichever is current.

- `FROM`: The line compared from, named as a mark names it: "self-managed 3.4".
- `TO`: The line compared to, of the same target: 3.5.
- `--format <FORMAT>`: How to show what differs.
  - `text` (the default): What was added, removed, and changed, for people.
  - `json`: One JSON document, for tools.
  - `markdown`: A first draft of release notes, with a heading for each page.

`--format markdown` writes a first draft of release notes: a heading for the line, and one for each page, with what's new, gone, and changed on it.

```markdown
## self-managed 3.5, since 3.4

### Configure the agent

In `docs/configure.md`.

- New: the page, in place of "Configure the agent (3.4)" in `docs/configure-legacy.md`

### API keys

In `docs/keys.md`.

- New: the section "Sign webhook events"

### Webhooks

In `docs/webhooks.md`.

- New: the page
```

Tested with ascribe 0.4.4 on Linux.

With `--format json`, it writes the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/versions.md#ascribe-versions-changes) describes:

**The JSON for this example**

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "target": "self-managed",
  "from": "3.4",
  "to": "3.5",
  "added": [
    {
      "file": "docs/configure.md",
      "line": 5,
      "kind": "page",
      "page": "Configure the agent",
      "text": "Configure the agent",
      "in_place_of": {
        "file": "docs/configure-legacy.md",
        "line": 5,
        "page": "Configure the agent (3.4)"
      }
    },
    {
      "file": "docs/keys.md",
      "line": 25,
      "kind": "section",
      "page": "API keys",
      "heading": "Sign webhook events",
      "text": "Sign webhook events"
    },
    {
      "file": "docs/webhooks.md",
      "line": 5,
      "kind": "page",
      "page": "Webhooks",
      "text": "Webhooks"
    }
  ],
  "removed": [],
  "changed": []
}
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | Compared, whether or not anything differs |
| `2` | It couldn't compare: the project doesn't load, or a line isn't named as a mark names one or isn't declared |

### `ascribe versions release`

Releases a line: rewrites each mark's [`planned`](https://ascribed-dev.com/docs/writing/directives.md#availability-specs) for its target to the line's version, makes the line live, and archives the old current line where no build is versioned by the target. [When a line ships](https://ascribed-dev.com/docs/versions.md#when-a-line-ships) has each step. It edits `ascribe.toml` in place, keeping its comments and order, and each mark where it's written. A feature's spec names its versions, so `planned` in one is left as it is. In a copy of `examples/quill` that plans 3.6, with a section marked `@available: self-managed planned`, `ascribe versions release "self-managed 3.6" --next 3.7 --dry-run` says what it would do, `--next` adding 3.7 to `planned` so that `planned` names it from then on. Its site is versioned, so 3.5 stays live, with an output of its own, `site@3.5`:

```text
self-managed 3.6 would be live, and current.
Would rewrite 1 mark from `self-managed planned` to `self-managed 3.6`, in 1 file.
self-managed 3.7 would be planned.
What a reader sees changes in:
  the file the lines share: 1 file changed
  site's plain output: 6 files changed
  site's site output: 2 files changed
  site@3.4's plain output: 5 files changed
  site@3.4's site output: 1 file changed
  site@3.5's plain output: 8 files added
  site@3.5's site output: 15 files added
Dry run: nothing written. It would write ascribe.toml and 1 file under docs.
```

Tested with ascribe 0.4.4 on Linux.

When it archives the old current line, the marks that name a version in it say nothing any more: every line left has what they mark. It removes or rewrites each one [`available-same-in-every-line`](https://ascribed-dev.com/docs/checks/diagnostics/project-checks.md#asc212-available-same-in-every-line) would report, as that check's fix does: a mark with nothing left to say goes, and one that still says something, such as a state or another target, keeps that alone, as `self-managed beta 3.5` becomes `self-managed beta`. A feature's spec is rewritten the same way. The text lists each with its file and line, and the JSON as `cleared` and `cleared_features`, so after it `ascribe check` reports no `available-same-in-every-line` for the archived line. A mark that said nothing before the release, naming an older line, is left for `ascribe check` to report. A badge build showed what these marks said, so the pages it drew a badge on are among the outputs the release changes.

It builds the project in memory before and after, and refuses when rewriting the marks or archiving the old line would change what a reader sees: both are meant to change nothing, so a change there is a mark the rewrite got wrong. What the release itself changes, each page whose content was planned and is now live, it lists by build. It writes nothing when it refuses, and `--dry-run` says what it would do and writes nothing.

It writes only files git can put back as they were: `ascribe.toml` and each page it rewrites must be tracked, and unchanged since the last commit. So the release is one commit to read, and `git checkout` undoes it. With `--force` it writes them anyway, as it must outside a repository.

In a project whose [`[from]`](https://ascribed-dev.com/docs/content-model.md) takes the target's lines from another project's `ascribe.toml`, the release is made where they're written: in that project, its `ascribe.toml`, its pages' marks, and its outputs. The text says so first, and the JSON's `from` names the file, from the current folder; `files` are then from that project's root. The lines move for every project that takes them, from the one release. A mark on a page of a project that takes them isn't rewritten.

A line that isn't planned can be released too, as long as it falls between the current line and the oldest planned one: a patch release, `3.5.1` when the lines are written as patches, needs no entry first. `planned` keeps naming the planned line, so no mark is rewritten.

It refuses a line that's live or archived already, so running it twice changes nothing the second time, and a planned line with an older one planned before it. A mark it can't rewrite where it's written, such as a frontmatter `available:` that spans lines, is named with its file and line, to write on one line first.

- `LINE`: The line to release, named as a mark names it: "ascribe 0.4.0".
- `--next <LINE>`: The line planned after the released one, added to planned, so that `planned` names it from then on.
- `--dry-run`: Say what the release would do, and write nothing.
- `--force`: Write the files even when git can't put them back: untracked or changed files, or no repository at all.
- `--format <FORMAT>`: How to show what the release did.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

With `--format json`, it writes the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/versions.md#ascribe-versions-release) describes:

**The JSON for this example**

```json
{
  "schema_version": 2,
  "ascribe_version": "0.4.4",
  "written": false,
  "target": "self-managed",
  "line": "3.6",
  "was_planned": true,
  "next": "3.7",
  "next_added": true,
  "archived": null,
  "files": [
    {
      "file": "docs/keys.md",
      "marks": [
        {
          "line": 30,
          "from": "self-managed planned",
          "to": "self-managed 3.6"
        }
      ]
    }
  ],
  "cleared": [],
  "cleared_features": [],
  "outputs": [
    {
      "build": "",
      "output": "_ascribe",
      "added": [],
      "removed": [],
      "changed": [
        "versions.json"
      ]
    },
    {
      "build": "site",
      "output": "plain",
      "added": [],
      "removed": [],
      "changed": [
        "configure.md",
        "install-agent.md",
        "keys.md",
        "quickstart.md",
        "upgrade.md",
        "webhooks.md"
      ]
    },
    {
      "build": "site",
      "output": "site",
      "added": [],
      "removed": [],
      "changed": [
        "_ascribe/help.json",
        "keys.md"
      ]
    },
    {
      "build": "site@3.4",
      "output": "plain",
      "added": [],
      "removed": [],
      "changed": [
        "configure-legacy.md",
        "install-agent.md",
        "keys.md",
        "quickstart.md",
        "upgrade.md"
      ]
    },
    {
      "build": "site@3.4",
      "output": "site",
      "added": [],
      "removed": [],
      "changed": [
        "upgrade.md"
      ]
    },
    {
      "build": "site@3.5",
      "output": "plain",
      "added": [
        "_fragments/prerequisites.png",
        "configure.md",
        "install-agent.md",
        "keys.md",
        "playground.png",
        "quickstart.md",
        "upgrade.md",
        "webhooks.md"
      ],
      "removed": [],
      "changed": []
    },
    {
      "build": "site@3.5",
      "output": "site",
      "added": [
        "_ascribe/headers.json",
        "_ascribe/help.json",
        "_ascribe/nav.json",
        "_ascribe/pages.json",
        "_ascribe/permalinks.json",
        "_ascribe/redirects.json",
        "_ascribe/schema.ts",
        "_fragments/prerequisites.png",
        "configure.md",
        "install-agent.md",
        "keys.md",
        "playground.png",
        "quickstart.md",
        "upgrade.md",
        "webhooks.md"
      ],
      "removed": [],
      "changed": []
    }
  ],
  "from": null
}
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | Released, or with `--dry-run`, it would be |
| `2` | Nothing was written: the line can't be released, a mark can't be rewritten, a step that should change no output changes some, or git can't put the files back and there's no `--force` |

### `ascribe versions export`

Renders a line, live or planned, into a folder of HTML that needs nothing else: every page of the line with a statement of what it is, a sidebar with a search box, a contents page, and a print stylesheet, marked so crawlers leave it alone, working from a disk with no server. Its archive is a copy of the line to host once the line leaves the window; exporting a line doesn't make it archived, which [`retire`](https://ascribed-dev.com/docs/versions/release.md#ascribe-versions-retire) does. [Export and retire a line](https://ascribed-dev.com/docs/versions/archive.md#1-export-the-line) says what's in it, and the [archive contract](https://ascribed-dev.com/docs/contracts/archive.md) its layout. In a copy of `examples/quill`, `ascribe versions export "self-managed 3.4" --to archive/3.4 --current https://docs.quill.dev/` writes the archive and says what to add to `ascribe.toml` once it's hosted:

```text
Wrote 5 pages to archive/3.4. Open archive/3.4/index.html.
Its search index is 4 KB.
A link is written as text, since the page it leads to isn't in the line:
  docs/upgrade.md: "webhooks" led to /webhooks/ in 3.5
Host the folder, then record its address:
  [releases.self-managed.archived]
  "3.4" = { url = "…" }
```

Tested with ascribe 0.4.4 on Linux.

`--to` is from the current folder, and is a folder that's empty or one an archive was written in: writing again replaces the files its manifest lists, and nothing else. It refuses a folder with anything else in it, writing nothing there. It writes nothing into the project, runs the checks for the line's build first, and refuses when they find an error. A planned line's build is checked too, though `ascribe check` checks only the live lines, so the error names the build it found them in. Exporting the current line works, and the text says it's unusual. A link to a page the line doesn't have is written as its text, and the text names each one. The text says how large the search index is, and when it's over 8 MB, that a reader's first search waits for it to load; it's written whatever its size.

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

It was `ascribe versions archive` until this release, which named the copy for the state the line reaches only once it's retired. The old name still runs it, with a warning, for one more release, so a script or CI job that names it keeps working until you change it.

- `LINE`: The line to export, named as a mark names it: "self-managed 3.4". It's live or planned.
- `--to <DIR>`: The folder to write the archive in, from the current folder: an empty one, or one an archive was written in, whose files it replaces.
- `--build <NAME>`: The versioned build to export the line of, when two builds are versioned by the line's target.
- `--current <URL>`: The address of the current documentation, which the statement at the top of every page links to.
- `--format <FORMAT>`: How to show what was written.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

With `--format json`, it writes the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/versions.md#ascribe-versions-export) describes:

**The JSON for this example**

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "target": "self-managed",
  "line": "3.4",
  "status": "live",
  "build": "site",
  "folder": "archive/3.4",
  "contents": "index.html",
  "pages": 5,
  "assets": 2,
  "removed": 0,
  "search_index_bytes": 3195,
  "search_index_large": false,
  "unlinked": [
    {
      "file": "docs/upgrade.md",
      "text": "webhooks",
      "href": "/webhooks/",
      "line": "3.5"
    }
  ]
}
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | The archive is written |
| `2` | Nothing was written: the project doesn't load, the line isn't declared or is archived, no build or more than one is versioned by its target and `--build` doesn't say which, the checks find an error, or the folder isn't empty and isn't an archive |

### `ascribe versions retire`

Takes the oldest live line out of the window once its archive is hosted: moves it from `live` to `archived` with its address and the day it ended, rewrites each mark for its target to the simplest spec that means the same in every line still live or planned, lists what no line has any more, and says where each of the line's addresses should go. [Export and retire a line](https://ascribed-dev.com/docs/versions/archive.md#4-retire-the-line) has each step. It edits `ascribe.toml` in place, keeping its comments and order, and each mark where it's written. In `examples/quill`, `ascribe versions retire "self-managed 3.4" --url https://archive.quill.dev/3.4/ --dry-run --show-unused` says what it would do:

```text
self-managed 3.4 would be archived, at https://archive.quill.dev/3.4/.
Would rewrite 6 marks in 5 files. No live line's output would change.
Expected: builds that aren't lines of self-managed change only self-managed's part of their availability lines:
  cloud's plain output changes install-agent.md
  cloud's site output changes install-agent.md
1 page is in no live line:
  docs/configure-legacy.md:5: the page "Configure the agent (3.4)"
Delete it with: ascribe versions retire "self-managed 3.4" --url https://archive.quill.dev/3.4/ --remove-unused
1 page was replaced by another. Its old address, and where it should go:
  /3.4/configure-legacy/  →  /configure/
4 other addresses of 3.4 go to the same page in the current line, or in the archive: --format json lists each.
Dry run: nothing written.
```

Tested with ascribe 0.4.4 on Linux.

The line's `ended` is its date in `ends`, which goes, or, when it had none, [the day the checks run as](https://ascribed-dev.com/docs/checks/check.md#checking-as-of-another-day). Its address is `--url`, the one its archive is read at.

It builds the project in memory before and after, and refuses when a live line's output changes: a line's output never showed a version from before the window, so the rewrite is meant to change none. A build that isn't a line of the target can change what it shows: a badge build shows those versions, and a filter build for an older version gains the content a mark no longer limits to later ones. So it names each such build's pages, as those it adds, removes, and changes, and the rewrite needs `--force` to go ahead.

A page whose only change is the target's part of its [availability](https://ascribed-dev.com/docs/glossary.md#availability) lines, such as a cloud build's `self-managed (preview, 3.4+)` becoming `self-managed (preview)`, is the retirement doing its job: it's listed under its own heading, as `expected` in the JSON, and needs no `--force`. Any other change does, another target's part of the same line included. It writes only files git can put back as they were, as [`release`](https://ascribed-dev.com/docs/versions/release.md#ascribe-versions-release) does, or anything with `--force`.

It refuses a line that isn't live, which is why running it twice changes nothing the second time; the current line, and a live line that isn't the oldest; a line with no `--url`, since after the clean-up the source no longer holds what the line needs, so [`ascribe versions export`](https://ascribed-dev.com/docs/versions/release.md#ascribe-versions-export) comes first; and a target no build is versioned by, whose list `release` already keeps to one live line. Like `release`, it's made in the project that writes the lines, when `[from]` takes them from there.

It deletes nothing unless it's told to. A page, section, block, or table row that no live or planned line has is listed with its file, line, and first line, and so is a mark that says nothing once rewritten: one for every target its scope allows, generally available, with nothing inside it relying on it. Such a mark still draws a badge, so the rewrite keeps it. `--show-unused` lists each, and `--remove-unused` deletes them, along with the `replaces` line of a page that replaced one it deletes. Both work on a line retired already.

Each address the line published goes to the same page in the current line, to the page that replaces it there, or to its page in the archive when the current line has neither. The text names the replaced pages, and the JSON lists every address. In a project with [`ascribe.addresses`](https://ascribed-dev.com/docs/publishing/redirects.md), each address that goes to a page in the current line is recorded there as a `[moved]` entry keyed by the address, so the build writes its redirect, and the text says how many; the JSON's `recorded` says whether they were. The archive's addresses aren't recorded, since an entry leads to a page of the project, and without the file nothing is: those are yours to add to the host's redirects.

- `LINE`: The line to retire, named as a mark names it: "self-managed 3.4". It's the oldest live line.
- `--url <URL>`: The address the line's archive is read at, recorded as its `url` in \[releases\].
- `--dry-run`: Say what the retirement would do, and write nothing.
- `--force`: Write the files even when git can't put them back, and rewrite the marks even when a build that isn't a line of the target changes.
- `--show-unused`: List each block, row, section, and page no live or planned line has, and each mark that says nothing once rewritten, with its page and first line. Works on a line retired already.
- `--remove-unused`: Delete what --show-unused lists. Works on a line retired already.
- `--format <FORMAT>`: How to show what the retirement did.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

With `--format json`, it writes the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/versions.md#ascribe-versions-retire) describes, here as of 16 October 2026:

**The JSON for this example**

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "written": false,
  "retired": true,
  "target": "self-managed",
  "line": "3.4",
  "url": "https://archive.quill.dev/3.4/",
  "ended": "2026-10-16",
  "features": [],
  "files": [
    {
      "file": "docs/configure.md",
      "marks": [
        {
          "line": 5,
          "from": "self-managed 3.5",
          "to": "self-managed"
        }
      ]
    },
    {
      "file": "docs/install-agent.md",
      "marks": [
        {
          "line": 5,
          "from": "cloud, self-managed preview 3.3",
          "to": "cloud, self-managed preview"
        },
        {
          "line": 80,
          "from": "cloud, self-managed preview 3.4",
          "to": "cloud, self-managed preview"
        }
      ]
    },
    {
      "file": "docs/keys.md",
      "marks": [
        {
          "line": 25,
          "from": "self-managed 3.5",
          "to": "self-managed"
        }
      ]
    },
    {
      "file": "docs/upgrade.md",
      "marks": [
        {
          "line": 5,
          "from": "self-managed 3.4",
          "to": "self-managed"
        }
      ]
    },
    {
      "file": "docs/webhooks.md",
      "marks": [
        {
          "line": 5,
          "from": "self-managed 3.5",
          "to": "self-managed"
        }
      ]
    }
  ],
  "unused": [
    {
      "file": "docs/configure-legacy.md",
      "line": 5,
      "kind": "page",
      "text": "Configure the agent (3.4)"
    }
  ],
  "removed": false,
  "redirects": [
    {
      "from": "/3.4/configure-legacy/",
      "to": "/configure/"
    },
    {
      "from": "/3.4/install-agent/",
      "to": "/install-agent/"
    },
    {
      "from": "/3.4/keys/",
      "to": "/keys/"
    },
    {
      "from": "/3.4/quickstart/",
      "to": "/quickstart/"
    },
    {
      "from": "/3.4/upgrade/",
      "to": "/upgrade/"
    }
  ],
  "recorded": false,
  "outputs": [],
  "expected": [
    {
      "build": "cloud",
      "output": "plain",
      "added": [],
      "removed": [],
      "changed": [
        "install-agent.md"
      ]
    },
    {
      "build": "cloud",
      "output": "site",
      "added": [],
      "removed": [],
      "changed": [
        "install-agent.md"
      ]
    }
  ],
  "from": null
}
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | Retired, or with `--dry-run`, it would be; or, on a line retired already, what it left is listed or deleted |
| `2` | Nothing was written: the line can't be retired, it has no address, a mark can't be rewritten, a live line's output changes, a build that isn't a line changes and there's no `--force`, or git can't put the files back and there's no `--force` |
