Skip to content

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.

  • Platform is Astro Starlight — not Docusaurus, not MkDocs/Zensical (per design spec rationale).
  • docs-site/ is a new, self-contained subdirectory with its own package.json — same convention as tools/design-bible/. It is NOT a new git repo (no git init inside it — it’s part of the existing bullet-heaven repo).
  • All existing docs are surfaced via symlinks, never copiesdocs/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 with docs/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.json catalog 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, via create-astro scaffold)
  • Modify: (none — this task only scaffolds)

Interfaces:

  • Consumes: nothing (first task)

  • Produces: a building docs-site/ Astro project at repo root; docs-site/package.json with build/dev/preview scripts; 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):

Terminal window
npm create astro@latest -- --template starlight docs-site --no-git --install --typescript strict --yes

Expected: 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
Terminal window
cat docs-site/.gitignore

Expected: contains node_modules, dist, and .astro (or equivalents). If any are missing, append them now:

Terminal window
printf 'node_modules\ndist\n.astro\n' >> docs-site/.gitignore
  • Step 3: Remove Starlight’s placeholder example content
Terminal window
rm -rf docs-site/src/content/docs/guides docs-site/src/content/docs/reference
  • Step 4: Verify the bare scaffold builds
Terminal window
cd docs-site && npm run build

Expected: exits 0, prints a summary ending in something like X page(s) built in Ys, and creates docs-site/dist/index.html.

Terminal window
test -f docs-site/dist/index.html && echo BUILD_OK

Expected output: BUILD_OK

  • Step 5: Commit
Terminal window
git add docs-site
git status --short # confirm node_modules/ and dist/ are NOT listed
git commit -m "$(cat <<'EOF'
feat(docs-site): scaffold Astro Starlight project
Bare Starlight starter, placeholder example content removed, builds
successfully. Content population and Cloudflare deploy follow in later
commits — 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/*.md already has frontmatter and is skipped automatically)

Interfaces:

  • Consumes: nothing new

  • Produces: every target file now starts with ---\ntitle: "<derived from first H1>"\n---\n\n followed by its original content unchanged — a precondition Task 3’s symlinks depend on.

  • Step 1: Write the frontmatter script

docs-site/scripts/add-frontmatter.mjs
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
Terminal window
node docs-site/scripts/add-frontmatter.mjs docs/godot-gotchas.md
head -4 docs/godot-gotchas.md

Expected: 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
Terminal window
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 $FILES

Expected: 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
Terminal window
for f in $(find docs -name "*.md") DESIGN.md PRODUCT.md NEXT_TASKS.md; do
head -1 "$f" | grep -q '^---$' || echo "MISSING FRONTMATTER: $f"
done

Expected: no output (an empty result means every file passed).

  • Step 5: Spot-check that original content is unchanged below the frontmatter
Terminal window
tail -n +5 docs/godot-gotchas.md | head -3

Expected: 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
Terminal window
git add docs-site/scripts/add-frontmatter.mjs docs DESIGN.md PRODUCT.md NEXT_TASKS.md
git commit -m "$(cat <<'EOF'
docs: add Starlight-required frontmatter to existing project docs
One-time, additive, mechanical change (script in docs-site/scripts/) — every
existing doc gets a title: frontmatter block prepended, derived from its
first H1. Nothing else changes. Required precondition for symlinking these
files into the new Starlight docs site without breaking content-collection
validation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EOF
)"

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

docs-site/scripts/symlink-docs.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(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; done
for f in docs/ROADMAP.md docs/ideas/*.md; do link "$f" roadmap; done
for f in DESIGN.md PRODUCT.md; do link "$f" design; done
link NEXT_TASKS.md housekeeping
for f in docs/research/*.md; do link "$f" research; done
for f in docs/superpowers/specs/*.md; do link "$f" specs/design; done
for f in docs/superpowers/plans/*.md; do link "$f" specs/plans; done
for 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
Terminal window
chmod +x docs-site/scripts/symlink-docs.sh
./docs-site/scripts/symlink-docs.sh

Expected: 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)
Terminal window
find docs-site/src/content/docs -xtype l

Expected: 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
Terminal window
readlink docs-site/src/content/docs/architecture/weapons-and-buildcraft.md
diff <(cat docs-site/src/content/docs/architecture/weapons-and-buildcraft.md) docs/architecture/weapons-and-buildcraft.md

Expected: 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
Terminal window
cd docs-site && npm run build

Expected: 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
Terminal window
git add docs-site
git 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 — same
pattern this repo already uses for the tvOS platform fold.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EOF
)"

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

Terminal window
cat docs-site/astro.config.mjs

Expected: 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
docs-site/astro.config.mjs
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
Terminal window
cd docs-site && npm run build

Expected: exits 0, no Astro/Starlight config validation errors.

  • Step 4: Commit
Terminal window
git add docs-site/astro.config.mjs
git 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 / Notes
sub-groups mirroring docs/superpowers's actual directory structure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EOF
)"

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 as index.mdx by default with template: splash frontmatter — 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

Terminal window
rm -f docs-site/src/content/docs/index.mdx
docs-site/src/content/docs/index.md
---
title: Dark Cosmos — Project Docs
description: 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. It
mirrors the project's git-tracked docs directly (via symlinks — there is no separate copy to
keep 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 changes
too often to duplicate here without drifting out of sync.
  • Step 2: Verify it builds and produces the expected page
Terminal window
cd docs-site && npm run build
test -f dist/index.html && grep -q "Dark Cosmos — Project Docs" dist/index.html && echo HOMEPAGE_OK

Expected output: HOMEPAGE_OK

  • Step 3: Commit
Terminal window
git add docs-site/src/content/docs/index.md
git commit -m "$(cat <<'EOF'
feat(docs-site): write the hand-authored homepage
Deliberately evergreen — links to the 6 sidebar sections rather than
duplicating CLAUDE.md's frequently-changing status, which would drift out
of 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.)


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

Terminal window
cd docs-site && rm -rf dist && npm run build

Expected: exits 0.

  • Step 2: Confirm no broken symlinks (repeat of Task 3’s check, as a final gate)
Terminal window
find docs-site/src/content/docs -xtype l

Expected: no output.

  • Step 3: Confirm the Pagefind search index was generated
Terminal window
test -d docs-site/dist/pagefind && echo PAGEFIND_OK

Expected output: PAGEFIND_OK

  • Step 4: Serve the built site and smoke-test key routes
Terminal window
cd docs-site && npm run preview -- --port 4322 &
PREVIEW_PID=$!
sleep 2
curl -sf http://localhost:4322/ | grep -q "Dark Cosmos — Project Docs" && echo HOME_OK
curl -sf http://localhost:4322/architecture/weapons-and-buildcraft/ | grep -q "build-craft" && echo ARCH_PAGE_OK
curl -sf http://localhost:4322/research/2026-07-04-bullet-heaven-genre-mechanics-survey/ | grep -q "Nova Drift" && echo RESEARCH_PAGE_OK
kill "$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.

Files:

  • Modify: docs-site/package.json (add wrangler as a devDependency)

Interfaces:

  • Consumes: docs-site/dist/ from Task 6’s verified build

  • Produces: a live https://bullet-heaven-docs.pages.dev URL

  • Step 1: Add Wrangler as a dev dependency

Terminal window
cd docs-site && npm install --save-dev wrangler
  • Step 2: Create the Cloudflare Pages project
Terminal window
cd docs-site && CLOUDFLARE_API_TOKEN="$CF_LUMARA_DEPLOY_TOKEN" npx wrangler pages project create bullet-heaven-docs --production-branch=main

Expected: 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
Terminal window
cd docs-site && npm run build && CLOUDFLARE_API_TOKEN="$CF_LUMARA_DEPLOY_TOKEN" npx wrangler pages deploy dist --project-name=bullet-heaven-docs

Expected: prints a deployment URL ending in .pages.dev.

  • Step 4: Verify the live site responds
Terminal window
curl -sfo /dev/null -w "%{http_code}\n" https://bullet-heaven-docs.pages.dev/

Expected output: 200

  • Step 5: Commit the dependency change
Terminal window
git add docs-site/package.json docs-site/package-lock.json
git commit -m "$(cat <<'EOF'
feat(docs-site): deploy to Cloudflare Pages
Direct-upload via Wrangler CLI (bullet-heaven-docs.pages.dev). This is a
deliberate, 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 fully
scriptable v1 mechanism; re-run `wrangler pages deploy` after future doc
changes, 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.


  • Spec coverage: every section of docs/superpowers/specs/2026-07-04-docs-site-design.md maps 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.json auto-generated catalog, access control, versioning/i18n. None of the 7 tasks touch these.