Noma Format Specification
This document defines the Noma plain-text document format at version 0.13.0. It covers the file model, lexical rules, block syntax, attribute grammar, AST shape, and renderer contracts. It is intentionally small.
Conventions
- All Noma files use UTF-8 encoding and the
.nomaextension. - Lines are terminated by
\n. Parsers must accept\r\nand normalize on read. - Whitespace at the start and end of lines inside text content is preserved; whitespace around block fences is not significant.
File structure
A Noma file consists of an optional frontmatter section followed by a body.
---
key: value
---
(body)
The frontmatter is YAML. It is exposed on the document AST as meta. If the opening --- line is missing, the file has no frontmatter.
Block syntax
Headings
# Heading 1
## Heading 2
### Heading 3
Headings auto-create section nodes. Each section has an id derived by slugifying the title. Sections nest by level: # A is a parent of ## B and ## B' until the next # C.
Heading attributes (v0.4). A heading line may end with a {...} attribute block to override the auto-slug or attach aliases:
## Risks {id="rp3-risks" aliases="risks,rp3-risk-list"}
id="..." replaces the slugified default. aliases="comma,or,space,separated" registers extra IDs that resolve to the same section — the validator accepts either, the HTML renderer emits a hidden <a id="alias"> anchor before the heading so wikilinks and URL fragments work.
Inline content
Paragraphs, headings, table cells, and review/note bodies keep lightweight inline Markdown as source text: **bold**, *emphasis*, _emphasis_, ` code , label, and id wikilinks. Link labels may escape literal brackets, backslashes, and table pipes as \[, \], \\, and |; HTML, Markdown, LLM, and DOCX renderers unescape those label characters while preserving the link target. Link targets stay inline and cannot contain whitespace or )` in source form; DOCX return readers percent-encode whitespace and parentheses from native Word relationship targets before emitting Markdown links.
Directive blocks
The core construct in Noma is the directive block. A directive opens with two or more colons followed by a name and an optional attribute list, and closes with a matching colon run on its own line.
::name{attrs}
content
::
Directive names may be namespaced for future community packs:
::finance::position{id="holding-asml"}
...
::
Without a registered pack renderer, HTML/PDF, Markdown, and DOCX output keep unknown or namespaced directives visible as readable fallback panels or labels. The fallback uses a human label (Finance position, Custom directive), preserves the body, and exposes non-structural attributes such as asset_class=, region=, or noverify as metadata instead of hiding the directive identity in an anonymous wrapper. DOCX fallback panels are bookmarked and framed, so accepted Word edits to body-only fallback content can return through replace_body.
Children are written with strictly more colons than the parent:
::grid{columns=2}
:::card{title="Bull"}
text
:::
:::card{title="Bear"}
text
:::
::
A ::: opener inside a :: parent creates a child block. The closer must use the same colon count as its opener.
Paragraphs and inline markup
Plain text outside any directive becomes a paragraph node. Inline markup is a small subset of Markdown: **bold**, *em*, ` code , label, and the Noma extension block-id for cross-references. DOCX renders wikilinks to captioned figures, tables, plots, computed plots, and computed tables as Word REF fields so the visible cross-reference can update with the numbered caption; DOCX review extraction maps generated complex or fldSimple REF fields back to block-id`.
Code, lists, quotes, rules
Standard Markdown: triple-backtick fenced code, - bulleted lists, 1. ordered lists, > block quotes, --- thematic breaks.
Tables
GitHub-style pipe tables. The parser detects a row of pipe-separated cells immediately followed by a separator row.
| Column A | Column B | Column C |
| :------- | :------: | -------: |
| left | center | right |
| **bold** | `code` | [link](#)|
The separator row sets per-column alignment: :--- left, :---: center, ---: right, --- default. Inline markdown inside cells is preserved — bold, italic, code, and links all render. Use \| for a literal pipe inside a non-code cell; renderers show the pipe outside code spans while preserving the source escape. Pipes inside inline code spans do not split cells and do not need escaping, so serializers preserve ` x|y ` as code text while escaping separator-like pipes elsewhere.
The HTML renderer emits a real <table class="noma-table"> with text-align styles per cell. The Markdown and LLM renderers keep the pipe format aligned to column widths so output stays readable in sharing and agent contexts.
For tables where pipe-syntax becomes ugly — single-character markers (✓, —) next to long prose cells force visible padding to keep the separator row valid — wrap the rows in a ::table directive. The separator row is no longer required, alignment is declared via an attribute, and the source stays compact:
::table{header align="-,c,r"}
| Vertical | Status | Score |
| Legal | ✓ | 3.4 |
| Healthcare | — | 2.9 |
::
align is a comma-separated list of column codes: l left, c center, r right, - default. Add the header flag to treat the first row as the header (omit it for body-only tables). Add title= or caption= when the table needs a visible Word handoff label; DOCX renders that as a Word-caption-style Table label with a SEQ Table field and keeps the table's bookmark on the label.
For source hygiene, run noma fmt <file> (--inplace to rewrite). It rebuilds GitHub-style pipe tables to a single column width and leaves everything else byte-identical, including tables inside fenced code blocks.
Attribute grammar
Attribute lists appear after a directive name in curly braces:
{key="quoted value" key=bare key=0.82 flag}
| Form | Type | Example |
|---|---|---|
key="text" | string | id="claim-1" |
key=word | string | tone=warning |
key=42 | number | columns=3 |
key=0.82 | number | confidence=0.82 |
flag | boolean | done |
key=true|false | boolean | pinned=true |
Attribute values are plain text. Inline markup (**bold**, *em*, ` code , link, id`) inside an attribute value is not parsed — it lands in the rendered output as a literal string. If you need rich content for a card title, evidence note, or callout heading, put it in the block body or as a child paragraph, not in an attribute. This keeps the attribute grammar trivially parseable and the AST shape predictable.
The attribute named id is special: it is promoted to the AST node's stable identifier and used by validators and the Noma Agent Protocol v1.0 RFC.
The attribute named variant is a theme hook. It lands as data-variant="..." on the rendered element so themes can style the same block class with different emphasis (important, subtle, success, danger, info are recognised by the bundled themes; custom values pass through). Use variant instead of inline styling to keep source readable. Example:
::card{title="Bull case" variant="success"}
EUV demand structurally high.
::
Core block types
Document blocks
section, paragraph, list, list_item, quote, code, thematic_break
Layout blocks
hero, grid, card, callout, columns, tabs, accordion, sidebar, button, page_setup, header, footer, toc, pagebreak
::page_setup{size="Letter|A4|Legal" orientation="portrait|landscape" margin="12mm"} controls print geometry. DOCX uses the first page setup as the initial section geometry; later ::page_setup blocks emit native Word section breaks and subsequent Word table columns, dataset tables, and grid/columns layouts size from the active page width and margins. HTML emits an @page rule for browser printing. Per-side margins use margin_top, margin_right, margin_bottom, and margin_left; lengths accept in, mm, cm, and pt.
::grid{columns=N} and ::columns{columns=N} preserve multi-column structure in DOCX by exporting fixed-width Word tables. HTML uses responsive layout styling; LLM output keeps the nested block order. For wider HTML artifacts, add wide, full, width="wide|full", or span="wide|full" to let the layout break out beyond the prose measure. Add min=, min_width=, minWidth=, min-width=, or minColumnWidth= to switch to auto-fit tracks with a minimum column width, and gap=, compact, or dense to tune spacing. HTML length attrs accept safe CSS lengths such as 220px, 14rem, 32ch, 50vw, or 100%; invalid lengths are ignored.
::card{title="..." variant="..." icon="..."} renders as a framed Word panel in DOCX. Titled cards use the title as the panel label; titleless cards use Card; variant controls Word shading and icon / variant stay visible as metadata.
::abstract, ::callout{tone="..."}, ::note, ::warning, and ::tip render as readable Word blocks in DOCX. note, warning, and tip are shorthand callout tones; explicit ::callout{tone=...} uses the same natural tone labels. Word exports use labels such as Note, Warning, and Tip plus tone-specific shading instead of raw directive names.
::hero, ::tabs, and ::accordion are interactive or web-layout containers in HTML. DOCX flattens them into their child content so Word handoffs do not show raw layout labels. :::tab{title="..."} renders as a titled Word panel, and ::sidebar{title="..."} renders as a framed Word aside.
::button{href="..."} renders as a visible call-to-action in HTML. DOCX renders it as a Word hyperlink when href= is an internal anchor or external URL, otherwise as a readable action label.
::header and ::footer define document chrome for handoff targets. DOCX emits native Word header/footer parts, preserves inline formatting, wikilinks, and external hyperlinks with part-local relationship files, and ::footer{page_numbers total_pages} inserts Word PAGE and NUMPAGES fields. HTML/PDF render visible header/footer bands as a fallback.
::toc{title="Contents" depth=3} emits a generated table of contents from document headings. HTML/PDF render a linked navigation list; DOCX renders linked entries that target the same Word bookmarks used by wikilinks, adds PAGEREF page-number fields for each entry, and writes Word settings that refresh fields when the document opens. Use ::toc{of="figures|tables|plots"} to generate linked caption lists instead of heading entries; DOCX uses the same bookmark and page-reference machinery for those caption lists.
::pagebreak inserts a hard page boundary in print-oriented targets. HTML emits a screen-visible dashed marker that becomes break-after: page in print CSS; DOCX emits a native Word page break.
Markdown export
noma render file.noma --to markdown (or --to md) renders a portable Markdown artifact for GitHub, Slack, email, Notion-style imports, and lightweight agent handoffs. Noma remains the source of truth; Markdown export is intentionally lossy for rich layout and computed artifacts.
The Markdown renderer preserves frontmatter, headings, paragraphs, inline Markdown, fenced code, lists, quotes, thematic breaks, and GitHub-style pipe tables. It converts [[id]] wikilinks to [id](#id), emits hidden <a id="..."></a> anchors for canonical IDs and aliases, renders ::agent_task / ::todo as task-list checkboxes, ::figure as , callouts as GitHub-style admonitions, and ::table{header ...} directives as Markdown pipe tables. Other directives degrade to readable labels plus metadata lines; by default each directive is wrapped in hidden <!-- noma:block ... --> comments carrying directive name, ID, and attributes so exported Markdown retains stable agent context without showing those details in rendered Markdown views. Raw ::html, ::svg, and ::script escape-hatch bodies are replaced by safe placeholders unless the API caller explicitly opts into including them.
Artifact-action blocks
export_button, control, computed_metric, computed_plot, computed_table
::export_button{format="prompt|markdown|json|llm" target="..."} renders as a real <button> with format-keyed colors. The body (or Label: line) becomes the button label. Agents and end-users use these to round-trip artifact state back into a prompt or to download the underlying source. DOCX renders it as an explicit export action with format metadata and a bookmark link to target when possible.
::control{type="slider|number|text|date|select|toggle" min=... max=... default=...} renders a labeled input. The HTML renderer maps type="slider" to a range input, type="date" to a date input, type="select" to a <select>, and type="toggle" to a checkbox-style input. Select options use options="1=Base,1.2=Upside"; the left side is the submitted value and the right side is the label. Omitted type= defaults to text. DOCX renders text/number/slider defaults as native editable text content controls, date as a native Word date-picker content control, select as a native Word dropdown list, and toggle / checkbox as native Word checkbox controls, while keeping type/default/range metadata readable beside the field. Date controls accept optional date_format= and locale= attributes for the Word content-control metadata. Add lock="control" (or the locked flag) when Word reviewers should not delete the content-control field itself; lock="content" locks the value, lock="all" locks field and value, and lock="unlocked" / lock="none" leaves it explicitly unlocked. ID-bearing controls also bind to a DOCX custom XML part (urn:noma:controls) through native w:dataBinding, so Word form handoffs carry a structured value layer behind the visible content controls. The DOCX data/sync return path reads visible control and task content controls from the document body, headers, and footers, which lets form fields embedded in document chrome return to source.
::doc_protection{edit="forms"} is a document-wide DOCX setting for form handoffs. Word output writes native w:documentProtection metadata in word/settings.xml; edit= / mode= accepts forms (default), readOnly, comments, or trackedChanges, and enforcement= defaults to true. This is a Word editing-mode hint, not a security boundary or password-protection feature.
::computed_metric{formula="..."}, ::computed_plot{formula="..." domain="year:0..10"}, and ::computed_table{formula="..." domain="year:0..10"} are the declarative computation blocks for that same interactive direction. They intentionally stay inside the generic directive AST node. formula: and domain: body lines are accepted when attributes would be awkward. The validator parses formulas, checks referenced ::control / computed IDs, catches invalid formulas, enforces numeric defaults on numeric control types, and warns when computed chains get deeper than two levels. Toggle defaults count as 1 / 0, and select controls participate in formulas when their option values are numeric. Standalone HTML embeds a small local runtime that recalculates computed metrics, simple-domain computed plots, and simple-domain computed tables when controls change; the current control state is encoded as a #noma: URL hash so scenarios can be shared. PDF output keeps the current static rendered artifact path. DOCX evaluates computed metrics against control defaults, embeds simple-domain computed plots as static SVG chart media, and exports simple-domain computed tables as native Word tables with formula/domain metadata visible. The LLM renderer emits each computed formula plus a default scalar result, short default series, or default table row series evaluated from current control defaults.
Formula syntax is numeric only: +, -, *, /, ^, comparisons (>, >=, <, <=, ==, !=), parenthesized expressions, hyphenated control IDs such as growth-rate, and the allow-listed functions pow, min, max, clamp, round, abs, and if. Unknown functions are validation errors, not runtime surprises.
Technical documentation
api, endpoint, parameter, example, changelog, instruction, query
Technical documentation blocks render as structured panels in HTML/PDF and DOCX instead of generic directive labels. ::api preserves title/version/base URL/status metadata; ::endpoint preserves method/path/auth/API links; ::parameter preserves location/type/required/default metadata; ::example{lang=...} and ::query{lang=...} keep their bodies in monospace code blocks; ::instruction preserves scope/audience/priority; and ::changelog preserves version/date/status metadata.
Research and reasoning
claim, evidence, counterevidence, assumption, risk, hypothesis, result, limitation, open_question, decision, adr, state_change
In HTML/PDF and DOCX output, reasoning blocks render as readable review blocks with key metadata preserved below the body. ::evidence and ::counterevidence keep for=, source=, url=, doi=, and accessed=; ::risk keeps severity=, owner=, and status=; ::decision / ::adr keep status=, owner=, and decision dates; ::open_question keeps status=, owner=, and due dates; and ::assumption, ::hypothesis, ::result, and ::limitation keep status=, owner=, confidence=, and source=. HTML/PDF output keeps references as anchors or external links; Word exports point references at bookmarks when the target ID exists and become external hyperlinks for URLs/DOIs.
::state_change{block="<id>" attribute="<name>" from=<old> to=<new> reason="..." at="<iso-date>"} records a typed delta against another block — useful for weekly/quarterly recap docs that need to express "value was X, now Y". The HTML and DOCX renderers show it as a readable old → new delta, while the LLM renderer keeps the structured fields. Validator: block= must point to an existing id; both from= and to= are required.
Data and computation
dataset, plot, metric, figure, code, code_cell, output
::dataset bodies can be YAML, CSV/TSV, or JSON. CSV/TSV rows support single-line double-quoted cells with doubled quotes ("North, America", "APAC ""Strategic""") when a cell needs the delimiter or quote character. HTML exposes the source in a collapsible details block, plots can query it by dataset=, and DOCX renders resolvable datasets as native Word tables with inferred headers. noma docx-review-sync can update inline dataset bodies from edited Word tables when the dataset table can be matched and represented in the source format; simple same-shape edits to inline YAML row arrays, CSV/TSV bodies, and pretty-printed JSON row or record arrays use update_dataset_cell, while simple row/column insert/delete edits use insert_dataset_row, delete_dataset_row, insert_dataset_column, or delete_dataset_column so surrounding schema text, comments, and unrelated rows stay byte-preserved. File-backed datasets are inlined by the CLI loader before renderers run.
::metric{label="..." value=... unit="..."} represents a KPI or scalar data point. Optional status=, trend=, change= / delta=, target=, source=, and as_of= attributes keep review context attached to the number. HTML/PDF and DOCX render metrics as readable KPI blocks with the value/unit separated from metadata and with source= linked to a bookmark or external URL when possible.
::code{id="..." lang="..."} is an addressable code or prompt snippet. It keeps the source patchable by block ID while HTML/PDF and DOCX render the body as a labeled monospace code block with the same bookmark/anchor used by wikilinks and comments.
::code_cell{lang="..." kernel="..."} and ::output{for="..." type="..."} represent computation source and its captured result. HTML/PDF and DOCX render both as technical handoff blocks: code-cell bodies and output bodies stay in monospace code paragraphs, kernel=, status=, and execution_count= metadata stays visible, and output for= links back to the source cell anchor/bookmark when possible.
::figure{src="..." alt="..." caption="..." width="..."} renders an image with optional caption. HTML emits <figure><img ...>. DOCX embeds PNG/JPEG/GIF/SVG images when the src= is a data URI or when the CLI can resolve a local file path; the renderer itself only consumes already-inlined image data so it remains pure. Embedded DOCX figures place a Word-caption-style Figure caption after the media, include a SEQ Figure field for numbering, and attach the block bookmark to that caption. width= / height= accept the same length units as page setup.
Agent collaboration
agent_task, todo, review, comment, change_request, provenance, confidence, citation, footnote, endnote, bibliography
In DOCX output, ::comment{parent="<id>" author="..." date="..."} becomes a native Word comment. HTML/PDF render the same block as a visible review-note panel. The block body becomes the comment text, preserving inline bold/emphasis/code, internal wikilinks, and external hyperlinks inside the native comments.xml part. author= / initials= / date= become review metadata, and parent=, for=, or target= anchors/links the comment to that block when the target exists. Use reply_to="<comment-id>" when a comment should reply to another comment; DOCX records the thread relationship in the Word commentsExtended part, while HTML/PDF still show the reply as a review-note panel. Replies whose parent is missing, deleted, or withdrawn are not exported as orphan standalone Word comments and do not force review-view settings. The validator checks reply_to= as an ID reference. Untargeted comments still render at their source position. Agents mark closed review notes with status="resolved" plus optional resolved_by= / resolved_at= metadata, usually through the resolve_comment patch op; DOCX output includes that resolution metadata at the top of the exported comment and records the resolved state in commentsExtended, while HTML/PDF show it in the panel metadata. status="deleted" / "withdrawn" keeps the source audit trail but suppresses native DOCX comment export so deleted review notes do not reappear in Word. Empty comment bodies may render as a readable Comment placeholder in DOCX, but review sync treats that placeholder as render-time fallback rather than accepted source text. If Word Track Changes markup appears inside returned native comment text, review sync reports the comment as skipped instead of accepting the current text as a body edit, while native comment resolution state can still return to source.
In DOCX output, ::agent_task and ::todo become native Word checkbox content controls plus scope=, owner=, due=, and priority= metadata when present. noma docx-data extracts those checkbox states, and noma docx-sync can apply them back to source done / status attributes.
::review, ::provenance, and ::confidence become readable collaboration metadata blocks in HTML/PDF and DOCX. for=, target=, or block= links back to the reviewed block anchor/bookmark when possible. ::review keeps status/reviewer/due/date metadata; ::provenance keeps source/url/tool/by/commit/time metadata; ::confidence keeps value/basis/source/update metadata.
::change_request{target="<id>" action="insert|delete|replace" from="..." to="..." author="..." date="..."} records a proposed edit. DOCX renders insert, delete, and replace actions as native Word tracked-change runs, preserving inline bold, emphasis, code, internal wikilinks, and external hyperlinks inside the revision text. When target= resolves and the request has valid revision text, Word output also marks the reviewed block with a native comment range and places the tracked revision block beside that target; unresolved targets or malformed tracked revisions still render at the change request's source position. Malformed DOCX fallbacks keep visible action, target, revision-text, author, and date metadata without creating native Word review parts, and review sync does not treat them as missing native tracked-revision siblings. DOCX files with native comments or valid change-request revisions include Word review-view settings so comments and insert/delete revisions are advertised as review markup. status="deleted" / "withdrawn" keeps the source audit trail but suppresses native DOCX tracked-revision export so closed proposals do not reappear in Word. HTML/PDF render matching <ins> / <del> deltas. replace requires both from= and to=. insert uses to=, text=, or the block body; delete uses from=, text=, or the block body.
In DOCX output, ::footnote{label="..."} becomes a native Word footnote. The block body becomes the footnote text, preserving inline bold/emphasis/code, internal wikilinks, and external hyperlinks inside the native footnotes.xml part. for=, parent=, target=, block=, or ref= anchors the superscript reference to that block when the target exists; unresolved targets still render the footnote at its source position. The validator treats those target attributes as ID references. status="deleted" / "withdrawn" keeps the source audit trail but suppresses native DOCX footnote export so deleted notes do not reappear in Word. HTML/PDF output keeps a styled note fallback so the content remains visible outside Word. Empty note bodies may render as readable Footnote / Endnote placeholders in DOCX, but review sync treats those placeholders as render-time fallback rather than accepted source text.
::endnote has the same source shape, target attributes, and deleted/withdrawn DOCX suppression behavior as ::footnote, but DOCX output writes a native endnotes.xml part and emits w:endnoteReference runs. Use it when notes should collect at the end of the Word document instead of appearing as page footnotes. HTML/PDF output renders a matching styled note fallback.
::bibliography{title="References"} emits a generated references section from every ::citation block in the document. HTML/PDF render an ordered list; DOCX renders a numbered Word list with clickable URL/DOI entries where present.
Math
math
::math{display="block|inline" id="..."} carries a LaTeX expression. The HTML renderer wraps the body in \[...\] (display) or \(...\) (inline) and ships KaTeX assets from CDN automatically when the document contains math, unless strict rendering disables external assets. DOCX renders block math and prose/table-cell inline math ($...$, $$...$$, \(...\), \[...\]) as Office Math containers with the linear source text preserved for Word review. The LLM renderer passes the LaTeX source through untouched so structural addressability survives.
::math{id="vol-target"}
w_{i,t} = \frac{\sigma_\text{target}}{\hat\sigma_{i,t-1}}
::
Force-disable assets with --math=none (or meta.math: false); force-enable with --math=katex.
Escape hatches
html, svg, script
Allowed but discouraged. The validator warns on every untrusted use. Add the trusted flag attribute to silence the warning on a single block; pass --no-unsafe to the CLI (or set allowEscapeHatches: false on the API) to block escape hatches entirely. Pass --strict to also omit external CDN runtimes for math, diagrams, and Plotly and to render computed controls as inert static defaults with disabled inputs and no generated inline runtime. Book manifests with trusted_publishing: true apply that same strict static HTML posture to every manifest-driven HTML/site/PDF render. The LLM renderer always strips escape-hatch bodies and replaces them with a placeholder so agent context stays predictable.
::svg{trusted}
<svg width="64" height="64" viewBox="0 0 64 64">
<circle cx="32" cy="32" r="30" fill="#b9522a" />
</svg>
::
Delimiter rule. Commas are canonical for both data and xlabels. Whitespace separators are still accepted for backward compatibility, but mixing styles between data and xlabels in the same plot triggers the plot-mixed-delimiters warning.
Dataset linkage. A ::plot may pull its data out of a sibling ::dataset instead of duplicating numbers inline. Use a dataset="<id>" attribute on the plot together with column="<name>" to select the y-series and optionally xcolumn="<name>" for categorical labels. If column is omitted, the renderer picks the first numeric column from the dataset schema.
Plot label controls. Dense categorical charts may opt into explicit x-axis label handling:
::plot{id="strategy-bars" type="bar" data="1,2,3" xlabels="crypto_long_short_carry,seasonality_eq_bond_reversion,vol_risk_premium" xlabel_angle=45 xlabel_wrap=12 xlabel_abbrev=18 compact}
::
xlabel_angle=N rotates x-axis labels by N degrees upward, xlabel_wrap=N wraps long labels into SVG <tspan> lines, xlabel_abbrev=N truncates display labels while preserving the full label in an SVG <title>, and compact uses tighter plot height/styling for report pages. height= and width= remain available for direct SVG sizing.
The validator emits plot-unknown-dataset (error) when the referenced dataset id does not exist and plot-unknown-column (error) when the column is not declared in the dataset's schema. See examples/research-thesis.noma for the full pattern.
Agent context selection
noma render <file> --to llm emits deterministic plain text for model context. Use --select and --exclude to scope that context by AST node type (section, paragraph, table, code, ...) or directive name (claim, evidence, risk, dataset, ...). Selection keeps ancestor sections so the extracted blocks retain document context; exclusion prunes the matched block and its children.
noma render report.noma --to llm --select claim,evidence,risk
noma render report.noma --to llm --exclude dataset,plot --budget 12000
--budget is a maximum character count. When output exceeds the budget, the renderer trims at a line boundary when possible and appends a truncation marker.
ID registry
noma ids <file.noma|book.yml> prints a JSON registry for agent discovery:
{
"ids": ["spec", "claim-1"],
"aliases": { "intro": "spec" },
"records": [
{ "id": "spec", "type": "section", "title": "Spec", "aliases": ["intro"], "line": 1 },
{ "id": "claim-1", "type": "directive", "name": "claim", "line": 3 }
]
}
For book manifests, chapter loading applies the same book-scoped IDs and aliases used by rendering and validation, so the registry is global across chapters.
Patch transactions
noma patch --ops patch.json accepts three payload shapes:
{ "op": "update_attribute", "id": "claim-1", "key": "confidence", "value": 0.9 }
[
{ "op": "update_attribute", "id": "claim-1", "key": "confidence", "value": 0.9 },
{ "op": "add_block", "parent": "evidence", "content": "::evidence{id=\"ev-2\" for=\"claim-1\"}\n...\n::" }
]
{
"ops": [
{ "op": "update_attribute", "id": "claim-1", "key": "confidence", "value": 0.9 }
],
"prevalidate": true,
"postvalidate": true
}
The CLI writes only after every operation has applied to an in-memory candidate. When prevalidate is true, existing validation errors block the transaction before patching. When postvalidate is true, validation errors in the patched candidate block the write, leaving the source file unchanged.
Patch schemas
The CLI ships machine-readable JSON Schemas for tools that want to validate patches, AST JSON, transcript records, and capability sidecars before calling Noma:
noma schema patch-op
noma schema patch-transaction
noma schema ast
noma schema transcript
noma schema capability
The shipped patch operation set is:
| Operation | Target | Purpose |
|---|---|---|
replace_block | directive block | Replace an entire semantic block. |
replace_body | body-only directive/text node | Replace body text while preserving wrapper. |
update_heading | section | Change heading title while preserving ID. |
add_comment | ID-bearing block | Add a targeted or threaded ::comment. |
resolve_comment | ::comment | Mark a review comment resolved. |
add_footnote | ID-bearing block | Add a targeted ::footnote after a block. |
add_endnote | ID-bearing block | Add a targeted ::endnote after a block. |
add_change_request | ID-bearing block | Add a targeted tracked change request. |
update_table_cell | ::table directive | Update one body cell by row and column. |
update_table_header_cell | ::table directive | Update one header cell by column. |
insert_table_row | ::table directive | Insert one body row by row index. |
delete_table_row | ::table directive | Delete one body row by row index. |
insert_table_column | ::table directive | Insert one column by column index. |
delete_table_column | ::table directive | Delete one column by index or header label. |
update_dataset_cell | ::dataset directive | Update one dataset body cell by row/column. |
insert_dataset_row | ::dataset directive | Insert one dataset data row by row index. |
delete_dataset_row | ::dataset directive | Delete one dataset data row by row index. |
insert_dataset_column | ::dataset directive | Insert one dataset column by column index. |
delete_dataset_column | ::dataset directive | Delete one dataset column by index or label. |
move_block | directive block | Move an existing directive under a parent. |
add_block | document/section/directive | Insert a directive child. |
delete_block | ID-bearing block | Remove a block. |
update_attribute | directive | Change one attribute except id. |
remove_attribute | directive | Remove one attribute except id. |
rename_id | ID-bearing block | Rename canonical ID and retarget references. |
update_heading preserves stable agent handles: when the new title would produce a different auto-slug, the source-preserving patcher adds an explicit {id="old-id"} heading attribute.
add_change_request targets an existing ID-bearing block and inserts a ::change_request near it. action is insert, delete, or replace; replace requires from and to, insert requires to or text, and delete requires from or text. Optional content becomes the rationale body rendered after the tracked revision. DOCX output anchors the generated tracked revision beside the target block and marks that block with a native Word comment range.
add_footnote and add_endnote target an existing ID-bearing block and insert ::footnote{for="<id>"} or ::endnote{for="<id>"} near that block. content becomes the note body and optional label becomes the visible source fallback label when the note renders outside native Word note parts.
update_table_cell targets ID-bearing ::table directives. row is zero-based and counts body rows; when the table has header, column may be the header label, otherwise it must be a zero-based column index. Table patch serializers escape literal separator pipes outside inline code spans and preserve pipes inside code spans. update_table_header_cell targets the header row itself and updates one header cell by zero-based column index or existing header label.
insert_table_row and delete_table_row also target ID-bearing ::table directives. row is zero-based over body rows; insert allows row equal to the current body-row count to append. cells is an array of single-line strings.
insert_table_column and delete_table_column also target ID-bearing ::table directives. Insert uses a zero-based numeric column and may include header when the table has header; cells supplies body-row values and is padded with empty cells when shorter than the current body row count. Delete accepts a zero-based numeric column or a header label when the table has header, and rejects removing the last remaining column.
update_dataset_cell targets ID-bearing ::dataset directives. row is zero-based over data rows (not the inferred header row), and column may be a zero-based index or a dataset column label from YAML schema, columns=, a CSV/TSV header row, or JSON columns / record keys. Source-preserving edits support inline YAML row arrays, single-line CSV/TSV bodies with quoted cells, and pretty-printed JSON row arrays or record arrays; structurally complex YAML/JSON datasets can still use replace_body.
insert_dataset_row and delete_dataset_row also target ID-bearing ::dataset directives. row is zero-based over data rows; insert allows row equal to the current data-row count to append. cells is an array of single-line strings, coerced through the dataset schema where available. Source-preserving row edits support inline YAML row arrays, single-line CSV/TSV bodies with quoted cells, and pretty-printed JSON row or record arrays.
insert_dataset_column and delete_dataset_column also target ID-bearing ::dataset directives. Insert uses a zero-based numeric column, requires a single-line header, and pads missing cells with empty values. Delete accepts a zero-based numeric column or a dataset column label from YAML schema, CSV/TSV headers, JSON columns, or JSON record keys, and rejects removing the last remaining column. Source-preserving column edits support inline YAML row arrays, single-line CSV/TSV bodies with quoted cells, and pretty-printed JSON row or record arrays.
move_block moves an existing directive block to a new ID-bearing section or directive parent. position is zero-based in the destination after removing the source block. Moving a block into itself or one of its descendants is rejected.
AST shape
Every node satisfies Node from src/ast.ts. The discriminated union has the following variants:
document — root, holds meta + children
section — id, level, title, children
paragraph — content (string with inline markup)
code — lang, content
list — ordered, items[]
list_item — content
quote — content
thematic_break
table — header[], align[] (left|center|right|null), rows[][]
directive — name, attrs, children, body?
Renderers exhaustively switch on type. Adding a new variant is a typed change: every renderer fails to compile until the new case is handled. Adding a new directive name (e.g., export_button, control) needs no AST change — only a renderer case.
Validator rules
The default validator detects:
- errors — duplicate IDs, references (
for=,[[id]]) pointing to unknown IDs,plotblocks with nodata=ordataset=,::memoryblocks missingtype=/id=or carrying an invalidtype/confidence/last_seen. - warnings —
evidence/counterevidencewith nofor=,figureblocks missingaltandcaption,claimblocks with no backingevidence,riskblocks with noowner=,decision/adrblocks with nostatus=,agent_task/todoblocks with noscope=or body, malformed tracked-revisionchange_requestblocks,citationblocks whoseaccessed=YYYY-MM-DDis older than the stale window (default 365 days), and[[wikilinks]]inside amemory-profile document that resolve to a non-::memorytarget.
Per-block opt-out: add the noverify flag attribute to silence rules on a single block (e.g. rhetorical claims that aren't expected to carry their own evidence chain).
::claim{id="claim-vs-markdown" confidence=0.85 noverify}
Markdown is too weak for AI-era documents.
::
Options surfaced on the API: requireEvidenceForClaims (default true), staleCitationDays (default 365), now (test injection point for the stale-citation rule).
Per-document override. Set stale_citation_days: 30 in frontmatter to tighten the window for the whole document (newsletters, research notes, portfolio updates).
Per-citation override. Add stale_after_days=N to a single citation to give it its own window — useful when one citation is intentionally evergreen (a definition, a constitution) inside an otherwise time-sensitive doc.
Precedence (high → low): CLI --stale-days, per-citation stale_after_days, frontmatter stale_citation_days, default 365.
Profiles
A document may declare a profile in frontmatter as a contract with downstream tools: "I only use these directives." Consumers (renderers, linters, schema generators) can then narrow safely instead of switching over the full Node union.
---
profile: research
---
Built-in profiles:
| Profile | Intended for | Includes (beyond core text blocks) |
|---|---|---|
minimal | Markdown-equivalent docs | summary, abstract, callout/note/warning/tip, page_setup, doc_protection, header, footer, toc, figure, citation, footnote, endnote, bibliography, math, code, table, pagebreak |
technical | Product docs, landing pages, manuals | minimal + hero, grid, card, columns, tabs, accordion, sidebar, button, api, endpoint, parameter, example, changelog, instruction, plot, dataset, query, code_cell, output, control, computed_metric, computed_plot, computed_table, export_button, agent_task, todo |
research | Theses, ADRs, post-mortems, weekly recaps | minimal + claim, evidence, counterevidence, assumption, risk, hypothesis, result, limitation, open_question, decision, adr, dataset, plot, metric, control, computed_metric, computed_plot, computed_table, state_change, agent_task, review, comment, change_request, provenance, confidence |
memory | Agent memory stores (per-project, per-user) | memory, memory_index only. Strict: each ::memory must declare id + type (one of user, feedback, project, reference). confidence must be a number in [0, 1]; last_seen must be an ISO date. |
math, code, and table are content-neutral and shipped in every profile — they were the most common "out-of-profile" warnings authors hit before they learned the workaround.
The validator emits a out-of-profile-directive warning for any directive outside the declared profile (noverify on a single block silences it). Authors who want the open surface should simply omit the profile field.
Composing profiles (v0.4). A document can opt in to the union of multiple profiles via profiles: (note the plural):
---
profiles: [research, technical]
---
The validator accepts every directive listed in either profile. The legacy profile: <single> form keeps working unchanged.
Suppressing rules at the file level (v0.4). Pass --ignore-rule <name> (repeatable) to noma check and noma render to drop matching diagnostics for that invocation. Useful when validating a single chapter that contains intentional cross-book wikilinks:
noma check chapters/03-risks.noma --ignore-rule broken-reference
Unknown rule names produce an info note; they do not fail the run.
Memory profile (v0.8)
The memory profile turns a .noma file into an agent memory store — a typed, validated, patch-addressable replacement for the loose Markdown-plus-frontmatter convention most coding agents use today.
---
profile: memory
---
::memory_index{id="index"}
- [[user_handle]] — ferax564 authorship alias
- [[feedback_release_scope]] — full ship pipeline on "make a release"
::
::memory{id="user_handle" type="user" confidence=0.95 last_seen="2026-05-09"}
ferax564 is the user's public authorship handle. Not a separate GitHub account.
::
::memory{id="feedback_release_scope" type="feedback" confidence=0.95 last_seen="2026-05-10"}
"Make a release" = the complete ship pipeline, not just tag + push.
::
Why a profile rather than free directives. Memory is high-stakes context: an agent reading a malformed memory file is more dangerous than rendering a malformed thesis. The profile narrows the allowed directives to ::memory and ::memory_index, then layers stricter rules on top.
::memory attributes.
| Attribute | Required | Notes |
|---|---|---|
id | yes | Canonical, stable, patch-addressable. Treat as immutable — use rename_id only when truly necessary. |
type | yes | One of user, feedback, project, reference. Maps to the same taxonomy Claude Code's memory uses. |
confidence | no | Numeric in [0, 1]. Use it to gate dump-time recall or to skip low-confidence memories during planning. |
last_seen | no | ISO date (YYYY-MM-DD) or full ISO 8601. Drives noma render --to llm --exclude-stale-days N. |
::memory_index. A single block whose body links every memory via [[id]] wikilinks. The validator does not yet enforce index completeness, but noma check --profile memory warns whenever an index wikilink resolves to a non-::memory target — catching the most common mistake of pointing the index at a heading or section.
HTML/PDF and DOCX handoff. HTML/PDF output and Word exports render ::memory_index as a labeled index panel and each ::memory as a typed panel (User memory, Feedback memory, Project memory, or Reference memory) with type, confidence, last_seen, scope, source, valid_until, superseded_by, and expired metadata visible below the body. HTML/PDF output keeps index wikilinks and supersession references as anchors; Word exports point them at bookmarks when the target memory exists.
Canonical id ≠ display title. A memory's id= is an immutable key that agents patch against — treat it like a database primary key, not like the title of the memory. Use the body for human phrasing; if a memory needs a renamable handle, use aliases= (which the validator also resolves for wikilink targeting).
Stale-aware recall. noma render --to llm --exclude-stale-days N (optionally with --now <ISO> for tests) drops ::memory blocks whose last_seen is older than the window before emitting the LLM context dump, AND drops ::memory_index body lines whose wikilinks resolve only to omitted memories (no dangling refs in the LLM context). Time-window staleness applies only to project and reference memories by default — durable rules (user, feedback) are kept regardless of last_seen unless they explicitly carry expired=true. This is a useful first cut at bounded recall; it is not a general retrieval system. last_seen measures observation freshness, not content freshness, so a memory can be recently seen yet factually superseded — richer recall (relevance to current task, wikilink-graph expansion, valid_until / superseded_by attributes) is future work.
Surgical edits. noma patch memory.noma --op '{"op":"update_attribute","id":"...","key":"last_seen","value":"..."}' rewrites only the targeted block. Sibling memories, the index, and frontmatter survive byte-for-byte. The same applies to add_block, delete_block, replace_block, and rename_id (the last also rewrites [[wikilinks]] and reference attributes — see docs/spec-agent-protocol-v1.noma).
Single-writer assumption. Current memory patching has no expected_sha precondition or atomic-write story — concurrent writers will race at file write time. Wrap the memory file in an external lock (or a queue) if more than one agent can patch it simultaneously.
Deferred schema work. The profile validates the attributes listed above. Future MVP increments should add source, scope (so a memory written for one project doesn't bleed into another's recall), valid_until / superseded_by for status memories that go factually stale, body-non-empty enforcement, and alias uniqueness once aliases are in common use. Tombstone semantics for forget operations are also future work.
See examples/agent-memory/ for the runnable demo and npm run demo:agent-memory for the trace.
Diagrams, plotly, external datasets (v0.5)
Three new directives turn .noma into a richer artifact format without breaking the AST.
::diagram{kind="mermaid|graphviz|drawio"} — body holds the source verbatim. The HTML renderer ships a small per-kind hydrator and only injects the matching CDN runtime when the document actually contains that kind, keeping plain pages CDN-free. DOCX renders a readable source fallback labeled with the diagram kind so Word reviewers can still inspect the Mermaid, Graphviz, or Draw.io source.
::diagram{kind="mermaid"}
flowchart LR
A --> B
B --> C
::
::plotly — body is a JSON spec ({ "data": [...], "layout": {...} }). The HTML renderer injects Plotly.js and hydrates each block; the LLM renderer keeps the JSON unchanged so an agent can still reason about the chart. DOCX preserves the JSON as an explicit interactive-chart spec fallback.
::dataset{src="data.csv"} — paths resolve relative to the source file (book mode resolves per chapter). The CLI calls inlineDatasetSources after parse so renderers stay pure: by the time HTML/LLM/JSON run, the dataset body is already inlined and the format attribute is set (csv, tsv, json, yaml). Plots reference these datasets by id and column exactly as before.
Source-preserving patch (v0.5)
noma patch no longer round-trips through renderNoma over the whole file. It now rewrites only the targeted line range — frontmatter quoting, sibling blocks, blank-line padding, and attribute order on unchanged lines all survive intact. The programmatic API exposes the new function as patchSource(source, ops); the AST-level patch(doc, op) stays for callers that already work in AST space. noma prove uses the same patch operations for a guarded dry run: it renders pre/post validation, ID registry, LLM context, source preservation, diff, hashes, and a sandboxed post-patch preview before an agent writes the source. For compatibility rules and bundled schemas, see Noma Compatibility Policy. For the locked v1.0 protocol core, see the Noma Agent Protocol v1.0 RFC.
Render targets (v0.13.0)
| Target | Status | Notes |
|---|---|---|
html | stable | Self-contained, semantic, with print stylesheet. Auto-injects KaTeX for math. |
llm | stable | Deterministic, LLM-friendly text |
json | stable | Full AST |
noma | stable | AST → .noma source (roundtrip-safe; backs noma patch) |
site | stable | Multi-page HTML site for book manifests; one page per chapter + index |
pdf | stable | HTML → Chromium print via Puppeteer. Requires --out <file.pdf> |
docx | experimental | WordprocessingML package for Word / Google Docs handoff. Requires --out <file.docx> |
epub | planned | |
slides | planned |
The static site also ships a browser workbench at workbench.html. It is not a separate render target; it bundles the parser, validator, HTML renderer, JSON renderer, and LLM renderer into a client-side editing surface for .noma source, safe preview, diagnostics, outline navigation, export downloads, find, print, selection formatting, headings/lists/links, and Noma block insertion templates for tables, figures, layout, controls, comments, change requests, and notes. When served by npm start, the same workbench can talk to /api/documents for persistent shared cloud documents and /d/<id> rendered artifact links.
noma render report.noma --to pdf --out report.pdf
noma render report.noma --to pdf --out report.pdf --page-size Letter --margin 12mm --css report.css
noma render report.noma --to docx --out report.docx
noma docx-data report.docx --out report.controls.json
noma docx-sync report.noma report.docx --out report.synced.noma --report report.sync.json
noma docx-review-data report.docx --out report.review.json
noma docx-review-sync report.noma report.docx --out report.reviewed.noma --report report.review-sync.json
PDF output uses the HTML renderer and theme first, then prints through Chromium with print media enabled. The HTML/PDF path renders technical API/reference blocks, semantic reasoning metadata, metric/code/computation blocks, memory profile panels, and collaboration review metadata as structured panels instead of generic directive boxes. PDF flags: --page-size, --margin, individual --margin-top/right/bottom/left, --no-print-background, and --css for appending custom CSS to the selected theme.
DOCX output is generated directly from the AST without external dependencies. It preserves frontmatter metadata (title, author, description, tags / keywords, profile, and status) in docProps/core.xml, plus headings, paragraphs, lists, page-aware tables, field-numbered table captions, dataset tables with rich generated metadata values, metric KPI blocks with rich value and metadata runs, static computed metric/plot/table handoffs, technical API/reference blocks, addressable code snippets, code-cell/output computation blocks, page-aware grid/columns layouts, framed card panels, memory profile panels, flattened hero/tabs/accordion sections, titled tab panels, framed sidebars, code blocks, abstract/callout/note/warning/tip blocks, Office Math blocks and inline equations, native checkbox action items with rich visible status text, native text/dropdown/date/checkbox control fields with custom XML data bindings, action blocks, section-level page setup, document-protection settings for form handoffs, rich native Word headers/footers with page-number fields and part-local hyperlinks, generated tables of contents and caption lists with rich labels and Word page-reference fields, caption cross-reference fields for figure/table/plot wikilinks, page breaks, targeted/threaded native Word comments with rich inline body content and resolved-state metadata, review-view settings for comments and revisions, target-anchored tracked change-request revisions, state-change deltas with rich from/to values, target-anchored rich native Word footnotes/endnotes, generated bibliographies with rich entry text, styled semantic review blocks and rich metadata values, review/provenance/confidence metadata blocks, embedded PNG/JPEG/GIF/SVG figures, field-numbered figure/plot captions, static SVG plots for resolvable data, diagram/Plotly source fallbacks, clickable citation source links, block-ID bookmarks, internal wikilinks, external hyperlinks with rich Markdown labels including combined bold+italic spans, and readable fallback labels for custom directives. Browser-only artifact blocks that cannot be resolved at render time still fall back to labeled placeholders.
Use noma docx-data <file.docx> to read the urn:noma:controls custom XML part, visible noma-control:<id> content controls, and native task checkbox content controls back out as JSON. This extracts the stable IDs, labels, types, and values for bound ::control fields plus checked state for ::agent_task / ::todo, giving form-review workflows a machine-readable return path from a DOCX package. When both sources exist, visible content-control values override the custom XML values so Word edits can return even if the bound data part is stale; when the custom XML part is missing, visible text/date/dropdown/combobox/checkbox controls still produce control values. Word w:cr carriage-return and w:br manual-break runs are preserved as line breaks, Word w:noBreakHyphen runs are normalized to -, Word w:softHyphen runs are preserved as U+00AD soft hyphen characters, Word w:tab and w:ptab runs are preserved as tabs, Unicode w:sym glyphs are preserved, text control leading/trailing spaces are preserved, and those empty run-token elements are accepted whether serialized as self-closing or paired empty elements. Deleted or moved-from tracked ranges inside visible fields, including w:moveFromRangeStart / w:moveFromRangeEnd spans, are ignored so the old side of a Track Changes edit does not come back as the current form value; moved-to ranges remain current text. Native checkbox metadata accepts both explicit val= states and present checked elements without a value; if an editor strips native checkbox metadata but leaves the generated checked/unchecked glyph as text or a Unicode symbol run, task tags and controls whose original custom XML type was toggle / checkbox still recover boolean state from the glyph. Use noma docx-sync <file.noma> <file.docx> to apply those values back to matching source ::control default= attributes and task done / status attributes with the source-preserving patcher; omitted type= is treated as text during sync, while numeric controls still coerce numeric-looking returned values. Unmatched DOCX control and task IDs are ignored. Add --report <file.json> to write the applied control/task changes and unmatched IDs as JSON without duplicating the patched source.
Use noma docx-review-data <file.docx> to read native Word comments from word/comments.xml / word/commentsExtended.xml, tracked revisions and tracked moves from word/document.xml plus native header/footer parts, native footnote/endnote bodies from word/footnotes.xml / word/endnotes.xml, bookmarked Word headings, bookmarked Word captions, bookmarked metric/control/action labels and block titles, prose block bodies, metric values, metric metadata, citation metadata, technical block metadata, computation block metadata, computed-metric and computed-plot metadata, control metadata, memory block metadata, and semantic block metadata, and bookmarked native tables from the document body and header/footer parts as JSON. The extractor returns each comment's native ID, author, initials, date, body text, resolved state, resolver metadata when Noma wrote it, and reply linkage when the DOCX package carries a threaded comment relationship. Explicit native done=false comment state wins over a stale generated Status: resolved paragraph, so reopened Word comments extract as unresolved while still stripping the stale status paragraph from the body; a comments-extended entry without a done attribute is treated as unknown native resolution state, so the generated status paragraph remains the fallback. Comment, note, tracked-revision, heading-title, caption-title, metric/control/action/block-title label text, and table-cell bodies reconstruct lightweight Noma Markdown for bold, emphasis, inline code, internal wikilinks, and external hyperlinks by reading the relevant relationship files. Adjacent same-style Word runs are coalesced before Markdown rendering, so editor-split bold, italic, bold-italic, and code runs return as one Markdown span instead of fragmented markup, including inside internal and external hyperlink labels. Word HYPERLINK fields, including complex fields and fldSimple, return through the same Markdown link path with result-run formatting preserved. Formatted or custom internal #id hyperlink labels return as [label](#id) when the visible anchor text or a verified Noma-generated bookmark identifies the target; plain ID labels and generated complex or fldSimple Word REF fields for Noma caption cross-references still return as [[id]]. Intentional spaces inside hyperlink labels are preserved, literal [ and ] characters inside returned external hyperlink labels are escaped, and whitespace or parentheses inside returned external hyperlink targets are percent-encoded, so Word links remain valid Noma Markdown without rewriting unchanged [ label ](...) source links. Current comment, note, heading-title, caption-title, metric/control/action label and block-title text, prose block-body text, metric value/metadata text, citation metadata text, technical block-metadata text, computation block-metadata text, computed-metric and computed-plot metadata text, control metadata text, memory block-metadata text, semantic block-metadata text, and table-cell bodies preserve Word w:cr carriage-return and w:br manual-break runs as line breaks, normalize Word w:noBreakHyphen runs to -, preserve Word w:softHyphen runs as U+00AD soft hyphen characters, preserve Word w:tab and w:ptab runs as tabs, preserve Unicode w:sym glyphs, preserve leading/trailing spaces in comment, note, and table-cell text, keep nested native table rows inside their parent table cell instead of promoting them to outer rows, accept those empty run-token elements whether serialized as self-closing or paired empty elements, ignore deleted or moved-from tracked ranges, including w:moveFromRangeStart / w:moveFromRangeEnd spans, and keep moved-to ranges as current text, while tracked-revision entries preserve deleted or moved-from text and run tokens as oldText and moved-to runs as newText. Adjacent same-ID tracked revision fragments are merged before grouping, so Word edits split across multiple formatted w:ins or w:del wrappers return as one revision. Multiple adjacent delete/insert pairs in one paragraph return as separate replace revisions, while delete/insert runs separated by current text remain independent revisions. Heading entries include native heading index, level, lightweight Noma Markdown title, same-paragraph bookmark anchors, and whether the heading itself contains tracked revision markup, so accepted Word title edits can return to the source heading without accepting proposed heading revisions as plain edits. Caption entries include native caption index, kind, lightweight Noma Markdown title with generated whole-caption Word styling ignored as presentation-only, bookmark anchors, and whether the caption itself contains tracked revision markup, so accepted Word caption edits can return to source title= / caption= attributes or a computed-plot body title: field without accepting proposed caption revisions as plain edits. Metric/control/action/block-title entries include native label index, kind, lightweight Noma Markdown title with generated whole-label Word styling, generated button hyperlinks, and generated export-target links ignored as presentation-only, bookmark anchors, and whether the label paragraph contains tracked revision markup, so accepted Word edits to Metric:, Computed metric:, Control:, ::button, ::export_button, and titled directive headings can return to source label/title fields without accepting proposed label revisions as plain edits. Metric value entries include native value index, visible value text, inherited metric bookmark anchors, and whether the value paragraph contains tracked revision markup, so accepted Word edits to ::metric value lines can return to source value fields without accepting proposed value revisions as plain edits. Metric metadata entries include native metadata index, parsed field labels/values, inherited metric bookmark anchors, and whether the metadata paragraph contains tracked revision markup, so accepted Word edits to ::metric metadata can update source attrs without accepting proposed metadata revisions as plain edits. Block metadata entries include native metadata index, parsed field labels/values across one or more consecutive metadata paragraphs, inherited directive bookmark anchors, and whether the metadata paragraph contains tracked revision markup, so accepted Word edits to citation, technical, computation, computed-metric/plot, control, memory, and semantic metadata lines can update source attrs or body fields without accepting proposed metadata revisions as plain edits. Block body entries include native body index, lightweight Noma Markdown body text, inherited directive bookmark anchors, and whether the body paragraphs contain tracked revision markup, so accepted Word edits to supported prose-like body-only directives can return through replace_body without accepting proposed body revisions as plain edits. Comment ranges/references and tracked revisions in header/footer parts use the same bookmark mapping as document-body comments and revisions. Comment ranges or point references, note references, and tracked revisions made inside a native table inherit the table's preceding Noma bookmark, so table-cell review notes and change requests can return to the source table or dataset block. Inside generated NomaLayout tables for ::grid / ::columns, nested child bookmarks take precedence over the outer layout bookmark, so comments, notes, and revisions in a rendered card/claim cell target that child block; that inheritance resets at each generated layout cell, so unbookmarked sibling cells fall back to the outer layout block instead of a previous cell's child bookmark. Native table extraction also recurses through those layout cells, so accepted edits to nested source ::table or ::dataset blocks inside cards or layout columns can return to the nested source block; comments, notes, and tracked revisions inside those nested table cells inherit the nested table or dataset bookmark. Framed paragraphs following a NomaDirective bookmark keep inheriting that directive bookmark, so comments, notes, and revisions on later paragraphs of a rendered claim, risk, card, or other framed directive body still return to that source block. It also returns insert/delete/replace revisions, including adjacent w:moveFrom / w:moveTo tracked moves and w:moveFromRangeStart / w:moveToRangeStart marker spans as replace proposals, with native revision IDs, author/date metadata, old/new text, same-paragraph or inherited directive-body anchor bookmarks, Noma target IDs when the revision follows a Noma-generated change-request label, and source change-request bookmarks when that label was generated from source. Footnote and endnote entries include the native note ID, body text, and same-paragraph or inherited directive-body anchor bookmarks from the note reference; Noma-generated targeted note references also carry the source note bookmark so edited notes can return to the original source block. Table entries include the native table index, header flag, cell rows, the preceding Noma bookmark anchors, and whether the table contains tracked revisions; generated header-row styling is treated as presentation-only while authored header-cell Markdown is preserved. Bookmarked table entries are still extracted if a reviewer restyles the native Word table.
Use noma docx-review-sync <file.noma> <file.docx> to apply the first native-review return path to source. The sync command maps Word comment ranges/references, notes, headings, captions, metric/control/action labels, block titles, prose and code block bodies, metric values, metric metadata, citation metadata, technical block metadata, computation block metadata, computed-metric and computed-plot metadata, control metadata, memory block metadata, semantic block metadata, tables, and revision anchors back to Noma block bookmarks, including comments and tracked revisions in native header/footer parts. It applies accepted heading-title edits with update_heading, preserving lightweight Markdown for Word bold, emphasis, inline code, internal wikilinks, and external hyperlinks, applies accepted table/figure/plot caption edits with update_attribute or, for computed plots whose title lives in the body, replace_body, applies accepted metric, computed-metric, control, action label, and block title edits with update_attribute or body-field replace_body, applies accepted prose block body edits for supported body-only directives with replace_body, applies accepted code block body edits for supported monospace directives with replace_body, applies accepted metric value edits with update_attribute or replace_body, applies accepted metric metadata edits with update_attribute or remove_attribute, applies accepted citation, technical, computation, computed-metric/plot, control, memory, and semantic block metadata edits with update_attribute, replace_body, or remove_attribute, adds new anchored Word comments as ::comment blocks after the matching source target, updates same-target source comments when the source bookmark, visible body, or metadata identifies them, treats metadata-conflicting same-target comments and replies as distinct Word comments instead of overwriting source blocks even when the visible text is identical, keeps deleted/withdrawn source comments and replies as audit history instead of matching returned Word comments or replies, even if an older DOCX still carries their source bookmarks, adds threaded Word replies as ::comment{reply_to="..."} blocks after the matching source comment, updates matched existing reply bodies when the thread parent plus visible text or metadata identifies the source reply, counts source-bookmarked replies as returned thread state for missing sibling deletion, adds native notes as ::footnote / ::endnote blocks, updates existing source-position comment and note bodies when their anchors identify the source blocks, updates targeted notes when the source note, exact or visible body, or one unambiguous same-target source note can be matched, keeps deleted/withdrawn source notes as audit history instead of matching returned notes, updates existing change requests when the source request, exact revision, metadata, or one unambiguous Noma-generated same-target request can be matched, treats metadata-conflicting target-only revisions as distinct change requests instead of overwriting source blocks even when the action and revision text are identical, counts target-anchored tracked revisions as returned review state for missing sibling deletion, keeps deleted/withdrawn source change requests as audit history instead of matching returned revisions, marks existing source comments resolved when Word's native comment state says they are done, refreshes returned resolver metadata, reopens source comments by removing stale resolution metadata when Word returns explicit native unresolved state, marks missing sibling comments, including previously resolved source comments, targeted notes, and change requests as status="deleted" when at least one other comment/note/request on the same target or reply thread came back from Word, adds table-cell Word comments, notes, tracked revisions, and wrapper or range-marker tracked moves to the matching source table or dataset block, updates matching ::table directive bodies and inline ::dataset bodies from edited Word tables, and imports unmatched anchored tracked revisions or tracked moves as ::change_request blocks. Alias and canonical targets compare as the same source reference during review sync, so Word's canonical bookmark return does not rewrite unchanged alias-authored wikilinks, internal links with escaped labels, generated caption cross-reference fields, table/dataset cell links, or target attributes. Returned comments, notes, and change requests inserted next to nested directive targets preserve that target's fence depth. Add --report <file.json> to write applied changes plus skipped comments, revisions, footnotes, endnotes, tables, headings, captions, labels, block bodies, metric values, metric metadata, and block metadata as JSON without duplicating the patched source. Edited comment and note bodies with Word bold, emphasis, inline code, internal wikilinks, or external hyperlinks return through replace_body as Noma Markdown. Equivalent lightweight Markdown spellings such as _emphasis_ and *emphasis*, escaped and raw backslash spellings inside hyperlink labels, and source \| escapes versus Word-returned literal pipes compare as unchanged during review sync, preserving the original source spelling unless Word introduced a real content or link change. If Word returns plain text where the source had bold, emphasis, inline code, wikilink, or hyperlink markup, review sync treats that as an accepted markup-removal edit and updates the source to plain text; target-only comments with sibling candidates use the same visible-text matching for links whose labels contain escaped literal brackets. Simple accepted ::table edits use update_table_header_cell, update_table_cell, insert_table_row, delete_table_row, insert_table_column, or delete_table_column when the table shape is unambiguous, preserving Markdown/link markup in unchanged cells and keeping literal pipes inside edited code spans unescaped; returned multiline table cells are skipped because they cannot be represented safely in source pipe tables, while ambiguous row/column diffs fall back to full table-body replacement. Simple same-shape accepted ::dataset edits for inline YAML row arrays, CSV/TSV bodies, and pretty-printed JSON row or record arrays use update_dataset_cell; simple row insert/delete edits use insert_dataset_row or delete_dataset_row; simple column insert/delete edits use insert_dataset_column or delete_dataset_column, preserving comments, schema text, and unrelated row lines. Returned multiline dataset cells are skipped because they cannot be represented safely across all supported source dataset formats. Unsupported dataset shapes still fall back to full dataset-body replacement when they can be serialized safely. Tables, headings, captions, labels, block bodies, metric values, metric metadata, and block metadata containing tracked revisions or tracked moves are not treated as direct accepted edits during sync; their revision markup returns as change requests, and those heading/table/caption/label/value/metadata entries remain visible in the skipped report lists. Unanchored comments, ambiguous same-target comments, replies whose parent cannot be matched, ambiguous edited replies, unanchored notes, ambiguous targeted notes, ambiguous change requests, unanchored headings, headings with tracked revisions, unanchored captions, captions with tracked revisions, unanchored labels, labels with tracked revisions, unanchored block bodies, block bodies with tracked revisions, unanchored metric values, metric values with tracked revisions, unanchored metric metadata, metric metadata with tracked revisions, unanchored block metadata, block metadata with tracked revisions, and tables that do not resolve to a supported ::table / ::dataset source block are extracted by docx-review-data but skipped by the sync command until they can be represented source-preservingly.
Comment, footnote, and endnote entries whose bodies contain Word tracked revisions carry hasRevisions. Review sync keeps those review bodies in the skipped report instead of accepting the current text as a plain source edit; source-bookmarked skipped items still count as returned review state so they are not falsely marked deleted.
Computed-plot and computed-table caption sync follow the same label precedence used by DOCX rendering: label=, title=, name=, body label:, then body title:. Accepted Word edits update that source field, using replace_body only when the displayed caption came from a body field; if the caption came only from the block ID fallback, sync adds a title= attribute. Computed-table DOCX output uses the Word Table sequence while showing a Computed table caption label, so it appears in table lists and still carries enough source identity for review sync.
Metric label sync follows the same label precedence used by DOCX rendering: ::metric uses label=, title=, name=, then the block ID fallback; ::computed_metric uses label=, title=, name=, body label:, body title:, then the block ID fallback. Accepted Word edits update the displayed source field, using replace_body only for computed-metric body fields; if the label came only from the block ID fallback, sync adds a label= attribute.
Control label sync follows the DOCX-rendered control label precedence: Label=, label=, then the Word control fallback. Accepted Word edits update that source attr, preserving Label= when present and otherwise using label=; the content-control value beside the label remains part of the docx-data / docx-sync form-data path.
Action label sync follows the DOCX-rendered action label precedence for ::button and ::export_button: Label=, label=, body Label:, then the action fallback. Accepted Word edits update that source field, preserving lightweight emphasis and mixed bold while ignoring generated button hyperlinks and export-target links; it preserves Label= / label= attrs and body fields when present, and if the label came only from the rendered fallback, sync adds a label= attribute.
Block title sync follows the DOCX-rendered title precedence for titled directive headings. Accepted Word edits update title=, caption=, or name= only when the rendered title maps unambiguously to that source field; fallback card/callout/sidebar/tab/memory/dataset/bibliography/technical/custom title edits add title=.
Custom fallback heading metadata sync parses the readable attribute summary rendered in unknown and namespaced directive headings. Accepted Word edits to that summary update existing attrs, add newly named attrs when the reviewed label uses name=value, and remove attrs omitted from the reviewed summary or when the whole summary is removed from an otherwise generated fallback heading. The parser recognizes existing attr labels as comma boundaries, so values such as note=A, B remain one attr value instead of becoming noisy title= text.
DOCX metadata parsing treats Word's visible · separator as a field boundary only when another valid metadata label for the rendered block follows it in the rendered field order. Values such as source="CRM · status: stale" and owner="Ops · Q1: Finance" therefore survive docx-review-data extraction and docx-review-sync unchanged unless the reviewer edits that value, even when Word serializes the value separator as its own run.
Block body sync follows the DOCX-rendered framed body paragraphs for supported prose-like body-only directives, including claims, cards, callouts, memory blocks, tasks, and semantic reasoning blocks. Accepted Word edits return through replace_body, preserving lightweight Markdown for bold, emphasis, inline code, internal wikilinks, and external hyperlinks, while source soft wraps compare against Word's paragraph-normalized text before patching.
Technical prose body sync follows the same body-only replace_body path for ::api, ::endpoint, ::parameter, ::instruction, ::changelog, and ::query / ::example blocks that do not render as language-backed code. Accepted Word edits preserve lightweight Markdown and use the same soft-wrap comparison as other prose bodies.
Code body sync follows the DOCX-rendered monospace body paragraphs for ::code, ::code_cell, ::output, and language-backed ::query / ::example blocks. Accepted Word edits return through replace_body in code mode, preserving line breaks instead of paragraph-normalizing the body.
Custom fallback body sync follows the DOCX-rendered fallback panels for unknown and namespaced directives. Accepted Word edits to body-only fallback content return through replace_body, while custom labels and attribute metadata remain renderer output rather than source body text.
Metric value sync follows the same value precedence used by DOCX rendering: value=, current=, amount=, then body text. Accepted Word edits update that source field, stripping the rendered unit= suffix from stored values when appropriate; if the reviewer removes the rendered unit from the visible value line, sync removes the unit= attribute so the source re-renders the accepted Word text.
Metric metadata sync follows the same metadata precedence used by DOCX rendering: status=, trend=, change= / delta=, target=, source=, and as_of= / asOf= / date=. Accepted Word edits update, add, or remove those source attrs, preserving existing alternate spellings when present and using canonical change= / as_of= for newly-added metadata.
Block metadata sync follows the metadata labels exported for ::citation, technical API/reference blocks, ::code_cell, ::output, ::computed_metric, ::computed_plot, ::computed_table, ::control, ::memory, reasoning blocks, ::review, ::provenance, ::confidence, ::agent_task, and ::todo. Accepted Word edits update, add, or remove matching source attrs or computed body metadata fields, preserving existing alternate spellings such as href=, baseUrl=, url=, location=, runtime=, count=, cell=, range=, suffix=, min=, max=, step=, lastSeen=, validUntil=, supersededBy=, due_at=, decided_at=, author=, by=, agent=, sha=, score=, and reason= when present. Citation URL and DOI metadata can span separate Word metadata paragraphs and still return to the same source citation block.
--to site
Pass a book manifest to noma render <book.yml> --to site --out <dir>. The renderer writes a static Noma Space: one <chapter-slug>.html page per chapter, a root index.html, shared theme assets, and _assets/search-index.json.
The output is intentionally serverless. It can be opened from disk, uploaded to GitHub Pages, or copied to any static host. Every page gets a sidebar, search box, breadcrumbs, copy-link and print actions, page metadata, related pages, and backlinks. The search UI reads an embedded per-page index; automation and agents can read the root JSON file directly.
Chapter slugs come from the chapter filename, or from the chapter root H1's id when that id is explicit and path-safe. Slugs may contain /; links to pages, the shared theme, the search index, and cross-chapter references are depth-aware. Cross-chapter wikilinks [[block-id]] resolve into <other-chapter>.html#block-id URLs, while same-page references stay as #block-id. Backlinks are computed from those wikilinks.
Page metadata comes from each chapter's frontmatter:
| Field | Space use |
|---|---|
tags | search terms, related-page grouping, tag chips |
status | sidebar and page inspector |
owner | sidebar and page inspector |
updated | page inspector and recent-pages list |
The manifest-level title, description, and author feed the space title, sidebar description, index header, and space inspector.
Book manifests
Multi-file projects use a YAML manifest with a .yml / .yaml extension.
title: Agentic Documents
description: Field guide for agent-editable documentation
author: ferax564
outputs:
html:
theme: default
llm: {}
chapters:
- chapters/01-introduction.noma
- chapters/02-block-model.noma
- chapters/03-agent-edits.noma
Chapter paths are resolved relative to the manifest directory. The CLI auto-detects the manifest by extension:
noma render book.noma.yml --to html --out dist/book.html
noma render book.noma.yml --to llm --out dist/book.llm.txt
noma check book.noma.yml
The loader concatenates each chapter's parsed AST into a single DocumentNode (top-level # Chapter headings stay distinct so HTML renders one continuous page with chapter sections; LLM export keeps chapter boundaries via the heading prefix). The manifest's title / author land on document.meta; the site renderer also reads description for Noma Space chrome. The outputs.html.theme value is honoured by future renderers and ignored for now.
Scoped heading IDs (v0.4). In book mode, every level ≥ 2 heading has its slug path-prefixed by its chapter root: ## Risks inside # Risk Premia 3 becomes risk-premia-3/risks. The original (un-prefixed) slug is kept as an alias so legacy [[risks]] links still resolve. This eliminates the duplicate-id floods that 30-chapter books hit when every chapter has ## Risks + ## Citations. Single-file mode behaves exactly as before.
Chapter aliases (v0.4). Two extra resolution paths land on the chapter root section:
- Filename slug.
chapters/risk-premia-3.nomaaddsrisk-premia-3as an alias for the chapter —[[risk-premia-3]]resolves regardless of the H1's spelling. - Frontmatter
aliases:. A list of strings on the chapter that registers extra IDs:
``yaml
---
title: Risk Premia 3 (RP3)
aliases: [rp3]
---
``
After this, [[rp3]], [[risk-premia-3]], and [[risk-premia-3-rp3]] all resolve to the same section.
Resolution order: explicit id= on the heading → auto-slug from the title → frontmatter aliases: → filename slug. Any of those paths matching the wikilink target counts as resolved.
Compatibility promises (v0.13.0)
- Public npm package names live under the
@ferax564scope:
@ferax564/noma-cli, @ferax564/noma-mcp-server, and @ferax564/noma-agent-sdk. The @noma/* npm scope belongs to a different project and must not be used in install instructions or generated package metadata.
Compatibility promises (v0.10.1)
- The root GitHub Action installs the CLI from the checked-out action ref by
default. This keeps uses: ferax564/noma@vN.N.N pinned to that release even if the npm registry package configured by an override points somewhere else. Explicit cli-package and deprecated cli-version overrides remain available for advanced workflows.
Compatibility promises (v0.10.0)
noma --version/noma -v,noma init,noma ids,noma render --strict,
and scoped LLM export flags (--select, --exclude, --budget) are additive CLI surface. Existing commands and render output remain compatible unless a caller opts into one of the new flags.
noma patch --opsaccepts transaction-shaped payloads with optional
prevalidate / postvalidate. Single-op --op payloads remain supported. Transaction writes are all-or-nothing when validation fails.
- Strict rendering blocks raw escape hatches, external runtime assets, and the
generated computed-control runtime. Interactive controls stay visible but are disabled with static default computed values. Manifest trusted_publishing: true applies the same static posture to manifest-driven HTML/site/PDF renders. The default HTML renderer remains permissive for local artifact authoring; use --strict or manifest trusted-publishing mode for team/published contexts.
- The reusable GitHub Action is a distribution wrapper around the CLI, not a new
format contract. Pin cli-version in workflows that require reproducible CI output.
Compatibility promises (v0.8)
- The
memoryprofile is stable.::memoryrequiresid=+type=(one of
user, feedback, project, reference); confidence must be a number in [0, 1]; last_seen must be an ISO date. The four type names will not be removed within 0.x — additive new types may be added.
noma render --to llm --exclude-stale-days Nis additive. Durable types
(user, feedback) stay pinned regardless of last_seen unless they carry expired=true; only project and reference memories age out of the recall window. --now <iso> overrides "today" for tests.
--to sitefrom a nested-slug chapter (level-1idcontaining/) now
emits depth-aware ../ prefixes on nav chapter links, the home link, and cross-chapter wikilink hrefs — matching the v0.7.1 stylesheet behaviour. Flat-slug books are byte-identical to v0.7.1.
Compatibility promises (v0.7)
noma diff a.noma b.nomais additive and pure: same inputs always produce
the same ::state_change block list. Output schema matches v0.3's ::state_change directive (block, attribute, from, to, at, optional reason). Attribute additions use from="(absent)"; attribute removals use to="(absent)".
book.ymltrusted_publishing: trueapplies the strict static HTML posture
for all HTML/site/PDF renders driven by that manifest. Existing manifests without the field are unchanged.
--to siteemits_assets/theme.cssonce and links it from every page.
Output of renderHtml (single-page) is unchanged unless the caller opts in via stylesheetHref.
Compatibility promises (v0.6.0)
- The directive opener/closer syntax is stable and will not change in 0.x.
- Attribute grammar is stable.
- Auto-generated heading IDs (slugify rules) are stable.
- The AST is not stable across 0.x — new fields may be added on existing variants. Renderers and validators inside this repo move in lockstep.