Files
summercms/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-RESEARCH.md
2026-09-28 18:20:42 +02:00

75 KiB
Raw Blame History

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.
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:

```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:

    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:

    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/ `.` [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

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=
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.

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

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

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

// 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'`
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
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)

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.