Context
The plugin was previously implemented incorrectly as an OpenAPI renderer. Its actual purpose is rendering OpenSpec project artifacts from a project's openspec/ folder. OpenSpec folders have a fixed structure:
openspec/
config.yaml ← project metadata
specs/
<capability>/spec.md ← canonical specs
changes/
<name>/.openspec.yaml ← change metadata
<name>/proposal.md
<name>/design.md
<name>/tasks.md
<name>/specs/<cap>/spec.md ← delta specs
archive/<date>-<name>/... ← archived changesSince OpenSpec artifacts are already Markdown, the core task of the plugin is: scan folder → copy pages into the VitePress srcDir → return sidebar/nav configuration.
Goals / Non-Goals
Goals:
openspecPlugin()as a Vite plugin reads theopenspec/folder and writes all artifact Markdown files into the VitePresssrcDir(with adjusted paths)- Generates index pages for the specs and changes sections
generateOpenSpecSidebar()returns a structured VitePress sidebar (specs group + changes group + archive group)openspecNav()returns a VitePress nav entry
Non-Goals:
- Content transformation of Markdown files (no conversion of OpenSpec syntax into Vue components)
- Support for other spec formats (OpenAPI, Swagger, AsyncAPI)
- Live reloading on changes in the
openspec/folder (can be added later)
Decisions
D1 — Copy Markdown files directly, do not transform The OpenSpec Markdown files are copied unchanged into the VitePress srcDir. VitePress renders them as normal Markdown pages. Why: Simplest implementation; no custom syntax parsers needed. OpenSpec Markdown is already readable. Alternative: Enrich Markdown with custom badges, callouts, and VitePress components. Deferred to a later iteration.
D2 — Folder structure in the VitePress srcDir mirrors the openspec/ structure
<outDir>/specs/<capability>/index.md ← copied from openspec/specs/<cap>/spec.md
<outDir>/changes/<name>/proposal.md ← copied
<outDir>/changes/<name>/design.md ← copied
<outDir>/changes/<name>/tasks.md ← copied
<outDir>/changes/archive/<name>/... ← copied
<outDir>/index.md ← generated (overview page)
<outDir>/specs/index.md ← generated
<outDir>/changes/index.md ← generatedWhy: Predictable, intuitive URL structure.
D3 — generateOpenSpecSidebar() is synchronous and reads the file system Same as generateSidebarFromSpec() previously: synchronous function that reads directly from the file system, suitable for defineConfig(). Why: VitePress evaluates the config before the Vite plugin hooks run.
D4 — .openspec.yaml is read for change metadatacreated and status fields are used for index pages and sidebar sorting. Why: Gives users meaningful additional information (date, status) without manual maintenance.
D5 — Archived changes as a collapsed sidebar group Archived changes (changes/archive/) are presented as a separate collapsed: true group. Why: Keeps the sidebar clean; older changes are accessible but not prominent.
Risks / Trade-offs
[VitePress build timing] VitePress scans pages before configResolved → files must already exist or the plugin must use an earlier hook. → Same approach as before: files are written in configResolved. On the first build the files must already be present on disk (which is always the case for existing projects).
[Delta specs in changes vs. canonical specs] Delta specs in changes/<name>/specs/ have different semantics from canonical specs. → For now, only canonical specs from openspec/specs/ and the top-level artifacts (proposal, design, tasks) of changes are rendered. Delta specs within changes are not rendered separately (they are part of the change context).
[Breaking change to the public API] generateSidebarFromSpec and the OpenAPI types are removed. → Version bump to 0.2.0; migration documented in CHANGELOG.
Migration Plan
- Replace
src/types.ts,src/utils.ts,src/plugin.tscompletely - Update
src/index.tsexports - Replace tests (new fixture: miniaturised
openspec/folder) - Update
docs/.vitepress/config.tsto the new API - Bump version to 0.2.0
Open Questions
- Should delta specs (within changes) also be rendered in a later iteration?
- Should
openspecPlugin()optionally support file watching for hot reload in dev mode?