Documenting a Tag
What is a .tag.mdoc file?
Every StarSpec Markdoc tag has a corresponding reference document in instructions/tag/{tagname}.tag.mdoc. These files are compiled by the tag processor into both a public documentation page (under /starspec/tag/{tagname}) and an agent bundle. They are the single source of truth for a tag’s schema, parent/child relationships, naming rules, and usage examples.
File structure
A tag document has three layers: frontmatter, a root {% tag %} block, and an optional prose appendix outside the root tag.
1. Frontmatter
---type: tagid: action # bare tag name — must match the tag's id attributetitle: "{% action %}" # display title in delimitersstatus: ready # draft | readytags: []context: []---typeis alwaystag.idmatches the tag name exactly (e.g.action,flow,requirement).titleuses the{% name %}convention so it renders recognisably in the sidebar and search.
2. Root {% tag %} block
The {% tag %} root declares the tag’s identity, parentage, and allowed children:
\{% tag id="action" parents="api" children="property[*] explanation[*]" %}
\{% property name="name" type="string" required=true description="..." /%}
Free-form prose describing the tag's purpose and behaviour.
\{% example id="..." summary="..." tags="..." %} ... \{% /example %}
\{% rule id="..." force="must" tags="..." %} ... \{% /rule %}
\{% /tag %}id — the tag name
The bare tag name as used in Markdoc source files (e.g. action, step, policy).
parents — where this tag may appear
Space-separated list of parent tag names. An empty string means the tag is a root document tag (top-level in a .{type}.mdoc file). Examples:
parents value | Meaning |
|---|---|
"" (empty) | Root document tag — appears at top level |
"api" | Only valid inside \{% api %} |
"blueprint domain manifest" | Valid inside any of those three |
children — what may appear inside
Space-separated child declarations using multiplicity notation:
| Notation | Meaning |
|---|---|
name[*] | Zero or more |
name[+] | One or more |
name[?] | Zero or one |
name[N] | Exactly N (e.g. tldr[1]) |
Example: children="tldr[1] explanation[*] property[*] rule[*] example[*]"
3. Inside the root {% tag %} block
The following children are recognised and rendered into the public page:
| Child | Purpose |
|---|---|
\{% property %} | Declares one attribute the tag accepts. Rendered as an Attributes table. |
\{% rule %} | Declares a naming convention or structural constraint. Rendered as a Rules table. |
\{% example %} | A Markdoc usage example with summary. Rendered as an example card. |
\{% explanation %} | Hint or detail block. Rendered near the top of the page. |
\{% tldr %} | One-line summary used in sidebar, search, and agent context. Place before \{% tag %}. |
| Free prose | Paragraphs, headings, code fences — rendered as the tag’s main documentation body. |
4. Optional prose appendix
Content placed after \{% /tag %} (outside the root block) is still rendered on the public page. Use this for supplementary reference tables (e.g. child tag quick-reference) that don’t belong inside the structured \{% tag %} block.
The {% property %} tag
Each attribute the tag accepts is declared with a self-closing \{% property %}:
\{% property name="id" type="string" required=true description="Unique kebab-case identifier" /%}| Attribute | Required | Description |
|---|---|---|
name | yes | Attribute name as written in Markdoc |
type | yes | Type label — string, boolean, enum, or a descriptive union like hint | details |
required | no | true or false (default: false) |
description | no | One-line description shown in the Attributes table |
Convention: list required=true properties first, then optional ones.
The {% rule %} tag
Rules declare constraints that the compiler validates or that authors must follow:
\{% rule id="action-names-kebab-case" force="must" tags="convention naming" %}Action names use plain kebab-case — no type prefix.\{% /rule %}| Attribute | Required | Description |
|---|---|---|
id | yes | Kebab-case rule id, unique within this document |
force | yes | must or should — constraint strength |
ref | no | Qualified reference to a motivating spec item (e.g. manifest/product#response-time) |
realm | no | global | spec | impl — scope of the rule |
tags | no | Space-separated labels for filtering and discovery |
Use force="must" for rules enforced by the compiler. Use force="should" for conventions enforced by review.
Rule id convention: {tagname}-{constraint} in kebab-case — no -tag- segment (unlike example ids). E.g. asset-no-autoplay, article-scope-public-only, cite-key-must-resolve.
The {% example %} tag
Examples show the tag in use within a realistic Markdoc snippet:
\{% example id="action-tag-with-properties" summary="Action with property children" tags="tag action" %}\`\`\`markdoc\{% action name="add-bookmark" %}Saves a new bookmark to the user's library.\{% property name="url" type="string" required=true /%}\{% /action %}\`\`\`\{% /example %}Conventions:
- Use a
markdoclanguage fence for the code block. - Include at least one realistic example per tag document.
- Example
id: use{tagname}-tag-{scenario}— the-tag-segment is required to prevent collisions with examples in domain and feature documents (e.g.action-tag-with-properties,domain-tag-basic,carousel-tag-autoplay). - Example
tags: always start with"tag"as the first token, then the tag name(s) the example demonstrates (e.g.tags="tag action property",tags="tag carousel slide article"). This makes tag-doc examples filterable as a group in the Examples Gallery.
Agent bundle shape (optional section)
For tags whose extracted data shape is not obvious from the attributes alone, include an “Agent bundle shape” section showing the JSON structure that appears in the TOON bundle. Place this inside the \{% tag %} block, after examples and rules:
## Agent bundle shape
\`\`\`json{ "explanations": [ { "type": "hint", "audience": "backend", "body": "..." } ]}\`\`\`This is especially valuable for container tags that collect children into arrays.
Article-type tags: {% article %} and its child tags ({% quote %}, {% atom %}, {% asset %}, etc.) produce no agent bundle — the agent pass skips all article documents entirely. Do not add an “Agent bundle shape” section to these tag docs. Instead, document the scope="public" restriction as a {% rule %} so the constraint is discoverable.
Rendered output
The tag processor renders the following sections on the public page (in order):
- TLDR — summary quote
- Explanations — any hint or detail blocks
- Attributes — table from
\{% property %}children - Valid Children — table from the
childrenattribute, linked to each child’s tag doc page - Valid Parent Contexts — links from the
parentsattribute - Rules — table from
\{% rule %}children - Free prose — paragraphs and headings from the body
- Examples — rendered example cards
- Tag Tree — interactive tree view (only for root tags with empty
parents)
Checklist for writing a new tag document
- Create
instructions/tag/{tagname}.tag.mdoc - Add frontmatter with
type: tag,id: {tagname},title: "{% {tagname} %}" - Write a
\{% tldr %}before the root\{% tag %} - Add the
\{% tag %}root with correctid,parents, andchildren - Declare every attribute with
\{% property %}— required attributes first - Write prose explaining the tag’s purpose and key semantics
- Add at least one
\{% example %}with a realisticmarkdocfence - Add
\{% rule %}entries for any naming conventions or structural constraints the compiler validates - If the agent bundle shape is non-obvious, add an “Agent bundle shape” section
- Update
instructions/fragments/tags/inventory.mdoc— add the tag to the appropriate table - Run
pnpm compile— verify the tag doc renders at/starspec/tag/{tagname}