usm/cli-docs [built]
usm docs serve and usm docs build — serves generated docs locally via VitePress dev server with live reload for the spec-first review workflow, and builds a static site for Cloudflare Pages deployment. Unifies all generated docs into a single docs/ directory with auto-generated sidebar navigation.
Status: built
Usage
# Start VitePress dev server with live reload
usm docs serve
# Serve filtered help docs (public-facing)
usm docs serve --audience help
# Build static site for deployment
usm docs build
# Build static help docs site
usm docs build --audience helpWhy this exists
The spec-first workflow requires a human to review generated feature docs before the agent builds. Currently the only way to read generated docs is raw markdown in a code editor — no rendering, no navigation, no hot reload. This makes the review step painful and breaks the workflow loop. This feature adds a dev server that serves generated docs with proper rendering, sidebar navigation, search, and live reload when .usm files change. It also unifies the scattered docs output (currently split across .agents-workspace/docs/ and apps/*/.agents-workspace/docs/) into a single docs/ directory with a clear hierarchy that maps to the .usm file structure.
Design decisions
vitepress-over-custom [accepted]
Decision: Use VitePress as the rendering layer instead of building a custom server
Rationale: Building a custom markdown server with navigation, search, themes, and live reload is a whole separate product. VitePress is purpose-built for docs: markdown-native, Vite-powered HMR, built-in sidebar/search/dark mode, static build output. USM owns the content (generating markdown from .usm), VitePress owns the experience (rendering, nav, search). Clean separation of concerns. Used by Vue, Vitest, Pinia — battle-tested. Alternatives considered: custom Express+marked.js (rejected — reinventing the wheel, permanent maintenance burden), Docsify (rejected — client-side rendering, poor SEO), Nextra (rejected — heavyweight, pulls in Next.js).
unified-docs-directory [accepted]
Decision: All generated docs write to a single docs/ directory instead of scattered .agents-workspace/ paths
Rationale: Currently feature docs land in apps/<service>/.agents-workspace/docs/ and service docs in .agents-workspace/docs/shared-services/. This scattering makes navigation impossible and paths unpredictable. A unified docs/ directory with services/, features/, architecture/ subdirectories creates a clean hierarchy that maps 1:1 to the .usm file structure and works as VitePress input.
sidebar-from-system-index [accepted]
Decision: Auto-generate VitePress sidebar config from system.usm index + feature directory structure
Rationale: system.usm already has an index[] with feature id, name, ref, status, and tags. This is the authoritative source of what features exist and how they're grouped. Generating the sidebar from this index means the navigation is always in sync with the spec — no manual sidebar maintenance. Feature grouping (CLI, generators, mcp) comes from the directory structure of .usm/features/.
vitepress-optional-dependency [accepted]
Decision: VitePress is an optional peer dependency, not bundled with USM
Rationale: Not every USM user needs the docs server — some just want the CLI and MCP tools. Making VitePress optional keeps the core install lightweight. usm docs serve checks for VitePress and prints install instructions if missing, rather than failing at install time.
dont-commit-generated-docs [accepted]
Decision: Generated docs are not committed to the repo — served locally and deployed via CI
Rationale: Committing generated files creates diff noise and merge conflicts. The docs/ directory is gitignored. usm docs serve reads from the local generated output. CI runs usm generate && usm docs build for deployment. GitHub browsing links to the deployed site. This matches the current .gitignore strategy for .agents-workspace/.
How it works
Docs serve pipeline (docs-serve)
System pipeline behind usm docs serve: generate docs, config, and watch for spec changes.
- Check — VitePress installed in project (optional peer dep)
- expects: installed: true
- Generate — markdown docs from .usm files into docs/ directory (unified structure)
- Generate — .vitepress/config.ts with sidebar and nav from system.usm index
- Start — VitePress dev server (auto-port default; explicit --port strict)
- Watch — .usm/ directory for file changes
- Regenerate — affected markdown when .usm file changes
- Observe — VitePress HMR updates browser automatically
Docs build pipeline (docs-build)
System pipeline behind usm docs build: generate then produce a deployable static site.
- Generate — markdown docs from .usm files into docs/ directory
- Generate — .vitepress/config.ts with sidebar and nav
- Build — vitepress build docs/ → static HTML in docs/.vitepress/dist/
- Observe — output directory ready for deployment
VitePress config pipeline (generate-vitepress-config)
System pipeline: generate .vitepress/config.ts from system.usm index and feature directory structure.
- Parse — system.usm for identity, services[], index[]
- Scan — .usm/features/ subdirectories for grouping (cli, generators, mcp, schema)
- Map — each index entry to a sidebar item with link, label, status badge
- Generate — .vitepress/config.ts with sidebar tree, nav bar, theme config
- Write — docs/.vitepress/config.ts
Unified docs output pipeline (unified-output-structure)
System pipeline: write all generated docs into a single docs/ directory with a clear hierarchy.
- Generate — docs/index.md from system.usm (overview, stats, principles)
- Generate — docs/services/<service>.md from each service .usm
- Generate — docs/features/<area>/<feature>.md from each feature .usm
- Generate — docs/architecture/ (diagrams, data models, dependencies)
- Generate — docs/risks.md, docs/roadmap.md, docs/decisions/ from system.usm
- Generate — docs/api/openapi.yaml from features with routes
Review specs in the live preview (review-specs-live)
The spec-first review loop: a spec author starts the dev server, edits specs, and sees changes render live within a second.
- Run — usm docs serve and note the announced URL
- Review — feature pages with sidebar navigation and status badges
- Edit — a .usm file and save
- Verify — the change rendered within 1 second via hot reload — including composed guides and nav
- Build — usm docs build for deployment when review is done
Guarantees
serve-starts-on-configurable-port
usm docs serve selects its port by audience need: auto-port by default (concurrent instances never clash), explicit --port strict.
Acceptance criteria:
- [ ] Omitting --port auto-selects the next free port starting at 5173 and logs which port was chosen
- [ ] Explicit --port N is strict: fails loudly with a clear, actionable error if N is taken
- [ ] Bind-race in auto-port mode retries with a fresh probe (3 attempts) instead of failing
- [ ] Announced URL always equals the bound URL (VitePress --strictPort)
- [ ] Server accessible at http://localhost:<port>
- [ ] Clear console output with URL for review
sidebar-matches-system-index
VitePress sidebar must enumerate every feature page in every area uniformly
Acceptance criteria:
- [ ] Every index entry appears in the sidebar
- [ ] Every feature page under docs/features/<area>/ appears, whether or not the area has an index.md (issue #36)
- [ ] Sidebar groups match .usm/features/ subdirectory names
- [ ] Feature status (planned, active, deprecated) shown as badge
- [ ] Services listed in sidebar from system.usm services[]
hot-reload-on-usm-change
Editing a .usm file must trigger regeneration and browser update — of the FULL docs surface, not just developer markdown. Nav (config.mts) and the help-docs stream are part of the served contract.
Acceptance criteria:
- [ ] File watcher monitors .usm/ directory recursively
- [ ] Regeneration covers BOTH streams: developer docs AND help-docs tree
- [ ] VitePress config.mts (sidebar/nav) refreshed on regeneration — write-on-change (idempotent), so the running server reloads nav exactly when it changed
- [ ] Concurrent server startups self-organize: bind-race in auto-port mode retries with a fresh probe (3 attempts) instead of failing
- [ ] generate --only help-docs preserves .vitepress/ across the tree rebuild (config.mts must not be deleted mid-serve)
- [ ] Sidebar renders zero dead links (every link resolves to a file on disk) and zero repeated links (dedup guard)
- [ ] VitePress HMR updates browser within 1 second
- [ ] Console shows which file changed and was regenerated
build-produces-static-output
usm docs build must produce a deployable static site
Acceptance criteria:
- [ ] Static HTML output in docs/.vitepress/dist/
- [ ] All assets (CSS, JS, images) bundled
- [ ] No server runtime required to serve output
- [ ] Output works on Cloudflare Pages, GitHub Pages, Netlify, or any static host
unified-structure-is-flat-and-navigable
All docs in a single docs/ directory with predictable paths
Acceptance criteria:
- [ ] One markdown file per .usm feature
- [ ] Path pattern: docs/features/<area>/<feature>.md
- [ ] Services at docs/services/<service>.md
- [ ] No docs scattered across apps/ or .agents-workspace/
vitepress-missing-graceful-error
If VitePress is not installed, show helpful error not a crash
Acceptance criteria:
- [ ] Error message explains VitePress is optional dependency
- [ ] Install command shown (pnpm add -D vitepress)
- [ ] Exit code 1 with clear message, not a stack trace
serve-identifies-project-and-url-shape
docs serve must make the served project and the docs URL shape unambiguous
Acceptance criteria:
- [ ] Startup output prints the serving project name (system.usm identity, else package.json, else dir)
- [ ] Startup output prints the route shape /features/<area>/<slug> with the $system namespace dropped
- [ ] MCP write tools return docs_url/docs_path so agents return correct links (issue #34)
help-tree-layout-is-sibling-safe
The help-docs tree is a filtered derivative of the developer docs tree; the two output roots must be siblings (or at least the help root must not sit inside the docs root), because the filter walks the source tree and must never copy its own destination.
Acceptance criteria:
- [ ] filterForHelpAudience rejects a helpRoot that is inside docsRoot (or equal to it) with a hard error naming both paths and the config keys to change (issue #47)
- [ ] The guard runs before any filesystem mutation — a bad layout fails fast without creating or deleting anything
- [ ] No self-copy recursion is possible: copyFiltered never walks the help destination
- [ ] Default sibling layout (.usm-workspace/docs + .usm-workspace/help-docs) unaffected
- [ ] Configured nested layouts (e.g. docs: docs/ + help_docs: docs/help/) fail with an actionable error instead of ENAMETOOLONG
help-output-path-honest
Every help-docs emission point reports the path files were actually written to — never a hardcoded default (issue #44).
Acceptance criteria:
- [ ] generate --only help-docs success message names the resolved helpRoot (path.relative(root, helpRoot)), not the hardcoded .usm-workspace/help-docs/
- [ ] Custom help_docs paths (e.g. docs/help/) are reflected in console output
- [ ] docs serve/build --audience help likewise reports the configured path
help-tree-derivation-gating-documented
The help-docs tree is derived from already-generated developer docs; plain usm generate does not emit it. This gating is documented where consumers configure it.
Acceptance criteria:
- [ ] --only help-docs help text states it filters the developer docs tree and requires a prior usm generate
- [ ] config-reference documents which commands emit the help tree (generate --only help-docs, docs serve/build --audience help) and that plain generate does not (issue #44)
help-build-derives-if-missing
usm docs build --audience help auto-runs the filter pass when the help tree is absent, instead of erroring.
Acceptance criteria:
- [ ] docs build --audience help with no existing help tree runs the filter pass first and then builds
- [ ] docs build --audience help with an existing tree still rebuilds it (no stale-tree builds)
vitepress-on-demand-fetch
Docs commands work on first run without any VitePress install: missing local + missing global → on-demand fetch via npx -y vitepress@1, transparently, mutating nothing; --no-fetch-vitepress restores the hard error for hermetic/CI environments.
Acceptance criteria:
- [ ] Resolution order: project-local → global install → on-demand fetch (npx -y vitepress@1) → hard error only with --no-fetch-vitepress
- [ ] Fetch mode prints one line stating what happened and how to make it permanent (npm i -D vitepress) — never silently
- [ ] On-demand fetch pins vitepress@1 (no unpinned floating major)
- [ ] First-ever usm docs serve in a greenfield repo with no package.json works and mutates nothing (no package.json/node_modules side effects)
- [ ] Offline + no install + --no-fetch-vitepress → clear error naming the install command (today's behaviour preserved)
- [ ] spawn sites use the fetch-mode resolution (npx -y vitepress@1) consistently in both serve and build
- [ ] Config-reference and getting-started document the fetch behaviour and the pinning option
Test specifications
serve-starts-and-accessible
Given:
- vitepress_installed: true
- usm_files_present: true
When: review-specs-live
Then:
- assertion: server starts on port 5173
- assertion: http://localhost:5173 returns rendered HTML
- assertion: sidebar visible with feature list
serve-custom-port
Given:
- vitepress_installed: true
- port_flag: "--port 3000"
Then:
- assertion: server starts on port 3000
- assertion: console output shows correct URL
sidebar-reflects-all-features
Given:
- system_usm_with_27_features: true
Then:
- assertion: sidebar contains all 27 features from index
- assertion: features grouped by directory (cli, generators, mcp, schema)
- assertion: planned features show badge
hot-reload-on-edit
Given:
- dev_server_running: true
When: review-specs-live
Then:
- assertion: editing a .usm file triggers regeneration
- assertion: browser updates within 1 second
- assertion: only affected markdown file regenerated
build-produces-static-site
Given:
- vitepress_installed: true
- usm_files_present: true
When: review-specs-live
Then:
- assertion: docs/.vitepress/dist/ directory created
- assertion: index.html present
- assertion: all pages rendered as static HTML
vitepress-missing-error
Given:
- vitepress_not_installed: true
Then:
- assertion: clear error message about missing VitePress
- assertion: install command shown in output
- assertion: exit code 1
unified-structure-paths
Given:
- generate_run: true
Then:
- assertion: docs/services/cli.md exists
- assertion: docs/features/mcp/write.md exists
- assertion: docs/features/generators/feature-review.md exists
- assertion: no files in apps/*/.agents-workspace/docs/
sidebar-includes-index-bearing-areas
Given:
- feature_area_with_index_md: true
Then:
- assertion: each feature page in the area appears in the sidebar
- assertion: the area index is still reachable
Implementation
- Primary: src/cli/docs.ts
- Test code status: none
See Also
- usm/cli-generate
- usm/gen-feature-review
- usm/gen-markdown