Scope Rules
How scope propagates
Before each pass, the compiler walks the full AST and propagates scope top-down:
Scope propagation top-down function
function propagateScope(node, inherited) { const resolved = node.attributes?.scope ?? inherited; node.attributes.scope = resolved; for (const child of node.children ?? []) { propagateScope(child, resolved); }}A child tag inherits its parentβs scope unless it explicitly declares its own. Multi-scope is space-separated: scope="public agent". Scope functions live in core/scope.ts (re-exported via utils/markdoc.ts):
isScoped(node, targetScope)β generic check used byAstWalkervisitorsisPublicScoped(node)β shorthand forisScoped(node, 'public')isAgentScoped(node)β shorthand forisScoped(node, 'agent')
Scope values
| Value | Visible to |
|---|---|
public | Rendered Starlight documentation site |
agent | AI agent TOON bundles |
internal | Neither β compile-time only notes |
A node may carry multiple scopes: scope="public agent" makes content appear in both outputs.
Scope filtering checklist for processors
When writing render() or extract():
- Top-level gate: return
''(render) ornull(extract) if the primary tag node is missing or not scoped to the current pass - Child tag lists: filter with
isPublicScoped(n)before iterating β do not render agent-only child tags in the public pass collectβTextcalls: passtrueas the second argument to skip note and property prose from polluting descriptions- Silent early return is correct for an expected absence (e.g. an optional tag not present)
tracker.addRuntimeIssueis correct for an unexpected absence (e.g. a required tag missing from a doc type that guarantees it)