ascribe explain, model, outline, link, refs, rename, resolve, and search

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.

For AI agents: the documentation index is at llms.txt, and this page is available as Markdown.

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.

ascribe explain

Says what a diagnostic means: its severity, its messages, how to fix it, a link to its entry in Diagnostics, 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.
ascribe 0.4.4 on Linux
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/

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 has every field, and so does the schema of what --list writes:

ascribe 0.4.4 on Linux
{
  "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"
      }
    ]
  }
}

ascribe model

Shows the content model as ascribe reads it, with the defaults filled in: page types with their files and frontmatter fields, dimensions, 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, 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’ files and frontmatter.
    • dimensions: Dimensions and their values.
    • lifecycle: Lifecycle states.
    • phrases: Phrases, with their values.
    • features: Features, with their availability.
    • notes: Note types, with their labels.
    • glossary: Glossary terms.
    • images: Image attributes.
    • widgets: Project widgets, with their attributes.
    • releases: Release lines, 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.
ascribe 0.4.4 on Linux
# 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>`

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 as the sidebar shows it: a label with a code span in it is plain text, without the backticks. Its schema has each section’s fields. --section builds:

ascribe 0.4.4 on Linux
{
  "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
    }
  ]
}

A project with release lines 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. 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.
ascribe 0.4.4 on Linux
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>`.

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 about it, with its first line, and (outdated) when the words it quotes aren’t there any more. A detached 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:

ascribe 0.4.4 on Linux
{
  "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": []
    }
  ]
}

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/.

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.
ascribe 0.4.4 on Linux
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

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.

ascribe 0.4.4 on Linux
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)

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.

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:

ascribe 0.4.4 on Linux
{
  "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
    }
  ]
}

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.

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.
ascribe 0.4.4 on Linux
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`

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.

ascribe 0.4.4 on Linux
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.

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 from reporting it. An image nothing uses has no places, and exits with 0, as any target that exists does.

A memo file 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:

ascribe 0.4.4 on Linux
{
  "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"
}

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.
ascribe 0.4.4 on Linux
changed ascribe.toml (1 place)
changed docs/install-agent.md (4 places)
changed docs/keys.md (1 place)

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’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, 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 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’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.
  • 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
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 writes one. written is true, and false with --dry-run: the MCP server’s ascribe_rename gives the same document with written false, and writes nothing. Its schema 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.
ascribe 0.4.4 on Linux
# 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).

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, 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:

ascribe 0.4.4 on Linux
## 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>

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 says.

go: and a permalink’s name, in place of the page, shows the help a product shows 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:

ascribe 0.4.4 on Linux
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/

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 has every field:

ascribe 0.4.4 on Linux
{
  "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"
}

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 has every field:

ascribe 0.4.4 on Linux
{
  "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"
}

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.
ascribe 0.4.4 on Linux
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.

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:

ascribe 0.4.4 on Linux
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.

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

ascribe 0.4.4 on Linux
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.

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:

ascribe 0.4.4 on Linux
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.

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:

ascribe 0.4.4 on Linux
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.

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:

ascribe 0.4.4 on Linux
No place matches.
The words aren't in any page.

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

ascribe 0.4.4 on Linux
No place matches.
Builds not searched publish them: `site`, `site@3.4`.

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

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

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:

ascribe 0.4.4 on Linux
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
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. ascribe search '"sign in to quill cloud"' --config examples/quill --format json:

ascribe 0.4.4 on Linux
{
  "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": []
}

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.