reference

navi export

Explicit cards and free-input parameters exported as navi .cheat files.

smill build navi <file-or-directory>... --out <new-dir> [--shell bash|zsh|fish|nu] [--root <dir>] [--strict] writes one <sheet>.cheat and <sheet>.navi.json pair per exported sheet to a new directory. It generates text only: SheetMill never runs navi, a shell or any exported command, and a lint test keeps the renderer free of os/exec, syscall, plugin and network imports. Export is one way; importing .cheat files is not supported. Behavior was checked against navi v2.24.0 (commit 5515367dc8a2a561d2d82b8352f729b1da4120d3) by reading its source, never by running it; see the source evidence.

Shared source contract

Use .command and cheatsheet.cards.<id>.params for shared command semantics. Feature 015 owns the core source contract; 006 consumes it for navi. The migration guide covers legacy aliases, conflicts and unchanged output/identity guarantees. Existing valid legacy inputs gain no default deprecation warning, including under --strict.

One explicit command card

Keep ordinary GFM prose, tables and layout. A card is exported only when it has {.command} (or legacy {.navi}) and an explicit {#id}, and contains exactly one fenced code block whose language belongs to the chosen profile. Generated IDs change with titles, so they cannot anchor parameter metadata. Unmarked cards are ignored.

---
title: SheetMill CLI
tags: [sheetmill, validation]
cheatsheet:
  schema: 1
  tool: smill
  cards:
    check-sheet:
      params:
        sheet:
          type: path
          description: Markdown sheet to validate
---

## Validation

### Check a sheet {#check-sheet .command}

Validate a sheet before publishing it.

```bash
smill check "<sheet>"
```

For the input sheets/smill.md (relative to the root) this produces:

; Generated by SheetMill smill build navi; format 1
; Source: sheets/smill.md
; Title: SheetMill CLI
; Shell: bash
; Parameters: trusted raw shell text; type hints are not enforced
; License: not stated

; card: check-sheet
% sheetmill:sheets/smill.md#check-sheet!, smill, sheetmill, validation
# Check a sheet
smill check "<sheet>"

After shared source validation, a selected card that fails navi-specific export checks is skipped with a warning; the rest of the build continues and the warning names the card. A .command card without an explicit ID fails core checking with CMD002; legacy .navi retains NAV101 warning/skip behavior. Metadata, profile and batch-identity problems are errors: nothing is written. For automation, --strict turns any warning into a failure (exit 1) before anything is written.

Legacy syntax

The following remains supported. With identical paths, metadata values, command bytes and build options, it produces byte-identical .cheat output and pinned entry identities to the shared example above. Do not change IDs while migrating.

---
title: SheetMill CLI
tags: [sheetmill, validation]
cheatsheet:
  schema: 1
  tool: smill
  navi:
    shell: bash
    cards:
      check-sheet:
        params:
          sheet:
            type: path
            description: Markdown sheet to validate
---

## Validation

### Check a sheet {#check-sheet .navi}

Validate a sheet before publishing it.

```bash
smill check "<sheet>"
```

Shell profiles

navi runs every snippet through the one shell in its own configuration and the fence label does not select it. A build therefore targets one profile, states it in each header, and does not configure navi: configure navi's shell to match. A legacy sheet's cheatsheet.navi.shell, when present, must equal the profile (NAV006 otherwise). Build each shell into its own directory.

ProfileAccepted fence languagesQuote check
bash (default)bash, shPinned Bash 5.2 parser, quote diagnostics only
zshzshPinned experimental Zsh parser, quote diagnostics only
fishfishbackslash escapes inside single quotes
nunu, nushellnone; nushell raw strings are not analysed

Cards in other languages are skipped with NAV101, never reinterpreted. Commands in the experimental Fish profile skip its heuristic quote check when they contain a heredoc marker. Bash/Zsh parsing understands heredoc payloads.

bash is the primary profile. zsh, fish and nu apply the same structural checks with their own fence languages and quoting rules, and are experimental: their fixtures are synthetic and have not been verified against real shells.

Bash and Zsh quote diagnostics use only mvdan.cc/sh/v3/syntax v3.14.1, a non-executing Go parser; its Bash grammar follows 5.2, and Zsh support is experimental. A validation-only copy substitutes inert words for navi placeholders; the emitted command is unchanged. Case patterns, nested substitutions and heredocs are parsed structurally. Only the parser's unclosed-quote diagnostics are reported; other syntax errors are ignored and can prevent later quote checks. This is not full syntax validation. Fish retains a heuristic, and Nushell performs no quote check. Stronger syntax checks remain planned.

Metadata schema

LocationValue
cheatsheet.cardsShared card settings validated by core; see command cards
cheatsheet.naviLegacy mapping with optional shell and cards only
cheatsheet.updated, cheatsheet.tool_versionSheet freshness: never exported, never a navi tag, .cheat bytes and identities unchanged; the companion keeps them verbatim as ordinary metadata. Source files are validated while parsing, so invalid values fail with VAL004 (source format)
.cards.<id>Keyed by an explicit command-card ID; mapping with only params
.params.<name>type (string, int or path; required) and description (optional single-line string)

Names match [a-z][a-z0-9_]{0,31} and are local to one card, which has its own navi context, so one name may be declared differently in different cards. Unknown keys, references to missing/unmarked cards or automatically generated IDs, and unsupported legacy types are NAV006. Shared metadata uses CMD001/CMD002; conflicting complete declarations in both namespaces use NAV006. Invalid metadata references are errors; a legacy marked card without metadata that lacks an explicit ID still follows the NAV101 warning-and-skip rule. type and description are SheetMill metadata; navi does not enforce them. Defaults, choice lists, suggestion commands, previews, multi-selection and shared contexts are not generated yet, and $ provider lines are never emitted.

Equal complete per-card declarations coalesce; disjoint card IDs combine. Different parameter subsets for one card are a conflict, not a partial merge. Both marker classes on one card select it once. See the migration rules.

Metadata companion

Every exported sheet has a <sheet>.navi.json companion with schema version 1:

{
  "schema": 1,
  "source": "sheets/smill.md",
  "shell": "bash",
  "metadata": {"title": "SheetMill CLI", "license": "EUPL-1.2"},
  "cards": [{"id": "check-sheet", "title": "Check a sheet", "context": "sheetmill:sheets/smill.md#check-sheet!"}]
}

metadata contains the complete semantic frontmatter, including provenance, unknown nested fields and parameter type/description hints. This preserves values, not YAML spelling, comments or the Markdown body; it is not a round-trip format. source is the normalized path relative to the selected root. shell records the selected profile. cards lists only successfully exported cards, in source order, with their original titles and exact generated contexts; metadata for skipped cards remains in metadata.

JSON uses two-space indentation, fixed schema-field order, lexically sorted metadata mapping keys, source-ordered arrays, HTML escaping and a final newline. It contains no generated timestamps or absolute host paths introduced by the exporter. Author-supplied metadata is kept unchanged, so review it before sharing. Companion schema and .cheat format versions are independent. navi consumes only .cheat; SheetMill does not load or execute companions. Both files use the existing new-directory/no-overwrite writer.

A sheet with no exported cards gets neither file. Invalid metadata still fails before writing, as does companion serialization failure (REN001). Preserved metadata no longer causes --strict to fail; actual warnings still do. License warnings remain even if an unusual license value is preserved in the companion.

Placeholders

<name> with a lowercase name as above is a placeholder, and every placeholder needs a declaration which must be used. navi's own token syntax is wider: its regex \\?<(\w[\w\d\-_]*)> (Unicode \w) also reads <1>, <_x>, <a-b>, <Name>, <ünï> and the escaped form \<name>. SheetMill matches navi's syntax first, then rejects whatever its narrower grammar does not allow: the card is skipped (NAV103). There is no escape for a literal placeholder. Redirections (< in > out), heredocs (<<EOF) and spaced angle brackets are ordinary shell. SheetMill adds no quoting: quote placeholders as you want them in the command.

Command text

The command is exported byte for byte. Skipped with NAV102: a carriage return, NUL and other control characters, invisible or bidirectional format characters, U+2028/U+2029, U+2800 (navi's finder field separator), blank lines (navi turns them into separators), a trailing continuation backslash, an unterminated quote, and a first word navi (navi rewrites it to its own executable path). navi reads a line starting in column 0 with %, @, ;, # or $, or a code fence, as syntax even inside a fence; such a line skips the card and is never indented or hidden. SheetMill does not detect dangerous commands; the card's author owns that.

Identity and the .cheat structure

navi keeps one entry per hash of the trimmed tags concatenated, without separator, with the trimmed description, and silently drops later duplicates. Each card therefore gets its own first tag, sheetmill:<path>#<id>!, where the path is relative to --root (default: the common directory of the inputs) and both parts percent-encode every byte outside [A-Za-z0-9_./-]. The final ! terminates the context so tags and titles cannot concatenate into another card's text. tool and root tags follow, in source order, for filtering. Before writing, every entry's pinned item hash is computed across the batch; equal hashes are NAV008 (error). --root must name an existing directory without symlinks, and every input must be inside it. An invalid root or outside input fails with IO001 before writing.

Titles, tags and header values are collapsed to one line, so they cannot create a navi line. A title that is empty after that (for example only U+200B) or contains U+2800 skips the card, because navi ignores entries without a description. A tag with a comma, a line break or U+2800 is NAV006. The generated ; card: comment records the card ID.

Free input is trusted shell text

navi substitutes placeholder values as raw text, including values from its environment. Double quotes keep ordinary spaces together, but do not make quotes, substitutions or shell operators in a value safe. Neither type: path nor type: string enforces anything at runtime. A leading dash may become a program option; use a tool-supported -- or a ./ path where appropriate. Choosing to run an exported template in navi is a separate user action.

Diagnostics

CodeSeverityMeaning
NAV001errorUnsupported shell profile
NAV006errorInvalid or unknown cheatsheet.navi metadata, dangling card ID, wrong shell, unusable tag
NAV007retiredWas a sheet-wide parameter conflict; parameters are now card-local
NAV008errorTwo entries share a pinned navi identity hash
NAV101warningCard skipped: no explicit ID, not exactly one code block, or language outside the profile
NAV102warningCard skipped: unsafe or unbalanced command text or title
NAV103warningCard skipped: malformed placeholder
NAV104warningCard skipped: placeholders and declarations disagree
NAV105warningLicense not stated; the header says so
NAV106warningSheet exported nothing, so no file is written for it. If no sheet exports, the build fails and writes nothing
NAV107retiredMetadata omission warning; complete frontmatter now survives in the companion
NAV000error--strict and at least one warning; nothing is written

Card/code diagnostics use their source spans. Sheet-level metadata and global profile diagnostics use the one-based 1:1 document/command anchor because the IR does not retain individual frontmatter key spans.

REN001 and IO003 apply as for the other renderers. The output directory must not exist.

Verification and gaps

Table-driven negative tests, golden bytes, a pure Go model of navi's line grammar and entry deduplication, a fuzz target over command text, CLI tests and the no-execution lint. The item hash model is pinned by fixed vectors (itemHash("") = 0x1c9d3adb639f298e, itemHash("abc") = 0x64372d1c87d01e7c) that an independent review calculated from the Rust hash input. Not done: running navi (never, by policy). Fish quote analysis remains heuristic; Bash/Zsh only surface selected parser diagnostics. Re-check the upstream source when moving to another navi version.