Context
generateOpenSpecPages() in src/plugin.ts writes all generated Markdown files to <srcDir>/<outDir>/ (typically docs/openspec/). Because these are build artefacts, every consumer must manually add docs/openspec (or their custom outDir) to their project's .gitignore. VitePress requires all routable Markdown pages to reside under srcDir — it does not scan .vitepress/cache/ or any directory outside srcDir — so the files cannot be relocated to a build-only cache directory without fundamentally changing VitePress internals.
Goals / Non-Goals
Goals:
- Consumers no longer need to manually configure their
.gitignorefor the generated output directory. - The solution is backwards-compatible with all existing consumer configurations.
- No new runtime dependencies; no changes to VitePress configuration or
srcDir.
Non-Goals:
- Moving generated files outside
srcDir— VitePress does not support this without a custom theme or virtual-module layer (not worth the complexity for this problem). - Modifying the consumer's project-level
.gitignoreprogrammatically (fragile, opinionated).
Decisions
Self-managed .gitignore inside the output directory
Decision: After writing all generated pages, generateOpenSpecPages() writes a .gitignore file to <absoluteOutDir>/.gitignore that ignores all file patterns within the directory except the .gitignore itself.
# Generated by vitepress-plugin-openspec — do not commit generated files.
*
!.gitignoreWhy this over alternatives:
- Project-level gitignore append: Requires parsing/writing the consumer's
.gitignore— brittle and opinionated. - Write to
.vitepress/cache/: VitePress excludes.vitepress/from page scanning; pages would never be discoverable as routes. - Virtual markdown modules via Vite: Requires VitePress to know all routes at startup (it scans
srcDirwithglobbefore any Vite plugin runs); virtual modules alone cannot inject new routes without a companion filesystem write. - Change default outDir to a dot-directory: Would be a breaking change for existing consumers; still requires a gitignore entry.
The self-managed .gitignore is the same pattern used by Next.js (.next/), Turbo, and other build tools — well understood and zero consumer friction.
Overwrite on every run
The .gitignore file is re-written unconditionally on every generateOpenSpecPages() call. This ensures it is never stale even if the consumer edits it by mistake.
No new option
No autoGitignore: boolean option is added. The behaviour is always-on: generated files should never be committed, so there is no valid use-case for opting out.
Risks / Trade-offs
- Monorepos with custom gitignore strategies — teams that intentionally track generated docs (rare) would need to explicitly remove or override the
.gitignore. Mitigation: document this case in the README. - Write permissions — same as all other file writes; if
absoluteOutDircannot be written, the existing error handling ingenerateOpenSpecPages()catches it.
Migration Plan
- Implement change (one additional
writeFilecall inplugin.ts). - Update README to note that a manual
docs/openspecgitignore entry is no longer necessary. - Existing consumers: the project-level gitignore entry for
docs/openspecbecomes redundant but harmless — no action required.