Skip to content

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 .gitignore for 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 .gitignore programmatically (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.
*
!.gitignore

Why 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 srcDir with glob before 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 absoluteOutDir cannot be written, the existing error handling in generateOpenSpecPages() catches it.

Migration Plan

  1. Implement change (one additional writeFile call in plugin.ts).
  2. Update README to note that a manual docs/openspec gitignore entry is no longer necessary.
  3. Existing consumers: the project-level gitignore entry for docs/openspec becomes redundant but harmless — no action required.