docs(11.1): research documentation phase domain

This commit is contained in:
Jakub Zych
2026-09-28 18:20:42 +02:00
parent b2845e016b
commit 09c1ade9a9

View File

@@ -0,0 +1,789 @@
# Phase 11.1: SummerCMS documentation for humans and AI agents - Research
**Researched:** 2026-09-28
**Domain:** Static documentation generator in Go (goldmark + html/template), docs-as-code verification (compiled snippets, identifier/link checkers), llms.txt publishing
**Confidence:** HIGH for the in-repo mechanics (goldmark API, go/ast checker, vet behaviour, CLI naming: all probed this session). MEDIUM for the WinterCMS docs anatomy and the llms.txt convention (official pages fetched, but the seam rates web sources LOW). LOW where tagged `[ASSUMED]`.
## Summary
This phase adds no runtime framework behaviour. It adds a docs pipeline and content. The main risk is drift, not the technology: docs that name APIs that do not exist, snippets that no longer compile, and links that break. The design below makes every one of those a `go test ./...` failure, and it needs **zero new dependencies**. `github.com/yuin/goldmark v1.8.6` is already a direct requirement in `go.mod` (postcard uses it for mail). The frontmatter parser is the in-tree `goccy/go-yaml`. Go syntax highlighting can use stdlib `go/scanner`. The identifier checker uses stdlib `go/parser`/`go/ast`. A prototype run this session checked all 742 package-qualified identifier spans in the 18 module READMEs and the root README with no misses.
The key decision (D-07) is **include by reference, embedmd style**. Each Go fence in the Markdown carries a source reference in its info string (```` ```go src=modules/bonfire/example_test.go#ExampleNewRoot ````). The fence body is a verbatim copy of that source region, so the file reads well on the git host (D-01). A test fails when the copy drifts from the source, and `summer docs:sync` rewrites the copies. The referenced sources are real Go: `Example*` functions with `// Output:` in the module packages, which `go test` runs and whose names `go vet` checks against real identifiers (probed), plus the `acme/blog` walkthrough plugin as real packages under `docs/examples/blog`. The alternative, extracting fenced snippets and compiling them, was rejected. It needs wrapper templates, it cannot be refactored with gopls, and it reports errors against generated files.
For D-08 the recommendation is **ingest, don't duplicate**. The generator publishes every `modules/*/README.md` as a page in the "API reference" section, with a synthesized title and description and with links rewritten. The READMEs stay the single per-package reference, so they cannot drift from a second copy, and the identifier checker covers them too. That automates the manual `go doc` rule in CLAUDE.md.
**Primary recommendation:** Build `internal/docsite` (a stdlib + goldmark generator, embedded theme) behind `summer docs:build`, `summer docs:serve` and `summer docs:sync`. Keep every accuracy check (snippet sync, identifiers, links and anchors, CLI names, forbidden app names, page-tree/llms sync) as a plain Go test that runs over the real `docs/` tree, so `go test ./...` is the gate.
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
#### Source and build
- **D-01:** The source is Markdown with YAML frontmatter (title, description, section, order) under `summercms.go/docs/`. The files read well on the git host without the site build.
- **D-02:** A small Go generator renders the site: stdlib `html/template` plus `github.com/yuin/goldmark` for Markdown. The user approved goldmark in this discussion, which satisfies the CLAUDE.md dependency rule. Any further dependency (syntax highlighting such as chroma, a frontmatter parser beyond goccy/go-yaml) must be named in RESEARCH.md and confirmed at the plan-count checkpoint.
- **D-03:** The generator is exposed through the `summer` CLI (preferred `summer docs:build` and `summer docs:serve`, final names subject to research against existing bonfire command naming). It produces a self-contained static output directory. No Node or npm toolchain.
- **D-04:** The site layout follows wintercms.com/docs: a left sidebar grouped by section, an on-page table of contents, prev/next links, "edit this page" source links, client-side search over a generated JSON index (vanilla JS, no framework) and a dark mode. Research should record what the Winter docs actually offer and flag anything left out.
#### AI-friendly outputs
- **D-05:** The build emits `llms.txt` (an index following the llms.txt convention) and `llms-full.txt` (every page concatenated in sidebar order) at the site root.
- **D-06:** Every rendered page is also published as clean Markdown at a predictable URL (page URL plus `.md`), without site chrome.
- **D-07:** Code examples are verified. Go snippets shown in the docs are compiled and run as part of `go test ./...`, either as `Example` functions included into pages by reference or as snippet files extracted and built by a test. Research picks one mechanism. A doc build fails if a referenced snippet is missing.
#### Structure and content
- **D-08:** The sections mirror WinterCMS: Getting started/Setup, Architecture, Plugins, Backend (admin auth, forms, lists, relation manager, settings), Database (models, migrations, relations, casts, validation), Services (events, config, mail, i18n, jobs, realtime, search, storage, rate limiting, HTTP routing and auth groups), Console (CLI commands and scaffolding), and API reference. Module READMEs remain the per-package reference. The docs link to them or derive from them rather than duplicating them. Research decides which.
- **D-09:** A "Coming from WinterCMS" page maps Winter concepts to SummerCMS equivalents (for example Plugin.php to the Plugin interface, `fields.yaml` to the schema pipeline, Eloquent to GORM plus lagoon, and so on).
- **D-10:** A porting walkthrough takes a neutral sample WinterCMS plugin (`acme/blog`) through to a compiled SummerCMS plugin: models, migrations, routes, admin controller and a scaffolding command. Its code is verified under D-07.
#### Accuracy rules (inherited from CLAUDE.md, applied to docs/)
- **D-11:** The docs never name a consuming application. Use "the application" or "host application" and neutral names such as `blog` or `acme`.
- **D-12:** Every identifier named in the docs must exist in the package. A checker verifies it (as `go doc ./modules/<name> <Identifier>` does for READMEs) and runs in the test suite. Internal links and anchors are checked too.
- **D-13:** From this phase on, a change to a module's exported API, config keys or CLI commands must update both the module README and the affected docs pages. Record this rule in CLAUDE.md's Documentation section as part of the phase.
### Claude's Discretion
- Exact page list within each section, URL scheme, theme styling (reuse the admin SPA's design tokens if convenient), search index format, and the generator's package location (for example `modules/<beach-name>` or `internal/docs`, following the existing naming convention).
### Deferred Ideas (OUT OF SCOPE)
- An agent skill or `AGENTS.md`/`CLAUDE.md` template for host applications. It was offered and not selected for this phase.
- Hosting and CI deployment of the site.
- Versioned docs (per framework release).
</user_constraints>
<phase_requirements>
## Phase Requirements (proposed IDs; none were assigned)
The roadmap lists `Requirements: TBD`. Proposed IDs, for adding to REQUIREMENTS.md in a new "Documentation (DOCS)" group and to the traceability table as Phase 11.1:
| ID | Description | Success criterion | Research support |
|----|-------------|-------------------|------------------|
| DOCS-01 | `docs/` holds Markdown pages with strict YAML frontmatter (title, description, section, order), grouped into the Winter-mirroring sections. Every framework module under `modules/` is reachable from the sidebar through an API reference page ingested from its README. | SC1 | §Page tree, §README strategy (D-08), `TestContentTree` |
| 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). `summer docs:serve` previews it on loopback. No Node toolchain, and no new dependency beyond goldmark unless approved. | SC2 | §Generator, §Theme, §Search |
| DOCS-03 | The build emits `llms.txt`, `llms-full.txt` and a clean `.md` for every page, and a test asserts all three match the page tree. | SC3 | §AI outputs, `TestAIOutputsInSync` |
| DOCS-04 | Every Go fence in the docs references compiled source by `src=`. A test fails on a missing or drifted snippet. Referenced Examples carry `// Output:` and run under `go test ./...`. | SC4 | §Verified examples |
| DOCS-05 | `go test ./...` runs these checkers and they fail on stale names or broken links: identifiers (docs pages **and** module READMEs), internal links and anchors, `summer`/app CLI command names, and forbidden consuming-application names. | SC4 (+D-11, D-12) | §Checkers |
| DOCS-06 | A "Coming from WinterCMS" page maps Winter concepts to SummerCMS equivalents. Every SummerCMS identifier on it is checker-verified. | SC5 | §Concept map draft |
| DOCS-07 | An `acme/blog` porting walkthrough covers models, migrations, routes, an admin controller and a console command. Its code is real packages under `docs/examples/blog`, verified by DOCS-04. | SC5 | §Walkthrough |
| DOCS-08 | CLAUDE.md's Documentation section records the D-13 rule (API, config or CLI changes update the README and the affected docs pages) and names the automated checkers. | D-13 | §Hygiene gate |
</phase_requirements>
## Project Constraints (from CLAUDE.md)
- Stdlib first (net/http, html/template, encoding/json). Add a dependency only when research or a phase decision names it. **This research names none** (goldmark is already approved and in `go.mod`).
- `go vet ./...` and `go test ./...` stay green at every commit.
- Lean planning: few, large plans. Checkpoint the plan count before writing PLAN.md files. **Unit tests are the last plan.**
- No runtime plugin loading. (Not relevant here. The docs generator is tool code, not a plugin.)
- Commits: never add co-author tags, one logical change per commit, planning docs and code in separate commits.
- Documentation rules: README updates in the same change as API, config, CLI or dependency changes. Standard README structure, with a root-table row for a new module. READMEs never name a consuming application. Every README identifier must exist (`go doc` check).
- Two repos: this phase writes to **summercms.go only**. Framework docs never name the consuming application (D-11).
- GSD workflow enforcement: edits happen inside GSD commands.
## Architectural Responsibility Map
| Capability | Primary tier | Secondary tier | Rationale |
|------------|--------------|----------------|-----------|
| Markdown source, frontmatter, snippet copies | Repo content (`docs/`) | — | D-01: the source of truth, readable on the git host |
| Parse, render, TOC, heading IDs, link rewriting | Build tool (`internal/docsite`, invoked by the `summer` CLI) | — | A tool-time concern like `internal/build`. Not a runtime framework API |
| Site chrome (sidebar, TOC, prev/next, theme toggle) | Static HTML/CSS (html/template output) | Browser JS (theme toggle, mobile nav) | Works with JS disabled, except search and the toggle |
| Search | Browser (vanilla JS over a JSON index) | Build tool (index generation) | D-04: client-side, no server |
| llms.txt, llms-full.txt, per-page .md | Build tool (static files) | — | D-05, D-06: plain files at predictable URLs |
| Snippet, identifier, link and CLI verification | Test suite (`go test ./...`) | Build tool (`docs:build` fails on the same errors) | SC4: tests are the gate. The build refuses broken input too |
| Verified example code | Module packages (`example_test.go`) and `docs/examples/blog` | — | Real compiled Go that go vet and go test cover |
| Preview server | Build tool (`docs:serve`, loopback only) | — | Local dev only. Hosting is deferred |
## Standard Stack
### Core (all already in `go.mod`, no approval needed)
| Library | Version | Purpose | Why standard |
|---------|---------|---------|--------------|
| `github.com/yuin/goldmark` | v1.8.6 (proxy: published 2026-09-03) | CommonMark + GFM parse/render, AST for TOC, links, code spans and fences | Approved in D-02. Already a direct dependency: `go.mod:26` reads `github.com/yuin/goldmark v1.8.6` [VERIFIED: go.mod:26]. Already imported by postcard: `"github.com/yuin/goldmark"` / `markdown = goldmark.New()` [VERIFIED: modules/postcard/templates.go:14,29]. Its go.mod has **no requirements** [VERIFIED: proxy.golang.org goldmark v1.8.6 .mod] |
| `github.com/goccy/go-yaml` | v1.19.2 | Strict frontmatter and `docs/site.yaml` decoding | `go.mod:13` reads `github.com/goccy/go-yaml v1.19.2` [VERIFIED: go.mod:13]. `DisallowUnknownField()` exists [VERIFIED: go-yaml option.go:65] and is already the tide idiom (`yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField())`, modules/tide/manifest.go:96) |
| stdlib `html/template`, `embed`, `net/http`, `encoding/json` | Go 1.27 | Page chrome, embedded theme, `docs:serve`, search index | Project rule |
| stdlib `go/parser`, `go/ast`, `go/token`, `go/doc`, `go/scanner`, `go/format` | Go 1.27 | Snippet region extraction, identifier index, Go highlighting | Stdlib. The prototype parsed all 18 modules, including Go 1.27 generic methods (`func (b *Bus) Fire[T any](...)`) |
| `github.com/fsnotify/fsnotify` | v1.10.1 | `docs:serve` rebuild on change (optional) | Already direct (`go.mod:10`, used by `internal/dev`) [VERIFIED: go.mod:10] |
### goldmark features to enable (all in core goldmark)
- `extension.GFM`, which is exactly `Linkify`, `Table`, `Strikethrough` and `TaskList` [VERIFIED: goldmark extension/gfm.go:11-18].
- `parser.WithAutoHeadingID()` [VERIFIED: parser/atx_heading.go:45-49], used with a **custom `parser.IDs`** passed through `parser.WithIDs(ids)` on the parse context [VERIFIED: parser/parser.go:81-87, 240-244]. The reason is under Pitfall 3.
- `parser.WithASTTransformers(...)` [VERIFIED: parser/parser.go:721] for link rewriting, callout (`> [!NOTE]`) detection and H1 stripping.
- Keep the html renderer **without** `html.WithUnsafe()` [VERIFIED: renderer/html/html.go:236]. Raw HTML in docs is not needed and stays escaped.
- `ast.FencedCodeBlock.Language(source)` returns the info string up to the first space [VERIFIED: goldmark ast/block.go:309-321]. So ```` ```go src=... ```` has language `go`, and the rest of the info string is available through `n.Info`.
### Alternatives considered (each needs explicit user approval; none recommended)
| Instead of | Could use | Tradeoff / verdict |
|------------|-----------|--------------------|
| Stdlib `go/scanner` highlighter for `go` fences (plus a small line-regex highlighter for `yaml`/`sh`) | `github.com/alecthomas/chroma/v2` v2.27.0 (2026-06-17; deps `dlclark/regexp2/v2`) called from a small custom goldmark `NodeRenderer` | Chroma highlights every language and ships themes. It costs 2 new modules and a lot of lexer code. Docs are almost entirely Go, YAML and shell. **Not recommended.** Ask at the checkpoint only if the user wants multi-language highlighting |
| — | `github.com/yuin/goldmark-highlighting/v2` | **Reject.** It has never been tagged: the only version is pseudo-version `v2.0.0-20230729083705-37449abec8cc`, and its go.mod pins `chroma/v2 v2.2.0` from 2023 [VERIFIED: proxy.golang.org] |
| Manual `---` split + goccy/go-yaml | `github.com/yuin/goldmark-meta` v1.1.0 (2022) | **Reject.** Its go.mod requires `gopkg.in/yaml.v2 v2.3.0` [VERIFIED: proxy.golang.org .mod], an unmaintained YAML line the stack research already warns against |
| AST walk for the TOC (~30 lines) | `go.abhg.dev/goldmark/toc` v0.12.0 | Not needed. The headings already carry IDs in the AST |
| Stdlib `go/ast` identifier index | `golang.org/x/tools/go/packages` v0.50.0 | Adds a heavy module for type-checked loading the checker does not need (0/742 misses with plain AST). Not needed |
| JSON search index + vanilla JS | lunr/pagefind/Algolia | Violates "no Node" or adds a service. Out |
**Installation:** none. `go.mod` does not change in this phase. The gate should assert that (see the hygiene gate).
## Package Legitimacy Audit
`gsd-tools package-legitimacy check` supports only npm, pypi and crates, so it rejected `--ecosystem go`. Verification was done against proxy.golang.org directly.
| Package | Registry | Age | Source repo | Verdict | Disposition |
|---------|----------|-----|-------------|---------|-------------|
| github.com/yuin/goldmark v1.8.6 | proxy.golang.org | tagged 2026-09-03, project since 2019 | github.com/yuin/goldmark | OK (already a direct dependency, approved in D-02) | Approved, no change |
| github.com/goccy/go-yaml v1.19.2 | proxy.golang.org | already direct | github.com/goccy/go-yaml | OK (already in tree) | Approved, no change |
| github.com/alecthomas/chroma/v2 v2.27.0 | proxy.golang.org | tagged 2026-06-17 | github.com/alecthomas/chroma | OK on registry. Only an *alternative* | Not recommended. User approval required if chosen |
| github.com/yuin/goldmark-highlighting/v2 | proxy.golang.org | untagged pseudo-version (2023) | github.com/yuin/goldmark-highlighting | SUS (never tagged, stale pins) | REMOVED from recommendations |
| github.com/yuin/goldmark-meta v1.1.0 | proxy.golang.org | 2022 | github.com/yuin/goldmark-meta | OK on registry, but pulls `gopkg.in/yaml.v2` | REMOVED from recommendations |
**Packages removed:** goldmark-highlighting/v2, goldmark-meta. **Packages flagged SUS:** goldmark-highlighting/v2 (removed anyway).
## Research question answers
### Q1. wintercms.com/docs: tree, anatomy, tone, mapping
**Sidebar (v1.2)** [CITED: wintercms.com/docs/v1.2/docs/setup/installation, …/database/model]:
- **Getting Started:** Installation, Configuration, Upgrade Guide
- **Architecture Concepts:** Introduction, Using Composer, Developer Guide, Maintainer Guide
- **Backend:** Controllers & AJAX, Views & Partials, Widgets, Forms, Lists, Relations, Sorting records, Importing & Exporting, Users & Permissions, User Interface Guide
- **Frontend:** Themes, Developing Themes, Pages, Partials, Layouts, Content Blocks, Components, Media Manager, Markup Guide, Twig Docs
- **Plugins:** Registration, Version History, Building Components, Settings & Config, Localization, Task Scheduling, Extending Plugins, Replacement & Forking, Unit Testing
- **AJAX Framework:** Introduction, Event Handlers, Updating Partials, Data Attributes API, JavaScript API, Extra Features
- **Snowboard:** Introduction, Migration Guide, Serverside Event Handlers, AJAX Requests (JS API), AJAX Requests (Data Attributes API), Extra Features, Utilities, Plugin Development
- **Database:** Getting Started, Structure, Queries, Models, Relationships, File Attachments, Collections, Mutators, Serialization, Traits, Behaviors
- **Services:** Application, Asset Compilation, Behaviors, Cache, Collections, Errors & Logging, Filesystem / CDN, Forms & Html, Hashing & Encryption, Helpers, Image Resizing, Mail, Pagination, Parser, Queues, Router, Request & Input, Response & View, Session, Validation
- **Console:** Introduction, Setup & Maintenance, Plugin Management, Theme Management, Asset Compilation (+Mix, +Vite, +Node Utilities), Scaffolding, Utilities
- **Events:** Introduction, Frontend Events Timeline, Available Events
- **Further Reading:** Laravel Docs, PHP Docs (external)
**Page anatomy** [CITED: wintercms.com/docs/v1.2/docs/setup/installation]:
- A header with the logo and a **version selector** (v1.2 / Develop).
- **Category tabs:** Docs, API, Markup, UI.
- A **"Search docs ⌘K"** box.
- An **"On this page"** TOC.
- A **"Next page → Section: Title"** link at the bottom.
- An **"Edit on GitHub"** button.
- A **light/dark/system** theme toggle.
- A copyright footer.
The source Markdown uses `> **NOTE:**` callouts, fenced code with language tags, ASCII directory trees and two-column Key/Description tables. Tone: second person, present tense, imperative ("Define the `$elevated` property") [CITED: raw.githubusercontent.com/wintercms/docs/develop/plugin/registration.md].
**What this phase includes and leaves out (D-04 flag):**
| Winter feature | SummerCMS docs | Note |
|----------------|----------------|------|
| Sidebar grouped by section | Include | Sections from D-08 |
| "On this page" TOC | Include | H2 and H3 |
| Next/prev page | Include | Sidebar order, crossing section boundaries |
| Edit on GitHub | Include as "Edit this page" | The forge is **Gitea** (git.golem15.com serves Gitea markup, probed). Configure the URL as a template in `docs/site.yaml` |
| Search ⌘K | Include | ⌘K / Ctrl+K and `/`, JSON index |
| Light/dark/system toggle | Include | Tri-state, persisted in `localStorage`. Note that the admin SPA follows the system only (theme.ts:1-2 reads "the admin follows the system preference only, with no toggle"), so the docs toggle is new behaviour |
| Version selector | **Leave out** | Versioned docs are deferred |
| Docs/API/Markup/UI tabs | **Leave out** | Replaced by one tree with an "API reference" section |
| og-description div | Replaced | By the frontmatter `description`, rendered as `<meta name="description">` and OG tags |
| (new) "View as Markdown" link per page | Add | Points at the page `.md`. Cheap and AI-friendly |
| (new) Copy button on code blocks | Optional | UI-SPEC decides |
**Winter section → SummerCMS page → module(s):**
| Winter section / page | SummerCMS | Module(s) | Disposition |
|-----------------------|-----------|-----------|-------------|
| Getting Started: Installation, Configuration | Setup: Introduction, Installation, Configuration | compass, lagoon (DB locale), cmd/summer | Port |
| Getting Started: Upgrade Guide | — | — | **Omit** (no releases, versioned docs deferred) |
| — | Setup: Coming from WinterCMS, Porting a plugin (walkthrough) | all | **New** (D-09, D-10) |
| Architecture: Introduction | Architecture: Introduction (single binary, compiled plugins, headless) | party, backpack | Port |
| Architecture: Using Composer | Architecture: Go modules and workspaces (`go.work`, `replace`, `summer.yaml`) | internal/build | Differs |
| — | Architecture: Application lifecycle (Register/Boot, container, services), Request lifecycle (surf pipeline, towel context) | party, backpack, surf, towel | New |
| Architecture: Developer/Maintainer Guide | — | — | **Omit** (contributor process lives in CLAUDE.md) |
| Plugins: Registration | Plugins: Registration | pact, party | Port |
| Plugins: Version History | Plugins: Migrations and versions (gormigrate, per-plugin history) | lagoon | Differs |
| Plugins: Settings & Config | Plugins: Configuration and settings | compass, pact (`pact.SettingsItem`), cabana | Port |
| Plugins: Localization | Plugins: Localization | phrasebook | Port |
| Plugins: Task Scheduling | Plugins: Task scheduling | Phase 11 scheduler, `HasSchedule` | Port after Phase 11 |
| Plugins: Extending Plugins, Replacement & Forking | Plugins: Extending plugins (events, GORM callbacks, companion structs, optional plugins, services) | festival, lagoon, backpack | Port. Fold forking into it (`replace` directive) |
| Plugins: Building Components | — | — | **"Differs" note** on the Frontend page (headless: components become HTTP handlers) |
| Plugins: Unit Testing | Plugins: Testing (go test, testcontainers, parity replay) | tide | Port |
| Backend: Controllers & AJAX | Backend: Admin controllers (JSON admin API, hooks) | cabana, pact | Differs (no AJAX) |
| Backend: Forms / Lists / Relations | Backend: Forms, Lists and filters, Relation manager | cabana | Port |
| Backend: Users & Permissions | Backend: Admin users and permissions | cabana, bouncer, pact | Port |
| Backend: Views & Partials, Widgets | Backend: Partials, widgets and assets | cabana/boardwalk after **Phase 10.1** | Port after 10.1 |
| Backend: UI Guide | Backend: The admin SPA | boardwalk | Differs (compiled SPA) |
| Backend: Sorting records, Import/Export | — | — | **Omit.** One line in "Coming from WinterCMS": not provided (FW-05 deferred) |
| Frontend (all 10 pages), AJAX Framework, Snowboard | **One page:** "Frontend and AJAX (not provided)" | — | **"Not supported / differs" page**, not omission. Winter users will look for it. It explains headless + JSON API + realtime |
| Database: Getting Started, Structure, Queries | Database: Connection, Migrations, Queries and pagination | lagoon | Port |
| Database: Models, Relationships, File Attachments | Database: Models, Relations, File attachments | lagoon, lagoon/attach | Port |
| Database: Mutators, Serialization, Traits | Database: Casts, Mass assignment and serialization, Validation | lagoon, wire | Port |
| Database: Collections, Behaviors | — | — | **Omit** (Go slices and composition). One line in the concept map |
| Services: Application, Router, Request & Input, Response & View, Validation, Mail, Pagination, Hashing & Encryption, Filesystem/CDN, Queues | Services: Container and services, Routing and auth groups, Rate limiting, HTTP responses, Mail, Hashing and encryption, File storage, Queues and jobs | backpack, surf, bouncer, wire, postcard, lagoon, Phase 11 jobs | Port |
| Events (own section) | Services: Events | festival | Port (D-08 puts events in Services) |
| — | Services: Authentication, OAuth server, Outbound HTTP, Realtime, Search, Localization in requests, API parity testing | bouncer, wristband, fetchguard, Phase 11, towel, tide | New |
| Services: Cache, Session, Helpers, Parser, Forms & Html, Asset Compilation, Image Resizing, Collections, Behaviors, Errors & Logging | — | — | **Omit.** List them in the concept map as "not provided" or "use the Go stdlib". Image resizing lives inside attachments (lagoon/attach thumbnails) |
| Console: Introduction, Setup & Maintenance, Plugin Management, Scaffolding, Utilities | Console: Introduction (tool vs app binary), Setup and maintenance, Plugin management, Scaffolding, Writing commands, Utilities | bonfire, internal/build, lagoon, cabana, surf, tide | Port |
| Console: Theme Management, Asset Compilation (all) | — | — | **Omit** |
| API docs tab | API reference: one page per module README | all modules | Ingested (D-08) |
### Q2. Inventory and the D-08 README decision
**What exists** (probed this session):
- 18 modules, each with a README (2,108 lines in total, root README 151).
- READMEs follow the fixed template, and their identifier convention is backticked `` `pkg.Ident` `` / `` `pkg.Type.Member` ``.
- **Zero** `Example*` functions exist in any module (`grep '^func Example'` found none).
- Exported surface (`go doc -short` top-level lines): cabana 113, tide 51, pact 44, lagoon 37, bouncer 32, surf 26, wristband 20, postcard 18, bonfire 10, fetchguard 10, towel 8, phrasebook 6, wire 5, backpack 4, boardwalk 4, compass 4, festival 4, party 3.
- `examples/hello` is a separate workspace module with three plugins (base, greeter, optional).
- `cmd/summer` holds the tool commands. `internal/build` holds the scaffolder. `admin/` holds the Vue SPA.
**Thin spots:**
- Usage sections are 15–70 lines, one snippet each. There is no conceptual, cross-module narrative: how a request flows through surf, towel and a handler, or how config reaches a plugin.
- boardwalk (15 usage lines) and fetchguard (23) are the thinnest.
- The known quickstart gaps (missing `http.body_limits`, the ICU `pl-PL` locale requirement, a stale generated `main.go`) are documented only as root README "Known issues".
- **Documentation gap to log:** `wristband.DefaultOptions()` hard-codes a consuming-application URL. modules/wristband/server.go:100 reads `Resource: "https://mcp.plytarium.com/mcp",` and the comment at server.go:63-64 reads `config('fonoteka.mcp.resource'), D-03). PHP default:` / `"https://mcp.plytarium.com/mcp".` [VERIFIED: modules/wristband/server.go:61-65, 100]. Docs pages must not quote that default (D-11). Per scope, log the gap and do not change the API in this phase. Several wristband comments (stores.go:16-17, 155, 173-174; client_issue.go:2) also name the application. They are not in READMEs, and a src= snippet that pulls them in would trip the forbidden-name test.
**D-08 decision: ingest READMEs into the site as the API reference (derive, don't duplicate).**
| Option | Drift | Agent usefulness | Verdict |
|--------|-------|------------------|---------|
| Link out to READMEs on Gitea | none | Poor. llms-full.txt misses the API reference and the site links leave the site | Reject |
| **Ingest** `modules/*/README.md` as `api/<module>` pages | **none** (one source) | Good. The API reference appears in llms-full.txt, in search and under the identifier checker | **Recommend** |
| Supersede (move content to docs/, shrink READMEs) | none | Good | Reject. It violates D-08 ("READMEs remain the per-package reference") and the CLAUDE.md README structure rule |
Ingestion rules:
- title = the README H1 (the module name), description = the summary sentence (the line after the H1), section = `api`, order = alphabetical.
- The H1 is stripped (the template renders the title).
- `../x/README.md` links become `/api/x.html` (and `.md` in the raw output).
- **Guides link to modules by the README's repo-relative path.** For example, `docs/services/mail.md` links `../../modules/postcard/README.md`, which works on Gitea and is rewritten to `/api/postcard.html` on the site.
- The module list is discovered dynamically. Any `modules/<name>/` holding non-test `.go` files must have a README, or the content test fails. So Phase 11's new modules (jobs, realtime, search) appear without code changes (SC1: "every framework module is reachable").
### Q3. llms.txt, llms-full.txt, per-page .md
**llms.txt format** [CITED: llmstxt.org]:
- An optional BOM.
- An **H1 with the project name**, the only required part.
- A blockquote summary.
- Zero or more non-heading Markdown sections.
- Zero or more **H2 "file list" sections** of `- [name](url)` items, optionally followed by `: notes`.
- An H2 named `Optional` for secondary links.
Pages should offer clean Markdown "at the same URL as the original page, either with `.md` appended (`page.html.md`) or with the extension replaced by `.md` (`page.md`)". URLs without file names append `index.html.md` or `index.md`.
**llms-full.txt** is not in the llmstxt.org spec (the page has no mention of it) [CITED: llmstxt.org]. The de facto format, from Mintlify, repeats per page: `# Title`, then `Source: <url>`, a blank line, the description, then the full Markdown [CITED: mintlify.com/docs/llms-full.txt]. Mintlify also publishes `/.well-known/llms-full.txt` [CITED: mintlify.com/docs/ai/llmstxt]. Optional here.
**Recommended outputs:**
- **URL scheme:** `docs/<section>/<slug>.md` becomes `/<section>/<slug>.html` plus `/<section>/<slug>.md` (the llmstxt "extension replaced" form). The landing page is `/index.html` plus `/index.md`. This works on every static host and over `file://`, with no rewrite rules. Wording note: D-06 says "page URL plus .md". The `.html`-to-`.md` sibling form is the spec's equivalent alternative. See Open Question 2.
- **Per-page `.md` = transformed source, not a byte copy:**
- drop the frontmatter;
- start with `# Title` and `> description`;
- rewrite internal links to sibling `.md` URLs;
- reduce fence info strings to the language word (strip `src=`);
- keep code bodies, which already equal the sources because of the sync test.
- **llms.txt:**
- `# SummerCMS`, then a `>` summary: the root README's first paragraph, reworded without application names.
- An "Important notes" bullet list: compiled plugins, Postgres only, headless with no frontend themes, Go 1.27.
- One `## <Section>` per sidebar section with `- [Title](<url>.md): <description>`.
- The API reference as its own `## API reference`.
- `## Optional` for the WinterCMS concept map? No. Keep the concept map in Setup. Use `Optional` for "Frontend and AJAX (not provided)".
- **llms-full.txt:** every page in sidebar order, in the Mintlify block format. Links inside it stay as they are in the `.md` pages.
- **Base URL:** `docs/site.yaml: base_url`, overridable by `--base-url`. When empty, emit root-relative URLs. The spec's examples use absolute URLs, so a real deployment should set it (hosting is deferred).
### Q4. Verified examples: mechanism choice (D-07)
**Pick: include by reference with a verbatim copy and a drift test (embedmd style).** It is one mechanism with three source forms:
````markdown
```go src=modules/bonfire/example_test.go#ExampleNewRoot
root, err := bonfire.NewRoot("acme", []bonfire.Command{hello}, os.Stdout)
...
// Output: hello
```
````
| src form | Extracts | Use |
|----------|----------|-----|
| `path` | whole file | small YAML, `fields.yaml`, `columns.yaml` |
| `path#Ident` | top-level Go declaration with its doc comment, verbatim bytes from `token.FileSet` offsets. **For `Example*` idents: the body, dedented, with its `// Output:` comment, as godoc shows it** | API examples, walkthrough types and functions |
| `path#region-name` | the lines between `// docs:start region-name` and `// docs:end region-name` (`#` comment markers in YAML), markers excluded, dedented | multi-declaration spans, parts of `plugin.go` |
**Rules the checker enforces:**
1. Every ```` ```go ```` fence in `docs/**/*.md` must carry `src=`. Non-Go content uses ```` ```text ````, `sh`, `yaml`, and so on. YAML fences *may* carry `src=`, and the walkthrough's YAML must.
2. `src` paths are repo-relative, cleaned, and must resolve inside the repo root (no `..` escape, no absolute paths). Go sources must sit **inside the root module**, so not `examples/hello/**` (a separate workspace module that root `go test ./...` skips, as the root README says) and not `admin/**`.
3. A referenced `Example*` must have an `// Output:` comment, so `go test` *runs* it, not just compiles it ("compiled and run", SC4). A referenced non-Example region must sit in a package directory that has `_test.go` files.
4. The fence body must equal the extracted text byte for byte after trailing-newline normalisation. On mismatch the test fails with a diff and the hint `run: summer docs:sync`.
5. A missing file, ident or region fails both the test and `docs:build` (D-07's "build fails if a referenced snippet is missing").
**Where the example sources live:**
- **API examples go in `modules/<name>/example_test.go`, `package <name>_test`, named after real identifiers** (`ExampleNewRoot`, `ExampleBus_Fire`, `ExampleConfig_Get`, and so on).
- Benefit (probed this session): `go vet`, which `go test` also runs, **rejects Example names that refer to unknown identifiers**. It printed `ExampleMissing refers to unknown identifier: Missing`, vet rc=1, and failed the test build. That is a free identifier check on every example, on top of the docs checker.
- These files add tests, not exported API, so no README change is needed under CLAUDE.md.
- **The walkthrough goes in `docs/examples/blog/…` as ordinary packages of the root module** (import path `git.golem15.com/golem15/summercms/docs/examples/blog`). They are covered by root `go vet ./...` and `go test ./...` automatically (root go.mod: `module git.golem15.com/golem15/summercms`, `ignore ./admin/node_modules` only [VERIFIED: go.mod:1,6]).
- Do **not** put `Example<Ident>` functions in a docs-only package. vet would flag them as unknown identifiers. Use `Example_suffix` (package-level, lowercase suffix) there. The probe accepted `Example_walkthrough`.
- Rejected alternative, extracting fenced Markdown and compiling it (txtar or a generated `_test.go`):
- Snippets must then be complete units or wrapped in templates, with import boilerplate.
- gopls rename and "find references" cannot see them.
- Compiler errors point to generated files.
- It duplicates the Example machinery Go already has.
**How the multi-file walkthrough stays verified.** `docs/examples/blog` mirrors the scaffold layout that `summer make:*` produces:
- `plugin.go`, `routes.go`;
- `models/post.go`, `models/post/fields.yaml`, `models/post/columns.yaml`;
- `updates/…`;
- `controllers/posts.go`, `controllers/posts/config_form.yaml`, `controllers/posts/config_list.yaml`;
- `console/…`, `lang/en/…`.
The admin YAML paths come from the scaffolder: `formRel := filepath.Join("controllers", snake, "config_form.yaml")`, `listRel := filepath.Join("controllers", snake, "config_list.yaml")`, `fieldsRel := filepath.Join("models", snake, "fields.yaml")`, `columnsRel := filepath.Join("models", snake, "columns.yaml")` [VERIFIED: internal/build/artifact.go:250-254].
Its tests:
- (a) `-short`-safe: activate the plugin with party and backpack, assemble routes with surf (route table assertions through `httptest`), build the console command with bonfire and run it against buffers, and compile the admin YAML through cabana if that can boot without a DB.
- (b) Docker (skipped under `-short`, like `lagoonDB`): migrate up, CRUD, `RollbackLast`, against testcontainers Postgres. Use a DB created with `TEMPLATE template0 ... LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL'`, the lagoon test idiom (modules/lagoon/postgres_test.go `dedicatedDB`). lagoon refuses any other locale.
- (c) A **scaffold-layout test**: run `build.MakePlugin`/`MakeModel`/`MakeMigration`/`MakeAdminController`/`MakeCommand` into a temp copy of `examples/hello`, as `cmd/summer`'s `TestMakeCommandsViaCLI` does with `copyHelloApp`. Assert that the relative file set matches the walkthrough tree, ignoring `go.mod`/`go.sum` and generated accessor files. That pins the walkthrough to what the scaffolder really emits.
Caveat: `summer make:*` requires the plugin directory to be its own module with a `go.mod` (`readModulePath(filepath.Join(pluginDir, "go.mod"))` in artifact.go). The in-root walkthrough package has none. Show its `go.mod` as a ```` ```text ```` block, derived from the scaffold test's output. See Open Question 3 for the nested-module alternative.
### Q5. Generator: extensions, highlighting, search, location, CLI naming
- **Extensions:** see Standard Stack. Only core goldmark and stdlib.
- **Frontmatter:** the file must start with `---\n`. Split at the next `\n---\n` and decode with `yaml.NewDecoder(r, yaml.DisallowUnknownField())` into:
```go
struct{ Title, Description, Section string; Order int }
```
Every field is required. `section` must equal the directory name, and `order` must be unique within the section. The body's first line must be `# <Title>` (readable on Gitea, D-01). The generator strips it.
- **Syntax highlighting:** a stdlib `go/scanner` tokenizer for `go` fences, emitting `<span class="tok-kw|tok-str|tok-com|tok-num">`. Use `scanner.ScanComments`. It works on fragments because no parse is needed. `yaml` and `sh` get a line-regex highlighter (keys, comments, `$` prompts), or none. Everything goes through `html.EscapeString`. No dependency.
- **Search:**
- `search-index.json` with one entry per page section (H2), so hits deep-link to anchors: `{"p":[{"u":"/plugins/registration.html","t":"Registration","s":"Plugins"}],"e":[{"p":0,"a":"capabilities","h":"Capabilities","x":"<plain text, ~300 chars>"}]}`.
- Vanilla JS lazy-fetches the index on first focus or ⌘K, does lowercase AND-token matching with a title > heading > text boost, and renders with `textContent` only.
- Expected size is roughly 60 guide pages plus 21 READMEs, a few hundred KB. Fine without a prebuilt inverted index.
- **Package location:** use **`internal/docsite`** (package `docsite`). It follows the precedent of tool internals (`internal/build`, `internal/dev`) that `cmd/summer` calls. Beach names are used only for public framework modules under `modules/`. A module would force a README, a root-table row and a public API commitment for something host apps don't use yet (a host-app docs skill is deferred). Avoid `internal/docs`, which is easy to confuse with the `docs/` content directory. If the user later wants it public, a beach name such as `lighthouse` [ASSUMED name suggestion] can wrap it.
- **Theme assets:** `internal/docsite/theme/{templates/*.html, assets/site.css, assets/site.js, assets/theme-init.js, assets/fonts/*}`, embedded with `//go:embed`. Output is `site/` by default, or `--out`. Add `/site/` to `.gitignore`: the current `.gitignore` has `/bin/`, `/dist/` and the rest, but no docs output [VERIFIED: .gitignore:1-27].
- **CLI naming:** bonfire accepts only the bare names `"build", "dev", "serve", "migrate"`, otherwise `ns, verb, ok := strings.Cut(name, ":")` / `return ok && ns != "" && verb != "" && !strings.Contains(verb, " ")` [VERIFIED: modules/bonfire/command.go:110-117]. The existing tool commands follow `namespace:verb`: `make:plugin`, `plugin:add`, `parity:proxy`, `parity:record`, `parity:replay`, `migrate:status` [VERIFIED: cmd/summer/main.go:27-46; cmd/summer/parity.go:15,32,54].
- **Use `docs:build`, `docs:serve` and `docs:sync`**, registered in `toolCommands()` in cmd/summer/main.go, not in app binaries.
- `docs:serve` does not collide with the bare `serve` delegate (main.go:44 reads `delegateCommand("serve", "Run the app HTTP server")`).
- Flags are string flags (bonfire `Flag`): `--src` (default `docs`), `--out` (default `site`), `--base-url`, `--addr` (default a loopback address, e.g. `127.0.0.1:8088`, at our discretion), `--check` (bare: validate without writing).
- Extend `TestToolCommandNames` in cmd/summer/main_test.go with the three names.
- **Config file `docs/site.yaml`:**
```yaml
title: SummerCMS
base_url: ""
edit_url: "https://git.golem15.com/golem15/summercms/_edit/master/{path}" # [ASSUMED] Gitea edit URL shape
source_url: "https://git.golem15.com/golem15/summercms/src/branch/master/{path}" # [ASSUMED]
sections: [setup, architecture, plugins, backend, database, services, console, api] # sidebar order, with display titles
```
Decode it strictly. Section display titles live here so the sidebar order is data.
- **The walker excludes** `docs/examples/**` (Go and YAML sources, not pages), `docs/site.yaml`, and any `_`-prefixed file or directory.
### Q6. Identifier checker and link/anchor checker (D-12)
**Identifier checker (stdlib, prototype-validated):**
- **Where it looks:** inline code spans (`ast.CodeSpan`) in `docs/**/*.md` **and** `modules/*/README.md` (and the root `README.md`). Fenced blocks are skipped, since compilation already verifies them.
- **Span grammar:**
```
^\*?(<pkg>)\.(<Ident>)(\.<Member>)?(\[[^\]]*\])?(\(.*\))?$
```
`<pkg>` is one of the discovered module names (the directory names under `modules/`, discovered live, so Phase 11 modules are included). A leading `*` and trailing generic args or a call suffix are allowed. That covers README forms like `` `compass.Load("config")` `` and `` `surf.Assemble(app, plugins)` ``. Spans whose first segment is not a module name are ignored: `http.Handler`, `fields.yaml`, `acme.blog`, `summer.yaml`, `context.Background()`. That is the main false-positive guard.
- **Index:** `go/parser.ParseFile` over each module directory's non-test `.go` files, recording:
- top-level funcs, types, consts and vars;
- methods keyed by receiver base type (strip `*`, `IndexExpr`, `IndexListExpr` for generics);
- struct fields, including embedded names;
- interface methods.
- **Fallback on miss:** shell out to `go doc ./modules/<pkg> <Ident>[.<Member>]` before failing. That catches promoted members through embedding without re-implementing type checking. It runs only on misses, so it stays cheap.
- **Sub-packages** (for example `lagoon/attach`): allow `attach.X` spans by also indexing `modules/<m>/<sub>/` dirs holding Go files, keyed by the last path element. If two sub-packages share a name, fail loudly and require the spelled-out form.
- **Evidence:** a throwaway prototype (about 100 lines, scratchpad, not committed) ran over the current READMEs and reported `checked=742 misses=0`.
- **Unexported / lowercase second segments** (`backpack.app`, config keys such as `mail.driver`) are ignored. Config-key checking is **out of scope** (Open Question 4).
**Link and anchor checker:**
- Walk `ast.Link` and `ast.Image` in every page (guides plus ingested READMEs).
- Classify each destination:
- `http(s)://` → allowed, not fetched (no network in tests).
- `#frag` → must match a heading ID in the same page.
- relative `*.md` (optionally `#frag`) → must resolve to a page in the tree or to an ingested `modules/<m>/README.md`, and `#frag` must exist in the target.
- `../../modules/<m>/README.md` → allowed (rewritten).
- Other repo paths (`.planning/**`, `examples/**`, source files) → **fail** in guide pages (they break on the site). Use a `source_url` shortcode or a full forge URL.
- `mailto:` → allowed.
- Anchors come from **the same `parser.IDs` implementation the renderer uses**. One function, so the site and the checker cannot disagree.
- **Lint:** no links or code spans inside headings. goldmark builds heading IDs from the raw last source line (atx_heading.go `generateAutoHeadingID` reads `lastLine.Value(reader.Source())`), so a link URL would leak into the ID.
**CLI command-name checker:**
- Parse (go/ast) every `bonfire.Command{Name: "<lit>"}` composite literal and every `delegateCommand("<lit>", …)` call under `modules/` and `cmd/summer`. Current names [VERIFIED: modules/lagoon/commands.go:19,32,53; keygen.go:15; modules/cabana/commands.go:21,35; modules/surf/routelist_command.go:17; cmd/summer/main.go:27-46; parity.go:15,32,54]:
- `migrate`, `migrate:rollback`, `migrate:status`, `key:generate`
- `admin:create`, `admin:reset-password`
- `route:list`
- the `make:*` family, `plugin:add`, `dev`, `build`
- `parity:proxy`, `parity:record`, `parity:replay`
- plus `serve`
- Any `summer <cmd>` or `./bin/<app> <cmd>` token in `sh` fences or code spans must be in that set.
**Forbidden-name check (D-11):**
- Case-insensitive `fonoteka|płytarium|plytarium` over the built outputs: every `.md`, `llms*.txt`, rendered HTML and search index. Checking the outputs covers text pulled in through src= snippets too.
- Currently clean: `grep -rniE` over README.md and modules/*/README.md returned nothing.
- The list is a constant in the test and gate script.
### Q7. Existing hygiene gates and this phase's gate
The pattern is `scripts/check-phase<N>.sh`:
- `set -euo pipefail`
- `refuse()`
- mode flags (`--self-test`, `--layout`, `--imports`, `--readmes`, `--status`, `--go`, `--all`)
- a `--self-test` that plants each violation in a scratch copy and asserts the matching refusal (`expect_refusal`)
- `run_go` running `go vet ./...` and `go test ./...`
(Read from scripts/check-phase10.2.sh.)
**Recommended `scripts/check-phase11.1.sh` modes:**
- `--preconditions`: the Phase 9, 10.1 and 11 packages exist. At minimum, the Phase 11 jobs/realtime/search module directories exist under `modules/` with READMEs, and `pact.HasSchedule` is declared. Today it is only a comment: capabilities.go:303-307 reads `// Future capability families are type-asserted when their first consumer` / `// packages exist:` / `// HasListeners` / `// HasSchedule` [VERIFIED: modules/pact/capabilities.go:303-307].
- `--deps`: `go.mod` direct requirements unchanged versus the phase base (SC2 "only new dependency is goldmark", here none).
- `--forbidden`: the D-11 grep over `docs/` and built output.
- `--docs`: `go run ./cmd/summer docs:build --out "$tmp"`, plus asserting that `index.html`, `llms.txt`, `llms-full.txt` and `search-index.json` exist, and that no `.go` file lands under output.
- `--claude`: CLAUDE.md's Documentation section contains the D-13 bullet.
- `--self-test`: plants a drifted snippet, an unknown identifier, a broken anchor, an unknown command, a forbidden name and a missing README, and asserts each is refused.
- `--go`: `go vet ./...`, `go test ./...`.
- `--all`.
Keep **all semantic checks in Go tests.** The script only orchestrates them and adds repo-level assertions (deps, CLAUDE.md, preconditions).
**CLAUDE.md edit (D-13 / DOCS-08).** Today the section has four bullets, ending `- Every identifier named in a README must exist in the package; check it with \`go doc ./modules/<name> <Identifier>\`.` [VERIFIED: CLAUDE.md:27-32]. Two changes:
- Add: "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 ./internal/docsite/...` checks identifiers, links, snippets and command names in `docs/` and in every module README."
- Amend the go doc bullet to name the automated checker.
It is an additive edit, in its own commit (a planning/docs-rules commit, separate from code).
### Q8. Sequencing risk and plan breakdown
**Risk:** 11.1 depends on Phase 11, which has not run. Several other phases also change surfaces the docs describe:
- **Phase 9** shows "In Progress" in ROADMAP. ADMIN-01..05 and AUTH-08 are still unchecked in REQUIREMENTS.
- **Phase 10.1** is planned but not executed. It changes cabana, pact and admin with partials, widgets, assets and toolbar actions.
The numeric execution order (9 → 10.1 → 11 → 11.1) means all of them should land first. Planning now is safe if the plans do three things:
1. **Never hard-code Phase 11 package or identifier names.** Phase 11's CONTEXT leaves names open ("ARCHITECTURE.md suggests `conga` for jobs"). Plans say "the jobs, realtime and search modules as shipped by Phase 11" and read them at execution time.
2. **Let the checkers enforce reality.** A page naming a nonexistent identifier or module fails `go test`, and the API reference is discovered dynamically. No stub pages that name future APIs.
3. **Put a `--preconditions` gate at the start of the content plans** that need those phases. If Phase 11 has not landed, those pages are simply not written. There are no "coming soon" stubs with identifiers, because stubs rot and would have to be special-cased in the checker.
**Recommended breakdown** (tracer first, lean, unit tests last). Present it at the plan-count checkpoint:
| # | Plan | Scope | Depends |
|---|------|-------|---------|
| 11.1-01 | **Tracer: generator core to every output** | `internal/docsite` load (frontmatter, site.yaml, walker), goldmark pipeline (GFM, custom IDs, transformers), README ingestion for **all** modules, minimal theme (sidebar + content), `.md` + `llms.txt` + `llms-full.txt` + `search-index.json` emission, the `src=` extractor and sync check, `summer docs:build` + `docs:sync`, `/site/` gitignored. Content: `docs/index.md`, `setup/installation.md` with **one** verified snippet from a new `modules/bonfire/example_test.go`. Smoke test: build the real tree into `t.TempDir()` | — |
| 11.1-02 | **Site UX and accuracy gates** | Full Winter-style theme per UI-SPEC (TOC, prev/next, edit link, View as Markdown, callouts, go/scanner highlighting, mobile nav, tri-state dark mode with a sync `theme-init.js`), search JS, `docs:serve`. Checkers: identifiers (docs + READMEs), links and anchors, CLI names, forbidden names, strict go-fence policy. `scripts/check-phase11.1.sh`. CLAUDE.md D-13 edit (separate commit) | 01 |
| 11.1-03 | **Framework content A** | Setup (Introduction, Installation, Configuration, **Coming from WinterCMS**), Architecture, Plugins, Console, plus `example_test.go` files for party, backpack, compass, festival, bonfire, phrasebook, towel | 02 |
| 11.1-04 | **Framework content B** (precondition: Phases 9, 10.1, 11 landed) | Database, Backend, Services (including jobs, realtime and search from Phase 11), the "Frontend and AJAX (not provided)" page, `example_test.go` files for lagoon, wire, surf, postcard, bouncer, fetchguard, cabana, and the Phase 11 modules | 03 |
| 11.1-05 | **acme/blog porting walkthrough** | `docs/examples/blog` plugin (models, updates, routes, admin controller + YAML, console command, lang), its `-short` and Docker tests, the scaffold-layout test, and `setup/porting-a-plugin.md` referencing all of it by `src=` | 02 (and 04 if it shows Phase 11 features, otherwise parallel with 03/04) |
| 11.1-06 | **Unit tests last** | Full coverage of `internal/docsite` (parser, IDs, extractor, every checker's failure path with planted fixtures, llms formats, search index, `docs:serve` handler), gate `--self-test`, assembled acceptance per SC | all |
Lean alternative with 5 plans: merge 03 and 04 into one content plan. It is large (about 45 guide pages), and its precondition is then Phase 11 for all content. Prefer 6 if per-plan context is a concern.
### Q9. Inputs for the UI-SPEC (/gsd-ui-phase)
- **Winter features to mirror:** listed in the Q1 include/omit table.
- **Admin design tokens to reuse as plain CSS custom properties.** No Tailwind: the admin compiles Tailwind v4 through npm (`@import "tailwindcss"`), which the docs cannot use.
- Light values [VERIFIED: admin/src/styles/main.css:83-106]: `--c-bg: #f4f6f9;` `--c-surface: #ffffff;` `--c-subtle: #f3f5f8;` `--c-border: #e6e9ef;` `--c-text: #141b2d;` `--c-muted: #566175;` `--c-primary: #22304d;` `--c-ring: rgba(252, 196, 40, 0.55);` `--c-sel: #fdf3cf;` `--c-side: #1d2740;`
- Dark overrides under `.dark` [VERIFIED: main.css:108-125]: `--c-bg: #111726;` `--c-surface: #182033;` `--c-subtle: #1f283d;` `--c-border: #29334b;` `--c-text: #eef1f6;` `--c-muted: #a9b3c6;` `--c-primary: #fcd34d;`
- Brand accent `#fcd34d`, sidebar text `#c3cbda`, radii (control 10px, card 16px, pill 999px) and spacing (header 64px, panel 224px) come from the same `@theme` block (main.css:24-81, as read via `sed`).
- Fonts: `--font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;` and `--font-mono: "DM Mono", ui-monospace, monospace;` [VERIFIED: main.css:25-26].
- Design rationale lives in `.planning/phases/10-admin-vue-spa/design/README.md`: navy plus sunny yellow, and a sidebar that stays dark in both modes.
- **Fonts:** the admin gets them from `@fontsource` npm packages. The built woff2 files in `modules/boardwalk/dist/assets/` have hash-suffixed names such as `dm-sans-latin-400-normal-CW0RaeGs.woff2`, so do not reference them. Either vendor a small set (DM Sans 400/600/700 and DM Mono 400, latin and latin-ext) into `internal/docsite/theme/assets/fonts/` with the OFL licence file (DM fonts are SIL OFL 1.1 [ASSUMED]), or use the system stack. The UI-SPEC decides.
- **Dark mode:** the class strategy is the same as the admin (`.dark` on `<html>`, CSS variables swap). The difference is a tri-state toggle (light/dark/system) persisted in `localStorage`, applied by a synchronous external `theme-init.js` in `<head>` to avoid a flash of unthemed content. Use an external file rather than an inline script so the site keeps working under a `script-src 'self'` CSP, consistent with Phase 10.1 D-16.
- **Callouts:** use GitHub-style `> [!NOTE]`, `> [!WARNING]` and `> [!TIP]` in source, detected by an AST transformer. Gitea renders these [ASSUMED], and Winter's `> **NOTE:**` style also stays readable.
## Architecture Patterns
### System architecture diagram
```text
docs/**/*.md ──┐ modules/*/README.md ──┐ docs/site.yaml
(frontmatter) │ (ingested as api/*) │ (sections, urls)
v v │
┌─────────────────────────── Load ───────────────┴──┐
│ walk + strict frontmatter + H1==title + order │
└───────────────┬───────────────────────────────────┘
v
┌──── Parse (goldmark: GFM, custom IDs) ────┐
│ AST transformers: link rewrite, callouts, │
│ H1 strip, fence src= resolution │
└───┬───────────────┬───────────────────┬────┘
│ │ │
v v v
┌── Verify ──────┐ ┌─ Render ─────────┐ ┌─ Emit AI/search ─────┐
│ snippet==src │ │ html/template: │ │ page.md (clean) │
│ identifiers │ │ sidebar, TOC, │ │ llms.txt │
│ links/anchors │ │ prev/next, edit, │ │ llms-full.txt │
│ CLI names │ │ go/scanner hl │ │ search-index.json │
│ forbidden names│ └────────┬──────────┘ └──────────┬───────────┘
└──────┬─────────┘ v v
│ errors → fail site/**.html + embedded theme assets ──> docs:serve (loopback)
v
go test ./... (same Verify functions over the real tree)
^
modules/*/example_test.go (// Output:) ─ run by go test, names checked by go vet
docs/examples/blog/** ─ compiled + tested in root module, referenced by src=
```
### Recommended structure
```text
docs/
├── site.yaml # title, base_url, edit/source URL templates, section order
├── index.md # landing
├── setup/ # introduction, installation, configuration, coming-from-wintercms, porting-a-plugin
├── architecture/ # introduction, go-modules-and-workspaces, application-lifecycle, request-lifecycle
├── plugins/ # registration, migrations, configuration-and-settings, localization, scheduling, extending, testing
├── backend/ # admin-controllers, forms, lists, relations, users-and-permissions, partials-and-widgets, admin-spa
├── database/ # connection, migrations, queries, models, relations, attachments, casts, serialization, validation
├── services/ # container, events, routing, rate-limiting, responses, authentication, oauth-server, mail, storage, hashing, outbound-http, jobs, realtime, search, parity-testing, frontend-and-ajax
├── console/ # introduction, setup-and-maintenance, plugin-management, scaffolding, writing-commands, utilities
└── examples/blog/ # NOT pages: the verified acme/blog plugin (Go + YAML + tests)
internal/docsite/
├── load.go parse.go ids.go render.go llms.go search.go snippet.go
├── check_identifiers.go check_links.go check_commands.go check_forbidden.go
├── serve.go
├── theme/ (templates/, assets/) # //go:embed
└── *_test.go + testdata/ (planted-violation fixtures)
cmd/summer/docs.go # docs:build, docs:serve, docs:sync → internal/docsite
modules/<m>/example_test.go # package <m>_test, Example<Ident> with // Output:
scripts/check-phase11.1.sh
```
### Pattern: one verify function, two callers
`docsite.Check(root) []Problem` is called by `docs:build` (which refuses to write when problems exist) and by `TestDocsTree` in `internal/docsite`, which runs on `../../docs` and `../../modules`. Problems carry `file:line: rule: message` so agents can fix them mechanically.
### Anti-patterns to avoid
- **Hand-written ```` ```go ```` without `src=`.** It is unverified by construction. The checker rejects it.
- **Examples in `examples/hello`.** Root `go test ./...` skips that workspace module, so it does not satisfy SC4.
- **`Example<RealIdent>` in a docs-only package.** vet fails with "refers to unknown identifier".
- **Generating the site into `docs/`.** The walker would then read its own output. Use `site/` and gitignore it.
- **A second heading-ID algorithm in the checker.** Share the renderer's `parser.IDs`.
- **Rendering search results with `innerHTML`.** Use `textContent`, as with Phase 10 T-10-16 hygiene.
## Don't Hand-Roll
| Problem | Don't build | Use instead | Why |
|---------|-------------|-------------|-----|
| Markdown parsing, GFM tables | a regex Markdown renderer | goldmark (in tree) | CommonMark edge cases |
| YAML frontmatter and config | a hand parser | goccy/go-yaml with `DisallowUnknownField()` | Typos in frontmatter must fail |
| Go tokenizing for highlighting | regex over Go | `go/scanner` | Raw strings, runes, comments and nesting are handled exactly |
| Example discovery and "is it runnable" | a custom test runner | Go `Example` functions with `// Output:` (and `go/doc.Examples` if you need Example metadata) | go test runs them, and vet validates names |
| Promoted-member resolution in the identifier checker | a type checker | `go doc` shell-out only on AST miss | Authoritative, rare path |
| HTML escaping in chrome | string concatenation | `html/template` | Contextual escaping |
## Common Pitfalls
### Pitfall 1: Snippet drift invisible on the git host
**What goes wrong:** the Markdown shows old code that still "looks right". **Why:** copies are static. **Avoid:** a byte-exact sync test plus `docs:sync`, and a build that refuses. **Warning sign:** a PR touching `modules/*/example_test.go` without touching `docs/`. The test catches it.
### Pitfall 2: SC4 silently unmet by `-short`
**What goes wrong:** walkthrough DB tests skip under `-short`, so "run" becomes "compiled". **Avoid:** every docs-referenced Example has `// Output:` and needs no DB. DB-dependent walkthrough steps are Docker tests. Run the gate's `--go` **without** `-short` (Docker is available here: client 29.7.2).
### Pitfall 3: Heading anchors differ between goldmark, Gitea and GitHub
**What goes wrong:** the default goldmark generator drops every multibyte character and maps `_` to `-`. Code: `if l != 1 { continue }` … `} else if util.IsSpace(v) || v == '-' || v == '_' { result = append(result, '-') }`, and duplicates get `-1`, `-2` [VERIFIED: goldmark parser/parser.go:99-138]. GitHub keeps `_` and Unicode letters [ASSUMED]. **Avoid:** implement `parser.IDs` with a GitHub-compatible slug (lowercase, keep letters/digits/`_`/`-`, spaces to `-`, drop other punctuation, `-N` dedupe), passed through `parser.WithIDs`. Keep headings ASCII, without links or code, and use the same IDs in the checker.
### Pitfall 4: Consuming-application names leaking through source snippets
**What goes wrong:** a `src=` region in wristband carries comments naming the application (server.go:63-64, stores.go:16-17). **Avoid:** run the forbidden-name check on *built output*, not just source Markdown. Choose regions that exclude those comments.
### Pitfall 5: `file://` breaks search
**What goes wrong:** browsers block `fetch()` of a JSON file from a `file://` page [ASSUMED standard browser behaviour]. **Avoid:** document `summer docs:serve` for local preview. The search box shows a "search needs a web server" hint when the fetch fails. Everything else works over `file://` because of the `.html` URLs.
### Pitfall 6: Checker false positives on non-API spans
**What goes wrong:** spans like `` `fields.yaml` ``, `` `acme.blog` ``, `` `summer.yaml` `` look like `pkg.Ident`. **Avoid:** match only when the first segment is a discovered module name **and** the second starts uppercase. That rule gave 0 misses on 742 real spans.
### Pitfall 7: New Phase 11 modules missing READMEs or API pages
**What goes wrong:** the sidebar misses a module, which violates SC1. **Avoid:** the dynamic module discovery test fails when any `modules/<m>/` with non-test `.go` files lacks a README.
### Pitfall 8: `go.mod` churn
**What goes wrong:** someone adds chroma or x/tools "just for the checker". **Avoid:** the gate's `--deps` compares direct requirements with the phase base commit.
## Code Examples
### Custom heading IDs wired into goldmark
```go
// Source: goldmark parser.WithIDs (parser/parser.go:240-244), IDs interface (81-87)
md := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
)
ctx := parser.NewContext(parser.WithIDs(newSlugIDs())) // same type the link checker uses
doc := md.Parser().Parse(text.NewReader(src), parser.WithContext(ctx))
```
### Fence reference resolution
```go
// Source: goldmark ast.FencedCodeBlock.Language / Info (ast/block.go:302-321)
if fc, ok := n.(*ast.FencedCodeBlock); ok {
lang := string(fc.Language(src))
info := string(fc.Info.Segment.Value(src)) // e.g. `go src=modules/bonfire/example_test.go#ExampleNewRoot`
ref, hasRef := parseSrc(info) // strings.Fields + "src=" prefix
_ = lang; _ = ref; _ = hasRef
}
```
### Example that vet and test both verify
```go
// modules/festival/example_test.go — illustrative; vet rejects the name if Bus.Fire disappears.
package festival_test
func ExampleBus_Fire() {
// ... construct a festival.Bus, Listen, Fire, print deterministic output ...
// Output: ...
}
```
(`festival.Bus.Fire` exists: `func (b *Bus) Fire[T any](ctx context.Context, event T) error`, per `go doc -all ./modules/festival` this session. Note that it is a generic **method**, a Go 1.27 feature, so the Example naming `ExampleBus_Fire` follows the method rule.)
## State of the Art
| Old approach | Current approach | Impact |
|--------------|------------------|--------|
| HTML-only docs | llms.txt index + per-page `.md` + llms-full.txt | Agents read without scraping. The format is settled enough (llmstxt.org spec; Mintlify's llms-full convention) |
| `> **NOTE:**` callouts | `> [!NOTE]` alerts (GitHub, and Gitea [ASSUMED]) | Rendered natively on forges and by our transformer |
| Manual `go doc` spot checks for README identifiers | An automated AST checker in `go test` | Replaces the CLAUDE.md manual rule's mechanics |
## Assumptions Log
| # | Claim | Section | Risk if wrong |
|---|-------|---------|---------------|
| A1 | The Gitea edit URL is `/<owner>/<repo>/_edit/<branch>/<path>` and the view URL is `/src/branch/<branch>/<path>` | Q5 site.yaml | Edit links 404. Config-only fix |
| A2 | Gitea renders `> [!NOTE]` alerts and ```` ```go src=... ```` fences with Go highlighting (language = first word) | Q9, Q4 | The source reads slightly worse on Gitea. Site unaffected |
| A3 | GitHub/Gitea slug rules keep `_` and Unicode letters | Pitfall 3 | Anchors from the git host differ on non-ASCII headings. Mitigated by the ASCII-heading lint |
| A4 | DM Sans and DM Mono are SIL OFL 1.1 and can be vendored with the licence file | Q9 | Use the system font stack instead |
| A5 | `fetch()` of local JSON fails over `file://` in major browsers | Pitfall 5 | None if wrong (search then works over file:// too) |
| A6 | cabana can compile a plugin's admin YAML at boot without a database connection | Q4 walkthrough (a) | That assertion moves to the Docker test tier |
| A7 | `lighthouse` as a beach name if the generator is ever made public | Q5 | Naming only |
| A8 | The master branch name for edit links is `master` | Q5 | Matches the current git branch (`master`). Config-only |
## Open Questions (for the plan-count checkpoint)
1. **Syntax highlighting:** is stdlib Go-only highlighting (plus a trivial YAML/sh highlighter) enough, or approve `alecthomas/chroma/v2`?
- Recommendation: stdlib, no new dependency.
2. **URL form for raw pages:** D-06 says "page URL plus .md". The recommendation is `/x/page.html` with `/x/page.md` (the llmstxt "extension replaced" form, portable to any host and to `file://`). The literal alternative, `/x/page.html.md`, is also spec-compliant but uglier.
- Recommendation: `.html`/`.md` siblings. Confirm.
3. **Walkthrough as an in-root package vs a nested plugin module:** in the root module, `go test ./...` covers it for free. A nested module (own `go.mod`, in `go.work`, like `examples/hello/plugins/*`) is truer to real plugins, but root `go test ./...` skips it, so a root test would have to shell out to `go test` in that dir.
- Recommendation: in-root package plus the scaffold-layout test.
4. **Config-key checking** (D-13 mentions config keys): out of scope for the checker in this phase? Checking `mail.driver`-style spans against embedded default YAML is feasible later.
- Recommendation: defer and note it in CLAUDE.md.
5. **The wristband default names the consuming application's URL** (server.go:100). Log it as a follow-up todo (a framework default should be empty or neutral). Do not change the API in this phase, per the phase boundary.
6. **Final package names for the Phase 11 modules** are unknown until Phase 11 executes. The content plan for Services reads them at execution time.
7. **Plan count:** 6 plans (recommended) or 5 (merged content).
## Environment Availability
| Dependency | Required by | Available | Version | Fallback |
|------------|-------------|-----------|---------|----------|
| Go toolchain | everything | ✓ | go1.27.0 linux/amd64 | — |
| git | edit links, gate `--deps` diff | ✓ | 2.55.0 | — |
| Docker | walkthrough DB tests, lagoon tests | ✓ | client 29.7.2 | `-short` skips DB tests (SC4 then partially unverified. The gate runs without `-short`) |
| Node.js | **not required** | — | — | — (the docs must not need it) |
| Network | none at test time | — | — | Link checker never fetches external URLs |
`go vet ./...` is currently green (rc 0, about 0.9 s). Nothing blocks execution.
## Validation Architecture
### Test framework
| Property | Value |
|----------|-------|
| Framework | Go stdlib `testing` (the repo uses no testify in tests: `grep` found 0 `_test.go` importing it) |
| Config file | none. Tests locate `docs/` and `modules/` through `../../` from `internal/docsite` |
| Quick run command | `go test -short ./internal/docsite/... ./cmd/summer/... ./docs/examples/...` |
| Full suite command | `go vet ./... && go test ./...` (Docker required for the walkthrough and lagoon DB tests) |
| Phase gate | `scripts/check-phase11.1.sh --all` |
### Phase requirements → test map
| Req | Behaviour | Type | Command | Exists? |
|-----|-----------|------|---------|---------|
| DOCS-01 / SC1 | Strict frontmatter, section == dir, unique order, H1 == title, allowed sections, every module has a README and a sidebar entry | unit (real tree) | `go test ./internal/docsite -run 'TestContentTree|TestEveryModuleInSidebar'` | ❌ Wave 0 |
| DOCS-02 / SC2 | Build writes html for every page with sidebar, TOC, prev/next, edit link, search input and theme toggle markers. Assets embedded. No `node`/`npm` invoked | unit + smoke | `go test ./internal/docsite -run TestBuildSite`; `go test ./cmd/summer -run TestToolCommandNames` | ❌ / extend existing |
| DOCS-02 / SC2 | `go.mod` direct requires unchanged | gate | `scripts/check-phase11.1.sh --deps` | ❌ |
| DOCS-02 / SC2 | Search works and dark mode toggles visually | **manual** (no JS runner without Node) | UAT in `/gsd-verify-work` via `summer docs:serve` | manual |
| DOCS-03 / SC3 | The sets of nav pages, `*.html`, `*.md`, llms.txt links and llms-full `Source:` lines are equal and in sidebar order. llms.txt matches the spec shape (H1, blockquote, H2 lists) | unit | `go test ./internal/docsite -run TestAIOutputsInSync` | ❌ |
| DOCS-04 / SC4 | Every go fence has src=. Bodies equal the sources. Referenced Examples have Output. Missing ref → error | unit (real tree + planted fixtures) | `go test ./internal/docsite -run 'TestSnippets'` | ❌ |
| DOCS-04 / SC4 | Examples compile and run, names valid | existing toolchain | `go test ./modules/...` (Examples run), `go vet ./...` | ❌ (no examples yet) |
| DOCS-05 / SC4 | Identifier, link/anchor, CLI-name and forbidden-name checkers fail on planted violations and pass on the real tree (docs + READMEs) | unit | `go test ./internal/docsite -run 'TestIdentifiers|TestLinks|TestCommands|TestForbidden'` | ❌ |
| DOCS-06 / SC5 | The concept-map page exists and its identifiers pass | unit | covered by `TestContentTree` (required page list) + `TestIdentifiers` | ❌ |
| DOCS-07 / SC5 | Walkthrough plugin compiles, registers and serves routes, the command runs, migrations go up and down, and the scaffold layout matches | unit + Docker integration | `go test ./docs/examples/...` (full) / `-short` | ❌ |
| DOCS-08 | CLAUDE.md contains the D-13 rule | gate | `scripts/check-phase11.1.sh --claude` | ❌ |
### Sampling rate
- **Per task commit:** `go vet ./... && go test -short ./...`
- **Per plan merge:** `go test ./...` (with Docker) plus `scripts/check-phase11.1.sh --docs --forbidden`
- **Phase gate:** `scripts/check-phase11.1.sh --all` green before `/gsd-verify-work`, then manual UAT of search and dark mode in `docs:serve`
### Wave 0 gaps
- [ ] `internal/docsite/` package with test scaffolding and a `testdata/` planted-violation corpus
- [ ] `modules/bonfire/example_test.go` (first verified snippet, tracer)
- [ ] `docs/site.yaml`, `docs/index.md`, `docs/setup/installation.md`
- [ ] `cmd/summer/docs.go` + `TestToolCommandNames` extension
- [ ] `scripts/check-phase11.1.sh` with `--self-test`
- [ ] `/site/` in `.gitignore`
## Security Domain
`security_enforcement` is absent from config, so it counts as enabled. The surface is small: a local build tool, a loopback preview server and static output.
| ASVS category | Applies | Control |
|---------------|---------|---------|
| V2 Authentication / V3 Session / V4 Access control | no | Static site, no auth |
| V5 Input validation | yes | Strict YAML (`DisallowUnknownField`). `src=` paths cleaned and confined to the repo root (reject `..`, absolute paths, symlink escapes via `filepath.EvalSymlinks`). Raw HTML disabled in goldmark |
| V12 Files and resources | yes | Output confined to `--out` (refuse `--out` equal to or inside `--src`, or the repo root). No writes outside it |
| V14 Configuration | yes | `docs:serve` binds loopback by default. Refuse non-loopback unless explicitly passed |
| V6 Cryptography | no | — |
| Threat | STRIDE | Mitigation |
|--------|--------|------------|
| T-11.1-01 Path traversal through `src=` pulls secrets (for example `.env`) into public docs | Information disclosure | Confine to the repo, root module only, deny dotfiles and `.env*`, and run the forbidden-content test on output |
| T-11.1-02 XSS through Markdown raw HTML or search results | Tampering | goldmark safe mode (no `WithUnsafe`), html/template chrome, search rendering with `textContent` |
| T-11.1-03 `docs:serve` exposed on the LAN | Information disclosure | Default `127.0.0.1`. Warn and require an explicit flag for other hosts |
| T-11.1-04 Consuming-application details published in framework docs | Information disclosure (policy) | The D-11 forbidden-name test over built output |
| T-11.1-05 `docs:build --out` deletes the wrong directory when cleaning | Tampering | Only remove the contents of an output directory that contains the generator's marker file (for example `.summer-docs`). Otherwise refuse |
## Sources
### Primary (HIGH: read or probed this session)
- `go.mod:1-30`, `.gitignore:1-27`, `CLAUDE.md:27-32`, `cmd/summer/main.go:27-46`, `cmd/summer/parity.go:12-57`, `modules/bonfire/command.go:108-119`, `modules/pact/capabilities.go:300-311`, `modules/postcard/templates.go:1-30`, `modules/wristband/server.go:56-105`, `modules/lagoon/commands.go:14-55`, `modules/lagoon/keygen.go:11-17`, `modules/cabana/commands.go:16-37`, `modules/surf/routelist_command.go:13-20`, `internal/build/artifact.go:240-255`, `admin/src/styles/main.css:21-26, 78-125`, `admin/src/app/theme.ts:1-6` (Read tool)
- goldmark v1.8.6 source in the module cache: `parser/parser.go:80-139, 238-245`, `parser/atx_heading.go:44-67`, `extension/gfm.go`, `ast/block.go:302-321`
- proxy.golang.org `@latest` and `.mod` for goldmark, goldmark-highlighting/v2, chroma/v2, goldmark-meta, goldmark/toc, x/tools
- Local probes: `go vet` Example-name check (scratch module), identifier-checker prototype (742 spans, 0 misses), `go doc` behaviour on members and misses, Docker/Go/git versions
### Secondary (CITED: official pages fetched; the seam rates webfetch LOW)
- https://wintercms.com/docs/v1.2/docs/setup/installation and /database/model (sidebar tree, anatomy)
- https://raw.githubusercontent.com/wintercms/docs/develop/plugin/registration.md (source conventions, tone)
- https://llmstxt.org/ (llms.txt format, `.md` URL rule)
- https://www.mintlify.com/docs/llms-full.txt, https://www.mintlify.com/docs/ai/llmstxt (llms-full convention)
### Tertiary (LOW)
- Gitea URL shapes and alert rendering (A1, A2), font licences (A4)
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH. No new dependencies, and every API used was read in the module cache.
- Architecture: HIGH for the mechanisms (probed). MEDIUM for the page list, which is a judgment call inside Claude's discretion.
- Pitfalls: HIGH for 1, 2, 3, 4, 6, 7, 8 (grounded in code or probes). MEDIUM for 5.
**Research date:** 2026-09-28
**Valid until:** about 2026-10-28 for the stack. The content inventory must be re-read when 11.1 executes, after Phases 9, 10.1 and 11 land.