> 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 explain`, `model`, `outline`, `link`, `refs`, `rename`, `resolve`, and `search`

> Reference · Ask a project about its diagnostics, content model, pages, links, and references, find where its pages say something, and rename what a page links to.

The commands that answer questions about a project, for writers and for agents, without changing anything, and `ascribe rename`, which changes a name and every reference to it. The options every command accepts are in the [command reference](https://ascribed-dev.com/docs/command.md#options-every-command-accepts).

## `ascribe explain`

Says what a diagnostic means: its severity, its messages, how to fix it, a link to its entry in [Diagnostics](https://ascribed-dev.com/docs/checks/diagnostics.md), and, for the diagnostics people meet most, a short page that has the problem and the same page without it. It needs no project.

- `[CODE]`: The diagnostic's code (`ASC036`) or name (`link-target-missing`). Without it or `--list`, a usage error that names `--list`.
- `--list`: List every diagnostic's code and name, one per line, instead.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

````text
ASC036 link-target-missing: error, found in each file

Message: `{path}` doesn't exist
  or: `{path}` doesn't exist; `{actual}` differs only in case, and paths are case-sensitive
  or: `{path}` doesn't exist; did you mean `{closest}`?
  or: `{path}` is outside the project, so it can't be copied into the output

Fix: Fix the path, or create the file. Names must match exactly, including case, and the file must be inside the project.

Wrong, in page.md:
```md
---
title: Install Quill
---

To get a key, see [API keys](api-key.md).
```

Right:
```md
---
title: Install Quill
---

To get a key, see [API keys](keys.md).
```

And keys.md:
```md
---
title: API keys
---

Create a key in **Settings**.
```

Docs: https://ascribed-dev.com/go/ASC036/
````

Tested with ascribe 0.4.4 on Linux.

The text output is for a terminal, so a link in the fix is written as its text, with the address after it when it leads off the docs; `--format json` keeps the Markdown, links and all. An example that needs something in `ascribe.toml` to go wrong shows that too: a few lines of the model it was checked against. Every example is checked, so each wrong page reports its diagnostic and nothing else, and each right page reports nothing.

| Code | Meaning |
| --- | --- |
| `0` | It explained the diagnostic, or listed them |
| `2` | No diagnostic has that code or name, and it names the closest; or a usage error |

With `--format json`, the document is as follows. Its [schema](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-explain) has every field, and so does the schema of what `--list` writes:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "code": "ASC036",
  "slug": "link-target-missing",
  "severity": "error",
  "next": "choose",
  "level": "file",
  "message": "`{path}` doesn't exist",
  "variants": [
    {
      "name": "case",
      "message": "`{path}` doesn't exist; `{actual}` differs only in case, and paths are case-sensitive"
    },
    {
      "name": "closest",
      "message": "`{path}` doesn't exist; did you mean `{closest}`?"
    },
    {
      "name": "outside",
      "message": "`{path}` is outside the project, so it can't be copied into the output"
    }
  ],
  "fix": "Fix the path, or create the file. Names must match exactly, including case, and the file must be inside the project.",
  "docs": "https://ascribed-dev.com/go/ASC036/",
  "example": {
    "wrong": "---\ntitle: Install Quill\n---\n\nTo get a key, see [API keys](api-key.md).\n",
    "right": "---\ntitle: Install Quill\n---\n\nTo get a key, see [API keys](keys.md).\n",
    "model": null,
    "files": [
      {
        "path": "keys.md",
        "text": "---\ntitle: API keys\n---\n\nCreate a key in **Settings**.\n"
      }
    ]
  }
}
```

Tested with ascribe 0.4.4 on Linux.

## `ascribe model`

Shows the [content model](https://ascribed-dev.com/docs/glossary.md#content-model) as `ascribe` reads it, with the defaults filled in: page types with their files and frontmatter fields, [dimensions](https://ascribed-dev.com/docs/glossary.md#dimension), phrases, features, glossary terms, project widgets, builds, and sources with their folders and the files they make readable. A source with an `ascribe.examples.toml` also says how many of its files are [tested examples](https://ascribed-dev.com/docs/examples/tested.md), and names each observation with what it was tested with and how many of its files are as it found them. For a source in another repository, that's read from its copies, and an observation's files are counted only where there's a copy. It's what an agent, or a new writer, reads before writing frontmatter, a directive's attributes, or a phrase.

- `[PATH]`: A file or folder in the project, to find its `ascribe.toml` from. By default, the current directory.
- `--section <NAME>`: Show only this section, in full.
  - `types`: Page types, with their files and frontmatter fields.
  - `frontmatter`: Frontmatter every page type shares.
  - `fragments`: [Fragments](https://ascribed-dev.com/docs/glossary.md#fragment)' files and frontmatter.
  - `dimensions`: Dimensions and their values.
  - `lifecycle`: Lifecycle states.
  - `phrases`: Phrases, with their values.
  - `features`: Features, with their [availability](https://ascribed-dev.com/docs/glossary.md#availability).
  - `notes`: Note types, with their labels.
  - `glossary`: Glossary terms.
  - `images`: Image attributes.
  - `widgets`: Project widgets, with their attributes.
  - `releases`: [Release lines](https://ascribed-dev.com/docs/glossary.md#release-line), each target's with its status and the builds that are it.
  - `builds`: Builds, with their variant and availability modes.
  - `sources`: Sources, with their folders and the files they make readable.
  - `apis`: APIs, with their specs and references.
  - `from`: What `[from]` takes.
  - `intended`: Acknowledged problems.
  - `project`: Where the content is, and where builds go.
  - `versions`: How versions are compared.
  - `consumer`: The site the outputs are for.
  - `editor`: The build the editor checks.
  - `checks`: Each check's level, Vale, and the external links.
  - `crawlers`: What the site tells crawlers.
  - `listing`: What the site lists for search engines.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
# Content model

## Page types

- `page`: frontmatter `title` (string, required), `description` (string)

## Dimensions

- `pm` (Package manager): `npm`, `pnpm`, `yarn`
- `deployment` (Deployment): `cloud` (versionless), `self-managed`

## Phrases

- `{product}`: Quill
- `{cloud}`: Quill Cloud
- `{version}`: 3.4.1
- `{api}`: https://api.quill.dev/v3/

## Image attributes

- `width` (number)
- `height` (number)

## Releases

- `self-managed`: 3.5 (current, `site`); 3.4 (live, `site@3.4`)

## Builds

- `site`, the editor's build: variants switch, availability badge, line 3.5 of `self-managed`
- `site@3.4`: variants switch, availability badge, line 3.4 of `self-managed`
- `cloud`: variants deployment=cloud, availability filter cloud

## Settings

- `project`, `versions`, `consumer`, `editor`, `checks`, `crawlers`, `listing`, each with its keys and values: `ascribe model --section <name>`
```

Tested with ascribe 0.4.4 on Linux.

The text is Markdown, at most about 4,000 characters: when the model has more than fits, each long list is cut, and the cut says which `--section` shows the rest. A section shown with `--section` is never cut. A section the model leaves empty isn't shown. When `ascribe.toml` has errors, it says to run `ascribe check`, and exits with `2`.

| Code | Meaning |
| --- | --- |
| `0` | It showed the model |
| `2` | No `ascribe.toml`, or one with errors |

With `--format json`, the document has a field for each section shown, and without `--section`, every section, empty or not, and the project's [navigation](https://ascribed-dev.com/docs/navigation.md) as the sidebar shows it: a label with a code span in it is plain text, without the backticks. Its [schema](https://ascribed-dev.com/docs/contracts/json-reports/model.md#ascribe-model) has each section's fields. `--section builds`:

```json
{
  "schema_version": 4,
  "ascribe_version": "0.4.4",
  "builds": [
    {
      "name": "site",
      "variants": "switch",
      "availability": "badge",
      "editor": true,
      "versions": "self-managed",
      "line": "3.5"
    },
    {
      "name": "site@3.4",
      "variants": "switch",
      "availability": "badge",
      "editor": false,
      "versions": "self-managed",
      "line": "3.4"
    },
    {
      "name": "cloud",
      "variants": "deployment=cloud",
      "availability": "filter cloud",
      "editor": false
    }
  ]
}
```

Tested with ascribe 0.4.4 on Linux.

A project with [release lines](https://ascribed-dev.com/docs/versions/lines.md) has a section for them, `releases` in JSON: each target's lines, with their status and the build that is each line. A versioned build says which target versions it and which line it is.

Every other table has a section too. `lifecycle` and `notes` have built-in entries, which the text without `--section` shows only when `ascribe.toml` writes them.

The rest of `ascribe.toml` is there too, a section of settings for each of its tables: `project`, `versions`, `consumer`, `editor`, `checks`, `crawlers`, and `listing`. The text without `--section` names them at its end, and `--section` shows one, each key with the value in effect, and its default: `ascribe model --section consumer` shows `[consumer]`'s `site`, `base-path`, and the rest, the ones the file leaves out at their defaults. `[checks]` has the keys of `[checks.vale]` and `[checks.links]`, written `vale.preset` and `links.reuse-days`, and each check the file sets a level for. In JSON, each section is a list of keys, in the [schema](https://ascribed-dev.com/docs/contracts/json-reports/model.md#ascribe-model). The keys and defaults are the ones the loader reads, so `model` shows no key `ascribe check` would refuse.

## `ascribe outline`

Shows a page's title and type, and the headings a link to it can name, with the ids links write after `#`: the page's own headings and those that come from the fragments it includes. They're the headings the editor offers after `page.md#`.

- `PAGE`: The page or fragment: a path from the current directory, or from the content root.
- `--build <NAME>`: Show only the headings this build publishes.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
API keys
docs/keys.md, type page

docs/keys.md:7: ## Create a key  #create-key
docs/keys.md:12: ## Rotate keys  #rotate-keys
docs/keys.md:23: ## Sign webhook events  #sign-webhooks

Link to a heading as `keys.md#<id>`.
```

Tested with ascribe 0.4.4 on Linux.

A heading from a fragment says which fragment. With `--build`, it shows only the headings that build publishes: a heading in a variant the build leaves out, or in a block it filters out by availability, isn't listed. When the build doesn't publish the page at all, it says why, lists no headings, and exits with `1`. A fragment's outline lists its headings, and those of the fragments it includes.

Under the page, and under each heading, a line lists each [memo file](https://ascribed-dev.com/docs/writing/memo-files.md) about it, with its first line, and `(outdated)` when the words it quotes aren't there any more. A detached [memo](https://ascribed-dev.com/docs/glossary.md#memo) isn't listed.

| Code | Meaning |
| --- | --- |
| `0` | It showed the outline |
| `1` | With `--build`: the build doesn't publish the page |
| `2` | It couldn't run: no `ascribe.toml`, one with errors, a path that isn't a page or fragment of the project, or an unknown build |

With `--format json`, the document is:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "page": "keys.md",
  "file": "docs/keys.md",
  "fragment": false,
  "title": "API keys",
  "type": "page",
  "build": null,
  "not_published": null,
  "memos": [],
  "headings": [
    {
      "level": 2,
      "text": "Create a key",
      "id": "create-key",
      "explicit_id": true,
      "file": "docs/keys.md",
      "line": 7,
      "fragment": null,
      "memos": []
    },
    {
      "level": 2,
      "text": "Rotate keys",
      "id": "rotate-keys",
      "explicit_id": true,
      "file": "docs/keys.md",
      "line": 12,
      "fragment": null,
      "memos": []
    },
    {
      "level": 2,
      "text": "Sign webhook events",
      "id": "sign-webhooks",
      "explicit_id": true,
      "file": "docs/keys.md",
      "line": 23,
      "fragment": null,
      "memos": []
    }
  ]
}
```

Tested with ascribe 0.4.4 on Linux.

`page` is the path from the content root, as links write it, and `file` is from the project root, as `ascribe check` reports it. `type` is null for a fragment. `explicit_id` is true when the heading has an `@id`, which stays when its text changes; prefer those in links. `fragment` is the fragment a heading comes from, or null.

`memos`, on the page and on each heading, lists the memo files about it, in path order: `file`, from the project root; `state`, `placed` or `outdated`; `quote`, or null; `first_line`, the first line of its text; and `personal`, which is false for the project's `memos/`.

## `ascribe link`

Says whether a link works, written on a given page: whether its target exists, the page's title or the heading's text, and the destination to write there. It resolves the link as `ascribe check` and the editor do, so the three agree.

- `TARGET`: The link's destination, as the page would write it: `keys.md`, `keys.md#rotate-keys`, `#install`.
- `--from <PAGE>`: The page the link is on: a path from the current directory, or from the content root.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
keys.md#rotate-keys exists: a heading on keys.md.
Write: [Rotate keys](keys.md#rotate-keys), or [](keys.md#rotate-keys), which shows the same title
```

Tested with ascribe 0.4.4 on Linux.

When the target doesn't work, it says why and lists the closest targets that do: the page's headings for a heading that isn't there, first those whose ids start with the words of the one written, as `ask-instead` does `ask-instead-of-guessing`, then those whose words start the written one's, then the rest, closest first; for a page that isn't, the pages with the heading the link names, wherever they are, then pages with a similar path; the pages that include a fragment; or the file that a published route belongs to. `ascribe check` offers the same targets as the broken link's fixes, marked `unsafe`, and when one clearly wins, such as the only heading whose id starts with the written id's words, its message ends with "did you mean `loop.md#ask-instead-of-guessing`?", so the concise output and the edit hook carry it too.

```text
keys.md#rotate doesn't work from examples/quill/docs/install-agent.md: `keys.md` has no heading with the id `rotate`.
Closest:
  [Rotate keys](keys.md#rotate-keys)
  [Create a key](keys.md#create-key)
  [Sign webhook events](keys.md#sign-webhooks)
```

Tested with ascribe 0.4.4 on Linux.

For a page or a heading, `Write:` gives the link with its title and with empty text, `[](../keys.md#rotate-keys)`, which shows the same title: either does, and the empty one is the smaller edit to a link that has no text.

A URL with a scheme isn't checked: it exists as far as `link` knows. A file that isn't a page, such as an image, exists when the project has it. A link to an API's operation exists when the spec has it, and the answer says which: see [Link to operations in the editor](https://ascribed-dev.com/docs/openapi/editor.md#for-agents).

| Code | Meaning |
| --- | --- |
| `0` | The target exists |
| `1` | It doesn't |
| `2` | It couldn't run: no `ascribe.toml`, one with errors, or a `--from` that isn't a page or fragment of the project |

With `--format json`, the document is:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "from": "examples/quill/docs/install-agent.md",
  "target": "keys.md#rotate-keys",
  "exists": true,
  "kind": "heading",
  "path": "keys.md",
  "id": "rotate-keys",
  "title": "Rotate keys",
  "href": "keys.md#rotate-keys",
  "problem": null,
  "closest": [],
  "lines": [
    {
      "build": "site",
      "line": "3.5",
      "page": "keys.md",
      "leads_to": null
    },
    {
      "build": "site@3.4",
      "line": "3.4",
      "page": "keys.md",
      "leads_to": null
    }
  ]
}
```

Tested with ascribe 0.4.4 on Linux.

`from` is the page as `--from` gave it, from the folder the command was run in, the way `ascribe check` names a file, whether it was given that way or from the content root, and `target` is the target as given. `exists` says whether the link works. `path` is the file the target names, from the content root, `id` the heading's id, and `title` the page's title or the heading's text. `kind` is `page`, `heading`, `fragment`, `file`, `operation`, `external`, or `missing`. `href` is null when the target doesn't work, and `problem` says why; `closest` lists other targets, best first, each with `href`, `path`, `id`, and `title`. With a versioned build, `lines` says where a link to a page goes in each line. For an operation, `operation` has its `api`, `operation_id`, `method`, `path`, `summary`, `tag`, `deprecated`, and `address`. Every field is in [the schema](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-link).

## `ascribe refs`

Lists the places that use a page, a fragment, a heading, an image, or an entry of the content model, each as `file:line:column`, with what kind of use it is. It's the editor's **Find All References**, on the command line: it finds a phrase only where it's a phrase, and a heading in a fragment through every page that includes it, which a text search can't.

- `TARGET`: What to find the uses of: a page or fragment (`keys.md`), a heading (`keys.md#rotate-keys`), an image (`images/keys.png`), `phrase:<key>`, `feature:<key>`, `term:<id>`, `dimension:<name>`, `note:<type>`, `widget:<name>`, or a line of a release target, `release:<target>@<version>` (`release:self-managed@3.4`). A path is from the current directory, or from the content root.
- `--limit <N>`: List at most this many places. By default, 50 in text and 500 in JSON.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
docs/install-agent.md:8:181: phrase
docs/install-agent.md:62:12: phrase
docs/install-agent.md:84:1: phrase

5 places use phrase:cloud.
Showing 3 of them; for all: `ascribe refs phrase:cloud --limit 5 --config examples/quill`
```

Tested with ascribe 0.4.4 on Linux.

A target that's a path, with or without `#id`, finds its project from the path. An entry of the model, or a glossary term, is written with a prefix: `phrase:product` (or `phrase:{product}`), `feature:sso`, `term:api-key`, `dimension:deployment`, `note:warning`, or `widget:release-note`, and the project is found from the current directory, or from `--config`. The places are in path order, and in order within a file. A use is `link`, `include`, `phrase`, `availability`, `term`, `variant`, `note`, or `widget`.

An `availability` place shows its spec as written, after the use: ``docs/keys.md:25:13: availability `self-managed 3.5` ``.

A line of a release target, one with `[releases.<target>]`, is written `release:<target>@<version>`. Its places are the availability specs that name a version in that line, as each is written, with `planned` read as the target's planned line. A line holds every version that starts with its parts, so `release:ascribe@0.4` finds `ascribe 0.4.1` and `ascribe (preview 0.4.0, ga 0.5.0)`, and `release:ascribe@0.4.1` only the first. A spec that names the target's dimension, such as `release`, names no version of it. A target with no `[releases.<target>]` doesn't exist.

```text
docs/configure-legacy.md:5:1: availability `self-managed (ga 3.4, removed 3.5)`
docs/configure.md:5:1: availability `self-managed 3.5`
docs/keys.md:25:13: availability `self-managed 3.5`
docs/webhooks.md:5:1: availability `self-managed 3.5`

4 places use release:self-managed@3.5.
```

Tested with ascribe 0.4.4 on Linux.

An image under the content root is a target too, named as a page is: `ascribe refs images/keys.png`. Its places are each file that shows it (`image`), links to it (`link`), names it in raw HTML such as `<img src>` (`html`), or names it in a frontmatter field such as `cover` (`frontmatter`): the uses that keep [`image-unused`](https://ascribed-dev.com/docs/checks/diagnostics/project-checks.md#asc159-image-unused) from reporting it. An image nothing uses has no places, and exits with `0`, as any target that exists does.

A [memo file](https://ascribed-dev.com/docs/writing/memo-files.md) about the target is a place too, with the use `memo`, at its `about` line, after the uses. One whose `about` names a target that doesn't exist is listed under that target, so `ascribe refs` finds the memos a rename left behind.

| Code | Meaning |
| --- | --- |
| `0` | The target exists, used or not |
| `1` | It doesn't exist |
| `2` | It couldn't run: no `ascribe.toml`, one with errors, a target that's neither a path nor an entry, or a file that's neither a page, a fragment, nor an image |

With `--format json`, the document is:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "target": "phrase:cloud",
  "kind": "phrase",
  "exists": true,
  "places": [
    {
      "file": "docs/install-agent.md",
      "line": 8,
      "column": 181,
      "use": "phrase",
      "personal": false,
      "spec": null
    },
    {
      "file": "docs/install-agent.md",
      "line": 62,
      "column": 12,
      "use": "phrase",
      "personal": false,
      "spec": null
    },
    {
      "file": "docs/install-agent.md",
      "line": 84,
      "column": 1,
      "use": "phrase",
      "personal": false,
      "spec": null
    }
  ],
  "total": 5,
  "shown": 3,
  "truncated": true,
  "next_command": "ascribe refs phrase:cloud --limit 5 --config examples/quill --format json"
}
```

Tested with ascribe 0.4.4 on Linux.

`kind` is what the target is: `page`, `fragment`, `heading`, `image`, `phrase`, `feature`, `term`, `dimension`, `note`, `widget`, or `release`. Each place's `spec` is its availability spec as written, for an `availability` place, and null for any other. `total` is how many places use the target, and `shown` how many are listed. When the list was cut, `truncated` is true and `next_command` is the command that lists them all; otherwise it's null.

## `ascribe rename`

Renames a phrase, a dimension's value, a heading's id, or a page, and changes everything that uses it, as the editor's rename does: it makes the same edits. A text search and replace finds a phrase in the pages, but misses what a rename knows about: frontmatter fields with `phrases = true`, code blocks with `phrases=true`, the code a `@snippet {phrases=true}` shows, and a link whose path changes when the page it's on moves.

- `TARGET`: What to rename: `phrase:<key>`, `value:<value>` (a dimension's value), a heading's id (`keys.md#rotate-keys`), or a page or fragment (`keys.md`), which moves. A path is from the current directory, or from the content root.
- `NEW`: The new name: a phrase's key, a value, or an id. For a page or fragment, where it moves to, read as TARGET is: from the current directory, or from the content root.
- `--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.
- `--format <FORMAT>`: How to show what changed. `json` gives each changed file's whole text, the file that moves, and the entries recorded in ascribe.addresses.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
changed ascribe.toml (1 place)
changed docs/install-agent.md (4 places)
changed docs/keys.md (1 place)
```

Tested with ascribe 0.4.4 on Linux.

What it renames, written as `ascribe refs` writes a target:

| Target | What changes |
| --- | --- |
| `phrase:cloud` (or `phrase:{cloud}`) | Its key under `[phrases]`, and each `{cloud}` that uses it. |
| `value:self-managed` | A dimension's value: in `ascribe.toml` (its `values`, `labels`, and `versionless`, features' `available`, and builds' `variants` and `filter`), and in pages (`@variant` attributes, `variant` and `available` frontmatter, `@available` lines, and table rows' `available`). |
| `keys.md#rotate-keys` | A heading's id, and each link and include that names it, each `[moved]` value in `ascribe.addresses` that names it, and each [memo file](https://ascribed-dev.com/docs/writing/memo-files.md)'s `about` that names it, in `memos/` and `.ascribe/memos/`. A heading with an `@id` has it changed. One whose id is made from its text gets an `@id` line under it, so its text stays, and links to the headings after it that were numbered after it follow. |
| `keys.md` | The page or fragment moves to the new path, and each link and include to it, or in it, whose path changes is changed. So is what `ascribe.toml` names it by: its path in `[glossary] pages`, and a phrase that's a link to it; and each `[moved]` value in `ascribe.addresses` that leads to it, keeping its `#id`. So is each memo file's `about` that names it or a heading in it, keeping its `#id`, and its entry in its section's `pages` in [`ascribe.nav`](https://ascribed-dev.com/docs/navigation.md#the-file), which is taken out when it moves to another folder. A section written inline, as `sections = [{ … }]`, isn't followed: the file is left as it is, and `ascribe check` reports the name that's gone. The new path is read as the target was: from the current folder, or from the content root. A new path that changes only the file's case, such as `Keys.md`, renames it on a file system that matches names without case too, as macOS's and Windows' do. |

A path is from the current folder, or from the content root, and the project is found from it; for a phrase or a value, the project is found from the current folder. `--config` names the project for any target, so `ascribe rename keys.md guides/keys.md --config docs` works from the repository's root. A page that isn't found is named with both places it was looked for. It writes the files it changes, then moves the page, and prints each file it changed, from the current folder. 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 file with changes not committed is refused, and named, unless you pass `--force`. With `--dry-run`, it says what it would change and move, and changes nothing.

Some things aren't renamed or moved:

- A [permalink](https://ascribed-dev.com/docs/glossary.md#permalink)'s id isn't renamed, since links from outside the docs use it: change its `@id` by hand, then move the old name to the new one, or retire it, with [`ascribe permalinks retire`](https://ascribed-dev.com/docs/permalinks/command.md#ascribe-permalinks-retire).
- A page isn't moved through a folder that's a symbolic link.
- A phrase used in the code of a source in another repository isn't renamed either, since that code is a copy.

| Code | Meaning |
| --- | --- |
| `0` | It renamed |
| `1` | What it names doesn't exist: a phrase, a value, a page, or a heading |
| `2` | It couldn't rename: no `ascribe.toml`, one with errors, a new name that can't be its name or is taken, a target it doesn't rename, a file it can't write, or, without `--force`, a file git couldn't put back |

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

With `--format json`, it writes what it changed as one document, instead of a line for each file: `files`, each file it changed, from the current folder, with its whole `text` after and how many places changed (`edits`), in path order, the page that moved under where it was; `moved`, the page's `from` and `to`, when one moved; and `recorded`, each entry of `ascribe.addresses`'s `[moved]` it recorded, as a `key` and a `value`, as [`ascribe addresses move`](https://ascribed-dev.com/docs/publishing/redirects.md#ascribe-addresses-move) writes one. `written` is `true`, and `false` with `--dry-run`: the MCP server's [`ascribe_rename`](https://ascribed-dev.com/docs/agents/commands.md#ascribe-mcp) gives the same document with `written` false, and writes nothing. Its [schema](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-rename) has every field.

## `ascribe resolve`

Resolves a page for one build and writes it as the build's plain Markdown output by default, as `ascribe build --emit plain` writes it, or, with `--emit site`, as its site output: the page as a reader of that build sees it, with its variants chosen, the blocks the build filters out removed, phrases and includes filled in, and links written as that output writes them. It writes nothing to disk.

- `PAGE`: The page: a path from the current directory, or from the content root. Or `go:` and a permalink's name, for the help a product shows for it: its title, summary, and text, as the build's `_ascribe/help.json` has them, and its address.
- `--build <NAME>`: The build whose reader to show it as. Needed when `ascribe.toml` has more than one build.
- `--emit <OUTPUT>`: Which output to write the page as, one of `plain` (the default) or `site`, as `ascribe build --emit` names them. One output, not a list. Not for `go:<name>`, whose help has one form.
  - `plain`: Fully resolved CommonMark with no HTML.
  - `site`: Markdown plus web components, for an Astro site.
- `--frontmatter`: Write the page's frontmatter first, between `---` lines: under `--emit site`, the site output's. Nothing for `go:<name>`, which has no frontmatter.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
# API keys

## Create a key

Sign in to Quill Cloud and open **Settings → Keys**.

## Rotate keys

Generate a new key, put it in `quill.yaml`, and restart the agent. To install the agent first, see [Install the agent](https://docs.quill.dev/install-agent/#install-agent).
```

Tested with ascribe 0.4.4 on Linux.

A variant group the build keeps more than one arm of, which the site shows as tabs, is a line saying so, `A reader sees one of the next 3 sections at a time, chosen by operating system.`, then each arm under a heading that names its dimension and value, such as `### Operating system: macOS`, or its title for labeled arms, as the plain output writes it.

When the build doesn't publish the page, it writes nothing to standard output, says why on standard error, and exits with `1`. A fragment isn't resolved on its own; resolve a page that includes it.

A line of a [versioned build](https://ascribed-dev.com/docs/versions.md), such as `--build site@3.4`, resolves the page as that line publishes it, its route and links under the line's folder.

`--emit site` writes the page as the site output writes it, as `ascribe build --emit site` writes its file and `@ascribed/astro` receives it: Markdown with Ascribe's elements, such as `<ascribe-tabs>`, `<ascribe-note>`, and availability badges, from the code the build and the editor's preview use. It's what a site's integration is given, which the plain form can't show: what a switcher, a badge, or a glossary link becomes before a site draws it. `--emit plain`, the default, is the plain form above. `--emit` takes one output, not a list as `ascribe build --emit` does; a list is an error, exit `2`. From `examples/quill`, `ascribe resolve install-agent.md --build site --emit site` writes its Connect section as tabs, where the plain form writes a section for each arm:

````markdown
## Connect to Quill <ascribe-attributes id="connect"></ascribe-attributes>

<ascribe-tabs sync="deployment">

<ascribe-tab value="cloud" label="Quill Cloud">

<p class="ascribe-tab-label">Quill Cloud</p>

Sign in to Quill Cloud and copy an API key from **Settings → Keys**, then add it to `quill.yaml`:

```yaml
cloud:
  api_key: ${QUILL_KEY}
```

</ascribe-tab>

<ascribe-tab value="self-managed" label="self-managed">

<p class="ascribe-tab-label">self-managed</p>

Point the agent at your server. Self-managed servers must run Quill Server 3.3 or later.

```yaml
server:
  url: https://quill.internal.example.com
```

</ascribe-tab>

</ascribe-tabs>
````

Tested with ascribe 0.4.4 on Linux.

The site form's frontmatter, under `--frontmatter`, is the site output's, a line of a versioned build writes the line's links, and its elements read without their script as [a page in the site output](https://ascribed-dev.com/docs/command/site-output.md) says.

`go:` and a permalink's name, in place of the page, shows the help a [product shows](https://ascribed-dev.com/docs/permalinks/in-your-product.md) for that permalink: its entry in the build's `_ascribe/help.json`, from the code that writes the file. The project is the current directory's, or `--config`'s. The text is the entry's title, its summary when the text doesn't start with it, its text, and "Read more" with its address, which needs [`[consumer] site`](https://ascribed-dev.com/docs/content-model/output.md#15-consumer):

```text
Prerequisites
Before you install the agent, make sure you have:

- Node.js 20 or later.
- A docs repository that Quill can read.

Checklist of prerequisites

Read more: https://docs.quill.dev/go/prerequisites/
```

Tested with ascribe 0.4.4 on Linux.

A name the build doesn't publish exits with `1`, and standard error names the closest it does publish; a retired permalink exits with `1`, and standard error gives its reason. An alias shows its target's entry.

`--emit`, either value, with `go:<name>` is an error, exit `2`: a permalink's help has one form, `help.json`'s.

| Code | Meaning |
| --- | --- |
| `0` | It wrote the page, or the permalink's help |
| `1` | The build doesn't publish the page, or the permalink; or the permalink is retired |
| `2` | It couldn't run: no `ascribe.toml`, one with errors, a path that isn't a page, more than one build and no `--build`, an unknown build, an `--emit` that isn't one output or is given with `go:<name>`, or a page that can't be written |

With `--format json`, the document is as follows, and its [schema](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-resolve) has every field:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "page": "keys.md",
  "build": "cloud",
  "emit": "plain",
  "not_published": null,
  "route": "/keys/",
  "text": "# API keys\n\n## Create a key\n\nSign in to Quill Cloud and open **Settings → Keys**.\n\n## Rotate keys\n\nGenerate a new key, put it in `quill.yaml`, and restart the agent. To install the agent first, see [Install the agent](https://docs.quill.dev/install-agent/#install-agent).\n"
}
```

Tested with ascribe 0.4.4 on Linux.

`emit` says which output `text` is written as: `plain`, or `site` under `--emit site`, where `text` is the site output's file, with its frontmatter under `--frontmatter`.

For `go:<name>`, the document is the entry as `help.json` writes it, with the file's `version`, `build`, and `line`, and the `name` asked for, after `schema_version` and `ascribe_version`. `version` is `help.json`'s format version, which a reader checks as it checks the file's. Its [schema](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-resolve-goname) has every field:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "version": 1,
  "build": "site",
  "line": "3.5",
  "name": "prerequisites",
  "title": "Prerequisites",
  "text": "Before you install the agent, make sure you have:\n\n- Node.js 20 or later.\n- A docs repository that Quill can read.\n\nChecklist of prerequisites",
  "address": "https://docs.quill.dev/go/prerequisites/",
  "source": "install-agent.md"
}
```

Tested with ascribe 0.4.4 on Linux.

## `ascribe search`

Finds where the pages say something, as their readers read it, and names the line to edit for each place. A text search reads the source, where a page says `Sign in to {cloud}`; `ascribe search` reads each page as every build publishes it, where it says "Sign in to Quill Cloud", and says which line wrote that, and through which phrase.

- `QUERY`: What to find: words, which must all be in one block in any order, apart from common words such as `how` and `the`, or a phrase in double quotes, matched as written. Quote several words together: `ascribe search "rate limit"`, and a phrase inside them: `ascribe search '"rate limit"'`. Case is ignored unless `--case`, and a word matches inside a longer one unless `--word`.
- `[PATHS]...`: Search only the pages in these folders, or these pages, from the current folder. A fragment's text counts for the pages here that show it. By default, every page.
- `--type <NAME>`: Search only the pages of this content type.
- `--case`: Match case as written.
- `--word`: Match a word or a phrase only where it isn't inside a longer word: where the characters on either side aren't letters, digits, or `_`.
- `--summary`: Show the pages and how many places each has, most first, instead of the places.
- `--build <NAME>`: Search the pages of this build; repeat it for several. By default, every build.
- `--limit <N>`: List at most this many places, or with `--summary`, pages. By default, 50 places in text and 500 in JSON, or 20 pages.
- `--format <FORMAT>`: How to show the answer.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

```text
docs/install-agent.md:62: Sign in to Quill Cloud and copy an API key from Settings → Keys, then add it to…
    Connect to Quill #connect · deployment=cloud arm · through phrase cloud
docs/keys.md:10: Sign in to Quill Cloud and open Settings → Keys.
    Create a key #create-key · through phrase cloud

2 places match, on 2 pages.
```

Tested with ascribe 0.4.4 on Linux.

Each place is `file:line:` and the block as a reader reads it, cut to about 80 characters around the match's first word, then where a reader meets it: the heading it's under and that heading's id, the arm of a variant group it's in, the phrases the match reads through, and, when it isn't in every build searched, the builds it's in. A line is listed once however many pages and builds show it. The places are in a fixed order: titles, then headings, then the rest; in each, the places written in pages come before those in fragments and code files, each in path order and in order within a file.

The query is one argument, and paths come after it. Words must all be in one block, in any order, and a match never crosses from one block into another.

- **Phrases.** A phrase in double quotes is matched as written, a run of spaces matching any run of white space. Quote several words together, and a phrase inside them: `ascribe search "rate limit"` finds both words in a block, and `ascribe search '"rate limit"'` finds them together.
- **Common words.** Common English words outside quotes, such as `how`, `I`, and `the`, needn't be, unless they're all the query has: `how do I publish llms.txt` finds what `publish llms.txt` does.
- **Case.** Case is ignored, and nothing else is: `cafe` doesn't find "café". With `--case`, case is matched as written, so `ascribe search Vale --case` doesn't find "vale".
- **Whole words.** A word matches inside a longer one, so `sync` finds "synchronize"; with `--word`, a word or a phrase matches only where the characters on either side of it aren't letters, digits, or `_`, so `ascribe search limit --word` finds "the rate limit." and not "limits".

There are no regular expressions. To run a pattern over the text readers read, write the plain output with `ascribe build --emit plain` and use `grep` on it. That loses what `ascribe search` adds: `grep` names a line of the build's output, not the line of the source to edit, and it can't say that a line came from a fragment, a phrase, or a snippet, or list a line many pages show once; and it reads one build's output at a time.

Paths after the query, from the current folder, keep the search to the pages in those folders, or to those pages: `ascribe search sidebar docs/content/navigation/`. A fragment's text counts for the pages there that show it, and its place lists only them. `--type how-to` keeps to the pages of that content type. A path that has no page or fragment of the project is an error, and when every word after the query could be one of its words, the message says to quote them with it.

What's searched is each page's title and the field its type marks `role = "description"`, as each build publishes them, and what each build's plain output says of the page: its headings, paragraphs, lists, block quotes, tables (a row at a time), code blocks, and the titles and text of notes and details, wherever each is written: in the page, in a fragment an include brings in, generated ones under `_generated/` included, since readers see them, or in the code file a `@snippet` shows. A match is a place in the source, listed once however many pages show it:

| The match is | The place is | Where a reader meets it says |
| --- | --- | --- |
| Written in the page | The page's line | The heading and its id, the arm, the builds when not all |
| Written in a fragment | The fragment's line | `on 5 pages:` and the first three, each with its heading's id, then `ascribe refs` with the fragment for the rest |
| Partly a phrase's value, as "Sign in to Quill Cloud" where the page says `Sign in to {cloud}` | The line that uses the phrase | `through phrase cloud` |
| Wholly inside a phrase's value, as "Quill Cloud" | The phrase's line in `ascribe.toml` | How many places use it, and `ascribe refs phrase:cloud` |
| In a link with no text, which reads as its target's title | The link | `through upgrade.md's title` |
| In a snippet's code | The code file's line | `through code:examples/greet.ts#greet`, and the pages that show it, as for a fragment |
| In a page's title or description | The key's line in the page's frontmatter | The builds when not all |

A page that shows a place in fewer builds than the place is in names them after it, as `install.md #prerequisites (in build cloud)`. A code file outside the project's folder is named as the snippet's address names it, such as `code:greet.ts`. A snippet's code shown on more than three pages lists the first three and how many more. When the code a snippet shows can't be lined up with its file, as when a phrase's value has a line break, the place is the first line the snippet shows, and says `at the first line it shows`.

After a match wholly inside a phrase's value, the rest of the block is still searched: where `Sign in to {cloud}, then edit quill.yaml` is searched for `quill`, the phrase is one place and the line, at `quill.yaml`, is another.

`ascribe search "docs repository" --config examples/quill` finds a line of the page and a line of the fragment it includes:

```text
docs/install-agent.md:8: …agent watches your docs repository and syncs changes to Quill. This page covers…
docs/_fragments/prerequisites.md:4: A docs repository that Quill can read.
    on 1 page: install-agent.md #prerequisites

2 places match, on 1 page.
```

Tested with ascribe 0.4.4 on Linux.

`ascribe search '"upgrade the agent"' --config examples/quill` finds the page's title first, then the links that read as it:

```text
docs/upgrade.md:3: Upgrade the agent
    in builds site, site@3.4
docs/configure-legacy.md:8: …folder instead: see Upgrade the agent.
    through upgrade.md's title · in build site@3.4
docs/configure.md:9: …changed from 3.4: Upgrade the agent.
    through upgrade.md's title · in build site

3 places match, on 3 pages.
```

Tested with ascribe 0.4.4 on Linux.

What isn't searched: what no build publishes, the source's own spelling of a phrase, frontmatter fields other than the title and the description, the navigation's labels, text the content model writes (labels, availability lines, a widget's fallback), text in images, raw HTML, and what a site adds around a page.

`ascribe search key examples/quill/docs/keys.md --word`, from the repository's root, keeps to one page and to the whole word:

```text
docs/keys.md:7: Create a key
    #create-key
docs/keys.md:21: Generate a new key, put it in quill.yaml, and restart the agent. To install the…
    Rotate keys #rotate-keys
docs/keys.md:27: A key also signs the events the agent sends to your webhooks.
    Sign webhook events #sign-webhooks · in build site

3 places match, on 1 page.
```

Tested with ascribe 0.4.4 on Linux.

With `--summary`, the places aren't listed: the pages that show them are, with how many each shows, most first, then in path order, and the total. A place a fragment puts on several pages counts for each. It lists 20 pages, or as many as `--limit` says, then how many more and the command that lists them all. `ascribe search agent --config examples/quill --summary`:

```text
By page:
  14  docs/install-agent.md
   3  docs/configure-legacy.md
   3  docs/configure.md
   3  docs/keys.md
   2  docs/upgrade.md
   1  docs/quickstart.md
   1  docs/webhooks.md

27 places match, on 7 pages.
```

Tested with ascribe 0.4.4 on Linux.

When nothing matches, search looks for the query, by the same rules, in the source as written: every page's and fragment's text, and `ascribe.toml`'s, leaving out memos, which nothing reads. With paths or `--type`, it reads only the pages they keep to, the fragments those pages include, and `ascribe.toml`, so words other pages publish don't count. The source's text is read in blocks between blank lines, with its markup as written, so `{cloud}` is the phrase's key, not its value. The last line says which it is:

| The last line | When |
| --- | --- |
| "The words aren't in any page." | The source doesn't have them either. A word only in a memo is in no page. |
| "No build searched publishes them. They're written at `docs/x.md:12` and 2 more places." | The source has them, where no build searched publishes them, or where a phrase's value hides them. |
| "Builds not searched publish them: `site`, `self-managed-3.3`." | With `--build`, when a build that wasn't named has a place. |

`ascribe search "zebra" --config examples/quill`:

```text
No place matches.
The words aren't in any page.
```

Tested with ascribe 0.4.4 on Linux.

`ascribe search '"point the agent at your server"' --build cloud --config examples/quill`, words only the self-managed arm says:

```text
No place matches.
Builds not searched publish them: `site`, `site@3.4`.
```

Tested with ascribe 0.4.4 on Linux.

`ascribe search '"{cloud}"' --config examples/quill`, the source's spelling of a phrase:

```text
No place matches.
No build searched publishes them. They're written at `docs/install-agent.md:8` and 3 more places.
```

Tested with ascribe 0.4.4 on Linux.

When no block has every word, search lists the blocks that have the most of them, at most 10, as partial matches, each with the words it lacks; they replace "The words aren't in any page." A partial match is a pointer: read it before linking to it, and search again with other words, such as `sign` for "signature", since a word's other forms don't match. The exit code is still `1`. `ascribe search "webhook signature" --config examples/quill`:

```text
No place matches.
No block has both words. These have 1 of 2:
docs/webhooks.md:3: Webhooks
    in build site · lacks signature
docs/keys.md:23: Sign webhook events
    #sign-webhooks · in build site · lacks signature
docs/keys.md:27: A key also signs the events the agent sends to your webhooks.
    Sign webhook events #sign-webhooks · in build site · lacks signature
docs/upgrade.md:8: …the agent. 3.5 adds webhooks, and reads its settings from a new place: see Set…
    in builds site, site@3.4 · lacks signature
docs/webhooks.md:8: The agent posts an event to each webhook in quill.yaml. See API keys.
    in build site · lacks signature
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | Something matched |
| `1` | Nothing matched, partial matches aside |
| `2` | It couldn't run: no `ascribe.toml`, one with errors, an empty query, a quote that isn't closed, a path with no page or fragment of the project, or an unknown build or type |

With `--format json`, the document is a contract of its own, whose schema is in the [JSON report contract](https://ascribed-dev.com/docs/contracts/json-reports/questions.md#ascribe-search). `ascribe search '"sign in to quill cloud"' --config examples/quill --format json`:

```json
{
  "schema_version": 1,
  "ascribe_version": "0.4.4",
  "query": "\"sign in to quill cloud\"",
  "builds": [
    "site",
    "site@3.4",
    "cloud"
  ],
  "places": [
    {
      "file": "docs/install-agent.md",
      "line": 62,
      "column": 1,
      "end_line": null,
      "in": "text",
      "text": "Sign in to Quill Cloud and copy an API key from Settings → Keys, then add it to quill.yaml:",
      "match": [
        0,
        22
      ],
      "through": [
        {
          "phrase": "cloud"
        }
      ],
      "arm": "deployment=cloud",
      "pages": [
        {
          "page": "install-agent.md",
          "title": "Install the Quill agent",
          "heading": "Connect to Quill",
          "id": "connect",
          "builds": [
            "site",
            "site@3.4",
            "cloud"
          ]
        }
      ],
      "pages_total": 1,
      "refs_command": null
    },
    {
      "file": "docs/keys.md",
      "line": 10,
      "column": 1,
      "end_line": null,
      "in": "text",
      "text": "Sign in to Quill Cloud and open Settings → Keys.",
      "match": [
        0,
        22
      ],
      "through": [
        {
          "phrase": "cloud"
        }
      ],
      "arm": null,
      "pages": [
        {
          "page": "keys.md",
          "title": "API keys",
          "heading": "Create a key",
          "id": "create-key",
          "builds": [
            "site",
            "site@3.4",
            "cloud"
          ]
        }
      ],
      "pages_total": 1,
      "refs_command": null
    }
  ],
  "pages": null,
  "pages_matched": 2,
  "total": 2,
  "shown": 2,
  "truncated": false,
  "next_command": null,
  "source_places": [],
  "not_searched_builds": [],
  "stop_words": [],
  "partial_places": []
}
```

Tested with ascribe 0.4.4 on Linux.

Each place has its `file` from the project root, its `line`, and the `column` of the match's first character, in Unicode characters. A match of several words can span a whole code block, and `line` is the line its first word is on, within the block. The other fields:

- `in` is what it is: `title`, `description`, `heading`, `text`, `table`, `code`, or `phrase`, a phrase's value at its line in `ascribe.toml`.
- `text` is the block as a reader reads it: whole when it's at most 240 characters, and otherwise cut to about that many around the match's first character, at words, with `…` where it's cut, so a long code block or table row costs no more than a paragraph; `line` and `column` say where to read the rest.
- `match` is where the match is in `text`: its first character and the one after its last, in characters, ending where `text` is cut when the match runs past it.
- `through` lists what the match reads through that the line doesn't spell, in order: `{"phrase": key}`, `{"title": page}` for a link with no text, which reads as the title of the page it links to, or the text of the heading it names (`page.md#id`), and `{"code": address}` for a snippet's code.
- `arm` is the arm of a variant group the line is in, or null.

`pages` names the pages that show the place, at most three, each with its `title`, the `heading` the place is under there and its `id`, and the `builds` that show it there. `pages_total` is how many pages show it; for a phrase, how many places in the source use it. `refs_command` is the `ascribe refs` command that lists them: for a phrase, and for a fragment's line on more pages than `pages` names; otherwise it's null. A snippet's code that can't be lined up with its file is at the first line the snippet shows, and `end_line` is the last; every other place's `end_line` is null.

`total` is how many places match, `pages_matched` how many pages show one, and `shown` how many places are listed, 500 unless `--limit` says otherwise. When the list was cut, `truncated` is true and `next_command` is the command that lists them all. With `--summary`, `places` is empty and `pages` lists each page, from the project root, with how many `places` it shows, and `shown` and `truncated` count pages; without it, `pages` is null. When nothing matched, `source_places` is where the source writes the words, at most three places, each a `file` and `line`, when no build searched publishes them, and `not_searched_builds` is the builds that weren't searched that publish them; both are empty otherwise. `stop_words` is the common words a place needn't have. `partial_places` is the partial matches, each a place with how many words it `has` and those it `lacks`; `places` and `total` count only full matches.
