Bullet Heaven Docs Site Implementation Plan
Bullet Heaven Docs Site Implementation Plan
Section titled “Bullet Heaven Docs Site Implementation Plan”For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Stand up a browsable Astro Starlight documentation site (docs-site/) that surfaces all existing project docs via symlinks (zero duplication) plus a new docs/research/ convention for durable research capture, deployed to Cloudflare Pages.
Architecture: A self-contained Astro Starlight project lives in docs-site/ (own package.json, same pattern as tools/design-bible/). Its content directory is populated almost entirely by symlinks pointing back into the existing repo docs — the same zero-duplication trick already used for the tvOS platform fold. A small one-time script adds the YAML frontmatter Starlight’s content schema requires to the existing docs (a real, necessary precondition, not optional polish). Deployed via Wrangler CLI direct-upload to Cloudflare Pages.
Tech Stack: Astro 5 + @astrojs/starlight (Node 18+, confirmed locally: Node v22.22.2 / npm 10.9.7), Wrangler CLI for deployment.
Global Constraints
Section titled “Global Constraints”- Platform is Astro Starlight — not Docusaurus, not MkDocs/Zensical (per design spec rationale).
docs-site/is a new, self-contained subdirectory with its ownpackage.json— same convention astools/design-bible/. It is NOT a new git repo (nogit initinside it — it’s part of the existingbullet-heavenrepo).- All existing docs are surfaced via symlinks, never copies —
docs/architecture/*.md,docs/godot-gotchas.md,docs/ROADMAP.md,docs/ideas/*.md,docs/superpowers/*.md(+specs/+plans/subdirs),DESIGN.md,PRODUCT.md,NEXT_TASKS.md,docs/research/*.md. docs/research/is a new real (non-symlinked) directory — one markdown file per investigation, already seeded withdocs/research/2026-07-04-bullet-heaven-genre-mechanics-survey.md.tools/design-bible/is untouched — stays the interactive balance-editing tool; not part of this build.- No auto-generated
data/bible.jsoncatalog page in this plan — explicitly deferred to a follow-up spec. - No access control / login gate.
- Deploy to Cloudflare Pages, Chris’s own CF account, no GitHub Actions layered on top.
- Spec reference:
docs/superpowers/specs/2026-07-04-docs-site-design.md.
Task 1: Scaffold the Astro Starlight project
Section titled “Task 1: Scaffold the Astro Starlight project”Files:
- Create:
docs-site/(entire Astro Starlight starter, viacreate-astroscaffold) - Modify: (none — this task only scaffolds)
Interfaces:
-
Consumes: nothing (first task)
-
Produces: a building
docs-site/Astro project at repo root;docs-site/package.jsonwithbuild/dev/previewscripts;docs-site/src/content/docs/as the (currently near-empty) content root that later tasks populate. -
Step 1: Scaffold the project
Run from the repo root (/Users/chris/Claude/bullet-heaven):
npm create astro@latest -- --template starlight docs-site --no-git --install --typescript strict --yesExpected: a new docs-site/ directory appears containing package.json, astro.config.mjs, tsconfig.json, src/content.config.ts, src/content/docs/ (with placeholder index.mdx, guides/example.md, reference/example.md), and its own .gitignore (which should already list node_modules, dist, .astro).
- Step 2: Verify the scaffold’s own .gitignore excludes build artifacts
cat docs-site/.gitignoreExpected: contains node_modules, dist, and .astro (or equivalents). If any are missing, append them now:
printf 'node_modules\ndist\n.astro\n' >> docs-site/.gitignore- Step 3: Remove Starlight’s placeholder example content
rm -rf docs-site/src/content/docs/guides docs-site/src/content/docs/reference- Step 4: Verify the bare scaffold builds
cd docs-site && npm run buildExpected: exits 0, prints a summary ending in something like X page(s) built in Ys, and creates docs-site/dist/index.html.
test -f docs-site/dist/index.html && echo BUILD_OKExpected output: BUILD_OK
- Step 5: Commit
git add docs-sitegit status --short # confirm node_modules/ and dist/ are NOT listedgit commit -m "$(cat <<'EOF'feat(docs-site): scaffold Astro Starlight project
Bare Starlight starter, placeholder example content removed, buildssuccessfully. Content population and Cloudflare deploy follow in latercommits — see docs/superpowers/plans/2026-07-05-docs-site.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"Task 2: Add Starlight-required frontmatter to existing project docs
Section titled “Task 2: Add Starlight-required frontmatter to existing project docs”Starlight’s content-collection schema requires a title frontmatter field on every page —
none of this project’s existing docs have YAML frontmatter (confirmed by inspection: they
all start directly with # Heading). Without this, Task 3’s symlinked docs will fail
Astro’s content-collection validation at build time. This is a one-time, additive,
mechanical change — it only prepends 3 lines to each file; nothing existing is removed
or altered.
Files:
- Create:
docs-site/scripts/add-frontmatter.mjs - Modify: every file in
docs/architecture/*.md,docs/godot-gotchas.md,docs/ROADMAP.md,docs/ideas/*.md,docs/superpowers/*.md,docs/superpowers/specs/*.md,docs/superpowers/plans/*.md,DESIGN.md,PRODUCT.md,NEXT_TASKS.md(≈85 files;docs/research/*.mdalready has frontmatter and is skipped automatically)
Interfaces:
-
Consumes: nothing new
-
Produces: every target file now starts with
---\ntitle: "<derived from first H1>"\n---\n\nfollowed by its original content unchanged — a precondition Task 3’s symlinks depend on. -
Step 1: Write the frontmatter script
import { readFileSync, writeFileSync } from 'node:fs';
const files = process.argv.slice(2);let changed = 0;
for (const file of files) { const content = readFileSync(file, 'utf8'); if (content.startsWith('---\n')) { continue; // already has frontmatter (e.g. docs/research/*.md) } const match = content.match(/^#\s+(.+)$/m); const rawTitle = match ? match[1].trim() : file.split('/').pop().replace(/\.md$/, ''); const title = rawTitle.replace(/"/g, '\\"'); const frontmatter = `---\ntitle: "${title}"\n---\n\n`; writeFileSync(file, frontmatter + content); changed++; console.log(`frontmatter added: ${file} -> "${title}"`);}
console.log(`\n${changed} file(s) updated, ${files.length - changed} already had frontmatter.`);- Step 2: Run it to verify it currently has work to do
node docs-site/scripts/add-frontmatter.mjs docs/godot-gotchas.mdhead -4 docs/godot-gotchas.mdExpected: prints frontmatter added: docs/godot-gotchas.md -> "Bullet Heaven — Godot 4.6 gotchas", and head -4 now shows:
---title: "Bullet Heaven — Godot 4.6 gotchas"---- Step 3: Run it across every remaining target file
FILES=$(find docs -name "*.md" -not -path "docs/research/*"; echo docs/research/*.md; echo DESIGN.md PRODUCT.md NEXT_TASKS.md)node docs-site/scripts/add-frontmatter.mjs $FILESExpected: one frontmatter added: ... line per file that didn’t already have frontmatter,
and the summary line reports the docs/research/*.md file(s) as “already had frontmatter.”
- Step 4: Verify no target file is missing frontmatter
for f in $(find docs -name "*.md") DESIGN.md PRODUCT.md NEXT_TASKS.md; do head -1 "$f" | grep -q '^---$' || echo "MISSING FRONTMATTER: $f"doneExpected: no output (an empty result means every file passed).
- Step 5: Spot-check that original content is unchanged below the frontmatter
tail -n +5 docs/godot-gotchas.md | head -3Expected: shows ## Godot 4.6 gotchas learned the hard way as before (the original first
content line), confirming only lines were prepended, nothing was altered or removed.
- Step 6: Commit
git add docs-site/scripts/add-frontmatter.mjs docs DESIGN.md PRODUCT.md NEXT_TASKS.mdgit commit -m "$(cat <<'EOF'docs: add Starlight-required frontmatter to existing project docs
One-time, additive, mechanical change (script in docs-site/scripts/) — everyexisting doc gets a title: frontmatter block prepended, derived from itsfirst H1. Nothing else changes. Required precondition for symlinking thesefiles into the new Starlight docs site without breaking content-collectionvalidation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"Task 3: Symlink existing docs into the Starlight content tree
Section titled “Task 3: Symlink existing docs into the Starlight content tree”Files:
- Create:
docs-site/scripts/symlink-docs.sh - Create (symlinks, not real files): ~85 entries under
docs-site/src/content/docs/{architecture,roadmap,design,housekeeping,research,specs/design,specs/plans,specs/notes}/
Interfaces:
-
Consumes: frontmatter added by Task 2 (every source file already passes Starlight’s schema)
-
Produces:
docs-site/src/content/docs/populated with symlinks across 8 subdirectories, ready for Task 4’s sidebar config to reference by directory name. -
Step 1: Write the symlink script
#!/usr/bin/env bashset -euo pipefailROOT="$(cd "$(dirname "$0")/../.." && pwd)"cd "$ROOT"CONTENT="docs-site/src/content/docs"
mkdir -p "$CONTENT/architecture" "$CONTENT/roadmap" "$CONTENT/design" \ "$CONTENT/housekeeping" "$CONTENT/research" \ "$CONTENT/specs/design" "$CONTENT/specs/plans" "$CONTENT/specs/notes"
link() { local src="$1" destdir="$2" base rel base="$(basename "$src")" rel="$(python3 -c "import os,sys;print(os.path.relpath(sys.argv[1], sys.argv[2]))" "$src" "$CONTENT/$destdir")" ln -sf "$rel" "$CONTENT/$destdir/$base"}
for f in docs/architecture/*.md docs/godot-gotchas.md; do link "$f" architecture; donefor f in docs/ROADMAP.md docs/ideas/*.md; do link "$f" roadmap; donefor f in DESIGN.md PRODUCT.md; do link "$f" design; donelink NEXT_TASKS.md housekeepingfor f in docs/research/*.md; do link "$f" research; donefor f in docs/superpowers/specs/*.md; do link "$f" specs/design; donefor f in docs/superpowers/plans/*.md; do link "$f" specs/plans; donefor f in docs/superpowers/*.md; do link "$f" specs/notes; done
echo "Symlinked $(find "$CONTENT" -type l | wc -l | tr -d ' ') files."- Step 2: Make it executable and run it
chmod +x docs-site/scripts/symlink-docs.sh./docs-site/scripts/symlink-docs.shExpected: prints Symlinked N files. where N is roughly 85 (exact count depends on how many
spec/plan files exist at execution time).
- Step 3: Verify every symlink resolves (none broken)
find docs-site/src/content/docs -xtype lExpected: no output. (-xtype l matches symlinks whose target does not exist — any output
here is a bug to fix before continuing.)
- Step 4: Spot-check one symlink resolves to the real, current file content
readlink docs-site/src/content/docs/architecture/weapons-and-buildcraft.mddiff <(cat docs-site/src/content/docs/architecture/weapons-and-buildcraft.md) docs/architecture/weapons-and-buildcraft.mdExpected: readlink prints a relative path ending in
docs/architecture/weapons-and-buildcraft.md; diff prints nothing (identical content,
since it’s the same file via symlink).
- Step 5: Verify the full build still succeeds with real content
cd docs-site && npm run buildExpected: exits 0. If it fails with a content-collection schema error, re-check Task 2 ran against every file (Step 4 of Task 2 should have caught this, but the build is the ground truth).
- Step 6: Commit
git add docs-sitegit commit -m "$(cat <<'EOF'feat(docs-site): symlink existing project docs into the content tree
Zero-duplication: docs/architecture, docs/godot-gotchas.md, docs/ROADMAP.md,docs/ideas, docs/superpowers (+specs/+plans/), DESIGN.md, PRODUCT.md,NEXT_TASKS.md, and docs/research are all symlinked in, never copied — samepattern this repo already uses for the tvOS platform fold.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"Task 4: Configure sidebar navigation
Section titled “Task 4: Configure sidebar navigation”Files:
- Modify:
docs-site/astro.config.mjs
Interfaces:
-
Consumes: the 8 content subdirectories created by Task 3 (
architecture,roadmap,design,housekeeping,research,specs/design,specs/plans,specs/notes) -
Produces: a working sidebar grouped into 6 top-level sections (Architecture, Roadmap & Ideas, Specs & Plans [with 3 sub-groups], Design & Product, Housekeeping, Research) — Task 6’s verification checks this renders correctly.
-
Step 1: Read the current scaffolded config
cat docs-site/astro.config.mjsExpected: shows the default Starlight config with a placeholder title and an empty or
example sidebar array.
- Step 2: Replace it with the full sidebar configuration
import { defineConfig } from 'astro/config';import starlight from '@astrojs/starlight';
export default defineConfig({ integrations: [ starlight({ title: 'Dark Cosmos Docs', sidebar: [ { label: 'Architecture', autogenerate: { directory: 'architecture' } }, { label: 'Roadmap & Ideas', autogenerate: { directory: 'roadmap' } }, { label: 'Specs & Plans', items: [ { label: 'Design Specs', autogenerate: { directory: 'specs/design' } }, { label: 'Implementation Plans', autogenerate: { directory: 'specs/plans' } }, { label: 'Notes', autogenerate: { directory: 'specs/notes' } }, ], }, { label: 'Design & Product', autogenerate: { directory: 'design' } }, { label: 'Housekeeping', autogenerate: { directory: 'housekeeping' } }, { label: 'Research', autogenerate: { directory: 'research' } }, ], }), ],});- Step 3: Verify it builds
cd docs-site && npm run buildExpected: exits 0, no Astro/Starlight config validation errors.
- Step 4: Commit
git add docs-site/astro.config.mjsgit commit -m "$(cat <<'EOF'feat(docs-site): configure sidebar navigation
Six top-level sections matching the design spec's information architecture,with Specs & Plans split into Design Specs / Implementation Plans / Notessub-groups mirroring docs/superpowers's actual directory structure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"Task 5: Write the hand-authored homepage
Section titled “Task 5: Write the hand-authored homepage”The homepage cannot be a symlink to CLAUDE.md (it lacks Starlight’s required frontmatter
and changes too often — see design spec). It also deliberately does NOT duplicate
CLAUDE.md’s frequently-changing “Current status” section, to avoid creating a second
copy that drifts out of sync — the exact anti-pattern this whole project exists to avoid.
Files:
- Modify:
docs-site/src/content/docs/index.md(Starlight scaffolds this asindex.mdxby default withtemplate: splashfrontmatter — replace its content entirely)
Interfaces:
-
Consumes: the 6 sidebar sections from Task 4 (linked to by name/description)
-
Produces: a real, evergreen landing page — no further task depends on its content.
-
Step 1: Replace the scaffolded homepage
rm -f docs-site/src/content/docs/index.mdx---title: Dark Cosmos — Project Docsdescription: Documentation, architecture, research, and specs for the Bullet Heaven / Dark Cosmos game project.---
This site is the browsable home for Bullet Heaven / Dark Cosmos project documentation. Itmirrors the project's git-tracked docs directly (via symlinks — there is no separate copy tokeep in sync), so it's always current with what's in the repo.
## Where to look
- **Architecture** — how each subsystem works today (content pipeline, weapons/build-craft, enemies/bosses, meta-progression, rendering/performance, dev tools), plus Godot-specific gotchas learned the hard way.- **Roadmap & Ideas** — vision, phasing, the long-term direction, and the backlog of ideas not yet spec'd.- **Specs & Plans** — every design spec and implementation plan this project has produced, organized by design vs. plan vs. ad-hoc note.- **Design & Product** — the visual/product context (theme, layout conventions, target players, brand personality).- **Housekeeping** — the standing infra/tooling backlog.- **Research** — investigations into other games, technical approaches, or anything else researched for this project, written up as a durable record rather than left in chat.
## Current project status
For the live, frequently-updated build status, see `CLAUDE.md` in the repo root — it changestoo often to duplicate here without drifting out of sync.- Step 2: Verify it builds and produces the expected page
cd docs-site && npm run buildtest -f dist/index.html && grep -q "Dark Cosmos — Project Docs" dist/index.html && echo HOMEPAGE_OKExpected output: HOMEPAGE_OK
- Step 3: Commit
git add docs-site/src/content/docs/index.mdgit commit -m "$(cat <<'EOF'feat(docs-site): write the hand-authored homepage
Deliberately evergreen — links to the 6 sidebar sections rather thanduplicating CLAUDE.md's frequently-changing status, which would drift outof sync immediately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"(Note: git add here will also pick up the index.mdx deletion automatically since it’s
tracked from Task 1’s commit.)
Task 6: Local verification pass
Section titled “Task 6: Local verification pass”Files: none created/modified — this task only runs checks against the site built in Tasks 1-5.
Interfaces:
-
Consumes: the fully-built
docs-site/dist/output -
Produces: a go/no-go signal before Task 7’s live deploy
-
Step 1: Full clean build
cd docs-site && rm -rf dist && npm run buildExpected: exits 0.
- Step 2: Confirm no broken symlinks (repeat of Task 3’s check, as a final gate)
find docs-site/src/content/docs -xtype lExpected: no output.
- Step 3: Confirm the Pagefind search index was generated
test -d docs-site/dist/pagefind && echo PAGEFIND_OKExpected output: PAGEFIND_OK
- Step 4: Serve the built site and smoke-test key routes
cd docs-site && npm run preview -- --port 4322 &PREVIEW_PID=$!sleep 2curl -sf http://localhost:4322/ | grep -q "Dark Cosmos — Project Docs" && echo HOME_OKcurl -sf http://localhost:4322/architecture/weapons-and-buildcraft/ | grep -q "build-craft" && echo ARCH_PAGE_OKcurl -sf http://localhost:4322/research/2026-07-04-bullet-heaven-genre-mechanics-survey/ | grep -q "Nova Drift" && echo RESEARCH_PAGE_OKkill "$PREVIEW_PID"Expected output: HOME_OK, ARCH_PAGE_OK, RESEARCH_PAGE_OK (order may vary slightly
depending on curl timing; if any is missing, check the preview server log before killing it).
- Step 5: No commit needed — this task is verification-only. If any check fails, fix the root cause in the relevant earlier task’s files and re-run this task before proceeding.
Task 7: Deploy to Cloudflare Pages
Section titled “Task 7: Deploy to Cloudflare Pages”Files:
- Modify:
docs-site/package.json(addwrangleras a devDependency)
Interfaces:
-
Consumes:
docs-site/dist/from Task 6’s verified build -
Produces: a live
https://bullet-heaven-docs.pages.devURL -
Step 1: Add Wrangler as a dev dependency
cd docs-site && npm install --save-dev wrangler- Step 2: Create the Cloudflare Pages project
cd docs-site && CLOUDFLARE_API_TOKEN="$CF_LUMARA_DEPLOY_TOKEN" npx wrangler pages project create bullet-heaven-docs --production-branch=mainExpected: prints confirmation the project was created. If this returns a 403/permission
error, $CF_LUMARA_DEPLOY_TOKEN doesn’t carry the account-level “Cloudflare Pages: Edit”
permission (it’s documented as a zone-level cache/DNS token, which is a different scope) —
invoke the /cloudflare skill to mint a correctly-scoped token rather than guessing further.
- Step 3: Deploy the built site
cd docs-site && npm run build && CLOUDFLARE_API_TOKEN="$CF_LUMARA_DEPLOY_TOKEN" npx wrangler pages deploy dist --project-name=bullet-heaven-docsExpected: prints a deployment URL ending in .pages.dev.
- Step 4: Verify the live site responds
curl -sfo /dev/null -w "%{http_code}\n" https://bullet-heaven-docs.pages.dev/Expected output: 200
- Step 5: Commit the dependency change
git add docs-site/package.json docs-site/package-lock.jsongit commit -m "$(cat <<'EOF'feat(docs-site): deploy to Cloudflare Pages
Direct-upload via Wrangler CLI (bullet-heaven-docs.pages.dev). This is adeliberate, flagged deviation from the design spec's "GitHub auto-deploy"framing: Cloudflare doesn't expose the git-repo-connection step via API/CLI,only through the dashboard (Workers & Pages -> project -> Settings ->Builds & deployments -> Connect to Git). Direct-upload is the fullyscriptable v1 mechanism; re-run `wrangler pages deploy` after future docchanges, or do the one-time dashboard connection later for push-to-deploy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>EOF)"- Step 6: Report the deviation to Chris
Not a code step — surface this clearly when reporting task completion: the site deploys via
wrangler pages deploy (manual re-run per update, or scriptable), not push-to-deploy from
GitHub as the spec originally framed it. Ask whether he wants the one-time dashboard Git
connection done now (I can drive it via browser automation if he’d rather not click through
it himself) or leave it on manual deploy for now.
Plan self-review notes
Section titled “Plan self-review notes”- Spec coverage: every section of
docs/superpowers/specs/2026-07-04-docs-site-design.mdmaps to a task — platform choice (Task 1), symlink structure (Tasks 2-3), sidebar IA (Task 4), homepage (Task 5), verification (Task 6), deployment (Task 7). The one deliberate deviation (direct-upload vs. GitHub-auto-deploy) is flagged inline in Task 7, not silently substituted. - Gap found and fixed during planning: the spec didn’t anticipate that Starlight requires frontmatter on every content page, which none of the existing docs have. Added as Task 2 — without it, Task 3’s symlinks would fail the build immediately.
- Explicitly out of scope, matching the spec’s non-goals: the
data/bible.jsonauto-generated catalog, access control, versioning/i18n. None of the 7 tasks touch these.