diff --git a/content/reference/commands.md b/content/reference/commands.md index d03a14bd..d8370538 100644 --- a/content/reference/commands.md +++ b/content/reference/commands.md @@ -18,9 +18,11 @@ This page is lookup. For which command to run first, see -The public CLI has six core commands plus the `standard-site` and `nostr` -families. With no command, Boris runs `build` and publishes an HTML site -under `dist/` — the default target, not the only one. +The public CLI is a small command set plus the `standard-site` and `nostr` +families: `build`, `validate`, `watch`, `check`, `impact`, `plan`, +`recipe-scale`, `graph`, `proof verify`, and `init`. With no command, Boris +runs `build` and publishes an HTML site under `dist/` — the default target, +not the only one. ## Commands at a glance @@ -37,6 +39,9 @@ under `dist/` — the default target, not the only one. | `nostr sign` | Sign a plan artifact into a signed-event bundle (key once via stdin) | No; bundle JSON on stdout or `--out PATH` | | `nostr publish` | Send the exact signed events to the plan's relays over WebSocket | No; report JSON on stdout or `--out PATH` | | `recipe-scale --id PAGE --factor TEXT` | Print a derived Cooklang scale view | No; view JSON on stdout or `--out PATH` | +| `graph` | Render the frozen graph as Mermaid (default) or Graphviz DOT | No; render on stdout or `--out PATH` | +| `proof verify` | Apply the publication-check policy to a committed `checks.json` | No; verdict report on stderr | +| `init [DIR]` | Materialize a deterministic starter site and verify that it compiles | A starter tree into `DIR` (must be empty) | ## Basic usage @@ -209,6 +214,47 @@ for the normative contract. | `--report PATH` | Write the analysis report to PATH instead of stderr (check/impact) | | `--quiet` | Suppress normal progress and diagnostic text where supported | +## Graph renders + +```bash +./zig-out/bin/boris graph --input content --format mermaid +./zig-out/bin/boris graph --input content --format dot --out graph.dot +``` + +`graph` renders the frozen content graph read-only: Mermaid by default, or +Graphviz DOT with `--format dot`. It writes nothing unless `--out PATH` names a +single file, and it never mutates the graph. See +[`graph-formats.md`](https://github.com/drawmeanelephant/boris/blob/main/docs/contracts/graph-formats.md) +for the normative render contract. + +## Publication-check policy + +```bash +./zig-out/bin/boris proof verify --html-dir dist +./zig-out/bin/boris proof verify --html-dir dist --max-warnings 0 +``` + +`proof verify` applies the publication-check severity policy to the committed +`checks.json` of one target. It writes no artifacts — the verdict goes to +stderr — and findings beyond the configured caps exit `1`. `--max-errors N`, +`--max-warnings N`, and repeatable `--block-code CODE` select the policy. This +is the gate the GitHub Pages workflow runs before it deploys, so running it +locally predicts the deploy. + +## Starter site + +```bash +./zig-out/bin/boris init my-site +``` + +`init [DIR]` materializes a fixed starter tree — three content pages that +exercise the graph, a closed-slot theme whose layout ships the rendered-search +client, and the two publication profiles — and then compiles it through the +normal HTML pipeline into a probe directory that is removed again. `DIR` must +be empty or not exist, and the tree is byte-deterministic. Exit `0` means the +starter was both materialized and compiled; a probe failure removes the tree +and exits `1`, and a target outside the workspace skips the probe and says so. + ## Publication profiles ```bash @@ -244,7 +290,7 @@ workflow plus `plan --profile` for the normalized location. | Code | Meaning | |---:|---| | `0` | Command completed successfully | -| `1` | Content/graph error, or a failing `check` health finding | +| `1` | Content/graph error, a failing `check` health finding, or a `proof verify` policy failure | | `2` | Usage error, missing value, or conflicting flags | | `3` | I/O or other system failure | diff --git a/docs/changelog.d/995-cli-reference-current.md b/docs/changelog.d/995-cli-reference-current.md new file mode 100644 index 00000000..922a76a3 --- /dev/null +++ b/docs/changelog.d/995-cli-reference-current.md @@ -0,0 +1,10 @@ +### Fixed + +- `boris --help` no longer prints a stray backslash on the `recipe-scale` mode + line — a doubled multiline marker that shipped because nothing read the help + text, which is now a named constant with a test over its shape — and the + interactive CLI reference documents the three shipped commands it was missing + (`graph`, `proof verify`, and `init`) instead of describing a + six-command surface. Links: [the command + reference](/content/reference/commands.md), [the CLI + contract](/docs/contracts/cli.md). diff --git a/src/cli.zig b/src/cli.zig index 7d5f31c1..ce00bada 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -2583,220 +2583,226 @@ fn takeValueAllowEmpty( return args[i.*]; } +/// The public CLI help text. It is a named const rather than an inline literal +/// inside the printer so a test can assert its shape: nothing read this text, +/// which is how a doubled multiline marker survived on one mode line and +/// printed a stray backslash in `boris --help`. +pub const usage_text = + \\Boris — Zig content compiler (HTML site + IR + optional RAG) + \\ + \\Usage: boris [options] + \\ + \\Modes: + \\ build Build the HTML site (default command) + \\ validate Validate selected HTML source/config without publication + \\ watch Build HTML, then watch and rebuild on changes + \\ check Read-only graph health report (findings do not fail by default) + \\ impact Read-only transitive impact report for a page + \\ plan Emit a normalized publication plan (no publication) + \\ recipe-scale Print a derived Cooklang scale view (no rewrite) + \\ graph Read-only graph render: Mermaid (default) or Graphviz DOT + \\ proof verify Fail when committed publication-check findings exceed policy + \\ standard-site publish One-shot Standard.site publish (stored session + reconcile; never implicit) + \\ standard-site plan Emit the deterministic Standard.site plan offline (no network) + \\ standard-site records Dump the full canonical record payloads offline (no network) + \\ standard-site verify Cross-check the built head links + well-known file offline (no network) + \\ standard-site login Authorize a DID or handle and persist the session + \\ standard-site sessions List persisted sessions (DID, flavor, PDS; no secrets) + \\ standard-site logout Remove a persisted session (secure erase; does not revoke) + \\ standard-site smoke Live interop smoke against a real PDS (manual, opt-in; never in CI) + \\ nostr plan Emit the offline Nostr NIP-23 publication plan (no signing, no relay) + \\ nostr sign Sign a plan artifact into a signed-event bundle (offline; key via stdin) + \\ nostr publish Send a signed-event bundle to the plan's relays; writes the report + \\ init [DIR] Write a starter site (content, theme, profile) into DIR (default: .) + \\ (no command) Same as build + \\ standard-site publish options: + \\ --profile PATH Standard.site publication profile (required) + \\ --plan PATH Committed standard-site plan to validate (fail closed on drift) + \\ --out PATH Evidence artifact path (default: stdout) + \\ --prune Explicit prune authority (ANDs with the profile prune flag) + \\ --source-commit C Source commit recorded in the evidence bindings + \\ standard-site plan options: + \\ --profile PATH Standard.site publication profile (required) + \\ --out PATH Plan artifact path (default: stdout) + \\ standard-site records options: + \\ --profile PATH Standard.site publication profile (required) + \\ --out PATH Records artifact path (default: stdout) + \\ standard-site verify options: + \\ --profile PATH Standard.site publication profile (required) + \\ --dist DIR Built output directory to check (default: dist) + \\ --out PATH Verify result artifact path (default: stdout) + \\ standard-site login/logout options: + \\ --did DID AT Protocol DID to authorize (login) or forget (logout) + \\ --handle HANDLE AT Protocol handle for login --app-password, logout, or smoke + \\ --app-password Opt-in app-password login (broad account write; never OAuth scope) + \\ standard-site smoke options: + \\ --did DID Test identity DID (or use --handle) + \\ --namespace NAME Unique rkey namespace prefix (default: clock-derived) + \\ --surface-url URL Served verification-surface origin to check (optional) + \\ --indexer URL Indexer/AppView origin observed non-normatively (optional) + \\ --out PATH Smoke result artifact path (default: stdout) + \\ standard-site options (all subcommands): + \\ --session-root PATH Override the persistent session store root + \\ --profile PATH Publication profile: emit Standard.site verification and/or Nostr naddr head links + \\ --html Explicit HTML site mode → --html-dir (default dist) + \\ --html-dir HTML site mode with output directory DIR + \\ --target NAME=DIR HTML multi-target mode (repeatable; order-independent); implies HTML + \\ --out IR mode → write JSON under DIR (default .boris when --no-rag) + \\ --no-rag Explicit IR mode (JSON under --out, default .boris) + \\ --rag RAG-only mode → working-context packs under --rag-dir (default rag) + \\ --rag-dir RAG-only mode with output directory DIR + \\ --complete Complete-corpus RAG export (with --rag): the entire validated corpus — system + per-page + graph + catalog + \\ --context Context-only mode → bundle under --context-dir (default context) + \\ --context-dir DIR Context-only mode with output directory DIR + \\ --scope VALUE RAG/context entity id or collection prefix + \\ --split-size BYTES Working-RAG pack target (default 262144); context bundle byte cap + \\ --bundles-only Accepted for RAG compatibility; working packs are bundle-style by design + \\ --llms Deterministic llms.txt export → llms.txt + \\ --llms-path PATH llms.txt export path (implies --llms) + \\ --rss Deterministic RSS 2.0 export → rss.xml + \\ --rss-path PATH RSS output path (implies --rss) + \\ --sitemap Add deterministic sitemap.xml to the HTML target + \\ --sitemap-path PATH Target-root-relative sitemap path (implies --sitemap) + \\ --static-dir DIR Copy a directory of static files byte-identically into + \\ the HTML target root (robots.txt, .well-known/; #804) + \\ + \\Options: + \\ --input Content root (default: content) + \\ --textile Explicit .textile-only input adapter mode (no mixed trees) + \\ --cooklang Explicit .cook-only Cooklang recipe mode (no mixed trees) + \\ --out IR output directory (selects IR mode; default: .boris) + \\ --rag-dir RAG corpus directory (implies RAG-only; default: rag) + \\ --site-url URL Required HTTP(S) deployment URL for RSS or sitemap + \\ --pages-base-url U Normalized Pages public base URL + \\ --pages-origin U Normalized Pages public origin + \\ --pages-base-path P Normalized Pages path (empty for root/custom sites) + \\ --rss-title TITLE Required RSS channel title + \\ --rss-description T Required RSS channel description + \\ --rss-limit N RSS item limit (1–500; default 20) + \\ --html-dir HTML output directory (implies HTML; default: dist) + \\ --html-layout PATH Global layout template (default: themes/boris/layouts/main.html) + \\ --theme ROOT Theme root sugar → ROOT/layouts/main.html (+ managed assets/) + \\ --target NAME=DIR Named HTML output root (repeatable; exclusive with --html-dir) + \\ --target-layout N=P Per-target layout (NAME=PATH; may precede or follow --target) + \\ --target-profile N=P Per-target Oliver serialization profile (NAME=html|xhtml; default html) + \\ --layout-rule T S P HTML layout rule: TARGET SELECTOR LAYOUT_PATH (repeatable; max 256/target) + \\ Selectors: id: | glob: | role:trunk|satellite + \\ --incremental Content-addressed incremental HTML rendering (HTML mode) + \\ --refresh-evidence Force full evidence re-derivation, skipping reuse of + \\ unchanged committed evidence (HTML mode, #728) + \\ --watch Compatibility flag; same as the watch command; with `validate` + \\ starts the zero-write validation daemon (validate --watch) + \\ --watch-json Emit one NDJSON event per build phase on stderr (watch only, + \\ including validate --watch); see docs/contracts/watch-mode.md §8 + \\ --serve Serve the built tree over loopback HTTP (watch only); + \\ auto-reload helper: http://127.0.0.1:PORT/__boris/ + \\ --port N Loopback port for --serve (default 8090; 0 = ephemeral); + \\ implies --serve + \\ --jobs N, -j N Bounded parallel HTML page workers (1–64; HTML mode; default 1; smoke-validated) + \\ --timings Print a machine-readable phase timing/counter JSON report to stdout + \\ (opt-in; default output, diagnostics, and exit codes unchanged) + \\ --quiet Suppress progress + success stderr; errors always print + \\ (exit codes/artifacts unchanged) + \\ --format human|json Analysis output format for check/impact (default human) + \\ --format mermaid|dot Graph render format for `graph` (default mermaid) + \\ --out PATH Graph render output path (single file; default stdout) + \\ --report PATH Write the report to PATH (check/impact analysis; build/validate HTML diagnostics) + \\ --fail-on-unreferenced Make check fail when it reports unreferenced pages + \\ --profile PATH Selected publication profile for `plan` + \\ --id PAGE Recipe page entity id (`recipe-scale`; required) + \\ --factor TEXT Scale factor: 2, 1/2, 1.5, 1 1/2 (`recipe-scale`; exclusive with --servings) + \\ --servings N Target serving count (`recipe-scale`; exclusive with --factor) + \\ --out PATH Scaled-view JSON path (`recipe-scale`; default: stdout only) + \\ --plan PATH Plan artifact to sign (`nostr sign`) + \\ --key-stdin Read the hex/nsec secret key once from stdin (`nostr sign`) + \\ --out PATH Signed-event bundle output path (`nostr sign`; default: stdout) + \\ --prior PATH Prior signed bundle to reuse unchanged evidence from (`nostr sign`) + \\ --created-at N Explicit signing-time override, unix seconds (`nostr sign`; test/recovery) + \\ --bundle PATH Signed-event bundle to publish (`nostr publish`) + \\ --out PATH Publish report output path (`nostr publish`; default: stdout) + \\ -h, --help Show this help and exit 0 + \\ -V, --version Print the compiler version (`boris/`) and exit 0 + \\ + \\HTML artifacts (success; Oliver + layout splice): + \\ /**/*.html or /**/*.html + \\ /sitemap.xml (with --sitemap; path configurable) + \\ /** (with --static-dir; byte-identical passthrough files, + \\ declared in _boris/proof/artifacts.json) + \\ /.boris-cache/manifest.json (with --incremental / --watch) + \\ Staging: .boris-stage (ephemeral; committed only on full target success) + \\ + \\IR artifacts (success; --out or --no-rag): + \\ /manifest.json /graph.json /completion.json /build-report.json + \\ + \\RAG artifacts (success; same graph validation as IR): + \\ working-N.md model-facing working packs (site documents only) + \\ manifest.json sidecar manifest — NOT normally uploaded (scope, counts, hashes) + \\ (with --complete) INDEX.md UPLOAD-GUIDE.md catalog.jsonl catalog_meta.json system/** + \\ content/pages/** graph/entity-catalog.md graph/relations.md + \\ + \\Context artifacts (success; same graph validation as IR/RAG): + \\ bundle.md manifest.json graph.json pages/.md + \\ parts/part-N.md (with --split-size) + \\ + \\Conflicts (exit 2): + \\ --rag with --no-rag + \\ --no-rag with --rag-dir + \\ --textile with --cooklang + \\ --complete without --rag / --rag-dir + \\ --complete with --scope, --split-size, or --bundles-only + \\ --context / --context-dir with --rag, --out, or HTML selectors + \\ --rss / --rss-path with HTML, IR, RAG, Context, llms.txt, validate, check, or impact + \\ --sitemap / --sitemap-path without --site-url, with non-HTML modes, + \\ or with multiple targets sharing one ambiguous public URL + \\ --site-url without --rss or --sitemap + \\ --out with nostr plan (stdout carries the plan document) + \\ session flags (--did, --handle, --app-password, --prune, + \\ --source-commit, --dist, --namespace, --surface-url, --indexer, + \\ --session-root) outside standard-site + \\ --static-dir with non-HTML modes or multiple targets + \\ explicit --out with --rag or --rag-dir + \\ --html / --html-dir / --html-layout / --theme / --target / --target-layout / + \\ --target-profile / --layout-rule with --rag, --rag-dir, --context, or explicit --out + \\ --target with --html-dir + \\ --theme with --html-layout (both select the one global layout) + \\ check / impact with any HTML selector (--html, --html-dir, + \\ --html-layout, --theme, --target, --target-layout, --target-profile, + \\ --layout-rule, --sitemap / --sitemap-path, --static-dir) or --profile + \\ --watch, --incremental, or --jobs with IR (--out / --no-rag) or RAG / context + \\ validate with --profile, non-HTML exports, --incremental, --refresh-evidence, --jobs, --format, or --out + \\ validate --watch with --html-dir, --target, --serve/--port, --incremental, --jobs, or --format + \\ Invalid target names, duplicate names, output collisions, workspace escape, + \\ content/layout overlap, unknown --target-layout / --layout-rule target, + \\ duplicate or invalid layout selectors, invalid layout paths (.. / absolute), + \\ mixed theme roots, >256 rules/target + \\ + \\Exit codes: 0 success, 1 content validation, 2 usage, 3 I/O/system + \\ + \\Note: Bare `boris` builds HTML under dist/ as target "default". Use --out for JSON IR. + \\ `boris validate` observes the selected HTML target configuration but writes no artifacts. + \\ `boris validate --watch` repeats that preflight on every change and exits 0 on signal; + \\ `--report PATH` is rewritten each cycle and `--watch-json` emits mode "validate" events. + \\ `boris plan --profile PATH` emits only the normalized declaration JSON on stdout. + \\ `boris recipe-scale --input DIR --id PAGE --factor TEXT` prints a derived + \\ scaled view on stdout; `--servings N` is the same view with + \\ factor = N / current (missing current is 1). Never rewrites .cook or graph.json. + \\ `boris standard-site` (no subcommand) prints the Standard.site family list. + \\ `boris nostr plan --profile PATH` emits the offline NIP-23 publication plan on stdout; + \\ it never signs, never contacts a relay, and never reads a key. + \\ `boris nostr sign --plan PLAN --key-stdin` reads the secret key once from stdin and + \\ writes the signed-event bundle to stdout (or --out PATH); it never contacts a relay. + \\ Secrets are never accepted from argv, profile, or environment. + \\ --html / --html-dir / bare CLI map to a single target named "default". + \\ Equivalent --target / --target-layout / --layout-rule permutations yield the + \\ same config (targets sorted by name; rules canonicalized). No layout frontmatter. + \\ Frontmatter `status:` is exactly draft, published, or archived (unknown + \\ values fail validation). A draft renders to its .html files but is + \\ excluded from nav, search, sitemap, RSS, and publication projections. + \\ +; + pub fn printUsage() void { - std.debug.print( - \\Boris — Zig content compiler (HTML site + IR + optional RAG) - \\ - \\Usage: boris [options] - \\ - \\Modes: - \\ build Build the HTML site (default command) - \\ validate Validate selected HTML source/config without publication - \\ watch Build HTML, then watch and rebuild on changes - \\ check Read-only graph health report (findings do not fail by default) - \\ impact Read-only transitive impact report for a page - \\ plan Emit a normalized publication plan (no publication) - \\ \\ recipe-scale Print a derived Cooklang scale view (no rewrite) - \\ graph Read-only graph render: Mermaid (default) or Graphviz DOT - \\ proof verify Fail when committed publication-check findings exceed policy - \\ standard-site publish One-shot Standard.site publish (stored session + reconcile; never implicit) - \\ standard-site plan Emit the deterministic Standard.site plan offline (no network) - \\ standard-site records Dump the full canonical record payloads offline (no network) - \\ standard-site verify Cross-check the built head links + well-known file offline (no network) - \\ standard-site login Authorize a DID or handle and persist the session - \\ standard-site sessions List persisted sessions (DID, flavor, PDS; no secrets) - \\ standard-site logout Remove a persisted session (secure erase; does not revoke) - \\ standard-site smoke Live interop smoke against a real PDS (manual, opt-in; never in CI) - \\ nostr plan Emit the offline Nostr NIP-23 publication plan (no signing, no relay) - \\ nostr sign Sign a plan artifact into a signed-event bundle (offline; key via stdin) - \\ nostr publish Send a signed-event bundle to the plan's relays; writes the report - \\ init [DIR] Write a starter site (content, theme, profile) into DIR (default: .) - \\ (no command) Same as build - \\ standard-site publish options: - \\ --profile PATH Standard.site publication profile (required) - \\ --plan PATH Committed standard-site plan to validate (fail closed on drift) - \\ --out PATH Evidence artifact path (default: stdout) - \\ --prune Explicit prune authority (ANDs with the profile prune flag) - \\ --source-commit C Source commit recorded in the evidence bindings - \\ standard-site plan options: - \\ --profile PATH Standard.site publication profile (required) - \\ --out PATH Plan artifact path (default: stdout) - \\ standard-site records options: - \\ --profile PATH Standard.site publication profile (required) - \\ --out PATH Records artifact path (default: stdout) - \\ standard-site verify options: - \\ --profile PATH Standard.site publication profile (required) - \\ --dist DIR Built output directory to check (default: dist) - \\ --out PATH Verify result artifact path (default: stdout) - \\ standard-site login/logout options: - \\ --did DID AT Protocol DID to authorize (login) or forget (logout) - \\ --handle HANDLE AT Protocol handle for login --app-password, logout, or smoke - \\ --app-password Opt-in app-password login (broad account write; never OAuth scope) - \\ standard-site smoke options: - \\ --did DID Test identity DID (or use --handle) - \\ --namespace NAME Unique rkey namespace prefix (default: clock-derived) - \\ --surface-url URL Served verification-surface origin to check (optional) - \\ --indexer URL Indexer/AppView origin observed non-normatively (optional) - \\ --out PATH Smoke result artifact path (default: stdout) - \\ standard-site options (all subcommands): - \\ --session-root PATH Override the persistent session store root - \\ --profile PATH Publication profile: emit Standard.site verification and/or Nostr naddr head links - \\ --html Explicit HTML site mode → --html-dir (default dist) - \\ --html-dir HTML site mode with output directory DIR - \\ --target NAME=DIR HTML multi-target mode (repeatable; order-independent); implies HTML - \\ --out IR mode → write JSON under DIR (default .boris when --no-rag) - \\ --no-rag Explicit IR mode (JSON under --out, default .boris) - \\ --rag RAG-only mode → working-context packs under --rag-dir (default rag) - \\ --rag-dir RAG-only mode with output directory DIR - \\ --complete Complete-corpus RAG export (with --rag): the entire validated corpus — system + per-page + graph + catalog - \\ --context Context-only mode → bundle under --context-dir (default context) - \\ --context-dir DIR Context-only mode with output directory DIR - \\ --scope VALUE RAG/context entity id or collection prefix - \\ --split-size BYTES Working-RAG pack target (default 262144); context bundle byte cap - \\ --bundles-only Accepted for RAG compatibility; working packs are bundle-style by design - \\ --llms Deterministic llms.txt export → llms.txt - \\ --llms-path PATH llms.txt export path (implies --llms) - \\ --rss Deterministic RSS 2.0 export → rss.xml - \\ --rss-path PATH RSS output path (implies --rss) - \\ --sitemap Add deterministic sitemap.xml to the HTML target - \\ --sitemap-path PATH Target-root-relative sitemap path (implies --sitemap) - \\ --static-dir DIR Copy a directory of static files byte-identically into - \\ the HTML target root (robots.txt, .well-known/; #804) - \\ - \\Options: - \\ --input Content root (default: content) - \\ --textile Explicit .textile-only input adapter mode (no mixed trees) - \\ --cooklang Explicit .cook-only Cooklang recipe mode (no mixed trees) - \\ --out IR output directory (selects IR mode; default: .boris) - \\ --rag-dir RAG corpus directory (implies RAG-only; default: rag) - \\ --site-url URL Required HTTP(S) deployment URL for RSS or sitemap - \\ --pages-base-url U Normalized Pages public base URL - \\ --pages-origin U Normalized Pages public origin - \\ --pages-base-path P Normalized Pages path (empty for root/custom sites) - \\ --rss-title TITLE Required RSS channel title - \\ --rss-description T Required RSS channel description - \\ --rss-limit N RSS item limit (1–500; default 20) - \\ --html-dir HTML output directory (implies HTML; default: dist) - \\ --html-layout PATH Global layout template (default: themes/boris/layouts/main.html) - \\ --theme ROOT Theme root sugar → ROOT/layouts/main.html (+ managed assets/) - \\ --target NAME=DIR Named HTML output root (repeatable; exclusive with --html-dir) - \\ --target-layout N=P Per-target layout (NAME=PATH; may precede or follow --target) - \\ --target-profile N=P Per-target Oliver serialization profile (NAME=html|xhtml; default html) - \\ --layout-rule T S P HTML layout rule: TARGET SELECTOR LAYOUT_PATH (repeatable; max 256/target) - \\ Selectors: id: | glob: | role:trunk|satellite - \\ --incremental Content-addressed incremental HTML rendering (HTML mode) - \\ --refresh-evidence Force full evidence re-derivation, skipping reuse of - \\ unchanged committed evidence (HTML mode, #728) - \\ --watch Compatibility flag; same as the watch command; with `validate` - \\ starts the zero-write validation daemon (validate --watch) - \\ --watch-json Emit one NDJSON event per build phase on stderr (watch only, - \\ including validate --watch); see docs/contracts/watch-mode.md §8 - \\ --serve Serve the built tree over loopback HTTP (watch only); - \\ auto-reload helper: http://127.0.0.1:PORT/__boris/ - \\ --port N Loopback port for --serve (default 8090; 0 = ephemeral); - \\ implies --serve - \\ --jobs N, -j N Bounded parallel HTML page workers (1–64; HTML mode; default 1; smoke-validated) - \\ --timings Print a machine-readable phase timing/counter JSON report to stdout - \\ (opt-in; default output, diagnostics, and exit codes unchanged) - \\ --quiet Suppress progress + success stderr; errors always print - \\ (exit codes/artifacts unchanged) - \\ --format human|json Analysis output format for check/impact (default human) - \\ --format mermaid|dot Graph render format for `graph` (default mermaid) - \\ --out PATH Graph render output path (single file; default stdout) - \\ --report PATH Write the report to PATH (check/impact analysis; build/validate HTML diagnostics) - \\ --fail-on-unreferenced Make check fail when it reports unreferenced pages - \\ --profile PATH Selected publication profile for `plan` - \\ --id PAGE Recipe page entity id (`recipe-scale`; required) - \\ --factor TEXT Scale factor: 2, 1/2, 1.5, 1 1/2 (`recipe-scale`; exclusive with --servings) - \\ --servings N Target serving count (`recipe-scale`; exclusive with --factor) - \\ --out PATH Scaled-view JSON path (`recipe-scale`; default: stdout only) - \\ --plan PATH Plan artifact to sign (`nostr sign`) - \\ --key-stdin Read the hex/nsec secret key once from stdin (`nostr sign`) - \\ --out PATH Signed-event bundle output path (`nostr sign`; default: stdout) - \\ --prior PATH Prior signed bundle to reuse unchanged evidence from (`nostr sign`) - \\ --created-at N Explicit signing-time override, unix seconds (`nostr sign`; test/recovery) - \\ --bundle PATH Signed-event bundle to publish (`nostr publish`) - \\ --out PATH Publish report output path (`nostr publish`; default: stdout) - \\ -h, --help Show this help and exit 0 - \\ -V, --version Print the compiler version (`boris/`) and exit 0 - \\ - \\HTML artifacts (success; Oliver + layout splice): - \\ /**/*.html or /**/*.html - \\ /sitemap.xml (with --sitemap; path configurable) - \\ /** (with --static-dir; byte-identical passthrough files, - \\ declared in _boris/proof/artifacts.json) - \\ /.boris-cache/manifest.json (with --incremental / --watch) - \\ Staging: .boris-stage (ephemeral; committed only on full target success) - \\ - \\IR artifacts (success; --out or --no-rag): - \\ /manifest.json /graph.json /completion.json /build-report.json - \\ - \\RAG artifacts (success; same graph validation as IR): - \\ working-N.md model-facing working packs (site documents only) - \\ manifest.json sidecar manifest — NOT normally uploaded (scope, counts, hashes) - \\ (with --complete) INDEX.md UPLOAD-GUIDE.md catalog.jsonl catalog_meta.json system/** - \\ content/pages/** graph/entity-catalog.md graph/relations.md - \\ - \\Context artifacts (success; same graph validation as IR/RAG): - \\ bundle.md manifest.json graph.json pages/.md - \\ parts/part-N.md (with --split-size) - \\ - \\Conflicts (exit 2): - \\ --rag with --no-rag - \\ --no-rag with --rag-dir - \\ --textile with --cooklang - \\ --complete without --rag / --rag-dir - \\ --complete with --scope, --split-size, or --bundles-only - \\ --context / --context-dir with --rag, --out, or HTML selectors - \\ --rss / --rss-path with HTML, IR, RAG, Context, llms.txt, validate, check, or impact - \\ --sitemap / --sitemap-path without --site-url, with non-HTML modes, - \\ or with multiple targets sharing one ambiguous public URL - \\ --site-url without --rss or --sitemap - \\ --out with nostr plan (stdout carries the plan document) - \\ session flags (--did, --handle, --app-password, --prune, - \\ --source-commit, --dist, --namespace, --surface-url, --indexer, - \\ --session-root) outside standard-site - \\ --static-dir with non-HTML modes or multiple targets - \\ explicit --out with --rag or --rag-dir - \\ --html / --html-dir / --html-layout / --theme / --target / --target-layout / - \\ --target-profile / --layout-rule with --rag, --rag-dir, --context, or explicit --out - \\ --target with --html-dir - \\ --theme with --html-layout (both select the one global layout) - \\ check / impact with any HTML selector (--html, --html-dir, - \\ --html-layout, --theme, --target, --target-layout, --target-profile, - \\ --layout-rule, --sitemap / --sitemap-path, --static-dir) or --profile - \\ --watch, --incremental, or --jobs with IR (--out / --no-rag) or RAG / context - \\ validate with --profile, non-HTML exports, --incremental, --refresh-evidence, --jobs, --format, or --out - \\ validate --watch with --html-dir, --target, --serve/--port, --incremental, --jobs, or --format - \\ Invalid target names, duplicate names, output collisions, workspace escape, - \\ content/layout overlap, unknown --target-layout / --layout-rule target, - \\ duplicate or invalid layout selectors, invalid layout paths (.. / absolute), - \\ mixed theme roots, >256 rules/target - \\ - \\Exit codes: 0 success, 1 content validation, 2 usage, 3 I/O/system - \\ - \\Note: Bare `boris` builds HTML under dist/ as target "default". Use --out for JSON IR. - \\ `boris validate` observes the selected HTML target configuration but writes no artifacts. - \\ `boris validate --watch` repeats that preflight on every change and exits 0 on signal; - \\ `--report PATH` is rewritten each cycle and `--watch-json` emits mode "validate" events. - \\ `boris plan --profile PATH` emits only the normalized declaration JSON on stdout. - \\ `boris recipe-scale --input DIR --id PAGE --factor TEXT` prints a derived - \\ scaled view on stdout; `--servings N` is the same view with - \\ factor = N / current (missing current is 1). Never rewrites .cook or graph.json. - \\ `boris standard-site` (no subcommand) prints the Standard.site family list. - \\ `boris nostr plan --profile PATH` emits the offline NIP-23 publication plan on stdout; - \\ it never signs, never contacts a relay, and never reads a key. - \\ `boris nostr sign --plan PLAN --key-stdin` reads the secret key once from stdin and - \\ writes the signed-event bundle to stdout (or --out PATH); it never contacts a relay. - \\ Secrets are never accepted from argv, profile, or environment. - \\ --html / --html-dir / bare CLI map to a single target named "default". - \\ Equivalent --target / --target-layout / --layout-rule permutations yield the - \\ same config (targets sorted by name; rules canonicalized). No layout frontmatter. - \\ Frontmatter `status:` is exactly draft, published, or archived (unknown - \\ values fail validation). A draft renders to its .html files but is - \\ excluded from nav, search, sitemap, RSS, and publication projections. - \\ - , .{}); + std.debug.print("{s}", .{usage_text}); } /// Focused usage for the `standard-site` family. `boris standard-site` with @@ -3098,6 +3104,35 @@ const expectEqual = std.testing.expectEqual; const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; +test "usage: the public help text is well-formed and lists the shipped commands" { + // Regression: a doubled multiline marker on the `recipe-scale` mode line + // printed a stray backslash into `boris --help`. Nothing read this text, so + // assert the two properties that would have caught it. + try std.testing.expect(std.mem.indexOfScalar(u8, usage_text, '\\') == null); + + // Every shipped command must be discoverable from the Modes list: this text + // and the reference page are the two places a user looks first. + const commands = [_][]const u8{ + "build", + "validate", + "watch", + "check", + "impact", + "plan", + "recipe-scale", + "graph", + "proof verify", + "init", + "standard-site publish", + "nostr plan", + }; + for (commands) |name| { + const line = try std.fmt.allocPrint(std.testing.allocator, " {s}", .{name}); + defer std.testing.allocator.free(line); + try std.testing.expect(std.mem.indexOf(u8, usage_text, line) != null); + } +} + test "parse: default is HTML mode" { var o = try parseOptions(std.testing.allocator, &.{"boris"}); defer o.deinit(std.testing.allocator);