docs(11.1): create phase plan

Six plans (tracer generator, site UX and checkers, content A, content B,
acme/blog walkthrough, unit tests). SC4/DOCS-04 narrowed to docs/ pages per
D-18; README Go fence conversion logged as a todo.
This commit is contained in:
Jakub Zych
2026-09-30 20:33:51 +02:00
parent a33b1ada80
commit 6f57604028
13 changed files with 1901 additions and 17 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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 `# <title>`, 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: <url>`, blank line, description, full Markdown) at the output root."
- "Per D-06 and D-17, every page is written as <section>/<slug>.html with a clean Markdown sibling <section>/<slug>.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=<path>[#<fragment>]` 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/<name>.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 `# <title>`, description over 160 characters) is reported as `<file>:1: frontmatter: <detail>` 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"
---
<objective>
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`.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
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 `<Root>/docs`), `Out string` (output dir, default `<Root>/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.
</interfaces>
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: `summer docs:build` turns docs/index.md and docs/setup/installation.md into a site with every output</name>
<files>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</files>
<read_first>
- 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)
</read_first>
<action>
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 `<file>:1: frontmatter: <detail>` for: missing field, unknown field, section not equal to the directory name, duplicate order in a section (`order N already used by <other>`), first body line not `# <title>`, 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:<line>: section: "<name>" 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 `<URL>.html` from `theme/templates/page.html` (html/template, embedded with `//go:embed`) and `<URL>.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 `## <Section title>` list per section with `- [Title](<base>/<URL>.md): <description>`), `llms-full.txt` (per page in reading order: `# Title`, `Source: <base>/<URL>.html`, blank line, description, blank line, the page .md body) and `search-index.json` shaped `{"p":[{"u":"<base>/<URL>.html","t":"<title>","s":"<section title>"}],"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`: `<html lang="en">`, `<title>` "{Page title} · SummerCMS docs" (index: "SummerCMS documentation"), `<meta name="description">`, `<link rel="stylesheet" href="{base}/assets/site.css">`, `<body class="docs">`, a `<nav class="sidebar" aria-label="Documentation">` grouped by section in `site.yaml` order with the current item marked `aria-current="page"`, and `<main id="content">` with the H1 title, the description lead and the rendered body. Header, on-page TOC, pager, search, theme toggle and highlighting are plan 11.1-02 (UI-SPEC theme); this template is the shell that plan replaces. `site.css` holds a readable two-column layout with the UI-SPEC light tokens only.
5. Output guard (T-11.1-03): `Build` refuses `Out` equal to `Root`, inside `Src`, or an ancestor of `Src` or `Root` (`docs:build: --out must not be inside --src or equal to the repository root`). An existing non-empty `Out` without `.summer-docs` is refused with `docs:build: refusing to clean {out}: it has no .summer-docs marker. Remove the directory or choose another --out.`; with the marker, remove its contents, write the files and re-create the marker. Never delete anything outside `Out`.
6. `cmd/summer/docs.go`: `docsBuildCommand()` named `docs:build`, description "Build the documentation site", string flags `root` (default `.`), `src`, `out`, `base-url`, and bare flag `check` (validate only, write nothing). It resolves defaults, calls `docsite.Build` (or `docsite.Check` with `--check`), prints each problem with `out.Printf("%s\n", p)`, then `docs:build: {n} problems, nothing written` and returns a short error so the binary exits 1; on success prints `docs:build: wrote {n} pages to {out}`. Register it in `toolCommands()`; add `docs:build` to the `TestToolCommandNames` want list and `--out` to its helpWants.
7. Content: `docs/site.yaml` with `title: SummerCMS`, `description` "SummerCMS is a content management framework for Go, inspired by WinterCMS.", `base_url: ""`, `edit_url: "https://git.golem15.com/golem15/summercms/_edit/master/{path}"`, `source_url: "https://git.golem15.com/golem15/summercms/src/branch/master/{path}"`, `llms_notes` (compiled plugins registered at build time; Postgres only; headless, no frontend themes; Go 1.27), and `sections` listing only `setup` (title "Setup") and `api` (title "API reference"). `docs/index.md`: frontmatter title "SummerCMS documentation", the UI-SPEC index description, section `index`, order 0; a short landing body linking `setup/installation.md`. `docs/setup/installation.md`: title "Installation", section `setup`, order 20; requirements (Go 1.27, PostgreSQL 16 with the ICU locale lagoon checks, Docker only for integration tests) and installing the tool with `go install ./cmd/summer`, as prose and `sh` fences. Use neutral names only (D-11).
8. `.gitignore`: add `/site/` in the "# Go" block next to `/dist/`.
9. `cmd/summer/docs_test.go`: `TestDocsTree` calls `docsite.Check` with `Root: "../.."` and fails listing every problem line; `TestDocsBuildRealTree` runs `docs:build --root ../.. --out <t.TempDir()>/site` through `bonfire.NewRoot("summer", toolCommands(), &buf)` and asserts `index.html`, `index.md`, `setup/installation.html`, `setup/installation.md`, `llms.txt`, `llms-full.txt`, `search-index.json`, `assets/site.css` and `.summer-docs` exist and the output line matches `docs:build: wrote N pages to`.
Commit rule for every task in this plan: another plan (11-08) may have uncommitted edits in the working tree; stage only the files this task lists (never `git add -A`). Do not modify modules/lagoon/transaction.go, modules/lagoon/transaction_test.go, modules/lagoon/README.md, modules/cabana/crud.go, modules/cabana/relation.go or modules/beachcomber/sync_test.go.
</action>
<verify>
<automated>go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestDocsTree|TestDocsBuildRealTree|TestToolCommandNames)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run" in the output</fails_when>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
<done>`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.</done>
</task>
<task type="auto">
<name>Task 2: Every framework module appears as an API reference page, with shared heading IDs, rewritten links and heading-level search entries</name>
<files>internal/docsite/load.go, internal/docsite/render.go, internal/docsite/emit.go, internal/docsite/docsite_test.go, cmd/summer/docs_test.go</files>
<read_first>
- 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
</read_first>
<action>
Per D-08, ingest instead of duplicating, and make anchors deterministic.
1. Module discovery (`load.go`): every directory `modules/<name>` (top level only) that contains a non-`_test.go` Go file is a module. Missing `modules/<name>/README.md` is the problem `modules/<name>: 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/<name>`, 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 `<base>/<URL>.html` in HTML and `<base>/<URL>.md` in the raw .md; a relative link to `modules/<m>/README.md` (for example `../../modules/postcard/README.md`) becomes the `api/<m>` page; in ingested READMEs `../<m>/README.md` becomes the `api/<m>` 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":<page index>,"a":"<id>","h":"<heading text>","x":"<plain text of that section, at most 300 characters>"}`. 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/<m>` with non-test Go files, `api/<m>.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.
</action>
<verify>
<automated>go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestSlugIDs|TestReadmeIngestion|TestEveryModuleInSidebar|TestDocsAIOutputsInSync|TestDocsTree)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run" in the output</fails_when>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
<done>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.</done>
</task>
<task type="auto">
<name>Task 3: Verified snippets: src= extraction, drift detection, `summer docs:sync`, and the first Example in the docs</name>
<files>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</files>
<read_first>
- 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"
</read_first>
<action>
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 `<figure class="code">` with a `<figcaption>` showing `path#fragment` linked to `source_url` with `{path}` filled (fragment not sent), then `<pre><code>` 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, <name>` 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.
</action>
<verify>
<automated>go vet ./... && go test ./internal/docsite ./cmd/summer ./modules/bonfire -count=1</automated>
<fails_when>non-zero exit or a "FAIL" line in the output</fails_when>
<automated>go test ./modules/bonfire -run '^ExampleCall$' -count=1 -v</automated>
<fails_when>non-zero exit, no "--- PASS: ExampleCall" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `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 <scratch-docs>` 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.
</acceptance_criteria>
<done>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.</done>
</task>
</tasks>
<threat_model>
## 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 |
</threat_model>
<verification>
- `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).
</verification>
<success_criteria>
- 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.
</success_criteria>
## 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: `<section>/<slug>.html`, `<section>/<slug>.md`, `index.html`, `index.md`, `api/<module>.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).
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-01-SUMMARY.md` when done
</output>

View File

@@ -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 `<file>:<line>: identifier: <pkg>.<Ident> does not exist in modules/<pkg>`; 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: <target> does not resolve` or `link: #<anchor> not found in <page>`."
- "A `summer <cmd>` token in a docs `sh` fence or code span must be a name from cmd/summer toolCommands(), and a `./bin/<app> <cmd>` 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 <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"
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
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.
</interfaces>
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: a stale identifier in any docs page or module README fails go test, docs:build and the phase gate</name>
<precondition>.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)</precondition>
<files>internal/docsite/check_identifiers.go, internal/docsite/docsite.go, internal/docsite/checks_test.go, cmd/summer/docs_test.go, scripts/check-phase11.1.sh</files>
<read_first>
- 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)
</read_first>
<action>
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/<path>", "<Ident>[.<Member>]")` 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 `<summer> docs:build --check --root <scratch copy>` 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 <mode> passed`.
Stage only this task's files plus any README it had to correct.
</action>
<verify>
<automated>go vet ./internal/docsite/... ./cmd/summer && go test ./internal/docsite ./cmd/summer -run '^(TestIdentifierChecker|TestDocsTree)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>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</automated>
<fails_when>non-zero exit, a line starting "refuse:", or a missing "phase11.1 self-test passed" line</fails_when>
</verify>
<acceptance_criteria>
- `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 <scratch-docs>` 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).
</acceptance_criteria>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Links, anchors, command names, consuming-application names and the go-fence policy fail the build; CLAUDE.md records the rule</name>
<files>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</files>
<read_first>
- 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
</read_first>
<action>
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/<m>/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 <name>` (optionally after `$ `) and `./bin/<app> <name>` tokens. A `summer` name must be in `Commands.Tool`; a `./bin/<app>` 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 `<pkg>.<Func>(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/<name> <Identifier>` 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): ...`.
</action>
<verify>
<automated>go vet ./... && go test ./internal/docsite ./cmd/summer -count=1</automated>
<fails_when>non-zero exit or a "FAIL" line</fails_when>
<automated>scripts/check-phase11.1.sh --self-test && scripts/check-phase11.1.sh --claude && scripts/check-phase11.1.sh --forbidden</automated>
<fails_when>non-zero exit or a line starting "refuse:"</fails_when>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
<done>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.</done>
</task>
<task type="auto">
<name>Task 3: The site gets the WinterCMS-style theme, chroma highlighting, search, dark mode and `summer docs:serve`</name>
<files>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</files>
<read_first>
- .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
</read_first>
<action>
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: `<figure class="code">`, the `<figcaption>` for `src=` fences (from plan 11.1-01), a copy button element that stays hidden until site.js activates it, and `<pre><code class="language-<lang>">`. 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 `<a class="heading-anchor" href="#<id>" aria-label="Link to section: <text>">#</a>` to H2/H3; callout transformer turns `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]` into `<aside class="callout callout-note|tip|warning" role="note">` with the icon and title ("Note", "Tip", "Warning"); collect H2/H3 entries for the TOC.
4. `emit.go` and templates per UI-SPEC Theme Parts: `page.html` (skip link, `<body class="docs">`, eyebrow, H1, lead, `<div class="page-actions">` with "Edit this page" from `edit_url` (omitted when empty) and "View as Markdown" to the sibling .md, `<details class="toc-inline">`, prose, pager, footer), `header.html` (wordmark "Summer"+"CMS" with the sun icon, `aria-label="SummerCMS documentation home"`, search trigger with `<kbd>`, `<button class="theme-toggle"`, Menu button with `aria-controls="sidebar"`), `sidebar.html` (`<nav class="sidebar" aria-label="Documentation">`, API items in DM Mono), `toc.html` (`<aside class="toc" aria-label="On this page">`, only with 2 or more H2/H3), `pager.html` (`<nav class="pager" aria-label="Previous and next page">`, reading order across sections, section line only for cross-section targets, no nav on a one-page site), `footer.html` (`<footer class="site-footer">`, llms.txt and llms-full.txt links), `search.html` (`<dialog class="search" id="search">`, combobox input, `<ul id="search-results" role="listbox">`, `aria-live="polite"` region), `icons.html` (the 15 Lucide icons as `<svg class="icon icon-<name>"` with `stroke="currentColor"`, `aria-hidden="true"`), `404.html` (`<body class="docs docs-404">`, "Page not found" copy, "documentation home" link) written to `404.html`. `<head>` loads `theme-init.js` synchronously before the stylesheet, and adds `<meta name="color-scheme" content="light dark">`, OG title/description and `<link rel="alternate" type="text/markdown" href="<page>.md">`. Script elements are external files under `{base}/assets/` only: no inline script bodies, no inline event handlers and no inline style attributes (CSP `script-src 'self'; style-src 'self'`).
5. Assets: `site.css` implements the UI-SPEC layout, breakpoints, spacing, typography (16/14/20/32 px, weights 400 and 600, `font-synthesis: none`), colour tokens copied by value from admin main.css with dark values under `:root.dark` and `@media (prefers-color-scheme: dark) { :root:not(.light) ... }`, syntax colours, `scroll-margin-top: 96px`, print rules, `prefers-reduced-motion`, and `@font-face` rules with the fontsource unicode-range values and `font-display: swap`. Copy exactly the 8 woff2 files and both licence files into `theme/assets/fonts/` and the Lucide ISC licence into `LICENSE-lucide.txt`; nothing reads node_modules at build time. `theme-init.js` reads `localStorage["summer-docs-theme"]` inside try/catch, sets `html.js` plus `dark` or `light` (neither for system) and follows the media query in system mode. `site.js`: theme toggle cycling System, Light, Dark with the UI-SPEC aria-label copy; mobile drawer (Esc, scrim, link, focus trap, body scroll lock, focus return); TOC scroll spy with `aria-current="location"`; scroll the active sidebar item into view with `block: "nearest"`; copy buttons only when `navigator.clipboard?.writeText` exists, with the Copied / Copy failed announcements. `search.js`: open on the trigger, ⌘K, Ctrl+K and `/` outside text fields; lazy-fetch `search-index.json` once and cache it; lowercase AND-token matching ranked title over heading over text; up to 20 rows built with `document.createElement` and `textContent` only (never assign HTML strings), `<mark>` around matched terms created as elements; arrow keys, Enter and click navigate to `<page>#<anchor>`; all UI-SPEC empty, loading, no-results, file:// and bad-index copy and the live-region counts.
6. `serve.go` and `docs:serve` (D-03, T-11.1-05, T-11.1-06): `Serve` builds into a fresh `os.MkdirTemp` directory (removed on exit), serves it with `Handler(dir)` built on `http.FileServer(http.Dir(dir))` that returns `404.html` with status 404 for missing paths and never serves dot-files, and watches `Src`, `modules/*/README.md` and `src=` source directories with fsnotify; on change it rebuilds and swaps the served directory only when the build has no problems, else prints the problem lines and `docs:serve: build failed, still serving the previous version`. It refuses an addr whose host is not `localhost` or a loopback IP with `docs:serve: refusing to listen on {addr}: not a loopback address. Pass --allow-remote to serve on the network.` unless allowRemote is true, and prints `Serving docs at http://{addr} (press Ctrl+C to stop)`. Register `docs:serve` in `toolCommands()` (flags `root`, `src`, `base-url`, `addr` default `127.0.0.1:8088`, bare `allow-remote`), add it to the `TestToolCommandNames` want list with helpWants `--allow-remote` and `127.0.0.1:8088`, and add a `summer docs:serve` line to README.md "Development".
7. Tests (smoke): `internal/docsite/theme_test.go` `TestBuildSiteMarkers` builds a two-section fixture tree and asserts every UI-SPEC Theme Parts marker string on a content page, `docs docs-404` on 404.html, no toc on a one-heading page, pager edge cases (first page Next only, last page Previous only), `tok-kw` spans in a highlighted Go fence, and that rendered HTML contains no inline script body, no inline style attribute and no `on[a-z]+=` attribute; `TestServeHandler` (200 for index.html, 404 with the Page not found body, dot-file refused) and `TestServeRefusesNonLoopback` (`0.0.0.0:8088` refused, `127.0.0.1:0` and `localhost:0` accepted). Extend `TestDocsBuildRealTree` to require `404.html`, `assets/site.js`, `assets/search.js`, `assets/theme-init.js` and the font files.
Stage only this task's files.
</action>
<verify>
<automated>go vet ./... && go test ./internal/docsite ./cmd/summer -count=1 && go test ./internal/docsite -run '^(TestBuildSiteMarkers|TestServeHandler|TestServeRefusesNonLoopback)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.1.sh --deps && scripts/check-phase11.1.sh --docs</automated>
<fails_when>non-zero exit or a line starting "refuse:"</fails_when>
</verify>
<acceptance_criteria>
- `grep -n 'github.com/alecthomas/chroma/v2 v2\.' go.mod` finds a direct requirement line.
- `! grep -n 'goldmark-highlighting' go.mod` (no match).
- `! grep -n 'formatters/html' internal/docsite/highlight.go` (no match).
- `! grep -rn 'innerHTML' internal/docsite/theme/assets` (no match).
- `! grep -rn 'style=' internal/docsite/theme/templates` (no match).
- `grep -rn '<script' internal/docsite/theme/templates | grep -v 'src='` prints nothing.
- `ls internal/docsite/theme/assets/fonts/*.woff2 | wc -l` prints 8.
- `grep -c 'Search needs a web server' internal/docsite/theme/assets/search.js` prints at least 1, and the same holds for `Loading the search index`, `No results for` and `The search index could not be loaded`.
- `grep -c 'summer-docs-theme' internal/docsite/theme/assets/theme-init.js` prints at least 1.
- `grep -c 'Copy failed. Select the code and copy it manually.' internal/docsite/theme/assets/site.js` prints 1.
- `grep -c '#fcd34d' internal/docsite/theme/assets/site.css` prints at least 1 and `grep -c 'scroll-margin-top: 96px' internal/docsite/theme/assets/site.css` prints at least 1.
- `go run ./cmd/summer docs:serve --addr 0.0.0.0:8088` exits non-zero and prints `not a loopback address. Pass --allow-remote`.
- `go test ./internal/docsite -run '^TestBuildSiteMarkers$' -count=1 -v` prints `--- PASS: TestBuildSiteMarkers`.
- `scripts/check-phase11.sh --hygiene` still passes after chroma/v2 enters the module graph (no excluded client library, no cron dependency).
</acceptance_criteria>
<done>The built site matches the UI-SPEC shell and markers, highlights code through chroma into UI-SPEC classes, searches client-side with textContent-only rendering, toggles light/dark/system without a flash, and `summer docs:serve` previews it on loopback with a real 404.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| browser → docs:serve | Local preview server; any host that can reach the port can read the site |
| search-index.json → search.js | Indexed page text is rendered into the DOM on every keystroke |
| docs content → published output | Framework docs must not leak consuming-application details |
| module proxy → go.mod | A new third-party module enters the build (chroma/v2) |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-11.1-05 | Information disclosure | internal/docsite/serve.go bind address | medium | mitigate | Default 127.0.0.1:8088; non-loopback host refused unless --allow-remote; TestServeRefusesNonLoopback |
| T-11.1-06 | Information disclosure | internal/docsite/serve.go static handler | medium | mitigate | http.FileServer over a generated temp dir only (no repo files), dot-files refused, 404.html for misses; TestServeHandler |
| T-11.1-07 | Tampering (XSS) | internal/docsite/theme/assets/search.js | high | mitigate | Result rows and mark elements built with createElement/textContent only; grep gate rejects HTML-string assignment in assets; CSP-compatible external scripts, no inline handlers (TestBuildSiteMarkers) |
| T-11.1-08 | Information disclosure (policy) | internal/docsite/check_forbidden.go | medium | mitigate | Forbidden-name check over page sources and every built output, including snippet copies; gate --forbidden and self-test plant |
| T-11.1-09 | Information disclosure (privacy) | theme templates and assets | low | mitigate | Fonts and icons vendored with licences; no third-party origins or analytics (prohibition, judgment review) |
| T-11.1-10 | Tampering | internal/docsite/check_identifiers.go go doc fallback | low | mitigate | exec.Command with an argument list (no shell); Ident and Member constrained to the Go identifier regex before exec |
| T-11.1-SC | Tampering | go module install (github.com/alecthomas/chroma/v2) | high | mitigate | User-approved at the plan-count checkpoint (D-15); version read from the Go module proxy (RESEARCH Package Legitimacy Audit: OK); go.sum pins hashes; gate --deps refuses any added module beyond chroma/v2 and regexp2/v2 |
</threat_model>
<verification>
- `go vet ./... && go test ./internal/docsite ./cmd/summer -count=1` green.
- `scripts/check-phase11.1.sh --self-test`, `--deps`, `--docs`, `--forbidden`, `--claude`, `--preconditions` each print `phase11.1 <mode> passed`.
- Manual UAT (VALIDATION manual-only rows): `summer docs:serve`, search for a module name and a command, toggle theme and reload.
</verification>
<success_criteria>
- SC2: `summer docs:build` produces sidebar, on-page TOC, prev/next, edit link, client-side search and dark mode with no Node toolchain; the only new dependency is chroma/v2 (approved, D-15).
- SC4 (checkers): identifier and link/anchor checkers (plus command, forbidden and fence policy) run in go test and fail on stale names or broken links.
- DOCS-08: CLAUDE.md records the rule and names the checkers.
</success_criteria>
## Artifacts this phase produces
- `internal/docsite`: `Commands`, `Options.Commands`, `Serve`, `Handler`; unexported checkers in `check_identifiers.go`, `check_links.go`, `check_commands.go`, `check_forbidden.go`, `check_policy.go`; chroma `NodeRenderer` in `highlight.go`.
- CLI command: `summer docs:serve` (flags `--root`, `--src`, `--base-url`, `--addr`, `--allow-remote`); `cmd/summer` helper `docsCommands()`.
- Theme: `theme/templates/{page,header,sidebar,toc,pager,footer,search,icons,404}.html`, `theme/assets/{site.css,site.js,search.js,theme-init.js,LICENSE-lucide.txt}`, `theme/assets/fonts/` (8 woff2 + 2 licences); built `404.html`.
- Gate: `scripts/check-phase11.1.sh` with `--preconditions`, `--deps`, `--docs`, `--forbidden`, `--claude`, `--self-test`, `--go`, `--all`.
- Dependency: `github.com/alecthomas/chroma/v2` (direct), `github.com/dlclark/regexp2/v2` (indirect).
- Docs rules: CLAUDE.md Documentation bullets; `.planning/todos/pending/wristband-neutral-resource-default.md`.
- Tests: `TestIdentifierChecker`, `TestLinkChecker`, `TestCommandChecker`, `TestForbiddenChecker`, `TestFencePolicy`, `TestBuildSiteMarkers`, `TestServeHandler`, `TestServeRefusesNonLoopback` (internal/docsite); `TestDocsCommandNames`, `TestDocsCommandsMirrorGeneratedMain` (cmd/summer).
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-02-SUMMARY.md` when done
</output>

View File

@@ -0,0 +1,293 @@
---
phase: 11.1-summercms-documentation-for-humans-and-ai-agents
plan: 03
type: execute
wave: 3
depends_on: ["11.1-02"]
files_modified:
- docs/site.yaml
- docs/index.md
- docs/setup/introduction.md
- docs/setup/installation.md
- docs/setup/configuration.md
- docs/setup/coming-from-wintercms.md
- docs/architecture/introduction.md
- docs/architecture/go-modules-and-workspaces.md
- docs/architecture/application-lifecycle.md
- docs/architecture/request-lifecycle.md
- docs/plugins/registration.md
- docs/plugins/scheduling.md
- docs/plugins/extending.md
- docs/plugins/testing.md
- docs/console/introduction.md
- docs/console/setup-and-maintenance.md
- docs/console/scaffolding.md
- docs/console/writing-commands.md
- docs/console/utilities.md
- modules/party/example_test.go
- modules/backpack/example_test.go
- modules/pact/example_test.go
- modules/festival/example_test.go
- modules/towel/example_test.go
- modules/bonfire/example_test.go
- cmd/summer/docs_test.go
autonomous: true
requirements: [DOCS-01, DOCS-04, DOCS-06]
assumption_delta_decision: no-change
user_setup: []
estimate:
tokens: 100000
raw_tokens: 100000
tasks: 3
confidence: low
must_haves:
truths:
- "Per D-09 and DOCS-06, docs/setup/coming-from-wintercms.md maps WinterCMS concepts (Plugin.php, version.yaml and updates, Eloquent models, fields.yaml and columns.yaml, backend controllers and behaviours, routes.php, middleware, config files, lang files, events, artisan commands, queues, the scheduler, mail templates, settings models) to SummerCMS equivalents, names each SummerCMS identifier as a checked `pkg.Ident` span, and marks the WinterCMS areas SummerCMS does not provide (CMS pages, themes, components, the AJAX framework, Snowboard, import/export, record sorting, collections, behaviours, cache, session) as not provided."
- "Per D-08, site.yaml lists the Setup, Architecture, Plugins and Console sections (with API reference last), and each has the pages in this plan's files list with unique, spaced order values (10, 20, 30...), installation keeping order 20."
- "Per D-07, every Go fence in these pages carries src= to an Example with `// Output:` or a docs:start region in a module example_test.go, the bodies were written by `summer docs:sync`, and go vet accepts every Example name (so each names a real identifier)."
- "Per D-11, no page or example names a consuming application; examples use acme and blog."
- "Per D-12, TestDocsTree passes on the real tree after each task: identifiers, links, anchors, command names, forbidden names, fences and frontmatter are clean."
- "The Console pages name only commands that exist: `summer` tokens from toolCommands() and `./bin/<app>` tokens from the runtime command set, as enforced by the command checker."
- "cmd/summer TestDocsRequiredPages lists every page of this plan and passes."
- "Pages describe awkward APIs as they are; no module API changes in this plan, and any gap found is logged as a todo under .planning/todos/pending/."
- statement: "The pages read in WinterCMS docs tone (second person, present tense, imperative) and are useful to a WinterCMS developer (manual review)."
verification: backstop
prohibitions:
- requirement_id: DOCS-06
category: transparency
status: resolved
verification: judgment
resolution: "The concept map has an explicit 'Not provided' column value and the headless nature is stated on the introduction pages."
reason: "A WinterCMS developer porting a site must learn early that themes, CMS pages and the AJAX framework do not exist, instead of discovering it mid-port."
statement: "The docs must not describe a WinterCMS feature that SummerCMS does not provide as if it were available."
artifacts:
- path: "docs/setup/coming-from-wintercms.md"
provides: "WinterCMS to SummerCMS concept map"
contains: "party.Plugin"
- path: "docs/console/setup-and-maintenance.md"
provides: "runtime command reference"
contains: "migrate:status"
- path: "modules/party/example_test.go"
provides: "verified plugin Example"
contains: "// Output:"
- path: "cmd/summer/docs_test.go"
provides: "TestDocsRequiredPages"
contains: "TestDocsRequiredPages"
key_links:
- from: "docs/setup/coming-from-wintercms.md"
to: "modules/party/example_test.go"
via: "go fence src= reference"
pattern: "src=modules/party/example_test\\.go#"
- from: "docs/plugins/scheduling.md"
to: "modules/pact/example_test.go"
via: "go fence src= reference to a schedule Example"
pattern: "src=modules/pact/example_test\\.go#"
- from: "docs/site.yaml"
to: "docs/architecture/introduction.md"
via: "architecture section listed in sidebar order"
pattern: "architecture"
---
<objective>
Write framework content A: the Setup section (Introduction, Installation, Configuration, Coming from WinterCMS), Architecture, Plugins and Console, with Go examples as compiled `example_test.go` Examples referenced by `src=`.
Purpose: D-08 (Winter-mirroring sections), D-09 (concept map, DOCS-06) and D-07 (verified examples) for the framework's core: plugins, lifecycle, request flow and the CLI. Every page passes the plan 11.1-02 checkers as it is written.
Output: 16 new or rewritten pages, site.yaml sections, `example_test.go` files for party, backpack, pact, festival and towel, an extended bonfire example, and `TestDocsRequiredPages`.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.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-02-SUMMARY.md
@CLAUDE.md
@README.md
@docs/site.yaml
@docs/index.md
@docs/setup/installation.md
Authoring rules for every page in this plan (from plans 11.1-01 and 11.1-02; the checkers enforce them):
- Frontmatter `title`, `description` (one sentence, at most 160 characters), `section` (the directory name), `order`; the first body line is `# <title>`.
- Headings are plain ASCII text with no links or code spans.
- A framework identifier is written as a backticked `pkg.Ident` or `pkg.Type.Member` span so the identifier checker verifies it.
- Link a module by its README path (`../../modules/<m>/README.md`), which the site rewrites to the API reference page; link guide pages by relative `.md` path. Link only to pages that exist when the task commits.
- A Go fence is written as a `go src=<path>#<fragment>` fence with an empty body, then `go run ./cmd/summer docs:sync` fills it. Never hand-type a Go fence body. YAML, `sh` and `php` fences may be written by hand (PHP fences show the WinterCMS side only).
- Examples live in `modules/<m>/example_test.go`, `package <m>_test`, named after real identifiers (`ExampleX`, `ExampleT_Method`), deterministic, ending in `// Output:`; no database or network. Use neutral names (`acme`, `blog`). A `docs:start` region in a test file is allowed only inside a Test or Example function or a helper one of them calls (the snippet checker enforces it), so every shown snippet runs under `go test ./...`.
- Tone: second person, present tense, imperative, as wintercms.com/docs. The docs describe the framework as it is; an awkward API is described as it is and logged as a todo, never changed here.
- Stage only the files each task lists (other work may be in the tree).
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: a WinterCMS developer finds the concept map in Setup, with checked identifiers and a verified plugin example</name>
<files>docs/setup/coming-from-wintercms.md, modules/party/example_test.go, docs/index.md, cmd/summer/docs_test.go</files>
<read_first>
- README.md (Key concepts, Using SummerCMS in an application)
- modules/party/README.md, modules/pact/README.md, modules/cabana/README.md, modules/lagoon/README.md (identifiers to map)
- `go doc ./modules/party` and `go doc ./modules/pact` output
- .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md Q1 "Winter section → SummerCMS page → module(s)" table
- cmd/summer/docs_test.go (TestDocsTree from plans 11.1-01 and 11.1-02)
</read_first>
<action>
Per D-09 and DOCS-06, prove one content page end to end (page, identifiers, verified snippet, sidebar, llms, search, checkers) before the bulk.
1. `modules/party/example_test.go` (package `party_test`): `ExamplePlugin` declares an `acme.blog` plugin type implementing `party.Plugin` (ID, Requires, Register, Boot) and prints its ID, ending `// Output: acme.blog`. Do not call `party.Register` in the Example (it is process-wide).
2. `docs/setup/coming-from-wintercms.md`: title "Coming from WinterCMS", section `setup`, order 40. Open with two paragraphs on what carries over (plugins that extend each other, YAML admin, models/controllers, scaffolding) and what does not (runtime plugin loading, PHP magic, the frontend). A concept table with columns WinterCMS, SummerCMS, Where: one row each for Plugin.php, `version.yaml` and `updates/`, Eloquent models, `fields.yaml` and `columns.yaml`, backend controllers with Form/List/Relation behaviours, `routes.php`, middleware, `config/*.php`, `lang/` files, `Event::listen`, artisan commands, queued jobs, the scheduler, mail templates, settings models, Laravel broadcasting, Scout search and the HTTP client. Each SummerCMS cell names identifiers as `pkg.Ident` spans (for example `party.Plugin`, `pact.HasMigrations`, `pact.HasRoutes`, `pact.AdminController`, `festival.Bus`, `bonfire.Command`, `pact.HasSchedule`) and the Where cell links the module README. Then a "Not provided" table: CMS pages, themes, layouts and partials, components, the AJAX framework and Snowboard, the media manager, import/export, record sorting, collections and behaviours (use Go slices and composition), cache and session (use the Go standard library). Say that the frontend is a separate application that calls the JSON API. Add a "Plugin.php in Go" section with a `php` fence of a WinterCMS `Plugin.php` for `Acme\Blog` and a `go src=modules/party/example_test.go#ExamplePlugin` fence filled by `summer docs:sync`.
3. `docs/index.md`: add a "Coming from WinterCMS" link.
4. `cmd/summer/docs_test.go`: `TestDocsRequiredPages` holds a slice of required page URLs (start with `index`, `setup/installation`, `setup/coming-from-wintercms`) and asserts each is a page in the loaded tree and is present as `.html` and `.md` in a real-tree build.
5. Run `go run ./cmd/summer docs:sync`, then `go run ./cmd/summer docs:build --check`; fix every reported problem in the page, not in the checker.
</action>
<verify>
<automated>go vet ./... && go test ./modules/party -run '^ExamplePlugin$' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `go test ./modules/party -run '^ExamplePlugin$' -count=1 -v` prints `--- PASS: ExamplePlugin`.
- `grep -n 'src=modules/party/example_test.go#ExamplePlugin' docs/setup/coming-from-wintercms.md` finds a match.
- `grep -oE '[a-z]+\.[A-Z][A-Za-z0-9]+' docs/setup/coming-from-wintercms.md | sort -u | wc -l` prints at least 15 (distinct package-qualified identifiers, each verified by TestDocsTree).
- `grep -c 'Not provided' docs/setup/coming-from-wintercms.md` prints at least 1.
- `go run ./cmd/summer docs:build --check` exits 0.
- `! grep -rniE 'fonoteka|p(l|ł)ytarium' docs modules/party/example_test.go` (no match).
</acceptance_criteria>
<done>The concept map page exists in Setup with every SummerCMS identifier verified and a running plugin Example, and the real-tree checks pass.</done>
</task>
<task type="auto">
<name>Task 2: Architecture and Plugins sections explain the single binary, the lifecycle and how plugins register, schedule, extend and test</name>
<files>docs/site.yaml, docs/architecture/introduction.md, docs/architecture/go-modules-and-workspaces.md, docs/architecture/application-lifecycle.md, docs/architecture/request-lifecycle.md, docs/plugins/registration.md, docs/plugins/scheduling.md, docs/plugins/extending.md, docs/plugins/testing.md, modules/backpack/example_test.go, modules/pact/example_test.go, modules/festival/example_test.go, modules/towel/example_test.go, cmd/summer/docs_test.go</files>
<read_first>
- modules/backpack/README.md, modules/party/README.md, modules/pact/README.md, modules/festival/README.md, modules/towel/README.md, modules/surf/README.md, modules/conga/README.md, modules/tide/README.md
- `go doc -all ./modules/backpack`, `go doc ./modules/pact HasSchedule`, `go doc ./modules/pact Cadence`, `go doc ./modules/festival Bus`, `go doc ./modules/towel`
- internal/build/build.go (generated main shape) and internal/build/scaffold.go (plugin leaves)
- README.md "Using SummerCMS in an application" (go.mod replace example)
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-02-SUMMARY.md (schedule semantics: ids, Daily/DailyAt/Every limits, schedule:run --once)
</read_first>
<action>
Per D-08 (Winter Architecture and Plugins sections, RESEARCH Q1 mapping):
1. `docs/site.yaml`: insert `architecture` ("Architecture") and `plugins` ("Plugins") after `setup` in the same change as their first pages.
2. Architecture pages (orders 10, 20, 30, 40):
- `introduction.md`: one binary with compiled plugins, headless JSON API plus embedded admin SPA, the module map grouped by concern with README links, what runs where (`summer` tool vs application binary).
- `go-modules-and-workspaces.md`: the framework module path, requiring it with a `replace` during development, `go.work` for local plugins, `summer.yaml` manifest, forking a plugin with a `replace` directive (Winter "Replacement and forking"). go.mod and summer.yaml appear as `text`/`yaml` fences.
- `application-lifecycle.md`: `party` ordering by `Requires`, Register then Boot, the `backpack.App` container and its typed service registry, capability interfaces checked by the kernel, database-dependent boot work through `lagoon.OnDatabase`. One verified snippet from `modules/backpack/example_test.go` (for example publishing and looking up a typed service).
- `request-lifecycle.md`: routes collected from `pact.HasRoutes`, named middleware, groups, constraints, body limits, CORS and recovery in `surf`, request values in `towel` context, JSON responses through `wire`. One verified snippet from `modules/towel/example_test.go` (`ExampleWithLocale` or similar with deterministic output).
3. Plugins pages (orders 10, 20, 30, 40):
- `registration.md`: plugin ID rules (vendor.plugin), `party.Plugin`, the `pact` capability interfaces a plugin opts into, embedded config, lang and mail template filesystems, the scaffolded plugin layout.
- `scheduling.md`: `pact.HasSchedule`, `pact.ScheduledCommand`, `pact.Cadence` with Daily, DailyAt and Every and their limits, how `conga` runs schedules (leader election, one run across instances), `schedule:run` and `schedule:run --once` for system cron. One verified snippet from `modules/pact/example_test.go` that builds a schedule entry and prints it.
- `extending.md`: extending other plugins through `festival` events, optional dependencies, services published on the app, GORM callbacks for model hooks, forking with `replace`. One verified snippet from `modules/festival/example_test.go` (`ExampleBus_Fire` style: listen, fire, print).
- `testing.md`: `go test ./...` and `-short`, Docker-backed tests with testcontainers-go and the ICU locale lagoon requires, parity replay with `tide` (link the tide README), where Examples fit. Commands as `sh` fences.
4. Append the eight page URLs to `TestDocsRequiredPages`.
5. Run `go run ./cmd/summer docs:sync` and `go run ./cmd/summer docs:build --check` and fix every problem in the pages.
</action>
<verify>
<automated>go vet ./... && go test ./modules/backpack ./modules/pact ./modules/festival ./modules/towel -run '^Example' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages|TestDocsAIOutputsInSync)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `ls docs/architecture/*.md docs/plugins/*.md | wc -l` prints 8.
- `grep -Ec 'architecture|plugins' docs/site.yaml` prints at least 2, and a real-tree build's `index.html` sidebar links `architecture/introduction.html` and `plugins/registration.html`.
- Each of `modules/backpack/example_test.go`, `modules/pact/example_test.go`, `modules/festival/example_test.go`, `modules/towel/example_test.go` contains `// Output:` (`grep -l '// Output:'` lists all four).
- `grep -c 'src=modules/' docs/architecture/application-lifecycle.md docs/architecture/request-lifecycle.md docs/plugins/scheduling.md docs/plugins/extending.md` reports at least 1 for each file.
- `grep -n 'schedule:run --once' docs/plugins/scheduling.md` finds a match.
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>Architecture and Plugins sections are in the sidebar with verified examples for the container, request context, schedules and events, and the real-tree checks pass.</done>
</task>
<task type="auto">
<name>Task 3: Setup and Console sections take a developer from install to every summer and runtime command</name>
<files>docs/site.yaml, docs/index.md, docs/setup/introduction.md, docs/setup/installation.md, docs/setup/configuration.md, docs/console/introduction.md, docs/console/setup-and-maintenance.md, docs/console/scaffolding.md, docs/console/writing-commands.md, docs/console/utilities.md, modules/bonfire/example_test.go, cmd/summer/docs_test.go</files>
<read_first>
- README.md (Requirements, Quick start, Known issues, Development)
- docs/setup/installation.md (plan 11.1-01 version, keep its ExampleCall snippet)
- modules/compass/README.md (config layering, SUMMER_ overrides), modules/lagoon/README.md (database config, key:generate), modules/surf/README.md (http.body_limits), modules/cabana/README.md (admin config, admin:create)
- cmd/summer/main.go and cmd/summer/runtime.go (tool command names, flags, delegation to bin/)
- modules/bonfire/README.md and `go doc -all ./modules/bonfire`
- internal/build/artifact.go (what each make:* command writes)
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-SUMMARY.md and 11-04-SUMMARY.md (queue:work, queue:clear, websockets:* commands)
</read_first>
<action>
Per D-08 (Winter Getting started and Console) and D-03 (the docs:* commands are part of the tool):
1. `docs/site.yaml`: insert `console` ("Console") after the sections that exist, before `api`.
2. Setup pages:
- `introduction.md` (order 10): what SummerCMS is, the headless model, who it is for, how the sections are organised, links to Installation and Coming from WinterCMS.
- `installation.md` (order 20, rewrite, keep the `ExampleCall` snippet section): requirements, `go install ./cmd/summer`, building `examples/hello` with `summer build`, running `./bin/hello greeter:hello` and `./bin/hello key:generate`, creating the database with `TEMPLATE template0 ... LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL'`, the `http.body_limits` requirement, `SUMMER_` environment variables with secrets written as `<secret>` markers (never real-looking keys), `./bin/hello migrate`, `./bin/hello route:list`, `./bin/hello serve --addr 127.0.0.1:8080` (loopback in examples). State the current known issues from the root README as a `> [!WARNING]` callout.
- `configuration.md` (order 30): the `config/` directory and per-environment directories, embedded plugin defaults, the `SUMMER_` override rule (double underscore separates path segments), the keys an application must set (take them from the module READMEs' Configuration sections: database, app key, body limits, uploads storage, admin JWT secret and prefix, mail driver, queue, realtime, search and push drivers), each linked to its module README. Add a `> [!NOTE]` that config keys in the docs are reviewed by hand (not checker-verified).
3. Console pages (orders 10-50):
- `introduction.md`: the `summer` tool vs the application binary, which `summer` commands delegate to `bin/<binary>`, and `--help`.
- `setup-and-maintenance.md`: every application runtime command with its flags and purpose: `migrate`, `migrate:rollback --plugin`, `migrate:status`, `key:generate`, `serve`, `route:list`, `admin:create`, `admin:reset-password`, `queue:work --queue`, `queue:clear`, `schedule:run --once`, `websockets:health`, `websockets:generate-vapid-keys`, `websockets:test-push`; say which ones an application appends itself (the websockets commands come from `centrifugo.Commands` and `flare.Commands`).
- `scaffolding.md`: `make:plugin`, `make:model --no-migration`, `make:migration`, `make:command`, `make:job`, `make:admin-controller`, `plugin:add`, `build`, `dev`; the files each writes (from internal/build/artifact.go) and the same-plugin and one-argument forms.
- `writing-commands.md`: `bonfire.Command`, `bonfire.Arg`, `bonfire.Flag` (Bare, Repeatable), `bonfire.Input`, `bonfire.Output`, running a command in-process with `bonfire.Call` and `bonfire.Catalog`, registering through `pact.HasCommands`. One or two verified snippets from `modules/bonfire/example_test.go` (reuse `ExampleCall`; add an Example for flags if useful).
- `utilities.md`: `parity:proxy`, `parity:record`, `parity:replay`, `parity:broadcasts` (link the tide README) and `docs:build`, `docs:sync`, `docs:serve` with their flags.
Write every command as `summer <name>` or `./bin/<app> <name>` in `sh` fences or code spans so the command checker verifies it.
4. `docs/index.md`: a short "Where to start" list linking the Setup, Architecture, Plugins and Console introductions.
5. Append the eight page URLs (introduction, configuration, five console pages; installation is already listed) to `TestDocsRequiredPages`.
6. Run `go run ./cmd/summer docs:sync` and `go run ./cmd/summer docs:build --check`; fix every problem.
</action>
<verify>
<automated>go vet ./... && go test ./modules/bonfire -run '^Example' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages|TestDocsAIOutputsInSync|TestDocsBuildRealTree)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.1.sh --docs && scripts/check-phase11.1.sh --forbidden</automated>
<fails_when>non-zero exit or a line starting "refuse:"</fails_when>
</verify>
<acceptance_criteria>
- `ls docs/setup/*.md | wc -l` prints 4 and `ls docs/console/*.md | wc -l` prints 5.
- `grep -c 'websockets:health' docs/console/setup-and-maintenance.md` prints at least 1 and `grep -c 'schedule:run' docs/console/setup-and-maintenance.md` prints at least 1.
- `grep -c 'make:admin-controller' docs/console/scaffolding.md` prints at least 1.
- `grep -c 'docs:serve' docs/console/utilities.md` prints at least 1.
- `grep -n 'ICU_LOCALE' docs/setup/installation.md` finds a match and `grep -n 'src=modules/bonfire/example_test.go#ExampleCall' docs/setup/installation.md` still finds a match.
- `! grep -rn '0\.0\.0\.0' docs/setup docs/console` (no match: examples bind loopback).
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>Setup and Console sections document install, configuration and every summer and runtime command, all command names checker-verified, with the real-tree checks and gate docs/forbidden modes green.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| docs pages → operators | Operators copy commands and config from the docs into real deployments |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-11.1-11 | Information disclosure | docs/setup/installation.md, configuration.md examples | low | mitigate | Secrets shown only as `<secret>` markers or as the value printed by key:generate; no real-looking keys or DSN passwords |
| T-11.1-12 | Elevation of privilege | docs/setup and docs/console serve examples | medium | mitigate | Examples bind 127.0.0.1; acceptance grep rejects 0.0.0.0 in setup and console pages |
| T-11.1-SC | Tampering | npm/pip/cargo/go installs | high | accept | This plan adds no module or package; content and test files only |
</threat_model>
<verification>
- `go vet ./... && go test ./cmd/summer ./modules/party ./modules/backpack ./modules/pact ./modules/festival ./modules/towel ./modules/bonfire -count=1` green.
- `go run ./cmd/summer docs:build --check` exits 0; `scripts/check-phase11.1.sh --docs --forbidden` modes pass.
</verification>
<success_criteria>
- SC1 (Setup, Architecture, Plugins, Console sections present).
- SC4 (every Go example in these pages compiled and run by go test).
- SC5 (the Coming from WinterCMS concept map exists with checker-verified identifiers).
</success_criteria>
## Artifacts this phase produces
- Pages: `docs/setup/{introduction,installation,configuration,coming-from-wintercms}.md`, `docs/architecture/{introduction,go-modules-and-workspaces,application-lifecycle,request-lifecycle}.md`, `docs/plugins/{registration,scheduling,extending,testing}.md`, `docs/console/{introduction,setup-and-maintenance,scaffolding,writing-commands,utilities}.md`.
- site.yaml sections: `architecture`, `plugins`, `console`.
- Examples: `party.ExamplePlugin`; Examples in `modules/backpack/example_test.go`, `modules/pact/example_test.go`, `modules/festival/example_test.go`, `modules/towel/example_test.go`; additions to `modules/bonfire/example_test.go`.
- Test: `TestDocsRequiredPages` (cmd/summer).
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-03-SUMMARY.md` when done
</output>

View File

@@ -0,0 +1,329 @@
---
phase: 11.1-summercms-documentation-for-humans-and-ai-agents
plan: 04
type: execute
wave: 4
depends_on: ["11.1-03"]
files_modified:
- docs/site.yaml
- docs/index.md
- docs/setup/coming-from-wintercms.md
- docs/database/models.md
- docs/database/migrations.md
- docs/database/queries-and-pagination.md
- docs/database/relations.md
- docs/database/casts-and-validation.md
- docs/database/attachments.md
- docs/database/transactions.md
- docs/backend/admin-controllers.md
- docs/backend/forms.md
- docs/backend/lists-and-filters.md
- docs/backend/relation-manager.md
- docs/backend/users-and-permissions.md
- docs/backend/settings.md
- docs/backend/partials-and-widgets.md
- docs/backend/admin-spa.md
- docs/services/configuration.md
- docs/services/events.md
- docs/services/routing.md
- docs/services/rate-limiting.md
- docs/services/authentication.md
- docs/services/oauth-server.md
- docs/services/mail.md
- docs/services/localization.md
- docs/services/storage.md
- docs/services/outbound-http.md
- docs/services/jobs.md
- docs/services/realtime.md
- docs/services/push.md
- docs/services/search.md
- docs/services/parity-testing.md
- docs/services/frontend-and-ajax.md
- modules/conga/example_test.go
- modules/conga/export_docs_test.go
- modules/lagoon/example_test.go
- modules/lagoon/export_docs_test.go
- modules/lagoon/attach/example_test.go
- modules/compass/example_test.go
- modules/surf/example_test.go
- modules/wire/example_test.go
- modules/bouncer/example_test.go
- modules/wristband/example_test.go
- modules/postcard/example_test.go
- modules/phrasebook/example_test.go
- modules/cabana/example_test.go
- modules/fetchguard/example_test.go
- modules/lighthouse/example_test.go
- modules/lighthouse/export_docs_test.go
- modules/flare/example_test.go
- modules/beachcomber/example_test.go
- modules/beachcomber/export_docs_test.go
- modules/tide/example_test.go
- cmd/summer/docs_test.go
autonomous: true
requirements: [DOCS-01, DOCS-04, DOCS-06]
assumption_delta_decision: no-change
user_setup: []
estimate:
tokens: 150000
raw_tokens: 150000
tasks: 3
confidence: low
must_haves:
truths:
- "Per D-08, site.yaml lists the sections in the order Setup, Architecture, Plugins, Backend, Database, Services, Console, API reference, and TestDocsRequiredPages asserts exactly that order."
- "Per D-08, the Backend section covers admin auth, forms, lists, the relation manager and settings; Database covers models, migrations, relations, casts and validation; Services covers events, config, mail, i18n, jobs, realtime, search, storage, rate limiting and HTTP routing with auth groups, plus push (flare), outbound HTTP (fetchguard), the OAuth server (wristband), parity testing (tide) and lagoon transactions with after-commit work."
- "The Services section has a 'Frontend and AJAX (not provided)' page that explains the headless model (JSON API, realtime, the frontend as a separate application) and states that CMS pages, themes, components, the AJAX framework and Snowboard are not provided."
- "Per D-09, every concept map row in docs/setup/coming-from-wintercms.md whose topic now has a guide page links that page, and the Not provided rows link the Frontend and AJAX page."
- "Per D-07, every Go fence in these pages is a src= copy: an Example with `// Output:` using memory, log or null drivers where the API runs without Postgres, or a docs:start region in a helper that a `TestDocs*` test runs against the module's existing Docker Postgres harness (skipped only under -short, like the module's other database tests)."
- "Per D-11 and D-17, no page quotes the wristband default resource URL or any consuming-application name, and no snippet is taken from wristband source comments; wristband snippets come from modules/wristband/example_test.go."
- "The realtime, push, jobs, search and transactions pages describe the Phase 11 behaviour as shipped: broadcasts enqueued in the write transaction and published only after commit, WithoutBroadcasting suppression per model type, search sync after commit gated by a kill-switch with SearchIDs as candidates to re-check in SQL, push only to https hosts on push.allowed_hosts without redirects, and AfterCommit refusing to run external work inside a foreign transaction."
- "No module API changes in this plan; an awkward API is described as it is and logged under .planning/todos/pending/."
- "TestDocsTree, TestDocsRequiredPages and TestDocsAIOutputsInSync pass on the real tree after each task."
- statement: "The Backend, Database and Services pages are accurate to the modules and useful to a WinterCMS developer (manual review against the module READMEs)."
verification: backstop
prohibitions:
- requirement_id: DOCS-06
category: transparency
status: resolved
verification: judgment
resolution: "The Frontend and AJAX page and the concept map's Not provided rows state the gaps; guide pages link them."
reason: "Porters plan work from the docs; an implied feature that does not exist costs them a rewrite."
statement: "The docs must not describe a WinterCMS feature that SummerCMS does not provide as if it were available."
- requirement_id: DOCS-05
category: safety
status: resolved
verification: judgment
resolution: "Pages state the secure defaults recorded in the module READMEs (mandatory STARTTLS, loopback-only proxies, allowlisted push hosts, SQL re-gate of search results) and mark insecure options as development-only."
reason: "Operators copy configuration from docs; a page that presents an insecure option as the normal setting weakens every deployment."
statement: "The docs must not present an insecure setting (plain SMTP, disabled TLS, a non-loopback preview, unfiltered search results) as the default or recommended configuration."
artifacts:
- path: "docs/services/jobs.md"
provides: "jobs guide (conga)"
contains: "conga."
- path: "docs/services/frontend-and-ajax.md"
provides: "not-provided page for the WinterCMS frontend, AJAX framework and Snowboard"
contains: "Snowboard"
- path: "docs/database/transactions.md"
provides: "lagoon.Transaction and AfterCommit guide"
contains: "lagoon.AfterCommit"
- path: "modules/conga/export_docs_test.go"
provides: "test-only access to the conga Postgres harness for docs regions"
contains: "package conga"
key_links:
- from: "docs/services/jobs.md"
to: "modules/conga/example_test.go"
via: "go fence src= reference"
pattern: "src=modules/conga/example_test\\.go#"
- from: "modules/conga/example_test.go"
to: "modules/conga/export_docs_test.go"
via: "TestDocs* runs the region helper on the exported harness DB"
pattern: "TestDocs"
- from: "docs/setup/coming-from-wintercms.md"
to: "docs/services/frontend-and-ajax.md"
via: "Not provided rows link the page"
pattern: "frontend-and-ajax\\.md"
---
<objective>
Write framework content B: the Database, Backend and Services sections, including the Phase 11 infrastructure (jobs with conga, realtime with lighthouse and its Centrifugo driver, Web Push with flare, search with beachcomber, the parity recorder in tide) and lagoon transactions with after-commit work, plus the "Frontend and AJAX (not provided)" page. Every Go fence is a verified copy of code that runs under `go test ./...`.
Purpose: complete D-08's section list and the D-09 concept map links. Per the Phase 11 summaries, the module names are conga, lighthouse (with lighthouse/centrifugo), flare, beachcomber (with beachcomber/typesense) and tide; the identifier checker stays the source of truth at execution time.
Output: 31 pages, three new site.yaml sections, `example_test.go` files for 17 packages, four test-only harness exports, and the updated concept map and required-pages test.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.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-03-SUMMARY.md
@.planning/phases/11-jobs-realtime-and-search-infrastructure/11-CONTEXT.md
@CLAUDE.md
@docs/site.yaml
@docs/setup/coming-from-wintercms.md
Authoring rules (same as plan 11.1-03; the checkers enforce them): strict frontmatter with one-sentence descriptions of at most 160 characters; first body line `# <title>`; ASCII headings without links or code; identifiers as backticked `pkg.Ident` spans; module links by README path; Go fences written empty with `src=` and filled by `go run ./cmd/summer docs:sync`; neutral names (`acme`, `blog`); Winter tone; describe APIs as they are. Stage only each task's files.
Database-bound snippets (D-07 "compiled and run"): when the code needs Postgres, write it as a helper function in the module's `example_test.go` (external `<m>_test` package) wrapped in `// docs:start <name>` / `// docs:end <name>`, and call the helper from a `TestDocs<Name>` test in the same file. The test gets a database from the module's existing internal harness through a new `export_docs_test.go` in the internal package (Go's export_test pattern: an exported test-only wrapper around `lagoonDB`/`dedicatedDB`, `migratedDB`, `testApp` and similar, read from modules/<m>/postgres_test.go). It skips under `testing.Short()` like the module's other database tests and fails, not skips, when Docker is missing in a full run (the harness TestMain already behaves this way). Prefer runnable Examples with the memory, log or null drivers where they exist (lighthouse memory driver, postcard memory driver, beachcomber null engine).
Files gap plan 11-08 changed (modules/lagoon/transaction.go, transaction_test.go, README.md, modules/cabana/crud.go, relation.go, modules/beachcomber/sync_test.go) are read-only for this plan.
Earlier phase gates also scan some of these module directories: scripts/check-phase10.sh --hygiene greps modules/boardwalk, modules/cabana and modules/phrasebook, and scripts/check-phase11.sh --hygiene greps modules/conga, modules/lighthouse, modules/flare and modules/beachcomber, both for application names and Polish catalogue words (see APPNAME_RE in scripts/check-phase11.sh). Example text in those directories uses acme/blog vocabulary only; a Polish plural example in phrasebook uses words such as post, posty, postów.
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: the Jobs page shows a dispatched job that really runs, and the concept map links it</name>
<precondition>.planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-SUMMARY.md exists (lagoon transaction and after-commit changes committed)</precondition>
<files>docs/site.yaml, docs/services/jobs.md, modules/conga/example_test.go, modules/conga/export_docs_test.go, docs/setup/coming-from-wintercms.md, cmd/summer/docs_test.go</files>
<read_first>
- modules/conga/README.md and `go doc -all ./modules/conga`
- modules/conga/postgres_test.go (TestMain, adminDB, migratedDB, testApp) and modules/conga/manager_test.go (how Dispatch is exercised)
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-SUMMARY.md and 11-02-SUMMARY.md
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-CONTEXT.md (the job manager, workers and scheduler decisions)
- docs/setup/coming-from-wintercms.md (queued jobs row)
</read_first>
<action>
Prove the database-bound snippet path end to end on one Services page before the bulk.
1. `docs/site.yaml`: add `services` ("Services") after `plugins` (and before `console`), in the same change as its first page.
2. `modules/conga/export_docs_test.go` (package `conga`): export test-only wrappers over the existing harness (for example `func DocsApp(t *testing.T) (*backpack.App, *gorm.DB)` built from `migratedDB` and `testApp`).
3. `modules/conga/example_test.go` (package `conga_test`): a runnable `Example` for declaring a typed job with `conga.Job` (no database needed), ending with `// Output:`; and a region helper (for example `docs:start dispatch`) that dispatches a job inside a transaction and reads its `summer_jobs` status, called from `TestDocsDispatch`, which uses the exported harness, skips under `-short`, and asserts the job completes.
4. `docs/services/jobs.md` (order 110): declaring jobs, registering them through `pact.HasJobs`, dispatching in the caller's transaction, the `summer_jobs` record and its statuses (in queue, in progress, complete, error, stopped), progress and cancellation, workers in `serve` versus `queue:work --queue`, `queue.work_in_serve`, `queue:clear`, and a link to Plugins, Scheduling. Two `go src=modules/conga/example_test.go#...` fences filled by `summer docs:sync`.
5. `docs/setup/coming-from-wintercms.md`: link the queued-jobs row to `../services/jobs.md`.
6. `TestDocsRequiredPages`: append `services/jobs`.
</action>
<verify>
<automated>go vet ./... && go test ./modules/conga -run '^(Example.*|TestDocsDispatch)$' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" or "--- SKIP: TestDocsDispatch" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `go test ./modules/conga -run '^TestDocsDispatch$' -count=1 -v` prints `--- PASS: TestDocsDispatch` (Docker available, not -short).
- `grep -c 'src=modules/conga/example_test.go#' docs/services/jobs.md` prints at least 2.
- `grep -n 'services/jobs.md' docs/setup/coming-from-wintercms.md` finds a match.
- `grep -n '// docs:start' modules/conga/example_test.go` finds a match.
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>The Jobs page is live in a new Services section with one runnable Example and one database-backed region that a test runs, and the concept map points to it.</done>
</task>
<task type="auto">
<name>Task 2: Database section and the core Services pages (configuration, events, routing, rate limiting, authentication, OAuth, mail, localization)</name>
<files>docs/site.yaml, docs/database/models.md, docs/database/migrations.md, docs/database/queries-and-pagination.md, docs/database/relations.md, docs/database/casts-and-validation.md, docs/database/attachments.md, docs/database/transactions.md, docs/services/configuration.md, docs/services/events.md, docs/services/routing.md, docs/services/rate-limiting.md, docs/services/authentication.md, docs/services/oauth-server.md, docs/services/mail.md, docs/services/localization.md, modules/lagoon/example_test.go, modules/lagoon/export_docs_test.go, modules/lagoon/attach/example_test.go, modules/compass/example_test.go, modules/surf/example_test.go, modules/wire/example_test.go, modules/bouncer/example_test.go, modules/wristband/example_test.go, modules/postcard/example_test.go, modules/phrasebook/example_test.go, cmd/summer/docs_test.go</files>
<read_first>
- modules/lagoon/README.md (after 11-08), modules/compass/README.md, modules/festival/README.md, modules/surf/README.md, modules/wire/README.md, modules/bouncer/README.md, modules/wristband/README.md, modules/postcard/README.md, modules/phrasebook/README.md, modules/towel/README.md
- `go doc -all` for lagoon, lagoon/attach, compass, surf, wire, bouncer, wristband, postcard, phrasebook
- modules/lagoon/postgres_test.go (TestMain, lagoonDB, dedicatedDB; ICU pl-PL database idiom)
- modules/festival/example_test.go (plan 11.1-03; reuse its region for the events page)
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-SUMMARY.md (AfterCommit behaviour in foreign transactions, nested Transaction rule)
- .planning/todos/pending/wristband-neutral-resource-default.md (do not quote the default)
</read_first>
<action>
Per D-08 (Database: models, migrations, relations, casts, validation; Services: config, events, i18n, mail, rate limiting, HTTP routing and auth groups):
1. `docs/site.yaml`: add `database` ("Database") before `services`.
2. Database pages (orders 10-70), each with at least one verified Go fence:
- `models.md`: GORM structs with lagoon helpers, mass assignment with `lagoon.Fill` and its allow-list, hidden fields in serialization, lifecycle hooks, the models-leaf rule.
- `migrations.md`: gormigrate migrations per plugin through `pact.HasMigrations`, `migrate`, `migrate:rollback --plugin`, `migrate:status`, ordering by file name, `make:migration`.
- `queries-and-pagination.md`: `lagoon.OrderBy` with a caller allow-list, `lagoon.Paginate` and its response shape.
- `relations.md`: GORM associations, join tables with `lagoon.RegisterJoinTable`, soft-delete cascades.
- `casts-and-validation.md`: JSON columns, encrypted columns and the application key, `lagoon.Validate` with Laravel-style rule strings.
- `attachments.md`: `attach` file attachments on models, blob keys, thumbnails, deletion after commit.
- `transactions.md`: `lagoon.Transaction`, `lagoon.AfterCommit` (runs after commit; skipped with a warning inside a foreign plain `*sql.Tx`; a nested `lagoon.Transaction` over a root handle fails), `lagoon.OnDatabase` for boot-time work, and why side effects (broadcasts, search sync) wait for commit.
Database-bound snippets follow the region-plus-`TestDocs*` rule with `modules/lagoon/export_docs_test.go` exposing the lagoon harness; pure helpers (Validate, OrderBy allow-list checks, Fill) are runnable Examples.
3. Services pages (orders 10-80):
- `configuration.md`: `compass` layering, per-environment dirs, `SUMMER_` overrides, plugin defaults, dot-path access, `compass.Config.Persist`; runnable Example over a temp config dir.
- `events.md`: `festival.Bus` listeners, priorities, stop-when-handled, collected results; reuse the festival region from plan 11.1-03 or add one.
- `routing.md`: routes from `pact.HasRoutes`, groups and auth groups, constraints (`surf.Where`, `surf.WhereIn` style), named middleware, body limits, CORS, recovery, `route:list`, JSON responses with `wire`; runnable Examples with `httptest`.
- `rate-limiting.md`: throttle buckets, named buckets from `surf.BucketProvider`, trusted proxies and client IP.
- `authentication.md`: `bouncer` JWT minting and verification, guards, the blacklist, password hashing; runnable Example that mints and verifies with a test-only secret.
- `oauth-server.md`: `wristband` metadata, dynamic client registration, authorization code with PKCE, consent, refresh rotation over application storage. Do not quote the default resource URL; tell the application to set it. Snippets only from `modules/wristband/example_test.go`.
- `mail.md`: `postcard` templates and layouts in plugins, drivers memory/log/smtp, mandatory STARTTLS by default with plain SMTP as an explicit development-only opt-in; runnable Example with the memory driver.
- `localization.md`: `phrasebook` catalogs, fallback order, `:name` interpolation, CLDR plurals, request locale in `towel`; runnable Example.
4. Append the 15 page URLs to `TestDocsRequiredPages`.
5. Run `go run ./cmd/summer docs:sync` and `go run ./cmd/summer docs:build --check`; fix every problem in the pages.
</action>
<verify>
<automated>go vet ./... && go test ./modules/lagoon/... ./modules/compass ./modules/surf ./modules/wire ./modules/bouncer ./modules/wristband ./modules/postcard ./modules/phrasebook -run '^(Example|TestDocs)' -count=1 && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages|TestDocsAIOutputsInSync)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `ls docs/database/*.md | wc -l` prints 7 and `ls docs/services/*.md | wc -l` prints 9.
- `grep -l '// Output:' modules/lagoon/example_test.go modules/compass/example_test.go modules/surf/example_test.go modules/wire/example_test.go modules/bouncer/example_test.go modules/wristband/example_test.go modules/postcard/example_test.go modules/phrasebook/example_test.go | wc -l` prints 8.
- `grep -n 'lagoon.AfterCommit' docs/database/transactions.md` finds a match.
- `grep -c 'src=modules/wristband/' docs/services/oauth-server.md` prints at least 1 and `! grep -n 'src=modules/wristband/server.go\|src=modules/wristband/stores.go\|src=modules/wristband/client_issue.go' docs/services/oauth-server.md` (no match).
- `grep -n 'STARTTLS' docs/services/mail.md` finds a match.
- `go run ./cmd/summer docs:build --check` exits 0 and `scripts/check-phase11.1.sh --forbidden` passes.
</acceptance_criteria>
<done>Database and the core Services pages are in the sidebar with running examples, the transaction page matches the 11-08 behaviour, and the real-tree checks and the forbidden-name gate pass.</done>
</task>
<task type="auto">
<name>Task 3: Backend section, the remaining Services pages, the Frontend and AJAX page, and concept-map links</name>
<files>docs/site.yaml, docs/index.md, docs/setup/coming-from-wintercms.md, docs/backend/admin-controllers.md, docs/backend/forms.md, docs/backend/lists-and-filters.md, docs/backend/relation-manager.md, docs/backend/users-and-permissions.md, docs/backend/settings.md, docs/backend/partials-and-widgets.md, docs/backend/admin-spa.md, docs/services/storage.md, docs/services/outbound-http.md, docs/services/realtime.md, docs/services/push.md, docs/services/search.md, docs/services/parity-testing.md, docs/services/frontend-and-ajax.md, modules/cabana/example_test.go, modules/fetchguard/example_test.go, modules/lighthouse/example_test.go, modules/lighthouse/export_docs_test.go, modules/flare/example_test.go, modules/beachcomber/example_test.go, modules/beachcomber/export_docs_test.go, modules/tide/example_test.go, cmd/summer/docs_test.go</files>
<read_first>
- modules/cabana/README.md, modules/boardwalk/README.md, modules/pact/README.md (admin interfaces, SettingsItem, AdminAction, partial and asset interfaces)
- modules/cabana/testdata/ (YAML fixtures that can be referenced by src= instead of hand-written YAML)
- modules/lighthouse/README.md, modules/lighthouse/centrifugo (go doc), modules/flare/README.md, modules/beachcomber/README.md, modules/beachcomber/typesense (go doc), modules/fetchguard/README.md, modules/tide/README.md
- modules/lighthouse/postgres_test.go and modules/beachcomber/postgres_test.go (harness helpers to export)
- .planning/phases/11-jobs-realtime-and-search-infrastructure/11-03-SUMMARY.md, 11-04-SUMMARY.md, 11-05-SUMMARY.md, 11-06-SUMMARY.md
- .planning/phases/10.1-runtime-admin-extension-point/ summaries (partials, widgets, assets, toolbar actions)
- docs/setup/coming-from-wintercms.md
</read_first>
<action>
Per D-08 (Backend: admin auth, forms, lists, relation manager, settings; Services: realtime, search, storage) and D-09:
1. `docs/site.yaml`: add `backend` ("Backend") after `plugins` and before `database`, so the final order is setup, architecture, plugins, backend, database, services, console, api. Make `TestDocsRequiredPages` also assert this exact section order.
2. Backend pages (orders 10-80): `admin-controllers.md` (`pact.AdminController`, config_form.yaml and config_list.yaml, the JSON admin API, hooks, toolbar actions, `make:admin-controller`), `forms.md` (fields.yaml, field types, options providers, spans, read-only and context fields), `lists-and-filters.md` (columns.yaml, sorting, pagination options, filter scopes), `relation-manager.md`, `users-and-permissions.md` (admin JWT and cookie auth, permissions, `admin:create`, `admin:reset-password`), `settings.md` (`pact.SettingsItem` settings pages), `partials-and-widgets.md` (server-rendered partials, client assets, widget and toolbar actions from Phase 10.1), `admin-spa.md` (`boardwalk`, the `backend.uri` prefix, OpenAPI-generated types). YAML examples use `yaml src=` references to real cabana test fixtures where one exists; Go snippets come from `modules/cabana/example_test.go` (schema compilation or controller declaration that runs without a database).
3. Services pages (orders 90-160): `storage.md` (upload buckets, bucket URLs, `attach` link), `outbound-http.md` (`fetchguard` private-address blocking, host, size and timeout limits), `realtime.md` (`lighthouse` drivers and `realtime.driver`, channel naming rules, the authorizer registry, `lighthouse.Mount` with surfaces, model broadcasts enqueued in the write transaction and published after commit, `lighthouse.WithoutBroadcasting` and `Emit`, the Centrifugo driver's token and subscribe routes, `websockets:health`), `push.md` (`flare` Web Push with VAPID, https-only allowlisted hosts, no redirects, `websockets:generate-vapid-keys`, `websockets:test-push`), `search.md` (`beachcomber` Searchable models, engines and `search.driver`, after-commit sync gated by a kill-switch, `SearchIDs` results as candidates that the application re-checks in SQL, the Typesense engine), `parity-testing.md` (`tide` record and replay, normalized diffs, broadcast goldens, the `parity:*` commands), `frontend-and-ajax.md` (title "Frontend and AJAX (not provided)": SummerCMS is headless; CMS pages, themes, layouts, components, the AJAX framework and Snowboard are not provided; build the frontend as a separate application against the JSON API and realtime channels; link routing, authentication and realtime). Realtime and search snippets are runnable Examples on the memory driver and null engine; database-bound broadcast or sync snippets use the region-plus-`TestDocs*` rule with `export_docs_test.go` in lighthouse and beachcomber.
4. `docs/setup/coming-from-wintercms.md`: link every row whose topic now has a guide page (models, migrations, backend controllers, forms and lists, routes, middleware, config, lang, events, mail, settings, broadcasting, search, HTTP client) and point the Not provided rows at `../services/frontend-and-ajax.md`. `docs/index.md`: add the Backend, Database and Services entry points.
5. Append the 15 page URLs to `TestDocsRequiredPages`.
6. Run `go run ./cmd/summer docs:sync` and `go run ./cmd/summer docs:build --check`; fix every problem in the pages.
</action>
<verify>
<automated>go vet ./... && go test ./modules/cabana ./modules/fetchguard ./modules/lighthouse/... ./modules/flare ./modules/beachcomber/... ./modules/tide -run '^(Example|TestDocs)' -count=1 && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages|TestDocsAIOutputsInSync|TestDocsBuildRealTree)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.1.sh --docs && scripts/check-phase11.1.sh --forbidden && scripts/check-phase10.sh --hygiene && scripts/check-phase11.sh --hygiene</automated>
<fails_when>non-zero exit or a line starting "refuse:"</fails_when>
</verify>
<acceptance_criteria>
- `ls docs/backend/*.md | wc -l` prints 8 and `ls docs/services/*.md | wc -l` prints 16.
- `grep -n 'Snowboard' docs/services/frontend-and-ajax.md` finds a match.
- `grep -c 'frontend-and-ajax.md' docs/setup/coming-from-wintercms.md` prints at least 1.
- `grep -n 'WithoutBroadcasting' docs/services/realtime.md` finds a match and `grep -n 'SearchIDs' docs/services/search.md` finds a match.
- `go test ./cmd/summer -run '^TestDocsRequiredPages$' -count=1 -v` prints `--- PASS: TestDocsRequiredPages` with the section-order assertion.
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>All eight D-08 sections are in the sidebar in Winter order, the Phase 11 services are documented as shipped with running examples, the not-provided frontend is explicit, and the concept map links every guide.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| docs pages → operators | Operators copy security-relevant configuration (mail TLS, push hosts, search re-gating, OAuth resource) from these pages |
| module sources → published snippets | src= copies publish source text, including comments |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-11.1-13 | Information disclosure (policy) | docs/services/oauth-server.md snippets | medium | mitigate | Snippets only from modules/wristband/example_test.go (acceptance grep forbids src= into wristband server.go, stores.go, client_issue.go); forbidden-name check over outputs |
| T-11.1-14 | Tampering (misconfiguration) | docs/services mail, push, search, realtime pages | medium | mitigate | Pages state the shipped secure defaults (STARTTLS, allowlisted https push hosts, SQL re-gate of SearchIDs, secret-checked subscribe proxy) and mark insecure options development-only; safety prohibition reviewed at verify |
| T-11.1-15 | Denial of service (test isolation) | export_docs_test.go harness wrappers | low | mitigate | Wrappers reuse each module's dedicated-database harness; no shared or developer database is touched |
| T-11.1-SC | Tampering | npm/pip/cargo/go installs | high | accept | This plan adds no module or package |
</threat_model>
<verification>
- `go vet ./... && go test ./... -count=1` green with Docker (the TestDocs* database tests run, not skip).
- `go run ./cmd/summer docs:build --check` exits 0; `scripts/check-phase11.1.sh --docs --forbidden` pass.
</verification>
<success_criteria>
- SC1: all Winter-mirroring sections present with every framework module reachable from the sidebar.
- SC4: every Go example in these pages is compiled and run by `go test ./...`.
- SC5 (partial): the concept map links every guide page and the not-provided page.
</success_criteria>
## Artifacts this phase produces
- Pages: `docs/database/{models,migrations,queries-and-pagination,relations,casts-and-validation,attachments,transactions}.md`, `docs/backend/{admin-controllers,forms,lists-and-filters,relation-manager,users-and-permissions,settings,partials-and-widgets,admin-spa}.md`, `docs/services/{configuration,events,routing,rate-limiting,authentication,oauth-server,mail,localization,storage,outbound-http,jobs,realtime,push,search,parity-testing,frontend-and-ajax}.md`.
- site.yaml sections: `backend`, `database`, `services` (final order setup, architecture, plugins, backend, database, services, console, api).
- Examples and docs tests in `modules/{conga,lagoon,lagoon/attach,compass,surf,wire,bouncer,wristband,postcard,phrasebook,cabana,fetchguard,lighthouse,flare,beachcomber,tide}/example_test.go`; `TestDocs*` database-backed snippet tests; test-only harness exports `modules/{conga,lagoon,lighthouse,beachcomber}/export_docs_test.go`.
- Updated `docs/setup/coming-from-wintercms.md`, `docs/index.md`, `TestDocsRequiredPages` with the section-order assertion.
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-04-SUMMARY.md` when done
</output>

View File

@@ -0,0 +1,272 @@
---
phase: 11.1-summercms-documentation-for-humans-and-ai-agents
plan: 05
type: execute
wave: 5
depends_on: ["11.1-04"]
files_modified:
- docs/examples/blog/plugin.go
- docs/examples/blog/routes.go
- docs/examples/blog/registry.gen.go
- docs/examples/blog/blog_test.go
- docs/examples/blog/postgres_test.go
- docs/examples/blog/scaffold_layout_test.go
- docs/examples/blog/classes/doc.go
- docs/examples/blog/config/config.yaml
- docs/examples/blog/console/doc.go
- docs/examples/blog/console/publish.go
- docs/examples/blog/console/publish_test.go
- docs/examples/blog/controllers/doc.go
- docs/examples/blog/controllers/posts.go
- docs/examples/blog/controllers/posts_test.go
- docs/examples/blog/controllers/posts/config_form.yaml
- docs/examples/blog/controllers/posts/config_list.yaml
- docs/examples/blog/jobs/doc.go
- docs/examples/blog/lang/en/lang.yaml
- docs/examples/blog/middleware/doc.go
- docs/examples/blog/models/doc.go
- docs/examples/blog/models/post.go
- docs/examples/blog/models/post_test.go
- docs/examples/blog/models/posts/fields.yaml
- docs/examples/blog/models/posts/columns.yaml
- docs/examples/blog/updates/doc.go
- docs/examples/blog/updates/20260101000000_create_acme_blog_posts.go
- docs/examples/blog/updates/20260101000100_add_published_at.go
- docs/examples/blog/updates/updates_test.go
- docs/examples/blog/views/mail/welcome.htm
- docs/setup/porting-a-plugin.md
- docs/setup/coming-from-wintercms.md
- docs/index.md
- cmd/summer/docs_test.go
autonomous: true
requirements: [DOCS-04, DOCS-07]
assumption_delta_decision: no-change
user_setup: []
estimate:
tokens: 110000
raw_tokens: 110000
tasks: 3
confidence: low
must_haves:
truths:
- "Per D-10 and DOCS-07, docs/setup/porting-a-plugin.md takes a WinterCMS `acme/blog` plugin (Plugin.php, a Post model, version.yaml updates, routes.php, a backend Posts controller with fields.yaml and columns.yaml, an artisan command) to a compiled SummerCMS plugin, showing the WinterCMS side as php/yaml fences and the SummerCMS side only as src= copies of docs/examples/blog."
- "Per D-16, docs/examples/blog is an ordinary package tree of the root module (import path git.golem15.com/golem15/summercms/docs/examples/blog), with no go.mod of its own, covered by root `go vet ./...` and `go test ./...`."
- "Per D-07, every Go fence and every YAML fence on the walkthrough page carries src= into docs/examples/blog and matches the source byte for byte; every referenced sub-package directory has _test.go files that exercise it."
- "The -short-safe tests activate the plugin through party and backpack, assert its routes are registered with surf, run the console command with bonfire into a buffer, and check the admin controller declaration; the Docker tests migrate up on an ICU pl-PL database, create, list and publish posts through the model, the route handler and the command, then roll back the last migration and confirm the column is gone."
- "TestScaffoldLayout runs build.MakePlugin, MakeModel, MakeAdminController, MakeCommand and MakeMigration for acme.blog into a copy of examples/hello and asserts that the relative file set of docs/examples/blog (without _test.go files, with 14-digit migration timestamps normalised) equals the scaffolder's output (without go.mod and go.sum)."
- "The walkthrough's writes go through an explicit fill allow-list (lagoon.Fill) and the admin controller declares a permission; the route handler uses parameterised queries only."
- "Per D-11, the plugin, its tests and the page use only neutral names (acme, blog)."
- "The walkthrough page lists the exact `summer make:*` commands that produced the layout, and the command checker accepts every `summer` and `./bin/<app>` token on it (blog:publish is collected from the docs/examples command literal)."
- "Scaffolder oddities found while porting (for example same-second migration ordering by file name, or the generated-code header on files the developer edits) are described on the page as they are and logged under .planning/todos/pending/, without changing internal/build."
- statement: "A WinterCMS developer can follow the walkthrough from top to bottom and end with the same plugin (manual review)."
verification: backstop
prohibitions:
- requirement_id: DOCS-07
category: safety
status: resolved
verification: judgment
resolution: "Model writes use lagoon.Fill with an allow-list, the admin controller declares a permission, and the handler binds parameters."
reason: "Developers copy walkthrough code into production plugins; an unsafe pattern in the canonical example spreads to every port."
statement: "The walkthrough must not demonstrate a write path without an explicit fill allow-list or an admin controller without a declared permission."
artifacts:
- path: "docs/examples/blog/plugin.go"
provides: "the acme.blog plugin"
contains: "acme.blog"
- path: "docs/examples/blog/scaffold_layout_test.go"
provides: "TestScaffoldLayout pinning the walkthrough to the scaffolder"
contains: "MakeAdminController"
- path: "docs/examples/blog/postgres_test.go"
provides: "Docker-backed migrate, CRUD, route, command and rollback tests"
contains: "ICU_LOCALE"
- path: "docs/setup/porting-a-plugin.md"
provides: "the porting walkthrough page"
contains: "src=docs/examples/blog/"
key_links:
- from: "docs/setup/porting-a-plugin.md"
to: "docs/examples/blog/plugin.go"
via: "go fence src= references kept in sync by the snippet check"
pattern: "src=docs/examples/blog/plugin\\.go"
- from: "docs/examples/blog/scaffold_layout_test.go"
to: "internal/build/artifact.go"
via: "calls build.Make* into a temp app and compares file sets"
pattern: "build\\.Make"
- from: "docs/examples/blog/registry.gen.go"
to: "docs/examples/blog/models/post.go"
via: "generated accessors list models, migrations, commands and admin controllers"
pattern: "generatedModels"
---
<objective>
Build the `acme/blog` porting walkthrough (D-10, D-16, DOCS-07): a real, compiled and tested SummerCMS plugin under `docs/examples/blog` with a model, two migrations, a route, an admin controller with its YAML, a console command and a language file, plus `docs/setup/porting-a-plugin.md` that walks a WinterCMS developer through it using only `src=` copies. A scaffold-layout test pins the tree to what `summer make:*` really emits.
Purpose: success criterion 5's walkthrough, verified under criterion 4.
Output: `docs/examples/blog/**` (package tree, tests, YAML), the walkthrough page, links from the concept map and index.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.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-04-SUMMARY.md
@CLAUDE.md
@internal/build/scaffold.go
@internal/build/artifact.go
@cmd/summer/main_test.go
@examples/hello/plugins/greeter/plugin.go
@modules/lagoon/postgres_test.go
Scaffolder facts (probed at planning time by running the tool in a scratch copy of examples/hello): `make:plugin acme.blog`, `make:model acme.blog Post`, `make:admin-controller acme.blog Posts`, `make:command acme.blog Publish` and `make:migration acme.blog AddPublishedAt` produce, relative to the plugin directory: `plugin.go`, `routes.go`, `registry.gen.go`, `go.mod`, `go.sum`, `classes/doc.go`, `config/config.yaml`, `console/doc.go`, `console/publish.go`, `controllers/doc.go`, `controllers/posts.go`, `controllers/posts/config_form.yaml`, `controllers/posts/config_list.yaml`, `jobs/doc.go`, `lang/en/lang.yaml`, `middleware/doc.go`, `models/doc.go`, `models/post.go`, `models/posts/fields.yaml`, `models/posts/columns.yaml`, `updates/doc.go`, `updates/<14-digit timestamp>_create_acme_blog_posts.go`, `updates/<timestamp>_add_published_at.go`, `views/mail/welcome.htm`. The admin YAML sits under the controller's snake name (`models/posts/`), and the generated admin controller names the model `Posts`. Two migrations created in the same second are listed in `registry.gen.go` in file-name order (`add_published_at` before `create_...`). TestScaffoldLayout is the source of truth if the scaffolder output differs at execution time.
Authoring rules for the page are those of plans 11.1-03 and 11.1-04 (strict frontmatter, ASCII headings, `pkg.Ident` spans, src= fences filled by `summer docs:sync`, neutral names). Stage only each task's files.
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: the blog plugin activates and serves its posts route, and the walkthrough page shows it from verified source</name>
<files>docs/examples/blog/plugin.go, docs/examples/blog/routes.go, docs/examples/blog/registry.gen.go, docs/examples/blog/blog_test.go, docs/examples/blog/classes/doc.go, docs/examples/blog/config/config.yaml, docs/examples/blog/console/doc.go, docs/examples/blog/controllers/doc.go, docs/examples/blog/jobs/doc.go, docs/examples/blog/lang/en/lang.yaml, docs/examples/blog/middleware/doc.go, docs/examples/blog/models/doc.go, docs/examples/blog/models/post.go, docs/examples/blog/models/post_test.go, docs/examples/blog/updates/doc.go, docs/examples/blog/updates/20260101000000_create_acme_blog_posts.go, docs/examples/blog/updates/updates_test.go, docs/examples/blog/views/mail/welcome.htm, docs/setup/porting-a-plugin.md, cmd/summer/docs_test.go</files>
<read_first>
- internal/build/scaffold.go and internal/build/stubs/*.tmpl (plugin.go, routes.go, registry and doc.go shapes)
- internal/build/artifact.go (MakeModel and migration stubs)
- cmd/summer/main_test.go copyHelloApp and TestMakeCommandsViaCLI (how to run the scaffolder against a copy of examples/hello)
- examples/hello/plugins/greeter/plugin.go (a hand-finished plugin)
- modules/surf/README.md (routes, pact.Router, BuildRouter or Assemble for tests), modules/lagoon/README.md (Fill, Paginate), modules/wire/README.md
- docs/setup/coming-from-wintercms.md (tone and PHP fence style)
</read_first>
<action>
Per D-10 and D-16, start from real scaffolder output so the layout is honest.
1. Generate the skeleton: build the tool (`go build -o <tmp>/summer ./cmd/summer`), copy examples/hello into a temp dir with its framework `replace` pointed at the repository root (as `copyHelloApp` does), run `make:plugin acme.blog` and `make:model acme.blog Post` there, then copy `plugins/blog/` into `docs/examples/blog/` without `go.mod` and `go.sum`. Rename the migration to the fixed timestamp `20260101000000_create_acme_blog_posts.go` (and its gormigrate ID to match) and rewrite every import path to `git.golem15.com/golem15/summercms/docs/examples/blog/...`. Keep the generated-code header lines as the scaffolder writes them.
2. Finish the model and migration: `models.Post` gains `Title`, `Slug` (unique) and `Body`; the publication timestamp arrives in Task 2 with the second migration. The create migration creates `acme_blog_posts` with those columns. Add a `models.Post` fill allow-list (title, slug, body) used through `lagoon.Fill`.
3. Route: in `routes.go`, `GET /api/blog/posts` lists posts newest first as JSON through `lagoon.Paginate` and `wire.WriteJSON`, using parameterised queries only and taking the `*gorm.DB` from the app the way framework plugins do (read lagoon's README). Declare it through `pact.HasRoutes`.
4. Tests (-short-safe, `blog_test.go` in package `blog_test`): `TestPluginActivates` activates the plugin through `party`/`backpack` and checks ID and capabilities; `TestRoutesRegistered` builds the router with surf and asserts the `GET /api/blog/posts` route exists without touching a database. `models/post_test.go`: `TestPostTableAndFill` (TableName and that `lagoon.Fill` with the allow-list drops an `id` key). `updates/updates_test.go`: `TestMigrationIDs` (IDs start with their 14-digit timestamps and are in ascending order in `registry.gen.go`'s list).
5. Page `docs/setup/porting-a-plugin.md` (title "Porting a plugin", section `setup`, order 50): intro (what we port and why a neutral `acme/blog`), then sections "Plugin registration" (a `php` fence of `Plugin.php` and a `go src=docs/examples/blog/plugin.go#Plugin` style fence), "The Post model" (`php` fence of `models/Post.php`, `go src=docs/examples/blog/models/post.go#Post`), "Migrations" (`yaml` fence of WinterCMS `version.yaml`, `go src=` of the create migration), "Routes" (`php` fence of `routes.php`, `go src=` of the route declaration and handler). Fill all src= fences with `go run ./cmd/summer docs:sync`.
6. Append `setup/porting-a-plugin` to `TestDocsRequiredPages`.
</action>
<verify>
<automated>go vet ./... && go test -short ./docs/examples/... -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
</verify>
<acceptance_criteria>
- `test ! -e docs/examples/blog/go.mod` (in-root package, D-16).
- `go list ./docs/examples/blog/...` prints `git.golem15.com/golem15/summercms/docs/examples/blog` among its lines.
- `go test -short ./docs/examples/blog -run '^(TestPluginActivates|TestRoutesRegistered)$' -count=1 -v` prints two `--- PASS` lines.
- `grep -c 'src=docs/examples/blog/' docs/setup/porting-a-plugin.md` prints at least 4.
- `grep -n 'lagoon.Fill' docs/examples/blog/routes.go docs/examples/blog/models/post.go docs/examples/blog/models/post_test.go` finds a match.
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>A compiled acme.blog plugin with a model, a migration and a route lives in the root module, its fast tests pass, and the walkthrough page shows it through verified src= copies.</done>
</task>
<task type="auto">
<name>Task 2: The walkthrough adds the admin controller, the console command and a second migration, and proves them against Postgres</name>
<files>docs/examples/blog/controllers/posts.go, docs/examples/blog/controllers/posts_test.go, docs/examples/blog/controllers/posts/config_form.yaml, docs/examples/blog/controllers/posts/config_list.yaml, docs/examples/blog/models/posts/fields.yaml, docs/examples/blog/models/posts/columns.yaml, docs/examples/blog/models/post.go, docs/examples/blog/console/publish.go, docs/examples/blog/console/publish_test.go, docs/examples/blog/updates/20260101000100_add_published_at.go, docs/examples/blog/registry.gen.go, docs/examples/blog/lang/en/lang.yaml, docs/examples/blog/postgres_test.go, docs/setup/porting-a-plugin.md</files>
<read_first>
- internal/build/artifact.go MakeAdminController, MakeCommand, MakeMigration stubs
- modules/cabana/README.md and modules/pact/README.md (AdminController, AdminPermissioned, config_form/config_list keys, fields.yaml and columns.yaml)
- modules/bonfire/README.md (Command, Arg, Output)
- modules/lagoon/postgres_test.go (TestMain behaviour, dedicatedDB with the ICU pl-PL template) and modules/lagoon/README.md (Migrate, RollbackLast)
- docs/examples/blog (Task 1 state)
</read_first>
<action>
1. Scaffold the rest the same way as Task 1 (`make:admin-controller acme.blog Posts`, `make:command acme.blog Publish`, `make:migration acme.blog AddPublishedAt` in the temp app), copy the new files in, rename the migration to `20260101000100_add_published_at.go` (ID to match) and update `registry.gen.go` so migrations are listed create first, then add_published_at.
2. Finish them: the admin controller points at the `Post` model, declares a permission (for example `acme.blog.access_posts`) and uses config_form.yaml and config_list.yaml; `models/posts/fields.yaml` has title, slug, body and published_at fields and `columns.yaml` lists title, slug and published_at, all in WinterCMS YAML syntax. The migration adds `published_at TIMESTAMPTZ NULL` and its Rollback drops it; `models.Post` gains `PublishedAt *time.Time`. `console/publish.go` becomes `blog:publish` with a required `slug` argument that sets `published_at` on the matching post (parameterised) and prints `published <slug>`, returning an error for an unknown slug. `lang/en/lang.yaml` carries the plugin name, the permission label and the field labels, referenced from the YAML.
3. Tests: `controllers/posts_test.go` (`TestPostsControllerDeclaration`: ID, ConfigDir, permission; and compile the admin YAML through cabana if it can do so without a database, otherwise assert the YAML parses and move the compile check into the Docker test). `console/publish_test.go` (`TestPublishCommandShape`: name, argument, `bonfire.Call` with no slug returns an error). `postgres_test.go` (package `blog_test`): a `TestMain` that starts a testcontainers Postgres and creates a database with `TEMPLATE template0 ENCODING 'UTF8' LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL'` (lagoon's idiom), skipping the container only under `testing.Short()` and failing when Docker is unavailable in a full run; `TestMigrateUpAndRollback` (lagoon.Migrate with the plugin, columns exist, `lagoon.RollbackLast` removes `published_at`, Migrate again), `TestPostsRouteAgainstDatabase` (seed through `lagoon.Fill`, GET /api/blog/posts through httptest returns them), `TestPublishCommandAgainstDatabase` (`bonfire.Call` of `blog:publish` sets `published_at` and prints `published <slug>`).
4. Page: add sections "Admin controller" (`php` fence of `controllers/Posts.php`, `go src=` of the controller, `yaml src=docs/examples/blog/controllers/posts/config_form.yaml`, `config_list.yaml`, `models/posts/fields.yaml`, `columns.yaml`), "Console command" (`php` fence of an artisan command, `go src=` of `console/publish.go`, a `sh` fence running `./bin/acme blog:publish hello-world`), "Adding a column" (the second migration, `summer migrate:rollback --plugin acme.blog`). Run `go run ./cmd/summer docs:sync`.
</action>
<verify>
<automated>go vet ./... && go test ./docs/examples/... -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, a "--- SKIP" line for a Postgres test in this full run, or "no tests to run"</fails_when>
<automated>go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages)$' -count=1</automated>
<fails_when>non-zero exit or a "FAIL" line</fails_when>
</verify>
<acceptance_criteria>
- `go test ./docs/examples/blog -run '^(TestMigrateUpAndRollback|TestPostsRouteAgainstDatabase|TestPublishCommandAgainstDatabase)$' -count=1 -v` prints three `--- PASS` lines.
- `grep -c 'yaml src=docs/examples/blog/' docs/setup/porting-a-plugin.md` prints at least 4.
- `grep -n 'ICU_LOCALE' docs/examples/blog/postgres_test.go` finds a match.
- `grep -n '"blog:publish"' docs/examples/blog/console/publish.go` finds a match.
- `grep -En 'Permission|permission' docs/examples/blog/controllers/posts.go` finds a match.
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>The walkthrough plugin has an admin controller with WinterCMS-style YAML, a console command and a reversible second migration, all exercised against a real Postgres, and the page shows them from verified source.</done>
</task>
<task type="auto">
<name>Task 3: The walkthrough is pinned to the scaffolder, lists the exact make commands, and is linked from the concept map and index</name>
<files>docs/examples/blog/scaffold_layout_test.go, docs/setup/porting-a-plugin.md, docs/setup/coming-from-wintercms.md, docs/index.md, cmd/summer/docs_test.go</files>
<read_first>
- internal/build/scaffold.go MakePlugin and pluginLeaves, internal/build/artifact.go Make* signatures (`go doc ./internal/build`)
- cmd/summer/main_test.go copyHelloApp (replace rewrite for a temp app)
- docs/examples/blog (Task 2 state) and docs/setup/porting-a-plugin.md
- .planning/todos/pending/redacting-slog-handler.md (todo format)
</read_first>
<action>
1. `docs/examples/blog/scaffold_layout_test.go` (package `blog_test`, -short-safe, no network): `TestScaffoldLayout` copies examples/hello into `t.TempDir()` with its framework `replace` pointed at the repository root, calls `build.MakePlugin(ctx, dir, "acme.blog")`, `build.MakeModel(ctx, dir, "acme.blog", "Post", false)`, `build.MakeAdminController(ctx, dir, "acme.blog", "Posts")`, `build.MakeCommand(ctx, dir, "acme.blog", "Publish")` and `build.MakeMigration(ctx, dir, "acme.blog", "AddPublishedAt")`, collects the relative file set under `plugins/blog` without `go.mod` and `go.sum`, collects the relative file set of `docs/examples/blog` without `_test.go` files, replaces a leading 14-digit timestamp in `updates/` file names with a fixed token in both, and fails with the symmetric difference when the sets differ.
2. Page: add a "Scaffold it yourself" section with the exact commands in order (`summer make:plugin acme.blog`, `summer make:model acme.blog Post`, `summer make:migration acme.blog AddPublishedAt`, `summer make:admin-controller acme.blog Posts`, `summer make:command acme.blog Publish`, `summer plugin:add plugins/blog`, `summer build`) and what each writes, a note that the in-repository copy has no go.mod because it lives in the framework module (show the go.mod a real plugin gets as a `text` fence), and a closing "Checklist" of what changed from WinterCMS. Describe the scaffolder oddities as they are (the generated admin controller names the model after the controller; migrations created in the same second are ordered by file name; generated files carry a generated-code header that `summer make` uses to rebuild the accessors) and log each one that is a real gap as a todo under `.planning/todos/pending/` in a separate planning commit, without changing internal/build.
3. Link the walkthrough from `docs/setup/coming-from-wintercms.md` (intro paragraph) and `docs/index.md`.
4. Run `go run ./cmd/summer docs:sync` and `go run ./cmd/summer docs:build --check`; fix every problem.
</action>
<verify>
<automated>go vet ./... && go test -short ./docs/examples/blog -run '^TestScaffoldLayout$' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestDocsRequiredPages|TestDocsAIOutputsInSync)$' -count=1</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.1.sh --docs && scripts/check-phase11.1.sh --forbidden</automated>
<fails_when>non-zero exit or a line starting "refuse:"</fails_when>
</verify>
<acceptance_criteria>
- `go test -short ./docs/examples/blog -run '^TestScaffoldLayout$' -count=1 -v` prints `--- PASS: TestScaffoldLayout`.
- `grep -c 'summer make:' docs/setup/porting-a-plugin.md` prints at least 5.
- `grep -n 'porting-a-plugin.md' docs/setup/coming-from-wintercms.md docs/index.md` finds a match in each file.
- `! grep -rniE 'fonoteka|p(l|ł)ytarium' docs` (no match).
- `git diff --name-only 9033d81 -- internal/build` prints nothing (scaffolder unchanged by this phase).
- `go run ./cmd/summer docs:build --check` exits 0.
</acceptance_criteria>
<done>The walkthrough's layout is pinned to the scaffolder by a test, the page lists the exact commands and the oddities are logged, and the concept map and index link it.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| walkthrough code → developers' plugins | The canonical example is copied into real plugins |
| walkthrough tests → Postgres | Docker tests create and drop databases |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-11.1-16 | Tampering / Elevation of privilege | docs/examples/blog routes, models, controllers | medium | mitigate | Writes through lagoon.Fill with an allow-list (TestPostTableAndFill), admin controller declares a permission (TestPostsControllerDeclaration), handler and command use bound parameters only |
| T-11.1-17 | Denial of service (test isolation) | docs/examples/blog/postgres_test.go | low | mitigate | testcontainers Postgres with a dedicated ICU database per run; never a developer or shared database |
| T-11.1-SC | Tampering | npm/pip/cargo/go installs | high | accept | This plan adds no module or package; testcontainers-go is already in go.mod |
</threat_model>
<verification>
- `go vet ./... && go test ./docs/examples/... -count=1` green with Docker; `go test -short ./docs/examples/...` green without.
- `go run ./cmd/summer docs:build --check` exits 0; `scripts/check-phase11.1.sh --docs --forbidden` pass.
</verification>
<success_criteria>
- SC5: the `acme/blog` porting walkthrough exists and its code (models, migrations, routes, admin controller, console command) is verified under criterion 4.
- SC4: every Go and YAML snippet on the walkthrough page is a src= copy of code that compiles and runs under `go test ./...`.
</success_criteria>
## Artifacts this phase produces
- Package tree `git.golem15.com/golem15/summercms/docs/examples/blog` with sub-packages `models`, `updates`, `controllers`, `console`, `classes`, `jobs`, `middleware`; symbols `blog.Plugin`, `models.Post`, `updates.CreatePosts`, `updates.AddPublishedAt`, `controllers.PostsController`, `console.PublishCommand`.
- Application command declared by the example: `blog:publish <slug>`; route `GET /api/blog/posts`; permission `acme.blog.access_posts` (or the name chosen, recorded in the SUMMARY).
- Tests: `TestPluginActivates`, `TestRoutesRegistered`, `TestPostTableAndFill`, `TestMigrationIDs`, `TestPostsControllerDeclaration`, `TestPublishCommandShape`, `TestMigrateUpAndRollback`, `TestPostsRouteAgainstDatabase`, `TestPublishCommandAgainstDatabase`, `TestScaffoldLayout`.
- Page `docs/setup/porting-a-plugin.md`; links from `docs/setup/coming-from-wintercms.md` and `docs/index.md`; any scaffolder gap todos under `.planning/todos/pending/`.
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md` when done
</output>

View File

@@ -0,0 +1,249 @@
---
phase: 11.1-summercms-documentation-for-humans-and-ai-agents
plan: 06
type: execute
wave: 6
depends_on: ["11.1-05"]
files_modified:
- internal/docsite/violations_test.go
- internal/docsite/testdata/clean/
- internal/docsite/testdata/violations/
- internal/docsite/load_test.go
- internal/docsite/render_test.go
- internal/docsite/emit_test.go
- internal/docsite/snippet_test.go
- internal/docsite/highlight_test.go
- internal/docsite/serve_test.go
- internal/docsite/checks_test.go
- internal/docsite/theme_test.go
- internal/docsite/docsite_test.go
- cmd/summer/docs_test.go
- cmd/summer/phase11_1_acceptance_test.go
- scripts/check-phase11.1.sh
- .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md
autonomous: true
requirements: [DOCS-01, DOCS-02, DOCS-03, DOCS-04, DOCS-05, DOCS-06, DOCS-07, DOCS-08]
assumption_delta_decision: no-change
user_setup: []
estimate:
tokens: 120000
raw_tokens: 120000
tasks: 3
confidence: low
must_haves:
truths:
- "Per D-14 and the CLAUDE.md lean rule, this sixth and last plan brings full unit test coverage for the phase's code: `go test -cover ./internal/docsite` reports at least 85.0% of statements."
- "Every problem rule the generator emits (frontmatter variants, section, readme, snippet variants including confinement, identifier, link, anchor, command, forbidden, go fence without src=, callout, heading, output guard) has a planted fixture that produces exactly that problem, and the clean fixture produces none (TestPlantedViolations)."
- "The forbidden-name fixture is generated at test time from split strings, so no committed test file or fixture contains a consuming-application name (D-11)."
- "TestPhase11_1Acceptance in cmd/summer has one subtest per ROADMAP success criterion (SC1 to SC5) that asserts it on the real tree, and all five pass."
- "scripts/check-phase11.1.sh --all runs --preconditions, --deps, --self-test (including TestPlantedViolations), --docs, --forbidden, --claude, --named and --go (full `go test ./...`, not -short) and prints `phase11.1 all passed`; --named fails when a named test is missing, skipped or matches zero tests."
- "11.1-VALIDATION.md has every per-task row filled with its command and a green status, `status: validated`, `nyquist_compliant: true` and `wave_0_complete: true`, and keeps the manual-only rows (search, dark mode) for /gsd-verify-work."
- "No production code changes in this plan except fixes for defects the new tests expose; each such fix lands with the failing-then-passing test in the same commit and is listed in the SUMMARY."
- statement: "Manual UAT of search and dark mode in `summer docs:serve` is run in /gsd-verify-work (VALIDATION manual-only rows)."
verification: backstop
prohibitions:
- requirement_id: DOCS-05
category: transparency
status: resolved
verification: judgment
resolution: "The gate detector requires named tests to PASS; skips, zero matches and 'no tests to run' fail the stage."
reason: "A docs gate that goes green because a checker test was skipped or filtered away certifies drift it never measured."
statement: "The phase gate must not count a skipped, filtered-out or zero-match test run as a pass."
artifacts:
- path: "internal/docsite/violations_test.go"
provides: "TestPlantedViolations over testdata fixtures"
contains: "TestPlantedViolations"
- path: "cmd/summer/phase11_1_acceptance_test.go"
provides: "TestPhase11_1Acceptance with SC1..SC5 subtests"
contains: "SC5"
- path: "scripts/check-phase11.1.sh"
provides: "final gate with --named and --all"
contains: "--named"
- path: ".planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md"
provides: "validated phase validation strategy"
contains: "nyquist_compliant: true"
key_links:
- from: "scripts/check-phase11.1.sh"
to: "internal/docsite/violations_test.go"
via: "--self-test and --named run TestPlantedViolations and require PASS"
pattern: "TestPlantedViolations"
- from: "cmd/summer/phase11_1_acceptance_test.go"
to: "internal/docsite"
via: "real-tree Check, Build and page tree assertions per success criterion"
pattern: "docsite\\.(Check|Build)"
---
<objective>
Unit tests last (D-14, CLAUDE.md lean rule 3): full coverage of `internal/docsite`, a planted-violation fixture for every checker rule, the final phase gate with named-test detection, one acceptance test per success criterion, and a validated 11.1-VALIDATION.md.
Purpose: make the docs gates fail closed. A checker regression that stops reporting a problem must turn a test red, and the gate must refuse a skipped or empty run.
Output: test files and fixtures under `internal/docsite`, `cmd/summer/phase11_1_acceptance_test.go`, the finished `scripts/check-phase11.1.sh`, and 11.1-VALIDATION.md.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.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-VALIDATION.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
@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-02-SUMMARY.md
@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-05-SUMMARY.md
@scripts/check-phase11.1.sh
@scripts/check-phase11.sh
@internal/docsite/docsite.go
Test conventions (repository): stdlib `testing` only, no testify in these packages; `t.TempDir()` fixtures; `t.Fatalf("x = %v, want %v")`. Stage only each task's files.
</context>
<tasks>
<task type="tracer">
<name>Task 1: Tracer: every checker rule has a planted fixture that must produce exactly its problem, and the gate runs them</name>
<files>internal/docsite/violations_test.go, internal/docsite/testdata/clean/, internal/docsite/testdata/violations/, scripts/check-phase11.1.sh</files>
<read_first>
- internal/docsite/*.go (every Problem rule and message format actually emitted; grep for `Rule:`)
- .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-UI-SPEC.md CLI output table (problem wording)
- scripts/check-phase11.1.sh (current modes and self-test)
- scripts/check-phase11.sh phase11_detect (go test -json detector shape)
</read_first>
<action>
1. `internal/docsite/testdata/clean/`: a small root that passes every check: `go.mod` (a throwaway module path), `docs/site.yaml` with two sections, `docs/index.md`, two pages per section with valid frontmatter, one Go fence `src=` into a fixture `modules/demo/example_test.go#ExampleHello` with `// Output:`, a region fence, a link with an anchor, a `summer docs:build` token, a callout, and `modules/demo/` with `demo.go` and a README using `demo.Hello`. Keep the fixture's Go files out of the root module build (put them under `testdata`, which the go tool ignores).
2. `internal/docsite/testdata/violations/<case>/`: one directory per case, each a copy of the clean fixture with one planted fault plus a `want.txt` holding the expected rule and message substring. Cases: frontmatter-unknown-field, frontmatter-missing-field, frontmatter-section-mismatch, frontmatter-duplicate-order, frontmatter-first-line, frontmatter-long-description, section-no-pages, readme-missing, snippet-drift, snippet-missing-file, snippet-missing-ident, snippet-missing-region, snippet-absolute-path, snippet-dotdot, snippet-dotfile, snippet-nested-module, snippet-example-no-output, snippet-unrun-region, go-fence-no-src, identifier-unknown, identifier-unknown-member, link-broken, anchor-missing, anchor-cross-page, command-unknown-summer, command-unknown-app, callout-unknown, heading-code, heading-link, heading-non-ascii. Symlink escape and the forbidden name are built at test time (a symlink in `t.TempDir()`, and the forbidden word assembled from split string literals so no committed file contains it).
3. `violations_test.go`: `TestPlantedViolations` copies each case into `t.TempDir()`, runs `Check` with a fixture `Commands` set, and asserts exactly one problem whose rule and message match `want.txt` (the rule proves it failed for its own reason, not another); `TestCleanFixture` asserts zero problems. Add output-guard cases (out equals root, out inside src, out containing src, unmarked non-empty out) asserting `Build` refuses and leaves a sentinel file untouched.
4. Gate: `--self-test` also runs `go test ./internal/docsite -run '^(TestPlantedViolations|TestCleanFixture)$' -count=1 -json` through a detector (Python, like `phase11_detect`) that fails on any FAIL, any SKIP, zero tests, or "no tests to run".
</action>
<verify>
<automated>go vet ./... && go test ./internal/docsite -run '^(TestPlantedViolations|TestCleanFixture)$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, or "no tests to run"</fails_when>
<automated>scripts/check-phase11.1.sh --self-test</automated>
<fails_when>non-zero exit, a line starting "refuse:", or no "phase11.1 self-test passed" line</fails_when>
</verify>
<acceptance_criteria>
- `ls -d internal/docsite/testdata/violations/*/ | wc -l` prints at least 30.
- `go test ./internal/docsite -run '^TestPlantedViolations$' -count=1 -v | grep -c -- '--- PASS: TestPlantedViolations/'` prints at least 30.
- `! grep -rniE 'fonoteka|p(l|ł)ytarium' internal/docsite/testdata internal/docsite/violations_test.go` (no match: the forbidden word is assembled at run time; the checker's own pattern in check_forbidden.go is the only source that names it).
- Deleting the body of the identifier checker's miss branch in a scratch copy makes `TestPlantedViolations/identifier-unknown` fail (checked once by hand during execution and recorded in the SUMMARY).
- `scripts/check-phase11.1.sh --self-test` prints `phase11.1 self-test passed`.
</acceptance_criteria>
<done>Each rule's failure path is pinned by a fixture that must fail for its own reason, the clean fixture passes, and the gate's self-test runs them fail-closed.</done>
</task>
<task type="auto">
<name>Task 2: internal/docsite reaches at least 85% statement coverage across load, render, emit, snippets, highlighting, checkers and serve</name>
<files>internal/docsite/load_test.go, internal/docsite/render_test.go, internal/docsite/emit_test.go, internal/docsite/snippet_test.go, internal/docsite/highlight_test.go, internal/docsite/serve_test.go, internal/docsite/checks_test.go, internal/docsite/theme_test.go, internal/docsite/docsite_test.go, cmd/summer/docs_test.go</files>
<read_first>
- internal/docsite/*.go and the existing *_test.go files (smoke tests from plans 11.1-01 and 11.1-02)
- `go test -coverprofile` output for internal/docsite (to target uncovered blocks)
- cmd/summer/docs.go (flag handling, problem printing, exit error)
</read_first>
<action>
Add branch-level tests (table-driven where natural):
- `load_test.go`: strict site.yaml (unknown key, missing sections, duplicate section names), frontmatter split edge cases (no closing delimiter, CRLF line endings, BOM), index page rules, `_`-prefixed and `examples/` exclusion, README ingestion (summary line after blank lines, missing H1).
- `render_test.go`: slug IDs (Unicode letters kept, emoji and punctuation dropped, `_` kept, duplicates `-1`/`-2`, empty heading), H1 strip, link rewriting for every link class (page .md with and without fragment, module README from guides and from READMEs, external, mailto), callout rendering for the three types, heading anchor markup, TOC threshold (0, 1, 2 headings).
- `emit_test.go`: llms.txt exact shape on a fixture (H1, blockquote, notes, Overview, one H2 per section, API reference), llms-full.txt block format and order, `.md` transform (frontmatter dropped, `# Title` and `> description`, fence info reduced to the language word, links to .md), search-index.json schema and the 300-character excerpt cap, base_url prefixing (empty, path prefix, absolute URL).
- `snippet_test.go`: whole-file, `#Ident` with doc comment, generic declarations, Example body dedent with Output, Go and YAML regions, unterminated and duplicate regions, trailing-newline normalisation, `Sync` idempotence and byte-for-byte preservation of surrounding text.
- `highlight_test.go`: each token class (`tok-kw`, `tok-key`, `tok-str`, `tok-com`, `tok-num`, `tok-prompt`) for go, yaml and sh samples; HTML escaping of `<`, `&` and quotes inside tokens; unknown language falls back to escaped plain text; no inline style attribute in output.
- `serve_test.go`: loopback acceptance for `127.0.0.1`, `::1`, `localhost`; refusal for `0.0.0.0`, `::`, a LAN IP and a non-local hostname; `--allow-remote` override; `Handler` 200, 404 with the 404 body, dot-file refused; rebuild keeps the last good directory when a build returns problems (inject the build function or use a fixture tree that you break between builds).
- `checks_test.go`: identifier index for generic methods, embedded fields, interface methods, sub-package keys and the ambiguous-key problem; the `go doc` fallback accepting a promoted member; command token forms (`$ summer x`, `summer x --flag`, `./bin/app x`, code spans); link checker on ingested READMEs.
- `cmd/summer/docs_test.go`: `docs:build --check` prints problems and `docs:build: N problems, nothing written` and returns an error for a scratch tree with a planted fault; `docs:sync` output lines; `docs:serve` refusal output.
Fix any defect these tests expose in the same commit as the test, and list each fix in the SUMMARY.
</action>
<verify>
<automated>go vet ./... && go test ./internal/docsite ./cmd/summer -count=1 && go test ./internal/docsite -count=1 -coverprofile="${TMPDIR:-/tmp}/docsite.cover" && go tool cover -func="${TMPDIR:-/tmp}/docsite.cover" | awk '/^total:/ { sub("%", "", $3); if ($3 + 0 < 85.0) { print "coverage " $3 "% below 85%"; exit 1 } }'</automated>
<fails_when>non-zero exit, a "FAIL" line, or a "coverage ... below 85%" line</fails_when>
</verify>
<acceptance_criteria>
- `go test -cover ./internal/docsite -count=1` prints `coverage:` with a value of at least 85.0%.
- `go test ./internal/docsite -run '^(TestSlugIDs|TestHighlight.*|TestServe.*|TestLLMS.*|TestSnippet.*)' -count=1 -v | grep -c -- '--- PASS'` prints at least 10.
- `go test ./cmd/summer -run '^TestDocs' -count=1` passes.
</acceptance_criteria>
<done>internal/docsite has branch-level tests for every stage with at least 85% statement coverage, and any defect found is fixed with its test.</done>
</task>
<task type="auto">
<name>Task 3: One acceptance test per success criterion, the final gate, and a validated VALIDATION.md</name>
<files>cmd/summer/phase11_1_acceptance_test.go, scripts/check-phase11.1.sh, .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md</files>
<read_first>
- .planning/ROADMAP.md Phase 11.1 success criteria 1-5
- .planning/REQUIREMENTS.md DOCS-01 to DOCS-08
- cmd/summer/docs_test.go (real-tree helpers to reuse)
- scripts/check-phase11.1.sh and scripts/check-phase11.sh (phase11_detect, run_named)
- .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md
- the six 11.1-0N-SUMMARY.md files (test names actually created)
</read_first>
<action>
1. `cmd/summer/phase11_1_acceptance_test.go`: `TestPhase11_1Acceptance` with subtests:
- `SC1`: site.yaml sections are exactly setup, architecture, plugins, backend, database, services, console, api; every page's frontmatter is valid (Check has no frontmatter or section problem); every `modules/<m>` with non-test Go files has an `api/<m>` page in the sidebar.
- `SC2`: a real-tree build into `t.TempDir()` contains every UI-SPEC Theme Parts marker on a guide page, `404.html`, the four assets and eight fonts; `internal/docsite` and `cmd/summer/docs.go` contain no `os/exec` call to `node` or `npm` (parse with go/ast and inspect exec.Command first arguments); `docs:serve` is registered.
- `SC3`: pages, `.html`, `.md`, llms.txt links and llms-full.txt Source lines are equal sets in reading order.
- `SC4`: every go fence under docs/ has src=, `Check` reports zero problems (snippets, identifiers, links, anchors, commands, forbidden), and every referenced `Example*` exists with an `// Output:` comment.
- `SC5`: `setup/coming-from-wintercms` and `setup/porting-a-plugin` exist, the walkthrough page references `docs/examples/blog` sources only through src=, and `go list ./docs/examples/...` includes the blog packages.
2. Gate: add `--named` (runs `go test -json` for the named tests of the phase across `./internal/docsite`, `./cmd/summer`, `./docs/examples/...` and the modules' `TestDocs*` and `Example*` tests, and requires each to PASS; FAIL, SKIP, zero tests and "no tests to run" refuse) and make `--all` run `--preconditions --deps --self-test --docs --forbidden --claude --named --go` and print `phase11.1 all passed`. Keep `--go` as full `go vet ./...` and `go test ./...` without `-short`.
3. `11.1-VALIDATION.md` (planning commit, separate from code): fill the per-task verification map with every task of plans 01 to 06 (task id, requirement, test type, the exact automated command, file exists, green status), set `status: validated`, `nyquist_compliant: true`, `wave_0_complete: true`, tick the sign-off list, and keep the two manual-only rows (search, dark mode) for /gsd-verify-work.
4. Run `scripts/check-phase11.1.sh --all` and fix any failure at its source.
</action>
<verify>
<automated>go vet ./... && go test ./cmd/summer -run '^TestPhase11_1Acceptance$' -count=1 -v</automated>
<fails_when>non-zero exit, a "--- FAIL" line, fewer than five "--- PASS: TestPhase11_1Acceptance/SC" lines, or "no tests to run"</fails_when>
<automated>bash -n scripts/check-phase11.1.sh && scripts/check-phase11.1.sh --self-test && scripts/check-phase11.1.sh --all</automated>
<fails_when>non-zero exit, a line starting "refuse:", or no "phase11.1 all passed" line</fails_when>
</verify>
<acceptance_criteria>
- `go test ./cmd/summer -run '^TestPhase11_1Acceptance$' -count=1 -v | grep -c -- '--- PASS: TestPhase11_1Acceptance/SC'` prints 5.
- `scripts/check-phase11.1.sh --all` prints `phase11.1 all passed`.
- `grep -n '^status: validated$' .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md` and `grep -n '^nyquist_compliant: true$' .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md` each find a match.
- `grep -c 'pending' .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md` counts only the manual-only rows (at most 2).
- Temporarily renaming `TestPlantedViolations` in a scratch copy makes `--named` refuse with a zero-tests or missing-test message (checked once during execution and recorded in the SUMMARY).
</acceptance_criteria>
<done>Each success criterion has a passing acceptance subtest, the final gate runs every stage fail-closed and passes, and VALIDATION.md is validated with only the manual UAT rows left for verify-work.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| gate and tests → phase sign-off | A green gate is the evidence that the docs match the code |
| test fixtures → published docs | Fixture content must never reach docs/ or the built site |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-11.1-18 | Repudiation (false green) | scripts/check-phase11.1.sh, internal/docsite checkers | high | mitigate | One planted fixture per rule that must fail for its own rule (TestPlantedViolations); go test -json detector refuses FAIL, SKIP, zero tests and "no tests to run"; --go runs without -short |
| T-11.1-19 | Information disclosure (policy) | internal/docsite/testdata | low | mitigate | Fixtures live under internal/docsite/testdata, outside docs/ and the build walker; the forbidden word is assembled at run time and never committed |
| T-11.1-SC | Tampering | npm/pip/cargo/go installs | high | accept | This plan adds no module or package |
</threat_model>
<verification>
- `scripts/check-phase11.1.sh --all` prints `phase11.1 all passed` (Docker available).
- `go test -cover ./internal/docsite` at least 85%.
- Manual UAT in /gsd-verify-work: `summer docs:serve`, search for a module and a command, toggle theme in all three modes and reload, check a guide and an API page at 1280px, 1024px and 375px, and with JS disabled.
</verification>
<success_criteria>
- All five ROADMAP success criteria asserted by TestPhase11_1Acceptance subtests SC1 to SC5.
- DOCS-01 to DOCS-08 each mapped to a green automated command in 11.1-VALIDATION.md.
- The phase gate is fail-closed and green.
</success_criteria>
## Artifacts this phase produces
- Tests: `TestPlantedViolations`, `TestCleanFixture` (internal/docsite/violations_test.go); branch tests in `load_test.go`, `render_test.go`, `emit_test.go`, `snippet_test.go`, `highlight_test.go`, `serve_test.go` and extended `checks_test.go`, `theme_test.go`, `docsite_test.go`; `TestPhase11_1Acceptance` with subtests `SC1` to `SC5` (cmd/summer/phase11_1_acceptance_test.go).
- Fixtures: `internal/docsite/testdata/clean/`, `internal/docsite/testdata/violations/<case>/` with `want.txt`.
- Gate modes: `--named` added; `--all` final; `--self-test` runs the fixture tests through the JSON detector.
- `11.1-VALIDATION.md` validated.
<output>
Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-06-SUMMARY.md` when done
</output>

View File

@@ -56,6 +56,7 @@ Not in this phase:
- **D-15:** Syntax highlighting uses `github.com/alecthomas/chroma/v2`, approved by the user at this checkpoint (overrides the research's stdlib-only recommendation). It is called from a small custom goldmark `NodeRenderer`; `yuin/goldmark-highlighting` stays rejected. This is the only new dependency; `go.mod` gains chroma/v2 and its transitive `dlclark/regexp2`, and the phase gate asserts no other module is added.
- **D-16:** The walkthrough code is an in-root package under `docs/examples/blog`, covered by root `go test ./...`, plus a scaffold-layout test.
- **D-17:** Raw pages sit beside their HTML pages (`/x/page.html` and `/x/page.md`). Config-key checking is deferred and noted in CLAUDE.md. The wristband default that names the consuming application is logged as a follow-up todo; its API does not change in this phase.
- **D-18:** (plan-check W1, 2026-09-30) The `src=` compile-and-run rule covers Go fences in `docs/` pages only. Go fences in the ingested module READMEs are rendered as written and covered by the identifier checker. DOCS-04 and ROADMAP SC4 are worded to match. Converting README fences to `src=` references is a follow-up todo (`.planning/todos/pending/readme-go-fences-src.md`).
</decisions>

View File

@@ -39,20 +39,28 @@ created: "2026-09-28"
## Per-Task Verification Map
Filled in by the planner once PLAN.md files exist. Requirement → test mapping from RESEARCH.md:
Filled in by the planner from the six PLAN.md files. Real-tree checks live in `cmd/summer` (package main owns `toolCommands()`, which the command checker needs); fixture and unit tests live in `internal/docsite`. Plan 11.1-06 Task 3 updates the Status column and the sign-off.
| Requirement | Behavior | Test Type | Automated Command | File Exists | Status |
|-------------|----------|-----------|-------------------|-------------|--------|
| DOCS-01 / SC1 | Strict frontmatter, section/dir match, unique order, H1 == title, every module in sidebar | unit | `go test ./internal/docsite -run 'TestContentTree\|TestEveryModuleInSidebar'` | ❌ W0 | ⬜ pending |
| DOCS-02 / SC2 | Build emits html with sidebar, TOC, prev/next, edit link, search, theme toggle; assets embedded; no Node | unit + smoke | `go test ./internal/docsite -run TestBuildSite && go test ./cmd/summer -run TestToolCommandNames` | ❌ W0 | ⬜ pending |
| DOCS-02 / SC2 | `go.mod` direct requires unchanged | gate | `scripts/check-phase11.1.sh --deps` | ❌ W0 | ⬜ pending |
| DOCS-03 / SC3 | nav pages == html == md == llms.txt links == llms-full sources, in sidebar order; llms.txt spec shape | unit | `go test ./internal/docsite -run TestAIOutputsInSync` | ❌ W0 | ⬜ pending |
| DOCS-04 / SC4 | Every go fence has `src=`, bodies equal sources, referenced Examples have Output, missing ref fails | unit | `go test ./internal/docsite -run TestSnippets` | ❌ W0 | ⬜ pending |
| DOCS-04 / SC4 | Examples compile and run with valid names | toolchain | `go vet ./... && go test ./modules/...` | ❌ W0 | ⬜ pending |
| DOCS-05 / SC4 | Identifier, link/anchor, CLI-name, forbidden-name checkers fail on planted violations and pass on the real tree | unit | `go test ./internal/docsite -run 'TestIdentifiers\|TestLinks\|TestCommands\|TestForbidden'` | ❌ W0 | ⬜ pending |
| DOCS-06 / SC5 | "Coming from WinterCMS" page exists and its identifiers pass | unit | covered by `TestContentTree` + `TestIdentifiers` | ❌ W0 | ⬜ pending |
| DOCS-07 / SC5 | `acme/blog` walkthrough compiles, registers, serves routes, migrates up/down, matches the scaffold layout | unit + integration | `go test ./docs/examples/...` | ❌ W0 | ⬜ pending |
| DOCS-08 | CLAUDE.md carries the D-13 docs-update rule | gate | `scripts/check-phase11.1.sh --claude` | ❌ W0 | ⬜ pending |
| Task ID | Plan | Requirement | Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|-------------|----------|-----------|-------------------|-------------|--------|
| 11.1-01-T1 | 01 | DOCS-01, DOCS-02, DOCS-03 | docs:build writes html, .md, llms.txt, llms-full.txt, search index; strict frontmatter; output guard | smoke (real tree) | `go test ./internal/docsite ./cmd/summer -run '^(TestDocsTree\|TestDocsBuildRealTree\|TestToolCommandNames)$' -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-01-T2 | 01 | DOCS-01, DOCS-03 | Every module is an API page in the sidebar; shared slug IDs; AI outputs match the page tree | unit + smoke | `go test ./internal/docsite ./cmd/summer -run '^(TestSlugIDs\|TestReadmeIngestion\|TestEveryModuleInSidebar\|TestDocsAIOutputsInSync\|TestDocsTree)$' -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-01-T3 | 01 | DOCS-04 | src= extraction, confinement, drift, docs:sync; ExampleCall runs | unit + toolchain | `go vet ./... && go test ./internal/docsite ./cmd/summer ./modules/bonfire -count=1` | ❌ W0 | ⬜ pending |
| 11.1-02-T1 | 02 | DOCS-05 | Identifier checker over docs, module READMEs and root README; gate self-test | unit + gate | `go test ./internal/docsite ./cmd/summer -run '^(TestIdentifierChecker\|TestDocsTree)$' -count=1 -v && scripts/check-phase11.1.sh --self-test` | ❌ W0 | ⬜ pending |
| 11.1-02-T2 | 02 | DOCS-05, DOCS-08 | Links/anchors, command names (collected sets), forbidden names, go-fence policy; CLAUDE.md rule | unit + gate | `go test ./internal/docsite ./cmd/summer -count=1 && scripts/check-phase11.1.sh --claude` | ❌ W0 | ⬜ pending |
| 11.1-02-T3 | 02 | DOCS-02 | UI-SPEC theme markers, chroma highlighting, search assets, docs:serve loopback and 404; go.mod adds only chroma/v2 and regexp2/v2 | unit + gate | `go test ./internal/docsite -run '^(TestBuildSiteMarkers\|TestServeHandler\|TestServeRefusesNonLoopback)$' -count=1 -v && scripts/check-phase11.1.sh --deps` | ❌ W0 | ⬜ pending |
| 11.1-03-T1 | 03 | DOCS-06 | Coming from WinterCMS page with checked identifiers and ExamplePlugin | toolchain + smoke | `go test ./modules/party -run '^ExamplePlugin$' -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree\|TestDocsRequiredPages)$' -count=1` | ❌ W0 | ⬜ pending |
| 11.1-03-T2 | 03 | DOCS-01, DOCS-04 | Architecture and Plugins pages with running Examples | toolchain + smoke | `go test ./modules/backpack ./modules/pact ./modules/festival ./modules/towel -run '^Example' -count=1 && go test ./cmd/summer -run '^(TestDocsTree\|TestDocsRequiredPages)$' -count=1` | ❌ W0 | ⬜ pending |
| 11.1-03-T3 | 03 | DOCS-01, DOCS-04 | Setup and Console pages; every command name verified | smoke + gate | `go test ./cmd/summer -run '^(TestDocsTree\|TestDocsRequiredPages)$' -count=1 && scripts/check-phase11.1.sh --docs` | ❌ W0 | ⬜ pending |
| 11.1-04-T1 | 04 | DOCS-04 | Jobs page with a runnable Example and a database-backed region run by TestDocsDispatch | integration (Docker) | `go test ./modules/conga -run '^(Example.*\|TestDocsDispatch)$' -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-04-T2 | 04 | DOCS-01, DOCS-04 | Database and core Services pages with running examples | toolchain + integration | `go test ./modules/lagoon/... ./modules/compass ./modules/surf ./modules/wire ./modules/bouncer ./modules/wristband ./modules/postcard ./modules/phrasebook -run '^(Example\|TestDocs)' -count=1` | ❌ W0 | ⬜ pending |
| 11.1-04-T3 | 04 | DOCS-01, DOCS-06 | Backend and remaining Services pages, Frontend and AJAX page, section order, concept-map links | smoke + gate | `go test ./cmd/summer -run '^(TestDocsTree\|TestDocsRequiredPages)$' -count=1 && scripts/check-phase11.1.sh --forbidden` | ❌ W0 | ⬜ pending |
| 11.1-05-T1 | 05 | DOCS-07 | Blog plugin activates, route registered; walkthrough page from src= | unit + smoke | `go test -short ./docs/examples/... -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-05-T2 | 05 | DOCS-07 | Admin controller, command, second migration proven against Postgres | integration (Docker) | `go test ./docs/examples/... -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-05-T3 | 05 | DOCS-07 | Walkthrough file set equals scaffolder output | unit | `go test -short ./docs/examples/blog -run '^TestScaffoldLayout$' -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-06-T1 | 06 | DOCS-05 | Every rule has a planted fixture that fails for its own rule | unit | `go test ./internal/docsite -run '^(TestPlantedViolations\|TestCleanFixture)$' -count=1 -v` | ❌ W0 | ⬜ pending |
| 11.1-06-T2 | 06 | DOCS-01..05 | internal/docsite statement coverage at least 85% | unit | `go test ./internal/docsite -count=1 -coverprofile=cover.out && go tool cover -func=cover.out` | ❌ W0 | ⬜ pending |
| 11.1-06-T3 | 06 | DOCS-01..08 | SC1..SC5 acceptance subtests; final gate | acceptance + gate | `go test ./cmd/summer -run '^TestPhase11_1Acceptance$' -count=1 -v && scripts/check-phase11.1.sh --all` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

View File

@@ -0,0 +1,3 @@
# Phase 11.1 API coverage
No external API integration: phase builds a static docs generator and docs content; chroma and goldmark are local libraries, not services.

View File

@@ -0,0 +1,8 @@
---
title: Convert module README Go code blocks to compiled src= references
date: 2026-09-30
priority: medium
area: summercms.go/modules/*/README.md, internal/docsite
---
Phase 11.1 (D-18) publishes every module README as an `api/<name>` docs page, but its Go code blocks (about 41 across 22 READMEs) are only identifier-checked, not compiled. Move each to an `Example*` or region in the module's `example_test.go` and reference it with `src=`, then drop the README exemption in `internal/docsite` `check_policy.go` so ROADMAP 11.1 SC4 can be widened back to "every Go example in the docs".