diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index 45b1c71..46b25ef 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -128,7 +128,7 @@ Requirements for v1 (the Płytarium port). Each maps to roadmap phases. "User" b
- [ ] **DOCS-01**: `docs/` holds Markdown pages with strict YAML frontmatter (title, description, section, order), grouped into the Winter-mirroring sections, and every framework module under `modules/` is reachable from the sidebar through an API reference page ingested from its README
- [ ] **DOCS-02**: `summer docs:build` writes a self-contained static site (sidebar, on-page TOC, prev/next, edit-this-page link, client-side search, light/dark/system theme) and `summer docs:serve` previews it on loopback; no Node toolchain, and the only new dependency is `alecthomas/chroma/v2` for syntax highlighting (approved at the 11.1 plan-count checkpoint)
- [ ] **DOCS-03**: The build emits `llms.txt`, `llms-full.txt` and a clean `.md` beside every `.html` page, and a test asserts all three match the page tree
-- [ ] **DOCS-04**: Every Go fence in the docs references compiled source by `src=`; a test fails on a missing or drifted snippet, and referenced Examples carry `// Output:` and run under `go test ./...`
+- [ ] **DOCS-04**: Every Go fence in a `docs/` page references compiled source by `src=` (Go fences in ingested module READMEs are identifier-checked only, per D-18); a test fails on a missing or drifted snippet, and referenced Examples carry `// Output:` and run under `go test ./...`
- [ ] **DOCS-05**: `go test ./...` runs checkers that fail on stale identifiers (docs pages and module READMEs), broken internal links and anchors, unknown `summer`/runtime CLI command names and forbidden consuming-application names
- [ ] **DOCS-06**: A "Coming from WinterCMS" page maps Winter concepts to SummerCMS equivalents, and every SummerCMS identifier on it is checker-verified
- [ ] **DOCS-07**: An `acme/blog` porting walkthrough covers models, migrations, routes, an admin controller and a console command; its code is a real in-root package under `docs/examples/blog`, verified by DOCS-04
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 02b7fd8..3e97586 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -538,13 +538,29 @@ Plans:
1. `docs/` holds Markdown pages with frontmatter, grouped into Winter-mirroring sections (Setup, Architecture, Plugins, Backend, Database, Services, Console, API reference), and every framework module is reachable from the sidebar.
2. A `summer` CLI command builds a self-contained static site with sidebar, on-page TOC, prev/next, client-side search and dark mode, using no Node toolchain. The only new dependency is goldmark unless research names another and it is approved.
3. The build emits `llms.txt`, `llms-full.txt` and a clean `.md` for every page, and a test asserts all three stay in sync with the page tree.
- 4. Every Go example in the docs is compiled and run by `go test ./...`. An identifier checker and an internal link/anchor checker also run there and fail on stale names or broken links.
+ 4. Every Go example in a `docs/` page is compiled and run by `go test ./...` (Go code in ingested module READMEs is identifier-checked, not compiled — D-18). An identifier checker and an internal link/anchor checker also run there and fail on stale names or broken links.
5. A "Coming from WinterCMS" concept map and an `acme/blog` porting walkthrough exist, and the walkthrough's code is verified under criterion 4.
-**Plans:** 0 plans
+**Plans:** 6 plans
Plans:
-- [ ] TBD (run /gsd-plan-phase 11.1 to break down)
+**Wave 1**
+- [ ] 11.1-01-PLAN.md — Tracer: internal/docsite generator core to every output (html, .md, llms.txt, llms-full.txt, search index), README ingestion, src= snippets, `summer docs:build` and `docs:sync`
+
+**Wave 2** *(blocked on Wave 1 completion)*
+- [ ] 11.1-02-PLAN.md — Accuracy gates (identifiers, links/anchors, command names, forbidden names, go-fence policy), UI-SPEC theme with chroma/v2, search, dark mode, `summer docs:serve`, phase gate, CLAUDE.md D-13 rule
+
+**Wave 3** *(blocked on Wave 2 completion)*
+- [ ] 11.1-03-PLAN.md — Content A: Setup (incl. Coming from WinterCMS), Architecture, Plugins, Console, module Examples
+
+**Wave 4** *(blocked on Wave 3 completion)*
+- [ ] 11.1-04-PLAN.md — Content B: Database, Backend, Services (jobs, realtime, push, search, parity, transactions), Frontend and AJAX (not provided), module Examples
+
+**Wave 5** *(blocked on Wave 4 completion)*
+- [ ] 11.1-05-PLAN.md — acme/blog porting walkthrough under docs/examples/blog with Docker and scaffold-layout tests
+
+**Wave 6** *(blocked on Wave 5 completion)*
+- [ ] 11.1-06-PLAN.md — Unit tests last: planted-violation fixtures, internal/docsite coverage, SC1-SC5 acceptance, final gate, validated VALIDATION.md
### Phase 11.2: Ready to share: summercms.io website and newsletter plugin (INSERTED)
diff --git a/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/.gitkeep b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-01-PLAN.md b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-01-PLAN.md
new file mode 100644
index 0000000..0bfe3be
--- /dev/null
+++ b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-01-PLAN.md
@@ -0,0 +1,325 @@
+---
+phase: 11.1-summercms-documentation-for-humans-and-ai-agents
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - internal/docsite/docsite.go
+ - internal/docsite/load.go
+ - internal/docsite/render.go
+ - internal/docsite/emit.go
+ - internal/docsite/snippet.go
+ - internal/docsite/docsite_test.go
+ - internal/docsite/theme/templates/page.html
+ - internal/docsite/theme/assets/site.css
+ - cmd/summer/docs.go
+ - cmd/summer/docs_test.go
+ - cmd/summer/main.go
+ - cmd/summer/main_test.go
+ - modules/bonfire/example_test.go
+ - docs/site.yaml
+ - docs/index.md
+ - docs/setup/installation.md
+ - .gitignore
+ - README.md
+autonomous: true
+requirements: [DOCS-01, DOCS-02, DOCS-03, DOCS-04]
+assumption_delta_decision: no-change
+user_setup: []
+
+estimate:
+ tokens: 95000
+ raw_tokens: 95000
+ tasks: 3
+ confidence: low
+
+must_haves:
+ truths:
+ - "Per D-01, every page under docs/ is Markdown that starts with YAML frontmatter (title, description, section, order) followed by a first body line `#
`, so the file reads on the git host without a build."
+ - "Per D-02 and D-03, `summer docs:build` renders docs/ through stdlib html/template and github.com/yuin/goldmark into a self-contained static directory (default site/, gitignored) and never invokes Node or npm."
+ - "Per D-05, the build writes llms.txt (H1 `# SummerCMS`, a blockquote summary, H2 link lists with `- [Title](url.md): description` items) and llms-full.txt (every page in reading order as `# Title`, `Source: `, blank line, description, full Markdown) at the output root."
+ - "Per D-06 and D-17, every page is written as /.html with a clean Markdown sibling /.md (index.html and index.md for the landing page), and the .md has no frontmatter, starts with `# Title` and `> description`, links to sibling .md URLs and reduces fence info strings to the language word."
+ - "Per D-07, a fence whose info string carries `src=[#]` must equal the extracted source byte for byte (after trailing-newline normalisation); a drifted or missing snippet is a problem that fails go test and makes docs:build write nothing, and `summer docs:sync` rewrites drifted copies."
+ - "Per D-08, every directory under modules/ that holds non-test Go files is published as api/.html from its README.md (title = README H1, description = the summary line, section api, alphabetical order, H1 stripped, ../x/README.md links rewritten to the api page); such a directory without README.md is a `readme:` problem."
+ - "A frontmatter problem (unknown field, missing field, section not matching the directory, duplicate order within a section, first body line other than `# `, description over 160 characters) is reported as `:1: frontmatter: ` using the UI-SPEC wording."
+ - "docs:build refuses to clean a non-empty --out directory that has no .summer-docs marker, and refuses an --out equal to the repository root, inside --src, or containing --src, printing the UI-SPEC output-guard copy."
+ - "Heading IDs come from one slug implementation passed to goldmark through parser.WithIDs: lowercase, letters, digits, `_` and `-` kept, spaces to `-`, other punctuation dropped, duplicates suffixed -1, -2."
+ - "cmd/summer tests TestDocsTree, TestDocsBuildRealTree and TestDocsAIOutputsInSync pass on the real repository tree, and TestToolCommandNames lists docs:build and docs:sync."
+ - "An empty section listed in docs/site.yaml is a `section:` problem, never an empty successful build."
+ - "A src= path that is absolute, escapes the root after cleaning or through a symlink, names a dotfile or a `.env` file, sits in a directory with its own go.mod (examples/hello, admin), names an Example without a `// Output:` comment, or names a region in a _test.go function that no Test or Example function runs is a snippet problem."
+ - statement: "llms.txt and the per-page .md files are useful to an AI agent reading raw files (spec shape, predictable URLs)."
+ verification: backstop
+ prohibitions:
+ - requirement_id: DOCS-02
+ category: privacy
+ status: resolved
+ verification: judgment
+ resolution: "Theme assets are embedded and emitted under /assets/; templates reference only base_url-prefixed paths."
+ reason: "Framework docs are served to developers; a third-party request leaks visitor data and breaks offline preview."
+ statement: "The built site must not reference any third-party origin (web fonts, scripts, analytics, CDNs); every asset URL is under the site's own /assets/ path."
+ artifacts:
+ - path: "internal/docsite/docsite.go"
+ provides: "Options, Problem, Result, SyncResult, Build, Check, Sync entry points"
+ contains: "func Build("
+ - path: "internal/docsite/load.go"
+ provides: "strict site.yaml and frontmatter decoding, page walk, README ingestion"
+ contains: "DisallowUnknownField"
+ - path: "internal/docsite/render.go"
+ provides: "goldmark pipeline with GFM, shared slug IDs and AST transformers"
+ contains: "parser.WithIDs"
+ - path: "internal/docsite/emit.go"
+ provides: "html, .md, llms.txt, llms-full.txt and search-index.json emission"
+ contains: "llms-full.txt"
+ - path: "internal/docsite/snippet.go"
+ provides: "src= parsing, confinement, extraction and drift detection"
+ contains: "docs:start"
+ - path: "cmd/summer/docs.go"
+ provides: "summer docs:build and docs:sync commands"
+ contains: "docs:build"
+ - path: "modules/bonfire/example_test.go"
+ provides: "first verified Example referenced by docs"
+ contains: "// Output:"
+ - path: "docs/setup/installation.md"
+ provides: "first guide page with a src= snippet"
+ contains: "src=modules/bonfire/example_test.go#"
+ key_links:
+ - from: "cmd/summer/docs.go"
+ to: "internal/docsite/docsite.go"
+ via: "docs:build calls docsite.Build, docs:sync calls docsite.Sync"
+ pattern: "docsite\\.(Build|Sync)"
+ - from: "internal/docsite/render.go"
+ to: "github.com/yuin/goldmark"
+ via: "parse context built with parser.WithIDs(slug IDs)"
+ pattern: "parser\\.WithIDs"
+ - from: "docs/setup/installation.md"
+ to: "modules/bonfire/example_test.go"
+ via: "go fence info string src= reference kept in sync by the snippet check"
+ pattern: "src=modules/bonfire/example_test\\.go#Example"
+ - from: "internal/docsite/load.go"
+ to: "modules/*/README.md"
+ via: "dynamic module discovery ingests each README as an api page"
+ pattern: "README\\.md"
+---
+
+
+Tracer for the whole phase: the docs generator core reaches every output. `summer docs:build` loads `docs/` (strict frontmatter, `docs/site.yaml`), ingests every module README as an API reference page, renders HTML through goldmark with shared heading IDs, and writes the `.html` pages, their `.md` siblings, `llms.txt`, `llms-full.txt` and `search-index.json`. A Go fence in `docs/setup/installation.md` is a verified copy of `modules/bonfire/example_test.go`, kept honest by a drift check and `summer docs:sync`.
+
+Purpose: prove the architecture end to end (D-01, D-02, D-03, D-05, D-06, D-07, D-08, D-17) before plan 11.1-02 adds the Winter-style theme and the accuracy checkers, and before the content plans write pages. Per D-14 this is plan 1 of 6.
+
+Output: `internal/docsite` package, `summer docs:build` and `summer docs:sync`, `docs/site.yaml`, `docs/index.md`, `docs/setup/installation.md`, `modules/bonfire/example_test.go`, real-tree smoke tests in `cmd/summer`.
+
+
+
+@~/.claude/gsd-core/workflows/execute-plan.md
+@~/.claude/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-CONTEXT.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-PATTERNS.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md
+@CLAUDE.md
+@cmd/summer/main.go
+@cmd/summer/main_test.go
+@modules/tide/manifest.go
+@internal/build/scaffold.go
+@modules/bonfire/call_test.go
+
+
+Package `internal/docsite` (import path `git.golem15.com/golem15/summercms/internal/docsite`). Later plans extend these names; keep them stable.
+
+- `type Options struct` with fields `Root string` (repository root; `src=` paths and `modules/` resolve against it), `Src string` (docs source dir, default `/docs`), `Out string` (output dir, default `/site`), `BaseURL string` (overrides `site.yaml` `base_url` when non-empty). Plan 11.1-02 adds a `Commands` field.
+- `type Problem struct { File string; Line int; Rule string; Message string }` with `func (p Problem) String() string` returning `file:line: rule: message` (File repo-relative, forward slashes).
+- `type Result struct { Pages int; Out string }`, `type SyncResult struct { Snippets, Files int }`.
+- `func Check(opts Options) ([]Problem, error)`: load, verify and render in memory; writes nothing.
+- `func Build(opts Options) (Result, []Problem, error)`: runs Check; when problems exist it writes nothing and returns them; otherwise guards and cleans Out, then writes every file.
+- `func Sync(opts Options) (SyncResult, []Problem, error)`: rewrites drifted `src=` fence bodies under Src.
+- `type Site struct` decoded strictly from `docs/site.yaml`: `Title`, `Description`, `BaseURL` (`base_url`), `EditURL` (`edit_url`, with a `{path}` token), `SourceURL` (`source_url`, with a `{path}` token), `LLMSNotes []string` (`llms_notes`), `Sections []Section` (`sections`, each `{name, title}` in sidebar order).
+- `type Frontmatter struct { Title, Description, Section string; Order int }` (yaml keys `title`, `description`, `section`, `order`, all required).
+- `type Page struct` with `Source` (repo-relative source path), `URL` (extension-less path such as `setup/installation`, `api/lagoon`, `index`), `Section`, `Title`, `Description`, `Order`, `Module` (non-empty for api pages), and the Markdown body.
+- `type Ref struct { Path, Fragment string }`, `func ParseSrc(info string) (Ref, bool)`, `func Extract(root string, ref Ref) (string, error)`.
+- `const MarkerFile = ".summer-docs"`.
+
+Reading order (used by pager, llms-full.txt and the AI sync test): `index` first, then each `site.yaml` section in order with its pages sorted by `order`, with the `api` section's pages sorted by module name.
+
+
+
+
+
+
+ Task 1: Tracer: `summer docs:build` turns docs/index.md and docs/setup/installation.md into a site with every output
+ internal/docsite/docsite.go, internal/docsite/load.go, internal/docsite/render.go, internal/docsite/emit.go, internal/docsite/theme/templates/page.html, internal/docsite/theme/assets/site.css, cmd/summer/docs.go, cmd/summer/main.go, cmd/summer/main_test.go, cmd/summer/docs_test.go, docs/site.yaml, docs/index.md, docs/setup/installation.md, .gitignore
+
+ - cmd/summer/main.go (toolCommands slice, command shape, flagTrue helper)
+ - cmd/summer/main_test.go (TestToolCommandNames want list and helpWants)
+ - modules/tide/manifest.go lines 85-104 (strict goccy/go-yaml decode idiom)
+ - internal/build/scaffold.go lines 20-23 (//go:embed plus template.ParseFS idiom)
+ - modules/postcard/templates.go lines 1-30 (existing goldmark use; do not change it)
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md sections Q3, Q5 and "Security Domain"
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md sections "Copywriting Contract" (CLI output table) and "Theme Parts"
+ - .gitignore (the "# Go" block)
+
+
+Build the thinnest production-quality path through every layer the generator touches, per D-01, D-02, D-03, D-05, D-06 and D-17.
+
+1. Create `internal/docsite` with the API in the plan's interfaces block. `load.go`: decode `docs/site.yaml` with goccy/go-yaml `yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField())` into `Site` (the tide ParseManifest idiom, errors prefixed `docsite:`). Walk `Src` for `*.md`, skipping `examples/`, `site.yaml` and any `_`-prefixed file or directory. Each page file must start with a `---` line; split frontmatter at the next `---` line and decode it strictly into `Frontmatter`. Report UI-SPEC problem lines `:1: frontmatter: ` for: missing field, unknown field, section not equal to the directory name, duplicate order in a section (`order N already used by `), first body line not `# `, description over 160 characters. `docs/index.md` is the only root-level page; its section is the reserved value `index`, it is not listed in `site.yaml`, and it is rendered first in reading order. Every `site.yaml` section must have at least one page, else report `docs/site.yaml:: section: "" has no pages (add the section in the same change as its first page)`.
+2. `render.go`: one `goldmark.New` with `extension.GFM` and `parser.WithAutoHeadingID()`. Do not enable goldmark's raw-HTML (unsafe) renderer option: raw HTML in Markdown stays escaped (T-11.1-02). Strip the page's `# Title` H1 with an AST transformer so the template renders the title once.
+3. `emit.go`: render everything into an in-memory map of output path to bytes before touching disk. For each page write `.html` from `theme/templates/page.html` (html/template, embedded with `//go:embed`) and `.md` (no frontmatter; `# Title`, blank line, `> description`, blank line, body). Write `llms.txt` (H1 `# SummerCMS`, `> ` site description, the `llms_notes` as a bullet list, a `## Overview` list holding the index page, then one `## ` list per section with `- [Title](/.md): `), `llms-full.txt` (per page in reading order: `# Title`, `Source: /.html`, blank line, description, blank line, the page .md body) and `search-index.json` shaped `{"p":[{"u":"/.html","t":"","s":""}],"e":[]}` (the `e` heading entries arrive in Task 2). Copy `theme/assets/site.css` to `assets/site.css`. URLs are `base_url` plus `/` plus the path; with an empty base they are root-relative.
+4. `page.html`: ``, `` "{Page title} · SummerCMS docs" (index: "SummerCMS documentation"), ``, ``, ``, a `
+
+ go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestDocsTree|TestDocsBuildRealTree|TestToolCommandNames)$' -count=1 -v
+ non-zero exit, a "--- FAIL" line, or "no tests to run" in the output
+
+
+ - `go test ./cmd/summer -run '^TestDocsBuildRealTree$' -count=1 -v` prints `--- PASS: TestDocsBuildRealTree`.
+ - `go run ./cmd/summer docs:build --out "$(mktemp -d)/site"` exits 0 and prints a line starting `docs:build: wrote `.
+ - `grep -n 'DisallowUnknownField' internal/docsite/load.go` finds a match.
+ - `grep -n '"docs:build"' cmd/summer/main_test.go` finds a match.
+ - `grep -n '^/site/$' .gitignore` finds a match.
+ - `! grep -rn 'WithUnsafe' internal/docsite` (no match).
+ - `head -1 docs/setup/installation.md` prints `---` and the first line after the closing frontmatter delimiter is `# Installation`.
+ - Pointing `--out` at a scratch dir that holds an unrelated file makes `docs:build` exit non-zero and print `has no .summer-docs marker`, and the unrelated file still exists afterwards.
+
+ `summer docs:build` writes HTML, .md siblings, llms.txt, llms-full.txt, search-index.json and assets for the index and installation pages from a strict source tree, refuses unsafe output dirs, and the real-tree smoke tests pass.
+
+
+
+ Task 2: Every framework module appears as an API reference page, with shared heading IDs, rewritten links and heading-level search entries
+ internal/docsite/load.go, internal/docsite/render.go, internal/docsite/emit.go, internal/docsite/docsite_test.go, cmd/summer/docs_test.go
+
+ - internal/docsite/load.go, internal/docsite/render.go, internal/docsite/emit.go (as written in Task 1)
+ - modules/bonfire/README.md and modules/lighthouse/README.md (H1, summary line, `../x/README.md` link shape)
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md sections Q2 "Ingestion rules", Q5 "Search", Pitfall 3, Pitfall 7
+ - goldmark v1.8.6 in the module cache: parser/parser.go lines 80-139 and 238-245 (IDs interface, WithIDs), ast/block.go lines 302-321
+
+
+Per D-08, ingest instead of duplicating, and make anchors deterministic.
+
+1. Module discovery (`load.go`): every directory `modules/` (top level only) that contains a non-`_test.go` Go file is a module. Missing `modules//README.md` is the problem `modules/: readme: package has Go files but no README.md`. The list is discovered at run time, never hard-coded, so Phase 11 modules (conga, lighthouse, flare, beachcomber) and any later module appear without code changes. Each README becomes a page with URL `api/`, section `api`, title = the README H1 text, description = the first non-empty line after the H1, order = alphabetical by name; strip the H1 with the Task 1 transformer. The `Source` of these pages is the README path so problems cite it.
+2. Slug IDs (`render.go`): implement goldmark's `parser.IDs` (`Generate(value []byte, kind ast.NodeKind) []byte`, `Put(value []byte)`) as a GitHub-compatible slugger: lowercase, keep Unicode letters, digits, `_` and `-`, map spaces to `-`, drop other punctuation, dedupe with `-1`, `-2`. Create a fresh instance per page and pass it with `parser.NewContext(parser.WithIDs(ids))`. Expose an unexported helper that returns the ID list for a page so emission, the search index and the plan 11.1-02 link checker share one algorithm.
+3. Link rewriting (AST transformer in `render.go`): in guide pages, a relative link to another page's `.md` becomes `/.html` in HTML and `/.md` in the raw .md; a relative link to `modules//README.md` (for example `../../modules/postcard/README.md`) becomes the `api/` page; in ingested READMEs `..//README.md` becomes the `api/` page. Fragments are kept. External `http(s)://` and `mailto:` links are untouched.
+4. Search index (`emit.go`): add one `e` entry per H2 heading `{"p":,"a":"","h":"","x":""}`. Plain text is extracted from the AST (text nodes only), never from rendered HTML.
+5. Sidebar: the `api` section lists every module page by name, and llms.txt gets `## API reference` with one item per module. Extend `docs/site.yaml` handling only in code; `site.yaml` already lists `api`.
+6. Tests (smoke level; full coverage is plan 11.1-06): in `internal/docsite/docsite_test.go` add `TestSlugIDs` (duplicates get `-1`/`-2`, punctuation dropped, `_` kept) and `TestReadmeIngestion` over a `t.TempDir()` fixture (one module with README, one without: the second yields the `readme:` problem). In `cmd/summer/docs_test.go` add `TestEveryModuleInSidebar` (for each discovered `modules/` with non-test Go files, `api/.html` exists in a real-tree build and the sidebar HTML of `index.html` links it) and `TestDocsAIOutputsInSync` (the set and order of pages from `docsite` equals: the `.html` files, the `.md` siblings, the `.md` links in llms.txt, and the `Source:` lines in llms-full.txt; llms.txt line 1 is `# SummerCMS`, the next non-empty line starts with `> `, and every H2 is followed by `- [` items).
+
+Stage only this task's files.
+
+
+ go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestSlugIDs|TestReadmeIngestion|TestEveryModuleInSidebar|TestDocsAIOutputsInSync|TestDocsTree)$' -count=1 -v
+ non-zero exit, a "--- FAIL" line, or "no tests to run" in the output
+
+
+ - `go test ./cmd/summer -run '^TestEveryModuleInSidebar$' -count=1 -v` prints `--- PASS: TestEveryModuleInSidebar`.
+ - `go test ./cmd/summer -run '^TestDocsAIOutputsInSync$' -count=1 -v` prints `--- PASS: TestDocsAIOutputsInSync`.
+ - `grep -n 'parser.WithIDs' internal/docsite/render.go` finds a match.
+ - After `go run ./cmd/summer docs:build --out "$d"`, `ls "$d/api" | grep -c '\.html$'` equals the number of `modules/*` directories that hold a non-test `.go` file (22 at planning time; derive the number, do not hard-code it).
+ - `grep -c '^## API reference$' "$d/llms.txt"` prints 1.
+ - `grep -o '"a":"[^"]*"' "$d/search-index.json" | head -1` prints a heading anchor entry.
+
+ Every module README is an API reference page in the sidebar, llms.txt and search; heading IDs come from one slug implementation; the AI outputs match the page tree in order.
+
+
+
+ Task 3: Verified snippets: src= extraction, drift detection, `summer docs:sync`, and the first Example in the docs
+ internal/docsite/snippet.go, internal/docsite/docsite.go, internal/docsite/render.go, internal/docsite/docsite_test.go, cmd/summer/docs.go, cmd/summer/main_test.go, modules/bonfire/example_test.go, docs/setup/installation.md, README.md
+
+ - internal/docsite/docsite.go and internal/docsite/render.go (as written in Tasks 1-2)
+ - modules/bonfire/call_test.go lines 11-36 (fixture shape for bonfire.Call)
+ - `go doc ./modules/bonfire Call` output
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md section Q4 (src forms and the five checker rules) and "Security Domain" T-11.1-01
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md CLI output rows "Drifted snippet", "Missing snippet source", "docs:sync"
+ - README.md sections "Repository layout" and "Development"
+
+
+Per D-07 (include by reference, embedmd style, chosen in RESEARCH Q4):
+
+1. `snippet.go`: `ParseSrc(info)` splits the fence info string with `strings.Fields` and returns the `src=` value split at the first `#` into `Ref{Path, Fragment}`. Three forms: no fragment = whole file; `#Ident` = top-level Go declaration with its doc comment, taken verbatim from `token.FileSet` offsets, except that an `Example*` function yields its body dedented with the trailing `// Output:` comment (as godoc shows it, via `go/doc` Examples or equivalent); `#region` = the lines strictly between `// docs:start region` and `// docs:end region` (`# docs:start region` / `# docs:end region` in YAML), dedented, markers excluded.
+2. Confinement (T-11.1-01), each a `snippet:` problem at the fence line: path must be relative, clean (no `..` after `filepath.Clean`), resolve inside `Root` after `filepath.EvalSymlinks`, not start any segment with `.` (dotfiles, `.env*`), and for `.go` files sit in the root module, meaning no directory between the file and `Root` holds its own `go.mod` (this excludes `examples/hello/**` and `admin/**`). An `Example*` fragment without an `// Output:` comment is a problem (SC4 "run", not just compiled). A `#region` or `#Ident` fragment inside a `_test.go` file must sit in a function that is a `Test*` or `Example*` function, or that is called from one in the test files of the same directory (checked with go/ast), so D-07's "compiled and run" holds. A fragment or whole file from non-test Go source must sit in a package directory that has `_test.go` files.
+3. Drift: for every fence with `src=` in pages under `Src`, compare the fence body with `Extract` after trailing-newline normalisation. Problems use the UI-SPEC wording: `{file}:{line}: snippet: body differs from {src} (run: summer docs:sync)` and `{file}:{line}: snippet: {src} not found`. Missing file, ident or region fails `Check`, so `docs:build` writes nothing (D-07).
+4. Raw output: in each page's `.md`, reduce a fence info string to its first word (drop `src=`). In HTML, render fences with `src=` inside `` with a `` showing `path#fragment` linked to `source_url` with `{path}` filled (fragment not sent), then `
` with the escaped body; fences without `src=` get the same figure without a caption.
+5. `Sync(opts)`: rewrite each drifted fence body in place under `Src`, preserving everything else byte for byte; report `docs:sync: updated {n} snippets in {m} files` or `docs:sync: all snippets up to date`. Add `docsSyncCommand()` named `docs:sync` (description "Rewrite src= code blocks from their sources", flags `root`, `src`) in `cmd/summer/docs.go`, register it in `toolCommands()`, and add it to the `TestToolCommandNames` want list.
+6. `modules/bonfire/example_test.go` (package `bonfire_test`): `ExampleCall` defines one `bonfire.Command` named `acme:greet` with an `Arg` named `name` whose Run prints `Hello, ` through `out.Printf`, calls `bonfire.Call(context.Background(), cmds, "acme:greet", []string{"blog"}, os.Stdout)`, and ends with `// Output: Hello, blog`. go vet checks the Example name against `bonfire.Call`.
+7. `docs/setup/installation.md`: add a "Check your install" section with a `go src=modules/bonfire/example_test.go#ExampleCall` fence whose body is produced by running `go run ./cmd/summer docs:sync` (never hand-typed), explaining that console commands are plain `bonfire.Command` values.
+8. Root `README.md`: add a `docs/` row to the "Repository layout" table ("Documentation source; `summer docs:build` renders it into a static site.") and a `summer docs:build` / `summer docs:sync` line in "Development". Use neutral names only.
+9. Smoke tests in `internal/docsite/docsite_test.go`: `TestSnippetForms` (whole file, `#Ident`, Example body with Output, `#region`) and `TestSnippetConfinement` (absolute path, `../x`, `.env`, a path inside a nested-go.mod dir, a symlink escaping the root, an Example without Output, and a region inside a `_test.go` helper that no Test or Example calls each yield a `snippet:` problem) over `t.TempDir()` fixtures; `TestSyncRewritesDrift` (a drifted fixture fence is rewritten and a second Sync reports up to date).
+
+Stage only this task's files.
+
+
+ go vet ./... && go test ./internal/docsite ./cmd/summer ./modules/bonfire -count=1
+ non-zero exit or a "FAIL" line in the output
+ go test ./modules/bonfire -run '^ExampleCall$' -count=1 -v
+ non-zero exit, no "--- PASS: ExampleCall" line, or "no tests to run"
+
+
+ - `grep -n 'src=modules/bonfire/example_test.go#ExampleCall' docs/setup/installation.md` finds a match.
+ - `grep -n '// Output: Hello, blog' modules/bonfire/example_test.go` finds a match.
+ - `go run ./cmd/summer docs:sync` prints `docs:sync: all snippets up to date` on a clean tree.
+ - Appending a character inside the installation page's Go fence body in a scratch copy of `docs/` and running `go run ./cmd/summer docs:build --check --src ` exits non-zero and prints `snippet: body differs from modules/bonfire/example_test.go#ExampleCall (run: summer docs:sync)`.
+ - `go test ./internal/docsite -run '^(TestSnippetForms|TestSnippetConfinement|TestSyncRewritesDrift)$' -count=1 -v` prints three `--- PASS` lines.
+ - `grep -n '"docs:sync"' cmd/summer/main_test.go` finds a match.
+ - `grep -n 'docs:build' README.md` finds a match.
+
+ Go fences in docs are verified copies of compiled, running source; drift or a missing source fails go test and the build; `summer docs:sync` repairs copies; the first Example runs under go test.
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| docs/ Markdown and module READMEs → generator | Repository content becomes public HTML; authors are trusted but mistakes and pasted HTML are expected |
+| src= references → repository files | A fence can name any path; the generator must not publish files outside the intended source set |
+| generator → filesystem (--out) | The build deletes and rewrites a directory named on the command line |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-11.1-01 | Information disclosure | internal/docsite/snippet.go src= resolution | high | mitigate | Relative, cleaned, EvalSymlinks-confined paths; dot-segments (dotfiles, .env) and nested-go.mod dirs refused; TestSnippetConfinement plants each escape |
+| T-11.1-02 | Tampering (XSS) | internal/docsite/render.go goldmark renderer | high | mitigate | goldmark raw-HTML option left off, so HTML in Markdown is escaped; chrome rendered by html/template with contextual escaping; the unsafe option is grep-gated absent |
+| T-11.1-03 | Tampering (data loss) | internal/docsite Build output guard | high | mitigate | Refuse --out equal to root, inside or containing --src; clean only a dir holding the .summer-docs marker; nothing written when problems exist |
+| T-11.1-04 | Tampering | docs/site.yaml and frontmatter decoding | low | mitigate | goccy/go-yaml DisallowUnknownField; every field required; typos fail as frontmatter problems |
+| T-11.1-SC | Tampering | npm/pip/cargo/go installs | high | accept | This plan adds no module or package (goldmark and goccy/go-yaml are already in go.mod); the only new dependency of the phase is gated in plan 11.1-02 |
+
+
+
+- `go vet ./... && go test ./internal/docsite ./cmd/summer ./modules/bonfire -count=1` green.
+- `go run ./cmd/summer docs:build --out "$(mktemp -d)/site"` writes pages; `go run ./cmd/summer docs:sync` reports up to date.
+- `git diff --name-only 9033d81 -- go.mod` prints nothing (no dependency change in this plan).
+
+
+
+- SC1 (partial): docs/ holds frontmatter pages grouped by site.yaml sections; every module reachable from the sidebar through its api page.
+- SC2 (partial): a `summer` CLI command builds a self-contained static site with no Node toolchain.
+- SC3: llms.txt, llms-full.txt and a clean .md per page are emitted and TestDocsAIOutputsInSync asserts they match the page tree.
+- SC4 (partial): the one Go example in docs is a src= copy of a running Example; drift fails go test.
+
+
+## Artifacts this phase produces
+
+- Package `internal/docsite`: `Options`, `Problem` (`String`), `Result`, `SyncResult`, `Check`, `Build`, `Sync`, `Site`, `Section`, `Frontmatter`, `Page`, `Ref`, `ParseSrc`, `Extract`, `MarkerFile`; embedded `theme/templates/page.html`, `theme/assets/site.css`.
+- CLI commands: `summer docs:build` (flags `--root`, `--src`, `--out`, `--base-url`, `--check`), `summer docs:sync` (flags `--root`, `--src`).
+- Example: `bonfire.ExampleCall` in `modules/bonfire/example_test.go`.
+- Files: `docs/site.yaml`, `docs/index.md`, `docs/setup/installation.md`, `cmd/summer/docs.go`, `cmd/summer/docs_test.go`, `internal/docsite/*.go`, `.gitignore` `/site/` entry, README.md layout and development lines.
+- Output files per build: `/.html`, `/.md`, `index.html`, `index.md`, `api/.html|.md`, `llms.txt`, `llms-full.txt`, `search-index.json`, `assets/site.css`, `.summer-docs`.
+- Tests: `TestDocsTree`, `TestDocsBuildRealTree`, `TestEveryModuleInSidebar`, `TestDocsAIOutputsInSync` (cmd/summer); `TestSlugIDs`, `TestReadmeIngestion`, `TestSnippetForms`, `TestSnippetConfinement`, `TestSyncRewritesDrift` (internal/docsite).
+
+
diff --git a/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-02-PLAN.md b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-02-PLAN.md
new file mode 100644
index 0000000..94d33f6
--- /dev/null
+++ b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-02-PLAN.md
@@ -0,0 +1,379 @@
+---
+phase: 11.1-summercms-documentation-for-humans-and-ai-agents
+plan: 02
+type: execute
+wave: 2
+depends_on: ["11.1-01"]
+files_modified:
+ - internal/docsite/docsite.go
+ - internal/docsite/render.go
+ - internal/docsite/emit.go
+ - internal/docsite/check_identifiers.go
+ - internal/docsite/check_links.go
+ - internal/docsite/check_commands.go
+ - internal/docsite/check_forbidden.go
+ - internal/docsite/check_policy.go
+ - internal/docsite/highlight.go
+ - internal/docsite/serve.go
+ - internal/docsite/checks_test.go
+ - internal/docsite/docsite_test.go
+ - internal/docsite/theme_test.go
+ - internal/docsite/theme/templates/page.html
+ - internal/docsite/theme/templates/header.html
+ - internal/docsite/theme/templates/sidebar.html
+ - internal/docsite/theme/templates/toc.html
+ - internal/docsite/theme/templates/pager.html
+ - internal/docsite/theme/templates/footer.html
+ - internal/docsite/theme/templates/search.html
+ - internal/docsite/theme/templates/icons.html
+ - internal/docsite/theme/templates/404.html
+ - internal/docsite/theme/assets/site.css
+ - internal/docsite/theme/assets/site.js
+ - internal/docsite/theme/assets/search.js
+ - internal/docsite/theme/assets/theme-init.js
+ - internal/docsite/theme/assets/LICENSE-lucide.txt
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-400-normal.woff2
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-600-normal.woff2
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-ext-400-normal.woff2
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-ext-600-normal.woff2
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-400-italic.woff2
+ - internal/docsite/theme/assets/fonts/dm-sans-latin-ext-400-italic.woff2
+ - internal/docsite/theme/assets/fonts/dm-mono-latin-400-normal.woff2
+ - internal/docsite/theme/assets/fonts/dm-mono-latin-ext-400-normal.woff2
+ - internal/docsite/theme/assets/fonts/LICENSE-dm-sans.txt
+ - internal/docsite/theme/assets/fonts/LICENSE-dm-mono.txt
+ - cmd/summer/docs.go
+ - cmd/summer/docs_test.go
+ - cmd/summer/main_test.go
+ - scripts/check-phase11.1.sh
+ - go.mod
+ - go.sum
+ - README.md
+ - CLAUDE.md
+ - .planning/todos/pending/wristband-neutral-resource-default.md
+autonomous: true
+requirements: [DOCS-02, DOCS-04, DOCS-05, DOCS-08]
+assumption_delta_decision: no-change
+user_setup: []
+
+estimate:
+ tokens: 140000
+ raw_tokens: 140000
+ tasks: 3
+ confidence: low
+
+must_haves:
+ truths:
+ - "Per D-12, an inline code span `pkg.Ident`, `pkg.Type.Member`, `*pkg.Ident`, `pkg.Ident[...]` or `pkg.Ident(...)` whose first segment is a discovered module (or sub-package) name and whose Ident does not exist in that package fails go test, docs:build and the gate with `:: identifier: . does not exist in modules/`; this covers docs/ pages, every module README and the root README."
+ - "Per D-12, a relative link or `#anchor` in a docs page or ingested README that does not resolve to a page or to a heading ID (computed by the renderer's slug IDs) fails with `link: does not resolve` or `link: # not found in `."
+ - "A `summer ` token in a docs `sh` fence or code span must be a name from cmd/summer toolCommands(), and a `./bin/` token must be an application command collected by calling the module command constructors the generated main uses plus centrifugo.Commands and flare.Commands, or a bonfire.Command literal declared under docs/examples or examples; the checker receives these sets and holds no hard-coded command list."
+ - "Per D-11, a consuming-application name in any page source, snippet copy or built output (HTML, .md, llms.txt, llms-full.txt, search-index.json) fails with `forbidden: consuming-application name in output`, without echoing the matched word."
+ - "Every go fence in a docs/ page carries src=; one without it fails with `snippet: go code block has no src= reference`; an unknown `> [!TYPE]` callout fails with `callout: unknown type (use NOTE, TIP or WARNING)`; a docs/ heading that is not plain ASCII text or contains a link or code span fails with a `heading:` problem."
+ - "Per D-04, every rendered page has the Winter-style shell: header with search trigger and theme toggle, sidebar grouped by section, on-page TOC when a page has at least 2 H2/H3 headings, prev/next pager in reading order crossing sections, Edit this page (edit_url) and View as Markdown actions, heading permalinks, callouts, copy buttons and a footer linking llms.txt and llms-full.txt; the version selector and Docs/API/Markup/UI tabs are left out."
+ - "Per D-15, code fences are highlighted at build time by github.com/alecthomas/chroma/v2 called from a custom goldmark NodeRenderer that maps chroma token types onto the UI-SPEC classes tok-kw, tok-key, tok-str, tok-com, tok-num and tok-prompt; goldmark-highlighting is not used, and go.mod gains only chroma/v2 and its transitive regexp2 module."
+ - "Per D-03, `summer docs:serve` builds the site and serves it on 127.0.0.1:8088 by default, refuses a non-loopback --addr with the UI-SPEC copy unless --allow-remote is passed, serves 404.html with status 404, and rebuilds on change while keeping the last good build."
+ - "Per D-13 and DOCS-08, CLAUDE.md's Documentation section states that API, config-key or CLI changes update the module README and the affected docs pages in the same change, and names the automated checkers; per D-17 it notes that config-key checking is deferred."
+ - "Per D-17, `.planning/todos/pending/wristband-neutral-resource-default.md` records that the wristband default resource URL names the consuming application; the wristband API is unchanged in this phase."
+ - "scripts/check-phase11.1.sh --self-test plants an unknown identifier, a missing README, a drifted snippet, a frontmatter typo, a broken anchor, an unknown command, a forbidden name, a go fence without src= and an unknown callout, and each is refused for its own rule."
+ - "Empty search query shows the heading 'Search the documentation' and its body copy; no results list renders."
+ - "Search with 0 matches shows 'No results for \"{query}\"' and its body copy; 1 to 20 matches render rows; more than 20 are truncated to the top 20 by rank; the live region says 'No results', '1 result' or '{n} results'."
+ - "While search-index.json is being fetched on first open, 'Loading the search index…' shows in the results area."
+ - "A rejected index fetch shows 'Search needs a web server. Run `summer docs:serve` and open the address it prints.'; a non-200 or invalid JSON response shows 'The search index could not be loaded. Reload the page to try again.'; the dialog stays usable and closable."
+ - "The search results list has max-height 60vh with its own scroll; excerpts clamp to 2 lines; titles and headings wrap."
+ - "The sidebar scrolls on its own (height calc(100vh - 64px), overflow-y auto) and site.js scrolls the active item into view on load; sidebar and TOC items wrap with min-height 32px and no ellipsis."
+ - "With fewer than 2 H2/H3 headings neither the TOC column nor toc-inline renders."
+ - "The first page renders only Next (kept in the right column), the last page only Previous; a cross-section target adds the section line; a one-page site renders no pager nav; pager titles wrap and both cards stretch to equal height."
+ - "The build writes 404.html with 'Page not found' and docs:serve returns it with status 404."
+ - "pre and tables scroll horizontally inside their own box (overflow-x auto); inline code and bare URLs use overflow-wrap anywhere."
+ - "If localStorage throws, the theme falls back to system for the session, the toggle still cycles and no error is shown."
+ - "The copy button is not rendered without navigator.clipboard; a rejected write announces 'Copy failed. Select the code and copy it manually.'"
+ - statement: "Search result rows show section › title, heading and a 2-line excerpt with mark highlights, and arrow keys and Enter work (manual UAT via summer docs:serve)."
+ verification: backstop
+ - statement: "theme-init.js applies dark or light before first paint in all three modes, so there is no flash of the wrong theme (manual UAT: reload in each mode)."
+ verification: backstop
+ - statement: "The UI-SPEC contrast pairs hold on a guide page and an API reference page in light and dark at 1280px, 1024px and 375px (manual UAT)."
+ verification: backstop
+ - statement: "Without JS the sidebar renders above the content below 1024px and the search and theme buttons are hidden (manual UAT with JS disabled)."
+ verification: backstop
+ - "Per D-18 (user decision): the strict src= policy applies to Go fences in docs/ pages; Go fences inside ingested module READMEs are rendered as written and are covered by the identifier checker, not by src= verification."
+ prohibitions:
+ - requirement_id: DOCS-02
+ category: privacy
+ status: resolved
+ verification: judgment
+ resolution: "Fonts and icon paths are vendored under theme/assets with their licence files; templates and JS reference only base_url-prefixed /assets/ paths."
+ reason: "A docs site that phones home to a font CDN or analytics service leaks every visitor's reading history."
+ statement: "The theme must not load fonts, icons or scripts from any third-party origin and must not include analytics or tracking."
+ artifacts:
+ - path: "internal/docsite/check_identifiers.go"
+ provides: "module identifier index and span checker with go doc fallback"
+ contains: "go/parser"
+ - path: "internal/docsite/check_links.go"
+ provides: "internal link and anchor checker sharing the renderer IDs"
+ contains: "not found in"
+ - path: "internal/docsite/check_commands.go"
+ provides: "summer and application command-name checker"
+ contains: "is not a summer or application command"
+ - path: "internal/docsite/check_forbidden.go"
+ provides: "consuming-application name check over sources and outputs"
+ contains: "consuming-application name in output"
+ - path: "internal/docsite/highlight.go"
+ provides: "goldmark NodeRenderer for fenced code using chroma/v2"
+ contains: "chroma/v2"
+ - path: "internal/docsite/serve.go"
+ provides: "loopback preview server with 404 handling and rebuild"
+ contains: "--allow-remote"
+ - path: "scripts/check-phase11.1.sh"
+ provides: "phase gate with --self-test and --all"
+ contains: "--self-test"
+ - path: "CLAUDE.md"
+ provides: "D-13 documentation rule and named checkers"
+ contains: "affected pages under `docs/`"
+ key_links:
+ - from: "cmd/summer/docs.go"
+ to: "internal/docsite/check_commands.go"
+ via: "docsCommands() passes toolCommands() names and module runtime command names into docsite.Options.Commands"
+ pattern: "toolCommands\\(\\)"
+ - from: "internal/docsite/check_links.go"
+ to: "internal/docsite/render.go"
+ via: "anchors computed with the same slug IDs the renderer passes to parser.WithIDs"
+ pattern: "slug|IDs"
+ - from: "internal/docsite/highlight.go"
+ to: "github.com/alecthomas/chroma/v2"
+ via: "lexers.Get + Tokenise mapped onto tok-* classes"
+ pattern: "chroma"
+ - from: "scripts/check-phase11.1.sh"
+ to: "cmd/summer docs:build --check"
+ via: "self-test plants violations in a scratch root and expects refusal by rule"
+ pattern: "docs:build --check"
+---
+
+
+Make the docs trustworthy and usable. Every accuracy rule becomes a `go test ./...` failure and a `docs:build` refusal (identifiers, links and anchors, command names, consuming-application names, strict go-fence policy, callouts, headings), the phase gate `scripts/check-phase11.1.sh` orchestrates them, and the site gets the full WinterCMS-style theme from 11.1-UI-SPEC.md with chroma highlighting, client-side search, dark mode and `summer docs:serve`. CLAUDE.md records the D-13 rule.
+
+Purpose: D-04, D-11, D-12, D-13, D-15 and D-17 before any content is written, so plans 11.1-03 to 11.1-05 are checked as they write.
+
+Per D-18 (user decision): the src= policy covers Go fences in `docs/` pages. Go fences in the ingested module READMEs are shown as written and covered by the identifier checker only. Converting README fences to src= copies would need edits to every module README, including files gap plan 11-08 is changing, so it is not in this plan.
+
+Output: checkers, theme, `docs:serve`, gate script, CLAUDE.md edit, wristband todo, chroma/v2 in go.mod.
+
+
+
+@~/.claude/gsd-core/workflows/execute-plan.md
+@~/.claude/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/STATE.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-CONTEXT.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-PATTERNS.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md
+@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-01-SUMMARY.md
+@CLAUDE.md
+@cmd/summer/main.go
+@cmd/summer/docs.go
+@internal/docsite/docsite.go
+@internal/build/build.go
+@scripts/check-phase11.sh
+@scripts/check-phase10.2.sh
+
+
+Additions to `internal/docsite` in this plan:
+
+- `type Commands struct { Tool []string; App []string }` and field `Options.Commands *Commands`. A nil `Commands` is a problem (`command: no command set supplied`), never a silent skip.
+- `func Serve(ctx context.Context, opts Options, addr string, allowRemote bool, out io.Writer) error` and `func Handler(dir string) http.Handler` (static files from dir, 404.html with status 404).
+- Problem rules added: `identifier`, `link`, `command`, `forbidden`, `callout`, `heading` (plus `snippet` for go fences without src=).
+
+In `cmd/summer/docs.go`: `func docsCommands() *docsite.Commands` collects names; `docs:serve` command with flags `root`, `src`, `base-url`, `addr` (default `127.0.0.1:8088`), bare `allow-remote`.
+
+Runtime command constructors the generated app main calls (internal/build/build.go lines 114-118): `lagoon.RuntimeCommands(app, plugins)`, `conga.RuntimeCommands(app, plugins)`, `surf.ServeCommand(app, plugins)`, `surf.RouteListCommand(app, plugins)`, `cabana.RuntimeCommands(app)`; plus `lagoon.KeyGenerateCommand()`, `centrifugo.Commands(app)` (modules/lighthouse/centrifugo) and `flare.Commands(app)`, which applications append. `backpack.New(cfg *compass.Config) *App` builds the app handle; the constructors only capture it.
+
+
+
+
+
+
+ Task 1: Tracer: a stale identifier in any docs page or module README fails go test, docs:build and the phase gate
+ .planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-SUMMARY.md exists (gap plan 11-08 has committed its lagoon and cabana changes, so module READMEs are stable)
+ internal/docsite/check_identifiers.go, internal/docsite/docsite.go, internal/docsite/checks_test.go, cmd/summer/docs_test.go, scripts/check-phase11.1.sh
+
+ - internal/docsite/docsite.go and internal/docsite/load.go (Check pipeline from plan 11.1-01)
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md section Q6 "Identifier checker" and Q7
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-PATTERNS.md section "scripts/check-phase11.1.sh"
+ - scripts/check-phase11.sh lines 1-60 and the final case block (mode layout, refuse, usage)
+ - scripts/check-phase10.2.sh lines 100-135 (expect_refusal, run_self_test)
+ - modules/lighthouse/README.md, modules/conga/README.md (Phase 11 README span style)
+
+
+Per D-12, wire one checker through every layer: index, Check, docs:build refusal, real-tree test and gate.
+
+1. `check_identifiers.go`: build an index with stdlib `go/parser` (non-test files only) for every directory under `modules/` that holds Go files, including sub-packages (`lagoon/attach`, `lighthouse/centrifugo`, `beachcomber/typesense`), keyed by the last path element. Record exported top-level funcs, types, consts and vars; methods keyed by receiver base type (strip `*`, `IndexExpr`, `IndexListExpr` so Go 1.27 generic methods such as `func (b *Bus) Fire[T any]` index under `Bus`); struct fields including embedded type names; interface methods. Two directories with the same key is a problem naming both paths.
+2. Spans: walk goldmark `ast.CodeSpan` nodes (never fenced blocks) in every docs page, every ingested module README and the root README.md. Match `^\*?(pkg)\.(Ident)(\.Member)?(\[[^\]]*\])?(\(.*\))?$` only when `pkg` is an index key and `Ident` starts uppercase; ignore a lowercase `Member`. So `http.Handler`, `fields.yaml`, `acme.blog`, `summer.yaml` and config keys are ignored (RESEARCH Pitfall 6).
+3. On an index miss, fall back to `exec.Command("go", "doc", "./modules/", "[.]")` run in `Root` (argument list, no shell; Ident and Member already match the identifier regex) and accept exit 0, which covers promoted members through embedding. Otherwise report `{file}:{line}: identifier: {pkg}.{Ident} does not exist in modules/{pkg}` with the span's source line (README line, or page line counted from the top of the file including frontmatter).
+4. Call the checker from `Check`, so `docs:build`, `docs:build --check` and `TestDocsTree` all enforce it. Fix any miss the real tree reports by correcting the README or page text, never by changing a module API (phase boundary); a README fix for a module the plan does not list is allowed and goes in this task's commit.
+5. `internal/docsite/checks_test.go`: `TestIdentifierChecker` over a `t.TempDir()` fixture module: a known func, method, generic method, field and interface method pass; `fixture.Missing` fails with the exact problem text; `http.Handler` and `fields.yaml` are ignored.
+6. `scripts/check-phase11.1.sh`, following check-phase11.sh and check-phase10.2.sh: header comment, `set -euo pipefail`, `ROOT="${PHASE11_1_ROOT:-...}"`, `refuse()`, `usage()` exiting 2, modes `--preconditions` (11-08-SUMMARY.md exists; conga, lighthouse, flare and beachcomber have README.md and a root README table row), `--deps` (compare the module paths in go.mod with `git show "${PHASE11_1_DEPS_BASE:-9033d81}":go.mod`: nothing removed; added paths must be a subset of `github.com/alecthomas/chroma/v2` and `github.com/dlclark/regexp2/v2`), `--docs` (build the summer binary once into a temp dir, run `docs:build --out "$tmp/site"`, require index.html, index.md, llms.txt, llms-full.txt, search-index.json, assets/site.css and .summer-docs, and no `.go` file under the output), `--forbidden` (case-insensitive grep of docs/ and a fresh build output for the consuming-application spellings listed in RESEARCH Q6 plus the accented variant the Phase 11 hygiene regex covers; any hit refuses without printing the matched line content beyond the file name), `--go` (`go vet ./...` then `go test ./...` without `-short`), `--self-test` and `--all`. `--self-test` runs `bash -n` on itself, copies `docs/`, `modules/`, `go.mod` and `go.sum` into a scratch root, and for each plant runs ` docs:build --check --root ` expecting a non-zero exit whose output contains the rule text: unknown identifier (`identifier: bonfire.NoSuchThing does not exist in modules/bonfire`), module dir with a Go file and no README (`readme: package has Go files but no README.md`), drifted installation snippet (`snippet: body differs`), unknown frontmatter field (`frontmatter: unknown field`). Each mode prints `phase11.1 passed`.
+
+Stage only this task's files plus any README it had to correct.
+
+
+ go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestIdentifierChecker|TestDocsTree)$' -count=1 -v
+ non-zero exit, a "--- FAIL" line, or "no tests to run"
+ bash -n scripts/check-phase11.1.sh && scripts/check-phase11.1.sh --self-test && scripts/check-phase11.1.sh --preconditions && scripts/check-phase11.1.sh --deps && scripts/check-phase11.1.sh --docs
+ non-zero exit, a line starting "refuse:", or a missing "phase11.1 self-test passed" line
+
+
+ - `go test ./internal/docsite -run '^TestIdentifierChecker$' -count=1 -v` prints `--- PASS: TestIdentifierChecker`.
+ - `scripts/check-phase11.1.sh --self-test` prints `phase11.1 self-test passed`.
+ - A scratch copy of docs/ with `` `lighthouse.NoSuchThing` `` added to docs/index.md makes `go run ./cmd/summer docs:build --check --src ` exit non-zero and print `identifier: lighthouse.NoSuchThing does not exist in modules/lighthouse`.
+ - `grep -n 'go/parser' internal/docsite/check_identifiers.go` finds a match.
+ - `go test ./cmd/summer -run '^TestDocsTree$' -count=1` passes on the real tree (READMEs of all 22 modules and the root README included).
+
+ The identifier rule runs over docs pages, module READMEs and the root README in go test, docs:build and the gate, and the gate's self-test proves it refuses a planted stale name.
+
+
+
+ Task 2: Links, anchors, command names, consuming-application names and the go-fence policy fail the build; CLAUDE.md records the rule
+ internal/docsite/check_links.go, internal/docsite/check_commands.go, internal/docsite/check_forbidden.go, internal/docsite/check_policy.go, internal/docsite/docsite.go, internal/docsite/checks_test.go, internal/docsite/docsite_test.go, cmd/summer/docs.go, cmd/summer/docs_test.go, cmd/summer/main_test.go, scripts/check-phase11.1.sh, CLAUDE.md, .planning/todos/pending/wristband-neutral-resource-default.md
+
+ - internal/docsite/render.go (slug IDs helper and link transformer from plan 11.1-01)
+ - internal/build/build.go lines 105-130 (runtime command constructors in the generated main)
+ - modules/lagoon/commands.go, modules/conga/commands.go, modules/cabana/commands.go, modules/surf/routelist_command.go, modules/lighthouse/centrifugo/commands.go, modules/flare/commands.go (constructor signatures)
+ - cmd/summer/main_test.go TestToolDoesNotImportExamplePlugins
+ - CLAUDE.md "## Documentation" section (4 bullets)
+ - .planning/todos/pending/redacting-slog-handler.md (todo frontmatter format)
+ - modules/wristband/server.go lines 56-105 (the default resource value and comments; read only, do not edit)
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md Q6 "Link and anchor checker", "CLI command-name checker", "Forbidden-name check"
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md CLI output table
+
+
+1. `check_links.go` (D-12): walk `ast.Link` and `ast.Image` in every page (guides and ingested READMEs). `http(s)://` and `mailto:` are allowed and never fetched. `#frag` must be a heading ID of the same page; a relative `*.md` link (optional `#frag`) must resolve to a page in the tree or to an ingested `modules//README.md`, and its fragment must be a heading ID of the target. Any other relative repo path in a guide page (`.planning/`, `examples/`, source files) fails. IDs come from the same slug helper the renderer passes to `parser.WithIDs`. Problems: `{file}:{line}: link: {target} does not resolve` and `{file}:{line}: link: #{anchor} not found in {page}`.
+2. `check_commands.go`: in `sh`/`shell`/`bash` fences and in code spans of docs pages and ingested READMEs, find `summer ` (optionally after `$ `) and `./bin/` tokens. A `summer` name must be in `Commands.Tool`; a `./bin/` name must be in `Commands.App` or in the set of `bonfire.Command` composite literals with a string-literal `Name` found by `go/parser` in non-test Go files under `docs/examples/` and `examples/` (the tool must not import example plugins). Problem: `{file}:{line}: command: "{name}" is not a summer or application command`. Flags and arguments after the name are ignored.
+3. `cmd/summer/docs.go`: `docsCommands()` returns `Tool` = names from `toolCommands()` and `App` = names from `lagoon.RuntimeCommands`, `lagoon.KeyGenerateCommand`, `conga.RuntimeCommands`, `surf.ServeCommand`, `surf.RouteListCommand`, `cabana.RuntimeCommands`, `centrifugo.Commands` and `flare.Commands`, called on `backpack.New` with an empty config and nil plugins (confirm the constructors only capture the app; if one dereferences config at construction, pass an empty `compass` config loaded from a temp directory instead). Pass it to every `docsite` call, including `TestDocsTree`, `TestDocsBuildRealTree` and the other real-tree tests, and give the plan 11.1-01 fixture tests in `internal/docsite/docsite_test.go` an explicit fixture `Commands` value so they keep asserting exact problem lists. In `cmd/summer/docs_test.go`: `TestDocsCommandNames` asserts the sets include `docs:build`, `make:plugin`, `migrate:status` (Tool) and `key:generate`, `route:list`, `admin:create`, `queue:clear`, `websockets:health` (App); `TestDocsCommandsMirrorGeneratedMain` parses internal/build/build.go and asserts that every `.(app` constructor it writes into the generated main is also called in cmd/summer/docs.go. Extend `TestToolDoesNotImportExamplePlugins` so an import path containing `docs/examples` also fails.
+4. `check_forbidden.go` (D-11): one unexported case-insensitive regexp holding the consuming-application spellings from RESEARCH Q6 (plus the accented variant from the Phase 11 hygiene regex). Run it over each page's source text (reporting source file and line) and over every in-memory output file (reporting the output-relative path and line). Problem: `{file}:{line}: forbidden: consuming-application name in output`; never echo the match. The test fixture for this rule builds the forbidden word at run time (string concatenation) so no test source contains it verbatim.
+5. `check_policy.go`: every fence whose language is `go` in a page under `Src` must carry `src=` (`{file}:{line}: snippet: go code block has no src= reference`); ingested READMEs are exempt (D-18). `> [!TYPE]` blockquotes with TYPE outside NOTE, TIP, WARNING fail with `{file}:{line}: callout: unknown type {TYPE} (use NOTE, TIP or WARNING)`. Headings in docs/ pages must be ASCII text with no link or code span (`{file}:{line}: heading: headings must be plain ASCII text without links or code`).
+6. Smoke tests in `internal/docsite/checks_test.go`: `TestLinkChecker`, `TestCommandChecker`, `TestForbiddenChecker`, `TestFencePolicy`, each with one passing and one failing fixture asserting the exact problem text.
+7. Gate: add `--claude` (the CLAUDE.md Documentation section contains the new bullets below) and self-test plants for a broken anchor (`link: #`), an unknown command (`summer no:such` → `is not a summer or application command`), a forbidden name (built at run time in the script from two halves), a go fence without src= and a `> [!DANGER]` callout. `--all` runs `--preconditions --deps --self-test --docs --forbidden --claude --go`.
+8. Docs-rules commit, separate from the code commit (CLAUDE.md commit rule), per D-13, DOCS-08 and D-17. In CLAUDE.md "## Documentation" add the bullet: "A change to a module's exported API, config keys or CLI commands also updates the affected pages under `docs/` in the same change. `go test ./cmd/summer -run TestDocsTree` and `summer docs:build --check` check identifiers, internal links and anchors, `src=` snippets, command names and consuming-application names across `docs/` and every module README." Amend the go doc bullet to: "Every identifier named in a README or a docs page must exist in the package. The docs checker verifies this automatically; `go doc ./modules/` remains the manual check." Add: "Config keys named in README or docs pages are not checked automatically yet (deferred in Phase 11.1); review them by hand." Write `.planning/todos/pending/wristband-neutral-resource-default.md` (frontmatter title, date 2026-09-30, priority medium, area `summercms.go wristband`) stating that `wristband.DefaultOptions()` ships a resource URL and comments that name a consuming application (server.go default and nearby comments, stores.go and client_issue.go comments), that a framework default should be empty or neutral, that docs pages must not quote it, and that the API is unchanged in Phase 11.1.
+
+Stage only this task's files; commit code first, then CLAUDE.md plus the todo as `docs(11.1): ...`.
+
+
+ go vet ./... && go test ./internal/docsite ./cmd/summer -count=1
+ non-zero exit or a "FAIL" line
+ scripts/check-phase11.1.sh --self-test && scripts/check-phase11.1.sh --claude && scripts/check-phase11.1.sh --forbidden
+ non-zero exit or a line starting "refuse:"
+
+
+ - `go test ./internal/docsite -run '^(TestLinkChecker|TestCommandChecker|TestForbiddenChecker|TestFencePolicy)$' -count=1 -v` prints four `--- PASS` lines.
+ - `go test ./cmd/summer -run '^(TestDocsCommandNames|TestDocsCommandsMirrorGeneratedMain|TestToolDoesNotImportExamplePlugins|TestDocsTree)$' -count=1 -v` prints four `--- PASS` lines.
+ - `grep -F 'also updates the affected pages under `docs/`' CLAUDE.md` finds a match.
+ - `grep -F 'Config keys named in README or docs pages are not checked automatically yet' CLAUDE.md` finds a match.
+ - `test -f .planning/todos/pending/wristband-neutral-resource-default.md`.
+ - `! grep -rniE 'fonoteka|p(l|ł)ytarium' docs internal/docsite/checks_test.go` (no match).
+ - `git diff --name-only 9033d81 -- modules/wristband` prints nothing (wristband untouched).
+ - `grep -c 'toolCommands()' cmd/summer/docs.go` prints at least 1.
+
+ Every D-11/D-12 rule and the go-fence policy fail go test and docs:build with UI-SPEC problem lines; command sets are collected from real command constructors; the gate self-test refuses every planted violation; CLAUDE.md carries the D-13 rule and the D-17 note; the wristband gap is logged.
+
+
+
+ Task 3: The site gets the WinterCMS-style theme, chroma highlighting, search, dark mode and `summer docs:serve`
+ internal/docsite/highlight.go, internal/docsite/serve.go, internal/docsite/render.go, internal/docsite/emit.go, internal/docsite/theme_test.go, internal/docsite/theme/templates/page.html, internal/docsite/theme/templates/header.html, internal/docsite/theme/templates/sidebar.html, internal/docsite/theme/templates/toc.html, internal/docsite/theme/templates/pager.html, internal/docsite/theme/templates/footer.html, internal/docsite/theme/templates/search.html, internal/docsite/theme/templates/icons.html, internal/docsite/theme/templates/404.html, internal/docsite/theme/assets/site.css, internal/docsite/theme/assets/site.js, internal/docsite/theme/assets/search.js, internal/docsite/theme/assets/theme-init.js, internal/docsite/theme/assets/LICENSE-lucide.txt, internal/docsite/theme/assets/fonts/*, cmd/summer/docs.go, cmd/summer/main_test.go, cmd/summer/docs_test.go, go.mod, go.sum, scripts/check-phase11.1.sh, README.md
+
+ - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md (whole file: Theme Parts, Layout, Spacing, Typography, Color, Page Anatomy, Interactions, Copywriting, UI Considerations)
+ - admin/src/styles/main.css lines 21-26 and 78-125 (token values to copy)
+ - admin/node_modules/@lucide/vue/dist/esm/icons/{sun,moon,monitor,search,menu,x,chevron-left,chevron-right,pencil,file-text,copy,check,info,lightbulb,triangle-alert}.mjs (SVG path data)
+ - admin/node_modules/@fontsource/dm-sans/files and admin/node_modules/@fontsource/dm-mono/files (the 8 woff2 files), their LICENSE files and 400.css/600.css/400-italic.css (unicode-range values)
+ - internal/docsite/render.go and internal/docsite/emit.go (plan 11.1-01 renderer and page template data)
+ - internal/dev/watch.go (fsnotify usage pattern)
+ - `go doc github.com/alecthomas/chroma/v2` and `go doc github.com/alecthomas/chroma/v2/lexers Get` after adding the module
+
+
+1. Dependency (D-15): run `go list -m -versions github.com/alecthomas/chroma/v2` and add the newest v2 tag (v2.27.0 at planning time) with `go get`, then `go mod tidy`. If tidy adds any module path other than `github.com/alecthomas/chroma/v2` and `github.com/dlclark/regexp2/v2`, stop and report: D-15 approves only those two. Extend the gate's `--deps` to require chroma/v2 as a direct requirement.
+2. `highlight.go`: a goldmark `renderer.NodeRenderer` registered with a priority above the default for `ast.KindFencedCodeBlock`. It emits the UI-SPEC code block: ``, the `` for `src=` fences (from plan 11.1-01), a copy button element that stays hidden until site.js activates it, and `
`. Tokenise with `lexers.Get(lang)` (fallback: plain escaped text) and map chroma token categories onto spans: keywords to `tok-kw`, YAML/JSON keys (name tags and attributes) to `tok-key`, string literals to `tok-str`, comments to `tok-com`, number literals and YAML booleans to `tok-num`, generic prompts (the `$ ` of `sh` fences) to `tok-prompt`; everything else is escaped plain text. Escape every token value with `html.EscapeString`. Do not use chroma's HTML formatter package or its styles: no inline style attributes, class names only from the UI-SPEC list. The goldmark highlighting extension stays rejected (untagged, stale chroma pin).
+3. `render.go`: heading renderer adds `#` to H2/H3; callout transformer turns `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]` into `